diff --git a/benchmarks/src/jmh/java/org/mozilla/javascript/benchmarks/BuiltinBenchmark.java b/benchmarks/src/jmh/java/org/mozilla/javascript/benchmarks/BuiltinBenchmark.java
index 27478b3352e..7a664df5df9 100644
--- a/benchmarks/src/jmh/java/org/mozilla/javascript/benchmarks/BuiltinBenchmark.java
+++ b/benchmarks/src/jmh/java/org/mozilla/javascript/benchmarks/BuiltinBenchmark.java
@@ -334,12 +334,11 @@ public Object dumbLambdaClassMethods(DumbLambdaState state) {
private static class DumbLambdaClass extends ScriptableObject {
- private static Object noop(Context cx, VarScope scope, Scriptable thisObj, Object[] args) {
+ private static Object noop(Context cx, VarScope scope, Object thisObj, Object[] args) {
return Undefined.instance;
}
- private static Object setValue(
- Context cx, VarScope scope, Scriptable thisObj, Object[] args) {
+ private static Object setValue(Context cx, VarScope scope, Object thisObj, Object[] args) {
if (args.length < 1) {
throw ScriptRuntime.throwError(cx, scope, "Not enough args");
}
@@ -349,8 +348,7 @@ private static Object setValue(
return Undefined.instance;
}
- private static Object getValue(
- Context cx, VarScope scope, Scriptable thisObj, Object[] args) {
+ private static Object getValue(Context cx, VarScope scope, Object thisObj, Object[] args) {
DumbLambdaClass self =
LambdaConstructor.convertThisObject(thisObj, DumbLambdaClass.class);
return self.value;
diff --git a/rhino-engine/src/main/java/org/mozilla/javascript/engine/Builtins.java b/rhino-engine/src/main/java/org/mozilla/javascript/engine/Builtins.java
index 6d779758c47..cf1b6994d27 100644
--- a/rhino-engine/src/main/java/org/mozilla/javascript/engine/Builtins.java
+++ b/rhino-engine/src/main/java/org/mozilla/javascript/engine/Builtins.java
@@ -11,7 +11,6 @@
import javax.script.ScriptContext;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptRuntime;
-import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.TopLevel;
import org.mozilla.javascript.Undefined;
@@ -48,7 +47,7 @@ void register(Context cx, TopLevel scope, ScriptContext sc) {
ScriptableObject.DONTENUM | ScriptableObject.READONLY);
}
- private static Object print(Context cx, VarScope scope, Scriptable thisObj, Object[] args) {
+ private static Object print(Context cx, VarScope scope, Object thisObj, Object[] args) {
try {
Builtins self = getSelf(scope);
for (Object arg : args) {
diff --git a/rhino-tools/src/main/java/org/mozilla/javascript/tools/debugger/Dim.java b/rhino-tools/src/main/java/org/mozilla/javascript/tools/debugger/Dim.java
index 617b3049a67..9d42b0156e8 100644
--- a/rhino-tools/src/main/java/org/mozilla/javascript/tools/debugger/Dim.java
+++ b/rhino-tools/src/main/java/org/mozilla/javascript/tools/debugger/Dim.java
@@ -980,7 +980,7 @@ public static class StackFrame implements DebugFrame {
private VarScope scope;
/** The 'this' object. */
- private Scriptable thisObj;
+ private Object thisObj;
/** Whether this frame represents a function (vs a top-level script). */
private boolean isFunction;
@@ -1006,7 +1006,7 @@ private StackFrame(Context cx, Dim dim, FunctionSource fsource, boolean isFuncti
/** Called when the stack frame is entered. */
@Override
- public void onEnter(Context cx, VarScope scope, Scriptable thisObj, Object[] args) {
+ public void onEnter(Context cx, VarScope scope, Object thisObj, Object[] args) {
contextData.pushFrame(this);
this.scope = scope;
this.thisObj = thisObj;
diff --git a/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Global.java b/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Global.java
index 6e4ea8ba6d7..7b647834c6e 100644
--- a/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Global.java
+++ b/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Global.java
@@ -78,6 +78,7 @@ public class Global extends ImporterTopLevel {
"load",
"loadClass",
"print",
+ "printJavaStack",
"quit",
"readline",
"readFile",
@@ -155,6 +156,10 @@ public void init(Context cx) {
history = (NativeArray) cx.newArray(this, 0);
defineProperty("history", history, ScriptableObject.DONTENUM);
+ // Initialize Test262 support
+ Test262 proto262 = Test262.init(cx, this, Test262.RealmMode.STANDARD);
+ Test262.install(this, proto262, Test262.RealmMode.STANDARD);
+
initialized = true;
}
@@ -221,6 +226,16 @@ public static Object print(Context cx, Scriptable thisObj, Object[] args, Functi
return doPrint(args, funObj, true);
}
+ public static void printJavaStack(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
+ Console c = getInstance(funObj).getConsole();
+ var stack = new Error().getStackTrace();
+ for (int i = 0; i < stack.length; i++) {
+ String s = stack[i].toString();
+ c.println(s);
+ }
+ c.println();
+ }
+
/** Print just as in "print," but without the trailing newline. */
public static Object write(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
return doPrint(args, funObj, false);
diff --git a/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/JavaPolicySecurity.java b/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/JavaPolicySecurity.java
index 720563501bc..e536ff69784 100644
--- a/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/JavaPolicySecurity.java
+++ b/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/JavaPolicySecurity.java
@@ -23,7 +23,6 @@
import org.mozilla.javascript.Context;
import org.mozilla.javascript.GeneratedClassLoader;
import org.mozilla.javascript.Script;
-import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.VarScope;
public class JavaPolicySecurity extends SecurityProxy {
@@ -205,7 +204,7 @@ public Object callWithDomain(
Context cx,
Callable callable,
VarScope scope,
- Scriptable thisObj,
+ Object thisObj,
Object[] args) {
return doAction(securityDomain, () -> callable.call(cx, scope, thisObj, args));
}
@@ -216,7 +215,7 @@ public Object callWithDomain(
Context cx,
Script script,
VarScope scope,
- Scriptable thisObj,
+ Object thisObj,
Object[] args) {
return doAction(securityDomain, () -> script.exec(cx, scope, thisObj));
}
diff --git a/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Test262.java b/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Test262.java
new file mode 100644
index 00000000000..76b4d8c5009
--- /dev/null
+++ b/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Test262.java
@@ -0,0 +1,144 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.javascript.tools.shell;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ScopeObject;
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.SymbolKey;
+import org.mozilla.javascript.TopLevel;
+import org.mozilla.javascript.Undefined;
+import org.mozilla.javascript.VarScope;
+import org.mozilla.javascript.typedarrays.NativeArrayBuffer;
+
+/**
+ * Implements the $262 object required by the Test262 ECMAScript conformance test suite.
+ *
+ *
This class provides the host-defined functions specified by Test262 for testing ECMAScript
+ * implementations. It supports creating new realms, evaluating scripts, and accessing global
+ * objects.
+ *
+ * @see
+ * Test262 Host-Defined Functions
+ */
+public class Test262 extends ScriptableObject {
+
+ /** Enum to control how realms are initialized. */
+ public enum RealmMode {
+ /** Uses initSafeStandardObjects - restricts Java access for sandboxing (used in tests) */
+ SAFE,
+ /** Uses initStandardObjects - full access to Java integration (used in shell) */
+ STANDARD
+ }
+
+ private RealmMode realmMode;
+
+ public Test262() {
+ super();
+ }
+
+ Test262(VarScope scope, Scriptable prototype, RealmMode mode) {
+ super(scope, prototype);
+ this.realmMode = mode;
+ }
+
+ /**
+ * Initialize the $262 prototype object with all Test262 host-defined functions.
+ *
+ * @param cx the current Context
+ * @param scope the scope to install the prototype in
+ * @param mode the realm mode (SAFE for tests, STANDARD for shell)
+ * @return the initialized $262 prototype
+ */
+ public static Test262 init(Context cx, VarScope scope, RealmMode mode) {
+ Test262 proto = new Test262();
+ proto.realmMode = mode;
+ proto.setPrototype(getObjectPrototype(scope));
+ proto.setParentScope(scope);
+
+ proto.defineProperty(scope, "gc", 0, Test262::gc);
+ proto.defineProperty(scope, "createRealm", 0, Test262::createRealm);
+ proto.defineProperty(scope, "evalScript", 1, Test262::evalScript);
+ proto.defineProperty(scope, "detachArrayBuffer", 0, Test262::detachArrayBuffer);
+
+ proto.defineProperty(cx, scope, "global", Test262::getGlobal, null, DONTENUM | READONLY);
+ proto.defineProperty(cx, scope, "agent", Test262::getAgent, null, DONTENUM | READONLY);
+
+ proto.defineProperty(SymbolKey.TO_STRING_TAG, "__262__", DONTENUM | READONLY);
+
+ ScriptableObject.defineProperty(scope, "__262__", proto, DONTENUM);
+ return proto;
+ }
+
+ /**
+ * Install a $262 instance into a scope.
+ *
+ * @param scope the scope to install into
+ * @param parentScope the parent scope for the $262 instance
+ * @param mode the realm mode
+ * @return the installed $262 instance
+ */
+ public static Test262 install(ScopeObject scope, Scriptable parentScope, RealmMode mode) {
+ Test262 instance = new Test262(scope, parentScope, mode);
+
+ scope.put("$262", scope, instance);
+ scope.setAttributes("$262", ScriptableObject.DONTENUM);
+
+ return instance;
+ }
+
+ private static Object gc(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ System.gc();
+ return Undefined.instance;
+ }
+
+ public static Object evalScript(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ if (args.length == 0) {
+ throw ScriptRuntime.throwError(cx, scope, "not enough args");
+ }
+ String source = Context.toString(args[0]);
+ return cx.evaluateString(scope, source, "", 1, null);
+ }
+
+ public static Object getGlobal(Scriptable scriptable) {
+ return ((TopLevel) scriptable.getParentScope()).getGlobalThis();
+ }
+
+ public static Test262 createRealm(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ // Get the realm mode from the parent $262 instance
+ Test262 parent = (Test262) ScriptRuntime.toObject(scope, thisObj);
+ RealmMode mode = parent.realmMode;
+
+ // Create realm based on mode
+ TopLevel realm;
+ if (mode == RealmMode.SAFE) {
+ realm = cx.initSafeStandardObjects(new TopLevel());
+ } else {
+ realm = cx.initStandardObjects(new TopLevel());
+ }
+
+ return install(realm, ScriptRuntime.toObject(realm, thisObj).getPrototype(), mode);
+ }
+
+ public static Object detachArrayBuffer(
+ Context cx, VarScope scope, Object thisObj, Object[] args) {
+ Scriptable buf = ScriptRuntime.toObject(scope, args[0]);
+ if (buf instanceof NativeArrayBuffer) {
+ ((NativeArrayBuffer) buf).detach();
+ }
+ return Undefined.instance;
+ }
+
+ public static Object getAgent(Scriptable scriptable) {
+ throw new UnsupportedOperationException("$262.agent property not yet implemented");
+ }
+
+ @Override
+ public String getClassName() {
+ return "__262__";
+ }
+}
diff --git a/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Timers.java b/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Timers.java
index e91bb9f5864..7f7e477e765 100644
--- a/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Timers.java
+++ b/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Timers.java
@@ -6,7 +6,6 @@
import org.mozilla.javascript.Function;
import org.mozilla.javascript.LambdaFunction;
import org.mozilla.javascript.ScriptRuntime;
-import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.VarScope;
@@ -29,19 +28,14 @@ public class Timers {
public void install(VarScope scope) {
LambdaFunction setTimeout =
new LambdaFunction(
- scope,
- "setTimeout",
- 1,
- (Context lcx, VarScope lscope, Scriptable thisObj, Object[] args) ->
- setTimeout(args));
+ scope, "setTimeout", 1, (lcx, lscope, thisObj, args) -> setTimeout(args));
ScriptableObject.defineProperty(scope, "setTimeout", setTimeout, ScriptableObject.DONTENUM);
LambdaFunction clearTimeout =
new LambdaFunction(
scope,
"clearTimeout",
1,
- (Context lcx, VarScope lscope, Scriptable thisObj, Object[] args) ->
- clearTimeout(args));
+ (lcx, lscope, thisObj, args) -> clearTimeout(args));
ScriptableObject.defineProperty(
scope, "clearTimeout", clearTimeout, ScriptableObject.DONTENUM);
}
diff --git a/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XMLLibImpl.java b/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XMLLibImpl.java
index ff0df5ef5c3..2332f213670 100644
--- a/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XMLLibImpl.java
+++ b/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XMLLibImpl.java
@@ -25,7 +25,7 @@ public final class XMLLibImpl extends XMLLib implements Serializable {
// EXPERIMENTAL Java interface
//
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = -6301237868232033480L;
/** This experimental interface is undocumented. */
public static org.w3c.dom.Node toDomNode(Object xmlObject) {
diff --git a/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XMLList.java b/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XMLList.java
index dd0e20f04a1..2acb4c3a099 100644
--- a/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XMLList.java
+++ b/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XMLList.java
@@ -765,7 +765,7 @@ private XMLList getPropertyList(XMLName name) {
}
private Object applyOrCall(
- boolean isApply, Context cx, VarScope scope, Scriptable thisObj, Object[] args) {
+ boolean isApply, Context cx, VarScope scope, Object thisObj, Object[] args) {
String methodName = isApply ? "apply" : "call";
if (!(thisObj instanceof XMLList) || ((XMLList) thisObj).targetProperty == null)
throw ScriptRuntime.typeErrorById("msg.isnt.function", methodName);
@@ -797,7 +797,7 @@ public Scriptable getExtraMethodSource(Context cx) {
}
@Override
- public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) {
+ public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) {
// This XMLList is being called as a Function.
// Let's find the real Function object.
if (targetProperty == null) throw ScriptRuntime.notFunctionError(this);
@@ -812,7 +812,7 @@ public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args
throw ScriptRuntime.typeErrorById("msg.incompat.call", methodName);
}
Object func = null;
- Scriptable sobj = thisObj;
+ Scriptable sobj = ScriptRuntime.toObject(getDeclarationScope(), thisObj);
while (sobj instanceof XMLObject) {
XMLObject xmlObject = (XMLObject) sobj;
@@ -839,4 +839,9 @@ public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args
public Scriptable construct(Context cx, VarScope scope, Object[] args) {
throw ScriptRuntime.typeErrorById("msg.not.ctor", "XMLList");
}
+
+ @Override
+ public Scriptable construct(Context cx, Object nt, VarScope s, Object thisObj, Object[] args) {
+ throw ScriptRuntime.typeErrorById("msg.not.ctor", "XMLList");
+ }
}
diff --git a/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XmlNode.java b/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XmlNode.java
index f54eaf668ad..7c8a1681d3e 100644
--- a/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XmlNode.java
+++ b/rhino-xml/src/main/java/org/mozilla/javascript/xmlimpl/XmlNode.java
@@ -20,7 +20,7 @@
import org.w3c.dom.UserDataHandler;
class XmlNode implements Serializable {
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 7498300745525888082L;
private static final String XML_NAMESPACES_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
diff --git a/rhino/src/main/java/org/mozilla/classfile/ClassFileWriter.java b/rhino/src/main/java/org/mozilla/classfile/ClassFileWriter.java
index 8b4beb9c5fe..7af9a3822f8 100644
--- a/rhino/src/main/java/org/mozilla/classfile/ClassFileWriter.java
+++ b/rhino/src/main/java/org/mozilla/classfile/ClassFileWriter.java
@@ -1237,6 +1237,12 @@ public void setTableSwitchJump(int switchStart, int caseIndex, int jumpTarget) {
public int acquireLabel() {
int top = itsLabelTableTop;
+ if (DEBUGLABELS) {
+ if (DEBUGCODEORIGINS) {
+ printOrigin();
+ }
+ System.err.printf("Acquired label %x.\n", top);
+ }
if (itsLabelTable == null || top == itsLabelTable.length) {
if (itsLabelTable == null) {
itsLabelTable = new int[MIN_LABEL_TABLE_SIZE];
@@ -1252,6 +1258,12 @@ public int acquireLabel() {
}
public void markLabel(int label) {
+ if (DEBUGLABELS) {
+ if (DEBUGCODEORIGINS) {
+ printOrigin();
+ }
+ System.err.printf("Mark label %x.\n", label);
+ }
if (!(label < 0)) throw new IllegalArgumentException("Bad label, no biscuit");
label &= 0x7FFFFFFF;
@@ -1308,7 +1320,7 @@ private void fixLabelGotos() {
int pc = itsLabelTable[label];
if (pc == -1) {
// Unlocated label
- throw new RuntimeException("unlocated label");
+ throw new RuntimeException(String.format("unlocated label %x", label));
}
// -1 to get delta from instruction start
addSuperBlockStart(pc);
@@ -1380,6 +1392,14 @@ private int addReservedCodeSpace(int size) {
public void addExceptionHandler(
int startLabel, int endLabel, int handlerLabel, String catchClassName) {
+ if (DEBUGCODE) {
+ if (DEBUGCODEORIGINS) {
+ printOrigin();
+ }
+ System.err.printf(
+ "Exception %x to %x, handler %x, %s.\n",
+ startLabel, endLabel, handlerLabel, catchClassName);
+ }
if ((startLabel & 0x80000000) != 0x80000000)
throw new IllegalArgumentException("Bad startLabel");
if ((endLabel & 0x80000000) != 0x80000000)
diff --git a/rhino/src/main/java/org/mozilla/javascript/ArrayLikeAbstractOperations.java b/rhino/src/main/java/org/mozilla/javascript/ArrayLikeAbstractOperations.java
index 304b2b81c24..045de788053 100644
--- a/rhino/src/main/java/org/mozilla/javascript/ArrayLikeAbstractOperations.java
+++ b/rhino/src/main/java/org/mozilla/javascript/ArrayLikeAbstractOperations.java
@@ -31,59 +31,6 @@ public interface LengthAccessor {
public long getLength(Context cx, Scriptable o);
}
- /**
- * Implements the methods "every", "filter", "forEach", "map", and "some" without using an
- * IdFunctionObject.
- */
- public static Object iterativeMethod(
- Context cx,
- IterativeOperation operation,
- VarScope scope,
- Object thisObj,
- Object[] args,
- LengthAccessor lengthAccessor) {
- return iterativeMethod(cx, null, operation, scope, thisObj, args, lengthAccessor, true);
- }
-
- /**
- * Implements the methods "every", "filter", "forEach", "map", and "some" using an
- * IdFunctionObject.
- */
- public static Object iterativeMethod(
- Context cx,
- IdFunctionObject fun,
- IterativeOperation operation,
- VarScope scope,
- Object thisObj,
- Object[] args,
- LengthAccessor lengthAccessor) {
- return iterativeMethod(cx, fun, operation, scope, thisObj, args, lengthAccessor, false);
- }
-
- private static Object iterativeMethod(
- Context cx,
- IdFunctionObject fun,
- IterativeOperation operation,
- VarScope scope,
- Object thisObj,
- Object[] args,
- LengthAccessor lengthAccessor,
- boolean skipCoercibleCheck) {
- Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj);
-
- if (!skipCoercibleCheck) {
- if (IterativeOperation.FIND == operation
- || IterativeOperation.FIND_INDEX == operation
- || IterativeOperation.FIND_LAST == operation
- || IterativeOperation.FIND_LAST_INDEX == operation) {
- requireObjectCoercible(cx, o, fun);
- }
- }
-
- long length = lengthAccessor.getLength(cx, o);
- return coercibleIterativeMethod(cx, operation, scope, o, args, length);
- }
-
public static Object iterativeMethod(
Context cx,
Object tag,
diff --git a/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java b/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java
index d5386cd7419..04872481fc0 100644
--- a/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java
+++ b/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java
@@ -6,6 +6,10 @@
package org.mozilla.javascript;
+import static org.mozilla.javascript.ClassDescriptor.Builder.value;
+import static org.mozilla.javascript.ClassDescriptor.Destination.PROTO;
+import static org.mozilla.javascript.Symbol.Kind.REGULAR;
+
import java.util.EnumSet;
import org.mozilla.javascript.xml.XMLObject;
@@ -23,100 +27,108 @@ public class BaseFunction extends ScriptableObject implements Function {
private static final Object FUNCTION_TAG = "Function";
private static final String FUNCTION_CLASS = "Function";
- static final String GENERATOR_FUNCTION_CLASS = "__GeneratorFunction";
+ static final SymbolKey GENERATOR_FUNCTION_CLASS =
+ new SymbolKey("GeneratorFunctionPrototype", REGULAR);
+ static final SymbolKey ASYNC_FUNCTION_CLASS = new SymbolKey("AsyncFunctionPrototype", REGULAR);
+ static final SymbolKey ASYNC_GENERATOR_FUNCTION_CLASS =
+ new SymbolKey("AsyncGeneratorFunctionPrototype", REGULAR);
private static final String APPLY_TAG = "APPLY_TAG";
private static final String CALL_TAG = "CALL_TAG";
private static final String PROTOTYPE_PROPERTY_NAME = "prototype";
- static LambdaConstructor init(Context cx, VarScope scope, boolean sealed) {
- LambdaConstructor ctor =
- new LambdaConstructor(
- scope,
- FUNCTION_CLASS,
- 1,
- BaseFunction::js_constructorCall,
- BaseFunction::js_constructor);
-
+ private static final ClassDescriptor DESCRIPTOR;
+ private static final ClassDescriptor ES6_DESCRIPTOR;
+ private static final ClassDescriptor GENERATOR_DESCRIPTOR;
+ // private static final ClassDescriptor GENERATOR_DESCRIPTOR;
+ private static final ClassDescriptor ASYNC_DESCRIPTOR;
+ private static final ClassDescriptor ASYNC_GENERATOR_DESCRIPTOR;
+ private static final JSDescriptor APPLY_DESCRIPTOR;
+ private static final JSDescriptor CALL_DESCRIPTOR;
+
+ static {
+ var builder =
+ new ClassDescriptor.Builder(
+ FUNCTION_CLASS,
+ 1,
+ BaseFunction::js_constructor,
+ BaseFunction::js_constructor)
+ .withMethod(PROTO, "call", 1, BaseFunction::js_call)
+ .withMethod(PROTO, "apply", 2, BaseFunction::js_apply)
+ .withMethod(PROTO, "bind", 1, BaseFunction::js_bind)
+ .withMethod(PROTO, "toSource", 1, BaseFunction::js_toSource)
+ .withMethod(PROTO, "toString", 0, BaseFunction::js_toString)
+ .withMethod(
+ PROTO,
+ SymbolKey.HAS_INSTANCE,
+ 1,
+ BaseFunction::js_hasInstance,
+ DONTENUM | READONLY | PERMANENT,
+ DONTENUM | READONLY);
+
+ DESCRIPTOR = builder.build();
+ ES6_DESCRIPTOR =
+ builder.withProp(
+ PROTO,
+ "arguments",
+ BaseFunction::js_protoArgumentsGetter,
+ BaseFunction::js_protoArgumentsSetter,
+ DONTENUM | READONLY)
+ .build();
+
+ APPLY_DESCRIPTOR = DESCRIPTOR.findProtoDesc("apply");
+ CALL_DESCRIPTOR = DESCRIPTOR.findProtoDesc("call");
+
+ GENERATOR_DESCRIPTOR =
+ new ClassDescriptor.Builder(
+ GENERATOR_FUNCTION_CLASS,
+ "GeneratorFunction",
+ 1,
+ BaseFunction::js_gen_constructor,
+ BaseFunction::js_gen_constructor)
+ .withProp(
+ PROTO,
+ SymbolKey.TO_STRING_TAG,
+ value("GeneratorFunction", READONLY | DONTENUM))
+ .build();
+
+ ASYNC_DESCRIPTOR =
+ new ClassDescriptor.Builder(
+ ASYNC_FUNCTION_CLASS,
+ "AsyncFunction",
+ 1,
+ BaseFunction::js_async_constructor,
+ BaseFunction::js_async_constructor)
+ .withProp(
+ PROTO,
+ SymbolKey.TO_STRING_TAG,
+ value("AsyncFunction", READONLY | DONTENUM))
+ .build();
+
+ ASYNC_GENERATOR_DESCRIPTOR =
+ new ClassDescriptor.Builder(
+ ASYNC_GENERATOR_FUNCTION_CLASS,
+ "AsyncGeneratorFunction",
+ 1,
+ BaseFunction::js_async_gen_constructor,
+ BaseFunction::js_async_gen_constructor)
+ .withProp(
+ PROTO,
+ SymbolKey.TO_STRING_TAG,
+ value("AsyncGeneratorFunction", READONLY | DONTENUM))
+ .build();
+ }
+
+ static JSFunction init(Context cx, VarScope scope, boolean sealed) {
var proto =
new LambdaFunction(
scope, "", 0, null, (lcx, lscope, lthisObj, largs) -> Undefined.instance);
- proto.defineProperty("constructor", ctor, DONTENUM);
- // Set the constructor correctly here. i.e. ctor.prototype.constructor == ctor
- // Redo the stuff about setupDefaultPrototype.
-
- ctor.setPrototypeProperty(proto);
- // Do this early, so that the functions on the prototype get
- // the right prototype...
- ScriptableObject.defineProperty(scope, FUNCTION_CLASS, ctor, DONTENUM);
- ctor.setPrototype((Scriptable) ctor.getPrototypeProperty());
-
- defKnownBuiltInOnProto(ctor, APPLY_TAG, scope, "apply", 2, BaseFunction::js_apply);
- defOnProto(ctor, scope, "bind", 1, BaseFunction::js_bind);
- defKnownBuiltInOnProto(ctor, CALL_TAG, scope, "call", 1, BaseFunction::js_call);
- defOnProto(ctor, scope, "toSource", 1, BaseFunction::js_toSource);
- defOnProto(ctor, scope, "toString", 0, BaseFunction::js_toString);
- defOnProto(
- ctor,
- scope,
- SymbolKey.HAS_INSTANCE,
- 1,
- BaseFunction::js_hasInstance,
- DONTENUM | READONLY | PERMANENT);
-
- // Function.prototype attributes: see ECMA 15.3.3.1
- ctor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT);
- if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
- ctor.setStandardPropertyAttributes(READONLY | DONTENUM);
- }
-
- if (!cx.isStrictMode() && cx.getLanguageVersion() >= Context.VERSION_ES6) {
- ctor.definePrototypeProperty(
- cx,
- "arguments",
- BaseFunction::js_protoArgumentsGetter,
- BaseFunction::js_protoArgumentsSetter,
- DONTENUM | READONLY);
- }
-
- ScriptableObject.defineProperty(scope, FUNCTION_CLASS, ctor, DONTENUM);
- if (sealed) {
- ctor.sealObject();
- ((ScriptableObject) ctor.getPrototypeProperty()).sealObject();
+ if (cx.getLanguageVersion() < Context.VERSION_ES6) {
+ return DESCRIPTOR.buildConstructor(cx, scope, proto, sealed);
+ } else {
+ return ES6_DESCRIPTOR.buildConstructor(cx, scope, proto, sealed);
}
- return ctor;
- }
-
- private static void defOnProto(
- LambdaConstructor constructor,
- VarScope scope,
- String name,
- int length,
- SerializableCallable target) {
- constructor.definePrototypeMethod(scope, name, length, target);
- }
-
- private static void defKnownBuiltInOnProto(
- LambdaConstructor constructor,
- Object tag,
- VarScope scope,
- String name,
- int length,
- SerializableCallable target) {
- constructor.defineKnownBuiltInPrototypeMethod(
- tag, scope, name, length, null, target, DONTENUM, DONTENUM | READONLY);
- }
-
- private static void defOnProto(
- LambdaConstructor constructor,
- VarScope scope,
- SymbolKey name,
- int length,
- SerializableCallable target,
- int attributes) {
- constructor.definePrototypeMethod(
- scope, name, length, null, target, attributes, DONTENUM | READONLY);
}
/**
@@ -127,54 +139,83 @@ static void init(VarScope scope, boolean sealed) {
init(Context.getContext(), scope, sealed);
}
- static Object initAsGeneratorFunction(VarScope scope, boolean sealed) {
+ static Object initAsGeneratorFunction(Context cx, VarScope scope, boolean sealed) {
var proto = new NativeObject();
- VarScope top = ScriptableObject.getTopLevelScope(scope);
-
- var function = (Scriptable) ScriptableObject.getProperty(scope, FUNCTION_CLASS);
- var functionProto =
- (Scriptable) ScriptableObject.getProperty(function, PROTOTYPE_PROPERTY_NAME);
- proto.setPrototype(functionProto);
- var iterator = (Scriptable) ScriptableObject.getProperty(scope, "Iterator");
- ScriptableObject.putProperty(
+ return GENERATOR_DESCRIPTOR.buildConstructor(
+ cx,
+ scope,
proto,
- PROTOTYPE_PROPERTY_NAME,
- ScriptableObject.getTopScopeValue(top, ES6Generator.GENERATOR_TAG));
+ sealed,
+ (c, ctor) -> {
+ VarScope top = ScriptableObject.getTopLevelScope(scope);
+
+ var function = (Scriptable) ScriptableObject.getProperty(scope, FUNCTION_CLASS);
+ var functionProto =
+ (Scriptable)
+ ScriptableObject.getProperty(function, PROTOTYPE_PROPERTY_NAME);
+ proto.setPrototype(functionProto);
- LambdaConstructor ctor =
- new LambdaConstructor(
- scope,
- GENERATOR_FUNCTION_CLASS,
- 1,
- proto,
- BaseFunction::js_gen_constructorCall,
- BaseFunction::js_gen_constructor);
+ var generatorProto =
+ ScriptableObject.getTopScopeValue(top, ES6Generator.GENERATOR_TAG);
- proto.defineProperty("constructor", ctor, READONLY | DONTENUM);
+ proto.setAttributes("constructor", DONTENUM | READONLY);
+ proto.defineProperty("prototype", generatorProto, READONLY | DONTENUM);
+ });
+ }
- // Function.prototype attributes: see ECMA 15.3.3.1
- ctor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT);
+ static Object initAsAsyncFunction(Context cx, VarScope scope, boolean sealed) {
+ var proto = new NativeObject();
- proto.defineProperty(SymbolKey.TO_STRING_TAG, "GeneratorFunction", READONLY | DONTENUM);
- ScriptableObject.putProperty(scope, GENERATOR_FUNCTION_CLASS, ctor);
- // Function.prototype attributes: see ECMA 15.3.3.1
- // The "GeneratorFunction" name actually never appears in the global scope.
- // Return it here so it can be cached as a "builtin"
- return ctor;
+ return ASYNC_DESCRIPTOR.buildConstructor(
+ cx,
+ scope,
+ proto,
+ sealed,
+ (c, ctor) -> {
+ var function = (Scriptable) ScriptableObject.getProperty(scope, FUNCTION_CLASS);
+ var functionProto =
+ (Scriptable)
+ ScriptableObject.getProperty(function, PROTOTYPE_PROPERTY_NAME);
+ proto.setPrototype(functionProto);
+ proto.setAttributes("constructor", DONTENUM | READONLY);
+ });
}
- public BaseFunction() {
- createProperties();
+ static Object initAsAsyncGeneratorFunction(Context cx, VarScope scope, boolean sealed) {
+ var proto = new NativeObject();
+
+ return ASYNC_GENERATOR_DESCRIPTOR.buildConstructor(
+ cx,
+ scope,
+ proto,
+ sealed,
+ (c, ctor) -> {
+ VarScope top = ScriptableObject.getTopLevelScope(scope);
+
+ var function = (Scriptable) ScriptableObject.getProperty(scope, FUNCTION_CLASS);
+ var functionProto =
+ (Scriptable)
+ ScriptableObject.getProperty(function, PROTOTYPE_PROPERTY_NAME);
+ proto.setPrototype(functionProto);
+ proto.setAttributes("constructor", DONTENUM | READONLY);
+
+ var asyncGeneratorProto =
+ ScriptableObject.getTopScopeValue(
+ top, ES6AsyncGenerator.ASYNC_GENERATOR_TAG);
+
+ proto.defineProperty("prototype", asyncGeneratorProto, READONLY | DONTENUM);
+ });
}
- public BaseFunction(boolean isGenerator) {
+ public BaseFunction(VarScope scope) {
+ declarationScope = scope;
createProperties();
- this.isGeneratorFunction = isGenerator;
}
public BaseFunction(VarScope scope, Scriptable prototype) {
super(scope, prototype);
+ declarationScope = scope;
createProperties();
ScriptRuntime.setBuiltinProtoAndParent(this, scope, TopLevel.Builtins.Function);
}
@@ -319,6 +360,11 @@ protected static boolean prototypeDescSetter(
}
}
+ static DescriptorInfo createThrowingProp(Context cx, VarScope scope, ScriptableObject obj) {
+ var thrower = ScriptRuntime.typeErrorThrower(scope);
+ return new DescriptorInfo(false, NOT_FOUND, true, thrower, thrower, null);
+ }
+
protected final boolean defaultHas(String name) {
return super.has(name, this);
}
@@ -333,7 +379,10 @@ protected final void defaultPut(String name, Object value) {
@Override
public String getClassName() {
- return isGeneratorFunction() ? GENERATOR_FUNCTION_CLASS : FUNCTION_CLASS;
+ if (isGeneratorFunction()) {
+ return isAsync() ? "AsyncGeneratorFunction" : "GeneratorFunction";
+ }
+ return isAsync() ? "AsyncFunction" : FUNCTION_CLASS;
}
// Generated code will override this
@@ -395,18 +444,34 @@ public boolean hasInstance(Scriptable instance) {
Id_arguments = 5,
MAX_INSTANCE_ID = 5;
- static boolean isApply(KnownBuiltInFunction f) {
- return f.getTag() == APPLY_TAG;
+ static boolean isApply(Callable f) {
+ if (f instanceof KnownBuiltInFunction) {
+ var kf = (KnownBuiltInFunction) f;
+ var tag = kf.getTag();
+ return tag == APPLY_TAG;
+ }
+ if (f instanceof JSFunction) {
+ return ((JSFunction) f).getDescriptor() == APPLY_DESCRIPTOR;
+ }
+ return false;
}
- static boolean isApplyOrCall(KnownBuiltInFunction f) {
- var tag = f.getTag();
- return tag == APPLY_TAG || tag == CALL_TAG;
+ static boolean isApplyOrCall(Callable f) {
+ if (f instanceof KnownBuiltInFunction) {
+ var kf = (KnownBuiltInFunction) f;
+ var tag = kf.getTag();
+ return tag == APPLY_TAG || tag == CALL_TAG;
+ }
+ if (f instanceof JSFunction) {
+ var desc = ((JSFunction) f).getDescriptor();
+ return desc == APPLY_DESCRIPTOR || desc == CALL_DESCRIPTOR;
+ }
+ return false;
}
private static Object js_hasInstance(
- Context cx, VarScope scope, Object thisObj, Object[] args) {
- if (!(thisObj instanceof Callable)) {
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ if (!(thisObj instanceof Callable && thisObj instanceof Scriptable)) {
return false;
}
Object protoProp = null;
@@ -434,7 +499,8 @@ private static Object js_hasInstance(
: "unknown");
}
- private static Object js_bind(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ private static Object js_bind(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
if (!(thisObj instanceof Callable)) {
throw ScriptRuntime.notFunctionError(thisObj);
}
@@ -443,25 +509,29 @@ private static Object js_bind(Context cx, VarScope scope, Object thisObj, Object
final Scriptable boundThis;
final Object[] boundArgs;
if (argc > 0) {
- boundThis = ScriptRuntime.toObjectOrNull(cx, args[0], scope);
+ boundThis = ScriptRuntime.toObjectOrNull(cx, args[0], s);
boundArgs = new Object[argc - 1];
System.arraycopy(args, 1, boundArgs, 0, argc - 1);
} else {
boundThis = null;
boundArgs = ScriptRuntime.emptyArgs;
}
- return new BoundFunction(cx, scope, targetFunction, boundThis, boundArgs);
+ return new BoundFunction(cx, s, targetFunction, boundThis, boundArgs);
}
- private static Object js_apply(Context cx, VarScope scope, Object thisObj, Object[] args) {
- return ScriptRuntime.applyOrCall(true, cx, scope, (Scriptable) thisObj, args);
+ private static Object js_apply(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return ScriptRuntime.applyOrCall(true, cx, f.getDeclarationScope(), thisObj, args);
}
- private static Object js_call(Context cx, VarScope scope, Object thisObj, Object[] args) {
- return ScriptRuntime.applyOrCall(false, cx, scope, (Scriptable) thisObj, args);
+ private static Object js_call(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ var thisArg = ScriptRuntime.toObject(f.getDeclarationScope(), thisObj);
+ return ScriptRuntime.applyOrCall(false, cx, f.getDeclarationScope(), thisArg, args);
}
- private static Object js_toSource(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ private static Object js_toSource(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
BaseFunction realf = realFunction(thisObj, "toSource");
int indent = 0;
EnumSet flags = EnumSet.of(DecompilerFlag.TO_SOURCE);
@@ -476,39 +546,45 @@ private static Object js_toSource(Context cx, VarScope scope, Object thisObj, Ob
return realf.decompile(indent, flags);
}
- private static Object js_toString(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ private static Object js_toString(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
BaseFunction realf = realFunction(thisObj, "toString");
int indent = ScriptRuntime.toInt32(args, 0);
return realf.decompile(indent, EnumSet.noneOf(DecompilerFlag.class));
}
- private static Scriptable js_gen_constructorCall(
- Context cx, VarScope scope, Object thisObj, Object[] args) {
- return js_gen_constructor(cx, scope, args);
+ private static Scriptable js_constructor(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return jsConstructor(cx, f, nt, f.getDeclarationScope(), args, false);
}
- private static Scriptable js_constructor(Context cx, VarScope scope, Object[] args) {
- return jsConstructor(cx, scope, args, false);
+ private static Scriptable js_gen_constructor(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return jsConstructor(cx, f, nt, f.getDeclarationScope(), args, true);
}
- private static Scriptable js_constructorCall(
- Context cx, VarScope scope, Object thisObj, Object[] args) {
- return js_constructor(cx, scope, args);
+ private static Scriptable js_async_constructor(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return jsConstructor(cx, f, nt, f.getDeclarationScope(), args, false, true);
}
- private static Scriptable js_gen_constructor(Context cx, VarScope scope, Object[] args) {
- return jsConstructor(cx, scope, args, true);
+ private static Scriptable js_async_gen_constructor(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return jsConstructor(cx, f, nt, f.getDeclarationScope(), args, true, true);
}
private static BaseFunction realFunction(Object thisObj, String functionName) {
if (thisObj == null) {
throw ScriptRuntime.notFunctionError(null);
}
- Object x = ((Scriptable) thisObj).getDefaultValue(ScriptRuntime.FunctionClass);
- if (x instanceof Delegator) {
- x = ((Delegator) x).getDelegee();
+ if (thisObj instanceof Scriptable) {
+ Object x = ((Scriptable) thisObj).getDefaultValue(ScriptRuntime.FunctionClass);
+ if (x instanceof Delegator) {
+ x = ((Delegator) x).getDelegee();
+ }
+ return ensureType(x, BaseFunction.class, functionName);
}
- return ensureType(x, BaseFunction.class, functionName);
+ return ensureType(thisObj, BaseFunction.class, functionName);
}
/** Make value as DontEnum, DontDelete, ReadOnly prototype property of this Function object */
@@ -531,7 +607,7 @@ protected Scriptable getClassPrototype() {
/** Should be overridden. */
@Override
- public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) {
+ public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) {
return Undefined.instance;
}
@@ -574,6 +650,11 @@ public Scriptable construct(Context cx, VarScope scope, Object[] args) {
return result;
}
+ @Override
+ public Scriptable construct(Context cx, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return construct(cx, s, args);
+ }
+
/**
* Creates new script object. The default implementation of {@link #construct} uses this method
* to to get the value for {@code thisObj} argument when invoking {@link #call}. The method is
@@ -588,6 +669,25 @@ public Scriptable createObject(Context cx, VarScope scope) {
return newInstance;
}
+ public Scriptable createObject(Context cx, VarScope scope, Object nt) {
+ Scriptable newInstance = new NativeObject();
+ Object proto;
+ if (nt instanceof JSFunction) {
+ proto = ((JSFunction) nt).getPrototypeProperty();
+ } else {
+ proto = Undefined.instance;
+ }
+
+ if (proto instanceof Scriptable) {
+ newInstance.setPrototype((Scriptable) proto);
+ } else {
+ newInstance.setPrototype(getClassPrototype());
+ }
+
+ newInstance.setParentScope(getParentScope());
+ return newInstance;
+ }
+
/**
* Decompile the source information associated with this js function/script back into a string.
*
@@ -693,7 +793,10 @@ protected synchronized Object setupDefaultPrototype(VarScope scope) {
// should be %GeneratorPrototype%, not Object.prototype
VarScope top = ScriptableObject.getTopLevelScope(scope);
Object generatorProto =
- ScriptableObject.getTopScopeValue(top, ES6Generator.GENERATOR_TAG);
+ isAsync()
+ ? ScriptableObject.getTopScopeValue(
+ top, ES6AsyncGenerator.ASYNC_GENERATOR_TAG)
+ : ScriptableObject.getTopScopeValue(top, ES6Generator.GENERATOR_TAG);
if (generatorProto instanceof Scriptable) {
proto = (Scriptable) generatorProto;
} else {
@@ -724,14 +827,17 @@ Object getArguments() {
return argumentsObj;
}
Context cx = Context.getContext();
+ if (this instanceof JSFunction
+ && ((JSFunction) this).isStrict()
+ && cx.getLanguageVersion() >= Context.VERSION_ES6) {
+ ScriptRuntime.ThrowTypeError.throwNotAllowed();
+ }
+
NativeCall activation = ScriptRuntime.findFunctionActivation(cx, this);
// return (activation == null) ? null : activation.get("arguments", activation);
if (activation == null) {
return null;
}
- if (activation.function.isStrict() && cx.getLanguageVersion() >= Context.VERSION_ES6) {
- ScriptRuntime.ThrowTypeError.throwNotAllowed();
- }
Object arguments = activation.get("arguments", activation);
if (arguments instanceof Arguments && cx.getLanguageVersion() >= Context.VERSION_ES6) {
return new Arguments.ReadonlyArguments((Arguments) arguments, cx);
@@ -742,10 +848,29 @@ Object getArguments() {
void setArguments(Object caller) {}
private static Scriptable jsConstructor(
- Context cx, VarScope scope, Object[] args, boolean isGeneratorFunction) {
+ Context cx,
+ JSFunction f,
+ Object nt,
+ VarScope scope,
+ Object[] args,
+ boolean isGeneratorFunction) {
+ return jsConstructor(cx, f, nt, scope, args, isGeneratorFunction, false);
+ }
+
+ private static Scriptable jsConstructor(
+ Context cx,
+ JSFunction f,
+ Object nt,
+ VarScope scope,
+ Object[] args,
+ boolean isGeneratorFunction,
+ boolean isAsync) {
int arglen = args.length;
StringBuilder sourceBuf = new StringBuilder();
+ if (isAsync) {
+ sourceBuf.append("async ");
+ }
sourceBuf.append("function ");
if (isGeneratorFunction) {
sourceBuf.append("* ");
@@ -777,7 +902,7 @@ private static Scriptable jsConstructor(
String sourceURI = ScriptRuntime.makeUrlForGeneratedScript(false, filename, linep[0]);
- TopLevel global = ScriptableObject.getTopLevelScope(scope);
+ TopLevel global = ScriptableObject.getTopLevelScope(f.getDeclarationScope());
ErrorReporter reporter;
reporter = DefaultErrorReporter.forEval(cx.getErrorReporter());
@@ -789,7 +914,26 @@ private static Scriptable jsConstructor(
// Compile with explicit interpreter instance to force interpreter
// mode.
- return cx.compileFunction(global, source, evaluator, reporter, sourceURI, 1, null);
+ var res =
+ (JSFunction)
+ cx.compileFunction(global, source, evaluator, reporter, sourceURI, 1, null);
+
+ boolean resIsGenerator = res.getDescriptor().isES6Generator();
+ boolean resIsAsync = res.getDescriptor().isAsync();
+ if (resIsGenerator && resIsAsync) {
+ ScriptRuntime.setBuiltinProtoAndParent(
+ (ScriptableObject) res, f, nt, scope, TopLevel.Builtins.AsyncGeneratorFunction);
+ } else if (resIsGenerator) {
+ ScriptRuntime.setBuiltinProtoAndParent(
+ (ScriptableObject) res, f, nt, scope, TopLevel.Builtins.GeneratorFunction);
+ } else if (resIsAsync) {
+ ScriptRuntime.setBuiltinProtoAndParent(
+ (ScriptableObject) res, f, nt, scope, TopLevel.Builtins.AsyncFunction);
+ } else {
+ ScriptRuntime.setBuiltinProtoAndParent(
+ (ScriptableObject) res, f, nt, scope, TopLevel.Builtins.Function);
+ }
+ return res;
}
public void setHomeObject(Scriptable homeObject) {
@@ -806,15 +950,12 @@ public boolean isConstructor() {
&& this.getHomeObject() != null);
}
- private static final int Id_constructor = 1,
- Id_toString = 2,
- Id_toSource = 3,
- Id_apply = 4,
- Id_call = 5,
- Id_bind = 6,
- SymbolId_hasInstance = 7,
- MAX_PROTOTYPE_ID = SymbolId_hasInstance;
+ @Override
+ public VarScope getDeclarationScope() {
+ return declarationScope;
+ }
+ private final VarScope declarationScope;
private Object prototypeProperty;
private Object argumentsObj = NOT_FOUND;
private Object nameValue = null;
diff --git a/rhino/src/main/java/org/mozilla/javascript/BoundFunction.java b/rhino/src/main/java/org/mozilla/javascript/BoundFunction.java
index 554b8008b03..6092d0a2ad2 100644
--- a/rhino/src/main/java/org/mozilla/javascript/BoundFunction.java
+++ b/rhino/src/main/java/org/mozilla/javascript/BoundFunction.java
@@ -26,6 +26,7 @@ public BoundFunction(
Callable targetFunction,
Scriptable boundThis,
Object[] boundArgs) {
+ super(scope);
this.targetFunction = targetFunction;
this.boundThis = boundThis;
this.boundArgs = boundArgs;
@@ -58,7 +59,7 @@ void setArguments(Object caller) {
}
@Override
- public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] extraArgs) {
+ public Object call(Context cx, VarScope scope, Object thisObj, Object[] extraArgs) {
return targetFunction.call(cx, scope, getCallThis(), concat(boundArgs, extraArgs));
}
diff --git a/rhino/src/main/java/org/mozilla/javascript/Callable.java b/rhino/src/main/java/org/mozilla/javascript/Callable.java
index 4fcca2b21be..bba87f56de0 100644
--- a/rhino/src/main/java/org/mozilla/javascript/Callable.java
+++ b/rhino/src/main/java/org/mozilla/javascript/Callable.java
@@ -20,5 +20,5 @@ public interface Callable {
* @param args the array of arguments
* @return the result of the call
*/
- public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args);
+ public Object call(Context cx, VarScope scope, Object thisObj, Object[] args);
}
diff --git a/rhino/src/main/java/org/mozilla/javascript/CatchScope.java b/rhino/src/main/java/org/mozilla/javascript/CatchScope.java
new file mode 100644
index 00000000000..8a264518658
--- /dev/null
+++ b/rhino/src/main/java/org/mozilla/javascript/CatchScope.java
@@ -0,0 +1,14 @@
+package org.mozilla.javascript;
+
+public class CatchScope extends DeclarationScope {
+ private static final long serialVersionUID = -7471457301304454454L;
+
+ public CatchScope(VarScope parentScope) {
+ super(parentScope);
+ }
+
+ @Override
+ public boolean isNestedScope() {
+ return true;
+ }
+}
diff --git a/rhino/src/main/java/org/mozilla/javascript/ClassDescriptor.java b/rhino/src/main/java/org/mozilla/javascript/ClassDescriptor.java
index 7f8b9c687fc..d6d98d069ee 100644
--- a/rhino/src/main/java/org/mozilla/javascript/ClassDescriptor.java
+++ b/rhino/src/main/java/org/mozilla/javascript/ClassDescriptor.java
@@ -211,7 +211,7 @@ public ScriptableObject populateGlobal(
ScriptableObject.defineProperty(
scope, ctorDesc.name.toString(), global, ctorDesc.attributes);
for (var e : ctorDescs) {
- var f = new JSFunction(scope, e.funcDesc, null, null);
+ var f = new JSFunction(scope, e.funcDesc, null, Undefined.instance, null);
f.setStandardPropertyAttributes(e.stdAttrs);
if (e.name instanceof String) {
global.put((String) e.name, global, f);
@@ -241,7 +241,7 @@ public JSFunction buildConstructor(
ScriptableObject proto,
boolean sealed,
BiConsumer customStep) {
- var ctor = new JSFunction(scope, ctorDesc.funcDesc, null, null);
+ var ctor = new JSFunction(scope, ctorDesc.funcDesc, null, Undefined.instance, null);
if (proto != null) {
ctor.setPrototypeProperty(proto);
@@ -258,7 +258,7 @@ public JSFunction buildConstructor(
}
for (var e : ctorDescs) {
- var f = new JSFunction(scope, e.funcDesc, null, null);
+ var f = new JSFunction(scope, e.funcDesc, null, Undefined.instance, null);
f.setStandardPropertyAttributes(e.stdAttrs);
if (e.name instanceof String) {
ctor.put((String) e.name, ctor, f);
@@ -280,7 +280,7 @@ public JSFunction buildConstructor(
}
proto.setAttributes("constructor", DONTENUM);
for (var e : protoDescs) {
- var f = new JSFunction(scope, e.funcDesc, null, null);
+ var f = new JSFunction(scope, e.funcDesc, null, Undefined.instance, null);
f.setStandardPropertyAttributes(e.stdAttrs);
if (e.name instanceof String) {
proto.put((String) e.name, proto, f);
@@ -782,7 +782,7 @@ public ClassDescriptor build() {
private static class BuiltInJSCode extends JSCode implements Serializable {
- private static final long serialVersionUID = 2691205302914111400L;
+ private static final long serialVersionUID = -346984669839537590L;
private final JSCodeExec exec;
private final JSCodeResume resume;
diff --git a/rhino/src/main/java/org/mozilla/javascript/CodeGenUtils.java b/rhino/src/main/java/org/mozilla/javascript/CodeGenUtils.java
index 412a64b272b..94b5fec6b14 100644
--- a/rhino/src/main/java/org/mozilla/javascript/CodeGenUtils.java
+++ b/rhino/src/main/java/org/mozilla/javascript/CodeGenUtils.java
@@ -26,15 +26,29 @@ public static void fillInForNestedFunction(
|| fnParent instanceof Block)) {
builder.declaredAsFunctionExpression = true;
boolean isArrow = fn.getFunctionType() == FunctionNode.ARROW_FUNCTION;
+ boolean isAsyncNonGenerator = fn.isAsync() && !fn.isES6Generator();
builder.hasLexicalThis = isArrow;
- builder.hasPrototype = !isArrow;
- if (!isArrow) {
+ builder.hasPrototype = !isArrow && !isAsyncNonGenerator;
+ if (!isArrow && !isAsyncNonGenerator) {
builder.constructor = builder.code;
} else {
builder.constructor = new JSCode.NullBuilder();
}
} else {
builder.hasLexicalThis = false;
+ boolean isAsyncNonGenerator = fn.isAsync() && !fn.isES6Generator();
+ builder.hasPrototype = !isAsyncNonGenerator;
+ if (!isAsyncNonGenerator) {
+ builder.constructor = builder.code;
+ } else {
+ builder.constructor = new JSCode.NullBuilder();
+ }
+ }
+
+ // Class constructors: always strict, always constructable, always have prototype
+ if (fn.isClassConstructor()) {
+ builder.isClassConstructor = true;
+ builder.isStrict = true;
builder.hasPrototype = true;
builder.constructor = builder.code;
}
@@ -55,6 +69,9 @@ private static void fillInForFunction(JSDescriptor.Builder builder, FunctionNode
if (fn.isES6Generator()) {
builder.isES6Generator = true;
}
+ if (fn.isAsync()) {
+ builder.isAsync = true;
+ }
if (fn.isShorthand()) {
builder.isShorthand = true;
}
@@ -123,8 +140,14 @@ public static > void setConstructor(
JSDescriptor.Builder builder, ScriptNode scriptOrFn) {
if (scriptOrFn instanceof FunctionNode) {
FunctionNode f = (FunctionNode) scriptOrFn;
+ // Class constructors are always constructable, even if marked as method definitions
+ if (f.isClassConstructor()) {
+ builder.constructor = builder.code;
+ return;
+ }
boolean isArrow = f.getFunctionType() == FunctionNode.ARROW_FUNCTION;
- if (isArrow || f.isMethodDefinition() || f.isGenerator()) {
+ boolean isAsyncNonGenerator = f.isAsync() && !f.isES6Generator();
+ if (isArrow || f.isMethodDefinition() || f.isGenerator() || isAsyncNonGenerator) {
builder.constructor = new JSCode.NullBuilder();
} else {
builder.constructor = builder.code;
diff --git a/rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java b/rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java
index 0eb9c63b8c2..03cdb1a73b0 100644
--- a/rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java
+++ b/rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java
@@ -14,8 +14,10 @@
import java.util.Map;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.Jump;
+import org.mozilla.javascript.ast.RegExpLiteral;
import org.mozilla.javascript.ast.ScriptNode;
import org.mozilla.javascript.ast.TemplateCharacters;
+import org.mozilla.javascript.ast.TemplateLiteral;
/** Generates bytecode for the Interpreter. */
class CodeGenerator> extends Icode {
@@ -50,6 +52,25 @@ class CodeGenerator> extends Icode {
private int exceptionTableTop;
+ // Maps a finally TARGET node to information about its enclosing FINALLY,
+ // so that JSRs taken on a normal (non-exceptional) path can be replaced
+ // with an inlined copy of the finally body instead of a GOSUB/RETSUB pair.
+ // Populated while processing the corresponding TRY node.
+ private final HashMap finallyByTarget = new HashMap<>();
+
+ private static final class FinallyContext {
+ final Node finallyNode;
+ // Pairs of [startPC, endPC) of inlined finally bodies. These are excluded
+ // from the catch/finally protected ranges so an exception thrown by an
+ // inlined finally body is not re-caught by the same try (which would
+ // re-run the finally body via STARTSUB).
+ final ArrayList inlineRanges = new ArrayList<>();
+
+ FinallyContext(Node finallyNode) {
+ this.finallyNode = finallyNode;
+ }
+ }
+
// ECF_ or Expression Context Flags constants: for now only TAIL
private static final int ECF_TAIL = 1 << 0;
@@ -138,9 +159,7 @@ private void generateFunctionICode() {
private void generateICodeFromTree(Node tree) {
generateNestedFunctions();
- generateRegExpLiterals();
-
- generateTemplateLiterals();
+ generateLiterals();
visitStatement(tree, 0);
fixLabelGotos();
@@ -220,37 +239,36 @@ private void generateNestedFunctions() {
}
}
- private void generateRegExpLiterals() {
- int N = scriptOrFn.getRegexpCount();
+ private void generateLiterals() {
+ int N = scriptOrFn.getLiteralCount();
if (N == 0) return;
- Context cx = Context.getContext();
- RegExpProxy rep = ScriptRuntime.checkRegExpProxy(cx);
+ Context cx = null;
+ RegExpProxy rep = null;
Object[] array = new Object[N];
for (int i = 0; i != N; i++) {
- String string = scriptOrFn.getRegexpString(i);
- String flags = scriptOrFn.getRegexpFlags(i);
- array[i] = rep.compileRegExp(cx, string, flags);
- }
- itsData.itsRegExpLiterals = array;
- }
-
- private void generateTemplateLiterals() {
- int N = scriptOrFn.getTemplateLiteralCount();
- if (N == 0) return;
-
- Object[] array = new Object[N];
- for (int i = 0; i != N; i++) {
- List strings = scriptOrFn.getTemplateLiteralStrings(i);
- int j = 0;
- String[] values = new String[strings.size() * 2];
- for (TemplateCharacters s : strings) {
- values[j++] = s.getValue();
- values[j++] = s.getRawValue();
+ var literal = scriptOrFn.getLiteral(i);
+ if (literal instanceof RegExpLiteral) {
+ if (rep == null) {
+ cx = Context.getContext();
+ rep = ScriptRuntime.checkRegExpProxy(cx);
+ }
+ var re = (RegExpLiteral) literal;
+ array[i] = rep.compileRegExp(cx, re.getValue(), re.getFlags());
+ } else if (literal instanceof TemplateLiteral) {
+ var strings = ((TemplateLiteral) literal).getTemplateStrings();
+ String[] values = new String[strings.size() * 2];
+ int j = 0;
+ for (TemplateCharacters s : strings) {
+ values[j++] = s.getValue();
+ values[j++] = s.getRawValue();
+ }
+ array[i] = values;
+ } else {
+ array[i] = literal;
}
- array[i] = values;
}
- itsData.itsTemplateLiterals = array;
+ builder.literals = array;
}
private void updateLineNumber(Node node) {
@@ -283,7 +301,8 @@ private void visitStatement(Node node, int initialStackDepth) {
// at script/function start.
// In addition, function expressions can not be present here
// at statement level, they must only be present as expressions.
- if (fnType == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) {
+ if (fnType == FunctionNode.FUNCTION_EXPRESSION_STATEMENT
+ || fnType == FunctionNode.FUNCTION_BLOCK_SCOPED) {
addIndexOp(Icode_CLOSURE_STMT, fnIndex);
} else {
if (fnType != FunctionNode.FUNCTION_STATEMENT) {
@@ -311,6 +330,7 @@ private void visitStatement(Node node, int initialStackDepth) {
case Token.LABEL:
case Token.LOOP:
case Token.BLOCK:
+ case Token.SCOPE_BLOCK:
case Token.EMPTY:
case Token.WITH:
updateLineNumber(node);
@@ -328,8 +348,12 @@ private void visitStatement(Node node, int initialStackDepth) {
stackChange(-1);
break;
- case Token.LEAVEWITH:
- addToken(Token.LEAVEWITH);
+ case Token.ENTER_SCOPE:
+ visitEnterScope(node, child);
+ break;
+
+ case Token.LEAVE_SCOPE:
+ addToken(Token.LEAVE_SCOPE);
break;
case Token.LOCAL_BLOCK:
@@ -400,17 +424,39 @@ private void visitStatement(Node node, int initialStackDepth) {
case Token.JSR:
{
Node target = ((Jump) node).target;
- addGoto(target, Icode_GOSUB);
+ FinallyContext fc = finallyByTarget.get(target);
+ if (fc != null) {
+ // Inline the finally body for this normal-path caller. The exception
+ // handler still enters the FINALLY block via STARTSUB/RETSUB, so
+ // those are emitted separately when the FINALLY child is visited.
+ // Reset target labels in the body subtree first so each inlined copy
+ // gets fresh label IDs.
+ resetTargetLabels(fc.finallyNode);
+ int inlineStart = iCodeTop;
+ Node body = fc.finallyNode.getFirstChild();
+ while (body != null) {
+ visitStatement(body, initialStackDepth);
+ body = body.getNext();
+ }
+ fc.inlineRanges.add(new int[] {inlineStart, iCodeTop});
+ } else {
+ addGoto(target, Icode_GOSUB);
+ }
}
break;
case Token.FINALLY:
{
- // Account for incomming GOTOSUB address
+ // Exception-path entry: STARTSUB stores the thrown exception in the
+ // local register, the body runs, RETSUB rethrows. Normal-path callers
+ // are handled by inlining (see Token.JSR), so the FINALLY block is
+ // entered only via the exception handler. Reset labels in the body
+ // subtree because any preceding inlined copy already used them.
stackChange(1);
int finallyRegister = getLocalBlockRef(node);
addIndexOp(Icode_STARTSUB, finallyRegister);
stackChange(-1);
+ resetTargetLabels(node);
while (child != null) {
visitStatement(child, initialStackDepth);
child = child.getNext();
@@ -435,6 +481,20 @@ private void visitStatement(Node node, int initialStackDepth) {
addIndexOp(Icode_SCOPE_SAVE, scopeLocal);
+ Node finallyTargetNode = tryNode.getFinally();
+ FinallyContext fc = null;
+ if (finallyTargetNode != null) {
+ for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
+ if (c.getType() == Token.FINALLY) {
+ fc = new FinallyContext(c);
+ break;
+ }
+ }
+ if (fc != null) {
+ finallyByTarget.put(finallyTargetNode, fc);
+ }
+ }
+
int tryStart = iCodeTop;
boolean savedFlag = itsInTryFlag;
itsInTryFlag = true;
@@ -444,7 +504,21 @@ private void visitStatement(Node node, int initialStackDepth) {
}
itsInTryFlag = savedFlag;
+ if (fc != null) {
+ finallyByTarget.remove(finallyTargetNode);
+ }
+
Node catchTarget = tryNode.target;
+ Node finallyTarget = tryNode.getFinally();
+ // Splitting both catch and finally ranges by inline-body PCs would
+ // produce two entries with identical (start, end), which trips the
+ // interpreter's "no shared end" invariant in getExceptionHandler.
+ // Only split when there is no catch (the destructuring iterator-close
+ // case), where avoiding self re-entry into the same finally is the
+ // motivation. With a catch present, fall back to the prior single
+ // protected ranges.
+ List excludeRanges =
+ (fc != null && catchTarget == null) ? fc.inlineRanges : null;
if (catchTarget != null) {
int catchStartPC = labelTable[getTargetLabel(catchTarget)];
addExceptionHandler(
@@ -455,16 +529,16 @@ private void visitStatement(Node node, int initialStackDepth) {
exceptionObjectLocal,
scopeLocal);
}
- Node finallyTarget = tryNode.getFinally();
if (finallyTarget != null) {
int finallyStartPC = labelTable[getTargetLabel(finallyTarget)];
- addExceptionHandler(
+ addExceptionHandlersExcluding(
tryStart,
finallyStartPC,
finallyStartPC,
true,
exceptionObjectLocal,
- scopeLocal);
+ scopeLocal,
+ excludeRanges);
}
addIndexOp(Icode_LOCAL_CLEAR, scopeLocal);
@@ -536,6 +610,7 @@ private void visitStatement(Node node, int initialStackDepth) {
case Token.ENUM_INIT_VALUES:
case Token.ENUM_INIT_ARRAY:
case Token.ENUM_INIT_VALUES_IN_ORDER:
+ case Token.ENUM_INIT_ASYNC_ITERATOR:
visitExpression(child, 0);
addIndexOp(type, getLocalBlockRef(node));
stackChange(-1);
@@ -553,6 +628,25 @@ private void visitStatement(Node node, int initialStackDepth) {
}
}
+ private void visitEnterScope(Node node, Node child) {
+ addToken(Token.ENTER_SCOPE);
+ stackChange(1);
+ Object[] names = (Object[]) node.getProp(Node.OBJECT_IDS_PROP);
+ int i = 0;
+ while (child != null) {
+ addIcode(Icode_DUP);
+ stackChange(1);
+ visitExpression(child, 0);
+ addStringOp(Token.SETNAME, (String) names[i]);
+ addIcode(Icode_POP);
+ stackChange(-2);
+ child = child.getNext();
+ i++;
+ }
+ addIcode(Icode_POP);
+ stackChange(-1);
+ }
+
private void visitExpression(Node node, int contextFlags) {
int type = node.getType();
Node child = node.getFirstChild();
@@ -576,6 +670,151 @@ private void visitExpression(Node node, int contextFlags) {
}
break;
+ case Token.CLASS:
+ {
+ // child 0: superclass expression (or Token.NULL for base classes
+ // with methods)
+ // child 1: constructor FUNCTION node
+ // child 2..N: method FUNCTION nodes (optional)
+ Node superClassExpr = child;
+ Node constructorFn = child.getNext();
+ int fnIndex = constructorFn.getExistingIntProp(Node.FUNCTION_PROP);
+ // Evaluate the superclass expression
+ visitExpression(superClassExpr, 0);
+ // Create the constructor function with the superclass as homeObject
+ // stack: ... superClass -> ... constructorFunction
+ addIndexOp(Icode_CLASS_EXPR, fnIndex);
+ // stack doesn't change: superClass consumed, function pushed
+
+ // Define instance methods on constructor.prototype
+ String[] methodNames = (String[]) node.getProp(Node.CLASS_METHODS_PROP);
+ int[] methodKinds = (int[]) node.getProp(Node.CLASS_METHOD_KINDS_PROP);
+ // Track the node after the constructor for iterating method children
+ Node nextMethodNode = constructorFn.getNext();
+ if (methodNames != null) {
+ for (int i = 0; i < methodNames.length; i++) {
+ boolean computed = methodNames[i] == null;
+ if (computed) {
+ // Evaluate the key expression; stack: ... constructor key
+ visitExpression(nextMethodNode, 0);
+ nextMethodNode = nextMethodNode.getNext();
+ }
+ int methodFnIndex =
+ nextMethodNode.getExistingIntProp(Node.FUNCTION_PROP);
+ int kind = methodKinds == null ? 0 : methodKinds[i];
+ if (computed) {
+ addIndexPrefix(methodFnIndex);
+ if (kind == 1) {
+ addIcode(Icode_DEFINE_CLASS_COMPUTED_GETTER);
+ } else if (kind == 2) {
+ addIcode(Icode_DEFINE_CLASS_COMPUTED_SETTER);
+ } else {
+ addIcode(Icode_DEFINE_CLASS_COMPUTED_METHOD);
+ }
+ stackChange(-1);
+ } else {
+ addStringPrefix(methodNames[i]);
+ addIndexPrefix(methodFnIndex);
+ if (kind == 1) {
+ addIcode(Icode_DEFINE_CLASS_GETTER);
+ } else if (kind == 2) {
+ addIcode(Icode_DEFINE_CLASS_SETTER);
+ } else {
+ addIcode(Icode_DEFINE_CLASS_METHOD);
+ }
+ }
+ nextMethodNode = nextMethodNode.getNext();
+ }
+ }
+
+ // Define static methods on the constructor itself
+ String[] staticMethodNames =
+ (String[]) node.getProp(Node.CLASS_STATIC_METHODS_PROP);
+ int[] staticMethodKinds =
+ (int[]) node.getProp(Node.CLASS_STATIC_METHOD_KINDS_PROP);
+ if (staticMethodNames != null) {
+ for (int i = 0; i < staticMethodNames.length; i++) {
+ boolean computed = staticMethodNames[i] == null;
+ if (computed) {
+ visitExpression(nextMethodNode, 0);
+ nextMethodNode = nextMethodNode.getNext();
+ }
+ int methodFnIndex =
+ nextMethodNode.getExistingIntProp(Node.FUNCTION_PROP);
+ int kind = staticMethodKinds == null ? 0 : staticMethodKinds[i];
+ if (computed) {
+ addIndexPrefix(methodFnIndex);
+ if (kind == 1) {
+ addIcode(Icode_DEFINE_STATIC_CLASS_COMPUTED_GETTER);
+ } else if (kind == 2) {
+ addIcode(Icode_DEFINE_STATIC_CLASS_COMPUTED_SETTER);
+ } else {
+ addIcode(Icode_DEFINE_STATIC_CLASS_COMPUTED_METHOD);
+ }
+ stackChange(-1);
+ } else {
+ addStringPrefix(staticMethodNames[i]);
+ addIndexPrefix(methodFnIndex);
+ if (kind == 1) {
+ addIcode(Icode_DEFINE_STATIC_CLASS_GETTER);
+ } else if (kind == 2) {
+ addIcode(Icode_DEFINE_STATIC_CLASS_SETTER);
+ } else {
+ addIcode(Icode_DEFINE_STATIC_CLASS_METHOD);
+ }
+ }
+ nextMethodNode = nextMethodNode.getNext();
+ }
+ }
+
+ // Define static named fields on the constructor
+ String[] staticFieldNames =
+ (String[]) node.getProp(Node.CLASS_STATIC_FIELDS_PROP);
+ if (staticFieldNames != null) {
+ for (int i = 0; i < staticFieldNames.length; i++) {
+ visitExpression(nextMethodNode, 0);
+ addStringPrefix(staticFieldNames[i]);
+ addIcode(Icode_DEFINE_STATIC_CLASS_FIELD);
+ stackChange(-1);
+ nextMethodNode = nextMethodNode.getNext();
+ }
+ }
+
+ // Define static computed fields on the constructor
+ int staticComputedFieldCount =
+ node.getIntProp(Node.CLASS_STATIC_COMPUTED_FIELDS_COUNT, 0);
+ for (int i = 0; i < staticComputedFieldCount; i++) {
+ visitExpression(nextMethodNode, 0); // key
+ nextMethodNode = nextMethodNode.getNext();
+ visitExpression(nextMethodNode, 0); // value
+ addIcode(Icode_DEFINE_STATIC_CLASS_COMPUTED_FIELD);
+ stackChange(-2);
+ nextMethodNode = nextMethodNode.getNext();
+ }
+
+ // Evaluate instance computed field keys at class declaration time and
+ // stash them on the constructor for later use by field initializers.
+ int computedFieldKeysCount =
+ node.getIntProp(Node.CLASS_COMPUTED_FIELD_KEYS_COUNT, 0);
+ if (computedFieldKeysCount > 0) {
+ for (int i = 0; i < computedFieldKeysCount; i++) {
+ visitExpression(nextMethodNode, 0);
+ nextMethodNode = nextMethodNode.getNext();
+ }
+ addIndexOp(Icode_STORE_CLASS_COMPUTED_KEYS, computedFieldKeysCount);
+ stackChange(-computedFieldKeysCount);
+ }
+ }
+ break;
+
+ case Token.GET_CLASS_COMPUTED_KEY:
+ {
+ int index = node.getExistingIntProp(Node.LITERAL_INDEX_PROP);
+ addIndexOp(Icode_GET_CLASS_COMPUTED_KEY, index);
+ stackChange(1);
+ }
+ break;
+
case Token.LOCAL_LOAD:
{
int localIndex = getLocalBlockRef(node);
@@ -588,9 +827,15 @@ private void visitExpression(Node node, int contextFlags) {
{
Node lastChild = node.getLastChild();
while (child != lastChild) {
- visitExpression(child, 0);
- addIcode(Icode_POP);
- stackChange(-1);
+ if (child.getType() == Token.LOCAL_BLOCK) {
+ // Embedded statement-level side-effect (e.g. try/finally for
+ // iterator cleanup in destructuring). Produces no value, so no POP.
+ visitStatement(child, stackDepth);
+ } else {
+ visitExpression(child, 0);
+ addIcode(Icode_POP);
+ stackChange(-1);
+ }
child = child.getNext();
}
// Preserve tail context flag if any
@@ -675,6 +920,12 @@ private void visitExpression(Node node, int contextFlags) {
addUint8(callType);
addUint8(type == Token.NEW ? 1 : 0);
addUint16(lineNumber & 0xFFFF);
+ } else if (node.getIntProp(Node.SUPER_CONSTRUCTOR_CALL, 0) == 1) {
+ if (node.getIntProp(Node.SUPER_CONSTRUCTOR_SPREAD_CALL, 0) == 1) {
+ addIcode(Icode_CONSTRUCT_SUPER_SPREAD);
+ } else {
+ addIndexOp(Icode_CONSTRUCT_SUPER, argCount);
+ }
} else if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) {
addIndexOp(Icode_CALL_ON_SUPER, argCount);
} else {
@@ -866,6 +1117,8 @@ private void visitExpression(Node node, int contextFlags) {
case Token.BITNOT:
case Token.TYPEOF:
case Token.VOID:
+ case Token.TO_OBJECT_COERCIBLE:
+ case Token.ITERATOR_CLOSE_ABRUPT:
visitExpression(child, 0);
if (type == Token.VOID) {
addIcode(Icode_POP);
@@ -921,6 +1174,28 @@ private void visitExpression(Node node, int contextFlags) {
}
break;
+ case Token.DEFINE_FIELD:
+ // Children: target, key, value. Pushes all three, pops all three, leaves value
+ // on stack as the expression result. FIELD_KIND_PROP selects between storing
+ // a value (0), a getter (1), or a setter (2) on the private slot.
+ visitExpression(child, 0); // target
+ child = child.getNext();
+ visitExpression(child, 0); // key (a symbol-valued Node.LOAD_LITERAL)
+ child = child.getNext();
+ visitExpression(child, 0); // value (or accessor fn)
+ {
+ int fieldKind = node.getIntProp(Node.FIELD_KIND_PROP, 0);
+ if (fieldKind == 1) {
+ addIcode(Icode_DEFINE_PRIVATE_GETTER);
+ } else if (fieldKind == 2) {
+ addIcode(Icode_DEFINE_PRIVATE_SETTER);
+ } else {
+ addIcode(Icode_DEFINE_PRIVATE_FIELD);
+ }
+ }
+ stackChange(-2); // three pushed, one left (value)
+ break;
+
case Token.SETELEM:
case Token.SETELEM_OP:
visitExpression(child, 0);
@@ -1052,6 +1327,7 @@ private void visitExpression(Node node, int contextFlags) {
case Token.THIS:
case Token.SUPER:
case Token.THISFN:
+ case Token.NEW_TARGET:
case Token.FALSE:
case Token.TRUE:
addToken(type);
@@ -1065,10 +1341,18 @@ private void visitExpression(Node node, int contextFlags) {
case Token.ENUM_NEXT:
case Token.ENUM_ID:
+ case Token.ENUM_ASYNC_NEXT:
addIndexOp(type, getLocalBlockRef(node));
stackChange(1);
break;
+ case Token.ENUM_ASYNC_STEP:
+ // Child is the expression that evaluates to the awaited IteratorResult.
+ visitExpression(child, 0);
+ addIndexOp(type, getLocalBlockRef(node));
+ // opcode pops the result and pushes the boolean: net zero on top of the child.
+ break;
+
case Token.BIGINT:
addBigInt(node.getBigInt());
stackChange(1);
@@ -1082,6 +1366,14 @@ private void visitExpression(Node node, int contextFlags) {
}
break;
+ case Token.LOAD_LITERAL:
+ {
+ int index = node.getExistingIntProp(Node.LITERAL_INDEX_PROP);
+ addIndexOp(Token.LOAD_LITERAL, index);
+ stackChange(1);
+ }
+ break;
+
case Token.ARRAYLIT:
case Token.OBJECTLIT:
visitLiteral(node, child);
@@ -1156,16 +1448,27 @@ private void visitExpression(Node node, int contextFlags) {
case Token.YIELD:
case Token.YIELD_STAR:
+ case Token.AWAIT:
if (child != null) {
visitExpression(child, 0);
} else {
addIcode(Icode_UNDEF);
stackChange(1);
}
- if (type == Token.YIELD) {
- addToken(Token.YIELD);
- } else {
+ if (type == Token.YIELD_STAR) {
addIcode(Icode_YIELD_STAR);
+ } else {
+ // Inside an async generator we need to tell yield and await apart at the
+ // driver level. Wrap the value of an await in an AwaitMarker; a plain yield
+ // yields the raw value.
+ if (type == Token.AWAIT
+ && scriptOrFn instanceof FunctionNode
+ && ((FunctionNode) scriptOrFn).isAsyncGenerator()) {
+ addIcode(Icode_WRAP_AWAIT);
+ }
+ // Token.YIELD and Token.AWAIT both use the same yield opcode; the async
+ // Promise runner drives the generator for await.
+ addToken(Token.YIELD);
}
addUint16(node.getLineno() & 0xFFFF);
break;
@@ -1178,7 +1481,17 @@ private void visitExpression(Node node, int contextFlags) {
addToken(Token.ENTERWITH);
stackChange(-1);
visitExpression(with.getFirstChild(), 0);
- addToken(Token.LEAVEWITH);
+ addToken(Token.LEAVE_SCOPE);
+ break;
+ }
+
+ case Token.SCOPEEXPR:
+ {
+ Node enterScope = node.getFirstChild();
+ Node expr = enterScope.getNext();
+ visitEnterScope(enterScope, enterScope.getFirstChild());
+ visitExpression(expr.getFirstChild(), 0);
+ addToken(Token.LEAVE_SCOPE);
break;
}
@@ -1691,6 +2004,21 @@ private void markTargetLabel(Node target) {
labelTable[label] = iCodeTop;
}
+ /**
+ * Recursively clear labelId on every TARGET node in the subtree, so the subtree can be walked
+ * again to emit a fresh copy of its bytecode (e.g. for inlining a finally body at multiple
+ * non-exceptional callers). YIELD/AWAIT label IDs are left alone because the function's
+ * resumption table refers to them.
+ */
+ private void resetTargetLabels(Node node) {
+ if (node.getType() == Token.TARGET) {
+ node.labelId(-1);
+ }
+ for (Node child = node.getFirstChild(); child != null; child = child.getNext()) {
+ resetTargetLabels(child);
+ }
+ }
+
private void addGoto(Node target, int gotoOp) {
int label = getTargetLabel(target);
if (!(label < labelTableTop)) Kit.codeBug();
@@ -1934,6 +2262,61 @@ private void addIndexPrefix(int index) {
}
}
+ /**
+ * Register one or more exception handler entries covering [icodeStart, icodeEnd) but skipping
+ * any PC ranges in {@code excludeRanges}. The exclude ranges correspond to inlined finally
+ * bodies on this try's normal-completion paths; an exception thrown inside such a body must not
+ * be re-caught by the same try (which would re-enter the finally and double-run it), matching
+ * the prior JSR/RETSUB behavior where the body executed at the FINALLY block's PC outside the
+ * protected range.
+ */
+ private void addExceptionHandlersExcluding(
+ int icodeStart,
+ int icodeEnd,
+ int handlerStart,
+ boolean isFinally,
+ int exceptionObjectLocal,
+ int scopeLocal,
+ List excludeRanges) {
+ if (excludeRanges == null || excludeRanges.isEmpty()) {
+ addExceptionHandler(
+ icodeStart,
+ icodeEnd,
+ handlerStart,
+ isFinally,
+ exceptionObjectLocal,
+ scopeLocal);
+ return;
+ }
+ int cursor = icodeStart;
+ for (int[] r : excludeRanges) {
+ int rStart = r[0];
+ int rEnd = r[1];
+ if (rEnd <= cursor || rStart >= icodeEnd) {
+ continue;
+ }
+ int segStart = cursor;
+ int segEnd = Math.min(rStart, icodeEnd);
+ if (segEnd > segStart) {
+ addExceptionHandler(
+ segStart,
+ segEnd,
+ handlerStart,
+ isFinally,
+ exceptionObjectLocal,
+ scopeLocal);
+ }
+ cursor = Math.max(cursor, rEnd);
+ if (cursor >= icodeEnd) {
+ break;
+ }
+ }
+ if (cursor < icodeEnd) {
+ addExceptionHandler(
+ cursor, icodeEnd, handlerStart, isFinally, exceptionObjectLocal, scopeLocal);
+ }
+ }
+
private void addExceptionHandler(
int icodeStart,
int icodeEnd,
diff --git a/rhino/src/main/java/org/mozilla/javascript/Constructable.java b/rhino/src/main/java/org/mozilla/javascript/Constructable.java
index 28f7f2d7842..36e47736742 100644
--- a/rhino/src/main/java/org/mozilla/javascript/Constructable.java
+++ b/rhino/src/main/java/org/mozilla/javascript/Constructable.java
@@ -16,4 +16,8 @@ public interface Constructable {
* @return the allocated object
*/
Scriptable construct(Context cx, VarScope scope, Object[] args);
+
+ default Scriptable construct(Context cx, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return construct(cx, s, args);
+ }
}
diff --git a/rhino/src/main/java/org/mozilla/javascript/Context.java b/rhino/src/main/java/org/mozilla/javascript/Context.java
index f757c649e02..5307272e144 100644
--- a/rhino/src/main/java/org/mozilla/javascript/Context.java
+++ b/rhino/src/main/java/org/mozilla/javascript/Context.java
@@ -561,7 +561,7 @@ public static Object call(
if (factory == null) {
factory = ContextFactory.getGlobal();
}
- return call(factory, cx -> callable.call(cx, (VarScope) scope, thisObj, args));
+ return call(factory, cx -> callable.call(cx, scope, thisObj, args));
}
/** The method implements {@link ContextFactory#call(ContextAction)} logic. */
@@ -1198,7 +1198,8 @@ public static Object getUndefinedValue() {
public final Object evaluateScript(ScriptCompileSpec spec, VarScope scope) {
Script script = compileScript(spec);
if (script != null) {
- return script.exec(this, scope, scope);
+ return script.exec(
+ this, scope, ScriptableObject.getTopLevelScope(scope).getGlobalThis());
}
return null;
}
diff --git a/rhino/src/main/java/org/mozilla/javascript/ContextFactory.java b/rhino/src/main/java/org/mozilla/javascript/ContextFactory.java
index b90bee1941c..6300382e134 100644
--- a/rhino/src/main/java/org/mozilla/javascript/ContextFactory.java
+++ b/rhino/src/main/java/org/mozilla/javascript/ContextFactory.java
@@ -323,8 +323,8 @@ public final void initApplicationClassLoader(ClassLoader loader) {
* perform the real call. In this way execution of any script happens inside this function.
*/
protected Object doTopCall(
- Callable callable, Context cx, VarScope scope, Scriptable thisObj, Object[] args) {
- Object result = callable.call(cx, (VarScope) scope, thisObj, args);
+ Callable callable, Context cx, VarScope scope, Object thisObj, Object[] args) {
+ Object result = callable.call(cx, scope, thisObj, args);
return result instanceof ConsString ? result.toString() : result;
}
@@ -333,7 +333,7 @@ protected Object doTopCall(
* will create the first stack frame with scriptable code, it calls this method to perform the
* real call. In this way execution of any script happens inside this function.
*/
- protected Object doTopCall(Script script, Context cx, VarScope scope, Scriptable thisObj) {
+ protected Object doTopCall(Script script, Context cx, VarScope scope, Object thisObj) {
Object result = script.exec(cx, scope, thisObj);
return result instanceof ConsString ? result.toString() : result;
}
diff --git a/rhino/src/main/java/org/mozilla/javascript/DeclarationScope.java b/rhino/src/main/java/org/mozilla/javascript/DeclarationScope.java
index 1d9571fe169..9ddd57e833f 100644
--- a/rhino/src/main/java/org/mozilla/javascript/DeclarationScope.java
+++ b/rhino/src/main/java/org/mozilla/javascript/DeclarationScope.java
@@ -1,7 +1,7 @@
package org.mozilla.javascript;
public class DeclarationScope extends ScopeObject {
- private static final long serialVersionUID = -7471457301304454454L;
+ private static final long serialVersionUID = -7992031023451233550L;
public DeclarationScope(VarScope parentScope) {
super(parentScope);
diff --git a/rhino/src/main/java/org/mozilla/javascript/Delegator.java b/rhino/src/main/java/org/mozilla/javascript/Delegator.java
index ca7ed36146f..a706e020e14 100644
--- a/rhino/src/main/java/org/mozilla/javascript/Delegator.java
+++ b/rhino/src/main/java/org/mozilla/javascript/Delegator.java
@@ -253,7 +253,7 @@ public boolean hasInstance(Scriptable instance) {
* @see org.mozilla.javascript.Function#call
*/
@Override
- public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) {
+ public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) {
return ((Function) getDelegee()).call(cx, scope, thisObj, args);
}
@@ -286,4 +286,22 @@ public Scriptable construct(Context cx, VarScope scope, Object[] args) {
}
return ((Constructable) myDelegee).construct(cx, scope, args);
}
+
+ @Override
+ public Scriptable construct(Context cx, Object nt, VarScope s, Object thisObj, Object[] args) {
+ Scriptable myDelegee = getDelegee();
+ if (myDelegee == null) {
+ // this little trick allows us to declare prototype objects for Delegators
+ Delegator n = newInstance();
+ Scriptable delegee;
+ if (args.length == 0) {
+ delegee = cx.newObject(s);
+ } else {
+ delegee = ScriptRuntime.toObject(cx, s, args[0]);
+ }
+ n.setDelegee(delegee);
+ return n;
+ }
+ return ((Constructable) myDelegee).construct(cx, nt, s, thisObj, args);
+ }
}
diff --git a/rhino/src/main/java/org/mozilla/javascript/ES6AsyncGenerator.java b/rhino/src/main/java/org/mozilla/javascript/ES6AsyncGenerator.java
new file mode 100644
index 00000000000..10bb844a32c
--- /dev/null
+++ b/rhino/src/main/java/org/mozilla/javascript/ES6AsyncGenerator.java
@@ -0,0 +1,353 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.javascript;
+
+import static org.mozilla.javascript.ClassDescriptor.Builder.value;
+import static org.mozilla.javascript.ClassDescriptor.Destination.CTOR;
+import static org.mozilla.javascript.Symbol.Kind.REGULAR;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * Async generator object returned by invoking an {@code async function*}. Drives the underlying
+ * {@link ES6Generator} through a FIFO queue of pending {@code next}/{@code throw}/{@code return}
+ * requests. Each request returns a Promise that resolves to an IteratorResult.
+ */
+public final class ES6AsyncGenerator extends ScriptableObject {
+ private static final long serialVersionUID = 1L;
+
+ static final SymbolKey ASYNC_GENERATOR_TAG = new SymbolKey("AsyncGeneratorPrototype", REGULAR);
+
+ private static final ClassDescriptor DESCRIPTOR;
+
+ static {
+ DESCRIPTOR =
+ new ClassDescriptor.Builder(ASYNC_GENERATOR_TAG)
+ .withMethod(CTOR, "next", 1, ES6AsyncGenerator::js_next)
+ .withMethod(CTOR, "return", 1, ES6AsyncGenerator::js_return)
+ .withMethod(CTOR, "throw", 1, ES6AsyncGenerator::js_throw)
+ .withMethod(
+ CTOR,
+ SymbolKey.ASYNC_ITERATOR,
+ 0,
+ ES6AsyncGenerator::js_asyncIterator)
+ .withProp(
+ CTOR,
+ SymbolKey.TO_STRING_TAG,
+ value("AsyncGenerator", DONTENUM | READONLY))
+ .build();
+ }
+
+ private final ES6Generator inner;
+ private final VarScope homeScope;
+ private final Deque queue = new ArrayDeque<>();
+ private boolean draining = false;
+ private boolean completed = false;
+
+ static ScriptableObject init(Context cx, TopLevel scope, boolean sealed) {
+ NativeObject prototype = new NativeObject();
+ DESCRIPTOR.populateGlobal(cx, scope, prototype, sealed);
+ if (scope != null) {
+ scope.associateValue(ASYNC_GENERATOR_TAG, prototype);
+ }
+ return prototype;
+ }
+
+ /** Only for constructing the prototype. */
+ private ES6AsyncGenerator() {
+ this.inner = null;
+ this.homeScope = null;
+ }
+
+ public ES6AsyncGenerator(VarScope scope, ES6Generator inner) {
+ this.inner = inner;
+ this.homeScope = scope;
+ VarScope top = ScriptableObject.getTopLevelScope(scope);
+ this.setParentScope(top);
+ ScriptableObject prototype =
+ (ScriptableObject) ScriptableObject.getTopScopeValue(top, ASYNC_GENERATOR_TAG);
+ if (prototype != null) {
+ this.setPrototype(prototype);
+ }
+ }
+
+ @Override
+ public String getClassName() {
+ return "AsyncGenerator";
+ }
+
+ private static ES6AsyncGenerator realThis(Object thisObj) {
+ return LambdaConstructor.convertThisObject(thisObj, ES6AsyncGenerator.class);
+ }
+
+ private static Object js_next(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return realThis(thisObj)
+ .enqueue(cx, s, RequestKind.NEXT, args.length > 0 ? args[0] : Undefined.instance);
+ }
+
+ private static Object js_return(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return realThis(thisObj)
+ .enqueue(cx, s, RequestKind.RETURN, args.length > 0 ? args[0] : Undefined.instance);
+ }
+
+ private static Object js_throw(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return realThis(thisObj)
+ .enqueue(cx, s, RequestKind.THROW, args.length > 0 ? args[0] : Undefined.instance);
+ }
+
+ private static Object js_asyncIterator(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ return thisObj;
+ }
+
+ private Object enqueue(Context cx, VarScope scope, RequestKind kind, Object value) {
+ VarScope topScope = ScriptableObject.getTopLevelScope(scope);
+ Object promiseCtor = TopLevel.getBuiltinCtor(cx, topScope, TopLevel.Builtins.Promise);
+ NativePromise.Capability cap = new NativePromise.Capability(cx, scope, promiseCtor);
+ queue.addLast(new Request(kind, value, cap));
+ // Only start a drain if nothing is already in progress. draining stays true while a
+ // step is suspended on an awaited Promise so new enqueues don't race the in-flight step.
+ if (!draining) {
+ drain(cx, scope);
+ }
+ return cap.promise;
+ }
+
+ private void drain(Context cx, VarScope scope) {
+ draining = true;
+ while (!queue.isEmpty()) {
+ Request req = queue.peekFirst();
+ if (completed) {
+ resolveCompleted(cx, scope, req);
+ queue.pollFirst();
+ continue;
+ }
+ if (!step(cx, scope, req)) {
+ // step() suspended on an external Promise; resume() will clear draining
+ // and call drain() again when the microtask fires.
+ return;
+ }
+ }
+ draining = false;
+ }
+
+ /**
+ * Run the generator once for the given request. Returns {@code true} if the request was fully
+ * resolved synchronously (and can be dequeued immediately), {@code false} if the flow is
+ * suspended on an awaited Promise and will continue via its then-reactions.
+ */
+ private boolean step(Context cx, VarScope scope, Request req) {
+ Scriptable result;
+ try {
+ switch (req.kind) {
+ case NEXT:
+ result = inner.resumeLocal(cx, scope, req.value);
+ break;
+ case THROW:
+ result =
+ inner.resumeAbruptLocal(
+ cx, scope, NativeGenerator.GENERATOR_THROW, req.value);
+ break;
+ case RETURN:
+ default:
+ result =
+ inner.resumeAbruptLocal(
+ cx, scope, NativeGenerator.GENERATOR_CLOSE, req.value);
+ break;
+ }
+ } catch (JavaScriptException jse) {
+ completed = true;
+ req.capability.reject.call(
+ cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {jse.getValue()});
+ queue.pollFirst();
+ return true;
+ } catch (RhinoException re) {
+ completed = true;
+ req.capability.reject.call(
+ cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {re.getMessage()});
+ queue.pollFirst();
+ return true;
+ }
+
+ boolean done =
+ Boolean.TRUE.equals(
+ ScriptableObject.getProperty(result, ES6Iterator.DONE_PROPERTY));
+ Object value = ScriptableObject.getProperty(result, ES6Iterator.VALUE_PROPERTY);
+
+ if (done) {
+ completed = true;
+ req.capability.resolve.call(
+ cx,
+ scope,
+ Undefined.SCRIPTABLE_UNDEFINED,
+ new Object[] {makeIteratorResult(cx, scope, value, true)});
+ queue.pollFirst();
+ return true;
+ }
+
+ if (value instanceof ScriptRuntime.AwaitMarker) {
+ // Await point: resolve the inner value, resume the generator with the result, and
+ // stay on the same request.
+ awaitAndContinue(cx, scope, ((ScriptRuntime.AwaitMarker) value).getValue(), req);
+ return false;
+ }
+
+ // Yield point: await the yielded value, then resolve the consumer's pending promise.
+ awaitAndYield(cx, scope, value, req);
+ return false;
+ }
+
+ /** Resolve a request when the generator is already completed. */
+ private void resolveCompleted(Context cx, VarScope scope, Request req) {
+ switch (req.kind) {
+ case THROW:
+ req.capability.reject.call(
+ cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {req.value});
+ break;
+ case RETURN:
+ req.capability.resolve.call(
+ cx,
+ scope,
+ Undefined.SCRIPTABLE_UNDEFINED,
+ new Object[] {makeIteratorResult(cx, scope, req.value, true)});
+ break;
+ case NEXT:
+ default:
+ req.capability.resolve.call(
+ cx,
+ scope,
+ Undefined.SCRIPTABLE_UNDEFINED,
+ new Object[] {makeIteratorResult(cx, scope, Undefined.instance, true)});
+ break;
+ }
+ }
+
+ private void awaitAndContinue(Context cx, VarScope scope, Object awaited, Request req) {
+ VarScope topScope = ScriptableObject.getTopLevelScope(scope);
+ Object promiseCtor = TopLevel.getBuiltinCtor(cx, topScope, TopLevel.Builtins.Promise);
+ NativePromise p =
+ (NativePromise) NativePromise.resolveInternal(cx, scope, promiseCtor, awaited);
+ VarScope driverScope = homeScope != null ? homeScope : scope;
+ p.then(
+ cx,
+ scope,
+ new Object[] {
+ new LambdaFunction(
+ topScope,
+ 1,
+ (cx2, s, thisObj, args) -> {
+ Object v = args.length > 0 ? args[0] : Undefined.instance;
+ // Resume the same request with the awaited value.
+ req.value = v;
+ req.kind = RequestKind.NEXT;
+ resumeFromMicrotask(cx2, driverScope);
+ return Undefined.instance;
+ }),
+ new LambdaFunction(
+ topScope,
+ 1,
+ (cx2, s, thisObj, args) -> {
+ Object r = args.length > 0 ? args[0] : Undefined.instance;
+ // Resume the same request by throwing the rejection.
+ req.value = r;
+ req.kind = RequestKind.THROW;
+ resumeFromMicrotask(cx2, driverScope);
+ return Undefined.instance;
+ })
+ });
+ }
+
+ private void awaitAndYield(Context cx, VarScope scope, Object yielded, Request req) {
+ VarScope topScope = ScriptableObject.getTopLevelScope(scope);
+ Object promiseCtor = TopLevel.getBuiltinCtor(cx, topScope, TopLevel.Builtins.Promise);
+ NativePromise p =
+ (NativePromise) NativePromise.resolveInternal(cx, scope, promiseCtor, yielded);
+ VarScope driverScope = homeScope != null ? homeScope : scope;
+ p.then(
+ cx,
+ scope,
+ new Object[] {
+ new LambdaFunction(
+ topScope,
+ 1,
+ (cx2, s, thisObj, args) -> {
+ Object v = args.length > 0 ? args[0] : Undefined.instance;
+ req.capability.resolve.call(
+ cx2,
+ s,
+ Undefined.SCRIPTABLE_UNDEFINED,
+ new Object[] {makeIteratorResult(cx2, s, v, false)});
+ // This request is done; move on to the next.
+ if (queue.peekFirst() == req) {
+ queue.pollFirst();
+ }
+ resumeFromMicrotask(cx2, driverScope);
+ return Undefined.instance;
+ }),
+ new LambdaFunction(
+ topScope,
+ 1,
+ (cx2, s, thisObj, args) -> {
+ Object r = args.length > 0 ? args[0] : Undefined.instance;
+ // A rejected yielded value: forward as a throw into the generator
+ // on the same request.
+ req.value = r;
+ req.kind = RequestKind.THROW;
+ resumeFromMicrotask(cx2, driverScope);
+ return Undefined.instance;
+ })
+ });
+ }
+
+ /** Re-enter the drain loop from a microtask callback. */
+ private void resumeFromMicrotask(Context cx, VarScope scope) {
+ boolean needTopCall = !ScriptRuntime.hasTopCall(cx);
+ if (needTopCall) {
+ cx.topCallScope = ScriptableObject.getTopLevelScope(scope);
+ cx.useDynamicScope = cx.hasFeature(Context.FEATURE_DYNAMIC_SCOPE);
+ }
+ try {
+ // The suspended step has completed; release the draining flag before re-entering.
+ draining = false;
+ drain(cx, scope);
+ } finally {
+ if (needTopCall) {
+ cx.topCallScope = null;
+ }
+ }
+ }
+
+ private static Scriptable makeIteratorResult(
+ Context cx, VarScope scope, Object value, boolean done) {
+ Scriptable obj = cx.newObject(scope);
+ ScriptableObject.putProperty(obj, ES6Iterator.VALUE_PROPERTY, value);
+ ScriptableObject.putProperty(obj, ES6Iterator.DONE_PROPERTY, Boolean.valueOf(done));
+ return obj;
+ }
+
+ private enum RequestKind {
+ NEXT,
+ THROW,
+ RETURN
+ }
+
+ private static final class Request {
+ RequestKind kind;
+ Object value;
+ final NativePromise.Capability capability;
+
+ Request(RequestKind kind, Object value, NativePromise.Capability capability) {
+ this.kind = kind;
+ this.value = value;
+ this.capability = capability;
+ }
+ }
+}
diff --git a/rhino/src/main/java/org/mozilla/javascript/ES6Generator.java b/rhino/src/main/java/org/mozilla/javascript/ES6Generator.java
index ca242b65fbc..1e84b1015f4 100644
--- a/rhino/src/main/java/org/mozilla/javascript/ES6Generator.java
+++ b/rhino/src/main/java/org/mozilla/javascript/ES6Generator.java
@@ -6,10 +6,30 @@
package org.mozilla.javascript;
-public final class ES6Generator extends ScriptableObject {
- private static final long serialVersionUID = 1645892441041347273L;
+import static org.mozilla.javascript.ClassDescriptor.Builder.value;
+import static org.mozilla.javascript.ClassDescriptor.Destination.CTOR;
+import static org.mozilla.javascript.Symbol.Kind.REGULAR;
- static final Object GENERATOR_TAG = "Generator";
+public final class ES6Generator extends ScriptableObject {
+ private static final long serialVersionUID = -1617667918827493330L;
+
+ static final SymbolKey GENERATOR_TAG = new SymbolKey("GeneratorPrototype", REGULAR);
+
+ private static final ClassDescriptor DESCRIPTOR;
+
+ static {
+ DESCRIPTOR =
+ new ClassDescriptor.Builder(GENERATOR_TAG)
+ .withMethod(CTOR, "next", 1, ES6Generator::js_next)
+ .withMethod(CTOR, "return", 1, ES6Generator::js_return)
+ .withMethod(CTOR, "throw", 1, ES6Generator::js_throw)
+ .withMethod(CTOR, SymbolKey.ITERATOR, 0, ES6Generator::js_iterator)
+ .withProp(
+ CTOR,
+ SymbolKey.TO_STRING_TAG,
+ value("Generator", DONTENUM | READONLY))
+ .build();
+ }
private JSFunction function;
private Object savedState;
@@ -17,34 +37,27 @@ public final class ES6Generator extends ScriptableObject {
private int lineNumber;
private State state = State.SUSPENDED_START;
private Object delegee;
-
- static ES6Generator init(TopLevel scope, boolean sealed) {
-
- ES6Generator prototype = new ES6Generator();
- if (scope != null) {
- prototype.setParentScope(scope);
- prototype.setPrototype(getObjectPrototype(scope));
- }
-
- // Define prototype methods using LambdaFunction
- LambdaFunction next = new LambdaFunction(scope, "next", 1, ES6Generator::js_next);
- ScriptableObject.defineProperty(prototype, "next", next, DONTENUM);
-
- LambdaFunction returnFunc = new LambdaFunction(scope, "return", 1, ES6Generator::js_return);
- ScriptableObject.defineProperty(prototype, "return", returnFunc, DONTENUM);
-
- LambdaFunction throwFunc = new LambdaFunction(scope, "throw", 1, ES6Generator::js_throw);
- ScriptableObject.defineProperty(prototype, "throw", throwFunc, DONTENUM);
-
- LambdaFunction iterator =
- new LambdaFunction(scope, "[Symbol.iterator]", 0, ES6Generator::js_iterator);
- prototype.defineProperty(SymbolKey.ITERATOR, iterator, DONTENUM);
-
- prototype.defineProperty(SymbolKey.TO_STRING_TAG, "Generator", DONTENUM | READONLY);
-
- if (sealed) {
- prototype.sealObject();
- }
+ private boolean delegeeIsAsync;
+ // Cached lookup of delegee's "next" method, per spec's IteratorRecord.[[NextMethod]]:
+ // the method must be fetched once (when the iterator is obtained) and reused for every
+ // step, rather than re-evaluated (and re-running any "next" accessor) on each call.
+ private ScriptRuntime.LookupResult delegeeNext;
+ // When the async driver is awaiting a Promise returned by the async delegee (from
+ // next/throw/return), the next resumeLocal/resumeAbruptLocal call provides the awaited
+ // IteratorResult (or rejection reason) and must be routed back into the delegee state
+ // machine instead of into the inner generator body.
+ private boolean awaitingDelegeeStep;
+ // Op used to resume the inner generator once the delegee signals done=true while awaiting a
+ // step. GENERATOR_SEND for next()/throw(); GENERATOR_CLOSE for return().
+ private int delegeeDoneOp = NativeGenerator.GENERATOR_SEND;
+
+ static ScriptableObject init(Context cx, TopLevel scope, boolean sealed) {
+
+ NativeObject prototype = new NativeObject();
+ DESCRIPTOR.populateGlobal(cx, scope, prototype, sealed);
+
+ var iterCtor = (JSFunction) scope.get("Iterator", scope);
+ prototype.setPrototype((Scriptable) iterCtor.getPrototypeProperty());
// Need to access Generator prototype when constructing
// Generator instances, but don't have a generator constructor
@@ -75,8 +88,8 @@ public ES6Generator(VarScope scope, JSFunction function, Object savedState) {
// If function.prototype is not an Object, use the intrinsic default prototype
// Ref: Ecma 2026, 10.1.14 GetPrototypeFromConstructor step 4.
// See test262: language/statements/generators/default-proto.js
- ES6Generator prototype =
- (ES6Generator) ScriptableObject.getTopScopeValue(top, GENERATOR_TAG);
+ ScriptableObject prototype =
+ (ScriptableObject) ScriptableObject.getTopScopeValue(top, GENERATOR_TAG);
this.setPrototype(prototype);
}
}
@@ -90,34 +103,44 @@ private static ES6Generator realThis(Object thisObj) {
return LambdaConstructor.convertThisObject(thisObj, ES6Generator.class);
}
- private static Object js_return(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ private void clearDelegee() {
+ delegee = null;
+ delegeeIsAsync = false;
+ delegeeNext = null;
+ }
+
+ private static Object js_return(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
ES6Generator generator = realThis(thisObj);
Object value = args.length >= 1 ? args[0] : Undefined.instance;
if (generator.delegee == null) {
- return generator.resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_CLOSE, value);
+ return generator.resumeAbruptLocal(cx, s, NativeGenerator.GENERATOR_CLOSE, value);
}
- return generator.resumeDelegeeReturn(cx, scope, value);
+ return generator.resumeDelegeeReturn(cx, s, value);
}
- private static Object js_next(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ private static Object js_next(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
ES6Generator generator = realThis(thisObj);
Object value = args.length >= 1 ? args[0] : Undefined.instance;
if (generator.delegee == null) {
- return generator.resumeLocal(cx, scope, value);
+ return generator.resumeLocal(cx, s, value);
}
- return generator.resumeDelegee(cx, scope, value);
+ return generator.resumeDelegee(cx, s, value);
}
- private static Object js_throw(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ private static Object js_throw(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
ES6Generator generator = realThis(thisObj);
Object value = args.length >= 1 ? args[0] : Undefined.instance;
if (generator.delegee == null) {
- return generator.resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, value);
+ return generator.resumeAbruptLocal(cx, s, NativeGenerator.GENERATOR_THROW, value);
}
- return generator.resumeDelegeeThrow(cx, scope, value);
+ return generator.resumeDelegeeThrow(cx, s, value);
}
- private static Object js_iterator(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ private static Object js_iterator(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
return thisObj;
}
@@ -127,18 +150,26 @@ private Scriptable resumeDelegee(Context cx, VarScope scope, Object value) {
Object[] nextArgs =
Undefined.isUndefined(value) ? ScriptRuntime.emptyArgs : new Object[] {value};
- var nextFn = ScriptRuntime.getPropAndThis(delegee, ES6Iterator.NEXT_METHOD, cx, scope);
- Object nr = nextFn.call(cx, scope, nextArgs);
+ Object nr = delegeeNext.call(cx, scope, nextArgs);
Scriptable nextResult = ScriptableObject.ensureScriptable(nr);
if (ScriptRuntime.isIteratorDone(cx, nextResult)) {
// Iterator is "done".
- delegee = null;
+ Object doneValue =
+ ScriptableObject.getProperty(nextResult, ES6Iterator.VALUE_PROPERTY);
+ clearDelegee();
// Return a result to the original generator
- return resumeLocal(
- cx,
- scope,
- ScriptableObject.getProperty(nextResult, ES6Iterator.VALUE_PROPERTY));
+ return resumeLocal(cx, scope, doneValue);
+ }
+ if (function.isAsync() && function.isGeneratorFunction()) {
+ // For async generators yield* extracts IteratorValue(innerResult) and
+ // AsyncGeneratorYields it (spec step vi). Read value now and return a fresh
+ // result so the async driver doesn't re-invoke the user's "done" accessor.
+ Object yieldValue =
+ ScriptableObject.getProperty(nextResult, ES6Iterator.VALUE_PROPERTY);
+ Scriptable result = ES6Iterator.makeIteratorResult(cx, scope, Boolean.FALSE);
+ ScriptableObject.putProperty(result, ES6Iterator.VALUE_PROPERTY, yieldValue);
+ return result;
}
// Otherwise, we have a normal result and should continue
return nextResult;
@@ -146,7 +177,7 @@ private Scriptable resumeDelegee(Context cx, VarScope scope, Object value) {
} catch (RhinoException re) {
// Exceptions from the delegee should be handled by the enclosing
// generator, including if they're because functions can't be found.
- delegee = null;
+ clearDelegee();
return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, re);
}
}
@@ -165,7 +196,7 @@ private Scriptable resumeDelegeeThrow(Context cx, VarScope scope, Object value)
returnCalled = true;
callReturnOptionally(cx, scope, Undefined.instance);
} finally {
- delegee = null;
+ clearDelegee();
}
return resumeLocal(
cx,
@@ -187,7 +218,7 @@ private Scriptable resumeDelegeeThrow(Context cx, VarScope scope, Object value)
}
}
} finally {
- delegee = null;
+ clearDelegee();
}
return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, re);
}
@@ -204,7 +235,7 @@ private Scriptable resumeDelegeeReturn(Context cx, VarScope scope, Object value)
if (retResult != null && !Undefined.isUndefined(retResult)) {
if (ScriptRuntime.isIteratorDone(cx, retResult)) {
// Iterator is "done".
- delegee = null;
+ clearDelegee();
// Return a result to the original generator
return resumeAbruptLocal(
cx,
@@ -219,18 +250,165 @@ private Scriptable resumeDelegeeReturn(Context cx, VarScope scope, Object value)
}
// No "return" -- let the original iterator return the value.
- delegee = null;
+ clearDelegee();
return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_CLOSE, value);
} catch (RhinoException re) {
// Exceptions from the delegee should be handled by the enclosing
// generator, including if they're because functions can't be found.
- delegee = null;
+ clearDelegee();
return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, re);
}
}
- private Scriptable resumeLocal(Context cx, VarScope scope, Object value) {
+ /**
+ * Call {@code next} on an async delegee. The delegee returns a Promise that the async driver
+ * awaits; control flow resumes in {@link #processAsyncDelegeeResult} with the resolved
+ * IteratorResult (or in {@link #resumeAbruptLocal} on rejection).
+ */
+ private Scriptable resumeAsyncDelegee(Context cx, VarScope scope, Object value) {
+ try {
+ Object[] nextArgs =
+ Undefined.isUndefined(value) ? ScriptRuntime.emptyArgs : new Object[] {value};
+ Object promise = delegeeNext.call(cx, scope, nextArgs);
+ return awaitDelegeeStep(cx, scope, promise, NativeGenerator.GENERATOR_SEND);
+ } catch (RhinoException re) {
+ clearDelegee();
+ return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, re);
+ }
+ }
+
+ private Scriptable resumeAsyncDelegeeThrow(Context cx, VarScope scope, Object value) {
+ Object throwFn;
+ try {
+ throwFn = ScriptableObject.getProperty((Scriptable) delegee, "throw");
+ } catch (RhinoException re) {
+ clearDelegee();
+ return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, re);
+ }
+ if (throwFn == Scriptable.NOT_FOUND || throwFn == null || Undefined.isUndefined(throwFn)) {
+ // No throw method: call return optionally, then throw original value into inner.
+ try {
+ callReturnOptionally(cx, scope, Undefined.instance);
+ } catch (RhinoException re2) {
+ clearDelegee();
+ return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, re2);
+ }
+ clearDelegee();
+ return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, value);
+ }
+ if (!(throwFn instanceof Callable)) {
+ clearDelegee();
+ return resumeAbruptLocal(
+ cx,
+ scope,
+ NativeGenerator.GENERATOR_THROW,
+ ScriptRuntime.notFunctionError((Scriptable) delegee, "throw"));
+ }
+ try {
+ Object promise =
+ ((Callable) throwFn)
+ .call(cx, scope, (Scriptable) delegee, new Object[] {value});
+ return awaitDelegeeStep(cx, scope, promise, NativeGenerator.GENERATOR_SEND);
+ } catch (RhinoException re) {
+ clearDelegee();
+ return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, re);
+ }
+ }
+
+ private Scriptable resumeAsyncDelegeeReturn(Context cx, VarScope scope, Object value) {
+ Object retFn;
+ try {
+ retFn =
+ ScriptRuntime.getObjectPropNoWarn(
+ delegee, ES6Iterator.RETURN_METHOD, cx, scope);
+ } catch (RhinoException re) {
+ clearDelegee();
+ return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, re);
+ }
+ if (retFn == null || Undefined.isUndefined(retFn)) {
+ // No "return" -- close the inner with the caller's value.
+ clearDelegee();
+ return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_CLOSE, value);
+ }
+ if (!(retFn instanceof Callable)) {
+ clearDelegee();
+ return resumeAbruptLocal(
+ cx,
+ scope,
+ NativeGenerator.GENERATOR_THROW,
+ ScriptRuntime.notFunctionError(
+ (Scriptable) delegee, ES6Iterator.RETURN_METHOD));
+ }
+ Object[] retArgs =
+ Undefined.isUndefined(value) ? ScriptRuntime.emptyArgs : new Object[] {value};
+ try {
+ Object promise = ((Callable) retFn).call(cx, scope, (Scriptable) delegee, retArgs);
+ return awaitDelegeeStep(cx, scope, promise, NativeGenerator.GENERATOR_CLOSE);
+ } catch (RhinoException re) {
+ clearDelegee();
+ return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, re);
+ }
+ }
+
+ /**
+ * Wrap a Promise returned by an async delegee method in an {@link ScriptRuntime.AwaitMarker} so
+ * the async driver awaits it. When the await settles the driver will call back into resumeLocal
+ * (on fulfilment) or resumeAbruptLocal (on rejection).
+ */
+ private Scriptable awaitDelegeeStep(Context cx, VarScope scope, Object promise, int doneOp) {
+ awaitingDelegeeStep = true;
+ delegeeDoneOp = doneOp;
+ Scriptable result = ES6Iterator.makeIteratorResult(cx, scope, Boolean.FALSE);
+ ScriptableObject.putProperty(
+ result, ES6Iterator.VALUE_PROPERTY, new ScriptRuntime.AwaitMarker(promise));
+ return result;
+ }
+
+ /**
+ * Process the IteratorResult that the driver produced by awaiting the Promise from an async
+ * delegee method. If the result signals {@code done}, resume the inner generator with the
+ * result's value (using SEND or CLOSE as recorded in {@link #delegeeDoneOp}); otherwise yield
+ * the value to the consumer.
+ */
+ private Scriptable processAsyncDelegeeResult(Context cx, VarScope scope, Object awaited) {
+ if (!(awaited instanceof Scriptable)) {
+ clearDelegee();
+ return resumeAbruptLocal(
+ cx,
+ scope,
+ NativeGenerator.GENERATOR_THROW,
+ ScriptRuntime.typeErrorById("msg.invalid.iterator"));
+ }
+ Scriptable ir = (Scriptable) awaited;
+ boolean done = ScriptRuntime.isIteratorDone(cx, ir);
+ Object value = ScriptableObject.getProperty(ir, ES6Iterator.VALUE_PROPERTY);
+ if (done) {
+ int op = delegeeDoneOp;
+ clearDelegee();
+ if (op == NativeGenerator.GENERATOR_CLOSE) {
+ return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_CLOSE, value);
+ }
+ return resumeLocal(cx, scope, value);
+ }
+ // Return a fresh result so the async driver does not re-invoke the user's
+ // "done"/"value" accessors that have already been read here.
+ Scriptable result = ES6Iterator.makeIteratorResult(cx, scope, Boolean.FALSE);
+ ScriptableObject.putProperty(result, ES6Iterator.VALUE_PROPERTY, value);
+ return result;
+ }
+
+ Scriptable resumeLocal(Context cx, VarScope scope, Object value) {
+ if (delegee != null) {
+ if (awaitingDelegeeStep) {
+ awaitingDelegeeStep = false;
+ return processAsyncDelegeeResult(cx, scope, value);
+ }
+ if (delegeeIsAsync) {
+ return resumeAsyncDelegee(cx, scope, value);
+ }
+ return resumeDelegee(cx, scope, value);
+ }
if (state == State.COMPLETED) {
return ES6Iterator.makeIteratorResult(cx, scope, Boolean.TRUE);
}
@@ -244,35 +422,52 @@ private Scriptable resumeLocal(Context cx, VarScope scope, Object value) {
try {
Object r =
function.resumeGenerator(
- cx,
- (VarScope) scope,
- NativeGenerator.GENERATOR_SEND,
- savedState,
- value);
+ cx, scope, NativeGenerator.GENERATOR_SEND, savedState, value);
if (r instanceof YieldStarResult) {
// This special result tells us that we are executing a "yield *"
state = State.SUSPENDED_YIELD;
YieldStarResult ysResult = (YieldStarResult) r;
+ Object delegeeTmp;
+ boolean delegeeIsAsyncTmp;
+
try {
- delegee = ScriptRuntime.callIterator(ysResult.getResult(), cx, scope);
+ if (function.isAsync() && function.isGeneratorFunction()) {
+ // In async generators, yield* first tries Symbol.asyncIterator and only
+ // falls back to Symbol.iterator when that lookup does not yield a method.
+ ScriptRuntime.AsyncIteratorResult ar =
+ ScriptRuntime.callAsyncIterator(ysResult.getResult(), cx, scope);
+ delegeeTmp = ar.getIterator();
+ delegeeIsAsyncTmp = ar.isAsync();
+ } else {
+ delegeeTmp = ScriptRuntime.callIterator(ysResult.getResult(), cx, scope);
+ delegeeIsAsyncTmp = false;
+ }
+ if (!ScriptRuntime.isObject(delegeeTmp)) {
+ delegee = null;
+ delegeeIsAsync = false;
+ throw ScriptRuntime.typeError("Iterator must be an object");
+ } else {
+ delegee = delegeeTmp;
+ delegeeIsAsync = delegeeIsAsyncTmp;
+ }
+
+ // Per spec GetIteratorFromMethod: fetch "next" once when the iterator is
+ // obtained and reuse it for every step.
+ delegeeNext =
+ ScriptRuntime.getPropAndThis(
+ delegee, ES6Iterator.NEXT_METHOD, cx, scope);
} catch (RhinoException re) {
// Need to handle exceptions if the iterator cannot be called.
return resumeAbruptLocal(cx, scope, NativeGenerator.GENERATOR_THROW, re);
}
- Scriptable delResult;
- try {
- // Re-execute but update state in case we end up back here
- // Value shall be Undefined based on the very complex spec!
- delResult = resumeDelegee(cx, scope, Undefined.instance);
- } finally {
- state = State.EXECUTING;
+ // Re-execute but update state in case we end up back here
+ // Value shall be Undefined based on the very complex spec!
+ if (delegeeIsAsync) {
+ return resumeAsyncDelegee(cx, scope, Undefined.instance);
}
- if (ScriptRuntime.isIteratorDone(cx, delResult)) {
- state = State.COMPLETED;
- }
- return delResult;
+ return resumeDelegee(cx, scope, Undefined.instance);
}
ScriptableObject.putProperty(result, ES6Iterator.VALUE_PROPERTY, r);
@@ -308,7 +503,28 @@ private Scriptable resumeLocal(Context cx, VarScope scope, Object value) {
return result;
}
- private Scriptable resumeAbruptLocal(Context cx, VarScope scope, int op, Object value) {
+ Scriptable resumeAbruptLocal(Context cx, VarScope scope, int op, Object value) {
+ if (delegee != null) {
+ if (awaitingDelegeeStep) {
+ // We were awaiting a Promise from the delegee when the driver delivered a
+ // rejection (op==THROW) as part of that same request. Tear down the delegee and
+ // fall through to throw the rejection into the inner generator at the yield*.
+ awaitingDelegeeStep = false;
+ clearDelegee();
+ } else if (delegeeIsAsync) {
+ if (op == NativeGenerator.GENERATOR_CLOSE) {
+ return resumeAsyncDelegeeReturn(cx, scope, value);
+ }
+ return resumeAsyncDelegeeThrow(cx, scope, value);
+ } else {
+ if (op == NativeGenerator.GENERATOR_THROW) {
+ return resumeDelegeeThrow(cx, scope, value);
+ }
+ if (op == NativeGenerator.GENERATOR_CLOSE) {
+ return resumeDelegeeReturn(cx, scope, value);
+ }
+ }
+ }
if (state == State.EXECUTING) {
throw ScriptRuntime.typeErrorById("msg.generator.executing");
}
@@ -343,7 +559,7 @@ private Scriptable resumeAbruptLocal(Context cx, VarScope scope, int op, Object
}
try {
- Object r = function.resumeGenerator(cx, (VarScope) scope, op, savedState, throwValue);
+ Object r = function.resumeGenerator(cx, scope, op, savedState, throwValue);
ScriptableObject.putProperty(result, ES6Iterator.VALUE_PROPERTY, r);
// If we get here without an exception we can still run.
state = State.SUSPENDED_YIELD;
@@ -375,7 +591,7 @@ private Scriptable resumeAbruptLocal(Context cx, VarScope scope, int op, Object
// After an abrupt completion we are always, umm, complete,
// and we will never delegate to the delegee again
if (state == State.COMPLETED) {
- delegee = null;
+ clearDelegee();
ScriptableObject.putProperty(result, ES6Iterator.DONE_PROPERTY, Boolean.TRUE);
}
}
diff --git a/rhino/src/main/java/org/mozilla/javascript/ES6Iterator.java b/rhino/src/main/java/org/mozilla/javascript/ES6Iterator.java
index cec23c0bb74..b821ccbf036 100644
--- a/rhino/src/main/java/org/mozilla/javascript/ES6Iterator.java
+++ b/rhino/src/main/java/org/mozilla/javascript/ES6Iterator.java
@@ -6,6 +6,10 @@
package org.mozilla.javascript;
+import static org.mozilla.javascript.ClassDescriptor.Builder.value;
+import static org.mozilla.javascript.ClassDescriptor.Destination.CTOR;
+import static org.mozilla.javascript.ClassDescriptor.Destination.PROTO;
+
public abstract class ES6Iterator extends ScriptableObject {
private static final long serialVersionUID = 2438373029140003950L;
@@ -16,35 +20,104 @@ public abstract class ES6Iterator extends ScriptableObject {
public static final String VALUE_PROPERTY = "value";
public static final String RETURN_METHOD = "return";
- protected static void init(
- TopLevel scope, boolean sealed, ScriptableObject prototype, String tag) {
- if (scope != null) {
- prototype.setParentScope(scope);
- prototype.setPrototype(getObjectPrototype(scope));
- }
-
- // Define prototype methods using LambdaFunction
- LambdaFunction next = new LambdaFunction(scope, NEXT_METHOD, 0, ES6Iterator::js_next);
- ScriptableObject.defineProperty(prototype, NEXT_METHOD, next, DONTENUM);
+ static final String ITERATOR_CLASS_NAME = "Iterator";
+ // Distinct from ITERATOR_CLASS_NAME: the "Iterator" associateValue slot is
+ // owned by NativeIterator for the StopIteration object (consulted by generators).
+ static final String ITERATOR_PROTOTYPE_TAG = "IteratorPrototype";
+ static final String WRAP_FOR_VALID_TAG = "IteratorHelper";
- LambdaFunction iterator =
- new LambdaFunction(scope, "[Symbol.iterator]", 1, ES6Iterator::js_iterator);
- prototype.defineProperty(SymbolKey.ITERATOR, iterator, DONTENUM);
+ public static ClassDescriptor makeDescriptor(String name, String tag) {
+ // Need a way to associate the built object with the tag. We
+ // kind of need this in general, and at the top level, but we
+ // can bodge it for now.
+ return new ClassDescriptor.Builder(name)
+ .withMethod(CTOR, "next", 0, ES6Iterator::js_next)
+ .withMethod(CTOR, SymbolKey.ITERATOR, 1, ES6Iterator::js_iterator)
+ .withProp(CTOR, SymbolKey.TO_STRING_TAG, value(tag, DONTENUM | READONLY))
+ .build();
+ }
- prototype.defineProperty(
- SymbolKey.TO_STRING_TAG, prototype.getClassName(), DONTENUM | READONLY);
+ /**
+ * Variant of {@link #makeDescriptor} for iterator helpers that need a {@code return}
+ * method on their prototype so consumers can close them (forwarding the close to any wrapped
+ * source iterator).
+ */
+ public static ClassDescriptor makeHelperDescriptor(String name, String tag) {
+ return new ClassDescriptor.Builder(name)
+ .withMethod(CTOR, "next", 0, ES6Iterator::js_next)
+ .withMethod(CTOR, RETURN_METHOD, 0, ES6Iterator::js_returnMethod)
+ .withMethod(CTOR, SymbolKey.ITERATOR, 1, ES6Iterator::js_iterator)
+ .withProp(CTOR, SymbolKey.TO_STRING_TAG, value(tag, DONTENUM | READONLY))
+ .build();
+ }
+ public static void initialize(
+ ClassDescriptor desc,
+ Context cx,
+ TopLevel scope,
+ ScriptableObject obj,
+ boolean sealed,
+ String name) {
+ // Defer sealing until after we have a chance to re-parent the populated
+ // prototype under %Iterator.prototype% when it is available.
+ var global = desc.populateGlobal(cx, scope, obj, false);
+ Object iterProto = ScriptableObject.getTopScopeValue(scope, ITERATOR_PROTOTYPE_TAG);
+ if (iterProto instanceof Scriptable) {
+ global.setPrototype((Scriptable) iterProto);
+ }
if (sealed) {
- prototype.sealObject();
+ global.sealObject();
}
+ scope.associateValue(name, global);
+ }
- // Need to access Iterator prototype when constructing
- // Iterator instances, but don't have a iterator constructor
- // to use to find the prototype. Use the "associateValue"
- // approach instead.
- if (scope != null) {
- scope.associateValue(tag, prototype);
- }
+ private static final ClassDescriptor ITERATOR_CTOR_DESCRIPTOR =
+ new ClassDescriptor.Builder(
+ ITERATOR_CLASS_NAME,
+ 0,
+ ClassDescriptor.typeError(),
+ ES6Iterator::js_construct)
+ .withMethod(CTOR, "from", 1, ES6Iterator::js_from)
+ .withMethod(PROTO, SymbolKey.ITERATOR, 0, ES6Iterator::js_iterator)
+ .withProp(
+ PROTO,
+ SymbolKey.TO_STRING_TAG,
+ value(ITERATOR_CLASS_NAME, DONTENUM | READONLY))
+ .withMethod(PROTO, "toArray", 0, ES6Iterator::js_toArray)
+ .withMethod(PROTO, "forEach", 1, ES6Iterator::js_forEach)
+ .withMethod(PROTO, "reduce", 1, ES6Iterator::js_reduce)
+ .withMethod(PROTO, "some", 1, ES6Iterator::js_some)
+ .withMethod(PROTO, "every", 1, ES6Iterator::js_every)
+ .withMethod(PROTO, "find", 1, ES6Iterator::js_find)
+ .withMethod(PROTO, "map", 1, ES6Iterator::js_map)
+ .withMethod(PROTO, "filter", 1, ES6Iterator::js_filter)
+ .withMethod(PROTO, "take", 1, ES6Iterator::js_take)
+ .withMethod(PROTO, "drop", 1, ES6Iterator::js_drop)
+ .withMethod(PROTO, "flatMap", 1, ES6Iterator::js_flatMap)
+ .build();
+
+ private static final ClassDescriptor WRAP_FOR_VALID_DESCRIPTOR =
+ makeHelperDescriptor(WRAP_FOR_VALID_TAG, "Iterator Helper");
+
+ /**
+ * Installs the modern ES2025 {@code Iterator} constructor (with the static {@code from} method)
+ * on the given scope, and registers the prototype used by iterators wrapped via {@code
+ * Iterator.from}.
+ */
+ public static void initIteratorConstructor(Context cx, TopLevel scope, boolean sealed) {
+ var iteratorProto = new NativeObject();
+ ITERATOR_CTOR_DESCRIPTOR.buildConstructor(cx, scope, iteratorProto, sealed);
+ // Stash the prototype so later helpers can look it up even if
+ // the script replaces the global "Iterator" binding.
+ scope.associateValue(ITERATOR_PROTOTYPE_TAG, iteratorProto);
+
+ ES6Iterator.initialize(
+ WRAP_FOR_VALID_DESCRIPTOR,
+ cx,
+ scope,
+ new WrapForValidIterator(),
+ sealed,
+ WRAP_FOR_VALID_TAG);
}
protected boolean exhausted = false;
@@ -67,15 +140,34 @@ private static ES6Iterator realThis(Object thisObj) {
return LambdaConstructor.convertThisObject(thisObj, ES6Iterator.class);
}
- private static Object js_next(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ private static Object js_next(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
ES6Iterator iterator = realThis(thisObj);
- return iterator.next(cx, scope);
+ return iterator.next(cx, s);
}
- private static Object js_iterator(Context cx, VarScope scope, Object thisObj, Object[] args) {
+ private static Object js_iterator(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
return thisObj;
}
+ private static Object js_returnMethod(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ ES6Iterator iterator = realThis(thisObj);
+ Object value = args.length > 0 ? args[0] : Undefined.instance;
+ return iterator.closeIterator(cx, s, value);
+ }
+
+ /**
+ * Subclass hook for the JavaScript {@code return} method. The default implementation marks the
+ * iterator exhausted and produces {@code {value, done: true}}. Iterator helpers override this
+ * to forward the close to their wrapped source iterator.
+ */
+ protected Object closeIterator(Context cx, VarScope scope, Object value) {
+ this.exhausted = true;
+ return makeIteratorResult(cx, scope, Boolean.TRUE, value);
+ }
+
protected abstract boolean isDone(Context cx, VarScope scope);
protected abstract Object nextValue(Context cx, VarScope scope);
@@ -106,4 +198,752 @@ static Scriptable makeIteratorResult(Context cx, VarScope scope, Boolean done, O
ScriptableObject.putProperty(iteratorResult, DONE_PROPERTY, done);
return iteratorResult;
}
+
+ /**
+ * Implementation of {@code Iterator.from(obj)}. Uses {@link #getIteratorFlattenable} so an
+ * {@code ES6Iterator} is returned directly when the argument already represents one; otherwise
+ * a {@link WrapForValidIterator} wraps the underlying iterator record.
+ */
+ private static Object js_from(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ Object arg = args.length == 0 ? Undefined.instance : args[0];
+ return getIteratorFlattenable(cx, s, arg);
+ }
+
+ /**
+ * Spec-equivalent of {@code GetIteratorFlattenable(obj, iterate-string-primitives)} fused with
+ * the {@code Iterator.from} step-1 identity check: returns the argument unchanged if it is
+ * already an {@code ES6Iterator}, otherwise obtains the underlying iterator (via
+ * {@code @@iterator} or directly treating {@code obj} as one) and wraps it.
+ */
+ public static ES6Iterator getIteratorFlattenable(Context cx, VarScope scope, Object obj) {
+ if (obj instanceof ES6Iterator) {
+ return (ES6Iterator) obj;
+ }
+ if (obj instanceof CharSequence) {
+ return new NativeStringIterator(scope, obj);
+ }
+ if (obj == null || Undefined.isUndefined(obj)) {
+ throw ScriptRuntime.typeErrorById("msg.not.iterable", ScriptRuntime.toString(obj));
+ }
+ if (!(obj instanceof Scriptable)) {
+ throw ScriptRuntime.typeErrorById("msg.not.iterable", ScriptRuntime.toString(obj));
+ }
+ Scriptable source = (Scriptable) obj;
+ Scriptable iterator;
+ if (ScriptableObject.hasProperty(source, SymbolKey.ITERATOR)) {
+ Object iteratorFn = ScriptableObject.getProperty(source, SymbolKey.ITERATOR);
+ if (Undefined.isUndefined(iteratorFn) || iteratorFn == null) {
+ // @@iterator present but nullish: fall through to use source as iterator.
+ iterator = source;
+ } else {
+ if (!(iteratorFn instanceof Callable)) {
+ throw ScriptRuntime.typeErrorById(
+ "msg.not.iterable", ScriptRuntime.toString(obj));
+ }
+ VarScope callScope =
+ (iteratorFn instanceof Function)
+ ? ((Function) iteratorFn).getDeclarationScope()
+ : cx.topCallScope;
+ Object v =
+ ((Callable) iteratorFn)
+ .call(cx, callScope, source, ScriptRuntime.emptyArgs);
+ if (!(v instanceof Scriptable)) {
+ throw ScriptRuntime.typeErrorById(
+ "msg.not.iterable", ScriptRuntime.toString(obj));
+ }
+ iterator = (Scriptable) v;
+ }
+ } else {
+ // Flatten case: treat `obj` itself as the iterator record.
+ iterator = source;
+ }
+
+ if (iterator instanceof ES6Iterator) {
+ return (ES6Iterator) iterator;
+ }
+ return new WrapForValidIterator(scope, iterator);
+ }
+
+ /**
+ * Constructor behaviour for the modern {@code Iterator} global. The constructor is not meant to
+ * be called directly: {@code new Iterator()} throws a TypeError. When invoked as {@code
+ * super()} from a subclass constructor (i.e. NewTarget is distinct from the Iterator
+ * constructor itself) a plain object inheriting from the subclass prototype is returned, so
+ * {@code class Foo extends Iterator} remains usable.
+ */
+ private static Object js_construct(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ if (nt == null || nt == f) {
+ throw ScriptRuntime.typeErrorById("msg.iterator.abstract");
+ }
+ NativeObject obj = new NativeObject();
+ ScriptRuntime.setBuiltinProtoAndParent(obj, f, nt, s, TopLevel.Builtins.Iterator);
+ return obj;
+ }
+
+ // ------------------------------------------------------------
+ // Eager Iterator-prototype helpers (toArray, forEach, reduce, some, every, find)
+ // ------------------------------------------------------------
+
+ private static Scriptable requireIteratorReceiver(Object thisObj) {
+ if (!(thisObj instanceof Scriptable) || thisObj == null || Undefined.isUndefined(thisObj)) {
+ throw ScriptRuntime.typeErrorById("msg.not.iterable", ScriptRuntime.toString(thisObj));
+ }
+ return (Scriptable) thisObj;
+ }
+
+ private static Callable requireNext(Scriptable iter) {
+ Object next = ScriptableObject.getProperty(iter, NEXT_METHOD);
+ if (next == Scriptable.NOT_FOUND) {
+ next = Undefined.instance;
+ }
+ if (!(next instanceof Callable)) {
+ throw ScriptRuntime.typeErrorById(
+ "msg.isnt.function", NEXT_METHOD, ScriptRuntime.typeof(next));
+ }
+ return (Callable) next;
+ }
+
+ private static Callable requireCallback(Object arg) {
+ if (!(arg instanceof Callable)) {
+ throw ScriptRuntime.typeErrorById(
+ "msg.isnt.function", ScriptRuntime.toString(arg), ScriptRuntime.typeof(arg));
+ }
+ return (Callable) arg;
+ }
+
+ private static VarScope callScopeFor(Context cx, Callable fn) {
+ return (fn instanceof Function) ? ((Function) fn).getDeclarationScope() : cx.topCallScope;
+ }
+
+ /** Returns the IteratorResult object, or {@code null} if the iterator is exhausted. */
+ private static Scriptable iteratorStep(
+ Context cx, VarScope scope, Scriptable iter, Callable nextFn) {
+ Object v = nextFn.call(cx, callScopeFor(cx, nextFn), iter, ScriptRuntime.emptyArgs);
+ if (!(v instanceof Scriptable)) {
+ throw ScriptRuntime.typeError("Iterator result is not an object");
+ }
+ Scriptable r = (Scriptable) v;
+ Object done = ScriptableObject.getProperty(r, DONE_PROPERTY);
+ if (ScriptRuntime.toBoolean(done)) {
+ return null;
+ }
+ return r;
+ }
+
+ private static Object iteratorValue(Scriptable result) {
+ Object v = ScriptableObject.getProperty(result, VALUE_PROPERTY);
+ return (v == Scriptable.NOT_FOUND) ? Undefined.instance : v;
+ }
+
+ /**
+ * Spec {@code IteratorClose} with a normal completion: invokes {@code return} if present;
+ * throws from {@code return} propagate to the caller.
+ */
+ private static void iteratorClose(Context cx, VarScope scope, Scriptable iter) {
+ if (!ScriptableObject.hasProperty(iter, RETURN_METHOD)) return;
+ Object ret = ScriptableObject.getProperty(iter, RETURN_METHOD);
+ if (ret == null || Undefined.isUndefined(ret)) return;
+ if (!(ret instanceof Callable)) {
+ throw ScriptRuntime.typeErrorById(
+ "msg.isnt.function", RETURN_METHOD, ScriptRuntime.typeof(ret));
+ }
+ Object result =
+ ((Callable) ret)
+ .call(cx, callScopeFor(cx, (Callable) ret), iter, ScriptRuntime.emptyArgs);
+ if (!(result instanceof Scriptable)) {
+ throw ScriptRuntime.typeError("Iterator return() result is not an object");
+ }
+ }
+
+ /**
+ * Spec {@code IteratorClose} with a throw completion: any error raised by the iterator's {@code
+ * return} method is swallowed and the original exception is re-thrown.
+ */
+ private static RhinoException iteratorCloseOnThrow(
+ Context cx, VarScope scope, Scriptable iter, RhinoException original) {
+ try {
+ if (ScriptableObject.hasProperty(iter, RETURN_METHOD)) {
+ Object ret = ScriptableObject.getProperty(iter, RETURN_METHOD);
+ if (ret instanceof Callable) {
+ ((Callable) ret)
+ .call(
+ cx,
+ callScopeFor(cx, (Callable) ret),
+ iter,
+ ScriptRuntime.emptyArgs);
+ }
+ }
+ } catch (RhinoException ignored) {
+ // Per spec, the original throw wins; any error from return() is discarded.
+ }
+ return original;
+ }
+
+ private static Object js_toArray(
+ Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) {
+ Scriptable iter = requireIteratorReceiver(thisObj);
+ Callable nextFn = requireNext(iter);
+ java.util.ArrayList