Skip to content
This repository was archived by the owner on Jun 19, 2024. It is now read-only.

Commit fe14b0b

Browse files
authored
Merge pull request #24 from LabyMod/develop
Fix issues with member access
2 parents 50d1338 + d415dd8 commit fe14b0b

File tree

5 files changed

+122
-38
lines changed

5 files changed

+122
-38
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
GNU LESSER GENERAL PUBLIC LICENSE
22
Version 3, 29 June 2007
33

4-
Copyright (C) 2007 LabyMedia. <https://labymedia.com/>
4+
Copyright (C) 2020 LabyMedia GmbH. <https://labymedia.com/>
55
Everyone is permitted to copy and distribute verbatim copies
66
of this license document, but changing it is not allowed.
77

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.2
1+
0.3.3

ultralight-java-databind/src/main/java/com/labymedia/ultralight/Databind.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public JavascriptClass toJavascript(Class<?> javaClass, boolean ignoreAutomaticP
7474

7575
javascriptClass = classCache.put(
7676
javaClass.getName(),
77-
DatabindJavascriptClass.create(configuration, conversionUtils, javaClass)
77+
DatabindJavascriptClass.create(configuration, conversionUtils, javaClass, classCache)
7878
.bake());
7979
}
8080

ultralight-java-databind/src/main/java/com/labymedia/ultralight/DatabindJavascriptClass.java

Lines changed: 115 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@
1919

2020
package com.labymedia.ultralight;
2121

22+
import com.labymedia.ultralight.cache.JavascriptClassCache;
2223
import com.labymedia.ultralight.call.CallData;
2324
import com.labymedia.ultralight.javascript.*;
2425
import com.labymedia.ultralight.javascript.interop.JavascriptInteropException;
2526
import com.labymedia.ultralight.call.MethodChooser;
26-
import com.labymedia.ultralight.javascript.*;
2727
import com.labymedia.ultralight.utils.JavascriptConversionUtils;
2828

29+
import java.lang.invoke.MethodHandles;
2930
import java.lang.reflect.*;
3031
import java.util.*;
3132

@@ -51,9 +52,14 @@ public final class DatabindJavascriptClass {
5152
* @param className The name of the Javascript class
5253
*/
5354
private DatabindJavascriptClass(
54-
DatabindConfiguration configuration, JavascriptConversionUtils conversionUtils, String className) {
55+
DatabindConfiguration configuration,
56+
JavascriptConversionUtils conversionUtils,
57+
String className,
58+
JavascriptClass parentClass
59+
) {
5560
this.definition = new JavascriptClassDefinition()
5661
.name(className)
62+
.parentClass(parentClass)
5763
.attributes(JavascriptClassAttributes.NO_AUTOMATIC_PROTOTYPE);
5864

5965
this.configuration = configuration;
@@ -76,16 +82,16 @@ private void registerCallbacks() {
7682
*
7783
* @param constructors The constructors to index
7884
*/
79-
private void addConstructors(Constructor<?>... constructors) {
80-
this.constructors.addAll(Arrays.asList(constructors));
85+
private void addConstructors(Collection<Constructor<?>> constructors) {
86+
this.constructors.addAll(constructors);
8187
}
8288

8389
/**
8490
* Indexes methods into this class.
8591
*
8692
* @param methods The methods to index
8793
*/
88-
private void addMethods(Method... methods) {
94+
private void addMethods(Collection<Method> methods) {
8995
for (Method method : methods) {
9096
String name = method.getName();
9197

@@ -108,7 +114,7 @@ private void addMethods(Method... methods) {
108114
*
109115
* @param fields The fields to index
110116
*/
111-
private void addFields(Field... fields) {
117+
private void addFields(Collection<Field> fields) {
112118
for (Field field : fields) {
113119
this.fields.put(field.getName(), field);
114120
}
@@ -146,20 +152,20 @@ private JavascriptObject onCallAsConstructor(
146152
try {
147153
// Invoke constructor with constructed arguments
148154
return context.makeObject(bake(), new Data(method.newInstance(parameters.toArray()), null));
149-
} catch (IllegalAccessException exception) {
155+
} catch(IllegalAccessException exception) {
150156
throw new JavascriptInteropException("Unable to access constructor: " + method.getName(), exception);
151-
} catch (InvocationTargetException exception) {
157+
} catch(InvocationTargetException exception) {
152158
throw new JavascriptInteropException("Constructor threw an exception", exception);
153-
} catch (InstantiationException exception) {
159+
} catch(InstantiationException exception) {
154160
throw new JavascriptInteropException("Unable to create instance", exception);
155161
}
156162
}
157163

158164
/**
159165
* Determines whether a property exists.
160166
*
161-
* @param context The context the check is executed in
162-
* @param object The object to check for the property on
167+
* @param context The context the check is executed in
168+
* @param object The object to check for the property on
163169
* @param propertyName The name of the property to check for
164170
* @return {@code true} if the property could be found, {@code false} otherwise
165171
*/
@@ -168,23 +174,23 @@ private boolean onHasProperty(JavascriptContext context, JavascriptObject object
168174
boolean instanceAvailable = ((Data) object.getPrivate()).instance != null;
169175

170176
Field f = fields.get(propertyName);
171-
if(f != null && (Modifier.isStatic(f.getModifiers()) || instanceAvailable)) {
177+
if (f != null && (Modifier.isStatic(f.getModifiers()) || instanceAvailable)) {
172178
// Field found and usable
173179
return true;
174180
}
175181

176182
Set<Method> methodsWithName = methods.get(propertyName);
177-
if(methodsWithName == null || methodsWithName.isEmpty()) {
183+
if (methodsWithName == null || methodsWithName.isEmpty()) {
178184
// No methods available with that name
179185
return false;
180-
} else if(instanceAvailable) {
186+
} else if (instanceAvailable) {
181187
// There is an instance and methods with this name are available
182188
return true;
183189
}
184190

185191
// There are methods with this name available, check if any of them is static
186-
for(Method method : methodsWithName) {
187-
if(Modifier.isStatic(method.getModifiers())) {
192+
for (Method method : methodsWithName) {
193+
if (Modifier.isStatic(method.getModifiers())) {
188194
// Found a static variant
189195
return true;
190196
}
@@ -211,15 +217,15 @@ private JavascriptValue onGetProperty(
211217
if (field != null) {
212218
try {
213219
return conversionUtils.toJavascript(context, field.get(privateData.instance), field.getType());
214-
} catch (IllegalAccessException exception) {
220+
} catch(IllegalAccessException exception) {
215221
throw new JavascriptInteropException("Unable to access field: " + field.getName(), exception);
216222
}
217223
}
218224

219225
Set<Method> methodSet = methods.get(propertyName);
220226
if (methodSet == null) {
221-
// Property does not exist
222-
return context.makeUndefined();
227+
// Property does not exist, delegate to parent
228+
return null;
223229
}
224230

225231
return context.makeObject(
@@ -253,8 +259,10 @@ private boolean onSetProperty(
253259
if (field != null) {
254260
try {
255261
field.set(privateData.instance, conversionUtils.fromJavascript(value, field.getType()));
256-
} catch (IllegalAccessException exception) {
257-
throw new JavascriptInteropException("Unable to access field: " + field.getName(), exception);
262+
return true;
263+
} catch(IllegalAccessException exception) {
264+
throw new JavascriptInteropException("Unable to access field: " + field.getName() +
265+
" (" + exception.getMessage() + ")", exception);
258266
}
259267
}
260268

@@ -280,30 +288,110 @@ public JavascriptClass bake() {
280288
* @param configuration The configuration to use
281289
* @param conversionUtils The conversion utilities to use for converting objects
282290
* @param javaClass The java class to create a binding for
291+
* @param classCache The class cache to retrieve or cache parent classes with
283292
* @return The created binding
284293
*/
285294
static DatabindJavascriptClass create(
286-
DatabindConfiguration configuration, JavascriptConversionUtils conversionUtils, Class<?> javaClass) {
295+
DatabindConfiguration configuration,
296+
JavascriptConversionUtils conversionUtils,
297+
Class<?> javaClass,
298+
JavascriptClassCache classCache
299+
) {
300+
Class<?> superClass = javaClass.getSuperclass();
301+
302+
JavascriptClass parentClass = null;
303+
if (superClass != null && javaClass != Object.class) {
304+
String parentClassName = superClass.getName();
305+
306+
if (!classCache.contains(parentClassName)) {
307+
DatabindJavascriptClass databindParent = create(
308+
configuration,
309+
conversionUtils,
310+
superClass,
311+
classCache
312+
);
313+
314+
parentClass = classCache.put(parentClassName, databindParent.bake());
315+
} else {
316+
parentClass = classCache.get(parentClassName);
317+
}
318+
}
319+
287320
DatabindJavascriptClass javascriptClass = new DatabindJavascriptClass(
288-
configuration, conversionUtils, javaClass.getName());
321+
configuration, conversionUtils, javaClass.getName(), parentClass);
289322

290323
javascriptClass.registerCallbacks();
291-
javascriptClass.addConstructors(javaClass.getDeclaredConstructors());
292-
javascriptClass.addMethods(javaClass.getDeclaredMethods());
293-
javascriptClass.addFields(javaClass.getDeclaredFields());
324+
325+
javascriptClass.addConstructors(filterAccessible(javaClass.getConstructors(), javaClass));
326+
javascriptClass.addMethods(filterAccessible(javaClass.getMethods(), javaClass));
327+
javascriptClass.addFields(filterAccessible(javaClass.getFields(), javaClass));
294328

295329
// Iteratively scan all interfaces
296330
Queue<Class<?>> toAdd = new LinkedList<>(Arrays.asList(javaClass.getInterfaces()));
297331
while (!toAdd.isEmpty()) {
298332
Class<?> iface = toAdd.remove();
299333
toAdd.addAll(Arrays.asList(iface.getInterfaces()));
300334

301-
javascriptClass.addMethods(iface.getMethods());
335+
javascriptClass.addMethods(filterAccessible(iface.getMethods(), iface));
336+
javascriptClass.addFields(filterAccessible(iface.getFields(), iface));
302337
}
303338

304339
return javascriptClass;
305340
}
306341

342+
/**
343+
* Filters an array of reflection objects by their accessibility.
344+
*
345+
* @param objects The objects to filter
346+
* @param owner The wanted owner class
347+
* @param <T> The type of the objects
348+
* @return The filtered objects
349+
*/
350+
private static <T extends AccessibleObject & Member> Set<T> filterAccessible(T[] objects, Class<?> owner) {
351+
Set<T> accessible = new HashSet<>();
352+
353+
for (T object : objects) {
354+
if (object.getDeclaringClass() != owner || !allPublic(object)) {
355+
continue;
356+
}
357+
358+
accessible.add(object);
359+
}
360+
361+
return accessible;
362+
}
363+
364+
/**
365+
* Determines if a members is accessible by checking if everything is public.
366+
*
367+
* @param member The member to check
368+
* @return {@code true} if the member is accessible using a public path, {@code false} otherwise
369+
*/
370+
private static boolean allPublic(Member member) {
371+
Class<?> classToCheck = null;
372+
373+
do {
374+
if (classToCheck == null) {
375+
if ((member.getModifiers() & Modifier.PUBLIC) == 0) {
376+
return false;
377+
}
378+
379+
classToCheck = member.getDeclaringClass();
380+
} else {
381+
if((classToCheck.getModifiers() & Modifier.PUBLIC) == 0) {
382+
return false;
383+
}
384+
385+
classToCheck = classToCheck.getSuperclass();
386+
if(classToCheck == Object.class) {
387+
return true;
388+
}
389+
}
390+
} while (classToCheck != null);
391+
392+
return true;
393+
}
394+
307395
/**
308396
* Represents the internal state of a Javascript class.
309397
*/

ultralight-java-native/src/main/c/src/platform/managed_javascript_class.cpp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ namespace ultralight_java {
198198

199199
if(!ret) {
200200
env->CallVoidMethod(java_lock, runtime.javascript_context_lock.unlock_method);
201-
*exception = Util::create_jserror(ctx, "Java function returned null");
202201
return nullptr;
203202
}
204203

@@ -245,9 +244,8 @@ namespace ultralight_java {
245244
java_property_name.get()));
246245

247246
} else {
248-
env->ThrowNew(
249-
runtime.illegal_argument_exception.clazz,
250-
("Tried to get non existent static property " + getter_index).c_str());
247+
env->CallVoidMethod(java_lock, runtime.javascript_context_lock.unlock_method);
248+
return nullptr;
251249
}
252250

253251
if(env->ExceptionCheck()) {
@@ -259,7 +257,6 @@ namespace ultralight_java {
259257

260258
if(!ret) {
261259
env->CallVoidMethod(java_lock, runtime.javascript_context_lock.unlock_method);
262-
*exception = Util::create_jserror(ctx, "Java function returned null");
263260
return nullptr;
264261
}
265262

@@ -356,9 +353,8 @@ namespace ultralight_java {
356353
java_property_name.get(),
357354
java_value.get());
358355
} else {
359-
env->ThrowNew(
360-
runtime.illegal_state_exception.clazz,
361-
("Tried to set non existent static property " + setter_index).c_str());
356+
env->CallVoidMethod(java_lock, runtime.javascript_context_lock.unlock_method);
357+
return false;
362358
}
363359

364360
if(env->ExceptionCheck()) {

0 commit comments

Comments
 (0)