From f48e4855f727e85d606a4cb9d0652a6a18977a4f Mon Sep 17 00:00:00 2001 From: "duncan.macgregor" Date: Fri, 9 Jan 2026 11:24:58 +0000 Subject: [PATCH 1/6] Serialise `JavaMembers`. --- .../src/main/java/org/mozilla/javascript/JavaMembers.java | 8 ++++++-- .../java/org/mozilla/javascript/NativeJavaObject.java | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java b/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java index 127df4f361..feeb13811b 100644 --- a/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java +++ b/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java @@ -9,6 +9,7 @@ import static java.lang.reflect.Modifier.isProtected; import static java.lang.reflect.Modifier.isPublic; +import java.io.Serializable; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -35,7 +36,8 @@ * @see NativeJavaObject * @see NativeJavaClass */ -class JavaMembers { +class JavaMembers implements Serializable { + private static final long serialVersionUID = 8260700214130563887L; private static final boolean STRICT_REFLECTIVE_ACCESS = isModularJava(); @@ -889,7 +891,9 @@ RuntimeException reportMemberNotFound(String memberName) { NativeJavaMethod ctors; // we use NativeJavaMethod for ctor overload resolution } -final class BeanProperty { +final class BeanProperty implements Serializable { + private static final long serialVersionUID = 8260700214130563887L; + BeanProperty(String name) { this.name = name; } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeJavaObject.java b/rhino/src/main/java/org/mozilla/javascript/NativeJavaObject.java index e675bf10b6..45cc7387e3 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeJavaObject.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeJavaObject.java @@ -879,6 +879,8 @@ private void writeObject(ObjectOutputStream out) throws IOException { } else { out.writeObject(null); } + + out.writeObject(members); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { @@ -904,7 +906,7 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE staticType = TypeInfo.NONE; } - initMembers(); + members = (JavaMembers) in.readObject(); } private static Callable symbol_iterator = From 92991cb27adbc94d358e6aa9eecd6b8df0fe2e10 Mon Sep 17 00:00:00 2001 From: "duncan.macgregor" Date: Fri, 9 Jan 2026 11:26:03 +0000 Subject: [PATCH 2/6] Expand `ContinuationsApiTest` to check more about native java methods. --- .../javascript/tests/ContinuationsApiTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java b/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java index 9de41f17fe..4dc39e45a9 100644 --- a/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java +++ b/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java @@ -263,6 +263,12 @@ public void continuationsPrototypesAndSerialization() cx.setInterpretedMode(true); // must use interpreter mode globalScope.put( "myObject", globalScope, Context.javaToJS(new MyClass(), globalScope)); + var func = (Scriptable) globalScope.get("Function", globalScope); + var fp = func.get("prototype", func); + var myObj = (Scriptable) globalScope.get("myObject", globalScope); + var myFunc = (Scriptable) myObj.get("f", myObj); + var myProto = myFunc.getPrototype(); + assertEquals("Thing should be equal", fp, myProto); } try (Context cx = Context.enter()) { @@ -304,6 +310,13 @@ public void continuationsPrototypesAndSerialization() Object result = cx.resumeContinuation(continuation, globalScope, 8); assertEquals("foo", result); + + var func = (Scriptable) globalScope.get("Function", globalScope); + var fp = func.get("prototype", func); + var myObj = (Scriptable) globalScope.get("myObject", globalScope); + var myFunc = (Scriptable) myObj.get("f", myObj); + var myProto = myFunc.getPrototype(); + assertEquals("Thing should be equal", fp, myProto); } catch (ContinuationPending e) { // TODO Auto-generated catch block e.printStackTrace(); From 7162365186497621ae1929488267264cf0a45b1c Mon Sep 17 00:00:00 2001 From: "duncan.macgregor" Date: Fri, 9 Jan 2026 15:07:33 +0000 Subject: [PATCH 3/6] Add a field to continuation API serialisation test. --- .../java/org/mozilla/javascript/tests/ContinuationsApiTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java b/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java index 4dc39e45a9..ddac2fd0df 100644 --- a/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java +++ b/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java @@ -70,6 +70,8 @@ public void directThrow() { throw cx.captureContinuation(); } } + + public Double publcField = 0.0; } @Before From 29a30034517fb8b8e6e64e314c2a9f7f9285ca71 Mon Sep 17 00:00:00 2001 From: "duncan.macgregor" Date: Fri, 9 Jan 2026 15:08:07 +0000 Subject: [PATCH 4/6] Make native jave fields serialisable. --- .../javascript/lc/member/NativeJavaField.java | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/rhino/src/main/java/org/mozilla/javascript/lc/member/NativeJavaField.java b/rhino/src/main/java/org/mozilla/javascript/lc/member/NativeJavaField.java index de28722699..21c7e0bd78 100644 --- a/rhino/src/main/java/org/mozilla/javascript/lc/member/NativeJavaField.java +++ b/rhino/src/main/java/org/mozilla/javascript/lc/member/NativeJavaField.java @@ -1,5 +1,9 @@ package org.mozilla.javascript.lc.member; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import org.mozilla.javascript.lc.type.TypeInfo; @@ -8,10 +12,13 @@ /** * @author ZZZank */ -public final class NativeJavaField { - private final Field field; +public final class NativeJavaField implements Serializable { + + private static final long serialVersionUID = -3440381785576412928L; + + private transient Field field; private final boolean isFinal; - private final TypeInfo type; + private transient TypeInfo type; public NativeJavaField(Field field, TypeInfoFactory typeFactory) { this.field = field; @@ -39,4 +46,28 @@ public void set(Object javaObject, Object value) throws IllegalAccessException { } field.set(javaObject, value); } + + private void init(Field field, TypeInfoFactory factory, Class parent) { + this.field = field; + this.type = factory.create(field.getGenericType()); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + var fieldName = (String) in.readObject(); + var declaringClass = (Class) in.readObject(); + + try { + var field = declaringClass.getField(fieldName); + init(field, TypeInfoFactory.GLOBAL, declaringClass); + } catch (NoSuchFieldException e) { + throw new IOException("Cannot find member: " + e); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(field.getName()); + out.writeObject(field.getDeclaringClass()); + } } From 4bc781424e4d8b47cca90483670de0b6204dc584 Mon Sep 17 00:00:00 2001 From: "duncan.macgregor" Date: Sun, 11 Jan 2026 21:16:01 +0000 Subject: [PATCH 5/6] Make `ExecutableBox` serialisable. --- .../javascript/lc/member/ExecutableBox.java | 74 ++++++++++++++++++- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/rhino/src/main/java/org/mozilla/javascript/lc/member/ExecutableBox.java b/rhino/src/main/java/org/mozilla/javascript/lc/member/ExecutableBox.java index 721f40e5aa..227412aaac 100644 --- a/rhino/src/main/java/org/mozilla/javascript/lc/member/ExecutableBox.java +++ b/rhino/src/main/java/org/mozilla/javascript/lc/member/ExecutableBox.java @@ -1,5 +1,10 @@ package org.mozilla.javascript.lc.member; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.invoke.MethodType; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Array; import java.lang.reflect.Constructor; @@ -22,17 +27,18 @@ /** * @author ZZZank */ -public final class ExecutableBox { +public final class ExecutableBox implements Serializable { + private static final long serialVersionUID = 8260700214130563887L; /** * Must be either {@link Method} or {@link Constructor}. * *

Not using {@link java.lang.reflect.Executable} for Android compatibility */ - private final Member member; + private transient Member member; - private final List argTypes; - private final TypeInfo returnType; + private transient List argTypes; + private transient TypeInfo returnType; private final boolean varArgs; public ExecutableBox(Method method, TypeInfoFactory factory, Class parent) { @@ -279,4 +285,64 @@ private static boolean tryToMakeAccessible(AccessibleObject accessible) { } return true; } + + private void init(Constructor member, TypeInfoFactory factory, Class parent) { + this.member = member; + + var argTypes = factory.createList(member.getGenericParameterTypes()); + this.argTypes = argTypes; + this.returnType = TypeInfo.NONE; + } + + private void init(Method member, TypeInfoFactory factory, Class parent) { + this.member = member; + + var argTypes = factory.createList(member.getGenericParameterTypes()); + var returnType = factory.create(member.getGenericReturnType()); + var mapping = factory.getConsolidationMapping(parent); + if (mapping.isEmpty()) { + this.argTypes = argTypes; + this.returnType = returnType; + } else { + this.argTypes = TypeInfoFactory.consolidateAll(argTypes, mapping); + this.returnType = returnType.consolidate(mapping); + } + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + boolean isMethod = in.readBoolean(); + String name = (String) in.readObject(); + Class declaring = (Class) in.readObject(); + Class[] parms = + MethodType.fromMethodDescriptorString( + (String) in.readObject(), ExecutableBox.class.getClassLoader()) + .parameterArray(); + + try { + if (isMethod) { + var member = declaring.getMethod(name, parms); + init(member, TypeInfoFactory.GLOBAL, declaring); + } else { + var member = declaring.getConstructor(parms); + init(member, TypeInfoFactory.GLOBAL, declaring); + } + } catch (NoSuchMethodException e) { + throw new IOException("Cannot find member: " + e); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeBoolean(member instanceof Method); + out.writeObject(member.getName()); + out.writeObject(member.getDeclaringClass()); + + var argTypes = + member instanceof Method + ? ((Method) member).getParameterTypes() + : ((Constructor) member).getParameterTypes(); + // we only care about parameter types, so return type is always void + out.writeObject(MethodType.methodType(void.class, argTypes).toMethodDescriptorString()); + } } From f3212e3a6d1516b7fcf4c87a97f0b43a9a6d1ab7 Mon Sep 17 00:00:00 2001 From: "duncan.macgregor" Date: Tue, 20 Jan 2026 12:54:54 +0000 Subject: [PATCH 6/6] Add check for function identity. --- .../mozilla/javascript/tests/ContinuationsApiTest.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java b/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java index ddac2fd0df..b1c5bc83bc 100644 --- a/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java +++ b/rhino/src/test/java/org/mozilla/javascript/tests/ContinuationsApiTest.java @@ -277,7 +277,10 @@ public void continuationsPrototypesAndSerialization() cx.setInterpretedMode(true); // must use interpreter mode cx.evaluateString( globalScope, - "function f(a) { Number.prototype.blargh = function() {return 'foo';}; var k = myObject.f(a); var t = []; return new Number(8).blargh(); }", + "function f(a) { Number.prototype.blargh = " + + "function() {return 'foo';}; var k = myObject.f(a); " + + "var t = []; return new Number(8).blargh(); }; " + + "var foo = myObject.f;", "function test source", 1, null); @@ -318,7 +321,9 @@ public void continuationsPrototypesAndSerialization() var myObj = (Scriptable) globalScope.get("myObject", globalScope); var myFunc = (Scriptable) myObj.get("f", myObj); var myProto = myFunc.getPrototype(); - assertEquals("Thing should be equal", fp, myProto); + var sameFunc = globalScope.get("foo", globalScope); + assertEquals("Prototypes to be equal", fp, myProto); + assertEquals("Functions to be equal", myFunc, sameFunc); } catch (ContinuationPending e) { // TODO Auto-generated catch block e.printStackTrace();