Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ static Scriptable js_createAdapter(Context cx, Scriptable scope, Object[] args)
System.arraycopy(args, classCount + 1, ctorArgs, 2, argsCount);
// TODO: cache class wrapper?
NativeJavaClass classWrapper = new NativeJavaClass(scope, adapterClass, true);
NativeJavaMethod ctors = classWrapper.members.ctors;
NativeJavaMethod ctors = classWrapper.getMembers().ctors;
int index = ctors.findCachedFunction(cx, ctorArgs);
if (index < 0) {
throw Context.reportRuntimeErrorById(
Expand Down
51 changes: 39 additions & 12 deletions rhino/src/main/java/org/mozilla/javascript/JavaMembers.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,24 @@ boolean has(String name, boolean isStatic) {
return findExplicitFunction(name, isStatic) != null;
}

Object get(Scriptable scope, String name, Object javaObject, boolean isStatic) {
Map<String, Object> ht = isStatic ? staticMembers : members;
Object member = ht.get(name);
/**
* Get a member from a table of all reflected members
*
* <p>Unlike {@link #get(Scriptable, String, Object, boolean)}, this method will NOT 'use' the
* found member and return its result
*/
Object getMember(String name, boolean isStatic) {
var ht = isStatic ? staticMembers : members;
var member = ht.get(name);
if (!isStatic && member == null) {
// Try to get static member from instance (LC3)
member = staticMembers.get(name);
}
return member;
}

Object get(Scriptable scope, String name, Object javaObject, boolean isStatic) {
var member = getMember(name, isStatic);
if (member == null) {
member =
this.getExplicitFunction(
Expand Down Expand Up @@ -283,7 +294,7 @@ private Object getExplicitFunction(

if (member instanceof NativeJavaMethod
&& ((NativeJavaMethod) member).methods.length > 1) {
NativeJavaMethod fun = new NativeJavaMethod(methodOrCtor, name);
NativeJavaMethod fun = new NativeJavaMethod(cl, methodOrCtor, name);
fun.setPrototype(prototype);
ht.put(name, fun);
member = fun;
Expand Down Expand Up @@ -477,7 +488,7 @@ private void reflect(
methodBoxes[i] = new ExecutableBox(method, typeFactory, this.cl);
}
}
NativeJavaMethod fun = new NativeJavaMethod(methodBoxes);
NativeJavaMethod fun = new NativeJavaMethod(cl, methodBoxes, entry.getKey());
if (scope != null) {
ScriptRuntime.setFunctionProtoAndParent(fun, cx, scope, false);
}
Expand All @@ -499,7 +510,7 @@ private void reflect(
NativeJavaMethod method = (NativeJavaMethod) member;
FieldAndMethods fam =
new FieldAndMethods(
scope, method.methods, new NativeJavaField(field, typeFactory));
scope, method, new NativeJavaField(field, typeFactory));
Map<String, FieldAndMethods> fmht =
isStatic ? staticFieldAndMethods : fieldAndMethods;
if (fmht == null) {
Expand Down Expand Up @@ -555,7 +566,7 @@ private void reflect(
for (int i = 0; i != constructors.length; ++i) {
ctorMembers[i] = new ExecutableBox(constructors[i], typeFactory);
}
ctors = new NativeJavaMethod(ctorMembers, cl.getSimpleName());
ctors = new NativeJavaMethod(cl, ctorMembers, cl.getSimpleName());
}

private static boolean maskingExistedMember(
Expand Down Expand Up @@ -628,7 +639,7 @@ private static Map<String, BeanProperty> extractBeaning(
if (method.methods.length == 1) {
bean.getter = method;
} else {
bean.getter = new NativeJavaMethod(new ExecutableBox[] {candidate});
bean.getter = new NativeJavaMethod(method.parent, candidate, name);
}
}
}
Expand All @@ -653,7 +664,7 @@ private static Map<String, BeanProperty> extractBeaning(
// We have a getter. Now, do we have a setter with matching type?
match = extractSetMethod(type, setterCandidates.methods, isStatic);
if (match != null) {
bean.setter = new NativeJavaMethod(match, match.getName());
bean.setter = new NativeJavaMethod(getter.parent, match, match.getName());
continue;
}
}
Expand Down Expand Up @@ -787,7 +798,7 @@ Map<String, FieldAndMethods> getFieldAndMethodsObjects(
int len = ht.size();
Map<String, FieldAndMethods> result = new HashMap<>(len);
for (FieldAndMethods fam : ht.values()) {
FieldAndMethods famNew = new FieldAndMethods(scope, fam.methods, fam.field);
FieldAndMethods famNew = new FieldAndMethods(scope, fam, fam.field);
famNew.javaObject = javaObject;
result.put(fam.field.raw().getName(), famNew);
}
Expand Down Expand Up @@ -902,15 +913,18 @@ final class BeanProperty {
class FieldAndMethods extends NativeJavaMethod {
private static final long serialVersionUID = -9222428244284796755L;

FieldAndMethods(Scriptable scope, ExecutableBox[] methods, NativeJavaField field) {
super(methods);
FieldAndMethods(Scriptable scope, NativeJavaMethod methods, NativeJavaField field) {
super(methods.parent, methods.methods, methods.getFunctionName());
this.field = field;
setParentScope(scope);
setPrototype(ScriptableObject.getFunctionPrototype(scope));
}

@Override
public Object getDefaultValue(Class<?> hint) {
getMethods(); // trigger lazy initialization of 'field'
var field = this.field;

if (hint == ScriptRuntime.FunctionClass) return this;
Object rval;
try {
Expand All @@ -927,6 +941,19 @@ public Object getDefaultValue(Class<?> hint) {
return rval;
}

@Override
protected void initMembers(Object found) {
super.initMembers(found);
if (found instanceof FieldAndMethods) {
this.field = ((FieldAndMethods) found).field;
} else {
throw new IllegalStateException(
String.format(
"Cannot find FieldAndMethods with name '%s' in '%s'",
functionName, parent));
}
}

NativeJavaField field;
Object javaObject;
}
14 changes: 9 additions & 5 deletions rhino/src/main/java/org/mozilla/javascript/NativeJavaClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public NativeJavaClass(Scriptable scope, Class<?> cl, boolean isAdapter) {
@Override
protected void initMembers() {
Class<?> cl = (Class<?>) javaObject;
members = JavaMembers.lookupClass(parent, cl, cl, isAdapter);
var members = JavaMembers.lookupClass(parent, cl, cl, isAdapter);
setMembers(members);
staticFieldAndMethods = members.getFieldAndMethodsObjects(this, cl, true);
}

Expand All @@ -53,7 +54,7 @@ public String getClassName() {

@Override
public boolean has(String name, Scriptable start) {
return members.has(name, true) || javaClassPropertyName.equals(name);
return getMembers().has(name, true) || javaClassPropertyName.equals(name);
}

@Override
Expand All @@ -64,6 +65,9 @@ public Object get(String name, Scriptable start) {
// one constructed out of whole cloth, so we return null.
if ("prototype".equals(name)) return null;

// trigger initialization of `staticFieldAndMethods`
var members = getMembers();

if (staticFieldAndMethods != null) {
Object result = staticFieldAndMethods.get(name);
if (result != null) return result;
Expand Down Expand Up @@ -95,12 +99,12 @@ public Object get(String name, Scriptable start) {

@Override
public void put(String name, Scriptable start, Object value) {
members.put(this, name, javaObject, value, true);
getMembers().put(this, name, javaObject, value, true);
}

@Override
public Object[] getIds() {
return members.getIds(true);
return getMembers().getIds(true);
}

public Class<?> getClassObject() {
Expand Down Expand Up @@ -139,7 +143,7 @@ public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
Class<?> classObject = getClassObject();
int modifiers = classObject.getModifiers();
if (!(Modifier.isInterface(modifiers) || Modifier.isAbstract(modifiers))) {
NativeJavaMethod ctors = members.ctors;
NativeJavaMethod ctors = getMembers().ctors;
int index = ctors.findCachedFunction(cx, args);
if (index < 0) {
String sig = NativeJavaMethod.scriptSignature(args);
Expand Down
83 changes: 67 additions & 16 deletions rhino/src/main/java/org/mozilla/javascript/NativeJavaMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,37 +31,70 @@ public class NativeJavaMethod extends BaseFunction {

private static final long serialVersionUID = -3440381785576412928L;

// TODO: serialization support by read/write class and method name
final ExecutableBox[] methods;
private final String functionName;
/** Class object representing the {@link JavaMembers} that holds this NativeJavaMethod */
final Class<?> parent;

/**
* Will be initialized lazily when (and only when) this object is created via deserialization.
* This also means that directly accessing this field can be safe, if they are usages within
* {@link JavaMembers}, or the object itself is directly from {@link JavaMembers}
*
* @see #getMethods()
*/
transient ExecutableBox[] methods;

protected final String functionName;
private final transient CopyOnWriteArrayList<ResolvedOverload> overloadCache =
new CopyOnWriteArrayList<>();

NativeJavaMethod(ExecutableBox[] methods) {
this.functionName = methods[0].getName();
this.methods = methods;
}

NativeJavaMethod(ExecutableBox[] methods, String name) {
NativeJavaMethod(Class<?> parent, ExecutableBox[] methods, String name) {
this.parent = parent;
this.functionName = name;
this.methods = methods;
}

NativeJavaMethod(ExecutableBox method, String name) {
NativeJavaMethod(Class<?> parent, ExecutableBox method, String name) {
this.parent = parent;
this.functionName = name;
this.methods = new ExecutableBox[] {method};
}

@Deprecated
public NativeJavaMethod(Method method, String name) {
this(new ExecutableBox(method, TypeInfoFactory.GLOBAL, method.getDeclaringClass()), name);
this(
method.getDeclaringClass(),
new ExecutableBox(method, TypeInfoFactory.GLOBAL, method.getDeclaringClass()),
name);
}

@Override
public String getFunctionName() {
return functionName;
}

ExecutableBox[] getMethods() {
var methods = this.methods;
if (methods == null) {
var scope = this.getParentScope();
var members = JavaMembers.lookupClass(scope, parent, parent, false);
var got = members.getMember(functionName, false);
initMembers(got);
methods = this.methods;
}
return methods;
}

protected void initMembers(Object found) {
if (found instanceof NativeJavaMethod) {
this.methods = ((NativeJavaMethod) found).methods;
} else {
throw new IllegalStateException(
String.format(
"Cannot find NativeJavaMethod with name '%s' in '%s'",
functionName, parent));
}
}

static String scriptSignature(Object[] values) {
StringBuilder sig = new StringBuilder();
for (int i = 0; i != values.length; ++i) {
Expand Down Expand Up @@ -117,25 +150,42 @@ String decompile(int indent, EnumSet<DecompilerFlag> flags) {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0, N = methods.length; i != N; ++i) {
var methods = this.getMethods();
for (var box : methods) {
// Check member type, we also use this for overloaded constructors
if (methods[i].isMethod()) {
Method method = methods[i].asMethod();
if (box.isMethod()) {
Method method = box.asMethod();
sb.append(JavaMembers.javaSignature(method.getReturnType()));
sb.append(' ');
sb.append(method.getName());
} else {
sb.append(methods[i].getName());
sb.append(box.getName());
}
sb.append(ReflectUtils.liveConnectSignature(methods[i].getArgTypes()));
sb.append(ReflectUtils.liveConnectSignature(box.getArgTypes()));
sb.append('\n');
}
return sb.toString();
}

@Override
public boolean equals(Object obj) {
if (obj instanceof NativeJavaMethod) {
var other = (NativeJavaMethod) obj;
// Given a class, the list of methods with the given name is determined
return this.parent == other.parent && this.functionName.equals(other.functionName);
}
return false;
}

@Override
public int hashCode() {
return parent.hashCode() * 31 + functionName.hashCode();
}

@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
// Find a method that matches the types given.
var methods = this.getMethods();
if (methods.length == 0) {
throw new RuntimeException("No methods defined for call");
}
Expand Down Expand Up @@ -220,6 +270,7 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar
}

int findCachedFunction(Context cx, Object[] args) {
var methods = this.getMethods();
if (methods.length > 1) {
for (ResolvedOverload ovl : overloadCache) {
if (ovl.matches(args)) {
Expand Down
Loading