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 buffer = new java.util.ArrayList<>(); + for (; ; ) { + Scriptable step = iteratorStep(cx, s, iter, nextFn); + if (step == null) { + return cx.newArray(s, buffer.toArray()); + } + buffer.add(iteratorValue(step)); + } + } + + private static Object js_forEach( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + Scriptable iter = requireIteratorReceiver(thisObj); + Callable nextFn = requireNext(iter); + Callable fn = requireCallback(args.length > 0 ? args[0] : Undefined.instance); + VarScope fnScope = callScopeFor(cx, fn); + int counter = 0; + for (; ; ) { + Scriptable step = iteratorStep(cx, s, iter, nextFn); + if (step == null) return Undefined.instance; + Object val = iteratorValue(step); + try { + fn.call( + cx, + fnScope, + Undefined.instance, + new Object[] {val, Integer.valueOf(counter++)}); + } catch (RhinoException e) { + throw iteratorCloseOnThrow(cx, s, iter, e); + } + } + } + + private static Object js_reduce( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + Scriptable iter = requireIteratorReceiver(thisObj); + Callable nextFn = requireNext(iter); + Callable reducer = requireCallback(args.length > 0 ? args[0] : Undefined.instance); + VarScope rScope = callScopeFor(cx, reducer); + boolean hasInitial = args.length >= 2; + Object accumulator; + int counter = 0; + if (hasInitial) { + accumulator = args[1]; + } else { + Scriptable firstStep = iteratorStep(cx, s, iter, nextFn); + if (firstStep == null) { + throw ScriptRuntime.typeErrorById("msg.empty.array.reduce"); + } + accumulator = iteratorValue(firstStep); + counter = 1; + } + for (; ; ) { + Scriptable step = iteratorStep(cx, s, iter, nextFn); + if (step == null) return accumulator; + Object val = iteratorValue(step); + try { + accumulator = + reducer.call( + cx, + rScope, + Undefined.instance, + new Object[] {accumulator, val, Integer.valueOf(counter++)}); + } catch (RhinoException e) { + throw iteratorCloseOnThrow(cx, s, iter, e); + } + } + } + + private static Object js_some( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return js_predicate(cx, s, thisObj, args, PredicateKind.SOME); + } + + private static Object js_every( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return js_predicate(cx, s, thisObj, args, PredicateKind.EVERY); + } + + private static Object js_find( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return js_predicate(cx, s, thisObj, args, PredicateKind.FIND); + } + + private enum PredicateKind { + SOME, + EVERY, + FIND + } + + private static Object js_predicate( + Context cx, VarScope scope, Object thisObj, Object[] args, PredicateKind kind) { + Scriptable iter = requireIteratorReceiver(thisObj); + Callable nextFn = requireNext(iter); + Callable pred = requireCallback(args.length > 0 ? args[0] : Undefined.instance); + VarScope pScope = callScopeFor(cx, pred); + int counter = 0; + for (; ; ) { + Scriptable step = iteratorStep(cx, scope, iter, nextFn); + if (step == null) { + return kind == PredicateKind.EVERY + ? Boolean.TRUE + : (kind == PredicateKind.SOME ? Boolean.FALSE : Undefined.instance); + } + Object val = iteratorValue(step); + boolean match; + try { + Object r = + pred.call( + cx, + pScope, + Undefined.instance, + new Object[] {val, Integer.valueOf(counter++)}); + match = ScriptRuntime.toBoolean(r); + } catch (RhinoException e) { + throw iteratorCloseOnThrow(cx, scope, iter, e); + } + switch (kind) { + case SOME: + if (match) { + iteratorClose(cx, scope, iter); + return Boolean.TRUE; + } + break; + case EVERY: + if (!match) { + iteratorClose(cx, scope, iter); + return Boolean.FALSE; + } + break; + case FIND: + if (match) { + iteratorClose(cx, scope, iter); + return val; + } + break; + } + } + } + + // ------------------------------------------------------------ + // Lazy Iterator-prototype helpers (map, filter, take, drop, flatMap) + // ------------------------------------------------------------ + + private static Object js_map( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + Scriptable iter = requireIteratorReceiver(thisObj); + Callable nextFn = requireNext(iter); + Callable mapper = requireCallback(args.length > 0 ? args[0] : Undefined.instance); + return new MapIterator(s, iter, nextFn, mapper); + } + + private static Object js_filter( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + Scriptable iter = requireIteratorReceiver(thisObj); + Callable nextFn = requireNext(iter); + Callable pred = requireCallback(args.length > 0 ? args[0] : Undefined.instance); + return new FilterIterator(s, iter, nextFn, pred); + } + + private static long helperLimit(Object arg) { + double n = ScriptRuntime.toInteger(arg); + if (n < 0 || Double.isNaN(n)) { + throw ScriptRuntime.rangeErrorById("msg.iterator.helper.limit"); + } + if (Double.isInfinite(n) || n > Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + return (long) n; + } + + private static Object js_take( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + Scriptable iter = requireIteratorReceiver(thisObj); + Callable nextFn = requireNext(iter); + long remaining = helperLimit(args.length > 0 ? args[0] : Undefined.instance); + return new TakeIterator(s, iter, nextFn, remaining); + } + + private static Object js_drop( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + Scriptable iter = requireIteratorReceiver(thisObj); + Callable nextFn = requireNext(iter); + long toSkip = helperLimit(args.length > 0 ? args[0] : Undefined.instance); + return new DropIterator(s, iter, nextFn, toSkip); + } + + private static Object js_flatMap( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + Scriptable iter = requireIteratorReceiver(thisObj); + Callable nextFn = requireNext(iter); + Callable mapper = requireCallback(args.length > 0 ? args[0] : Undefined.instance); + return new FlatMapIterator(s, iter, nextFn, mapper); + } + + /** + * Shared base for lazy iterator helpers: holds a source iterator with a cached next method and + * provides default {@code isDone}/{@code nextValue} overrides (the helpers override {@code + * nextHelper} for their per-step logic, since it doesn't match the isDone/nextValue split). + * + *

Per spec, an iterator helper's {@code next} runs as a generator closure, so re-entering it + * while it is already running throws a TypeError. We enforce that with an {@code active} flag + * set across each {@code next} invocation. + */ + abstract static class AbstractIteratorHelper extends ES6Iterator { + + private static final long serialVersionUID = 1L; + + protected final Scriptable source; + protected final Callable sourceNext; + private boolean active; + + protected AbstractIteratorHelper(VarScope scope, Scriptable source, Callable sourceNext) { + super(scope, WRAP_FOR_VALID_TAG); + this.source = source; + this.sourceNext = sourceNext; + } + + @Override + public String getClassName() { + return "Iterator Helper"; + } + + @Override + protected boolean isDone(Context cx, VarScope scope) { + return true; + } + + @Override + protected Object nextValue(Context cx, VarScope scope) { + return Undefined.instance; + } + + @Override + protected final Object next(Context cx, VarScope scope) { + if (active) { + throw ScriptRuntime.typeErrorById("msg.iterator.helper.executing"); + } + active = true; + try { + return nextHelper(cx, scope); + } finally { + active = false; + } + } + + protected abstract Object nextHelper(Context cx, VarScope scope); + + @Override + protected Object closeIterator(Context cx, VarScope scope, Object value) { + exhausted = true; + if (source != null) { + iteratorClose(cx, scope, source); + } + return makeIteratorResult(cx, scope, Boolean.TRUE, value); + } + } + + static final class MapIterator extends AbstractIteratorHelper { + + private static final long serialVersionUID = 1L; + + private final Callable mapper; + private int counter = 0; + + MapIterator(VarScope scope, Scriptable source, Callable sourceNext, Callable mapper) { + super(scope, source, sourceNext); + this.mapper = mapper; + } + + @Override + protected Object nextHelper(Context cx, VarScope scope) { + if (exhausted) return makeIteratorResult(cx, scope, Boolean.TRUE); + Scriptable step = iteratorStep(cx, scope, source, sourceNext); + if (step == null) { + exhausted = true; + return makeIteratorResult(cx, scope, Boolean.TRUE); + } + Object val = iteratorValue(step); + Object mapped; + try { + mapped = + mapper.call( + cx, + callScopeFor(cx, mapper), + Undefined.instance, + new Object[] {val, Integer.valueOf(counter++)}); + } catch (RhinoException e) { + exhausted = true; + throw iteratorCloseOnThrow(cx, scope, source, e); + } + return makeIteratorResult(cx, scope, Boolean.FALSE, mapped); + } + } + + static final class FilterIterator extends AbstractIteratorHelper { + + private static final long serialVersionUID = 1L; + + private final Callable predicate; + private int counter = 0; + + FilterIterator(VarScope scope, Scriptable source, Callable sourceNext, Callable predicate) { + super(scope, source, sourceNext); + this.predicate = predicate; + } + + @Override + protected Object nextHelper(Context cx, VarScope scope) { + if (exhausted) return makeIteratorResult(cx, scope, Boolean.TRUE); + VarScope predScope = callScopeFor(cx, predicate); + for (; ; ) { + Scriptable step = iteratorStep(cx, scope, source, sourceNext); + if (step == null) { + exhausted = true; + return makeIteratorResult(cx, scope, Boolean.TRUE); + } + Object val = iteratorValue(step); + boolean keep; + try { + Object r = + predicate.call( + cx, + predScope, + Undefined.instance, + new Object[] {val, Integer.valueOf(counter++)}); + keep = ScriptRuntime.toBoolean(r); + } catch (RhinoException e) { + exhausted = true; + throw iteratorCloseOnThrow(cx, scope, source, e); + } + if (keep) { + return makeIteratorResult(cx, scope, Boolean.FALSE, val); + } + } + } + } + + static final class TakeIterator extends AbstractIteratorHelper { + + private static final long serialVersionUID = 1L; + + private long remaining; + + TakeIterator(VarScope scope, Scriptable source, Callable sourceNext, long remaining) { + super(scope, source, sourceNext); + this.remaining = remaining; + } + + @Override + protected Object nextHelper(Context cx, VarScope scope) { + if (exhausted) return makeIteratorResult(cx, scope, Boolean.TRUE); + if (remaining <= 0) { + exhausted = true; + iteratorClose(cx, scope, source); + return makeIteratorResult(cx, scope, Boolean.TRUE); + } + remaining--; + Scriptable step = iteratorStep(cx, scope, source, sourceNext); + if (step == null) { + exhausted = true; + return makeIteratorResult(cx, scope, Boolean.TRUE); + } + return makeIteratorResult(cx, scope, Boolean.FALSE, iteratorValue(step)); + } + } + + static final class DropIterator extends AbstractIteratorHelper { + + private static final long serialVersionUID = 1L; + + private long toSkip; + private boolean primed = false; + + DropIterator(VarScope scope, Scriptable source, Callable sourceNext, long toSkip) { + super(scope, source, sourceNext); + this.toSkip = toSkip; + } + + @Override + protected Object nextHelper(Context cx, VarScope scope) { + if (exhausted) return makeIteratorResult(cx, scope, Boolean.TRUE); + if (!primed) { + primed = true; + while (toSkip > 0) { + toSkip--; + Scriptable step = iteratorStep(cx, scope, source, sourceNext); + if (step == null) { + exhausted = true; + return makeIteratorResult(cx, scope, Boolean.TRUE); + } + } + } + Scriptable step = iteratorStep(cx, scope, source, sourceNext); + if (step == null) { + exhausted = true; + return makeIteratorResult(cx, scope, Boolean.TRUE); + } + return makeIteratorResult(cx, scope, Boolean.FALSE, iteratorValue(step)); + } + } + + static final class FlatMapIterator extends AbstractIteratorHelper { + + private static final long serialVersionUID = 1L; + + private final Callable mapper; + private int counter = 0; + private ES6Iterator inner; + + FlatMapIterator(VarScope scope, Scriptable source, Callable sourceNext, Callable mapper) { + super(scope, source, sourceNext); + this.mapper = mapper; + } + + @Override + protected Object nextHelper(Context cx, VarScope scope) { + if (exhausted) return makeIteratorResult(cx, scope, Boolean.TRUE); + for (; ; ) { + if (inner != null) { + Object innerResult = inner.next(cx, scope); + if (!(innerResult instanceof Scriptable)) { + exhausted = true; + throw ScriptRuntime.typeError("Iterator result is not an object"); + } + Scriptable r = (Scriptable) innerResult; + Object done = ScriptableObject.getProperty(r, DONE_PROPERTY); + if (!ScriptRuntime.toBoolean(done)) { + return r; + } + inner = null; + } + Scriptable step = iteratorStep(cx, scope, source, sourceNext); + if (step == null) { + exhausted = true; + return makeIteratorResult(cx, scope, Boolean.TRUE); + } + Object val = iteratorValue(step); + Object mapped; + try { + mapped = + mapper.call( + cx, + callScopeFor(cx, mapper), + Undefined.instance, + new Object[] {val, Integer.valueOf(counter++)}); + } catch (RhinoException e) { + exhausted = true; + throw iteratorCloseOnThrow(cx, scope, source, e); + } + try { + inner = getIteratorFlattenable(cx, scope, mapped); + } catch (RhinoException e) { + exhausted = true; + throw iteratorCloseOnThrow(cx, scope, source, e); + } + } + } + + @Override + protected Object closeIterator(Context cx, VarScope scope, Object value) { + exhausted = true; + RhinoException pending = null; + if (inner != null) { + try { + inner.closeIterator(cx, scope, Undefined.instance); + } catch (RhinoException e) { + pending = e; + } + inner = null; + } + try { + iteratorClose(cx, scope, source); + } catch (RhinoException e) { + if (pending == null) pending = e; + } + if (pending != null) throw pending; + return makeIteratorResult(cx, scope, Boolean.TRUE, value); + } + } + + /** + * Wraps an arbitrary iterator record (an iterator object plus its {@code next} method) as an + * {@link ES6Iterator}. The wrapped iterator is consulted directly for each step; the + * IteratorResult object produced by the inner {@code next} is returned unchanged. + */ + static final class WrapForValidIterator extends ES6Iterator { + + private static final long serialVersionUID = 1L; + + private Scriptable wrapped; + private Callable wrappedNext; + + /** Only used to build the prototype object during initialisation. */ + private WrapForValidIterator() { + super(); + } + + WrapForValidIterator(VarScope scope, Scriptable wrapped) { + super(scope, WRAP_FOR_VALID_TAG); + this.wrapped = wrapped; + Object nextMethod = ScriptableObject.getProperty(wrapped, NEXT_METHOD); + if (!(nextMethod instanceof Callable)) { + // The iterator is unusable; defer the TypeError until next() is actually + // invoked, matching spec semantics where GetIteratorDirect only reads next. + this.wrappedNext = null; + } else { + this.wrappedNext = (Callable) nextMethod; + } + } + + @Override + public String getClassName() { + return "Iterator Helper"; + } + + @Override + protected boolean isDone(Context cx, VarScope scope) { + return true; + } + + @Override + protected Object nextValue(Context cx, VarScope scope) { + return Undefined.instance; + } + + @Override + protected Object next(Context cx, VarScope scope) { + if (exhausted) { + return makeIteratorResult(cx, scope, Boolean.TRUE); + } + if (wrappedNext == null) { + exhausted = true; + throw ScriptRuntime.typeErrorById( + "msg.not.iterable", ScriptRuntime.toString(wrapped)); + } + Object result = + wrappedNext.call( + cx, callScopeFor(cx, wrappedNext), wrapped, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + exhausted = true; + throw ScriptRuntime.typeErrorById( + "msg.not.iterable", ScriptRuntime.toString(wrapped)); + } + Scriptable resultObj = (Scriptable) result; + Object done = ScriptableObject.getProperty(resultObj, DONE_PROPERTY); + if (ScriptRuntime.toBoolean(done)) { + exhausted = true; + } + return resultObj; + } + + @Override + protected Object closeIterator(Context cx, VarScope scope, Object value) { + exhausted = true; + if (wrapped != null) { + iteratorClose(cx, scope, wrapped); + } + return makeIteratorResult(cx, scope, Boolean.TRUE, value); + } + } } diff --git a/rhino/src/main/java/org/mozilla/javascript/EqualObjectGraphs.java b/rhino/src/main/java/org/mozilla/javascript/EqualObjectGraphs.java index 39000f9612a..cc32798075f 100644 --- a/rhino/src/main/java/org/mozilla/javascript/EqualObjectGraphs.java +++ b/rhino/src/main/java/org/mozilla/javascript/EqualObjectGraphs.java @@ -150,6 +150,8 @@ private boolean equalGraphsNoMemo(Object o1, Object o2) { && equalJSFunctions((ScriptOrFn) o1, (ScriptOrFn) o2); } else if (o1 instanceof Scriptable) { return o2 instanceof Scriptable && equalScriptables((Scriptable) o1, (Scriptable) o2); + } else if (o1 instanceof VarScope) { + return o2 instanceof VarScope && equalScopes((VarScope) o1, (VarScope) o2); } else if (o1 instanceof SymbolKey) { return o2 instanceof SymbolKey && equalGraphs(((SymbolKey) o1).getName(), ((SymbolKey) o2).getName()); @@ -173,6 +175,25 @@ private boolean equalGraphsNoMemo(Object o1, Object o2) { return o1.equals(o2); } + private boolean equalScopes(final VarScope s1, final VarScope s2) { + final Object[] ids1 = getSortedIds(s1); + final Object[] ids2 = getSortedIds(s2); + if (!equalObjectArrays(ids1, ids2)) { + return false; + } + final int l = ids1.length; + for (int i = 0; i < l; ++i) { + if (!equalGraphs(getValue(s1, ids1[i]), getValue(s2, ids2[i]))) { + return false; + } + } + if (!equalGraphs(s1.getParentScope(), s2.getParentScope())) { + return false; + } + + return true; + } + private boolean equalScriptables(final Scriptable s1, final Scriptable s2) { final Object[] ids1 = getSortedIds(s1); final Object[] ids2 = getSortedIds(s2); @@ -285,7 +306,7 @@ private static boolean equalJSFunctions(final ScriptOrFn f1, final ScriptOrFn } // Sort IDs deterministically - private static Object[] getSortedIds(final Scriptable s) { + private static > Object[] getSortedIds(final PropHolder s) { final Object[] ids = getIds(s); Arrays.sort( ids, @@ -320,14 +341,14 @@ private static Object[] getSortedIds(final Scriptable s) { return ids; } - private static Object[] getIds(final Scriptable s) { - if (s instanceof ScriptableObject) { + private static > Object[] getIds(final PropHolder s) { + if (s instanceof SlotMapOwner smo) { // Grabs symbols too - try (var map = ((ScriptableObject) s).startCompoundOp(false)) { - return ((ScriptableObject) s).getIds(map, true, true); + try (var map = smo.startCompoundOp(false)) { + return smo.getIds(map, true, true); } - } else if (s instanceof DebuggableObject) { - return ((DebuggableObject) s).getAllIds(); + } else if (s instanceof DebuggableObject dbo) { + return dbo.getAllIds(); } else { return s.getIds(); } @@ -344,4 +365,16 @@ private static Object getValue(final Scriptable s, final Object id) { throw new ClassCastException(); } } + + private static Object getValue(final VarScope s, final Object id) { + if (id instanceof Symbol) { + return s.get((Symbol) id, s); + } else if (id instanceof Integer) { + return s.get((int) id, s); + } else if (id instanceof String) { + return s.get((String) id, s); + } else { + throw new ClassCastException(); + } + } } diff --git a/rhino/src/main/java/org/mozilla/javascript/Function.java b/rhino/src/main/java/org/mozilla/javascript/Function.java index 039d1b639c0..73970a1fa1b 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Function.java +++ b/rhino/src/main/java/org/mozilla/javascript/Function.java @@ -29,7 +29,7 @@ public interface Function extends Scriptable, Callable, Constructable { * @return the result of the call */ @Override - Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args); + Object call(Context cx, VarScope scope, Object thisObj, Object[] args); /** * Call the function as a constructor. @@ -46,6 +46,9 @@ public interface Function extends Scriptable, Callable, Constructable { @Override Scriptable construct(Context cx, VarScope scope, Object[] args); + @Override + Scriptable construct(Context cx, Object nt, VarScope s, Object thisObj, Object[] args); + /** * Return the scope in which this function was declared or closed over. This is the * "[[Environment]]" defined in https://tc39.es/ecma262/#sec-ecmascript-function-objects. @@ -61,4 +64,9 @@ default VarScope getDeclarationScope() { default boolean isConstructor() { return true; } + + /** Returns whether this is an async function. */ + default boolean isAsync() { + return false; + } } diff --git a/rhino/src/main/java/org/mozilla/javascript/FunctionObject.java b/rhino/src/main/java/org/mozilla/javascript/FunctionObject.java index bdc7f4c2683..ca0495492ae 100644 --- a/rhino/src/main/java/org/mozilla/javascript/FunctionObject.java +++ b/rhino/src/main/java/org/mozilla/javascript/FunctionObject.java @@ -16,7 +16,7 @@ import java.lang.reflect.Modifier; public class FunctionObject extends BaseFunction { - private static final long serialVersionUID = -5332312783643935019L; + private static final long serialVersionUID = 8880062939740158370L; /** * Create a JavaScript function object from a Java method. @@ -77,6 +77,7 @@ public class FunctionObject extends BaseFunction { * @see org.mozilla.javascript.Scriptable */ public FunctionObject(String name, Member methodOrConstructor, VarScope scope) { + super(scope); if (methodOrConstructor instanceof Constructor) { member = new MemberBox(scope, (Constructor) methodOrConstructor); isStatic = true; // well, doesn't take a 'this' @@ -353,7 +354,7 @@ public static Object convertArg(Context cx, VarScope scope, Object arg, Class * @see org.mozilla.javascript.Function#call( Context, VarScope, Scriptable, Object[]) */ @Override - public Object call(Context cx, VarScope scope, Scriptable thisArg, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisArg, Object[] args) { Object result; boolean checkMethodResult = false; int argsLength = args.length; diff --git a/rhino/src/main/java/org/mozilla/javascript/FunctionScope.java b/rhino/src/main/java/org/mozilla/javascript/FunctionScope.java index f2ad393e45f..ed4017e7d2a 100644 --- a/rhino/src/main/java/org/mozilla/javascript/FunctionScope.java +++ b/rhino/src/main/java/org/mozilla/javascript/FunctionScope.java @@ -1,7 +1,7 @@ package org.mozilla.javascript; public class FunctionScope extends DeclarationScope { - private static final long serialVersionUID = -7471457301304454454L; + private static final long serialVersionUID = 4760825497832652202L; public FunctionScope(ScopeObject parentScope) { super(parentScope); diff --git a/rhino/src/main/java/org/mozilla/javascript/IRFactory.java b/rhino/src/main/java/org/mozilla/javascript/IRFactory.java index 50e9cc738b1..f15aec9de26 100644 --- a/rhino/src/main/java/org/mozilla/javascript/IRFactory.java +++ b/rhino/src/main/java/org/mozilla/javascript/IRFactory.java @@ -8,7 +8,9 @@ import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Queue; import org.mozilla.javascript.ast.AbstractObjectProperty; @@ -18,10 +20,13 @@ import org.mozilla.javascript.ast.Assignment; import org.mozilla.javascript.ast.AstNode; import org.mozilla.javascript.ast.AstRoot; +import org.mozilla.javascript.ast.AwaitExpression; import org.mozilla.javascript.ast.BigIntLiteral; import org.mozilla.javascript.ast.Block; import org.mozilla.javascript.ast.BreakStatement; import org.mozilla.javascript.ast.CatchClause; +import org.mozilla.javascript.ast.ClassComputedKeyRef; +import org.mozilla.javascript.ast.ClassNode; import org.mozilla.javascript.ast.ComputedPropertyKey; import org.mozilla.javascript.ast.ConditionalExpression; import org.mozilla.javascript.ast.ContinueStatement; @@ -100,6 +105,12 @@ public final class IRFactory { private Parser parser; private AstNodePosition astNodePos; private boolean outerScopeIsStrict; + private boolean insideClassConstructor; + + /** + * Stack of enclosing classes' {@code #name -> SymbolKey} maps, used to resolve private names. + */ + private final Deque> privateSymbolScopes = new ArrayDeque<>(); public IRFactory(CompilerEnvirons env, String sourceString) { this(env, null, sourceString, env.getErrorReporter()); @@ -173,6 +184,8 @@ private Node transform(AstNode node) { return transformForInLoop((ForInLoop) node); } return transformForLoop((ForLoop) node); + case Token.CLASS: + return transformClass((ClassNode) node); case Token.FUNCTION: return transformFunction((FunctionNode) node); case Token.GENEXPR: @@ -198,6 +211,7 @@ private Node transform(AstNode node) { case Token.NULL: case Token.UNDEFINED: case Token.DEBUGGER: + case Token.NEW_TARGET: return transformLiteral(node); case Token.SUPER: parser.setRequiresActivation(); @@ -235,6 +249,10 @@ private Node transform(AstNode node) { case Token.YIELD: case Token.YIELD_STAR: return transformYield((Yield) node); + case Token.AWAIT: + return transformAwait((AwaitExpression) node); + case Token.GET_CLASS_COMPUTED_KEY: + return node; default: if (node instanceof ExpressionStatement) { return transformExprStmt((ExpressionStatement) node); @@ -413,7 +431,8 @@ private Node arrayCompTransformHelper(ArrayComprehension node, String arrayName) body, acl, acl.isForEach(), - acl.isForOf()); + acl.isForOf(), + false); } } finally { for (int i = 0; i < pushed; i++) { @@ -468,6 +487,29 @@ private Node transformAssignment(Assignment node) { (originalLeft == left); // If we removed parens, we won't try to infer name left = transformAssignmentLeft(node, left, right); + // Private field initialization: the synthesized Assignment is marked so that the first + // write creates the slot with attribute PRIVATE instead of following normal SETELEM + // semantics. + if (node.getIntProp(Node.PRIVATE_FIELD_INIT_PROP, 0) == 1 && left instanceof PropertyGet) { + PropertyGet pg = (PropertyGet) left; + String privateName = pg.getProperty().getIdentifier(); + Node targetNode = transform(pg.getTarget()); + Node loadSymbol = loadPrivateSymbolLiteral(privateName); + astNodePos.push(left); + try { + Node transformedRight = transform(right); + Node defineField = + new Node(Token.DEFINE_FIELD, targetNode, loadSymbol, transformedRight); + int kind = node.getIntProp(Node.FIELD_KIND_PROP, 0); + if (kind != 0) { + defineField.putIntProp(Node.FIELD_KIND_PROP, kind); + } + return defineField; + } finally { + astNodePos.pop(); + } + } + Node target = null; if (isDestructuring(left)) { target = left; @@ -614,7 +656,15 @@ private Node transformForInLoop(ForInLoop loop) { Node obj = transform(loop.getIteratedObject()); Node body = transform(loop.getBody()); return createForIn( - declType, loop, lhs, obj, body, loop, loop.isForEach(), loop.isForOf()); + declType, + loop, + lhs, + obj, + body, + loop, + loop.isForEach(), + loop.isForOf(), + loop.isForAwaitOf()); } finally { parser.popScope(); } @@ -637,6 +687,371 @@ private Node transformForLoop(ForLoop loop) { } } + private Node transformClass(ClassNode classNode) { + FunctionNode constructor = classNode.getConstructor(); + boolean isStatement = classNode.isStatement(); + boolean hasSuperClass = classNode.getSuperClass() != null; + boolean hasMethods = classNode.getMethodCount() > 0; + boolean hasStaticMethods = classNode.getStaticMethodCount() > 0; + boolean hasStaticFields = classNode.getStaticFieldCount() > 0; + boolean hasStaticComputedFields = classNode.getStaticComputedFieldCount() > 0; + boolean hasStaticPrivateFields = classNode.getStaticPrivateFieldCount() > 0; + boolean hasPrivateFields = classNode.getPrivateFieldCount() > 0; + boolean hasComputedFields = classNode.getComputedFieldCount() > 0; + Name className = classNode.getClassName(); + + // Class bodies are always strict + constructor.setInStrictMode(true); + + // Always use FUNCTION_EXPRESSION for the constructor function itself. + // For class declarations, we wrap the result in a SETNAME to bind + // the name in the enclosing scope (like a let binding). + constructor.setFunctionType(FunctionNode.FUNCTION_EXPRESSION); + + // Set the class name on the constructor + if (className != null) { + constructor.setFunctionName(className); + } + + // For derived classes (has extends), keep the method definition flag + // so the CodeGenerator knows to use the homeObject mechanism. + // For base classes, clear it so the constructor can be constructed normally. + if (!hasSuperClass) { + constructor.setMethodDefinition(false); + } + + // Inject field initialization into the constructor body + if (classNode.getFieldCount() > 0 + || classNode.getComputedFieldCount() > 0 + || hasPrivateFields) { + injectFieldInitializers(classNode, constructor, hasSuperClass); + } + + // Make the class's private names visible to its constructor and all methods during IR + // transformation. + Map privateSymbols = classNode.getPrivateSymbols(); + boolean pushedPrivateScope = !privateSymbols.isEmpty(); + if (pushedPrivateScope) { + privateSymbolScopes.push(privateSymbols); + } + Node constructorNode; + java.util.List methodNodes; + java.util.List staticMethodNodes; + try { + // Transform the constructor as a regular function + boolean savedInsideClassConstructor = insideClassConstructor; + insideClassConstructor = hasSuperClass; + try { + constructorNode = transformFunction(constructor); + } finally { + insideClassConstructor = savedInsideClassConstructor; + } + + // Transform instance method functions + methodNodes = new java.util.ArrayList<>(); + if (hasMethods) { + for (FunctionNode methodFn : classNode.getMethods()) { + methodFn.setInStrictMode(true); + methodNodes.add(transformFunction(methodFn)); + } + } + + // Transform static method functions + staticMethodNodes = new java.util.ArrayList<>(); + if (hasStaticMethods) { + for (FunctionNode methodFn : classNode.getStaticMethods()) { + methodFn.setInStrictMode(true); + staticMethodNodes.add(transformFunction(methodFn)); + } + } + } finally { + if (pushedPrivateScope) { + privateSymbolScopes.pop(); + } + } + + if (hasSuperClass + || hasMethods + || hasStaticMethods + || hasStaticFields + || hasStaticComputedFields + || hasStaticPrivateFields + || hasComputedFields) { + // Use Token.CLASS node for classes with extends, methods, or static fields. + // For base classes without superclass, use Token.NULL as sentinel. + Node superClassNode = + hasSuperClass ? transform(classNode.getSuperClass()) : new Node(Token.NULL); + Node classSetup = new Node(Token.CLASS, superClassNode, constructorNode); + classSetup.setLineColumnNumber(classNode.getLineno(), classNode.getColumn()); + + // Add instance method nodes as children. For methods with computed keys + // (name == null), emit the key expression child just before the function + // child so the CodeGenerator can evaluate the key at definition time. + if (hasMethods) { + java.util.List methodKeys = classNode.getMethodComputedKeys(); + for (int i = 0; i < methodNodes.size(); i++) { + AstNode keyExpr = methodKeys.isEmpty() ? null : methodKeys.get(i); + if (keyExpr != null) { + classSetup.addChildToBack(transform(keyExpr)); + } + classSetup.addChildToBack(methodNodes.get(i)); + } + classSetup.putProp( + Node.CLASS_METHODS_PROP, classNode.getMethodNames().toArray(new String[0])); + classSetup.putProp( + Node.CLASS_METHOD_KINDS_PROP, toKindInts(classNode.getMethodKinds())); + } + + // Add static method nodes as children (after instance methods) + if (hasStaticMethods) { + java.util.List smKeys = classNode.getStaticMethodComputedKeys(); + for (int i = 0; i < staticMethodNodes.size(); i++) { + AstNode keyExpr = smKeys.isEmpty() ? null : smKeys.get(i); + if (keyExpr != null) { + classSetup.addChildToBack(transform(keyExpr)); + } + classSetup.addChildToBack(staticMethodNodes.get(i)); + } + classSetup.putProp( + Node.CLASS_STATIC_METHODS_PROP, + classNode.getStaticMethodNames().toArray(new String[0])); + classSetup.putProp( + Node.CLASS_STATIC_METHOD_KINDS_PROP, + toKindInts(classNode.getStaticMethodKinds())); + } + + // Add static named field value expressions as children + if (hasStaticFields) { + java.util.List sfInits = classNode.getStaticFieldInitializers(); + for (AstNode init : sfInits) { + if (init != null) { + classSetup.addChildToBack(transform(init)); + } else { + classSetup.addChildToBack(new Node(Token.VOID, Node.newNumber(0))); + } + } + classSetup.putProp( + Node.CLASS_STATIC_FIELDS_PROP, + classNode.getStaticFieldNames().toArray(new String[0])); + } + + // Add static computed field key+value expressions as children (alternating). + // Static private fields are emitted here too, with a LOAD_LITERAL pushing the + // shared SymbolKey as the key. The runtime detects the private-name SymbolKey + // and sets the PRIVATE attribute on the slot it creates. + int computedFieldPairs = 0; + if (hasStaticComputedFields) { + java.util.List scKeys = classNode.getStaticComputedFieldKeys(); + java.util.List scInits = classNode.getStaticComputedFieldInitializers(); + for (int i = 0; i < scKeys.size(); i++) { + classSetup.addChildToBack(transform(scKeys.get(i))); + AstNode init = scInits.get(i); + if (init != null) { + classSetup.addChildToBack(transform(init)); + } else { + classSetup.addChildToBack(new Node(Token.VOID, Node.newNumber(0))); + } + } + computedFieldPairs += classNode.getStaticComputedFieldCount(); + } + if (hasStaticPrivateFields) { + java.util.List spNames = classNode.getStaticPrivateFieldNames(); + java.util.List spInits = classNode.getStaticPrivateFieldInitializers(); + // Make private names visible while transforming initializers, since + // an initializer may read from other private names declared on the class. + if (pushedPrivateScope) { + privateSymbolScopes.push(privateSymbols); + } + try { + for (int i = 0; i < spNames.size(); i++) { + SymbolKey key = classNode.getOrCreatePrivateSymbol(spNames.get(i)); + Node keyLoad = new Node(Token.LOAD_LITERAL); + keyLoad.putIntProp( + Node.LITERAL_INDEX_PROP, parser.currentScriptOrFn.addLiteral(key)); + classSetup.addChildToBack(keyLoad); + AstNode init = spInits.get(i); + if (init != null) { + classSetup.addChildToBack(transform(init)); + } else { + classSetup.addChildToBack(new Node(Token.VOID, Node.newNumber(0))); + } + } + } finally { + if (pushedPrivateScope) { + privateSymbolScopes.pop(); + } + } + computedFieldPairs += classNode.getStaticPrivateFieldCount(); + } + if (computedFieldPairs > 0) { + classSetup.putIntProp(Node.CLASS_STATIC_COMPUTED_FIELDS_COUNT, computedFieldPairs); + } + + // Instance computed field keys must be evaluated at class declaration time and then + // stored on the constructor so each instance can look them up by index when the + // injected field initializers run. + if (hasComputedFields) { + java.util.List cKeys = classNode.getComputedFieldKeys(); + if (pushedPrivateScope) { + privateSymbolScopes.push(privateSymbols); + } + try { + for (AstNode keyExpr : cKeys) { + classSetup.addChildToBack(transform(keyExpr)); + } + } finally { + if (pushedPrivateScope) { + privateSymbolScopes.pop(); + } + } + classSetup.putIntProp(Node.CLASS_COMPUTED_FIELD_KEYS_COUNT, cKeys.size()); + } + + if (isStatement && className != null) { + Node setName = + new Node( + Token.EXPR_VOID, + createAssignment( + Token.ASSIGN, + parser.createName(className.getIdentifier()), + classSetup)); + return setName; + } + return classSetup; + } + + // No superclass and no methods - simple case + if (isStatement && className != null) { + Node setName = + new Node( + Token.EXPR_VOID, + createAssignment( + Token.ASSIGN, + parser.createName(className.getIdentifier()), + constructorNode)); + return setName; + } + return constructorNode; + } + + private static int[] toKindInts(java.util.List kinds) { + int[] out = new int[kinds.size()]; + for (int i = 0; i < kinds.size(); i++) { + out[i] = kinds.get(i).ordinal(); + } + return out; + } + + private void injectFieldInitializers( + ClassNode classNode, FunctionNode constructor, boolean hasSuperClass) { + AstNode body = constructor.getBody(); + + // Create field init statements + java.util.List initStmts = new java.util.ArrayList<>(); + + // Named fields: this.fieldName = initializer + var instaceFields = classNode.getInstanceFields(); + for (var e : instaceFields) { + var thisNode = new KeywordLiteral(); + thisNode.setType(Token.THIS); + Name propName = new Name(0, e.name); + PropertyGet propGet = new PropertyGet(thisNode, propName); + + AstNode value = e.initializer; + if (value == null) { + value = new KeywordLiteral(); + value.setType(Token.UNDEFINED); + } + + Assignment assign = new Assignment(Token.ASSIGN, propGet, value, 0); + initStmts.add(new ExpressionStatement(assign)); + } + + // Computed fields: this[] = initializer. + // The actual key expressions are evaluated once at class declaration time (as + // children of the CLASS setup node) and stashed on the constructor; here we only + // emit a reference that will load the i-th stored key at instance creation time. + java.util.List computedKeys = classNode.getComputedFieldKeys(); + java.util.List computedInits = classNode.getComputedFieldInitializers(); + for (int i = 0; i < computedKeys.size(); i++) { + KeywordLiteral thisNode = new KeywordLiteral(); + thisNode.setType(Token.THIS); + ElementGet elemGet = new ElementGet(thisNode, new ClassComputedKeyRef(i)); + + AstNode value = computedInits.get(i); + if (value == null) { + value = new KeywordLiteral(); + value.setType(Token.UNDEFINED); + } + + Assignment assign = new Assignment(Token.ASSIGN, elemGet, value, 0); + initStmts.add(new ExpressionStatement(assign)); + } + + // Private fields: this.#name = initializer, marked so the assignment becomes a + // DEFINE_FIELD that establishes the slot with attribute PRIVATE. Private accessors + // (get/set #name) are marked with FIELD_KIND_PROP so DEFINE_FIELD installs a + // getter/setter on the slot instead of storing a value. + java.util.List privateNames = classNode.getPrivateFieldNames(); + java.util.List privateInits = classNode.getPrivateFieldInitializers(); + java.util.List privateKinds = classNode.getPrivateFieldKinds(); + for (int i = 0; i < privateNames.size(); i++) { + KeywordLiteral thisNode = new KeywordLiteral(); + thisNode.setType(Token.THIS); + Name propName = new Name(0, privateNames.get(i)); + PropertyGet propGet = new PropertyGet(thisNode, propName); + + AstNode value = privateInits.get(i); + if (value == null) { + value = new KeywordLiteral(); + value.setType(Token.UNDEFINED); + } + + Assignment assign = new Assignment(Token.ASSIGN, propGet, value, 0); + assign.putIntProp(Node.PRIVATE_FIELD_INIT_PROP, 1); + ClassNode.ElementKind kind = privateKinds.get(i); + if (kind == ClassNode.ElementKind.GETTER) { + assign.putIntProp(Node.FIELD_KIND_PROP, 1); + } else if (kind == ClassNode.ElementKind.SETTER) { + assign.putIntProp(Node.FIELD_KIND_PROP, 2); + } + initStmts.add(new ExpressionStatement(assign)); + } + + if (!hasSuperClass) { + // Base class: prepend field init statements to constructor body + for (int i = initStmts.size() - 1; i >= 0; i--) { + body.addChildToFront(initStmts.get(i)); + } + } else { + // Derived class: find super() call and insert field inits after it + Node insertAfter = findSuperCall(body); + if (insertAfter != null) { + for (AstNode stmt : initStmts) { + body.addChildAfter(stmt, insertAfter); + insertAfter = stmt; + } + } + } + } + + private static Node findSuperCall(AstNode body) { + // Walk the body's direct children to find the ExpressionStatement + // containing a super() call + for (Node child = body.getFirstChild(); child != null; child = child.getNext()) { + if (child instanceof ExpressionStatement) { + AstNode expr = ((ExpressionStatement) child).getExpression(); + if (expr instanceof FunctionCall) { + AstNode target = ((FunctionCall) expr).getTarget(); + if (target instanceof KeywordLiteral && target.getType() == Token.SUPER) { + return child; + } + } + } + } + return null; + } + private Node transformFunction(FunctionNode fn) { Node mexpr = decompileFunctionHeader(fn); int index = parser.currentScriptOrFn.addFunction(fn); @@ -650,6 +1065,14 @@ private Node transformFunction(FunctionNode fn) { Node destructuring = (Node) fn.getProp(Node.DESTRUCTURING_PARAMS); fn.removeProp(Node.DESTRUCTURING_PARAMS); + // Async non-generator functions use generator machinery internally to implement + // await: each await expression becomes a yield point. Mark the function as a + // generator now so that default-parameter and return-statement handling below + // uses the correct (generator) code paths. + if (fn.isAsync() && !fn.isES6Generator()) { + fn.setIsGenerator(); + } + int lineno = fn.getBody().getLineno(), column = fn.getBody().getColumn(); ++parser.nestingOfFunction; // only for body, not params Node body = transform(fn.getBody()); @@ -663,6 +1086,8 @@ private Node transformFunction(FunctionNode fn) { && defaultParams.get(i - 1) instanceof String) { AstNode rhs = (AstNode) defaultParams.get(i); String name = (String) defaultParams.get(i - 1); + Node transformedRhs = transform(rhs); + inferNameIfMissing(new Name(0, name), transformedRhs, null); Node paramInit = createIf( createBinary( @@ -674,7 +1099,7 @@ private Node transformFunction(FunctionNode fn) { createAssignment( Token.ASSIGN, parser.createName(name), - transform(rhs)), + transformedRhs), body.getLineno(), body.getColumn()), null, @@ -703,13 +1128,27 @@ private Node transformFunction(FunctionNode fn) { Node a = i[0]; if (i[1] instanceof AstNode) { AstNode b = (AstNode) i[1]; - a.replaceChild(b, transform(b)); + Node transformed = transform(b); + if (i.length > 2 && i[2] instanceof Name) { + inferNameIfMissing(i[2], transformed, null); + } + a.replaceChild(b, transformed); } } } if (destructuring != null) { - body.addChildToFront(new Node(Token.EXPR_VOID, destructuring, lineno, column)); + Node destructuringStmt = new Node(Token.EXPR_VOID, destructuring, lineno, column); + if (fn.isGenerator()) { + Node paramInitBlock = fn.getGeneratorParamInitBlock(); + if (paramInitBlock == null) { + paramInitBlock = new Node(Token.BLOCK); + fn.setGeneratorParamInitBlock(paramInitBlock); + } + paramInitBlock.addChildToFront(destructuringStmt); + } else { + body.addChildToFront(destructuringStmt); + } } int syntheticType = fn.getFunctionType(); @@ -737,7 +1176,30 @@ private Node transformFunction(FunctionNode fn) { private Node transformFunctionCall(FunctionCall node) { astNodePos.push(node); try { - Node transformedTarget = transform(node.getTarget()); + AstNode target = node.getTarget(); + Node transformedTarget = transform(target); + + // super() in a derived class constructor + if (target.getType() == Token.SUPER && insideClassConstructor) { + // Replace the super keyword with THISFN so the interpreter can + // get the homeObject (superclass) from the current function. + Node call = createCallOrNew(Token.CALL, new Node(Token.THISFN)); + call.setLineColumnNumber(node.getLineno(), node.getColumn()); + List args = node.getArguments(); + if (args.size() == 1 && args.get(0) instanceof Spread) { + // super(...expr): evaluate expr and spread its elements at runtime. + Spread spread = (Spread) args.get(0); + call.addChildToBack(transform(spread.getExpression())); + call.putIntProp(Node.SUPER_CONSTRUCTOR_SPREAD_CALL, 1); + } else { + for (int i = 0; i < args.size(); i++) { + call.addChildToBack(transform(args.get(i))); + } + } + call.putIntProp(Node.SUPER_CONSTRUCTOR_CALL, 1); + return call; + } + Node call = createCallOrNew(Token.CALL, transformedTarget); call.setLineColumnNumber(node.getLineno(), node.getColumn()); List args = node.getArguments(); @@ -879,7 +1341,8 @@ private Node genExprTransformHelper(GeneratorExpression node) { body, acl, acl.isForEach(), - acl.isForOf()); + acl.isForOf(), + false); } } finally { for (int i = 0; i < pushed; i++) { @@ -948,10 +1411,12 @@ private Node transformLetNode(LetNode node) { private Node transformLiteral(AstNode node) { // Trying to call super as a function. See 15.4.2 Static Semantics: HasDirectSuper - // Note that this will need to change when classes are implemented, because in a class - // constructor calling "super()" _is_ allowed. - if (node.getParent() instanceof FunctionCall && node.getType() == Token.SUPER) + // In a class constructor calling "super()" is allowed. + if (node.getParent() instanceof FunctionCall + && node.getType() == Token.SUPER + && !insideClassConstructor) { parser.reportError("msg.super.shorthand.function"); + } return node; } @@ -1059,9 +1524,45 @@ private Node transformComputedPropertyKey(ComputedPropertyKey node) { private Node transformPropertyGet(PropertyGet node) { Node target = transform(node.getTarget()); String name = node.getProperty().getIdentifier(); + if (isPrivateName(name)) { + Node loadSymbol = loadPrivateSymbolLiteral(name); + Node elemGet = new Node(Token.GETELEM, target, loadSymbol); + if (node.type == Token.QUESTION_DOT) { + elemGet.putIntProp(Node.OPTIONAL_CHAINING, 1); + } + return elemGet; + } return createPropertyGet(target, null, name, 0, node.type); } + private static boolean isPrivateName(String name) { + return name != null && !name.isEmpty() && name.charAt(0) == '#'; + } + + /** + * Build a {@code LOAD_LITERAL} IR node that pushes the {@link SymbolKey} for the given private + * name (e.g., {@code #foo}) at runtime. The key is shared across all functions of the enclosing + * class; each function gets its own literal-table entry pointing to the same key instance. + */ + private Node loadPrivateSymbolLiteral(String privateName) { + SymbolKey key = null; + for (Map scope : privateSymbolScopes) { + SymbolKey candidate = scope.get(privateName); + if (candidate != null) { + key = candidate; + break; + } + } + if (key == null) { + parser.reportError("msg.undeclared.private.name", privateName); + // Fall back to a locally-created key so IR construction proceeds. + key = new SymbolKey(privateName, org.mozilla.javascript.Symbol.Kind.PRIVATE); + } + Node load = new Node(Token.LOAD_LITERAL); + load.putIntProp(Node.LITERAL_INDEX_PROP, parser.currentScriptOrFn.addLiteral(key)); + return load; + } + private Node transformTemplateLiteral(TemplateLiteral node) { List elems = node.getElements(); // start with an empty string to ensure ToString() for each substitution @@ -1096,12 +1597,12 @@ private Node transformTemplateLiteralCall(TaggedTemplateLiteral node) { call.addChildToBack(transform(elem)); } } - parser.currentScriptOrFn.addTemplateLiteral(templateLiteral); + parser.currentScriptOrFn.addLiteral(templateLiteral); return call; } private Node transformRegExp(RegExpLiteral node) { - parser.currentScriptOrFn.addRegExp(node); + parser.currentScriptOrFn.addLiteral(node); return node; } @@ -1384,6 +1885,12 @@ private Node transformYield(Yield node) { return new Node(node.getType(), node.getLineno(), node.getColumn()); } + private Node transformAwait(AwaitExpression node) { + Node kid = node.getValue() == null ? null : transform(node.getValue()); + if (kid != null) return new Node(Token.AWAIT, kid, node.getLineno(), node.getColumn()); + return new Node(Token.AWAIT, node.getLineno(), node.getColumn()); + } + private Node transformSpread(Spread node) { Node kid = transform(node.getExpression()); return new Node(node.getType(), kid, node.getLineno(), node.getColumn()); @@ -1579,7 +2086,13 @@ private Node initFunction( // Add return to end if needed. Node lastStmt = statements.getLastChild(); if (lastStmt == null || lastStmt.getType() != Token.RETURN) { - statements.addChildToBack(new Node(Token.RETURN)); + if (fnNode.isClassConstructor()) { + // Class constructors should return 'this' implicitly. + // For derived constructors, 'this' was set by super(). + statements.addChildToBack(new Node(Token.RETURN, new Node(Token.THIS))); + } else { + statements.addChildToBack(new Node(Token.RETURN)); + } } Node result = Node.newString(Token.FUNCTION, fnNode.getName()); @@ -1694,7 +2207,8 @@ private Node createForIn( Node body, AstNode ast, boolean isForEach, - boolean isForOf) { + boolean isForOf, + boolean isForAwaitOf) { astNodePos.push(ast); try { int destructuring = -1; @@ -1731,18 +2245,31 @@ private Node createForIn( } Node localBlock = new Node(Token.LOCAL_BLOCK); - int initType = - isForEach - ? Token.ENUM_INIT_VALUES - : isForOf - ? Token.ENUM_INIT_VALUES_IN_ORDER - : (destructuring != -1 - ? Token.ENUM_INIT_ARRAY - : Token.ENUM_INIT_KEYS); + int initType; + if (isForAwaitOf) { + initType = Token.ENUM_INIT_ASYNC_ITERATOR; + } else if (isForEach) { + initType = Token.ENUM_INIT_VALUES; + } else if (isForOf) { + initType = Token.ENUM_INIT_VALUES_IN_ORDER; + } else { + initType = destructuring != -1 ? Token.ENUM_INIT_ARRAY : Token.ENUM_INIT_KEYS; + } Node init = new Node(initType, obj); init.putProp(Node.LOCAL_BLOCK_PROP, localBlock); - Node cond = new Node(Token.ENUM_NEXT); - cond.putProp(Node.LOCAL_BLOCK_PROP, localBlock); + Node cond; + if (isForAwaitOf) { + // cond = ENUM_ASYNC_STEP(AWAIT(ENUM_ASYNC_NEXT)); produces a boolean !done. + Node asyncNext = new Node(Token.ENUM_ASYNC_NEXT); + asyncNext.putProp(Node.LOCAL_BLOCK_PROP, localBlock); + Node await = new Node(Token.AWAIT, asyncNext); + Node asyncStep = new Node(Token.ENUM_ASYNC_STEP, await); + asyncStep.putProp(Node.LOCAL_BLOCK_PROP, localBlock); + cond = asyncStep; + } else { + cond = new Node(Token.ENUM_NEXT); + cond.putProp(Node.LOCAL_BLOCK_PROP, localBlock); + } Node id = new Node(Token.ENUM_ID); id.putProp(Node.LOCAL_BLOCK_PROP, localBlock); @@ -1892,7 +2419,7 @@ private Node createTryCatchFinally( // but prefix it with LEAVEWITH since try..catch produces // "with"code in order to limit the scope of the exception // object. - catchStatement.addChildToBack(new Node(Token.LEAVEWITH)); + catchStatement.addChildToBack(new Node(Token.LEAVE_SCOPE)); catchStatement.addChildToBack(makeJump(Token.GOTO, endCatch)); // Create condition "if" when present @@ -1911,13 +2438,9 @@ private Node createTryCatchFinally( catchScope.putIntProp(Node.CATCH_SCOPE_PROP, scopeIndex); catchScopeBlock.addChildToBack(catchScope); - // Add with statement based on catch scope object - catchScopeBlock.addChildToBack( - createWith( - createUseLocal(catchScopeBlock), - condStmt, - catchLineno, - catchColumn)); + parser.setRequiresActivation(); + catchScopeBlock.addChildToBack(condStmt); + catchScopeBlock.addChildToBack(new Node(Token.LEAVE_SCOPE)); // move to next cb cb = cb.getNext(); @@ -1935,22 +2458,7 @@ private Node createTryCatchFinally( } if (hasFinally) { - Node finallyTarget = Node.newTarget(); - pn.setFinally(finallyTarget); - - // add jsr finally to the try block - pn.addChildToBack(makeJump(Token.JSR, finallyTarget)); - - // jump around finally code - Node finallyEnd = Node.newTarget(); - pn.addChildToBack(makeJump(Token.GOTO, finallyEnd)); - - pn.addChildToBack(finallyTarget); - Node fBlock = new Node(Token.FINALLY, finallyBlock); - fBlock.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock); - pn.addChildToBack(fBlock); - - pn.addChildToBack(finallyEnd); + Parser.makeFinallyNode(finallyBlock, handlerBlock, pn); } handlerBlock.addChildToBack(pn); return handlerBlock; @@ -1962,7 +2470,7 @@ private Node createWith(Node obj, Node body, int lineno, int column) { result.addChildToBack(new Node(Token.ENTERWITH, obj)); Node bodyNode = new Node(Token.WITH, body, lineno, column); result.addChildrenToBack(bodyNode); - result.addChildToBack(new Node(Token.LEAVEWITH)); + result.addChildToBack(new Node(Token.LEAVE_SCOPE)); return result; } diff --git a/rhino/src/main/java/org/mozilla/javascript/Icode.java b/rhino/src/main/java/org/mozilla/javascript/Icode.java index 7e9f640a924..c1534c0a7ca 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Icode.java +++ b/rhino/src/main/java/org/mozilla/javascript/Icode.java @@ -162,14 +162,96 @@ abstract class Icode { // delete super.prop Icode_DELPROP_SUPER = Icode_CALL_ON_SUPER - 1, + // Call super() in a derived class constructor + Icode_CONSTRUCT_SUPER = Icode_DELPROP_SUPER - 1, + + // Call super(...arr) in a derived class constructor. Top of stack is the + // array-like whose elements are spread as the super constructor arguments. + Icode_CONSTRUCT_SUPER_SPREAD = Icode_CONSTRUCT_SUPER - 1, + + // Class declaration with extends: stack has superclass, creates constructor with + // homeObject + Icode_CLASS_STMT = Icode_CONSTRUCT_SUPER_SPREAD - 1, + + // Class expression with extends: stack has superclass, creates constructor with + // homeObject + Icode_CLASS_EXPR = Icode_CLASS_STMT - 1, + // spread - Icode_SPREAD = Icode_DELPROP_SUPER - 1, + Icode_SPREAD = Icode_CLASS_EXPR - 1, // object rest - create object excluding extracted keys Icode_OBJECT_REST = Icode_SPREAD - 1, + // Wrap top-of-stack in an AwaitMarker (used inside async generator bodies so the + // driver can distinguish await from yield). + Icode_WRAP_AWAIT = Icode_OBJECT_REST - 1, + + // Define a method on a class prototype + Icode_DEFINE_CLASS_METHOD = Icode_WRAP_AWAIT - 1, + + // Define a getter on a class prototype + Icode_DEFINE_CLASS_GETTER = Icode_DEFINE_CLASS_METHOD - 1, + + // Define a setter on a class prototype + Icode_DEFINE_CLASS_SETTER = Icode_DEFINE_CLASS_GETTER - 1, + + // Define a static method on a class constructor + Icode_DEFINE_STATIC_CLASS_METHOD = Icode_DEFINE_CLASS_SETTER - 1, + + // Define a static getter on a class constructor + Icode_DEFINE_STATIC_CLASS_GETTER = Icode_DEFINE_STATIC_CLASS_METHOD - 1, + + // Define a static setter on a class constructor + Icode_DEFINE_STATIC_CLASS_SETTER = Icode_DEFINE_STATIC_CLASS_GETTER - 1, + + // Define a static named field on a class constructor (value on stack) + Icode_DEFINE_STATIC_CLASS_FIELD = Icode_DEFINE_STATIC_CLASS_SETTER - 1, + + // Define a static computed field on a class constructor (key and value on stack) + Icode_DEFINE_STATIC_CLASS_COMPUTED_FIELD = Icode_DEFINE_STATIC_CLASS_FIELD - 1, + + // Define a class private field: stack has target, symbol key, value -> ... + Icode_DEFINE_PRIVATE_FIELD = Icode_DEFINE_STATIC_CLASS_COMPUTED_FIELD - 1, + + // Define a class private getter: stack has target, symbol key, fn -> ... fn + Icode_DEFINE_PRIVATE_GETTER = Icode_DEFINE_PRIVATE_FIELD - 1, + + // Define a class private setter: stack has target, symbol key, fn -> ... fn + Icode_DEFINE_PRIVATE_SETTER = Icode_DEFINE_PRIVATE_GETTER - 1, + + // Store instance computed field keys on a constructor. + // Stack has constructor and `count` keys, with the constructor underneath. + // indexReg holds `count`. After the op: ... constructor. + Icode_STORE_CLASS_COMPUTED_KEYS = Icode_DEFINE_PRIVATE_SETTER - 1, + + // Push the i-th pre-evaluated instance computed field key of the currently + // executing function onto the stack. indexReg holds the index. + Icode_GET_CLASS_COMPUTED_KEY = Icode_STORE_CLASS_COMPUTED_KEYS - 1, + + // Define a method on a class prototype with a computed key on the stack. + // Stack: ... constructor key -> ... constructor. indexReg holds the method fn index. + Icode_DEFINE_CLASS_COMPUTED_METHOD = Icode_GET_CLASS_COMPUTED_KEY - 1, + + // Define a getter on a class prototype with a computed key on the stack. + Icode_DEFINE_CLASS_COMPUTED_GETTER = Icode_DEFINE_CLASS_COMPUTED_METHOD - 1, + + // Define a setter on a class prototype with a computed key on the stack. + Icode_DEFINE_CLASS_COMPUTED_SETTER = Icode_DEFINE_CLASS_COMPUTED_GETTER - 1, + + // Define a static method on a class constructor with a computed key on the stack. + Icode_DEFINE_STATIC_CLASS_COMPUTED_METHOD = Icode_DEFINE_CLASS_COMPUTED_SETTER - 1, + + // Define a static getter on a class constructor with a computed key on the stack. + Icode_DEFINE_STATIC_CLASS_COMPUTED_GETTER = + Icode_DEFINE_STATIC_CLASS_COMPUTED_METHOD - 1, + + // Define a static setter on a class constructor with a computed key on the stack. + Icode_DEFINE_STATIC_CLASS_COMPUTED_SETTER = + Icode_DEFINE_STATIC_CLASS_COMPUTED_GETTER - 1, + // Last icode - MIN_ICODE = Icode_OBJECT_REST; + MIN_ICODE = Icode_DEFINE_STATIC_CLASS_COMPUTED_SETTER; static String bytecodeName(int bytecode) { if (!validBytecode(bytecode)) { @@ -359,10 +441,58 @@ static String bytecodeName(int bytecode) { return "CALL_ON_SUPER"; case Icode_DELPROP_SUPER: return "DELPROP_SUPER"; + case Icode_CONSTRUCT_SUPER: + return "CONSTRUCT_SUPER"; + case Icode_CONSTRUCT_SUPER_SPREAD: + return "CONSTRUCT_SUPER_SPREAD"; + case Icode_CLASS_STMT: + return "CLASS_STMT"; + case Icode_CLASS_EXPR: + return "CLASS_EXPR"; case Icode_SPREAD: return "SPREAD"; case Icode_OBJECT_REST: return "OBJECT_REST"; + case Icode_WRAP_AWAIT: + return "WRAP_AWAIT"; + case Icode_DEFINE_CLASS_METHOD: + return "DEFINE_CLASS_METHOD"; + case Icode_DEFINE_CLASS_GETTER: + return "DEFINE_CLASS_GETTER"; + case Icode_DEFINE_CLASS_SETTER: + return "DEFINE_CLASS_SETTER"; + case Icode_DEFINE_STATIC_CLASS_METHOD: + return "DEFINE_STATIC_CLASS_METHOD"; + case Icode_DEFINE_STATIC_CLASS_GETTER: + return "DEFINE_STATIC_CLASS_GETTER"; + case Icode_DEFINE_STATIC_CLASS_SETTER: + return "DEFINE_STATIC_CLASS_SETTER"; + case Icode_DEFINE_STATIC_CLASS_FIELD: + return "DEFINE_STATIC_CLASS_FIELD"; + case Icode_DEFINE_STATIC_CLASS_COMPUTED_FIELD: + return "DEFINE_STATIC_CLASS_COMPUTED_FIELD"; + case Icode_DEFINE_PRIVATE_FIELD: + return "DEFINE_PRIVATE_FIELD"; + case Icode_DEFINE_PRIVATE_GETTER: + return "DEFINE_PRIVATE_GETTER"; + case Icode_DEFINE_PRIVATE_SETTER: + return "DEFINE_PRIVATE_SETTER"; + case Icode_STORE_CLASS_COMPUTED_KEYS: + return "STORE_CLASS_COMPUTED_KEYS"; + case Icode_GET_CLASS_COMPUTED_KEY: + return "GET_CLASS_COMPUTED_KEY"; + case Icode_DEFINE_CLASS_COMPUTED_METHOD: + return "DEFINE_CLASS_COMPUTED_METHOD"; + case Icode_DEFINE_CLASS_COMPUTED_GETTER: + return "DEFINE_CLASS_COMPUTED_GETTER"; + case Icode_DEFINE_CLASS_COMPUTED_SETTER: + return "DEFINE_CLASS_COMPUTED_SETTER"; + case Icode_DEFINE_STATIC_CLASS_COMPUTED_METHOD: + return "DEFINE_STATIC_CLASS_COMPUTED_METHOD"; + case Icode_DEFINE_STATIC_CLASS_COMPUTED_GETTER: + return "DEFINE_STATIC_CLASS_COMPUTED_GETTER"; + case Icode_DEFINE_STATIC_CLASS_COMPUTED_SETTER: + return "DEFINE_STATIC_CLASS_COMPUTED_SETTER"; } // icode without name diff --git a/rhino/src/main/java/org/mozilla/javascript/IdFunctionObject.java b/rhino/src/main/java/org/mozilla/javascript/IdFunctionObject.java index e81379675b7..cf3eec2e034 100644 --- a/rhino/src/main/java/org/mozilla/javascript/IdFunctionObject.java +++ b/rhino/src/main/java/org/mozilla/javascript/IdFunctionObject.java @@ -11,9 +11,10 @@ import java.util.Objects; public class IdFunctionObject extends BaseFunction { - private static final long serialVersionUID = -5332312783643935019L; + private static final long serialVersionUID = 4323463961654640261L; public IdFunctionObject(IdFunctionCall idcall, Object tag, int id, int arity) { + super(null); if (arity < 0) throw new IllegalArgumentException(); this.idcall = idcall; @@ -81,17 +82,17 @@ public Scriptable getPrototype() { } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { // We need to do some sneakiness here for constructors... return idcall.execIdCall(this, cx, scope, getThisObj(thisObj), args); } - public final Scriptable getThisObj(Scriptable thisObj) { + public final Scriptable getThisObj(Object thisObj) { if (useCallAsConstructor && (thisObj == null || Undefined.isUndefined(thisObj))) { var res = ScriptableObject.getTopLevelScope(getDeclarationScope()).getGlobalThis(); return res; } else { - return thisObj; + return ScriptRuntime.toObject(getDeclarationScope(), thisObj); } } diff --git a/rhino/src/main/java/org/mozilla/javascript/ImporterTopLevel.java b/rhino/src/main/java/org/mozilla/javascript/ImporterTopLevel.java index 06743ae8775..bdb9a54fda4 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ImporterTopLevel.java +++ b/rhino/src/main/java/org/mozilla/javascript/ImporterTopLevel.java @@ -9,6 +9,7 @@ package org.mozilla.javascript; import static org.mozilla.javascript.ClassDescriptor.Destination.PROTO; +import static org.mozilla.javascript.UniqueTag.NOT_FOUND; import java.util.ArrayList; diff --git a/rhino/src/main/java/org/mozilla/javascript/Interpreter.java b/rhino/src/main/java/org/mozilla/javascript/Interpreter.java index 862c7e96ef9..3c002e677a6 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Interpreter.java +++ b/rhino/src/main/java/org/mozilla/javascript/Interpreter.java @@ -9,6 +9,7 @@ import static org.mozilla.javascript.ScriptableObject.PERMANENT; import static org.mozilla.javascript.ScriptableObject.READONLY; import static org.mozilla.javascript.UniqueTag.DOUBLE_MARK; +import static org.mozilla.javascript.UniqueTag.NOT_FOUND; import java.io.PrintStream; import java.io.Serializable; @@ -74,7 +75,8 @@ private static class CallFrame implements Cloneable, Serializable { final boolean useActivation; boolean isContinuationsTopFrame; - final Scriptable thisObj; + Object thisObj; + final Object newTarget; // The values that change during interpretation @@ -92,7 +94,8 @@ private static class CallFrame implements Cloneable, Serializable { CallFrame( Context cx, - Scriptable thisObj, + Object thisObj, + Object newTarget, ScriptOrFn fnOrScript, InterpreterData code, CallFrame parentFrame, @@ -115,6 +118,7 @@ private static class CallFrame implements Cloneable, Serializable { this.fnOrScript = fnOrScript; varSource = this; this.thisObj = thisObj; + this.newTarget = newTarget; this.parentFrame = parentFrame; if (parentFrame == null) { @@ -182,6 +186,7 @@ private CallFrame( isContinuationsTopFrame = original.isContinuationsTopFrame; thisObj = original.thisObj; + newTarget = original.newTarget; result = original.result; resultDbl = original.resultDbl; @@ -234,6 +239,7 @@ private CallFrame( isContinuationsTopFrame = original.isContinuationsTopFrame; thisObj = original.thisObj; + newTarget = original.newTarget; result = original.result; resultDbl = original.resultDbl; @@ -303,7 +309,7 @@ void initializeArgs( // creation // Ref: Ecma 2026, 10.2.11, FunctionDeclarationInstantiation - if (desc.getFunctionCount() != 0 && !desc.isES6Generator()) { + if (desc.getFunctionCount() != 0 && !desc.isES6Generator() && !desc.isAsync()) { if (desc.getFunctionType() != 0 && !desc.requiresActivationFrame()) Kit.codeBug(); for (int i = 0; i < desc.getFunctionCount(); i++) { JSDescriptor fdesc = desc.getFunction(i); @@ -541,24 +547,22 @@ public void delete(String name) {} public void delete(int index) {} @Override - public Object get(String name, Scriptable start) { + public void delete(Symbol key) {} + + @Override + public Object get(String name, VarScope scope) { int offset = getOffsets().getOrDefault(name, -1); return offset >= 0 ? frame.getFromVars(offset) : NOT_FOUND; } @Override - public Object get(int index, Scriptable start) { + public Object get(int index, VarScope scope) { return NOT_FOUND; } @Override - public String getClassName() { - return "debugscope"; - } - - @Override - public Object getDefaultValue(Class hint) { - return null; + public Object get(Symbol key, VarScope scope) { + return NOT_FOUND; } @Override @@ -572,27 +576,22 @@ public VarScope getParentScope() { } @Override - public Scriptable getPrototype() { - return null; - } - - @Override - public boolean has(String name, Scriptable start) { + public boolean has(String name, VarScope start) { return getOffsets().containsKey(name); } @Override - public boolean has(int index, Scriptable start) { + public boolean has(int index, VarScope start) { return false; } @Override - public boolean hasInstance(Scriptable instance) { + public boolean has(Symbol key, VarScope start) { return false; } @Override - public void put(String name, Scriptable start, Object value) { + public void put(String name, VarScope start, Object value) { int offset = getOffsets().getOrDefault(name, -1); if (offset >= 0) { frame.setInVars(offset, value); @@ -600,22 +599,17 @@ public void put(String name, Scriptable start, Object value) { } @Override - public void put(int index, Scriptable start, Object value) { + public void put(int index, VarScope start, Object value) { // Do nothing. } @Override - public void setParentScope(VarScope parent) { + public void put(Symbol key, VarScope start, Object value) { // Do nothing. } @Override - public void setPrototype(Scriptable prototype) { - // Do nothing. - } - - @Override - public void defineConst(String name, Scriptable start) { + public void defineConst(String name, VarScope start) { // TODO Auto-generated method stub } @@ -632,7 +626,7 @@ public boolean isConst(String name) { } @Override - public void putConst(String name, Scriptable start, Object value) { + public void putConst(String name, VarScope start, Object value) { // TODO Auto-generated method stub } @@ -1176,8 +1170,9 @@ static Object interpret( InterpreterData idata, Context cx, VarScope scope, - Scriptable thisObj, - Object[] args) { + Object thisObj, + Object[] args, + Object newTarget) { if (!ScriptRuntime.hasTopCall(cx)) Kit.codeBug(); var desc = ifun.getDescriptor(); @@ -1225,7 +1220,8 @@ static Object interpret( args.length, ifun, idata, - null); + null, + newTarget); frame.isContinuationsTopFrame = cx.isContinuationsTopCall; cx.isContinuationsTopCall = false; @@ -1244,7 +1240,7 @@ static class GeneratorState { } public static Object resumeGenerator( - Context cx, Scriptable scope, int operation, Object savedState, Object value) { + Context cx, VarScope scope, int operation, Object savedState, Object value) { CallFrame frame = (CallFrame) savedState; CallFrame activeFrame = frame.shallowCloneFrozen((CallFrame) cx.lastInterpreterFrame); try { @@ -1496,10 +1492,15 @@ static void dumpJumpTarget(String tname, ICodeDumpContext ctx) { instructionObjs[base + Icode_CALLSPECIAL_OPTIONAL] = new DoCallSpecial(); instructionObjs[base + Token.CALL] = new DoCallByteCode(); instructionObjs[base + Icode_CALL_ON_SUPER] = new DoCallByteCode(); + instructionObjs[base + Icode_CONSTRUCT_SUPER] = new DoConstructSuper(); + instructionObjs[base + Icode_CONSTRUCT_SUPER_SPREAD] = new DoConstructSuperSpread(); + instructionObjs[base + Icode_CLASS_EXPR] = new DoClassExpr(); instructionObjs[base + Icode_TAIL_CALL] = new DoCallByteCode(); instructionObjs[base + Token.REF_CALL] = new DoCallByteCode(); instructionObjs[base + Token.NEW] = new DoNew(); instructionObjs[base + Token.TYPEOF] = new DoTypeOf(); + instructionObjs[base + Token.TO_OBJECT_COERCIBLE] = new DoToObjectCoercible(); + instructionObjs[base + Token.ITERATOR_CLOSE_ABRUPT] = new DoIteratorCloseAbrupt(); instructionObjs[base + Icode_TYPEOFNAME] = new DoTypeOfName(); instructionObjs[base + Token.STRING] = new DoString(); instructionObjs[base + Icode_SHORTNUMBER] = new DoShortNumber(); @@ -1521,19 +1522,24 @@ static void dumpJumpTarget(String tname, ICodeDumpContext ctx) { instructionObjs[base + Token.THIS] = new DoThis(); instructionObjs[base + Token.SUPER] = new DoSuper(); instructionObjs[base + Token.THISFN] = new DoThisFunction(); + instructionObjs[base + Token.NEW_TARGET] = new DoNewTarget(); instructionObjs[base + Token.FALSE] = new DoFalse(); instructionObjs[base + Token.TRUE] = new DoTrue(); instructionObjs[base + Icode_UNDEF] = new DoUndef(); instructionObjs[base + Token.ENTERWITH] = new DoEnterWith(); - instructionObjs[base + Token.LEAVEWITH] = new DoLeaveWith(); + instructionObjs[base + Token.ENTER_SCOPE] = new DoEnterScope(); + instructionObjs[base + Token.LEAVE_SCOPE] = new DoLeaveScope(); instructionObjs[base + Token.CATCH_SCOPE] = new DoCatchScope(); instructionObjs[base + Token.ENUM_INIT_KEYS] = new DoEnumInit(); instructionObjs[base + Token.ENUM_INIT_VALUES] = new DoEnumInit(); instructionObjs[base + Token.ENUM_INIT_ARRAY] = new DoEnumInit(); instructionObjs[base + Token.ENUM_INIT_VALUES_IN_ORDER] = new DoEnumInit(); instructionObjs[base + Token.ENUM_INIT_VALUES_IN_ORDER] = new DoEnumInit(); + instructionObjs[base + Token.ENUM_INIT_ASYNC_ITERATOR] = new DoEnumInitAsyncIterator(); instructionObjs[base + Token.ENUM_NEXT] = new DoEnumOp(); instructionObjs[base + Token.ENUM_ID] = new DoEnumOp(); + instructionObjs[base + Token.ENUM_ASYNC_NEXT] = new DoEnumAsyncNext(); + instructionObjs[base + Token.ENUM_ASYNC_STEP] = new DoEnumAsyncStep(); instructionObjs[base + Token.REF_SPECIAL] = new DoRefSpecial(); instructionObjs[base + Token.REF_MEMBER] = new DoRefMember(); instructionObjs[base + Token.REF_NS_MEMBER] = new DoRefNsMember(); @@ -1543,10 +1549,38 @@ static void dumpJumpTarget(String tname, ICodeDumpContext ctx) { instructionObjs[base + Icode_SCOPE_SAVE] = new DoScopeSave(); instructionObjs[base + Icode_SPREAD] = new DoSpread(); instructionObjs[base + Icode_OBJECT_REST] = new DoObjectRest(); + instructionObjs[base + Icode_WRAP_AWAIT] = new DoWrapAwait(); + instructionObjs[base + Icode_DEFINE_CLASS_METHOD] = new DoDefineClassMethod(); + instructionObjs[base + Icode_DEFINE_CLASS_GETTER] = new DoDefineClassGetter(); + instructionObjs[base + Icode_DEFINE_CLASS_SETTER] = new DoDefineClassSetter(); + instructionObjs[base + Icode_DEFINE_STATIC_CLASS_METHOD] = new DoDefineStaticClassMethod(); + instructionObjs[base + Icode_DEFINE_STATIC_CLASS_GETTER] = new DoDefineStaticClassGetter(); + instructionObjs[base + Icode_DEFINE_STATIC_CLASS_SETTER] = new DoDefineStaticClassSetter(); + instructionObjs[base + Icode_DEFINE_STATIC_CLASS_FIELD] = new DoDefineStaticClassField(); + instructionObjs[base + Icode_DEFINE_STATIC_CLASS_COMPUTED_FIELD] = + new DoDefineStaticClassComputedField(); + instructionObjs[base + Icode_DEFINE_CLASS_COMPUTED_METHOD] = + new DoDefineClassComputedMethod(); + instructionObjs[base + Icode_DEFINE_CLASS_COMPUTED_GETTER] = + new DoDefineClassComputedGetter(); + instructionObjs[base + Icode_DEFINE_CLASS_COMPUTED_SETTER] = + new DoDefineClassComputedSetter(); + instructionObjs[base + Icode_DEFINE_STATIC_CLASS_COMPUTED_METHOD] = + new DoDefineStaticClassComputedMethod(); + instructionObjs[base + Icode_DEFINE_STATIC_CLASS_COMPUTED_GETTER] = + new DoDefineStaticClassComputedGetter(); + instructionObjs[base + Icode_DEFINE_STATIC_CLASS_COMPUTED_SETTER] = + new DoDefineStaticClassComputedSetter(); + instructionObjs[base + Icode_DEFINE_PRIVATE_FIELD] = new DoDefinePrivateField(); + instructionObjs[base + Icode_DEFINE_PRIVATE_GETTER] = new DoDefinePrivateAccessor(false); + instructionObjs[base + Icode_DEFINE_PRIVATE_SETTER] = new DoDefinePrivateAccessor(true); + instructionObjs[base + Icode_STORE_CLASS_COMPUTED_KEYS] = new DoStoreClassComputedKeys(); + instructionObjs[base + Icode_GET_CLASS_COMPUTED_KEY] = new DoGetClassComputedKey(); instructionObjs[base + Icode_CLOSURE_EXPR] = new DoClosureExpr(); instructionObjs[base + Icode_METHOD_EXPR] = new DoMethodExpr(); instructionObjs[base + Icode_CLOSURE_STMT] = new DoClosureStatement(); instructionObjs[base + Token.REGEXP] = new DoRegExp(); + instructionObjs[base + Token.LOAD_LITERAL] = new DoLoadLiteral(); instructionObjs[base + Icode_TEMPLATE_LITERAL_CALLSITE] = new DoTemplateLiteralCallSite(); instructionObjs[base + Icode_LITERAL_NEW_OBJECT] = new DoLiteralNewObject(); instructionObjs[base + Icode_LITERAL_NEW_ARRAY] = new DoLiteralNewArray(); @@ -1925,17 +1959,9 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { if (!frame.frozen) { // First time encountering this opcode: create new generator // object and return - generatorCreate(cx, frame); + generatorCreate(cx, frame, state); return BREAK_LOOP; } - /* This is both where we yield from and re-enter the - * generator. - */ - if (!frame.frozen) { - return new YieldResult( - freezeGenerator( - cx, frame, state, state.generatorState, op == Icode_YIELD_STAR)); - } Object obj = thawGenerator(frame, state, state.generatorState, op); if (obj != Scriptable.NOT_FOUND) { state.throwable = obj; @@ -1944,18 +1970,29 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { return null; } - private static void generatorCreate(Context cx, CallFrame frame) { + private static void generatorCreate(Context cx, CallFrame frame, InterpreterState state) { // First time encountering this opcode: create new generator // object and return frame.pc--; // we want to come back here when we resume + frame.savedStackTop = state.stackTop; CallFrame generatorFrame = captureFrameForGenerator(frame); generatorFrame.frozen = true; if (cx.getLanguageVersion() >= Context.VERSION_ES6) { - frame.result = - new ES6Generator( - frame.scope, - (JSFunction) generatorFrame.fnOrScript, - generatorFrame); + JSFunction fn = (JSFunction) generatorFrame.fnOrScript; + ES6Generator gen = new ES6Generator(frame.scope, fn, generatorFrame); + if (fn.isAsync() && fn.isGeneratorFunction()) { + // Async generator function: wrap the underlying generator in an async + // generator object that drives requests through a FIFO queue. + frame.result = new ES6AsyncGenerator(frame.scope, gen); + } else if (fn.isAsync() && !fn.isGeneratorFunction()) { + // Async non-generator function: drive via Promise runner. + // isGeneratorFunction() returns descriptor.isES6Generator(), which is + // false for async non-generators (we only called setIsGenerator(), not + // setIsES6Generator(), in IRFactory). + frame.result = NativePromise.createAsyncFunctionPromise(cx, frame.scope, gen); + } else { + frame.result = gen; + } } else { frame.result = new NativeGenerator( @@ -1989,7 +2026,10 @@ private static Object freezeGenerator( frame.resultDbl = frame.sDbl[state.stackTop]; frame.savedStackTop = state.stackTop; frame.pc--; // we want to come back here when we resume - ScriptRuntime.exitActivationFunction(cx); + var desc = frame.fnOrScript.getDescriptor(); + if (!desc.isES6Generator() && !desc.isAsync()) { + ScriptRuntime.exitActivationFunction(cx); + } final Object result = (frame.result != DOUBLE_MARK) ? frame.result @@ -2898,6 +2938,7 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { private static class DoSetConst extends InstructionClass { @Override + @SuppressWarnings("unchecked") NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { final Object[] stack = frame.stack; final double[] sDbl = frame.sDbl; @@ -3416,7 +3457,7 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { // Check if the lookup result is a function and throw if it's not // must not be done sooner according to the spec Callable fun = result.getCallable(); - Scriptable funThisObj = result.getThis(); + Object funThisObj = result.getThis(); Scriptable funHomeObj = (fun instanceof BaseFunction) ? ((BaseFunction) fun).getHomeObject() : null; if (op == Icode_CALL_ON_SUPER) { @@ -3444,82 +3485,72 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { // if the function is already an interpreted function, which // should be the majority of cases. for (; ; ) { - if (fun instanceof KnownBuiltInFunction) { - KnownBuiltInFunction kfun = (KnownBuiltInFunction) fun; - // Bug 405654 -- make the best effort to keep - // Function.apply and Function.call within this - // interpreter loop invocation - if (BaseFunction.isApplyOrCall(kfun)) { - // funThisObj becomes fun - fun = ScriptRuntime.getCallable(funThisObj); - // first arg becomes thisObj - funThisObj = - getApplyThis( - cx, + if (BaseFunction.isApplyOrCall(fun)) { + // funThisObj becomes fun + var target = ScriptRuntime.getCallable(funThisObj); + // first arg becomes thisObj + funThisObj = + getApplyThis( + cx, + stack, + sDbl, + boundArgs, + state.stackTop + 1, + state.indexReg, + (Function) target, + frame); + if (BaseFunction.isApply(fun)) { + // Apply: second argument after new "this" + // should be array-like + // and we'll spread its elements on the stack + final Object[] callArgs; + if (blen > 1) { + callArgs = ScriptRuntime.getApplyArguments(cx, boundArgs[1]); + } else if (state.indexReg < 2) { + callArgs = ScriptRuntime.emptyArgs; + } else { + callArgs = + ScriptRuntime.getApplyArguments( + cx, stack[state.stackTop - blen + 2]); + } + + int alen = callArgs.length; + // We're coming from the outside, so this + // is replacing any bound args we might + // have had already. + boundArgs = callArgs; + blen = alen; + state.indexReg = alen; + } else { + // Call: shift args left, starting from 2nd + if (state.indexReg > 0) { + if (state.indexReg > 1 && blen == 0) { + System.arraycopy( + stack, + state.stackTop + 2, stack, + state.stackTop + 1, + state.indexReg - 1); + System.arraycopy( + sDbl, + state.stackTop + 2, sDbl, - boundArgs, state.stackTop + 1, - state.indexReg, - (Function) fun, - frame); - if (BaseFunction.isApply(kfun)) { - // Apply: second argument after new "this" - // should be array-like - // and we'll spread its elements on the stack - final Object[] callArgs; - if (blen > 1) { - callArgs = ScriptRuntime.getApplyArguments(cx, boundArgs[1]); - } else if (state.indexReg < 2) { - callArgs = ScriptRuntime.emptyArgs; + state.indexReg - 1); + } else if (state.indexReg > 1) { + Object[] newBArgs = new Object[boundArgs.length - 1]; + System.arraycopy(boundArgs, 1, newBArgs, 0, boundArgs.length - 1); + boundArgs = newBArgs; + blen = newBArgs.length; } else { - callArgs = - ScriptRuntime.getApplyArguments( - cx, stack[state.stackTop - blen + 2]); - } - - int alen = callArgs.length; - // We're coming from the outside, so this - // is replacing any bound args we might - // have had already. - boundArgs = callArgs; - blen = alen; - state.indexReg = alen; - } else { - // Call: shift args left, starting from 2nd - if (state.indexReg > 0) { - if (state.indexReg > 1 && blen == 0) { - System.arraycopy( - stack, - state.stackTop + 2, - stack, - state.stackTop + 1, - state.indexReg - 1); - System.arraycopy( - sDbl, - state.stackTop + 2, - sDbl, - state.stackTop + 1, - state.indexReg - 1); - } else if (state.indexReg > 1) { - Object[] newBArgs = new Object[boundArgs.length - 1]; - System.arraycopy( - boundArgs, 1, newBArgs, 0, boundArgs.length - 1); - boundArgs = newBArgs; - blen = newBArgs.length; - } else { - // Bound args is 1 long. - boundArgs = new Object[0]; - blen = 0; - } - state.indexReg--; + // Bound args is 1 long. + boundArgs = new Object[0]; + blen = 0; } + state.indexReg--; } - } else { - // Some other IdFunctionObject we don't know how to - // reduce. - break; } + fun = target; } else if (fun instanceof LambdaConstructor) { break; } else if (fun instanceof LambdaFunction) { @@ -3564,8 +3595,13 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { if (fun instanceof JSFunction && ((JSFunction) fun).getDescriptor().getCode() instanceof InterpreterData) { JSFunction ifun = (JSFunction) fun; - JSDescriptor desc = ifun.getDescriptor(); - InterpreterData idata = (InterpreterData) desc.getCode(); + + JSDescriptor desc = ifun.getDescriptor(); + if (desc.isClassConstructor()) { + throw ScriptRuntime.typeErrorById( + "msg.class.not.called.with.new", ifun.getFunctionName()); + } + var idata = (InterpreterData) desc.getCode(); if (frame.fnOrScript.getDescriptor().getSecurityDomain() == desc.getSecurityDomain()) { CallFrame callParentFrame = frame; @@ -3607,7 +3643,8 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { state.indexReg, ifun, idata, - callParentFrame); + callParentFrame, + ifun.getLexicalNewTarget()); if (op != Icode_TAIL_CALL) { frame.savedStackTop = state.stackTop; frame.savedCallOp = op; @@ -3635,8 +3672,9 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { return BREAK_WITHOUT_EXTENSION; } - if (fun instanceof IdFunctionObject) { - IdFunctionObject ifun = (IdFunctionObject) fun; + if (fun instanceof JSFunction) { + var ifun = (JSFunction) fun; + if (NativeContinuation.isContinuationConstructor(ifun)) { frame.stack[state.stackTop] = captureContinuation(cx, frame.parentFrame, false); return null; @@ -3681,14 +3719,17 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { if (lhs instanceof JSFunction && ((JSFunction) lhs).getConstructor() instanceof InterpreterData) { JSFunction f = (JSFunction) lhs; - JSDescriptor desc = f.getDescriptor(); - InterpreterData idata = (InterpreterData) desc.getConstructor(); + + JSDescriptor desc = f.getDescriptor(); + var idata = (InterpreterData) desc.getConstructor(); if (frame.fnOrScript.getDescriptor().getSecurityDomain() == desc.getSecurityDomain()) { if (cx.getLanguageVersion() >= Context.VERSION_ES6 - && f.getHomeObject() != null) { + && f.getHomeObject() != null + && !desc.isClassConstructor()) { // Only methods have home objects associated with - // them + // them - class constructors also have home objects + // (the superclass) but they ARE constructors. throw ScriptRuntime.typeErrorById("msg.not.ctor", f.getFunctionName()); } @@ -3707,7 +3748,8 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { state.indexReg, f, idata, - frame); + frame, + lhs); frame.stack[state.stackTop] = newInstance; frame.savedStackTop = state.stackTop; @@ -3715,23 +3757,64 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { return new StateContinueResult(calleeFrame, state.indexReg); } } + if (lhs instanceof JSFunction) { + var f = (JSFunction) lhs; + + if (NativeContinuation.isContinuationConstructor(f)) { + frame.stack[state.stackTop] = captureContinuation(cx, frame.parentFrame, false); + return null; + } + } + if (!(lhs instanceof Constructable)) { if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); throw ScriptRuntime.notFunctionError(lhs); } Constructable ctor = (Constructable) lhs; - if (ctor instanceof IdFunctionObject) { - IdFunctionObject ifun = (IdFunctionObject) ctor; - if (NativeContinuation.isContinuationConstructor(ifun)) { - frame.stack[state.stackTop] = captureContinuation(cx, frame.parentFrame, false); - return null; - } + Object[] outArgs = + getArgsArray(frame.stack, frame.sDbl, state.stackTop + 1, state.indexReg); + frame.stack[state.stackTop] = ctor.construct(cx, frame.scope, outArgs); + return null; + } + + @Override + void dumpICode(int op, String tname, ICodeDumpContext ctx) { + ctx.out.println(tname + " " + ctx.indexReg); + } + } + + private static class DoConstructSuper extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + if (state.instructionCounting) { + cx.instructionCount += INVOCATION_COST; + } + // stack layout: lookup_result arg0 .. argN -> result + // The lookup_result came from VALUE_AND_THIS on THISFN. + // We get the homeObject (superclass) from the current function. + state.stackTop -= state.indexReg; + + // The homeObject on the current function is the parent class + Scriptable homeObject = frame.fnOrScript.getHomeObject(); + if (homeObject == null || !(homeObject instanceof Constructable)) { + throw ScriptRuntime.typeErrorById("msg.extends.not.ctor"); } Object[] outArgs = getArgsArray(frame.stack, frame.sDbl, state.stackTop + 1, state.indexReg); - frame.stack[state.stackTop] = ctor.construct(cx, frame.scope, outArgs); + // Pass new.target through the super() call so the correct prototype is used + Object constructed; + if (homeObject instanceof JSFunction) { + constructed = + ((JSFunction) homeObject) + .construct(cx, frame.newTarget, frame.scope, null, outArgs); + } else { + constructed = ((Constructable) homeObject).construct(cx, frame.scope, outArgs); + } + // The result of super() becomes 'this' for the derived constructor. + frame.thisObj = constructed; + frame.stack[state.stackTop] = constructed; return null; } @@ -3741,6 +3824,38 @@ void dumpICode(int op, String tname, ICodeDumpContext ctx) { } } + private static class DoConstructSuperSpread extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + if (state.instructionCounting) { + cx.instructionCount += INVOCATION_COST; + } + // stack layout: lookup_result arrayArg -> result + Object arrayArg = frame.stack[state.stackTop]; + if (arrayArg == DOUBLE_MARK) + arrayArg = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + state.stackTop--; + + Scriptable homeObject = frame.fnOrScript.getHomeObject(); + if (homeObject == null || !(homeObject instanceof Constructable)) { + throw ScriptRuntime.typeErrorById("msg.extends.not.ctor"); + } + + Object[] outArgs = ScriptRuntime.getApplyArguments(cx, arrayArg); + Object constructed; + if (homeObject instanceof JSFunction) { + constructed = + ((JSFunction) homeObject) + .construct(cx, frame.newTarget, frame.scope, null, outArgs); + } else { + constructed = ((Constructable) homeObject).construct(cx, frame.scope, outArgs); + } + frame.thisObj = constructed; + frame.stack[state.stackTop] = constructed; + return null; + } + } + private static class DoTypeOf extends InstructionClass { @Override NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { @@ -3753,6 +3868,31 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { } } + private static class DoToObjectCoercible extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + Object[] stack = frame.stack; + double[] sDbl = frame.sDbl; + Object lhs = stack[state.stackTop]; + if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]); + stack[state.stackTop] = ScriptRuntime.toObject(cx, frame.scope, lhs); + return null; + } + } + + private static class DoIteratorCloseAbrupt extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + Object[] stack = frame.stack; + double[] sDbl = frame.sDbl; + Object iter = stack[state.stackTop]; + if (iter == DOUBLE_MARK) iter = ScriptRuntime.wrapNumber(sDbl[state.stackTop]); + ScriptRuntime.closeIteratorAbrupt(iter, cx, frame.scope); + stack[state.stackTop] = Undefined.instance; + return null; + } + } + private static class DoTypeOfName extends InstructionClass { @Override NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { @@ -4108,6 +4248,14 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { } } + private static class DoNewTarget extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + frame.stack[++state.stackTop] = frame.newTarget; + return null; + } + } + private static class DoFalse extends InstructionClass { @Override NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { @@ -4143,10 +4291,19 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { } } - private static class DoLeaveWith extends InstructionClass { + private static class DoEnterScope extends InstructionClass { @Override NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { - frame.scope = ScriptRuntime.leaveWith(frame.scope); + frame.scope = new LocalScope(frame.scope); + frame.stack[++state.stackTop] = frame.scope; + return null; + } + } + + private static class DoLeaveScope extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + frame.scope = ScriptRuntime.leaveScope(frame.scope); return null; } } @@ -4163,15 +4320,17 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { boolean afterFirstScope = (frame.idata.itsICode[frame.pc] != 0); Throwable caughtException = (Throwable) frame.stack[state.stackTop + 1]; - Scriptable lastCatchScope; + VarScope lastCatchScope; if (!afterFirstScope) { lastCatchScope = null; } else { - lastCatchScope = (Scriptable) frame.stack[state.indexReg]; + lastCatchScope = (VarScope) frame.stack[state.indexReg]; } - frame.stack[state.indexReg] = + VarScope catchScope = ScriptRuntime.newCatchScope( caughtException, lastCatchScope, state.stringReg, cx, frame.scope); + frame.stack[state.indexReg] = catchScope; + frame.scope = catchScope; ++frame.pc; return null; } @@ -4192,7 +4351,9 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { state.indexReg += frame.idata.itsMaxVars; int enumType = op == Token.ENUM_INIT_KEYS - ? ScriptRuntime.ENUMERATE_KEYS + ? cx.getLanguageVersion() <= Context.VERSION_1_8 + ? ScriptRuntime.ENUMERATE_KEYS + : ScriptRuntime.ENUMERATE_KEYS_NO_ITERATOR : op == Token.ENUM_INIT_VALUES ? ScriptRuntime.ENUMERATE_VALUES : op == Token.ENUM_INIT_VALUES_IN_ORDER @@ -4217,6 +4378,52 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { } } + private static class DoEnumInitAsyncIterator extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + Object lhs = frame.stack[state.stackTop]; + if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + state.indexReg += frame.idata.itsMaxVars; + frame.stack[state.indexReg] = ScriptRuntime.enumInitAsyncIterator(lhs, cx, frame.scope); + --state.stackTop; + return null; + } + } + + private static class DoEnumAsyncNext extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + state.indexReg += frame.idata.itsMaxVars; + Object enumObj = frame.stack[state.indexReg]; + frame.stack[++state.stackTop] = ScriptRuntime.enumAsyncNext(enumObj, cx); + return null; + } + } + + private static class DoWrapAwait extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + Object v = frame.stack[state.stackTop]; + if (v == DOUBLE_MARK) v = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + frame.stack[state.stackTop] = ScriptRuntime.wrapAwait(v); + return null; + } + } + + private static class DoEnumAsyncStep extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + state.indexReg += frame.idata.itsMaxVars; + Object enumObj = frame.stack[state.indexReg]; + Object awaited = frame.stack[state.stackTop]; + if (awaited == DOUBLE_MARK) { + awaited = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + } + frame.stack[state.stackTop] = ScriptRuntime.enumAsyncStep(enumObj, awaited, cx); + return null; + } + } + private static class DoRefSpecial extends InstructionClass { @Override NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { @@ -4285,6 +4492,293 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { } } + private static class DoClassExpr extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + // stack: ... superClass -> ... constructorFunction + Object superClassObj = frame.stack[state.stackTop]; + Scriptable superClass = null; + if (superClassObj != null + && !Undefined.isUndefined(superClassObj) + && superClassObj != UniqueTag.NULL_VALUE) { + if (!(superClassObj instanceof Scriptable)) { + throw ScriptRuntime.typeErrorById("msg.extends.not.ctor"); + } + superClass = (Scriptable) superClassObj; + } + + // Create the constructor function with superClass as homeObject + JSFunction fn = createMethod(cx, frame, state.indexReg, superClass); + + // Set up prototype chain + ScriptRuntime.setupClassPrototypeChain(fn, superClass, frame.scope); + + // Replace superClass on stack with the constructor function + frame.stack[state.stackTop] = fn; + return null; + } + + @Override + void dumpICode(int op, String tname, ICodeDumpContext ctx) { + ctx.out.println(tname + " #" + ctx.indexReg); + } + } + + private static class DoDefineClassMethod extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + // Constructor is on the stack top (peeked, not popped) + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + Object protoObj = constructor.getPrototypeProperty(); + Scriptable prototype = (protoObj instanceof Scriptable) ? (Scriptable) protoObj : null; + // Create method with homeObject = prototype (for super support) + JSFunction method = createMethod(cx, frame, state.indexReg, prototype); + ScriptRuntime.defineClassMethod(constructor, state.stringReg, method); + return null; + } + } + + private static class DoDefineStaticClassMethod extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + // Constructor is on the stack top (peeked, not popped) + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + // Create static method with homeObject = constructor (for super support) + JSFunction method = createMethod(cx, frame, state.indexReg, constructor); + ScriptRuntime.defineStaticClassMethod(constructor, state.stringReg, method); + return null; + } + } + + private static class DoDefineClassGetter extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + Object protoObj = constructor.getPrototypeProperty(); + Scriptable prototype = (protoObj instanceof Scriptable) ? (Scriptable) protoObj : null; + JSFunction method = createMethod(cx, frame, state.indexReg, prototype); + ScriptRuntime.defineClassGetter(constructor, state.stringReg, method); + return null; + } + } + + private static class DoDefineClassSetter extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + Object protoObj = constructor.getPrototypeProperty(); + Scriptable prototype = (protoObj instanceof Scriptable) ? (Scriptable) protoObj : null; + JSFunction method = createMethod(cx, frame, state.indexReg, prototype); + ScriptRuntime.defineClassSetter(constructor, state.stringReg, method); + return null; + } + } + + private static class DoDefineStaticClassGetter extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + JSFunction method = createMethod(cx, frame, state.indexReg, constructor); + ScriptRuntime.defineStaticClassGetter(constructor, state.stringReg, method); + return null; + } + } + + private static class DoDefineStaticClassSetter extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + JSFunction method = createMethod(cx, frame, state.indexReg, constructor); + ScriptRuntime.defineStaticClassSetter(constructor, state.stringReg, method); + return null; + } + } + + private static class DoDefineClassComputedMethod extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + // Stack: ... constructor key -> ... constructor + Object key = frame.stack[state.stackTop]; + if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + Object protoObj = constructor.getPrototypeProperty(); + Scriptable prototype = (protoObj instanceof Scriptable) ? (Scriptable) protoObj : null; + JSFunction method = createMethod(cx, frame, state.indexReg, prototype); + ScriptRuntime.defineClassComputedMethod(constructor, key, method); + return null; + } + } + + private static class DoDefineClassComputedGetter extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + Object key = frame.stack[state.stackTop]; + if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + Object protoObj = constructor.getPrototypeProperty(); + Scriptable prototype = (protoObj instanceof Scriptable) ? (Scriptable) protoObj : null; + JSFunction method = createMethod(cx, frame, state.indexReg, prototype); + ScriptRuntime.defineClassComputedGetter(constructor, key, method); + return null; + } + } + + private static class DoDefineClassComputedSetter extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + Object key = frame.stack[state.stackTop]; + if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + Object protoObj = constructor.getPrototypeProperty(); + Scriptable prototype = (protoObj instanceof Scriptable) ? (Scriptable) protoObj : null; + JSFunction method = createMethod(cx, frame, state.indexReg, prototype); + ScriptRuntime.defineClassComputedSetter(constructor, key, method); + return null; + } + } + + private static class DoDefineStaticClassComputedMethod extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + Object key = frame.stack[state.stackTop]; + if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + JSFunction method = createMethod(cx, frame, state.indexReg, constructor); + ScriptRuntime.defineStaticClassComputedMethod(constructor, key, method); + return null; + } + } + + private static class DoDefineStaticClassComputedGetter extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + Object key = frame.stack[state.stackTop]; + if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + JSFunction method = createMethod(cx, frame, state.indexReg, constructor); + ScriptRuntime.defineStaticClassComputedGetter(constructor, key, method); + return null; + } + } + + private static class DoDefineStaticClassComputedSetter extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + Object key = frame.stack[state.stackTop]; + if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + JSFunction method = createMethod(cx, frame, state.indexReg, constructor); + ScriptRuntime.defineStaticClassComputedSetter(constructor, key, method); + return null; + } + } + + private static class DoDefineStaticClassField extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + // Stack: ... constructor value -> ... constructor + Object value = frame.stack[state.stackTop]; + if (value == DOUBLE_MARK) value = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + ScriptRuntime.defineStaticClassField(constructor, state.stringReg, value); + return null; + } + } + + private static class DoDefineStaticClassComputedField extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + // Stack: ... constructor key value -> ... constructor + Object value = frame.stack[state.stackTop]; + if (value == DOUBLE_MARK) value = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + Object key = frame.stack[state.stackTop]; + if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + ScriptRuntime.defineStaticClassComputedField(constructor, key, value); + return null; + } + } + + private static class DoStoreClassComputedKeys extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + int count = state.indexReg; + Object[] keys = new Object[count]; + for (int i = count - 1; i >= 0; i--) { + Object key = frame.stack[state.stackTop]; + if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + keys[i] = key; + --state.stackTop; + } + BaseFunction constructor = (BaseFunction) frame.stack[state.stackTop]; + ScriptRuntime.storeClassComputedFieldKeys(constructor, keys); + return null; + } + } + + private static class DoGetClassComputedKey extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + Object key = ScriptRuntime.getClassComputedFieldKey(frame.fnOrScript, state.indexReg); + frame.stack[++state.stackTop] = key; + return null; + } + } + + private static class DoDefinePrivateField extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + // Stack: ... target key value -> ... value + Object value = frame.stack[state.stackTop]; + if (value == DOUBLE_MARK) value = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + Object key = frame.stack[state.stackTop]; + if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + Object target = frame.stack[state.stackTop]; + if (target == DOUBLE_MARK) + target = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + ScriptRuntime.definePrivateField(target, (Symbol) key, value, cx, frame.scope); + frame.stack[state.stackTop] = value; + return null; + } + } + + private static class DoDefinePrivateAccessor extends InstructionClass { + private final boolean isSetter; + + DoDefinePrivateAccessor(boolean isSetter) { + this.isSetter = isSetter; + } + + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + // Stack: ... target key fn -> ... fn + Object fn = frame.stack[state.stackTop]; + if (fn == DOUBLE_MARK) fn = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + Object key = frame.stack[state.stackTop]; + if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + --state.stackTop; + Object target = frame.stack[state.stackTop]; + if (target == DOUBLE_MARK) + target = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]); + ScriptRuntime.definePrivateAccessor( + target, (Symbol) key, fn, isSetter, cx, frame.scope); + frame.stack[state.stackTop] = fn; + return null; + } + } + private static class DoScopeLoad extends InstructionClass { @Override NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { @@ -4348,24 +4842,37 @@ void dumpICode(int op, String tname, ICodeDumpContext ctx) { private static class DoRegExp extends InstructionClass { @Override NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { - Object re = frame.idata.itsRegExpLiterals[state.indexReg]; + Object re = frame.fnOrScript.getDescriptor().getLiteral(state.indexReg); frame.stack[++state.stackTop] = ScriptRuntime.wrapRegExp(cx, frame.scope, re); return null; } @Override void dumpICode(int op, String tname, ICodeDumpContext ctx) { - ctx.out.println(tname + " " + ctx.idata.itsRegExpLiterals[ctx.indexReg]); + ctx.out.println(tname + " #" + ctx.indexReg); + } + } + + private static class DoLoadLiteral extends InstructionClass { + @Override + NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { + frame.stack[++state.stackTop] = + frame.fnOrScript.getDescriptor().getLiteral(state.indexReg); + return null; + } + + @Override + void dumpICode(int op, String tname, ICodeDumpContext ctx) { + ctx.out.println(tname + " #" + ctx.indexReg); } } private static class DoTemplateLiteralCallSite extends InstructionClass { @Override NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) { - Object[] templateLiterals = frame.idata.itsTemplateLiterals; frame.stack[++state.stackTop] = ScriptRuntime.getTemplateLiteralCallSite( - cx, frame.scope, templateLiterals, state.indexReg); + cx, frame.scope, frame.fnOrScript.getDescriptor(), state.indexReg); return null; } } @@ -5028,7 +5535,7 @@ private static Scriptable getApplyThis( private static CallFrame initFrame( Context cx, VarScope callerScope, - Scriptable thisObj, + Object thisObj, Scriptable homeObj, Object[] args, double[] argsDbl, @@ -5037,11 +5544,13 @@ private static CallFrame initFrame( int argCount, ScriptOrFn fnOrScript, InterpreterData code, - CallFrame parentFrame) { + CallFrame parentFrame, + Object newTarget) { CallFrame frame = new CallFrame( cx, thisObj, + newTarget, fnOrScript, code, parentFrame, @@ -5056,7 +5565,8 @@ private static CallFrame initFrame( private static void enterFrame( Context cx, CallFrame frame, Object[] args, boolean continuationRestart) { - boolean usesActivation = frame.fnOrScript.getDescriptor().requiresActivationFrame(); + var desc = frame.fnOrScript.getDescriptor(); + boolean usesActivation = desc.requiresActivationFrame(); boolean isDebugged = frame.debuggerFrame != null; if (usesActivation) { VarScope scope = frame.scope; @@ -5069,10 +5579,10 @@ private static void enterFrame( // However, when called from interpretLoop() as part of // restarting a continuation, it can also be a WIthScope if // the continuation was captured within a "with" or "catch" - // block ("catch" implicitly uses NativeWith to create a scope + // block ("catch" implicitly uses WithScope to create a scope // to expose the exception variable). for (; ; ) { - if (scope instanceof WithScope) { + if (!(scope instanceof NativeCall)) { scope = scope.getParentScope(); if (scope == null || (frame.parentFrame != null @@ -5092,14 +5602,22 @@ private static void enterFrame( if (isDebugged) { frame.debuggerFrame.onEnter(cx, scope, frame.thisObj, args); } - ScriptRuntime.enterActivationFunction(cx, scope); + if (!(desc.isStrict() && cx.getLanguageVersion() >= Context.VERSION_ES6) + && !desc.isES6Generator() + && !desc.isAsync()) { + ScriptRuntime.enterActivationFunction(cx, scope); + } } else if (isDebugged) { frame.debuggerFrame.onEnter(cx, new DebugScope(frame), frame.thisObj, args); } } private static void exitFrame(Context cx, CallFrame frame, Object throwable) { - if (frame.fnOrScript.getDescriptor().requiresActivationFrame()) { + var desc = frame.fnOrScript.getDescriptor(); + if (desc.requiresActivationFrame() + && !(desc.isStrict() && cx.getLanguageVersion() >= Context.VERSION_ES6) + && !desc.isES6Generator() + && !desc.isAsync()) { ScriptRuntime.exitActivationFunction(cx); } @@ -5145,8 +5663,12 @@ private static void setCallResult(CallFrame frame, Object callResult, double cal // If construct returns scriptable, // then it replaces on stack top saved original instance // of the object. - if (callResult instanceof Scriptable) { - frame.stack[frame.savedStackTop] = callResult; + if (!Undefined.isUndefined(callResult)) { + if (callResult instanceof Scriptable) { + frame.stack[frame.savedStackTop] = callResult; + } else { + throw ScriptRuntime.typeErrorById("msg.ctor.res.not.object"); + } } } else { Kit.codeBug(); @@ -5287,7 +5809,8 @@ private static JSFunction createClosure(Context cx, CallFrame frame, int index) var desc = frame.fnOrScript.getDescriptor().getFunction(index); boolean isArrow = desc.getFunctionType() == FunctionNode.ARROW_FUNCTION; var homeObject = isArrow ? frame.fnOrScript.getHomeObject() : null; - JSFunction f = new JSFunction(cx, frame.scope, desc, frame.thisObj, homeObject); + var newTarget = isArrow ? frame.newTarget : Undefined.instance; + JSFunction f = new JSFunction(cx, frame.scope, desc, frame.thisObj, newTarget, homeObject); return f; } @@ -5295,7 +5818,8 @@ private static JSFunction createMethod( Context cx, CallFrame frame, int index, Scriptable homeObject) { var desc = frame.fnOrScript.getDescriptor().getFunction(index); boolean isArrow = desc.getFunctionType() == FunctionNode.ARROW_FUNCTION; - JSFunction f = new JSFunction(cx, frame.scope, desc, frame.thisObj, homeObject); + var newTarget = isArrow ? frame.newTarget : Undefined.instance; + JSFunction f = new JSFunction(cx, frame.scope, desc, frame.thisObj, newTarget, homeObject); return f; } } diff --git a/rhino/src/main/java/org/mozilla/javascript/InterpreterData.java b/rhino/src/main/java/org/mozilla/javascript/InterpreterData.java index a305fdf4978..f1dfdb950ca 100644 --- a/rhino/src/main/java/org/mozilla/javascript/InterpreterData.java +++ b/rhino/src/main/java/org/mozilla/javascript/InterpreterData.java @@ -24,8 +24,6 @@ final class InterpreterData> extends JSCode implement double[] itsDoubleTable, BigInteger[] itsBigIntTable, InterpreterData[] itsNestedFunctions, - Object[] itsRegExpLiterals, - Object[] itsTemplateLiterals, byte[] itsICode, int[] itsExceptionTable, int itsMaxVars, @@ -40,8 +38,6 @@ final class InterpreterData> extends JSCode implement this.itsDoubleTable = itsDoubleTable; this.itsBigIntTable = itsBigIntTable; this.itsNestedFunctions = itsNestedFunctions; - this.itsRegExpLiterals = itsRegExpLiterals; - this.itsTemplateLiterals = itsTemplateLiterals; this.itsICode = itsICode; this.itsExceptionTable = itsExceptionTable; this.itsMaxVars = itsMaxVars; @@ -58,8 +54,6 @@ final class InterpreterData> extends JSCode implement final double[] itsDoubleTable; final BigInteger[] itsBigIntTable; final InterpreterData[] itsNestedFunctions; - final Object[] itsRegExpLiterals; - final Object[] itsTemplateLiterals; final byte[] itsICode; @@ -101,7 +95,7 @@ public Object execute( VarScope scope, Object thisObj, Object[] args) { - return Interpreter.interpret(executableObject, this, cx, scope, (Scriptable) thisObj, args); + return Interpreter.interpret(executableObject, this, cx, scope, thisObj, args, newTarget); } @Override @@ -120,8 +114,6 @@ public static class Builder> extends JSCode.Builder { double[] itsDoubleTable; BigInteger[] itsBigIntTable; InterpreterData[] itsNestedFunctions; - Object[] itsRegExpLiterals; - Object[] itsTemplateLiterals; byte[] itsICode; @@ -158,8 +150,6 @@ public JSCode build() { itsDoubleTable, itsBigIntTable, itsNestedFunctions, - itsRegExpLiterals, - itsTemplateLiterals, itsICode, itsExceptionTable, itsMaxVars, diff --git a/rhino/src/main/java/org/mozilla/javascript/JSCode.java b/rhino/src/main/java/org/mozilla/javascript/JSCode.java index 84fc5450e49..92e218782e9 100644 --- a/rhino/src/main/java/org/mozilla/javascript/JSCode.java +++ b/rhino/src/main/java/org/mozilla/javascript/JSCode.java @@ -35,7 +35,7 @@ public JSCode build() { private static class NotCallable extends JSCode implements Serializable { - private static final long serialVersionUID = 2691205302914111400L; + private static final long serialVersionUID = -31340315773728063L; @Override public Object execute( diff --git a/rhino/src/main/java/org/mozilla/javascript/JSDescriptor.java b/rhino/src/main/java/org/mozilla/javascript/JSDescriptor.java index a05b05e430f..2e4d58d4acf 100644 --- a/rhino/src/main/java/org/mozilla/javascript/JSDescriptor.java +++ b/rhino/src/main/java/org/mozilla/javascript/JSDescriptor.java @@ -30,6 +30,9 @@ public final class JSDescriptor> implements Serializable private static final int REQUIRES_ACTIVATION_FRAME_FLAG = 1 << 10; private static final int REQUIRES_ARGUMENT_OBJECT_FLAG = 1 << 11; private static final int DECLARED_AS_FUNCTION_EXPRESSION_FLAG = 1 << 12; + private static final int DERIVED_CONSTRUCTOR_FLAG = 1 << 13; + private static final int IS_ASYNC_FLAG = 1 << 14; + private static final int IS_CLASS_CONSTRUCTOR_FLAG = 1 << 15; private final JSCode code; private final JSCode constructor; @@ -50,6 +53,7 @@ public final class JSDescriptor> implements Serializable private final SecurityController securityController; private final Object securityDomain; private final int functionType; + private final Object[] literals; public JSDescriptor( JSCode code, @@ -79,9 +83,13 @@ public JSDescriptor( boolean requiresActivationFrame, boolean requiresArgumentObject, boolean declaredAsFunctionExpression, + boolean derivedConstructor, + boolean isAsync, + boolean isClassConstructor, SecurityController securityController, Object securityDomain, - int functionType) { + int functionType, + Object[] literals) { this.code = code; this.constructor = constructor; this.parent = parent; @@ -102,6 +110,9 @@ public JSDescriptor( flags = flags | (requiresActivationFrame ? REQUIRES_ACTIVATION_FRAME_FLAG : 0); flags = flags | (requiresArgumentObject ? REQUIRES_ARGUMENT_OBJECT_FLAG : 0); flags = flags | (declaredAsFunctionExpression ? DECLARED_AS_FUNCTION_EXPRESSION_FLAG : 0); + flags = flags | (derivedConstructor ? DERIVED_CONSTRUCTOR_FLAG : 0); + flags = flags | (isAsync ? IS_ASYNC_FLAG : 0); + flags = flags | (isClassConstructor ? IS_CLASS_CONSTRUCTOR_FLAG : 0); this.flags = flags; this.sourceFile = sourceFile; @@ -116,6 +127,15 @@ public JSDescriptor( this.securityController = securityController; this.securityDomain = securityDomain; this.functionType = functionType; + this.literals = literals; + } + + public Object[] getLiterals() { + return literals; + } + + public Object getLiteral(int index) { + return literals[index]; } public JSCode getCode() { @@ -152,10 +172,18 @@ public boolean isES6Generator() { return (flags & IS_ES6_GENERATOR_FLAG) != 0; } + public boolean isAsync() { + return (flags & IS_ASYNC_FLAG) != 0; + } + public boolean isShorthand() { return (flags & IS_SHORTHAND_FLAG) != 0; } + public boolean isClassConstructor() { + return (flags & IS_CLASS_CONSTRUCTOR_FLAG) != 0; + } + public boolean hasPrototype() { return (flags & HAS_PROTOTYPE_FLAG) != 0; } @@ -297,6 +325,7 @@ public static class Builder> { public boolean isScript; public boolean isTopLevel; public boolean isES6Generator; + public boolean isAsync; public boolean isShorthand; public boolean hasPrototype; public boolean hasLexicalThis; @@ -315,9 +344,11 @@ public static class Builder> { public boolean requiresActivationFrame; public boolean requiresArgumentObject; public boolean declaredAsFunctionExpression; + public boolean isClassConstructor; public SecurityController securityController; public Object securityDomain; public int functionType; + public Object[] literals; public Builder() {} @@ -385,9 +416,13 @@ private JSDescriptor build(JSDescriptor parent, Consumer> requiresActivationFrame, requiresArgumentObject, declaredAsFunctionExpression, + false, + isAsync, + isClassConstructor, securityController, securityDomain, - functionType); + functionType, + literals); consumer.accept(result); result.nestedFunctions = Collections.unmodifiableList( diff --git a/rhino/src/main/java/org/mozilla/javascript/JSFunction.java b/rhino/src/main/java/org/mozilla/javascript/JSFunction.java index a0816b1feb5..bea88f5e307 100644 --- a/rhino/src/main/java/org/mozilla/javascript/JSFunction.java +++ b/rhino/src/main/java/org/mozilla/javascript/JSFunction.java @@ -10,20 +10,25 @@ */ public class JSFunction extends BaseFunction implements ScriptOrFn { private final JSDescriptor descriptor; - private final Scriptable lexicalThis; + private final Object lexicalThis; + private final Object lexicalNewTarget; private final Scriptable homeObject; public JSFunction( Context cx, VarScope scope, JSDescriptor descriptor, - Scriptable lexicalThis, + Object lexicalThis, + Object lexicalNewTarget, Scriptable homeObject) { + super(scope); this.descriptor = descriptor; this.lexicalThis = lexicalThis; + this.lexicalNewTarget = lexicalNewTarget; this.homeObject = homeObject; - ScriptRuntime.setFunctionProtoAndParent(this, cx, scope, descriptor.isES6Generator()); - if (!descriptor.isShorthand()) { + ScriptRuntime.setFunctionProtoAndParent( + this, cx, scope, descriptor.isES6Generator(), descriptor.isAsync()); + if (!descriptor.isShorthand() && !(descriptor.isAsync() && !descriptor.isES6Generator())) { setupDefaultPrototype(scope); } } @@ -32,9 +37,12 @@ public JSFunction( VarScope scope, JSDescriptor descriptor, Scriptable lexicalThis, + Object lexicalNewTarget, Scriptable homeObject) { + super(scope); this.descriptor = descriptor; this.lexicalThis = lexicalThis; + this.lexicalNewTarget = lexicalNewTarget; this.homeObject = homeObject; setParentScope(scope); setPrototype(ScriptableObject.getFunctionPrototype(scope)); @@ -91,6 +99,11 @@ protected boolean isGeneratorFunction() { return descriptor.isES6Generator(); } + @Override + public boolean isAsync() { + return descriptor.isAsync(); + } + @Override public int getLength() { return descriptor.getArity(); @@ -136,33 +149,39 @@ JSCode getConstructor() { } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { + if (descriptor.isClassConstructor()) { + throw ScriptRuntime.typeErrorById("msg.class.not.called.with.new", getFunctionName()); + } if (!ScriptRuntime.hasTopCall(cx)) { return ScriptRuntime.doTopCall(this, cx, scope, thisObj, args, isStrict()); } var realThis = getThisObj(thisObj); - return descriptor.getCode().execute(cx, this, Undefined.instance, scope, realThis, args); + return descriptor.getCode().execute(cx, this, lexicalNewTarget, scope, realThis, args); } - public final Scriptable getThisObj(Scriptable thisObj) { + public final Object getThisObj(Object thisObj) { if (descriptor.hasLexicalThis()) { return lexicalThis; - } else if (!descriptor.isStrict() && (thisObj == null || Undefined.isUndefined(thisObj))) { - var res = ScriptableObject.getTopLevelScope(getDeclarationScope()).getGlobalThis(); - return res; + } else if (!descriptor.isStrict()) { + if (thisObj == null || Undefined.isUndefined(thisObj)) { + return ScriptableObject.getTopLevelScope(getDeclarationScope()).getGlobalThis(); + } else { + return ScriptRuntime.toObject(getDeclarationScope(), thisObj); + } } else { return thisObj; } } @Override - public Scriptable construct(Context cx, VarScope scope, Object[] args) { + public Scriptable construct(Context cx, Object nt, VarScope s, Object thisObj, Object[] args) { if (!ScriptRuntime.hasTopCall(cx)) { return (Scriptable) ScriptRuntime.doTopCall( (lcx, lscope, lthisObj, largs) -> construct(lcx, lscope, largs), cx, - scope, + s, null, args, isStrict()); @@ -170,14 +189,26 @@ public Scriptable construct(Context cx, VarScope scope, Object[] args) { if (descriptor.getConstructor() == null) { throw ScriptRuntime.typeErrorById("msg.not.ctor", getFunctionName()); } - var thisObj = homeObject == null ? createObject(cx, scope) : null; + if (nt == null || Undefined.isUndefined(nt)) { + nt = this; + } + thisObj = homeObject == null ? this.createObject(cx, s, nt) : null; // Pass `this` in as new.target for now. This can change when // the public `construct` signature changes. - var res = descriptor.getConstructor().execute(cx, this, this, scope, thisObj, args); - if (res instanceof Scriptable) { - thisObj = (Scriptable) res; + var res = descriptor.getConstructor().execute(cx, this, nt, s, thisObj, args); + if (!Undefined.isUndefined(res)) { + if (res instanceof Scriptable) { + thisObj = (Scriptable) res; + } else { + throw ScriptRuntime.typeErrorById("msg.ctor.res.not.object"); + } } - return thisObj; + return (Scriptable) thisObj; + } + + @Override + public Scriptable construct(Context cx, VarScope scope, Object[] args) { + return construct(cx, this, scope, null, args); } public boolean isScript() { @@ -208,12 +239,16 @@ public Scriptable getHomeObject() { return homeObject; } + public Object getLexicalNewTarget() { + return lexicalNewTarget; + } + @Override public void setHomeObject(Scriptable homeObject) { throw new UnsupportedOperationException("Cannot set home object on JS function."); } - public Scriptable getFunctionThis(Scriptable functionThis) { + public Object getFunctionThis(Scriptable functionThis) { if (descriptor.hasLexicalThis()) { return this.lexicalThis; } else { @@ -237,7 +272,7 @@ public static JSFunction createFunction( Scriptable homeObject, Object staticSecurityDomain) { assert (desc.getSecurityDomain() == staticSecurityDomain); - JSFunction f = new JSFunction(cx, scope, desc, null, homeObject); + JSFunction f = new JSFunction(cx, scope, desc, null, Undefined.instance, homeObject); return f; } @@ -245,7 +280,7 @@ public static JSFunction createFunction( static JSFunction createFunction( Context cx, VarScope scope, JSDescriptor parent, int index, Scriptable homeObject) { JSDescriptor desc = parent.getFunction(index); - JSFunction f = new JSFunction(cx, scope, desc, null, homeObject); + JSFunction f = new JSFunction(cx, scope, desc, null, Undefined.instance, homeObject); return f; } diff --git a/rhino/src/main/java/org/mozilla/javascript/JSScript.java b/rhino/src/main/java/org/mozilla/javascript/JSScript.java index 9771e4da617..665232e869f 100644 --- a/rhino/src/main/java/org/mozilla/javascript/JSScript.java +++ b/rhino/src/main/java/org/mozilla/javascript/JSScript.java @@ -29,11 +29,8 @@ JSCode getCode() { } @Override - public Object exec(Context cx, VarScope scope, Scriptable thisObj) { + public Object exec(Context cx, VarScope scope, Object thisObj) { Object ret; - if (thisObj instanceof TopLevel) { - thisObj = ((TopLevel) thisObj).getGlobalThis(); - } if (!ScriptRuntime.hasTopCall(cx)) { // It will go through "call" path. but they are equivalent ret = ScriptRuntime.doTopCall(this, cx, scope, thisObj, descriptor.isStrict()); diff --git a/rhino/src/main/java/org/mozilla/javascript/JavaAdapter.java b/rhino/src/main/java/org/mozilla/javascript/JavaAdapter.java index b8182ff1613..b782a53ce68 100644 --- a/rhino/src/main/java/org/mozilla/javascript/JavaAdapter.java +++ b/rhino/src/main/java/org/mozilla/javascript/JavaAdapter.java @@ -576,9 +576,9 @@ public static Scriptable runScript(final Script script) { return ContextFactory.getGlobal() .call( cx -> { - TopLevel global = ScriptRuntime.getGlobal(cx); - script.exec(cx, global, global.getGlobalThis()); - return global.getGlobalThis(); + TopLevel top = ScriptRuntime.getGlobal(cx); + script.exec(cx, top, top.getGlobalThis()); + return top.getGlobalThis(); }); } diff --git a/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java b/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java index c8574d094fd..1f6f16bdb7d 100644 --- a/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java +++ b/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java @@ -109,7 +109,7 @@ Object get(Scriptable obj, VarScope scope, String name, Object javaObject, boole return new FieldAndMethods(scope, withField); } else { var method = (ExecutableOverload) member; - var built = new NativeJavaMethod(method.methods, method.name); + var built = new NativeJavaMethod(scope, method.methods, method.name); ScriptRuntime.setFunctionProtoAndParent( built, Context.getCurrentContext(), scope, false); return built; @@ -290,7 +290,7 @@ private Object getExplicitFunction( Scriptable prototype = ScriptableObject.getFunctionPrototype(scope); if (methodOrCtor.isConstructor()) { - NativeJavaConstructor fun = new NativeJavaConstructor(methodOrCtor); + NativeJavaConstructor fun = new NativeJavaConstructor(scope, methodOrCtor); fun.setPrototype(prototype); member = fun; ht.put(name, fun); @@ -300,7 +300,7 @@ private Object getExplicitFunction( if (member instanceof ExecutableOverload && ((ExecutableOverload) member).methods.length > 1) { - NativeJavaMethod fun = new NativeJavaMethod(methodOrCtor, name); + NativeJavaMethod fun = new NativeJavaMethod(scope, methodOrCtor, name); fun.setPrototype(prototype); ht.put(name, fun); member = fun; @@ -458,7 +458,7 @@ private void reflect( collectFields(accessibleFields, isStatic, typeFactory); var table = isStatic ? staticMembers : members; - table.putAll(extractBeaning(table, isStatic, includePrivate)); + table.putAll(extractBeaning(scope, table, isStatic, includePrivate)); } // Reflect constructors @@ -467,7 +467,7 @@ private void reflect( for (int i = 0; i != constructors.length; ++i) { ctorMembers[i] = new ExecutableBox(constructors[i], typeFactory); } - ctors = new NativeJavaMethod(ctorMembers, cl.getSimpleName()); + ctors = new NativeJavaMethod(scope, ctorMembers, cl.getSimpleName()); } /** @@ -592,7 +592,7 @@ private static String getBeanName(String nameComponent) { * member table at this stage */ private static Map extractBeaning( - Map members, boolean isStatic, boolean includePrivate) { + VarScope scope, Map members, boolean isStatic, boolean includePrivate) { var beans = new HashMap(); for (var entry : members.entrySet()) { var name = entry.getKey(); @@ -624,16 +624,16 @@ private static Map extractBeaning( // prefer 'get' over 'is' || bean.getter.getFunctionName().startsWith("is")) { if (method.methods.length == 1) { - bean.getter = new NativeJavaMethod(method.methods, name); + bean.getter = new NativeJavaMethod(scope, method.methods, name); } else { - bean.getter = new NativeJavaMethod(candidate, name); + bean.getter = new NativeJavaMethod(scope, candidate, name); } } } } else { // isSetBeaning var bean = beans.computeIfAbsent(beanName, BeanProperty::new); // capture all possible setters for now, actual setter will be searched later - bean.setter = new NativeJavaMethod(method.methods, name); + bean.setter = new NativeJavaMethod(scope, method.methods, name); } } @@ -651,7 +651,7 @@ private static Map extractBeaning( // We have a getter. Now, do we have a setter with matching type? match = extractSetMethod(type, setterCandidates.methods, isStatic); if (match != null) { - bean.setter = new NativeJavaMethod(match, match.getName()); + bean.setter = new NativeJavaMethod(scope, match, match.getName()); continue; } } @@ -911,7 +911,7 @@ class FieldAndMethods extends NativeJavaMethod { private static final long serialVersionUID = -9222428244284796755L; FieldAndMethods(VarScope scope, ExecutableOverload.WithField withField) { - super(withField.methods, withField.name); + super(scope, withField.methods, withField.name); this.field = withField.field; setParentScope(scope); setPrototype(ScriptableObject.getFunctionPrototype(scope)); diff --git a/rhino/src/main/java/org/mozilla/javascript/Kit.java b/rhino/src/main/java/org/mozilla/javascript/Kit.java index c932da7a234..fd58771a594 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Kit.java +++ b/rhino/src/main/java/org/mozilla/javascript/Kit.java @@ -297,9 +297,9 @@ public static String readReader(Reader reader) throws IOException { try (BufferedReader in = new BufferedReader(reader)) { char[] cbuf = new char[1024]; StringBuilder sb = new StringBuilder(1024); - int bytes_read; - while ((bytes_read = in.read(cbuf, 0, 1024)) != -1) { - sb.append(cbuf, 0, bytes_read); + int chars_read; + while ((chars_read = in.read(cbuf, 0, 1024)) != -1) { + sb.append(cbuf, 0, chars_read); } return sb.toString(); } diff --git a/rhino/src/main/java/org/mozilla/javascript/KnownBuiltInFunction.java b/rhino/src/main/java/org/mozilla/javascript/KnownBuiltInFunction.java index 56a3d5cda89..85bb7adfd34 100644 --- a/rhino/src/main/java/org/mozilla/javascript/KnownBuiltInFunction.java +++ b/rhino/src/main/java/org/mozilla/javascript/KnownBuiltInFunction.java @@ -12,7 +12,7 @@ */ public class KnownBuiltInFunction extends LambdaFunction { - private static final long serialVersionUID = -8388132362854748293L; + private static final long serialVersionUID = 4527445659952834584L; private final Object tag; diff --git a/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java b/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java index 8d7d4ac56a4..ed3ef900807 100644 --- a/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java +++ b/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java @@ -124,7 +124,7 @@ protected Constructable getTargetConstructor() { } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { if ((flags & CONSTRUCTOR_FUNCTION) == 0) { throw ScriptRuntime.typeErrorById("msg.constructor.no.function", getFunctionName()); } diff --git a/rhino/src/main/java/org/mozilla/javascript/LambdaFunction.java b/rhino/src/main/java/org/mozilla/javascript/LambdaFunction.java index f6ae5f1e80e..7f24d1a56e7 100644 --- a/rhino/src/main/java/org/mozilla/javascript/LambdaFunction.java +++ b/rhino/src/main/java/org/mozilla/javascript/LambdaFunction.java @@ -13,7 +13,7 @@ */ public class LambdaFunction extends BaseFunction { - private static final long serialVersionUID = -8388132362854748293L; + private static final long serialVersionUID = 6594410813947157220L; // The target is expected to be a lambda. Lambdas may be serialized, which // requires this special interface. @@ -38,6 +38,7 @@ public LambdaFunction( int length, SerializableCallable target, boolean defaultPrototype) { + super(scope); this.target = target; this.name = name; this.length = length; @@ -78,6 +79,7 @@ public LambdaFunction( int length, Object prototype, SerializableCallable target) { + super(scope); this.target = target; this.name = name; this.length = length; @@ -87,6 +89,7 @@ public LambdaFunction( /** Create a new built-in function, with no name, and no default prototype. */ public LambdaFunction(VarScope scope, int length, SerializableCallable target) { + super(scope); this.target = target; this.length = length; this.name = ""; @@ -94,7 +97,7 @@ public LambdaFunction(VarScope scope, int length, SerializableCallable target) { } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { return target.call(cx, getDeclarationScope(), thisObj, args); } diff --git a/rhino/src/main/java/org/mozilla/javascript/LazilyLoadedCtor.java b/rhino/src/main/java/org/mozilla/javascript/LazilyLoadedCtor.java index a08e568fc43..d7820a68138 100644 --- a/rhino/src/main/java/org/mozilla/javascript/LazilyLoadedCtor.java +++ b/rhino/src/main/java/org/mozilla/javascript/LazilyLoadedCtor.java @@ -17,7 +17,7 @@ *

This improves startup time and average memory usage. */ public final class LazilyLoadedCtor> implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -1531091529063000542L; private static final int STATE_BEFORE_INIT = 0; private static final int STATE_INITIALIZING = 1; private static final int STATE_WITH_VALUE = 2; diff --git a/rhino/src/main/java/org/mozilla/javascript/LocalScope.java b/rhino/src/main/java/org/mozilla/javascript/LocalScope.java new file mode 100644 index 00000000000..6579204da3d --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/LocalScope.java @@ -0,0 +1,18 @@ +package org.mozilla.javascript; + +public class LocalScope extends DeclarationScope { + private static final long serialVersionUID = -7471457301304454454L; + + public LocalScope(VarScope parentScope) { + super(parentScope); + } + + public boolean isNestedScope() { + return true; + } + + @Override + public void putConst(String name, VarScope start, Object value) { + super.put(name, start, value); + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/MemberBox.java b/rhino/src/main/java/org/mozilla/javascript/MemberBox.java index 1772a2d1b08..8445fd3f61a 100644 --- a/rhino/src/main/java/org/mozilla/javascript/MemberBox.java +++ b/rhino/src/main/java/org/mozilla/javascript/MemberBox.java @@ -171,7 +171,7 @@ Function asGetterFunction(final String name) { public Object call( Context cx, VarScope callScope, - Scriptable thisObj, + Object thisObj, Object[] originalArgs) { MemberBox nativeGetter = MemberBox.this; if (nativeGetter.delegateTo == null) { @@ -204,7 +204,7 @@ Function asSetterFunction(final String name) { public Object call( Context cx, VarScope callScope, - Scriptable thisObj, + Object thisObj, Object[] originalArgs) { MemberBox nativeSetter = MemberBox.this; Object value = diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeArray.java b/rhino/src/main/java/org/mozilla/javascript/NativeArray.java index 1601f788d2f..f245bd8c8e0 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeArray.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeArray.java @@ -645,7 +645,7 @@ private static Object js_from( final Scriptable items = ScriptRuntime.toObject(s, (args.length >= 1) ? args[0] : Undefined.instance); Object mapArg = (args.length >= 2) ? args[1] : Undefined.instance; - Scriptable thisArg = null; + Object thisArg = null; final boolean mapping = !Undefined.isUndefined(mapArg); Function mapFn = null; diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeArrayIterator.java b/rhino/src/main/java/org/mozilla/javascript/NativeArrayIterator.java index 5593f0fad1c..4423a47f030 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeArrayIterator.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeArrayIterator.java @@ -15,13 +15,17 @@ public enum ARRAY_ITERATOR_TYPE { VALUES } - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 3370645355934941865L; private static final String ITERATOR_TAG = "ArrayIterator"; + private static final ClassDescriptor DESCRIPTOR = + ES6Iterator.makeDescriptor(ITERATOR_TAG, "Array Iterator"); + private ARRAY_ITERATOR_TYPE type; - static void init(TopLevel scope, boolean sealed) { - ES6Iterator.init(scope, sealed, new NativeArrayIterator(), ITERATOR_TAG); + static void init(Context cx, VarScope scope, boolean sealed) { + ES6Iterator.initialize( + DESCRIPTOR, cx, (TopLevel) scope, new NativeArrayIterator(), sealed, ITERATOR_TAG); } /** Only for constructing the prototype object. */ @@ -58,7 +62,7 @@ protected Object nextValue(Context cx, VarScope scope) { return Integer.valueOf(index++); } - Object value = arrayLike.get(index, arrayLike); + Object value = ScriptableObject.getProperty(arrayLike, index); if (value == Scriptable.NOT_FOUND) { value = Undefined.instance; } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeBigInt.java b/rhino/src/main/java/org/mozilla/javascript/NativeBigInt.java index f3322f6b233..56f5ae2dd56 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeBigInt.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeBigInt.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; + import java.math.BigInteger; /** This class implements the BigInt native object. */ @@ -14,46 +18,33 @@ final class NativeBigInt extends ScriptableObject { private static final String CLASS_NAME = "BigInt"; + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + CLASS_NAME, + 1, + NativeBigInt::js_constructorFunc, + NativeBigInt::js_constructor) + .withMethod(CTOR, "asIntN", 2, NativeBigInt::js_asIntN) + .withMethod(CTOR, "asUintN", 2, NativeBigInt::js_asUintN) + .withMethod(PROTO, "toString", 0, NativeBigInt::js_toString) + // Alias toLocaleString to toString + .withMethod(PROTO, "toLocaleString", 0, NativeBigInt::js_toString) + .withMethod(PROTO, "toSource", 0, NativeBigInt::js_toSource) + .withMethod(PROTO, "valueOf", 0, NativeBigInt::js_valueOf) + .withProp( + PROTO, + SymbolKey.TO_STRING_TAG, + value(CLASS_NAME, DONTENUM | READONLY)) + .build(); + } + private final BigInteger bigIntValue; static Object init(Context cx, VarScope scope, boolean sealed) { - LambdaConstructor constructor = - new LambdaConstructor( - scope, - CLASS_NAME, - 1, - NativeBigInt::js_constructorFunc, - NativeBigInt::js_constructor); - constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); - constructor.defineConstructorMethod( - scope, - "asIntN", - 2, - (Context lcx, VarScope lscope, Scriptable thisObj, Object[] args) -> - js_asIntOrUintN(true, args)); - constructor.defineConstructorMethod( - scope, - "asUintN", - 2, - (Context lcx, VarScope lscope, Scriptable thisObj, Object[] args) -> - js_asIntOrUintN(false, args)); - constructor.definePrototypeMethod(scope, "toString", 0, NativeBigInt::js_toString); - // Alias toLocaleString to toString - constructor.definePrototypeMethod(scope, "toLocaleString", 0, NativeBigInt::js_toString); - constructor.definePrototypeMethod(scope, "toSource", 0, NativeBigInt::js_toSource); - constructor.definePrototypeMethod( - scope, - "valueOf", - 0, - (Context lcx, VarScope lscope, Scriptable thisObj, Object[] args) -> - toSelf(thisObj).bigIntValue); - constructor.definePrototypeProperty( - SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); - if (sealed) { - constructor.sealObject(); - ((ScriptableObject) constructor.getPrototypeProperty()).sealObject(); - } - return constructor; + return DESCRIPTOR.buildConstructor(cx, scope, new NativeObject(), sealed); } NativeBigInt(BigInteger bigInt) { @@ -70,15 +61,17 @@ private static NativeBigInt toSelf(Object thisObj) { } private static Object js_constructorFunc( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return (args.length >= 1) ? ScriptRuntime.toBigInt(args[0]) : BigInteger.ZERO; } - private static Scriptable js_constructor(Context cx, VarScope scope, Object[] args) { + private static Scriptable js_constructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { throw ScriptRuntime.typeErrorById("msg.no.new", CLASS_NAME); } - 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) { int base = (args.length == 0 || args[0] == Undefined.instance) ? 10 @@ -87,10 +80,26 @@ private static Object js_toString(Context cx, VarScope scope, Object thisObj, Ob return ScriptRuntime.bigIntToString(value, base); } - 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) { return "(new BigInt(" + ScriptRuntime.toString(toSelf(thisObj).bigIntValue) + "))"; } + private static Object js_asIntN( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return js_asIntOrUintN(true, args); + } + + private static Object js_asUintN( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return js_asIntOrUintN(false, args); + } + + private static Object js_valueOf( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return toSelf(thisObj).bigIntValue; + } + private static Object js_asIntOrUintN(boolean isSigned, Object[] args) { int bits = ScriptRuntime.toIndex(args.length < 1 ? Undefined.instance : args[0]); BigInteger bigInt = ScriptRuntime.toBigInt(args.length < 2 ? Undefined.instance : args[1]); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeBoolean.java b/rhino/src/main/java/org/mozilla/javascript/NativeBoolean.java index 31f350f8afe..7658259ac47 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeBoolean.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeBoolean.java @@ -37,7 +37,7 @@ final class NativeBoolean extends ScriptableObject { static void init(Context cx, VarScope scope, boolean sealed) { // Boolean is an unusual object in that the prototype is itself a Boolean - DESCRIPTOR.buildConstructor(cx, (VarScope) scope, new NativeBoolean(false), sealed); + DESCRIPTOR.buildConstructor(cx, scope, new NativeBoolean(false), sealed); } NativeBoolean(boolean b) { @@ -71,8 +71,7 @@ private static NativeBoolean js_constructor( Context cx, JSFunction fun, Object nt, VarScope s, Object thisObj, Object[] args) { boolean b = ScriptRuntime.toBoolean(args.length > 0 ? args[0] : Undefined.instance); var res = new NativeBoolean(b); - res.setPrototype((Scriptable) fun.getPrototypeProperty()); - res.setParentScope(s); + ScriptRuntime.setBuiltinProtoAndParent(res, fun, nt, s, TopLevel.Builtins.Boolean); return res; } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeCallSite.java b/rhino/src/main/java/org/mozilla/javascript/NativeCallSite.java index a1fe0ef57b6..e0d34e5eba1 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeCallSite.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeCallSite.java @@ -6,26 +6,45 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Destination.PROTO; + /** * This class is used by the V8 extension "Error.prepareStackTrace." It is passed to that function, * which may then use it to format the stack as it sees fit. */ -public class NativeCallSite extends IdScriptableObject { +public class NativeCallSite extends ScriptableObject { private static final long serialVersionUID = 2688372752566593594L; - private static final String CALLSITE_TAG = "CallSite"; + private ScriptStackElement element; - static void init(VarScope scope, boolean sealed) { - NativeCallSite cs = new NativeCallSite(); - cs.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + "CallSite", + 0, + NativeCallSite::js_constructor, + NativeCallSite::js_constructor) + .withMethod(PROTO, "getThis", 0, NativeCallSite::js_getThis) + .withMethod(PROTO, "getTypeName", 0, NativeCallSite::js_getTypeName) + .withMethod(PROTO, "getFunction", 0, NativeCallSite::js_getFunction) + .withMethod(PROTO, "getFunctionName", 0, NativeCallSite::js_getFunctionName) + .withMethod(PROTO, "getMethodName", 0, NativeCallSite::js_getMethodName) + .withMethod(PROTO, "getFileName", 0, NativeCallSite::js_getFileName) + .withMethod(PROTO, "getLineNumber", 0, NativeCallSite::js_getLineNumber) + .withMethod(PROTO, "getColumnNumber", 0, NativeCallSite::js_getColumnNumber) + .withMethod(PROTO, "getEvalOrigin", 0, NativeCallSite::js_getEvalOrigin) + .withMethod(PROTO, "isToplevel", 0, NativeCallSite::js_isToplevel) + .withMethod(PROTO, "isEval", 0, NativeCallSite::js_isEval) + .withMethod(PROTO, "isNative", 0, NativeCallSite::js_isNative) + .withMethod(PROTO, "isConstructor", 0, NativeCallSite::js_isConstructor) + .withMethod(PROTO, "toString", 0, NativeCallSite::js_toString) + .build(); } - static NativeCallSite make(VarScope scope, Scriptable ctorObj) { - NativeCallSite cs = new NativeCallSite(); - Scriptable proto = (Scriptable) ctorObj.get("prototype", ctorObj); - cs.setParentScope(scope); - cs.setPrototype(proto); - return cs; + static void init(Context cx, VarScope scope, boolean sealed) { + DESCRIPTOR.buildConstructor(cx, scope, new NativeCallSite(), sealed); } private NativeCallSite() {} @@ -39,113 +58,6 @@ public String getClassName() { return "CallSite"; } - @Override - protected void initPrototypeId(int id) { - String s; - int arity; - switch (id) { - case Id_constructor: - arity = 0; - s = "constructor"; - break; - case Id_getThis: - arity = 0; - s = "getThis"; - break; - case Id_getTypeName: - arity = 0; - s = "getTypeName"; - break; - case Id_getFunction: - arity = 0; - s = "getFunction"; - break; - case Id_getFunctionName: - arity = 0; - s = "getFunctionName"; - break; - case Id_getMethodName: - arity = 0; - s = "getMethodName"; - break; - case Id_getFileName: - arity = 0; - s = "getFileName"; - break; - case Id_getLineNumber: - arity = 0; - s = "getLineNumber"; - break; - case Id_getColumnNumber: - arity = 0; - s = "getColumnNumber"; - break; - case Id_getEvalOrigin: - arity = 0; - s = "getEvalOrigin"; - break; - case Id_isToplevel: - arity = 0; - s = "isToplevel"; - break; - case Id_isEval: - arity = 0; - s = "isEval"; - break; - case Id_isNative: - arity = 0; - s = "isNative"; - break; - case Id_isConstructor: - arity = 0; - s = "isConstructor"; - break; - case Id_toString: - arity = 0; - s = "toString"; - break; - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - initPrototypeMethod(CALLSITE_TAG, id, s, arity); - } - - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, VarScope scope, Scriptable thisObj, Object[] args) { - if (!f.hasTag(CALLSITE_TAG)) { - return super.execIdCall(f, cx, scope, thisObj, args); - } - int id = f.methodId(); - switch (id) { - case Id_constructor: - return make(scope, f); - case Id_getFunctionName: - return getFunctionName(thisObj); - case Id_getFileName: - return getFileName(thisObj); - case Id_getLineNumber: - return getLineNumber(thisObj); - case Id_getThis: - case Id_getTypeName: - case Id_getFunction: - case Id_getColumnNumber: - return Undefined.instance; - case Id_getMethodName: - return null; - case Id_getEvalOrigin: - case Id_isEval: - case Id_isConstructor: - case Id_isNative: - case Id_isToplevel: - return Boolean.FALSE; - case Id_toString: - return js_toString(thisObj); - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - } - @Override public String toString() { if (element == null) { @@ -154,6 +66,84 @@ public String toString() { return element.toString(); } + private static Object js_constructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + var res = new NativeCallSite(); + res.setPrototype((Scriptable) f.getPrototypeProperty()); + res.setParentScope(s); + return res; + } + + private static Object js_getThis( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return Undefined.instance; + } + + private static Object js_getTypeName( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return Undefined.instance; + } + + private static Object js_getFunction( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return Undefined.instance; + } + + private static Object js_getFunctionName( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return getFunctionName((Scriptable) thisObj); + } + + private static Object js_getMethodName( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return null; + } + + private static Object js_getFileName( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return getFileName((Scriptable) thisObj); + } + + private static Object js_getLineNumber( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return getLineNumber((Scriptable) thisObj); + } + + private static Object js_getColumnNumber( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return Undefined.instance; + } + + private static Object js_getEvalOrigin( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return Boolean.FALSE; + } + + private static Object js_isToplevel( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return Boolean.FALSE; + } + + private static Object js_isEval( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return Boolean.FALSE; + } + + private static Object js_isNative( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return Boolean.FALSE; + } + + private static Object js_isConstructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return Boolean.FALSE; + } + + private static Object js_toString( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return js_toString((Scriptable) thisObj); + } + private static Object js_toString(Scriptable obj) { while (obj != null && !(obj instanceof NativeCallSite)) { obj = obj.getPrototype(); @@ -202,77 +192,4 @@ private static Object getLineNumber(Scriptable obj) { } return Integer.valueOf(cs.element.lineNumber); } - - @Override - protected int findPrototypeId(String s) { - int id; - switch (s) { - case "constructor": - id = Id_constructor; - break; - case "getThis": - id = Id_getThis; - break; - case "getTypeName": - id = Id_getTypeName; - break; - case "getFunction": - id = Id_getFunction; - break; - case "getFunctionName": - id = Id_getFunctionName; - break; - case "getMethodName": - id = Id_getMethodName; - break; - case "getFileName": - id = Id_getFileName; - break; - case "getLineNumber": - id = Id_getLineNumber; - break; - case "getColumnNumber": - id = Id_getColumnNumber; - break; - case "getEvalOrigin": - id = Id_getEvalOrigin; - break; - case "isToplevel": - id = Id_isToplevel; - break; - case "isEval": - id = Id_isEval; - break; - case "isNative": - id = Id_isNative; - break; - case "isConstructor": - id = Id_isConstructor; - break; - case "toString": - id = Id_toString; - break; - default: - id = 0; - break; - } - return id; - } - - private static final int Id_constructor = 1, - Id_getThis = 2, - Id_getTypeName = 3, - Id_getFunction = 4, - Id_getFunctionName = 5, - Id_getMethodName = 6, - Id_getFileName = 7, - Id_getLineNumber = 8, - Id_getColumnNumber = 9, - Id_getEvalOrigin = 10, - Id_isToplevel = 11, - Id_isEval = 12, - Id_isNative = 13, - Id_isConstructor = 14, - Id_toString = 15, - MAX_PROTOTYPE_ID = 15; } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeCollectionIterator.java b/rhino/src/main/java/org/mozilla/javascript/NativeCollectionIterator.java index c84174b4167..82f78a168bc 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeCollectionIterator.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeCollectionIterator.java @@ -18,10 +18,6 @@ enum Type { BOTH } - static void init(TopLevel scope, String tag, boolean sealed) { - ES6Iterator.init(scope, sealed, new NativeCollectionIterator(tag), tag); - } - public NativeCollectionIterator(String tag) { this.className = tag; this.iterator = Collections.emptyIterator(); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeConsole.java b/rhino/src/main/java/org/mozilla/javascript/NativeConsole.java index 6ac688d0c09..b1b8d86175a 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeConsole.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeConsole.java @@ -255,7 +255,7 @@ private static String formatObj(Context cx, VarScope scope, Object arg) { public Object call( Context callCx, VarScope callScope, - Scriptable callThisObj, + Object callThisObj, Object[] callArgs) { Object value = callArgs[1]; while (value instanceof Delegator) { diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeContinuation.java b/rhino/src/main/java/org/mozilla/javascript/NativeContinuation.java index b7dc353a464..61ab6da3a83 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeContinuation.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeContinuation.java @@ -8,16 +8,34 @@ import java.util.Objects; -public final class NativeContinuation extends IdScriptableObject implements Function { +public final class NativeContinuation extends ScriptableObject implements Function { private static final long serialVersionUID = 1794167133757605367L; - private static final Object FTAG = "Continuation"; + private static final String CLASS_NAME = "Continuation"; + + private static final ClassDescriptor DESCRIPTOR; + private static final JSDescriptor CTOR_DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + CLASS_NAME, + 0, + NativeContinuation::js_constructor, + NativeContinuation::js_constructor) + .build(); + CTOR_DESCRIPTOR = DESCRIPTOR.ctorDesc(); + } private Object implementation; public static void init(Context cx, VarScope scope, boolean sealed) { - NativeContinuation obj = new NativeContinuation(); - obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + DESCRIPTOR.buildConstructor(cx, scope, new NativeContinuation(), sealed); + } + + private static Object js_constructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + throw Context.reportRuntimeError("Direct call is not supported"); } public Object getImplementation() { @@ -39,15 +57,17 @@ public Scriptable construct(Context cx, VarScope scope, Object[] args) { } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Scriptable construct(Context cx, Object nt, VarScope s, Object thisObj, Object[] args) { + throw Context.reportRuntimeError("Direct call is not supported"); + } + + @Override + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { return Interpreter.restartContinuation(this, cx, scope, args); } - public static boolean isContinuationConstructor(IdFunctionObject f) { - if (f.hasTag(FTAG) && f.methodId() == Id_constructor) { - return true; - } - return false; + public static boolean isContinuationConstructor(JSFunction f) { + return f.getDescriptor() == CTOR_DESCRIPTOR; } /** @@ -61,49 +81,4 @@ public static boolean isContinuationConstructor(IdFunctionObject f) { public static boolean equalImplementations(NativeContinuation c1, NativeContinuation c2) { return Objects.equals(c1.implementation, c2.implementation); } - - @Override - protected void initPrototypeId(int id) { - String s; - int arity; - switch (id) { - case Id_constructor: - arity = 0; - s = "constructor"; - break; - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - initPrototypeMethod(FTAG, id, s, arity); - } - - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, VarScope scope, Scriptable thisObj, Object[] args) { - if (!f.hasTag(FTAG)) { - return super.execIdCall(f, cx, scope, thisObj, args); - } - int id = f.methodId(); - switch (id) { - case Id_constructor: - throw Context.reportRuntimeError("Direct call is not supported"); - } - throw new IllegalArgumentException(String.valueOf(id)); - } - - @Override - protected int findPrototypeId(String s) { - int id; - switch (s) { - case "constructor": - id = Id_constructor; - break; - default: - id = 0; - break; - } - return id; - } - - private static final int Id_constructor = 1, MAX_PROTOTYPE_ID = 1; } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeDate.java b/rhino/src/main/java/org/mozilla/javascript/NativeDate.java index 24baa583ec7..f22702bb9e5 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeDate.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeDate.java @@ -106,7 +106,7 @@ final class NativeDate extends ScriptableObject { static void init(Context cx, VarScope scope, boolean sealed) { DESCRIPTOR.buildConstructor( cx, - (VarScope) scope, + scope, cx.getLanguageVersion() >= Context.VERSION_ES6 ? new NativeObject() : new NativeDate(Double.NaN), @@ -187,7 +187,7 @@ private static Object js_toJSON( ScriptRuntime.toString(o), ScriptRuntime.toString(toISO)); } - Object result = ((Callable) toISO).call(cx, (VarScope) s, o, ScriptRuntime.emptyArgs); + Object result = ((Callable) toISO).call(cx, s, o, ScriptRuntime.emptyArgs); if (!ScriptRuntime.isPrimitive(result)) { throw ScriptRuntime.typeErrorById( "msg.toisostring.must.return.primitive", ScriptRuntime.toString(result)); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeError.java b/rhino/src/main/java/org/mozilla/javascript/NativeError.java index 27ffa0f036d..4cdc61ae223 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeError.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeError.java @@ -82,7 +82,7 @@ private static Object js_isError( static void init(Context cx, VarScope scope, boolean sealed) { var ctor = DESCRIPTOR.buildConstructor(cx, scope, new NativeObject(), sealed); - NativeCallSite.init(scope, sealed); + NativeCallSite.init(cx, scope, sealed); ProtoProps protoProps = new ProtoProps(); ((ScriptableObject) ctor.getPrototypeProperty()).associateValue(ProtoProps.KEY, protoProps); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeFunction.java b/rhino/src/main/java/org/mozilla/javascript/NativeFunction.java index 2652854d81a..fd54e548d17 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeFunction.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeFunction.java @@ -20,6 +20,10 @@ public abstract class NativeFunction extends BaseFunction { private boolean isShorthand; + protected NativeFunction(VarScope scope) { + super(scope); + } + public final void initScriptFunction( Context cx, VarScope scope, boolean es6GeneratorFunction, boolean isShorthand) { ScriptRuntime.setFunctionProtoAndParent(this, cx, scope, es6GeneratorFunction); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeGenerator.java b/rhino/src/main/java/org/mozilla/javascript/NativeGenerator.java index fb89f2d7031..7e3b47565ab 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeGenerator.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeGenerator.java @@ -6,40 +6,36 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Destination.CTOR; +import static org.mozilla.javascript.Symbol.Kind.REGULAR; + /** * This class implements generator objects. See Generators * * @author Norris Boyd */ -public final class NativeGenerator extends IdScriptableObject { - private static final long serialVersionUID = 1645892441041347273L; - - private static final Object GENERATOR_TAG = "Generator"; - - static NativeGenerator init(TopLevel scope, boolean sealed) { - // Generator - // Can't use "NativeGenerator().exportAsJSClass" since we don't want - // to define "Generator" as a constructor in the top-level scope. - - NativeGenerator prototype = new NativeGenerator(); - if (scope != null) { - prototype.setParentScope(scope); - prototype.setPrototype(getObjectPrototype(scope)); - } - prototype.activatePrototypeMap(MAX_PROTOTYPE_ID); - if (sealed) { - prototype.sealObject(); - } - - // Need to access Generator prototype when constructing - // Generator instances, but don't have a generator constructor - // to use to find the prototype. Use the "associateValue" - // approach instead. - if (scope != null) { - scope.associateValue(GENERATOR_TAG, prototype); - } +public final class NativeGenerator extends ScriptableObject { + private static final long serialVersionUID = -1383456283657974338L; + + private static final SymbolKey GENERATOR_TAG = new SymbolKey("GeneratorPrototype", REGULAR); + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder(GENERATOR_TAG) + .withMethod(CTOR, "close", 1, NativeGenerator::js_close) + .withMethod(CTOR, "next", 1, NativeGenerator::js_next) + .withMethod(CTOR, "send", 0, NativeGenerator::js_send) + .withMethod(CTOR, "throw", 0, NativeGenerator::js_throw) + .withMethod(CTOR, "__iterator__", 1, NativeGenerator::js_iterator) + .build(); + } + static NativeGenerator init(Context cx, TopLevel scope, boolean sealed) { + var prototype = new NativeGenerator(); + DESCRIPTOR.populateGlobal(cx, scope, prototype, sealed); + scope.associateValue(GENERATOR_TAG, prototype); return prototype; } @@ -66,78 +62,49 @@ public String getClassName() { return "Generator"; } - @Override - protected void initPrototypeId(int id) { - String s; - int arity; - switch (id) { - case Id_close: - arity = 1; - s = "close"; - break; - case Id_next: - arity = 1; - s = "next"; - break; - case Id_send: - arity = 0; - s = "send"; - break; - case Id_throw: - arity = 0; - s = "throw"; - break; - case Id___iterator__: - arity = 1; - s = "__iterator__"; - break; - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - initPrototypeMethod(GENERATOR_TAG, id, s, arity); + private static Object js_close( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + // need to run any pending finally clauses + var generator = realThis(thisObj); + return generator.resume(cx, s, GENERATOR_CLOSE, new GeneratorClosedException()); } - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, VarScope scope, Scriptable thisObj, Object[] args) { - if (!f.hasTag(GENERATOR_TAG)) { - return super.execIdCall(f, cx, scope, thisObj, args); - } - int id = f.methodId(); - - NativeGenerator generator = ensureType(thisObj, NativeGenerator.class, f); - - switch (id) { - case Id_close: - // need to run any pending finally clauses - return generator.resume(cx, scope, GENERATOR_CLOSE, new GeneratorClosedException()); - - case Id_next: - // arguments to next() are ignored - generator.firstTime = false; - return generator.resume(cx, scope, GENERATOR_SEND, Undefined.instance); - - case Id_send: - { - Object arg = args.length > 0 ? args[0] : Undefined.instance; - if (generator.firstTime && !arg.equals(Undefined.instance)) { - throw ScriptRuntime.typeErrorById("msg.send.newborn"); - } - return generator.resume(cx, scope, GENERATOR_SEND, arg); - } - - case Id_throw: - return generator.resume( - cx, scope, GENERATOR_THROW, args.length > 0 ? args[0] : Undefined.instance); - - case Id___iterator__: - return thisObj; - - default: - throw new IllegalArgumentException(String.valueOf(id)); + private static Object js_next( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + // arguments to next() are ignored + var generator = realThis(thisObj); + generator.firstTime = false; + return generator.resume(cx, s, GENERATOR_SEND, Undefined.instance); + } + + private static Object js_send( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + { + var generator = realThis(thisObj); + Object arg = args.length > 0 ? args[0] : Undefined.instance; + if (generator.firstTime && !arg.equals(Undefined.instance)) { + throw ScriptRuntime.typeErrorById("msg.send.newborn"); + } + return generator.resume(cx, s, GENERATOR_SEND, arg); } } + private static Object js_throw( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + var generator = realThis(thisObj); + return generator.resume( + cx, s, GENERATOR_THROW, args.length > 0 ? args[0] : Undefined.instance); + } + + private static Object js_iterator( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return thisObj; + } + + private static NativeGenerator realThis(Object thisObj) { + return LambdaConstructor.convertThisObject(thisObj, NativeGenerator.class); + } + private Object resume(Context cx, VarScope scope, int operation, Object value) { if (savedState == null) { if (operation == GENERATOR_CLOSE) return Undefined.instance; @@ -176,39 +143,6 @@ private Object resume(Context cx, VarScope scope, int operation, Object value) { } } - @Override - protected int findPrototypeId(String s) { - int id; - switch (s) { - case "close": - id = Id_close; - break; - case "next": - id = Id_next; - break; - case "send": - id = Id_send; - break; - case "throw": - id = Id_throw; - break; - case "__iterator__": - id = Id___iterator__; - break; - default: - id = 0; - break; - } - return id; - } - - private static final int Id_close = 1, - Id_next = 2, - Id_send = 3, - Id_throw = 4, - Id___iterator__ = 5, - MAX_PROTOTYPE_ID = 5; - private JSFunction function; private Object savedState; private String lineSource; diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeGlobal.java b/rhino/src/main/java/org/mozilla/javascript/NativeGlobal.java index 07ec60764ff..0438cb27460 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeGlobal.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeGlobal.java @@ -6,11 +6,14 @@ 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; import static org.mozilla.javascript.ScriptableObject.DONTENUM; -import static org.mozilla.javascript.ScriptableObject.PERMANENT; -import static org.mozilla.javascript.ScriptableObject.READONLY; import java.io.Serializable; +import java.util.EnumMap; +import java.util.Map; import org.mozilla.javascript.xml.XMLLib; /** @@ -23,37 +26,73 @@ public class NativeGlobal implements Serializable { static final long serialVersionUID = 6080442165748707530L; + private static final ClassDescriptor DESCRIPTOR; + private static final JSDescriptor EVAL_DESCRIPTOR; + private static final Map ERROR_DESCRIPTORS; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder("globalThis") + .withMethod(CTOR, "decodeURI", 1, NativeGlobal::js_decodeURI) + .withMethod( + CTOR, "decodeURIComponent", 1, NativeGlobal::js_decodeURIComponent) + .withMethod(CTOR, "encodeURI", 1, NativeGlobal::js_encodeURI) + .withMethod( + CTOR, "encodeURIComponent", 1, NativeGlobal::js_encodeURIComponent) + .withMethod(CTOR, "escape", 1, NativeGlobal::js_escape) + .withMethod(CTOR, "isFinite", 1, NativeGlobal::js_isFinite) + .withMethod(CTOR, "isNaN", 1, NativeGlobal::js_isNaN) + .withMethod(CTOR, "isXMLName", 1, NativeGlobal::js_isXMLName) + .withMethod(CTOR, "parseFloat", 1, NativeGlobal::js_parseFloat) + .withMethod(CTOR, "parseInt", 2, NativeGlobal::js_parseInt) + .withMethod(CTOR, "unescape", 1, NativeGlobal::js_unescape) + .withMethod(CTOR, "uneval", 1, NativeGlobal::js_uneval) + .withMethod(CTOR, "eval", 1, NativeGlobal::js_eval) + .withProp(CTOR, "NaN", value(ScriptRuntime.NaNobj)) + .withProp(CTOR, "Infinity", value(Double.POSITIVE_INFINITY)) + .withProp(CTOR, "undefined", value(Undefined.instance)) + .build(); + EVAL_DESCRIPTOR = DESCRIPTOR.findCtorDesc("eval"); + + var errorDescs = + new EnumMap(TopLevel.NativeErrors.class); + for (var e : TopLevel.NativeErrors.values()) { + if (e == TopLevel.NativeErrors.Error) continue; + ClassDescriptor.Builder builder; + if (e != TopLevel.NativeErrors.AggregateError) { + builder = + new ClassDescriptor.Builder( + e.name(), + 1, + (c, f, nt, s, thisObj, args) -> NativeError.make(c, s, f, args), + (c, f, nt, s, thisObj, args) -> NativeError.make(c, s, f, args)); + } else { + builder = + new ClassDescriptor.Builder( + e.name(), + 2, + (c, f, nt, s, thisObj, args) -> + NativeError.makeAggregate(c, s, f, args), + (c, f, nt, s, thisObj, args) -> + NativeError.makeAggregate(c, s, f, args)); + } + errorDescs.put( + e, + builder.withProp(PROTO, "name", value(e.name(), DONTENUM)) + .withProp(PROTO, "message", value("", DONTENUM)) + .build()); + } + ERROR_DESCRIPTORS = Map.copyOf(errorDescs); + } + public static void init(Context cx, TopLevel scope, boolean sealed) { - defineGlobalFunction(scope, sealed, "decodeURI", 1, NativeGlobal::js_decodeURI); - defineGlobalFunction( - scope, sealed, "decodeURIComponent", 1, NativeGlobal::js_decodeURIComponent); - defineGlobalFunction(scope, sealed, "encodeURI", 1, NativeGlobal::js_encodeURI); - defineGlobalFunction( - scope, sealed, "encodeURIComponent", 1, NativeGlobal::js_encodeURIComponent); - defineGlobalFunction(scope, sealed, "escape", 1, NativeGlobal::js_escape); - defineGlobalFunction(scope, sealed, "isFinite", 1, NativeGlobal::js_isFinite); - defineGlobalFunction(scope, sealed, "isNaN", 1, NativeGlobal::js_isNaN); - defineGlobalFunction(scope, sealed, "isXMLName", 1, NativeGlobal::js_isXMLName); - defineGlobalFunction(scope, sealed, "parseFloat", 1, NativeGlobal::js_parseFloat); - defineGlobalFunction(scope, sealed, "parseInt", 2, NativeGlobal::js_parseInt); - defineGlobalFunction(scope, sealed, "unescape", 1, NativeGlobal::js_unescape); - defineGlobalFunction(scope, sealed, "uneval", 1, NativeGlobal::js_uneval); - defineGlobalFunctionEval(scope, sealed); - - ScriptableObject.defineProperty( - scope, "NaN", ScriptRuntime.NaNobj, READONLY | DONTENUM | PERMANENT); - ScriptableObject.defineProperty( - scope, "Infinity", Double.POSITIVE_INFINITY, READONLY | DONTENUM | PERMANENT); - ScriptableObject.defineProperty( - scope, "undefined", Undefined.instance, READONLY | DONTENUM | PERMANENT); var globalThis = scope.getGlobalThis(); + DESCRIPTOR.populateGlobal(cx, scope, globalThis, false); var obj = (Scriptable) scope.get("Object", scope); var objProto = (Scriptable) obj.get("prototype", obj); globalThis.setPrototype(objProto); - ScriptableObject.defineProperty(scope, "globalThis", globalThis, DONTENUM); - /* Each error constructor gets its own Error object as a prototype, with the 'name' property set to the name of the error. @@ -64,119 +103,43 @@ public static void init(Context cx, TopLevel scope, boolean sealed) { ScriptableObject.ensureScriptable( ScriptableObject.getProperty(nativeError, "prototype")); - for (TopLevel.NativeErrors error : TopLevel.NativeErrors.values()) { - if (error == TopLevel.NativeErrors.Error) { - // Error is initialized elsewhere and we should not overwrite it. - continue; - } - String name = error.name(); - TopLevel topLevelScope = ScriptableObject.getTopLevelScope(scope); - Function builtinErrorCtor = - TopLevel.getBuiltinCtor(cx, topLevelScope, TopLevel.Builtins.Error); - ScriptableObject errorProto = NativeError.makeProto(topLevelScope, builtinErrorCtor); - errorProto.defineProperty("name", name, DONTENUM); - errorProto.defineProperty("message", "", DONTENUM); - - BaseFunction ctor; - - // Building errors is complex because of the prototype chain requirements. This is a bit - // arcane, but it's a combination that makes test262 happy. - if (error == TopLevel.NativeErrors.AggregateError) { - var target = - new SerializableConstructable() { - // We need a reference to the LambdaFunction, so we use this "lateBound" - // trick. It ain't great, but it's the only idea I have. - private Function lateBoundCtor; - - @Override - public Scriptable construct( - Context callCx, VarScope callScope, Object[] args) { - return NativeError.makeAggregate( - callCx, callScope, lateBoundCtor, args); - } - }; - ctor = - new LambdaConstructor(scope, name, 2, target) { - // Necessary to make the test262 case - // built-ins/NativeErrors/AggregateError/newtarget-proto-custom.js work - // correctly - @Override - public Scriptable createObject(Context cx, VarScope scope) { - return null; - } - }; - target.lateBoundCtor = ctor; - } else { - var target = - new SerializableConstructable() { - private Function lateBoundCtor; - - @Override - public Scriptable construct( - Context callCx, VarScope callScope, Object[] args) { - return NativeError.make(callCx, callScope, lateBoundCtor, args); - } - }; - ctor = new LambdaConstructor(scope, name, 1, target); - target.lateBoundCtor = ctor; - } - - ctor.setImmunePrototypeProperty(errorProto); - ctor.setPrototype(nativeError); - errorProto.put("constructor", errorProto, ctor); - errorProto.setAttributes("constructor", ScriptableObject.DONTENUM); - errorProto.setPrototype(nativeErrorProto); - ctor.setAttributes("name", DONTENUM | READONLY); - ctor.setAttributes("length", DONTENUM | READONLY); - if (sealed) { - errorProto.sealObject(); - ctor.sealObject(); - } - - ScriptableObject.defineProperty(scope, name, ctor, DONTENUM); - } - } - - private static void defineGlobalFunction( - VarScope scope, - boolean sealed, - String name, - int length, - SerializableCallable callable) { - LambdaFunction fun = new LambdaFunction(scope, name, length, null, callable); - registerGlobalFunction(scope, sealed, name, fun); - } - - private static void registerGlobalFunction( - VarScope scope, boolean sealed, String name, LambdaFunction fun) { - ScriptableObject.defineProperty(scope, name, fun, DONTENUM); - if (sealed) { - fun.sealObject(); + Function builtinErrorCtor = TopLevel.getBuiltinCtor(cx, scope, TopLevel.Builtins.Error); + for (var e : ERROR_DESCRIPTORS.entrySet()) { + var name = e.getKey().name(); + var desc = e.getValue(); + var errorProto = NativeError.makeProto(scope, builtinErrorCtor); + desc.buildConstructor( + cx, + scope, + errorProto, + sealed, + (c, ctor) -> { + ctor.setPrototype(nativeError); + errorProto.setPrototype(nativeErrorProto); + }); } } - // Eval is special because we need to "recognize" it in isEvalFunction - private static void defineGlobalFunctionEval(VarScope scope, boolean sealed) { - LambdaFunction evalFun = new EvalLambdaFunction(scope); - registerGlobalFunction(scope, sealed, "eval", evalFun); - } - static boolean isEvalFunction(Object functionObj) { - return functionObj instanceof EvalLambdaFunction; + return functionObj instanceof JSFunction + && ((JSFunction) functionObj).getDescriptor() == EVAL_DESCRIPTOR; } - private static String js_uneval(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static String js_uneval( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { Object value = (args.length != 0) ? args[0] : Undefined.instance; - return ScriptRuntime.uneval(cx, scope, value); + return ScriptRuntime.uneval(cx, s, value); } - private static Boolean js_isXMLName(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Boolean js_isXMLName( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { Object name = (args.length == 0) ? Undefined.instance : args[0]; - XMLLib xmlLib = XMLLib.extractFromScope(scope); + XMLLib xmlLib = XMLLib.extractFromScope(s); return xmlLib.isXMLName(cx, name); } - private static Boolean js_isNaN(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Boolean js_isNaN( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { // The global method isNaN, as per ECMA-262 15.1.2.6. if (args.length < 1) { return true; @@ -186,48 +149,52 @@ private static Boolean js_isNaN(Context cx, VarScope scope, Object thisObj, Obje } } - private static Object js_isFinite(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_isFinite( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (args.length < 1) { return Boolean.FALSE; } return NativeNumber.isFinite(args[0]); } - private static String js_decodeURI(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static String js_decodeURI( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String str = ScriptRuntime.toString(args, 0); return decode(str, true); } private static String js_decodeURIComponent( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String str = ScriptRuntime.toString(args, 0); return decode(str, false); } - private static String js_encodeURI(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static String js_encodeURI( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String str = ScriptRuntime.toString(args, 0); return encode(str, true); } private static String js_encodeURIComponent( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String str = ScriptRuntime.toString(args, 0); return encode(str, false); } /** The global method parseInt, as per ECMA-262 15.1.2.2. */ - static Object js_parseInt(Context cx, VarScope scope, Object thisObj, Object[] args) { - String s = ScriptRuntime.toString(args, 0); + static Object js_parseInt( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + String str = ScriptRuntime.toString(args, 0); int radix = ScriptRuntime.toInt32(args, 1); - int len = s.length(); + int len = str.length(); if (len == 0) return ScriptRuntime.NaNobj; boolean negative = false; int start = 0; char c; do { - c = s.charAt(start); + c = str.charAt(start); if (!ScriptRuntime.isStrWhiteSpaceChar(c)) break; start++; } while (start < len); @@ -239,15 +206,15 @@ static Object js_parseInt(Context cx, VarScope scope, Object thisObj, Object[] a radix = NO_RADIX; } else if (radix < 2 || radix > 36) { return ScriptRuntime.NaNobj; - } else if (radix == 16 && len - start > 1 && s.charAt(start) == '0') { - c = s.charAt(start + 1); + } else if (radix == 16 && len - start > 1 && str.charAt(start) == '0') { + c = str.charAt(start + 1); if (c == 'x' || c == 'X') start += 2; } if (radix == NO_RADIX) { radix = 10; - if (len - start > 1 && s.charAt(start) == '0') { - c = s.charAt(start + 1); + if (len - start > 1 && str.charAt(start) == '0') { + c = str.charAt(start + 1); if (c == 'x' || c == 'X') { radix = 16; start += 2; @@ -260,7 +227,7 @@ static Object js_parseInt(Context cx, VarScope scope, Object thisObj, Object[] a } } - double d = ScriptRuntime.stringPrefixToNumber(s, start, radix); + double d = ScriptRuntime.stringPrefixToNumber(str, start, radix); return negative ? -d : d; } @@ -269,11 +236,12 @@ static Object js_parseInt(Context cx, VarScope scope, Object thisObj, Object[] a * * @param args the arguments to parseFloat, ignoring args[>=1] */ - static Object js_parseFloat(Context cx, VarScope scope, Object thisObj, Object[] args) { + static Object js_parseFloat( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (args.length < 1) return ScriptRuntime.NaNobj; - String s = ScriptRuntime.toString(args[0]); - int len = s.length(); + String str = ScriptRuntime.toString(args[0]); + int len = str.length(); int start = 0; // Scan forward to skip whitespace char c; @@ -281,7 +249,7 @@ static Object js_parseFloat(Context cx, VarScope scope, Object thisObj, Object[] if (start == len) { return ScriptRuntime.NaNobj; } - c = s.charAt(start); + c = str.charAt(start); if (!ScriptRuntime.isStrWhiteSpaceChar(c)) { break; } @@ -294,13 +262,13 @@ static Object js_parseFloat(Context cx, VarScope scope, Object thisObj, Object[] if (i == len) { return ScriptRuntime.NaNobj; } - c = s.charAt(i); + c = str.charAt(i); } if (c == 'I') { // check for "Infinity" - if (i + 8 <= len && s.regionMatches(i, "Infinity", 0, 8)) { - if (s.charAt(start) == '-') { + if (i + 8 <= len && str.regionMatches(i, "Infinity", 0, 8)) { + if (str.charAt(start) == '-') { return Double.NEGATIVE_INFINITY; } else { return Double.POSITIVE_INFINITY; @@ -314,7 +282,7 @@ static Object js_parseFloat(Context cx, VarScope scope, Object thisObj, Object[] int exponent = -1; boolean exponentValid = false; for (; i < len; i++) { - switch (s.charAt(i)) { + switch (str.charAt(i)) { case '.': if (decimal != -1) // Only allow a single decimal point. break; @@ -365,9 +333,9 @@ static Object js_parseFloat(Context cx, VarScope scope, Object thisObj, Object[] if (exponent != -1 && !exponentValid) { i = exponent; } - s = s.substring(start, i); + str = str.substring(start, i); try { - return Double.valueOf(s); + return Double.valueOf(str); } catch (NumberFormatException ex) { return ScriptRuntime.NaNobj; } @@ -379,10 +347,11 @@ static Object js_parseFloat(Context cx, VarScope scope, Object thisObj, Object[] *

Includes code for the 'mask' argument supported by the C escape method, which used to be * part of the browser embedding. Blame for the strange constant names should be directed there. */ - private static Object js_escape(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_escape( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { final int URL_XALPHAS = 1, URL_XPALPHAS = 2, URL_PATH = 4; - String s = ScriptRuntime.toString(args, 0); + String str = ScriptRuntime.toString(args, 0); int mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH; if (args.length > 1) { // the 'mask' argument. Non-ECMA. @@ -395,8 +364,8 @@ private static Object js_escape(Context cx, VarScope scope, Object thisObj, Obje } StringBuilder sb = null; - for (int k = 0, L = s.length(); k != L; ++k) { - int c = s.charAt(k); + for (int k = 0, L = str.length(); k != L; ++k) { + int c = str.charAt(k); if (mask != 0 && ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') @@ -440,16 +409,17 @@ private static Object js_escape(Context cx, VarScope scope, Object thisObj, Obje } } - return (sb == null) ? s : sb.toString(); + return (sb == null) ? str : sb.toString(); } /** The global unescape method, as per ECMA-262 15.1.2.5. */ - private static Object js_unescape(Context cx, VarScope scope, Object thisObj, Object[] args) { - String s = ScriptRuntime.toString(args, 0); - int firstEscapePos = s.indexOf('%'); + private static Object js_unescape( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + String str = ScriptRuntime.toString(args, 0); + int firstEscapePos = str.indexOf('%'); if (firstEscapePos >= 0) { - int L = s.length(); - char[] buf = s.toCharArray(); + int L = str.length(); + char[] buf = str.toCharArray(); int destination = firstEscapePos; for (int k = firstEscapePos; k != L; ) { char c = buf[k]; @@ -477,18 +447,19 @@ private static Object js_unescape(Context cx, VarScope scope, Object thisObj, Ob buf[destination] = c; ++destination; } - s = new String(buf, 0, destination); + str = new String(buf, 0, destination); } - return s; + return str; } /** * This is an indirect call to eval, and thus uses the global environment. Direct calls are * executed via ScriptRuntime.callSpecial(). */ - private static Object js_eval(Context cx, VarScope scope, Object[] args) { - TopLevel global = ScriptableObject.getTopLevelScope(scope); - return ScriptRuntime.evalSpecial(cx, global, global.getGlobalThis(), args, "eval code", 1); + private static Object js_eval( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + TopLevel top = ScriptableObject.getTopLevelScope(f.getDeclarationScope()); + return ScriptRuntime.evalSpecial(cx, top, top.getGlobalThis(), args, "eval code", 1); } /** @@ -740,20 +711,4 @@ private static int oneUcs4ToUtf8Char(byte[] utf8Buffer, int ucs4Char) { } return utf8Length; } - - /** - * A simple subclass of {@link LambdaFunction} used to "tag" eval, so that we can recognize it - * in {@link NativeGlobal#isEvalFunction} - */ - private static class EvalLambdaFunction extends LambdaFunction { - public EvalLambdaFunction(VarScope scope) { - super( - scope, - "eval", - 1, - null, - (callCx, callScope, thisObj, args) -> - NativeGlobal.js_eval(callCx, callScope, args)); - } - } } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeIterator.java b/rhino/src/main/java/org/mozilla/javascript/NativeIterator.java index ccbd64a74b9..107f041267e 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeIterator.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeIterator.java @@ -6,6 +6,8 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Destination.PROTO; + import java.util.Iterator; /** @@ -21,48 +23,46 @@ public final class NativeIterator extends ScriptableObject { private Object objectIterator; - static void init(Context cx, TopLevel scope, boolean sealed) { - LambdaConstructor constructor = - new LambdaConstructor( - scope, - CLASS_NAME, - 2, - NativeIterator::jsConstructorCall, - NativeIterator::jsConstructor); - constructor.setPrototypePropertyAttributes(PERMANENT | READONLY | DONTENUM); - - NativeIterator proto = new NativeIterator(); - constructor.setPrototypeScriptable(proto); - - constructor.definePrototypeMethod(scope, "next", 0, NativeIterator::js_next); - constructor.definePrototypeMethod( - scope, ITERATOR_PROPERTY_NAME, 1, NativeIterator::js_iteratorMethod); - - ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, ScriptableObject.DONTENUM); - if (sealed) { - constructor.sealObject(); - ((ScriptableObject) constructor.getPrototypeProperty()).sealObject(); - } + private static final String STOP_ITERATION = "StopIteration"; + public static final String ITERATOR_PROPERTY_NAME = "__iterator__"; + + private static final ClassDescriptor DESCRIPTOR; + + private static final ClassDescriptor STOP_ITER_DESCRIPTOR; - // Generator + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + CLASS_NAME, + 2, + NativeIterator::jsConstructorCall, + NativeIterator::jsConstructor) + .withMethod(PROTO, "next", 0, NativeIterator::js_next) + .withMethod( + PROTO, ITERATOR_PROPERTY_NAME, 1, NativeIterator::js_iteratorMethod) + .build(); + STOP_ITER_DESCRIPTOR = new ClassDescriptor.Builder(STOP_ITERATION, 0, null, null).build(); + } + + static void init(Context cx, TopLevel scope, boolean sealed) { if (cx.getLanguageVersion() >= Context.VERSION_ES6) { - ES6Generator.init(scope, sealed); + // Modern (ES2025) Iterator constructor with static `from`. Replaces the + // JS1.7 Iterator() ctor, which is no longer installed at ES6+. + ES6Iterator.initIteratorConstructor(cx, scope, sealed); + ES6Generator.init(cx, scope, sealed); + ES6AsyncGenerator.init(cx, scope, sealed); } else { - NativeGenerator.init(scope, sealed); + NativeObject proto = new NativeObject(); + DESCRIPTOR.buildConstructor(cx, scope, proto, sealed); + NativeGenerator.init(cx, scope, sealed); } // StopIteration - NativeObject obj = new StopIteration(); - obj.setPrototype(getObjectPrototype(scope)); - obj.setParentScope(scope); - if (sealed) { - obj.sealObject(); - } - ScriptableObject.defineProperty(scope, STOP_ITERATION, obj, ScriptableObject.DONTENUM); + var stopCtor = STOP_ITER_DESCRIPTOR.populateGlobal(cx, scope, new StopIteration(), sealed); // Use "associateValue" so that generators can continue to // throw StopIteration even if the property of the global // scope is replaced or deleted. - scope.associateValue(ITERATOR_TAG, obj); + scope.associateValue(ITERATOR_TAG, stopCtor); } /** Only for constructing the prototype object. */ @@ -85,9 +85,6 @@ public static Object getStopIterationObject(VarScope scope) { return ScriptableObject.getTopScopeValue(top, ITERATOR_TAG); } - private static final String STOP_ITERATION = "StopIteration"; - public static final String ITERATOR_PROPERTY_NAME = "__iterator__"; - public static class StopIteration extends NativeObject { private static final long serialVersionUID = 2485151085722377663L; @@ -123,13 +120,13 @@ public String getClassName() { } private static Object jsConstructorCall( - Context cx, VarScope scope, Object thisObj, Object[] args) { - Scriptable target = requireIteratorTarget(cx, scope, args); + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + Scriptable target = requireIteratorTarget(cx, s, args); boolean keyOnly = isKeyOnly(args); Iterator iterator = getJavaIterator(target); if (iterator != null) { - VarScope topScope = ScriptableObject.getTopLevelScope(scope); + VarScope topScope = ScriptableObject.getTopLevelScope(s); return cx.getWrapFactory() .wrap( cx, @@ -143,13 +140,14 @@ private static Object jsConstructorCall( return jsIterator; } - return createNativeIterator(cx, scope, target, keyOnly); + return createNativeIterator(cx, s, target, keyOnly); } - private static Scriptable jsConstructor(Context cx, VarScope scope, Object[] args) { - Scriptable target = requireIteratorTarget(cx, scope, args); + private static Scriptable jsConstructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + Scriptable target = requireIteratorTarget(cx, s, args); boolean keyOnly = isKeyOnly(args); - return createNativeIterator(cx, scope, target, keyOnly); + return createNativeIterator(cx, s, target, keyOnly); } private static Scriptable requireIteratorTarget(Context cx, VarScope scope, Object[] args) { @@ -165,9 +163,10 @@ private static boolean isKeyOnly(Object[] args) { return args.length > 1 && ScriptRuntime.toBoolean(args[1]); } - 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) { NativeIterator iterator = realThis(thisObj); - return iterator.next(cx, scope); + return iterator.next(cx, s); } private static NativeIterator createNativeIterator( @@ -188,7 +187,7 @@ private static NativeIterator createNativeIterator( } private static Object js_iteratorMethod( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj); } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeJSON.java b/rhino/src/main/java/org/mozilla/javascript/NativeJSON.java index 1c70459eb3e..965feb3df12 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeJSON.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeJSON.java @@ -6,6 +6,9 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Builder.value; +import static org.mozilla.javascript.ClassDescriptor.Destination.CTOR; + import java.lang.reflect.Array; import java.math.BigInteger; import java.util.ArrayDeque; @@ -30,21 +33,21 @@ public final class NativeJSON extends ScriptableObject { private static final int MAX_STRINGIFY_GAP_LENGTH = 10; - static Object init(Context cx, VarScope scope, boolean sealed) { - NativeJSON json = new NativeJSON(); - json.setPrototype(getObjectPrototype(scope)); - json.setParentScope(scope); - - json.defineBuiltinProperty(scope, "parse", 2, NativeJSON::parse); - json.defineBuiltinProperty(scope, "stringify", 3, NativeJSON::stringify); - - json.defineProperty("toSource", "JSON", DONTENUM | READONLY | PERMANENT); + private static final ClassDescriptor DESCRIPTION; + + static { + DESCRIPTION = + new ClassDescriptor.Builder(JSON_TAG) + .withMethod(CTOR, "parse", 2, NativeJSON::parse) + .withMethod(CTOR, "stringify", 3, NativeJSON::stringify) + .withProp(CTOR, "toSource", value("JSON")) + .withProp( + CTOR, SymbolKey.TO_STRING_TAG, value(JSON_TAG, DONTENUM | READONLY)) + .build(); + } - json.defineProperty(SymbolKey.TO_STRING_TAG, JSON_TAG, DONTENUM | READONLY); - if (sealed) { - json.sealObject(); - } - return json; + static Object init(Context cx, VarScope scope, boolean sealed) { + return DESCRIPTION.populateGlobal(cx, scope, new NativeJSON(), sealed); } private NativeJSON() {} @@ -54,19 +57,21 @@ public String getClassName() { return "JSON"; } - private static Object parse(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object parse( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String jtext = ScriptRuntime.toString(args, 0); Object reviver = null; if (args.length > 1) { reviver = args[1]; } if (reviver instanceof Callable) { - return parse(cx, scope, jtext, (Callable) reviver); + return parse(cx, f.getDeclarationScope(), jtext, (Callable) reviver); } - return parse(cx, scope, jtext); + return parse(cx, f.getDeclarationScope(), jtext); } - private static Object stringify(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object stringify( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { Object value = Undefined.instance, replacer = null, space = null; if (args.length > 0) { @@ -78,7 +83,7 @@ private static Object stringify(Context cx, VarScope scope, Object thisObj, Obje } } } - return stringify(cx, scope, value, replacer, space); + return stringify(cx, s, value, replacer, space); } private static Object parse(Context cx, VarScope scope, String jtext) { diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeJavaClass.java b/rhino/src/main/java/org/mozilla/javascript/NativeJavaClass.java index 1b34ad1787d..080e1bd2f8a 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeJavaClass.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeJavaClass.java @@ -116,7 +116,7 @@ public Object getDefaultValue(Class hint) { } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { // If it looks like a "cast" of an object to this class type, // walk the prototype chain to see if there's a wrapper of a // object that's an instanceof this class. @@ -180,6 +180,10 @@ public Scriptable construct(Context cx, VarScope scope, Object[] args) { throw Context.reportRuntimeErrorById("msg.cant.instantiate", msg, classObject.getName()); } + public Scriptable construct(Context cx, Object nt, VarScope s, Object thisObj, Object args[]) { + return construct(cx, s, args); + } + static Scriptable constructSpecific( Context cx, VarScope scope, Object[] args, ExecutableBox ctor) { Object instance = constructInternal(args, ctor); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeJavaConstructor.java b/rhino/src/main/java/org/mozilla/javascript/NativeJavaConstructor.java index 810221989cc..19aed2c415b 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeJavaConstructor.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeJavaConstructor.java @@ -25,12 +25,13 @@ public class NativeJavaConstructor extends BaseFunction { ExecutableBox ctor; - public NativeJavaConstructor(ExecutableBox ctor) { + public NativeJavaConstructor(VarScope scope, ExecutableBox ctor) { + super(scope); this.ctor = ctor; } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { return NativeJavaClass.constructSpecific(cx, scope, args, ctor); } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeJavaMap.java b/rhino/src/main/java/org/mozilla/javascript/NativeJavaMap.java index 7b4d86a449f..21a0cc50fe9 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeJavaMap.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeJavaMap.java @@ -30,8 +30,8 @@ public class NativeJavaMap extends NativeJavaObject { private final TypeInfo keyType; private final TypeInfo valueType; - static void init(TopLevel scope, boolean sealed) { - NativeJavaMapIterator.init(scope, sealed); + static void init(Context cx, TopLevel scope, boolean sealed) { + NativeJavaMapIterator.init(cx, scope, sealed); } @SuppressWarnings("unchecked") @@ -160,7 +160,7 @@ public int hashCode() { } private static Callable symbol_iterator = - (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> { + (cx, scope, thisObj, args) -> { if (!(thisObj instanceof NativeJavaMap)) { throw ScriptRuntime.typeErrorById("msg.incompat.call", SymbolKey.ITERATOR); } @@ -168,11 +168,20 @@ public int hashCode() { }; private static final class NativeJavaMapIterator extends ES6Iterator { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -6354388021424261488L; private static final String ITERATOR_TAG = "JavaMapIterator"; - static void init(TopLevel scope, boolean sealed) { - ES6Iterator.init(scope, sealed, new NativeJavaMapIterator(), ITERATOR_TAG); + private static final ClassDescriptor DESCRIPTOR = + ES6Iterator.makeDescriptor(ITERATOR_TAG, "Java Map Iterator"); + + static void init(Context cx, VarScope scope, boolean sealed) { + ES6Iterator.initialize( + DESCRIPTOR, + cx, + (TopLevel) scope, + new NativeJavaMapIterator(), + sealed, + ITERATOR_TAG); } /** Only for constructing the prototype object. */ diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeJavaMethod.java b/rhino/src/main/java/org/mozilla/javascript/NativeJavaMethod.java index 8a71ae9cabf..e5724c30514 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeJavaMethod.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeJavaMethod.java @@ -37,19 +37,24 @@ public class NativeJavaMethod extends BaseFunction { private final transient CopyOnWriteArrayList overloadCache = new CopyOnWriteArrayList<>(); - NativeJavaMethod(ExecutableBox[] methods, String name) { + NativeJavaMethod(VarScope scope, ExecutableBox[] methods, String name) { + super(scope); this.functionName = name; this.methods = methods; } - NativeJavaMethod(ExecutableBox method, String name) { + NativeJavaMethod(VarScope scope, ExecutableBox method, String name) { + super(scope); this.functionName = name; this.methods = new ExecutableBox[] {method}; } @Deprecated public NativeJavaMethod(VarScope scope, Method method, String name) { - this(new ExecutableBox(method, TypeInfoFactory.GLOBAL, method.getDeclaringClass()), name); + this( + scope, + new ExecutableBox(method, TypeInfoFactory.GLOBAL, method.getDeclaringClass()), + name); } @Override @@ -129,7 +134,7 @@ public String toString() { } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { // Find a method that matches the types given. if (methods.length == 0) { throw new RuntimeException("No methods defined for call"); @@ -159,7 +164,7 @@ public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args if (meth.isStatic()) { javaObject = null; // don't need an object } else { - Scriptable o = thisObj; + Scriptable o = ScriptRuntime.toObject(getDeclarationScope(), thisObj); Class c = meth.getDeclaringClass(); for (; ; ) { if (o == null) { diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeJavaObject.java b/rhino/src/main/java/org/mozilla/javascript/NativeJavaObject.java index cd234c517ef..a78ed395992 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeJavaObject.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeJavaObject.java @@ -35,8 +35,8 @@ public class NativeJavaObject implements Scriptable, SymbolScriptable, Wrapper, private static final long serialVersionUID = -6948590651130498591L; - static void init(TopLevel scope, boolean sealed) { - JavaIterableIterator.init(scope, sealed); + static void init(Context cx, TopLevel scope, boolean sealed) { + JavaIterableIterator.init(cx, scope, sealed); } public NativeJavaObject() {} @@ -47,7 +47,7 @@ public NativeJavaObject(VarScope scope, Object javaObject, TypeInfo staticType) public NativeJavaObject( VarScope scope, Object javaObject, TypeInfo staticType, boolean isAdapter) { - this.parent = (VarScope) scope; + this.parent = scope; this.javaObject = javaObject; this.staticType = staticType; this.isAdapter = isAdapter; @@ -904,7 +904,7 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE } private static Callable symbol_iterator = - (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> { + (cx, scope, thisObj, args) -> { if (!(thisObj instanceof NativeJavaObject)) { throw ScriptRuntime.typeErrorById("msg.incompat.call", SymbolKey.ITERATOR); } @@ -916,11 +916,15 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE }; private static final class JavaIterableIterator extends ES6Iterator { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -2636492890037940863L; private static final String ITERATOR_TAG = "JavaIterableIterator"; - static void init(TopLevel scope, boolean sealed) { - ES6Iterator.init(scope, sealed, new JavaIterableIterator(), ITERATOR_TAG); + private static final ClassDescriptor DESCRIPTOR = + ES6Iterator.makeDescriptor(ITERATOR_TAG, "Java Iterable Iterator"); + + static void init(Context cx, TopLevel scope, boolean sealed) { + ES6Iterator.initialize( + DESCRIPTOR, cx, scope, new JavaIterableIterator(), sealed, ITERATOR_TAG); } /** Only for constructing the prototype object. */ diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeJavaTopPackage.java b/rhino/src/main/java/org/mozilla/javascript/NativeJavaTopPackage.java index 517d2b79195..85cc53c576c 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeJavaTopPackage.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeJavaTopPackage.java @@ -38,7 +38,7 @@ public class NativeJavaTopPackage extends NativeJavaPackage implements Function, } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { return construct(cx, scope, args); } @@ -63,6 +63,10 @@ public Scriptable construct(Context cx, VarScope scope, Object[] args) { return pkg; } + public Scriptable construct(Context cx, Object nt, VarScope s, Object thisObj, Object[] args) { + return construct(cx, s, args); + } + public static void init(Context cx, VarScope scope, boolean sealed) { ClassLoader loader = cx.getApplicationClassLoader(); final NativeJavaTopPackage top = new NativeJavaTopPackage(loader); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeMap.java b/rhino/src/main/java/org/mozilla/javascript/NativeMap.java index 2c09bbd68df..c9ee3b7d76a 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeMap.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeMap.java @@ -6,6 +6,11 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Builder.alias; +import static org.mozilla.javascript.ClassDescriptor.Builder.value; +import static org.mozilla.javascript.ClassDescriptor.Destination.CTOR; +import static org.mozilla.javascript.ClassDescriptor.Destination.PROTO; + import java.util.List; import java.util.Map; @@ -14,60 +19,61 @@ public class NativeMap extends ScriptableObject { private static final String CLASS_NAME = "Map"; static final String ITERATOR_TAG = "Map Iterator"; + private static final ClassDescriptor DESCRIPTOR; + private static final ClassDescriptor ITER_DESCRIPTOR = + ES6Iterator.makeDescriptor(ITERATOR_TAG, ITERATOR_TAG); + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + CLASS_NAME, + 0, + ClassDescriptor.typeError(), + NativeMap::js_constructor) + .withMethod(CTOR, "groupBy", 2, NativeMap::jsGroupBy) + .withMethod(PROTO, "set", 2, NativeMap::js_set) + .withMethod(PROTO, "delete", 1, NativeMap::js_delete) + .withMethod(PROTO, "get", 1, NativeMap::js_get) + .withMethod(PROTO, "has", 1, NativeMap::js_has) + .withMethod(PROTO, "clear", 0, NativeMap::js_clear) + .withMethod(PROTO, "keys", 0, NativeMap::js_keys) + .withMethod(PROTO, "values", 0, NativeMap::js_values) + .withMethod(PROTO, "forEach", 1, NativeMap::js_forEach) + .withMethod(PROTO, "entries", 0, NativeMap::js_entries) + .withProp(PROTO, SymbolKey.ITERATOR, alias("entries", DONTENUM)) + .withProp( + PROTO, + "size", + (thisObj) -> realThis(thisObj, "size").js_getSize(), + null, + DONTENUM) + .withProp( + PROTO, + NativeSet.GETSIZE, + (thisObj) -> realThis(thisObj, "size").js_getSize(), + null, + DONTENUM | PERMANENT) + .withProp( + PROTO, + SymbolKey.TO_STRING_TAG, + value(CLASS_NAME, DONTENUM | READONLY)) + .withProp(CTOR, SymbolKey.SPECIES, ScriptRuntimeES6::symbolSpecies) + .build(); + } + private final Hashtable entries = new Hashtable(); private boolean instanceOfMap = false; static Object init(Context cx, VarScope scope, boolean sealed) { - LambdaConstructor constructor = - new LambdaConstructor( - scope, - CLASS_NAME, - 0, - LambdaConstructor.CONSTRUCTOR_NEW, - NativeMap::jsConstructor); - constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); - - constructor.defineConstructorMethod(scope, "groupBy", 2, NativeMap::jsGroupBy); - - constructor.definePrototypeMethod(scope, "set", 2, NativeMap::js_set); - constructor.definePrototypeMethod(scope, "delete", 1, NativeMap::js_delete); - constructor.definePrototypeMethod(scope, "get", 1, NativeMap::js_get); - constructor.definePrototypeMethod(scope, "has", 1, NativeMap::js_has); - constructor.definePrototypeMethod(scope, "clear", 0, NativeMap::js_clear); - constructor.definePrototypeMethod(scope, "keys", 0, NativeMap::js_keys); - constructor.definePrototypeMethod(scope, "values", 0, NativeMap::js_values); - constructor.definePrototypeMethod(scope, "forEach", 1, NativeMap::js_forEach); - - constructor.definePrototypeMethod(scope, "entries", 0, NativeMap::js_entries); - constructor.definePrototypeAlias("entries", SymbolKey.ITERATOR, DONTENUM); - - // The spec requires very specific handling of the "size" prototype - // property that's not like other things that we already do. - ScriptableObject desc = (ScriptableObject) cx.newObject(scope); - desc.put("enumerable", desc, Boolean.FALSE); - desc.put("configurable", desc, Boolean.TRUE); - LambdaFunction sizeFunc = - new LambdaFunction( - scope, - "get size", - 0, - (Context lcx, VarScope lscope, Scriptable thisObj, Object[] args) -> - realThis(thisObj, "size").js_getSize(), - false); - desc.put("get", desc, sizeFunc); - constructor.definePrototypeProperty(cx, "size", desc); - constructor.definePrototypeProperty(cx, NativeSet.GETSIZE, desc); - - constructor.definePrototypeProperty( - SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); - - ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor); - if (sealed) { - constructor.sealObject(); - ((ScriptableObject) constructor.getPrototypeProperty()).sealObject(); - } - return constructor; + ES6Iterator.initialize( + ITER_DESCRIPTOR, + cx, + (TopLevel) scope, + new NativeCollectionIterator(ITERATOR_TAG), + sealed, + ITERATOR_TAG); + return DESCRIPTOR.buildConstructor(cx, scope, new NativeObject(), sealed); } @Override @@ -75,40 +81,44 @@ public String getClassName() { return CLASS_NAME; } - private static Scriptable jsConstructor(Context cx, VarScope scope, Object[] args) { + private static Scriptable js_constructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeMap nm = new NativeMap(); nm.instanceOfMap = true; if (args.length > 0) { - loadFromIterable(cx, scope, nm, key(args)); + loadFromIterable(cx, s, nm, key(args)); } + ScriptRuntime.setBuiltinProtoAndParent(nm, f, nt, s, TopLevel.Builtins.Map); return nm; } - private static Object jsGroupBy(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object jsGroupBy( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { Object items = args.length < 1 ? Undefined.instance : args[0]; Object callback = args.length < 2 ? Undefined.instance : args[1]; Map> groups = AbstractEcmaObjectOperations.groupBy( cx, - scope, + s, CLASS_NAME, "groupBy", items, callback, AbstractEcmaObjectOperations.KEY_COERCION.COLLECTION); - NativeMap map = (NativeMap) cx.newObject(scope, "Map"); + NativeMap map = (NativeMap) cx.newObject(s, "Map"); for (Map.Entry> entry : groups.entrySet()) { - Scriptable elements = cx.newArray(scope, entry.getValue().toArray()); + Scriptable elements = cx.newArray(s, entry.getValue().toArray()); map.entries.put(entry.getKey(), elements); } return map; } - private static Object js_set(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_set( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "set") .js_set(key(args), args.length > 1 ? args[1] : Undefined.instance); } @@ -123,7 +133,8 @@ private Object js_set(Object k, Object v) { return this; } - private static Object js_delete(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_delete( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "delete").js_delete(key(args)); } @@ -131,7 +142,8 @@ private Object js_delete(Object arg) { return entries.deleteEntry(arg); } - private static Object js_get(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_get( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "get").js_get(key(args)); } @@ -143,7 +155,8 @@ private Object js_get(Object arg) { return entry.value; } - private static Object js_has(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_has( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "has").js_has(key(args)); } @@ -155,23 +168,30 @@ private Object js_getSize() { return entries.size(); } - private static Object js_keys(Context cx, VarScope scope, Object thisObj, Object[] args) { - return realThis(thisObj, "keys").js_iterator(scope, NativeCollectionIterator.Type.KEYS); + private static Object js_keys( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return realThis(thisObj, "keys") + .js_iterator(f.getDeclarationScope(), NativeCollectionIterator.Type.KEYS); } - private static Object js_values(Context cx, VarScope scope, Object thisObj, Object[] args) { - return realThis(thisObj, "values").js_iterator(scope, NativeCollectionIterator.Type.VALUES); + private static Object js_values( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return realThis(thisObj, "values") + .js_iterator(f.getDeclarationScope(), NativeCollectionIterator.Type.VALUES); } - private static Object js_entries(Context cx, VarScope scope, Object thisObj, Object[] args) { - return realThis(thisObj, "entries").js_iterator(scope, NativeCollectionIterator.Type.BOTH); + private static Object js_entries( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return realThis(thisObj, "entries") + .js_iterator(f.getDeclarationScope(), NativeCollectionIterator.Type.BOTH); } private Object js_iterator(VarScope scope, NativeCollectionIterator.Type type) { return new NativeCollectionIterator(scope, ITERATOR_TAG, type, entries.iterator()); } - private static Object js_clear(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_clear( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "clear").js_clear(); } @@ -181,11 +201,11 @@ private Object js_clear() { } private static Object js_forEach( - Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "forEach") .js_forEach( cx, - scope, + s, args.length > 0 ? args[0] : Undefined.instance, args.length > 1 ? args[1] : Undefined.instance); } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeMath.java b/rhino/src/main/java/org/mozilla/javascript/NativeMath.java index c20590ffbef..c1b732e2304 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeMath.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeMath.java @@ -6,6 +6,9 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Builder.value; +import static org.mozilla.javascript.ClassDescriptor.Destination.CTOR; + /** * This class implements the Math native object. See ECMA 15.8. * @@ -18,64 +21,62 @@ final class NativeMath extends ScriptableObject { private static final double LOG2E = 1.4426950408889634; private static final Double Double32 = Double.valueOf(32d); + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder(MATH_TAG) + .withProp(CTOR, "toSource", value("Math")) + .withMethod(CTOR, "abs", 1, NativeMath::abs) + .withMethod(CTOR, "acos", 1, NativeMath::acos) + .withMethod(CTOR, "acosh", 1, NativeMath::acosh) + .withMethod(CTOR, "asin", 1, NativeMath::asin) + .withMethod(CTOR, "asinh", 1, NativeMath::asinh) + .withMethod(CTOR, "atan", 1, NativeMath::atan) + .withMethod(CTOR, "atanh", 1, NativeMath::atanh) + .withMethod(CTOR, "atan2", 2, NativeMath::atan2) + .withMethod(CTOR, "cbrt", 1, NativeMath::cbrt) + .withMethod(CTOR, "ceil", 1, NativeMath::ceil) + .withMethod(CTOR, "clz32", 1, NativeMath::clz32) + .withMethod(CTOR, "cos", 1, NativeMath::cos) + .withMethod(CTOR, "cosh", 1, NativeMath::cosh) + .withMethod(CTOR, "exp", 1, NativeMath::exp) + .withMethod(CTOR, "expm1", 1, NativeMath::expm1) + .withMethod(CTOR, "f16round", 1, NativeMath::f16round) + .withMethod(CTOR, "floor", 1, NativeMath::floor) + .withMethod(CTOR, "fround", 1, NativeMath::fround) + .withMethod(CTOR, "hypot", 2, NativeMath::hypot) + .withMethod(CTOR, "imul", 2, NativeMath::imul) + .withMethod(CTOR, "log", 1, NativeMath::log) + .withMethod(CTOR, "log1p", 1, NativeMath::log1p) + .withMethod(CTOR, "log10", 1, NativeMath::log10) + .withMethod(CTOR, "log2", 1, NativeMath::log2) + .withMethod(CTOR, "max", 2, NativeMath::max) + .withMethod(CTOR, "min", 2, NativeMath::min) + .withMethod(CTOR, "pow", 2, NativeMath::pow) + .withMethod(CTOR, "random", 0, NativeMath::random) + .withMethod(CTOR, "round", 1, NativeMath::round) + .withMethod(CTOR, "sign", 1, NativeMath::sign) + .withMethod(CTOR, "sin", 1, NativeMath::sin) + .withMethod(CTOR, "sinh", 1, NativeMath::sinh) + .withMethod(CTOR, "sqrt", 1, NativeMath::sqrt) + .withMethod(CTOR, "tan", 1, NativeMath::tan) + .withMethod(CTOR, "tanh", 1, NativeMath::tanh) + .withMethod(CTOR, "trunc", 1, NativeMath::trunc) + .withProp(CTOR, "E", value(Math.E)) + .withProp(CTOR, "PI", value(Math.PI)) + .withProp(CTOR, "LN10", value(2.302585092994046)) + .withProp(CTOR, "LN2", value(0.6931471805599453)) + .withProp(CTOR, "LOG2E", value(LOG2E)) + .withProp(CTOR, "LOG10E", value(0.4342944819032518)) + .withProp(CTOR, "SQRT1_2", value(0.7071067811865476)) + .withProp(CTOR, "SQRT2", value(1.4142135623730951)) + .withProp(CTOR, SymbolKey.TO_STRING_TAG, value("Math", DONTENUM | READONLY)) + .build(); + } + static Object init(Context cx, VarScope scope, boolean sealed) { - NativeMath math = new NativeMath(); - math.setPrototype(getObjectPrototype(scope)); - math.setParentScope(scope); - - math.defineProperty("toSource", "Math", DONTENUM | READONLY | PERMANENT); - - math.defineBuiltinProperty(scope, "abs", 1, NativeMath::abs); - math.defineBuiltinProperty(scope, "acos", 1, NativeMath::acos); - math.defineBuiltinProperty(scope, "acosh", 1, NativeMath::acosh); - math.defineBuiltinProperty(scope, "asin", 1, NativeMath::asin); - math.defineBuiltinProperty(scope, "asinh", 1, NativeMath::asinh); - math.defineBuiltinProperty(scope, "atan", 1, NativeMath::atan); - math.defineBuiltinProperty(scope, "atanh", 1, NativeMath::atanh); - math.defineBuiltinProperty(scope, "atan2", 2, NativeMath::atan2); - math.defineBuiltinProperty(scope, "cbrt", 1, NativeMath::cbrt); - math.defineBuiltinProperty(scope, "ceil", 1, NativeMath::ceil); - math.defineBuiltinProperty(scope, "clz32", 1, NativeMath::clz32); - math.defineBuiltinProperty(scope, "cos", 1, NativeMath::cos); - math.defineBuiltinProperty(scope, "cosh", 1, NativeMath::cosh); - math.defineBuiltinProperty(scope, "exp", 1, NativeMath::exp); - math.defineBuiltinProperty(scope, "expm1", 1, NativeMath::expm1); - math.defineBuiltinProperty(scope, "f16round", 1, NativeMath::f16round); - math.defineBuiltinProperty(scope, "floor", 1, NativeMath::floor); - math.defineBuiltinProperty(scope, "fround", 1, NativeMath::fround); - math.defineBuiltinProperty(scope, "hypot", 2, NativeMath::hypot); - math.defineBuiltinProperty(scope, "imul", 2, NativeMath::imul); - math.defineBuiltinProperty(scope, "log", 1, NativeMath::log); - math.defineBuiltinProperty(scope, "log1p", 1, NativeMath::log1p); - math.defineBuiltinProperty(scope, "log10", 1, NativeMath::log10); - math.defineBuiltinProperty(scope, "log2", 1, NativeMath::log2); - math.defineBuiltinProperty(scope, "max", 2, NativeMath::max); - math.defineBuiltinProperty(scope, "min", 2, NativeMath::min); - math.defineBuiltinProperty(scope, "pow", 2, NativeMath::pow); - math.defineBuiltinProperty(scope, "random", 0, NativeMath::random); - math.defineBuiltinProperty(scope, "round", 1, NativeMath::round); - math.defineBuiltinProperty(scope, "sign", 1, NativeMath::sign); - math.defineBuiltinProperty(scope, "sin", 1, NativeMath::sin); - math.defineBuiltinProperty(scope, "sinh", 1, NativeMath::sinh); - math.defineBuiltinProperty(scope, "sqrt", 1, NativeMath::sqrt); - math.defineBuiltinProperty(scope, "tan", 1, NativeMath::tan); - math.defineBuiltinProperty(scope, "tanh", 1, NativeMath::tanh); - math.defineBuiltinProperty(scope, "trunc", 1, NativeMath::trunc); - - math.defineProperty("E", Math.E, DONTENUM | READONLY | PERMANENT); - math.defineProperty("PI", Math.PI, DONTENUM | READONLY | PERMANENT); - math.defineProperty("LN10", 2.302585092994046, DONTENUM | READONLY | PERMANENT); - math.defineProperty("LN2", 0.6931471805599453, DONTENUM | READONLY | PERMANENT); - math.defineProperty("LOG2E", LOG2E, DONTENUM | READONLY | PERMANENT); - math.defineProperty("LOG10E", 0.4342944819032518, DONTENUM | READONLY | PERMANENT); - math.defineProperty("SQRT1_2", 0.7071067811865476, DONTENUM | READONLY | PERMANENT); - math.defineProperty("SQRT2", 1.4142135623730951, DONTENUM | READONLY | PERMANENT); - - math.defineProperty(SymbolKey.TO_STRING_TAG, MATH_TAG, DONTENUM | READONLY); - if (sealed) { - math.sealObject(); - } - return math; + return DESCRIPTOR.populateGlobal(cx, scope, new NativeMath(), sealed); } private NativeMath() {} @@ -85,7 +86,8 @@ public String getClassName() { return "Math"; } - private static Object abs(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object abs( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); // abs(-0.0) should be 0.0, but -0.0 < 0.0 == false x = (x == 0.0) ? 0.0 : (x < 0.0) ? -x : x; @@ -93,7 +95,8 @@ private static Object abs(Context cx, VarScope scope, Object thisObj, Object[] a return ScriptRuntime.wrapNumber(x); } - private static Object acos(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object acos( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); if (!Double.isNaN(x) && -1.0 <= x && x <= 1.0) { x = Math.acos(x); @@ -103,7 +106,8 @@ private static Object acos(Context cx, VarScope scope, Object thisObj, Object[] return ScriptRuntime.wrapNumber(x); } - private static Object acosh(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object acosh( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); if (!Double.isNaN(x)) { return Double.valueOf(Math.log(x + Math.sqrt(x * x - 1.0))); @@ -111,7 +115,8 @@ private static Object acosh(Context cx, VarScope scope, Object thisObj, Object[] return ScriptRuntime.NaNobj; } - private static Object asin(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object asin( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); if (!Double.isNaN(x) && -1.0 <= x && x <= 1.0) { x = Math.asin(x); @@ -121,7 +126,8 @@ private static Object asin(Context cx, VarScope scope, Object thisObj, Object[] return ScriptRuntime.wrapNumber(x); } - private static Object asinh(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object asinh( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); if (Double.isInfinite(x)) { return Double.valueOf(x); @@ -138,13 +144,15 @@ private static Object asinh(Context cx, VarScope scope, Object thisObj, Object[] return ScriptRuntime.NaNobj; } - private static Object atan(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object atan( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.atan(x); return ScriptRuntime.wrapNumber(x); } - private static Object atanh(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object atanh( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); if (!Double.isNaN(x) && -1.0 <= x && x <= 1.0) { if (x == 0) { @@ -158,25 +166,29 @@ private static Object atanh(Context cx, VarScope scope, Object thisObj, Object[] return ScriptRuntime.NaNobj; } - private static Object atan2(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object atan2( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.atan2(x, ScriptRuntime.toNumber(args, 1)); return ScriptRuntime.wrapNumber(x); } - private static Object cbrt(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object cbrt( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.cbrt(x); return ScriptRuntime.wrapNumber(x); } - private static Object ceil(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object ceil( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.ceil(x); return ScriptRuntime.wrapNumber(x); } - private static Object clz32(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object clz32( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); if (x == 0 || Double.isNaN(x) || Double.isInfinite(x)) { return Double32; @@ -214,19 +226,22 @@ private static Object clz32(Context cx, VarScope scope, Object thisObj, Object[] return Double.valueOf(32 - place); } - private static Object cos(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object cos( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Double.isInfinite(x) ? Double.NaN : Math.cos(x); return ScriptRuntime.wrapNumber(x); } - private static Object cosh(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object cosh( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.cosh(x); return ScriptRuntime.wrapNumber(x); } - private static Object exp(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object exp( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = (x == Double.POSITIVE_INFINITY) @@ -235,19 +250,22 @@ private static Object exp(Context cx, VarScope scope, Object thisObj, Object[] a return ScriptRuntime.wrapNumber(x); } - private static Object expm1(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object expm1( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.expm1(x); return ScriptRuntime.wrapNumber(x); } - private static Object floor(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object floor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.floor(x); return ScriptRuntime.wrapNumber(x); } - private static Object f16round(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object f16round( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { // Handle missing arguments if (args.length == 0) { return ScriptRuntime.NaNobj; @@ -392,7 +410,8 @@ private static Object reconstructNormalF16(int sign, int exponent, long mantissa return ScriptRuntime.wrapNumber(Double.longBitsToDouble(resultBits)); } - private static Object fround(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object fround( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); // Rely on Java to truncate down to a "float" here" float fx = (float) x; @@ -401,7 +420,8 @@ private static Object fround(Context cx, VarScope scope, Object thisObj, Object[ // Based on code from // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot - private static Object hypot(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object hypot( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (args == null) { return 0.0; } @@ -431,7 +451,8 @@ private static Object hypot(Context cx, VarScope scope, Object thisObj, Object[] return Math.sqrt(y); } - private static Object imul(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object imul( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (args == null) { return 0; } @@ -442,33 +463,38 @@ private static Object imul(Context cx, VarScope scope, Object thisObj, Object[] return ScriptRuntime.wrapNumber(x * y); } - private static Object log(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object log( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); // Java's log(<0) = -Infinity; we need NaN x = (x < 0) ? Double.NaN : Math.log(x); return ScriptRuntime.wrapNumber(x); } - private static Object log1p(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object log1p( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.log1p(x); return ScriptRuntime.wrapNumber(x); } - private static Object log10(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object log10( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.log10(x); return ScriptRuntime.wrapNumber(x); } - private static Object log2(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object log2( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); // Java's log(<0) = -Infinity; we need NaN x = (x < 0) ? Double.NaN : Math.log(x) * LOG2E; return ScriptRuntime.wrapNumber(x); } - private static Object max(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object max( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = Double.NEGATIVE_INFINITY; for (int i = 0; i != args.length; ++i) { double d = ScriptRuntime.toNumber(args[i]); @@ -478,7 +504,8 @@ private static Object max(Context cx, VarScope scope, Object thisObj, Object[] a return ScriptRuntime.wrapNumber(x); } - private static Object min(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object min( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = Double.POSITIVE_INFINITY; for (int i = 0; i != args.length; ++i) { double d = ScriptRuntime.toNumber(args[i]); @@ -489,7 +516,8 @@ private static Object min(Context cx, VarScope scope, Object thisObj, Object[] a } // See Ecma 15.8.2.13 - private static Object pow(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object pow( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); double y = ScriptRuntime.toNumber(args, 1); double result; @@ -545,11 +573,13 @@ private static Object pow(Context cx, VarScope scope, Object thisObj, Object[] a return ScriptRuntime.wrapNumber(result); } - private static Object random(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object random( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return ScriptRuntime.wrapNumber(Math.random()); } - private static Object round(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object round( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); if (!Double.isNaN(x) && !Double.isInfinite(x)) { // Round only finite x @@ -568,7 +598,8 @@ private static Object round(Context cx, VarScope scope, Object thisObj, Object[] return ScriptRuntime.wrapNumber(x); } - private static Object sign(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object sign( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); if (!Double.isNaN(x)) { if (x == 0) { @@ -582,37 +613,43 @@ private static Object sign(Context cx, VarScope scope, Object thisObj, Object[] return ScriptRuntime.NaNobj; } - private static Object sin(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object sin( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Double.isInfinite(x) ? Double.NaN : Math.sin(x); return ScriptRuntime.wrapNumber(x); } - private static Object sinh(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object sinh( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.sinh(x); return ScriptRuntime.wrapNumber(x); } - private static Object sqrt(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object sqrt( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.sqrt(x); return ScriptRuntime.wrapNumber(x); } - private static Object tan(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object tan( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.tan(x); return ScriptRuntime.wrapNumber(x); } - private static Object tanh(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object tanh( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = Math.tanh(x); return ScriptRuntime.wrapNumber(x); } - private static Object trunc(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object trunc( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double x = ScriptRuntime.toNumber(args, 0); x = ((x < 0.0) ? Math.ceil(x) : Math.floor(x)); return ScriptRuntime.wrapNumber(x); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeNumber.java b/rhino/src/main/java/org/mozilla/javascript/NativeNumber.java index b5e4f3899d0..ffdf5edb0bb 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeNumber.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeNumber.java @@ -6,6 +6,11 @@ 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; +import static org.mozilla.javascript.ScriptRuntime.wrapNumber; + import java.text.NumberFormat; import java.util.IllformedLocaleException; import java.util.Locale; @@ -34,87 +39,57 @@ final class NativeNumber extends ScriptableObject { private static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; private static final double EPSILON = 2.220446049250313e-16; - private final double doubleValue; + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + CLASS_NAME, + 1, + NativeNumber::js_constructorFunc, + NativeNumber::js_constructor) + .withProp(CTOR, "NaN", value(ScriptRuntime.NaNobj)) + .withProp( + CTOR, + "POSITIVE_INFINITY", + value(wrapNumber(Double.POSITIVE_INFINITY))) + .withProp( + CTOR, + "NEGATIVE_INFINITY", + value(wrapNumber(Double.NEGATIVE_INFINITY))) + .withProp(CTOR, "MAX_VALUE", value(wrapNumber(Double.MAX_VALUE))) + .withProp(CTOR, "MIN_VALUE", value(wrapNumber(Double.MIN_VALUE))) + .withProp(CTOR, "MAX_SAFE_INTEGER", value(wrapNumber(MAX_SAFE_INTEGER))) + .withProp(CTOR, "MIN_SAFE_INTEGER", value(wrapNumber(MIN_SAFE_INTEGER))) + .withProp(CTOR, "EPSILON", value(wrapNumber(EPSILON))) + .withMethod(CTOR, "isFinite", 1, NativeNumber::js_isFinite) + .withMethod(CTOR, "isNaN", 1, NativeNumber::js_isNaN) + .withMethod(CTOR, "isInteger", 1, NativeNumber::js_isInteger) + .withMethod(CTOR, "isSafeInteger", 1, NativeNumber::js_isSafeInteger) + .withProp( + CTOR, + "parseFloat", + (c, s, o) -> + new DescriptorInfo(s.get("parseFloat", s), DONTENUM, true)) + .withProp( + CTOR, + "parseInt", + (c, s, o) -> + new DescriptorInfo(s.get("parseInt", s), DONTENUM, true)) + .withMethod(PROTO, "toString", 1, NativeNumber::js_toString) + .withMethod(PROTO, "toLocaleString", 0, NativeNumber::js_toLocaleString) + .withMethod(PROTO, "toSource", 0, NativeNumber::js_toSource) + .withMethod(PROTO, "valueOf", 0, NativeNumber::js_valueOf) + .withMethod(PROTO, "toFixed", 1, NativeNumber::js_toFixed) + .withMethod(PROTO, "toExponential", 1, NativeNumber::js_toExponential) + .withMethod(PROTO, "toPrecision", 1, NativeNumber::js_toPrecision) + .build(); + } - static void init(VarScope scope, boolean sealed) { - LambdaConstructor constructor = - new LambdaConstructor( - scope, - CLASS_NAME, - 1, - NativeNumber::js_constructorFunc, - NativeNumber::js_constructor); - constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); - constructor.setPrototypeScriptable(new NativeNumber(0.0)); - - final int propAttr = DONTENUM | PERMANENT | READONLY; - - constructor.defineProperty("NaN", ScriptRuntime.NaNobj, propAttr); - constructor.defineProperty( - "POSITIVE_INFINITY", ScriptRuntime.wrapNumber(Double.POSITIVE_INFINITY), propAttr); - constructor.defineProperty( - "NEGATIVE_INFINITY", ScriptRuntime.wrapNumber(Double.NEGATIVE_INFINITY), propAttr); - constructor.defineProperty( - "MAX_VALUE", ScriptRuntime.wrapNumber(Double.MAX_VALUE), propAttr); - constructor.defineProperty( - "MIN_VALUE", ScriptRuntime.wrapNumber(Double.MIN_VALUE), propAttr); - constructor.defineProperty( - "MAX_SAFE_INTEGER", ScriptRuntime.wrapNumber(MAX_SAFE_INTEGER), propAttr); - constructor.defineProperty( - "MIN_SAFE_INTEGER", ScriptRuntime.wrapNumber(MIN_SAFE_INTEGER), propAttr); - constructor.defineProperty("EPSILON", ScriptRuntime.wrapNumber(EPSILON), propAttr); - - constructor.defineConstructorMethod( - scope, - "isFinite", - 1, - null, - NativeNumber::js_isFinite, - DONTENUM, - DONTENUM | READONLY); - constructor.defineConstructorMethod( - scope, "isNaN", 1, null, NativeNumber::js_isNaN, DONTENUM, DONTENUM | READONLY); - constructor.defineConstructorMethod( - scope, - "isInteger", - 1, - null, - NativeNumber::js_isInteger, - DONTENUM, - DONTENUM | READONLY); - constructor.defineConstructorMethod( - scope, - "isSafeInteger", - 1, - null, - NativeNumber::js_isSafeInteger, - DONTENUM, - DONTENUM | READONLY); - - Object parseFloat = ScriptRuntime.getTopLevelProp(scope, "parseFloat"); - if (parseFloat instanceof Function) { - constructor.defineProperty("parseFloat", parseFloat, DONTENUM); - } - Object parseInt = ScriptRuntime.getTopLevelProp(scope, "parseInt"); - if (parseInt instanceof Function) { - constructor.defineProperty("parseInt", parseInt, DONTENUM); - } + private final double doubleValue; - constructor.definePrototypeMethod(scope, "toString", 1, NativeNumber::js_toString); - constructor.definePrototypeMethod( - scope, "toLocaleString", 0, NativeNumber::js_toLocaleString); - constructor.definePrototypeMethod(scope, "toSource", 0, NativeNumber::js_toSource); - constructor.definePrototypeMethod(scope, "valueOf", 0, NativeNumber::js_valueOf); - constructor.definePrototypeMethod(scope, "toFixed", 1, NativeNumber::js_toFixed); - constructor.definePrototypeMethod( - scope, "toExponential", 1, NativeNumber::js_toExponential); - constructor.definePrototypeMethod(scope, "toPrecision", 1, NativeNumber::js_toPrecision); - - ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, DONTENUM); - if (sealed) { - constructor.sealObject(); - ((ScriptableObject) constructor.getPrototypeProperty()).sealObject(); - } + static void init(Context cx, VarScope scope, boolean sealed) { + DESCRIPTOR.buildConstructor(cx, scope, new NativeNumber(0.0), sealed); } NativeNumber(double number) { @@ -126,21 +101,26 @@ public String getClassName() { return CLASS_NAME; } - private static Scriptable js_constructor(Context cx, VarScope scope, Object[] args) { + private static Scriptable js_constructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double val = (args.length > 0) ? ScriptRuntime.toNumeric(args[0]).doubleValue() : 0.0; - return new NativeNumber(val); + var res = new NativeNumber(val); + ScriptRuntime.setBuiltinProtoAndParent(res, f, nt, s, TopLevel.Builtins.Number); + return res; } private static Object js_constructorFunc( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return (args.length > 0) ? ScriptRuntime.toNumeric(args[0]).doubleValue() : 0.0; } - private static Object js_valueOf(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_valueOf( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return toSelf(thisObj).doubleValue; } - private static Object js_toFixed(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_toFixed( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double value = toSelf(thisObj).doubleValue; int fractionDigits; @@ -162,7 +142,7 @@ private static Object js_toFixed(Context cx, VarScope scope, Object thisObj, Obj } private static Object js_toExponential( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double value = toSelf(thisObj).doubleValue; double p; @@ -188,7 +168,7 @@ private static Object js_toExponential( } private static Object js_toPrecision( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { double value = toSelf(thisObj).doubleValue; // Undefined precision, fall back to ToString() if (args.length == 0 || Undefined.isUndefined(args[0])) { @@ -217,7 +197,8 @@ private static NativeNumber toSelf(Object thisObj) { return LambdaConstructor.convertThisObject(thisObj, NativeNumber.class); } - 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) { int base = (args.length == 0 || Undefined.isUndefined(args[0])) ? 10 @@ -226,9 +207,9 @@ private static Object js_toString(Context cx, VarScope scope, Object thisObj, Ob } private static Object js_toLocaleString( - Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (!cx.hasFeature(Context.FEATURE_INTL_402)) { - return js_toString(cx, scope, thisObj, ScriptRuntime.emptyArgs); + return js_toString(cx, f, nt, s, thisObj, ScriptRuntime.emptyArgs); } if (args.length != 0 && args[0] instanceof String) { @@ -245,7 +226,7 @@ private static Object js_toLocaleString( } private static Object js_toSource( - Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return "(new Number(" + ScriptRuntime.toString(toSelf(thisObj).doubleValue) + "))"; } @@ -263,7 +244,8 @@ public String toString() { return ScriptRuntime.numberToString(doubleValue, 10); } - private static Object js_isFinite(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_isFinite( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { Number n = argToNumber(args); return n == null ? Boolean.FALSE : isFinite(n); } @@ -273,7 +255,8 @@ static Object isFinite(Object val) { return Double.isFinite(nd); } - private static Object js_isNaN(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_isNaN( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { Number val = argToNumber(args); if (val == null) { return false; @@ -285,7 +268,8 @@ private static Object js_isNaN(Context cx, VarScope scope, Object thisObj, Objec return Double.isNaN(d); } - private static Object js_isInteger(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_isInteger( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { Number val = argToNumber(args); if (val == null) { return false; @@ -297,7 +281,7 @@ private static Object js_isInteger(Context cx, VarScope scope, Object thisObj, O } private static Object js_isSafeInteger( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { Number val = argToNumber(args); if (val == null) { return false; diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeObject.java b/rhino/src/main/java/org/mozilla/javascript/NativeObject.java index 68063000b34..cc06e45c064 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeObject.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeObject.java @@ -96,7 +96,7 @@ public class NativeObject extends ScriptableObject implements Map { static JSFunction init(Context cx, VarScope s, boolean sealed) { var desc = cx.version >= Context.VERSION_ES6 ? ES6_DESCRIPTOR : LEGACY_DESCRIPTOR; - return desc.buildConstructor(cx, (VarScope) s, new NativeObject(), sealed); + return desc.buildConstructor(cx, s, new NativeObject(), sealed); } @Override diff --git a/rhino/src/main/java/org/mozilla/javascript/NativePromise.java b/rhino/src/main/java/org/mozilla/javascript/NativePromise.java index 368a733c758..44b6eb80323 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativePromise.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativePromise.java @@ -4,6 +4,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; + import java.util.ArrayList; import org.mozilla.javascript.TopLevel.NativeErrors; @@ -20,6 +24,34 @@ enum ReactionType { REJECT } + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + "Promise", + 1, + ClassDescriptor.typeError(), + NativePromise::constructor) + .withMethod(CTOR, "resolve", 1, NativePromise::resolve) + .withMethod(CTOR, "reject", 1, NativePromise::reject) + .withMethod(CTOR, "all", 1, NativePromise::all) + .withMethod(CTOR, "allSettled", 1, NativePromise::allSettled) + .withMethod(CTOR, "race", 1, NativePromise::race) + .withMethod(CTOR, "any", 1, NativePromise::any) + .withMethod(CTOR, "withResolvers", 0, NativePromise::withResolvers) + .withMethod(CTOR, "try", 1, NativePromise::promiseTry) + .withMethod(PROTO, "then", 2, NativePromise::doThen) + .withMethod(PROTO, "catch", 1, NativePromise::doCatch) + .withMethod(PROTO, "finally", 1, NativePromise::doFinally) + .withProp( + PROTO, + SymbolKey.TO_STRING_TAG, + value("Promise", DONTENUM | READONLY)) + .withProp(CTOR, SymbolKey.SPECIES, ScriptRuntimeES6::symbolSpecies) + .build(); + } + private State state = State.PENDING; private Object result = null; private boolean handled = false; @@ -28,49 +60,19 @@ enum ReactionType { private ArrayList rejectReactions = new ArrayList<>(); public static Object init(Context cx, VarScope scope, boolean sealed) { - LambdaConstructor constructor = - new LambdaConstructor( - scope, - "Promise", - 1, - LambdaConstructor.CONSTRUCTOR_NEW, - NativePromise::constructor); - constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); - - constructor.defineConstructorMethod(scope, "resolve", 1, NativePromise::resolve); - constructor.defineConstructorMethod(scope, "reject", 1, NativePromise::reject); - constructor.defineConstructorMethod(scope, "all", 1, NativePromise::all); - constructor.defineConstructorMethod(scope, "allSettled", 1, NativePromise::allSettled); - constructor.defineConstructorMethod(scope, "race", 1, NativePromise::race); - constructor.defineConstructorMethod(scope, "any", 1, NativePromise::any); - constructor.defineConstructorMethod( - scope, "withResolvers", 0, NativePromise::withResolvers); - constructor.defineConstructorMethod(scope, "try", 1, NativePromise::promiseTry); - - ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor); - - constructor.definePrototypeMethod(scope, "then", 2, NativePromise::doThen); - constructor.definePrototypeMethod(scope, "catch", 1, NativePromise::doCatch); - constructor.definePrototypeMethod(scope, "finally", 1, NativePromise::doFinally); - - constructor.definePrototypeProperty( - SymbolKey.TO_STRING_TAG, "Promise", DONTENUM | READONLY); - if (sealed) { - constructor.sealObject(); - ((ScriptableObject) constructor.getPrototypeProperty()).sealObject(); - } - return constructor; - } - - private static Scriptable constructor(Context cx, VarScope scope, Object[] args) { + return DESCRIPTOR.buildConstructor(cx, scope, new NativeObject(), sealed); + } + + private static Scriptable constructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (args.length < 1 || !(args[0] instanceof Callable)) { throw ScriptRuntime.typeErrorById("msg.function.expected"); } Callable executor = (Callable) args[0]; NativePromise promise = new NativePromise(); - ResolvingFunctions resolving = new ResolvingFunctions(scope, promise); + ResolvingFunctions resolving = new ResolvingFunctions(f.getDeclarationScope(), promise); - Scriptable thisObj = Undefined.SCRIPTABLE_UNDEFINED; + thisObj = Undefined.SCRIPTABLE_UNDEFINED; if (!cx.isStrictMode()) { TopLevel tcs = cx.topCallScope; if (tcs != null) { @@ -78,12 +80,16 @@ private static Scriptable constructor(Context cx, VarScope scope, Object[] args) } } + var thisArg = (Scriptable) thisObj; + try { - executor.call(cx, scope, thisObj, new Object[] {resolving.resolve, resolving.reject}); + executor.call(cx, s, thisArg, new Object[] {resolving.resolve, resolving.reject}); } catch (RhinoException re) { - resolving.reject.call(cx, scope, thisObj, new Object[] {getErrorObject(cx, scope, re)}); + resolving.reject.call( + cx, s, thisArg, new Object[] {getErrorObject(cx, f.getDeclarationScope(), re)}); } + ScriptRuntime.setBuiltinProtoAndParent(promise, f, nt, s, TopLevel.Builtins.Promise); return promise; } @@ -97,17 +103,17 @@ Object getResult() { } // Promise.resolve - private static Object resolve(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + private static Object resolve( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (!ScriptRuntime.isObject(thisObj)) { throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(thisObj)); } Object arg = (args.length > 0 ? args[0] : Undefined.instance); - return resolveInternal(cx, scope, thisObj, arg); + return resolveInternal(cx, s, thisObj, arg); } // PromiseResolve abstract operation - private static Object resolveInternal( - Context cx, VarScope scope, Object constructor, Object arg) { + static Object resolveInternal(Context cx, VarScope scope, Object constructor, Object arg) { if (arg instanceof NativePromise) { Object argConstructor = ScriptRuntime.getObjectProp(arg, "constructor", cx, scope); if (argConstructor == constructor) { @@ -120,18 +126,20 @@ private static Object resolveInternal( } // Promise.reject - private static Object reject(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + private static Object reject( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (!ScriptRuntime.isObject(thisObj)) { throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(thisObj)); } Object arg = (args.length > 0 ? args[0] : Undefined.instance); - Capability cap = new Capability(cx, scope, thisObj); - cap.reject.call(cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {arg}); + Capability cap = new Capability(cx, f.getDeclarationScope(), thisObj); + cap.reject.call( + cx, f.getDeclarationScope(), Undefined.SCRIPTABLE_UNDEFINED, new Object[] {arg}); return cap.promise; } private static Object doAll( - Context cx, VarScope scope, Scriptable thisObj, Object[] args, boolean failFast) { + Context cx, VarScope scope, Object thisObj, Object[] args, boolean failFast) { Capability cap = new Capability(cx, scope, thisObj); Object arg = (args.length > 0 ? args[0] : Undefined.instance); @@ -150,7 +158,8 @@ private static Object doAll( IteratorLikeIterable.Itr iterator = iterable.iterator(); try { - PromiseAllResolver resolver = new PromiseAllResolver(iterator, thisObj, cap, failFast); + PromiseAllResolver resolver = + new PromiseAllResolver(iterator, (Scriptable) thisObj, cap, failFast); try { return resolver.resolve(cx, scope); } finally { @@ -169,38 +178,41 @@ private static Object doAll( } // Promise.all - private static Object all(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { - return doAll(cx, scope, thisObj, args, true); + private static Object all( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return doAll(cx, f.getDeclarationScope(), thisObj, args, true); } // Promise.allSettled private static Object allSettled( - Context cx, VarScope scope, Scriptable thisObj, Object[] args) { - return doAll(cx, scope, thisObj, args, false); + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return doAll(cx, f.getDeclarationScope(), thisObj, args, false); } // Promise.race - private static Object race(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { - Capability cap = new Capability(cx, scope, thisObj); + private static Object race( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + var fscope = f.getDeclarationScope(); + Capability cap = new Capability(cx, fscope, thisObj); Object arg = (args.length > 0 ? args[0] : Undefined.instance); IteratorLikeIterable iterable; try { - Object maybeIterable = ScriptRuntime.callIterator(arg, cx, scope); - iterable = new IteratorLikeIterable(cx, scope, maybeIterable); + Object maybeIterable = ScriptRuntime.callIterator(arg, cx, f.getDeclarationScope()); + iterable = new IteratorLikeIterable(cx, fscope, maybeIterable); } catch (RhinoException re) { cap.reject.call( cx, - scope, + fscope, Undefined.SCRIPTABLE_UNDEFINED, - new Object[] {getErrorObject(cx, scope, re)}); + new Object[] {getErrorObject(cx, fscope, re)}); return cap.promise; } IteratorLikeIterable.Itr iterator = iterable.iterator(); try { try { - return performRace(cx, scope, iterator, thisObj, cap); + return performRace(cx, fscope, iterator, thisObj, cap); } finally { if (!iterator.isDone()) { iterable.close(); @@ -209,9 +221,9 @@ private static Object race(Context cx, VarScope scope, Scriptable thisObj, Objec } catch (RhinoException re) { cap.reject.call( cx, - scope, + fscope, Undefined.SCRIPTABLE_UNDEFINED, - new Object[] {getErrorObject(cx, scope, re)}); + new Object[] {getErrorObject(cx, fscope, re)}); return cap.promise; } } @@ -220,7 +232,7 @@ private static Object performRace( Context cx, VarScope scope, IteratorLikeIterable.Itr iterator, - Scriptable thisObj, + Object thisObj, Capability cap) { var resolve = ScriptRuntime.getPropAndThis(thisObj, "resolve", cx, scope); @@ -255,28 +267,31 @@ private static Object performRace( } } - private static Object any(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { - Capability cap = new Capability(cx, scope, thisObj); + private static Object any( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + var fscope = f.getDeclarationScope(); + Capability cap = new Capability(cx, fscope, thisObj); Object arg = (args.length > 0 ? args[0] : Undefined.instance); IteratorLikeIterable iterable; try { - Object maybeIterable = ScriptRuntime.callIterator(arg, cx, scope); - iterable = new IteratorLikeIterable(cx, scope, maybeIterable); + Object maybeIterable = ScriptRuntime.callIterator(arg, cx, fscope); + iterable = new IteratorLikeIterable(cx, fscope, maybeIterable); } catch (RhinoException re) { cap.reject.call( cx, - scope, + fscope, Undefined.SCRIPTABLE_UNDEFINED, - new Object[] {getErrorObject(cx, scope, re)}); + new Object[] {getErrorObject(cx, fscope, re)}); return cap.promise; } IteratorLikeIterable.Itr iterator = iterable.iterator(); try { - PromiseAnyRejector rejector = new PromiseAnyRejector(iterator, thisObj, cap); + PromiseAnyRejector rejector = + new PromiseAnyRejector(iterator, (Scriptable) thisObj, cap); try { - return rejector.reject(cx, scope); + return rejector.reject(cx, fscope); } finally { if (!iterator.isDone()) { iterable.close(); @@ -285,25 +300,26 @@ private static Object any(Context cx, VarScope scope, Scriptable thisObj, Object } catch (RhinoException re) { cap.reject.call( cx, - scope, + fscope, Undefined.SCRIPTABLE_UNDEFINED, - new Object[] {getErrorObject(cx, scope, re)}); + new Object[] {getErrorObject(cx, fscope, re)}); return cap.promise; } } // Promise.withResolvers private static Object withResolvers( - Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + var fscope = f.getDeclarationScope(); if (!ScriptRuntime.isObject(thisObj)) { throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(thisObj)); } // Create a capability which properly constructs a promise with resolve/reject functions - Capability cap = new Capability(cx, scope, thisObj); + Capability cap = new Capability(cx, fscope, thisObj); // Create the result object with promise, resolve, and reject properties - Scriptable result = cx.newObject(scope); + Scriptable result = cx.newObject(fscope); result.put("promise", result, cap.promise); result.put("resolve", result, cap.resolve); result.put("reject", result, cap.reject); @@ -313,7 +329,8 @@ private static Object withResolvers( // Promise.try private static Object promiseTry( - Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + var fscope = f.getDeclarationScope(); if (!ScriptRuntime.isObject(thisObj)) { throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(thisObj)); } @@ -325,7 +342,7 @@ private static Object promiseTry( Callable func = (Callable) args[0]; // Create a new promise capability using the constructor - Capability cap = new Capability(cx, scope, thisObj); + Capability cap = new Capability(cx, fscope, thisObj); // Prepare the arguments to pass to the function (all args after the function) Object[] funcArgs = new Object[args.length - 1]; @@ -333,24 +350,24 @@ private static Object promiseTry( try { // Call the function synchronously - Object result = func.call(cx, scope, Undefined.SCRIPTABLE_UNDEFINED, funcArgs); + Object result = func.call(cx, fscope, Undefined.SCRIPTABLE_UNDEFINED, funcArgs); // Resolve the promise with the result - cap.resolve.call(cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {result}); + cap.resolve.call(cx, fscope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {result}); } catch (RhinoException re) { // If the function throws, reject the promise with the error cap.reject.call( cx, - scope, + fscope, Undefined.SCRIPTABLE_UNDEFINED, - new Object[] {getErrorObject(cx, scope, re)}); + new Object[] {getErrorObject(cx, fscope, re)}); } return cap.promise; } // Promise.prototype.then - private Object then(Context cx, VarScope scope, Object[] args) { + Object then(Context cx, VarScope scope, Object[] args) { Constructable constructable = AbstractEcmaObjectOperations.speciesConstructor( cx, @@ -393,22 +410,27 @@ private void markHandled(Context cx) { } } - private static Object doThen(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + private static Object doThen( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativePromise self = LambdaConstructor.convertThisObject(thisObj, NativePromise.class); - return self.then(cx, scope, args); + return self.then(cx, f.getDeclarationScope(), args); } // Promise.prototype.catch - private static Object doCatch(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + private static Object doCatch( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + var fscope = f.getDeclarationScope(); Object arg = (args.length > 0 ? args[0] : Undefined.instance); - Scriptable coercedThis = ScriptRuntime.toObject(cx, scope, thisObj); + Scriptable coercedThis = ScriptRuntime.toObject(cx, fscope, thisObj); // No guarantee that the caller didn't change the prototype of "then"! - var thenFunc = ScriptRuntime.getPropAndThis(coercedThis, "then", cx, scope); - return thenFunc.call(cx, scope, new Object[] {Undefined.instance, arg}); + var thenFunc = ScriptRuntime.getPropAndThis(coercedThis, "then", cx, fscope); + return thenFunc.call(cx, fscope, new Object[] {Undefined.instance, arg}); } // Promise.prototype.finally - private static Object doFinally(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + private static Object doFinally( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + var fscope = f.getDeclarationScope(); if (!ScriptRuntime.isObject(thisObj)) { throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(thisObj)); } @@ -417,16 +439,16 @@ private static Object doFinally(Context cx, VarScope scope, Scriptable thisObj, Object catchFinally = onFinally; var ctor = TopLevel.getBuiltinCtor( - cx, ScriptableObject.getTopLevelScope(scope), TopLevel.Builtins.Promise); + cx, ScriptableObject.getTopLevelScope(fscope), TopLevel.Builtins.Promise); Constructable constructor = - AbstractEcmaObjectOperations.speciesConstructor(cx, thisObj, ctor); + AbstractEcmaObjectOperations.speciesConstructor(cx, (Scriptable) thisObj, ctor); if (onFinally instanceof Callable) { Callable callableOnFinally = (Callable) thenFinally; - thenFinally = makeThenFinally(scope, constructor, callableOnFinally); - catchFinally = makeCatchFinally(scope, constructor, callableOnFinally); + thenFinally = makeThenFinally(fscope, constructor, callableOnFinally); + catchFinally = makeCatchFinally(fscope, constructor, callableOnFinally); } - var thenFunc = ScriptRuntime.getPropAndThis(thisObj, "then", cx, scope); - return thenFunc.call(cx, scope, new Object[] {thenFinally, catchFinally}); + var thenFunc = ScriptRuntime.getPropAndThis(thisObj, "then", cx, fscope); + return thenFunc.call(cx, fscope, new Object[] {thenFinally, catchFinally}); } // Abstract "Then Finally Function" @@ -435,13 +457,13 @@ private static Callable makeThenFinally( return new LambdaFunction( scope, 1, - (Context cx, VarScope ls, Scriptable thisObj, Object[] args) -> { + (Context cx, VarScope ls, Object thisObj, Object[] args) -> { Object value = args.length > 0 ? args[0] : Undefined.instance; LambdaFunction valueThunk = new LambdaFunction( scope, 0, - (Context vc, VarScope vs, Scriptable vt, Object[] va) -> value); + (Context vc, VarScope vs, Object vt, Object[] va) -> value); Object result = onFinally.call( cx, @@ -460,13 +482,13 @@ private static Callable makeCatchFinally( return new LambdaFunction( scope, 1, - (Context cx, VarScope ls, Scriptable thisObj, Object[] args) -> { + (Context cx, VarScope ls, Object thisObj, Object[] args) -> { Object reason = args.length > 0 ? args[0] : Undefined.instance; LambdaFunction reasonThrower = new LambdaFunction( scope, 0, - (Context vc, VarScope vs, Scriptable vt, Object[] va) -> { + (Context vc, VarScope vs, Object vt, Object[] va) -> { throw new JavaScriptException(reason, null, 0); }); Object result = @@ -576,6 +598,95 @@ private static Object getErrorObject(Context cx, VarScope scope, RhinoException return ScriptRuntime.newNativeError(cx, scope, constructor, new Object[] {re.getMessage()}); } + // Async function support: create a Promise that drives an ES6Generator + // This implements the "async function" semantics: await expr = yield expr internally, + // with the Promise runner stepping through the generator via .then() reactions. + public static Object createAsyncFunctionPromise(Context cx, VarScope scope, ES6Generator gen) { + VarScope topScope = ScriptableObject.getTopLevelScope(scope); + Object promiseCtor = TopLevel.getBuiltinCtor(cx, topScope, TopLevel.Builtins.Promise); + Capability cap = new Capability(cx, scope, promiseCtor); + asyncStep(cx, scope, gen, cap, Undefined.instance, false); + return cap.promise; + } + + private static void asyncStep( + Context cx, + VarScope scope, + ES6Generator gen, + Capability cap, + Object value, + boolean isThrow) { + // Generator resumption requires cx.topCallScope to be set. When called + // from a microtask (after doTopCall has returned), it will be null. + boolean needTopCall = !ScriptRuntime.hasTopCall(cx); + if (needTopCall) { + cx.topCallScope = ScriptableObject.getTopLevelScope(scope); + cx.useDynamicScope = cx.hasFeature(Context.FEATURE_DYNAMIC_SCOPE); + } + try { + Scriptable result; + try { + if (isThrow) { + result = + gen.resumeAbruptLocal( + cx, scope, NativeGenerator.GENERATOR_THROW, value); + } else { + result = gen.resumeLocal(cx, scope, value); + } + } catch (JavaScriptException jse) { + cap.reject.call( + cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {jse.getValue()}); + return; + } catch (RhinoException re) { + cap.reject.call( + cx, + scope, + Undefined.SCRIPTABLE_UNDEFINED, + new Object[] {getErrorObject(cx, scope, re)}); + return; + } + boolean done = + Boolean.TRUE.equals( + ScriptableObject.getProperty(result, ES6Iterator.DONE_PROPERTY)); + Object nextValue = ScriptableObject.getProperty(result, ES6Iterator.VALUE_PROPERTY); + if (done) { + cap.resolve.call( + cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {nextValue}); + } else { + VarScope topScope = ScriptableObject.getTopLevelScope(scope); + Object promiseCtor = + TopLevel.getBuiltinCtor(cx, topScope, TopLevel.Builtins.Promise); + NativePromise nextPromise = + (NativePromise) resolveInternal(cx, scope, promiseCtor, nextValue); + nextPromise.then( + cx, + scope, + new Object[] { + new LambdaFunction( + scope, + 1, + (cx2, s, thisObj, args) -> { + Object v = args.length > 0 ? args[0] : Undefined.instance; + asyncStep(cx2, s, gen, cap, v, false); + return Undefined.instance; + }), + new LambdaFunction( + scope, + 1, + (cx2, s, thisObj, args) -> { + Object r = args.length > 0 ? args[0] : Undefined.instance; + asyncStep(cx2, s, gen, cap, r, true); + return Undefined.instance; + }) + }); + } + } finally { + if (needTopCall) { + cx.topCallScope = null; + } + } + } + // Output of "CreateResolvingFunctions." Carries with it an "alreadyResolved" state, // so we make it a separate object. This actually fires resolution functions on // the passed callbacks. @@ -590,7 +701,7 @@ private static class ResolvingFunctions { new LambdaFunction( topScope, 1, - (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> + (Context cx, VarScope scope, Object thisObj, Object[] args) -> resolve( cx, scope, @@ -600,7 +711,7 @@ private static class ResolvingFunctions { new LambdaFunction( topScope, 1, - (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> + (Context cx, VarScope scope, Object thisObj, Object[] args) -> reject( cx, scope, @@ -696,7 +807,7 @@ void invoke(Context cx, VarScope scope, Object arg) { // "Promise Capability Record" // This abstracts a promise from the specific native implementation by keeping track // of the "resolve" and "reject" functions. - private static class Capability { + static class Capability { Object promise; private Object rawResolve = Undefined.instance; Callable resolve; @@ -714,7 +825,7 @@ private static class Capability { new LambdaFunction( topScope, 2, - (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> + (Context cx, VarScope scope, Object thisObj, Object[] args) -> executor(args)); promise = ((Constructable) pc).construct(topCx, topScope, new Object[] {executorFunc}); @@ -753,15 +864,12 @@ private static class PromiseAllResolver { int remainingElements = 1; IteratorLikeIterable.Itr iterator; - Scriptable thisObj; + Object thisObj; Capability capability; boolean failFast; PromiseAllResolver( - IteratorLikeIterable.Itr iter, - Scriptable thisObj, - Capability cap, - boolean failFast) { + IteratorLikeIterable.Itr iter, Object thisObj, Capability cap, boolean failFast) { this.iterator = iter; this.thisObj = thisObj; this.capability = cap; @@ -812,7 +920,7 @@ Object resolve(Context topCx, VarScope topScope) { new LambdaFunction( topScope, 1, - (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> { + (cx, scope, thisObj, args) -> { Object value = (args.length > 0 ? args[0] : Undefined.instance); if (!failFast) { Scriptable elementResult = cx.newObject(scope); @@ -829,10 +937,7 @@ Object resolve(Context topCx, VarScope topScope) { new LambdaFunction( topScope, 1, - (Context cx, - VarScope scope, - Scriptable thisObj, - Object[] args) -> { + (cx, scope, thisObj, args) -> { Scriptable result = cx.newObject(scope); result.put("status", result, " rejected"); result.put( @@ -929,7 +1034,7 @@ Object reject(Context topCx, VarScope topScope) { new LambdaFunction( topScope, 1, - (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> { + (cx, scope, thisObj, args) -> { Object value = (args.length > 0 ? args[0] : Undefined.instance); return eltResolver.reject(cx, scope, value, this); }); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeProxy.java b/rhino/src/main/java/org/mozilla/javascript/NativeProxy.java index 060b0199f17..7b57c243f68 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeProxy.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeProxy.java @@ -6,6 +6,8 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Destination.CTOR; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -35,6 +37,19 @@ class NativeProxy extends ScriptableObject { private static final String TRAP_APPLY = "apply"; private static final String TRAP_CONSTRUCT = "construct"; + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + PROXY_TAG, + 2, + ClassDescriptor.typeError(), + NativeProxy::js_constructor) + .withMethod(CTOR, "revocable", 2, NativeProxy::revocable) + .build(); + } + private ScriptableObject targetObj; private Scriptable handlerObj; private final String typeOf; @@ -47,7 +62,7 @@ public Revoker(NativeProxy proxy) { } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { if (revocableProxy != null) { revocableProxy.handlerObj = null; revocableProxy.targetObj = null; @@ -58,31 +73,7 @@ public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args } public static Object init(Context cx, VarScope scope, boolean sealed) { - LambdaConstructor constructor = - new LambdaConstructor( - scope, - PROXY_TAG, - 2, - null, // Proxy constructor has *no* prototype - null, // Proxy constructor may not be called as a function. - NativeProxy::constructor) { - - @Override - public Scriptable construct(Context cx, VarScope scope, Object[] args) { - NativeProxy obj = - (NativeProxy) getTargetConstructor().construct(cx, scope, args); - // avoid getting trapped - obj.setPrototypeDirect(getClassPrototype()); - obj.setParentScope(scope); - return obj; - } - }; - - constructor.defineConstructorMethod(scope, "revocable", 2, NativeProxy::revocable); - if (sealed) { - constructor.sealObject(); - } - return constructor; + return DESCRIPTOR.buildConstructor(cx, scope, null, sealed); } private NativeProxy(ScriptableObject target, Scriptable handler) { @@ -1224,7 +1215,8 @@ public void setPrototype(Scriptable prototype) { target.setPrototype(prototype); } - private static NativeProxy constructor(Context cx, VarScope scope, Object[] args) { + private static NativeProxy js_constructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (args.length < 2) { throw ScriptRuntime.typeErrorById( "msg.method.missing.parameter", @@ -1242,22 +1234,25 @@ private static NativeProxy constructor(Context cx, VarScope scope, Object[] args proxy = new NativeProxy(target, handler); } - proxy.setPrototypeDirect(ScriptableObject.getClassPrototype(scope, PROXY_TAG)); - proxy.setParentScope(scope); + // Can't use the normal function here as we `setPrototype()` would call the trap. + proxy.setPrototypeDirect(ScriptRuntime.findPrototype(f, nt, TopLevel.Builtins.Proxy)); + proxy.setParentScope(s); + return proxy; } // Proxy.revocable - private static Object revocable(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object revocable( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (!ScriptRuntime.isObject(thisObj)) { throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(thisObj)); } - NativeProxy proxy = constructor(cx, scope, args); + NativeProxy proxy = js_constructor(cx, f, nt, s, thisObj, args); - NativeObject revocable = (NativeObject) cx.newObject(scope); + NativeObject revocable = (NativeObject) cx.newObject(s); revocable.put("proxy", revocable, proxy); - revocable.put("revoke", revocable, new LambdaFunction(scope, "", 0, new Revoker(proxy))); + revocable.put("revoke", revocable, new LambdaFunction(s, "", 0, new Revoker(proxy))); return revocable; } @@ -1328,13 +1323,44 @@ public Scriptable construct(Context cx, VarScope scope, Object[] args) { return ((Constructable) target).construct(cx, scope, args); } + @Override + public Scriptable construct( + Context cx, Object nt, VarScope s, Object thisObj, Object[] args) { + /* + * 1. Let handler be O.[[ProxyHandler]]. + * 2. If handler is null, throw a TypeError exception. + * 3. Assert: Type(handler) is Object. + * 4. Let target be O.[[ProxyTarget]]. + * 5. Assert: IsConstructor(target) is true. + * 6. Let trap be ? GetMethod(handler, "construct"). + * 7. If trap is undefined, then + * a. Return ? Construct(target, argumentsList, newTarget). + * 8. Let argArray be ! CreateArrayFromList(argumentsList). + * 9. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »). + * 10. If Type(newObj) is not Object, throw a TypeError exception. + * 11. Return newObj. + */ + ScriptableObject target = getTargetThrowIfRevoked(); + + Function trap = getTrap(TRAP_CONSTRUCT); + if (trap != null) { + Object result = callTrap(trap, new Object[] {target, args, this}); + if (!(result instanceof Scriptable) || ScriptRuntime.isSymbol(result)) { + throw ScriptRuntime.typeError("Constructor trap has to return a scriptable."); + } + return (ScriptableObject) result; + } + + return ((Constructable) target).construct(cx, nt, s, thisObj, args); + } + /** * see 10.5.12 * [[Call]] (thisArgument, argumentsList) */ @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { /* * 1. Let handler be O.[[ProxyHandler]]. * 2. If handler is null, throw a TypeError exception. diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeReflect.java b/rhino/src/main/java/org/mozilla/javascript/NativeReflect.java index 006596e3336..64fb4b5c02c 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeReflect.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeReflect.java @@ -6,6 +6,9 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Builder.value; +import static org.mozilla.javascript.ClassDescriptor.Destination.CTOR; + import java.util.ArrayList; import java.util.List; import org.mozilla.javascript.ScriptRuntime.StringIdOrIndex; @@ -20,32 +23,37 @@ final class NativeReflect extends ScriptableObject { private static final String REFLECT_TAG = "Reflect"; + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder(REFLECT_TAG) + .withMethod(CTOR, "apply", 3, NativeReflect::apply) + .withMethod(CTOR, "construct", 2, NativeReflect::construct) + .withMethod(CTOR, "defineProperty", 3, NativeReflect::defineProperty) + .withMethod(CTOR, "deleteProperty", 2, NativeReflect::deleteProperty) + .withMethod(CTOR, "get", 2, NativeReflect::get) + .withMethod( + CTOR, + "getOwnPropertyDescriptor", + 2, + NativeReflect::getOwnPropertyDescriptor) + .withMethod(CTOR, "getPrototypeOf", 1, NativeReflect::getPrototypeOf) + .withMethod(CTOR, "has", 2, NativeReflect::has) + .withMethod(CTOR, "isExtensible", 1, NativeReflect::isExtensible) + .withMethod(CTOR, "ownKeys", 1, NativeReflect::ownKeys) + .withMethod(CTOR, "preventExtensions", 1, NativeReflect::preventExtensions) + .withMethod(CTOR, "set", 3, NativeReflect::set) + .withMethod(CTOR, "setPrototypeOf", 2, NativeReflect::setPrototypeOf) + .withProp( + CTOR, + SymbolKey.TO_STRING_TAG, + value(REFLECT_TAG, DONTENUM | READONLY)) + .build(); + } + public static Object init(Context cx, VarScope scope, boolean sealed) { - NativeReflect reflect = new NativeReflect(); - reflect.setPrototype(getObjectPrototype(scope)); - reflect.setParentScope(scope); - - reflect.defineBuiltinProperty(scope, "apply", 3, NativeReflect::apply); - reflect.defineBuiltinProperty(scope, "construct", 2, NativeReflect::construct); - reflect.defineBuiltinProperty(scope, "defineProperty", 3, NativeReflect::defineProperty); - reflect.defineBuiltinProperty(scope, "deleteProperty", 2, NativeReflect::deleteProperty); - reflect.defineBuiltinProperty(scope, "get", 2, NativeReflect::get); - reflect.defineBuiltinProperty( - scope, "getOwnPropertyDescriptor", 2, NativeReflect::getOwnPropertyDescriptor); - reflect.defineBuiltinProperty(scope, "getPrototypeOf", 1, NativeReflect::getPrototypeOf); - reflect.defineBuiltinProperty(scope, "has", 2, NativeReflect::has); - reflect.defineBuiltinProperty(scope, "isExtensible", 1, NativeReflect::isExtensible); - reflect.defineBuiltinProperty(scope, "ownKeys", 1, NativeReflect::ownKeys); - reflect.defineBuiltinProperty( - scope, "preventExtensions", 1, NativeReflect::preventExtensions); - reflect.defineBuiltinProperty(scope, "set", 3, NativeReflect::set); - reflect.defineBuiltinProperty(scope, "setPrototypeOf", 2, NativeReflect::setPrototypeOf); - - reflect.defineProperty(SymbolKey.TO_STRING_TAG, REFLECT_TAG, DONTENUM | READONLY); - if (sealed) { - reflect.sealObject(); - } - return reflect; + return DESCRIPTOR.populateGlobal(cx, scope, new NativeObject(), sealed); } private NativeReflect() {} @@ -55,7 +63,8 @@ public String getClassName() { return "Reflect"; } - private static Object apply(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object apply( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (args.length < 3) { throw ScriptRuntime.typeErrorById( "msg.method.missing.parameter", @@ -69,7 +78,7 @@ private static Object apply(Context cx, VarScope scope, Object thisObj, Object[] if (args[1] instanceof Scriptable) { thisObj = (Scriptable) args[1]; } else if (ScriptRuntime.isPrimitive(args[1])) { - thisObj = cx.newObject(scope, "Object", new Object[] {args[1]}); + thisObj = cx.newObject(s, "Object", new Object[] {args[1]}); } if (ScriptRuntime.isSymbol(args[2])) { @@ -78,7 +87,7 @@ private static Object apply(Context cx, VarScope scope, Object thisObj, Object[] ScriptableObject argumentsList = ScriptableObject.ensureScriptableObject(args[2]); return ScriptRuntime.applyOrCall( - true, cx, scope, callable, new Object[] {thisObj, argumentsList}); + true, cx, s, callable, new Object[] {thisObj, argumentsList}); } /** @@ -86,7 +95,7 @@ private static Object apply(Context cx, VarScope scope, Object thisObj, Object[] * Reflect.construct (target, argumentsList[, newTarget]) */ private static Scriptable construct( - Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { /* * 1. If IsConstructor(target) is false, throw a TypeError exception. * 2. If newTarget is not present, set newTarget to target. @@ -106,62 +115,27 @@ private static Scriptable construct( throw ScriptRuntime.typeErrorById("msg.not.ctor", ScriptRuntime.typeof(args[0])); } - Constructable ctor = (Constructable) args[0]; + Constructable target = (Constructable) args[0]; if (args.length < 2) { - return ctor.construct(cx, scope, ScriptRuntime.emptyArgs); - } - - if (args.length > 2 && !AbstractEcmaObjectOperations.isConstructor(cx, args[2])) { - throw ScriptRuntime.typeErrorById("msg.not.ctor", ScriptRuntime.typeof(args[2])); + return target.construct(cx, target, s, null, ScriptRuntime.emptyArgs); } Object[] callArgs = ScriptRuntime.getApplyArguments(cx, args[1]); - Object newTargetPrototype = null; - if (args.length > 2) { - Scriptable newTarget = ScriptableObject.ensureScriptable(args[2]); - - if (newTarget instanceof BaseFunction) { - newTargetPrototype = ((BaseFunction) newTarget).getPrototypeProperty(); - } else { - newTargetPrototype = newTarget.get("prototype", newTarget); - } - - if (!(newTargetPrototype instanceof Scriptable) - || ScriptRuntime.isSymbol(newTargetPrototype) - || Undefined.isUndefined(newTargetPrototype)) { - newTargetPrototype = null; - } - } - - // our Constructable interface does not support the newTarget; - // therefore we use a cloned implementation that fixes - // the prototype before executing call(..). - if (ctor instanceof BaseFunction && newTargetPrototype != null) { - BaseFunction ctorBaseFunction = (BaseFunction) ctor; - Scriptable result = ctorBaseFunction.createObject(cx, scope); - if (result != null) { - result.setPrototype((Scriptable) newTargetPrototype); - - Object val = ctorBaseFunction.call(cx, scope, result, callArgs); - if (val instanceof Scriptable) { - return (Scriptable) val; - } - - return result; - } - } - - Scriptable newScriptable = ctor.construct(cx, scope, callArgs); - if (newTargetPrototype != null) { - newScriptable.setPrototype((Scriptable) newTargetPrototype); + Constructable newTarget; + if (args.length < 3) { + newTarget = target; + } else if (AbstractEcmaObjectOperations.isConstructor(cx, args[2])) { + newTarget = (Function) args[2]; + } else { + throw ScriptRuntime.typeErrorById("msg.not.ctor", ScriptRuntime.typeof(args[2])); } - return newScriptable; + return target.construct(cx, newTarget, s, null, callArgs); } private static Object defineProperty( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (args.length < 3) { throw ScriptRuntime.typeErrorById( "msg.method.missing.parameter", @@ -191,7 +165,7 @@ private static Object defineProperty( } private static Object deleteProperty( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { ScriptableObject target = checkTarget(args); if (args.length > 1) { @@ -204,7 +178,8 @@ private static Object deleteProperty( return false; } - private static Object get(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object get( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { ScriptableObject target = checkTarget(args); if (args.length > 1) { @@ -224,29 +199,30 @@ private static Object get(Context cx, VarScope scope, Object thisObj, Object[] a } private static Scriptable getOwnPropertyDescriptor( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { ScriptableObject target = checkTarget(args); if (args.length > 1) { if (ScriptRuntime.isSymbol(args[1])) { var desc = target.getOwnPropertyDescriptor(cx, args[1]); - return desc == null ? Undefined.SCRIPTABLE_UNDEFINED : desc.toObject(scope); + return desc == null ? Undefined.SCRIPTABLE_UNDEFINED : desc.toObject(s); } var desc = target.getOwnPropertyDescriptor(cx, ScriptRuntime.toString(args[1])); - return desc == null ? Undefined.SCRIPTABLE_UNDEFINED : desc.toObject(scope); + return desc == null ? Undefined.SCRIPTABLE_UNDEFINED : desc.toObject(s); } return Undefined.SCRIPTABLE_UNDEFINED; } private static Scriptable getPrototypeOf( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { ScriptableObject target = checkTarget(args); return target.getPrototype(); } - private static Object has(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object has( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { ScriptableObject target = checkTarget(args); if (args.length > 1) { @@ -259,12 +235,14 @@ private static Object has(Context cx, VarScope scope, Object thisObj, Object[] a return false; } - private static Object isExtensible(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object isExtensible( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { ScriptableObject target = checkTarget(args); return target.isExtensible(); } - private static Scriptable ownKeys(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Scriptable ownKeys( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { ScriptableObject target = checkTarget(args); final List strings = new ArrayList<>(); @@ -286,17 +264,18 @@ private static Scriptable ownKeys(Context cx, VarScope scope, Object thisObj, Ob System.arraycopy(strings.toArray(), 0, keys, 0, strings.size()); System.arraycopy(symbols.toArray(), 0, keys, strings.size(), symbols.size()); - return cx.newArray(scope, keys); + return cx.newArray(s, keys); } private static Object preventExtensions( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { ScriptableObject target = checkTarget(args); return target.preventExtensions(); } - private static Object set(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object set( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { ScriptableObject target = checkTarget(args); if (args.length < 2) { return true; @@ -309,7 +288,7 @@ private static Object set(Context cx, VarScope scope, Object thisObj, Object[] a if (descriptor != null) { Object setter = descriptor.setter; if (setter != null && setter != NOT_FOUND) { - ((Function) setter).call(cx, scope, receiver, new Object[] {args[2]}); + ((Function) setter).call(cx, s, receiver, new Object[] {args[2]}); return true; } @@ -322,11 +301,11 @@ private static Object set(Context cx, VarScope scope, Object thisObj, Object[] a if (ScriptRuntime.isSymbol(args[1])) { receiver.put((Symbol) args[1], receiver, args[2]); } else { - StringIdOrIndex s = ScriptRuntime.toStringIdOrIndex(args[1]); - if (s.stringId == null) { - receiver.put(s.index, receiver, args[2]); + StringIdOrIndex soi = ScriptRuntime.toStringIdOrIndex(args[1]); + if (soi.stringId == null) { + receiver.put(soi.index, receiver, args[2]); } else { - receiver.put(s.stringId, receiver, args[2]); + receiver.put(soi.stringId, receiver, args[2]); } } @@ -334,7 +313,7 @@ private static Object set(Context cx, VarScope scope, Object thisObj, Object[] a } private static Object setPrototypeOf( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (args.length < 2) { throw ScriptRuntime.typeErrorById( "msg.method.missing.parameter", diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeScript.java b/rhino/src/main/java/org/mozilla/javascript/NativeScript.java index bf528b1282b..7e5331b51f5 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeScript.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeScript.java @@ -6,6 +6,8 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Destination.PROTO; + import java.util.EnumSet; /** @@ -23,46 +25,26 @@ class NativeScript extends BaseFunction { private static final long serialVersionUID = -6795101161980121700L; - private static final Object SCRIPT_TAG = "Script"; - - static LambdaConstructor init(Context cx, VarScope scope, boolean sealed) { - LambdaConstructor obj = - new LambdaConstructor( - scope, - "Script", - 1, - NativeScript::js_constructorCall, - NativeScript::js_constructor); - - var proto = new NativeScript(null); - proto.setPrototypeProperty(null); - obj.setPrototypeProperty(proto); - - var function = (Scriptable) ScriptableObject.getProperty(scope, "Function"); - var functionProto = (Scriptable) ScriptableObject.getProperty(function, "prototype"); - proto.setPrototype(functionProto); - - defineMethod(obj, scope, "toString", 0, NativeScript::js_toString); - defineMethod(obj, scope, "exec", 0, NativeScript::js_exec); - defineMethod(obj, scope, "compile", 0, NativeScript::js_compile); + private static final String SCRIPT_TAG = "Script"; - ScriptableObject.defineProperty(scope, "Script", obj, DONTENUM); - if (sealed) { - obj.sealObject(); - ((ScriptableObject) obj.getPrototypeProperty()).sealObject(); - } + private static final ClassDescriptor DESCRIPTOR; - return obj; + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + SCRIPT_TAG, + 1, + NativeScript::js_constructorCall, + NativeScript::js_constructor) + .withMethod(PROTO, "toString", 0, NativeScript::js_toString) + .withMethod(PROTO, "exec", 0, NativeScript::js_exec) + .withMethod(PROTO, "compile", 0, NativeScript::js_compile) + .build(); } - private static void defineMethod( - LambdaConstructor typedArray, - VarScope scope, - String name, - int length, - SerializableCallable target) { - typedArray.definePrototypeMethod( - scope, name, length, target, DONTENUM, DONTENUM | READONLY); + static JSFunction init2(Context cx, VarScope scope, boolean sealed) { + var proto = new NativeScript(null); + return DESCRIPTOR.buildConstructor(cx, scope, proto, sealed); } /** @@ -70,10 +52,11 @@ private static void defineMethod( */ @Deprecated static void init(VarScope scope, boolean sealed) { - init(Context.getContext(), scope, sealed); + init2(Context.getContext(), scope, sealed); } private NativeScript(Script script) { + super(null); this.script = script; } @@ -84,7 +67,7 @@ public String getClassName() { } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { if (script != null) { return script.exec(cx, scope, thisObj); } @@ -114,18 +97,21 @@ String decompile(int indent, EnumSet flags) { return super.decompile(indent, flags); } - private static Object js_compile(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_compile( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeScript real = realThis(thisObj, "compile"); String source = ScriptRuntime.toString(args, 0); real.script = compile(cx, source); return real; } - private static Object js_exec(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_exec( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { throw Context.reportRuntimeErrorById("msg.cant.call.indirect", "exec"); } - 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) { NativeScript real = realThis(thisObj, "toString"); Script realScript = real.script; if (realScript == null) { @@ -135,15 +121,16 @@ private static Object js_toString(Context cx, VarScope scope, Object thisObj, Ob } private static Scriptable js_constructorCall( - Context cx, VarScope scope, Object thisObj, Object[] args) { - return js_constructor(cx, scope, args); + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return js_constructor(cx, f, nt, s, thisObj, args); } - private static Scriptable js_constructor(Context cx, VarScope scope, Object[] args) { + private static Scriptable js_constructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String source = (args.length == 0) ? "" : ScriptRuntime.toString(args[0]); Script script = compile(cx, source); NativeScript nscript = new NativeScript(script); - ScriptRuntime.setObjectProtoAndParent(nscript, scope); + ScriptRuntime.setObjectProtoAndParent(nscript, f.getDeclarationScope()); return nscript; } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeSet.java b/rhino/src/main/java/org/mozilla/javascript/NativeSet.java index 1fa7ca0db51..cdcb8e60933 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeSet.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeSet.java @@ -6,6 +6,11 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Builder.alias; +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 class NativeSet extends ScriptableObject { private static final long serialVersionUID = -8442212766987072986L; private static final String CLASS_NAME = "Set"; @@ -13,80 +18,69 @@ public class NativeSet extends ScriptableObject { static final SymbolKey GETSIZE = new SymbolKey("[Symbol.getSize]", Symbol.Kind.REGULAR); + private static final ClassDescriptor DESCRIPTOR; + private static final ClassDescriptor ITER_DESCRIPTOR = + ES6Iterator.makeDescriptor(ITERATOR_TAG, ITERATOR_TAG); + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + CLASS_NAME, + 0, + ClassDescriptor.typeError(), + NativeSet::js_constructor) + .withMethod(PROTO, "add", 1, NativeSet::js_add) + .withMethod(PROTO, "delete", 1, NativeSet::js_delete) + .withMethod(PROTO, "has", 1, NativeSet::js_has) + .withMethod(PROTO, "clear", 0, NativeSet::js_clear) + .withMethod(PROTO, "values", 0, NativeSet::js_values) + .withProp(PROTO, "keys", alias("values", DONTENUM | READONLY)) + .withProp(PROTO, SymbolKey.ITERATOR, alias("values", DONTENUM)) + .withMethod(PROTO, "forEach", 1, NativeSet::js_forEach) + .withMethod(PROTO, "entries", 0, NativeSet::js_entries) + + // ES2025 Set methods + .withMethod(PROTO, "intersection", 1, NativeSet::js_intersection) + .withMethod(PROTO, "union", 1, NativeSet::js_union) + .withMethod(PROTO, "difference", 1, NativeSet::js_difference) + .withMethod( + PROTO, "symmetricDifference", 1, NativeSet::js_symmetricDifference) + .withMethod(PROTO, "isSubsetOf", 1, NativeSet::js_isSubsetOf) + .withMethod(PROTO, "isSupersetOf", 1, NativeSet::js_isSupersetOf) + .withMethod(PROTO, "isDisjointFrom", 1, NativeSet::js_isDisjointFrom) + .withProp( + PROTO, + "size", + (thisObj) -> realThis(thisObj, "size").js_getSize(), + null, + DONTENUM) + .withProp( + PROTO, + NativeSet.GETSIZE, + (thisObj) -> realThis(thisObj, "size").js_getSize(), + null, + DONTENUM | PERMANENT) + .withProp( + PROTO, + SymbolKey.TO_STRING_TAG, + value(CLASS_NAME, DONTENUM | READONLY)) + .withProp(CTOR, SymbolKey.SPECIES, ScriptRuntimeES6::symbolSpecies) + .build(); + } + private final Hashtable entries = new Hashtable(); private boolean instanceOfSet = false; - static Object init(Context cx, VarScope s, boolean sealed) { - TopLevel scope = (TopLevel) s; - LambdaConstructor constructor = - new LambdaConstructor( - scope, - CLASS_NAME, - 0, - LambdaConstructor.CONSTRUCTOR_NEW, - NativeSet::jsConstructor); - constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); - - var propAttrs = DONTENUM | READONLY; - constructor.definePrototypeMethod(scope, "add", 1, NativeSet::js_add, DONTENUM, propAttrs); - constructor.definePrototypeMethod( - scope, "delete", 1, NativeSet::js_delete, DONTENUM, propAttrs); - constructor.definePrototypeMethod(scope, "has", 1, NativeSet::js_has, DONTENUM, propAttrs); - constructor.definePrototypeMethod( - scope, "clear", 0, NativeSet::js_clear, DONTENUM, propAttrs); - constructor.definePrototypeMethod( - scope, "values", 0, NativeSet::js_values, DONTENUM, propAttrs); - constructor.definePrototypeAlias("values", "keys", propAttrs); - constructor.definePrototypeAlias("values", SymbolKey.ITERATOR, DONTENUM); - - constructor.definePrototypeMethod( - scope, "forEach", 1, NativeSet::js_forEach, DONTENUM, propAttrs); - - constructor.definePrototypeMethod( - scope, "entries", 0, NativeSet::js_entries, DONTENUM, propAttrs); - - // ES2025 Set methods - constructor.definePrototypeMethod( - scope, "intersection", 1, NativeSet::js_intersection, DONTENUM, propAttrs); - constructor.definePrototypeMethod( - scope, "union", 1, NativeSet::js_union, DONTENUM, propAttrs); - constructor.definePrototypeMethod( - scope, "difference", 1, NativeSet::js_difference, DONTENUM, propAttrs); - constructor.definePrototypeMethod( - scope, - "symmetricDifference", - 1, - NativeSet::js_symmetricDifference, - DONTENUM, - propAttrs); - constructor.definePrototypeMethod( - scope, "isSubsetOf", 1, NativeSet::js_isSubsetOf, DONTENUM, propAttrs); - constructor.definePrototypeMethod( - scope, "isSupersetOf", 1, NativeSet::js_isSupersetOf, DONTENUM, propAttrs); - constructor.definePrototypeMethod( - scope, "isDisjointFrom", 1, NativeSet::js_isDisjointFrom, DONTENUM, propAttrs); - - // The spec requires very specific handling of the "size" prototype - // property that's not like other things that we already do. - ScriptableObject desc = (ScriptableObject) cx.newObject(scope); - desc.put("enumerable", desc, Boolean.FALSE); - desc.put("configurable", desc, Boolean.TRUE); - LambdaFunction sizeFunc = new LambdaFunction(scope, "get size", 0, NativeSet::js_getSize); - sizeFunc.setPrototypeProperty(Undefined.instance); - desc.put("get", desc, sizeFunc); - constructor.definePrototypeProperty(cx, "size", desc); - constructor.definePrototypeProperty(cx, NativeSet.GETSIZE, desc); - - constructor.definePrototypeProperty( - SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); - - ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor); - if (sealed) { - constructor.sealObject(); - ((ScriptableObject) constructor.getPrototypeProperty()).sealObject(); - } - return constructor; + static Object init(Context cx, VarScope scope, boolean sealed) { + ES6Iterator.initialize( + ITER_DESCRIPTOR, + cx, + (TopLevel) scope, + new NativeCollectionIterator(ITERATOR_TAG), + sealed, + ITERATOR_TAG); + return DESCRIPTOR.buildConstructor(cx, scope, new NativeObject(), sealed); } @Override @@ -94,16 +88,19 @@ public String getClassName() { return CLASS_NAME; } - private static Scriptable jsConstructor(Context cx, VarScope scope, Object[] args) { + private static Scriptable js_constructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeSet ns = new NativeSet(); ns.instanceOfSet = true; if (args.length > 0) { - loadFromIterable(cx, scope, ns, NativeMap.key(args)); + loadFromIterable(cx, s, ns, NativeMap.key(args)); } + ScriptRuntime.setBuiltinProtoAndParent(ns, f, nt, s, TopLevel.Builtins.Set); return ns; } - private static Object js_add(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_add( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeSet realThis = realThis(thisObj, "add"); var k = NativeMap.key(args); return realThis.js_add(k); @@ -119,7 +116,8 @@ private Object js_add(Object k) { return this; } - private static Object js_delete(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_delete( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeSet realThis = realThis(thisObj, "add"); var arg = NativeMap.key(args); return realThis.js_delete(arg); @@ -129,7 +127,8 @@ private Object js_delete(Object arg) { return entries.deleteEntry(arg); } - private static Object js_has(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_has( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeSet realThis = realThis(thisObj, "add"); var arg = NativeMap.key(args); return realThis.js_has(arg); @@ -143,7 +142,8 @@ private Object js_has(Object arg) { return entries.has(arg); } - private static Object js_clear(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_clear( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeSet realThis = realThis(thisObj, "add"); return realThis.js_clear(); } @@ -153,34 +153,34 @@ private Object js_clear() { return Undefined.instance; } - private static Object js_getSize(Context cx, VarScope scope, Object thisObj, Object[] args) { - NativeSet realThis = realThis(thisObj, "add"); - return realThis.js_getSize(); - } - private Object js_getSize() { return entries.size(); } - private static Object js_values(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_values( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeSet realThis = realThis(thisObj, "add"); - return realThis(thisObj, "values").js_iterator(scope, NativeCollectionIterator.Type.VALUES); + return realThis(thisObj, "values") + .js_iterator(f.getDeclarationScope(), NativeCollectionIterator.Type.VALUES); } - private static Object js_entries(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_entries( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeSet realThis = realThis(thisObj, "add"); - return realThis(thisObj, "values").js_iterator(scope, NativeCollectionIterator.Type.BOTH); + return realThis(thisObj, "values") + .js_iterator(f.getDeclarationScope(), NativeCollectionIterator.Type.BOTH); } private Object js_iterator(VarScope scope, NativeCollectionIterator.Type type) { return new NativeCollectionIterator(scope, ITERATOR_TAG, type, entries.iterator()); } - private static Object js_forEach(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_forEach( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "forEach") .js_forEach( cx, - scope, + f.getDeclarationScope(), NativeMap.key(args), args.length > 1 ? args[1] : Undefined.SCRIPTABLE_UNDEFINED); } @@ -192,9 +192,8 @@ private Object js_forEach(Context cx, VarScope scope, Object arg1, Object arg2) final Function f = (Function) arg1; for (Hashtable.Entry entry : entries) { - Scriptable thisObj = ScriptRuntime.getThisForScope(f.getDeclarationScope(), arg2); final Hashtable.Entry e = entry; - f.call(cx, scope, thisObj, new Object[] {e.value, e.value, this}); + f.call(cx, scope, arg2, new Object[] {e.value, e.value, this}); } return Undefined.instance; } @@ -243,8 +242,8 @@ private static NativeSet realThis(Object thisObj, String name) { // ES2025 Set Methods Implementation private static Object js_intersection( - Context cx, VarScope scope, Object thisObj, Object[] args) { - return realThis(thisObj, "intersection").js_intersection(cx, scope, args); + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return realThis(thisObj, "intersection").js_intersection(cx, s, args); } private Object js_intersection(Context cx, VarScope scope, Object[] args) { @@ -329,8 +328,9 @@ private Object js_intersectionSetLike( return result; } - private static Object js_union(Context cx, VarScope scope, Object thisObj, Object[] args) { - return realThis(thisObj, "union").js_union(cx, scope, args); + private static Object js_union( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return realThis(thisObj, "union").js_union(cx, s, args); } private Object js_union(Context cx, VarScope scope, Object[] args) { @@ -384,8 +384,9 @@ private Object js_union(Context cx, VarScope scope, Object[] args) { return result; } - private static Object js_difference(Context cx, VarScope scope, Object thisObj, Object[] args) { - return realThis(thisObj, "difference").js_difference(cx, scope, args); + private static Object js_difference( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return realThis(thisObj, "difference").js_difference(cx, s, args); } private Object js_difference(Context cx, VarScope scope, Object[] args) { @@ -484,8 +485,8 @@ private Object js_differenceSetLike( } private static Object js_symmetricDifference( - Context cx, VarScope scope, Object thisObj, Object[] args) { - return realThis(thisObj, "symmetricDifference").js_symmetricDifference(cx, scope, args); + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return realThis(thisObj, "symmetricDifference").js_symmetricDifference(cx, s, args); } private Object js_symmetricDifference(Context cx, VarScope scope, Object[] args) { @@ -547,8 +548,9 @@ private Object js_symmetricDifference(Context cx, VarScope scope, Object[] args) return result; } - private static Object js_isSubsetOf(Context cx, VarScope scope, Object thisObj, Object[] args) { - return realThis(thisObj, "isSubsetOf").js_isSubsetOf(cx, scope, args); + private static Object js_isSubsetOf( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return realThis(thisObj, "isSubsetOf").js_isSubsetOf(cx, s, args); } private Object js_isSubsetOf(Context cx, VarScope scope, Object[] args) { @@ -605,8 +607,8 @@ private Object js_isSubsetOf(Context cx, VarScope scope, Object[] args) { } private static Object js_isSupersetOf( - Context cx, VarScope scope, Object thisObj, Object[] args) { - return realThis(thisObj, "isSupersetOf").js_isSupersetOf(cx, scope, args); + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return realThis(thisObj, "isSupersetOf").js_isSupersetOf(cx, s, args); } private Object js_isSupersetOf(Context cx, VarScope scope, Object[] args) { @@ -654,8 +656,8 @@ private Object js_isSupersetOf(Context cx, VarScope scope, Object[] args) { } private static Object js_isDisjointFrom( - Context cx, VarScope scope, Object thisObj, Object[] args) { - return realThis(thisObj, "isDisjointFrom").js_isDisjointFrom(cx, scope, args); + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + return realThis(thisObj, "isDisjointFrom").js_isDisjointFrom(cx, s, args); } private Object js_isDisjointFrom(Context cx, VarScope scope, Object[] args) { diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeString.java b/rhino/src/main/java/org/mozilla/javascript/NativeString.java index e8e2db35c39..a44b9c34c50 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeString.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeString.java @@ -6,6 +6,8 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ClassDescriptor.Destination.CTOR; +import static org.mozilla.javascript.ClassDescriptor.Destination.PROTO; import static org.mozilla.javascript.ScriptRuntime.rangeError; import static org.mozilla.javascript.ScriptRuntimeES6.requireObjectCoercible; @@ -18,6 +20,7 @@ import java.util.Locale; import java.util.Map; import org.mozilla.javascript.AbstractEcmaStringOperations.ReplacementOperation; +import org.mozilla.javascript.ClassDescriptor.BuiltInJSCodeExec; import org.mozilla.javascript.ScriptRuntime.StringIdOrIndex; /** @@ -39,145 +42,132 @@ final class NativeString extends ScriptableObject { private final CharSequence string; - static void init(VarScope scope, boolean sealed) { - LambdaConstructor c = - new LambdaConstructor( - scope, - CLASS_NAME, - 1, - NativeString::js_constructorFunc, - NativeString::js_constructor); - c.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); - c.setPrototypeScriptable(new NativeString("")); - - defConsMethod(c, scope, "fromCharCode", 1, NativeString::js_fromCharCode); - defConsMethod(c, scope, "fromCodePoint", 1, NativeString::js_fromCodePoint); - defConsMethod(c, scope, "raw", 1, NativeString::js_raw); - - /* - * All of the methods below are on the constructor for compatibility with ancient Rhino - * versions. They are no longer part of ECMAScript. The "wrapConstructor" method is a - * technique used in the past in Rhino to adapt the instance functions so that they - * may be called on the constructor directly. - */ - defConsMethod(c, scope, "charAt", 1, wrapConstructor(NativeString::js_charAt)); - defConsMethod(c, scope, "charCodeAt", 1, wrapConstructor(NativeString::js_charCodeAt)); - defConsMethod(c, scope, "indexOf", 2, wrapConstructor(NativeString::js_indexOf)); - defConsMethod(c, scope, "lastIndexOf", 2, wrapConstructor(NativeString::js_lastIndexOf)); - defConsMethod(c, scope, "split", 3, wrapConstructor(NativeString::js_split)); - defConsMethod(c, scope, "substring", 3, wrapConstructor(NativeString::js_substring)); - defConsMethod(c, scope, "toLowerCase", 1, wrapConstructor(NativeString::js_toLowerCase)); - defConsMethod(c, scope, "toUpperCase", 1, wrapConstructor(NativeString::js_toUpperCase)); - defConsMethod(c, scope, "substr", 3, wrapConstructor(NativeString::js_substr)); - defConsMethod(c, scope, "concat", 2, wrapConstructor(NativeString::js_concat)); - defConsMethod(c, scope, "slice", 3, wrapConstructor(NativeString::js_slice)); - defConsMethod( - c, - scope, - "equalsIgnoreCase", - 2, - wrapConstructor(NativeString::js_equalsIgnoreCase)); - defConsMethod(c, scope, "match", 2, wrapConstructor(NativeString::js_match)); - defConsMethod(c, scope, "search", 2, wrapConstructor(NativeString::js_search)); - defConsMethod(c, scope, "replace", 2, wrapConstructor(NativeString::js_replace)); - defConsMethod(c, scope, "replaceAll", 2, wrapConstructor(NativeString::js_replaceAll)); - defConsMethod( - c, scope, "localeCompare", 2, wrapConstructor(NativeString::js_localeCompare)); - defConsMethod( - c, - scope, - "toLocaleLowerCase", - 1, - wrapConstructor(NativeString::js_toLocaleLowerCase)); - - /* Back to prototype methods -- these are all part of ECMAScript */ - defProtoMethod(c, scope, SymbolKey.ITERATOR, 0, NativeString::js_iterator); - defProtoMethod(c, scope, "toString", 0, NativeString::js_toString); - defProtoMethod(c, scope, "toSource", 0, NativeString::js_toSource); - defProtoMethod(c, scope, "valueOf", 0, NativeString::js_toString); - defProtoMethod(c, scope, "charAt", 1, NativeString::js_charAt); - defProtoMethod(c, scope, "charCodeAt", 1, NativeString::js_charCodeAt); - defProtoMethod(c, scope, "indexOf", 1, NativeString::js_indexOf); - defProtoMethod(c, scope, "lastIndexOf", 1, NativeString::js_lastIndexOf); - defProtoMethod(c, scope, "split", 2, NativeString::js_split); - defProtoMethod(c, scope, "substring", 2, NativeString::js_substring); - defProtoMethod(c, scope, "toLowerCase", 0, NativeString::js_toLowerCase); - defProtoMethod(c, scope, "toUpperCase", 0, NativeString::js_toUpperCase); - defProtoMethod(c, scope, "substr", 2, NativeString::js_substr); - defProtoMethod(c, scope, "concat", 1, NativeString::js_concat); - defProtoMethod(c, scope, "slice", 2, NativeString::js_slice); - defProtoMethod(c, scope, "bold", 0, NativeString::js_bold); - defProtoMethod(c, scope, "italics", 0, NativeString::js_italics); - defProtoMethod(c, scope, "fixed", 0, NativeString::js_fixed); - defProtoMethod(c, scope, "strike", 0, NativeString::js_strike); - defProtoMethod(c, scope, "small", 0, NativeString::js_small); - defProtoMethod(c, scope, "big", 0, NativeString::js_big); - defProtoMethod(c, scope, "blink", 0, NativeString::js_blink); - defProtoMethod(c, scope, "sup", 0, NativeString::js_sup); - defProtoMethod(c, scope, "sub", 0, NativeString::js_sub); - defProtoMethod(c, scope, "fontsize", 0, NativeString::js_fontsize); - defProtoMethod(c, scope, "fontcolor", 0, NativeString::js_fontcolor); - defProtoMethod(c, scope, "link", 0, NativeString::js_link); - defProtoMethod(c, scope, "anchor", 0, NativeString::js_anchor); - defProtoMethod(c, scope, "equals", 1, NativeString::js_equals); - defProtoMethod(c, scope, "equalsIgnoreCase", 1, NativeString::js_equalsIgnoreCase); - defProtoMethod(c, scope, "match", 1, NativeString::js_match); - defProtoMethod(c, scope, "matchAll", 1, NativeString::js_matchAll); - defProtoMethod(c, scope, "search", 1, NativeString::js_search); - defProtoMethod(c, scope, "replace", 2, NativeString::js_replace); - defProtoMethod(c, scope, "replaceAll", 2, NativeString::js_replaceAll); - defProtoMethod(c, scope, "at", 1, NativeString::js_at); - defProtoMethod(c, scope, "localeCompare", 1, NativeString::js_localeCompare); - defProtoMethod(c, scope, "toLocaleLowerCase", 0, NativeString::js_toLocaleLowerCase); - defProtoMethod(c, scope, "toLocaleUpperCase", 0, NativeString::js_toLocaleUpperCase); - defProtoMethod(c, scope, "trim", 0, NativeString::js_trim); - defProtoMethod(c, scope, "trimLeft", 0, NativeString::js_trimLeft); - defProtoMethod(c, scope, "trimStart", 0, NativeString::js_trimLeft); - defProtoMethod(c, scope, "trimRight", 0, NativeString::js_trimRight); - defProtoMethod(c, scope, "trimEnd", 0, NativeString::js_trimRight); - defProtoMethod(c, scope, "includes", 1, NativeString::js_includes); - defProtoMethod(c, scope, "startsWith", 1, NativeString::js_startsWith); - defProtoMethod(c, scope, "endsWith", 1, NativeString::js_endsWith); - defProtoMethod(c, scope, "normalize", 0, NativeString::js_normalize); - defProtoMethod(c, scope, "repeat", 1, NativeString::js_repeat); - defProtoMethod(c, scope, "codePointAt", 1, NativeString::js_codePointAt); - defProtoMethod(c, scope, "padStart", 1, NativeString::js_padStart); - defProtoMethod(c, scope, "padEnd", 1, NativeString::js_padEnd); - defProtoMethod(c, scope, "isWellFormed", 0, NativeString::js_isWellFormed); - defProtoMethod(c, scope, "toWellFormed", 0, NativeString::js_toWellFormed); - - if (sealed) { - c.sealObject(); - ((NativeString) c.getPrototypeProperty()).sealObject(); - } - ScriptableObject.defineProperty(scope, CLASS_NAME, c, DONTENUM); - } - - private static void defConsMethod( - LambdaConstructor c, - VarScope scope, - String name, - int length, - SerializableCallable target) { - c.defineConstructorMethod(scope, name, length, target); - } - - private static void defProtoMethod( - LambdaConstructor c, - VarScope scope, - SymbolKey key, - int length, - SerializableCallable target) { - c.definePrototypeMethod(scope, key, length, target); - } - - private static void defProtoMethod( - LambdaConstructor c, - VarScope scope, - String name, - int length, - SerializableCallable target) { - c.definePrototypeMethod(scope, name, length, target); + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + CLASS_NAME, + 1, + NativeString::js_constructorFunc, + NativeString::js_constructor) + .withMethod(CTOR, "fromCharCode", 1, NativeString::js_fromCharCode) + .withMethod(CTOR, "fromCodePoint", 1, NativeString::js_fromCodePoint) + .withMethod(CTOR, "raw", 1, NativeString::js_raw) + + /* + * All of the methods below are on the constructor for compatibility with ancient Rhino + * versions. They are no longer part of ECMAScript. The "wrapConstructor" method is a + * technique used in the past in Rhino to adapt the instance functions so that they + * may be called on the constructor directly. + */ + .withMethod(CTOR, "charAt", 1, forCtor(NativeString::js_charAt)) + .withMethod(CTOR, "charCodeAt", 1, forCtor(NativeString::js_charCodeAt)) + .withMethod(CTOR, "indexOf", 2, forCtor(NativeString::js_indexOf)) + .withMethod(CTOR, "lastIndexOf", 2, forCtor(NativeString::js_lastIndexOf)) + .withMethod(CTOR, "split", 3, forCtor(NativeString::js_split)) + .withMethod(CTOR, "substring", 3, forCtor(NativeString::js_substring)) + .withMethod(CTOR, "toLowerCase", 1, forCtor(NativeString::js_toLowerCase)) + .withMethod(CTOR, "toUpperCase", 1, forCtor(NativeString::js_toUpperCase)) + .withMethod(CTOR, "substr", 3, forCtor(NativeString::js_substr)) + .withMethod(CTOR, "concat", 2, forCtor(NativeString::js_concat)) + .withMethod(CTOR, "slice", 3, forCtor(NativeString::js_slice)) + .withMethod( + CTOR, + "equalsIgnoreCase", + 2, + forCtor(NativeString::js_equalsIgnoreCase)) + .withMethod(CTOR, "match", 2, forCtor(NativeString::js_match)) + .withMethod(CTOR, "search", 2, forCtor(NativeString::js_search)) + .withMethod(CTOR, "replace", 2, forCtor(NativeString::js_replace)) + .withMethod(CTOR, "replaceAll", 2, forCtor(NativeString::js_replaceAll)) + .withMethod( + CTOR, "localeCompare", 2, forCtor(NativeString::js_localeCompare)) + .withMethod( + CTOR, + "toLocaleLowerCase", + 1, + forCtor(NativeString::js_toLocaleLowerCase)) + + /* Back to prototype methods -- these are all part of ECMAScript */ + + .withMethod(PROTO, SymbolKey.ITERATOR, 0, NativeString::js_iterator) + .withMethod(PROTO, "toString", 0, NativeString::js_toString) + .withMethod(PROTO, "toSource", 0, NativeString::js_toSource) + .withMethod(PROTO, "valueOf", 0, NativeString::js_toString) + .withMethod(PROTO, "charAt", 1, NativeString::js_charAt) + .withMethod(PROTO, "charCodeAt", 1, NativeString::js_charCodeAt) + .withMethod(PROTO, "indexOf", 1, NativeString::js_indexOf) + .withMethod(PROTO, "lastIndexOf", 1, NativeString::js_lastIndexOf) + .withMethod(PROTO, "split", 2, NativeString::js_split) + .withMethod(PROTO, "substring", 2, NativeString::js_substring) + .withMethod(PROTO, "toLowerCase", 0, NativeString::js_toLowerCase) + .withMethod(PROTO, "toUpperCase", 0, NativeString::js_toUpperCase) + .withMethod(PROTO, "substr", 2, NativeString::js_substr) + .withMethod(PROTO, "concat", 1, NativeString::js_concat) + .withMethod(PROTO, "slice", 2, NativeString::js_slice) + .withMethod(PROTO, "bold", 0, NativeString::js_bold) + .withMethod(PROTO, "italics", 0, NativeString::js_italics) + .withMethod(PROTO, "fixed", 0, NativeString::js_fixed) + .withMethod(PROTO, "strike", 0, NativeString::js_strike) + .withMethod(PROTO, "small", 0, NativeString::js_small) + .withMethod(PROTO, "big", 0, NativeString::js_big) + .withMethod(PROTO, "blink", 0, NativeString::js_blink) + .withMethod(PROTO, "sup", 0, NativeString::js_sup) + .withMethod(PROTO, "sub", 0, NativeString::js_sub) + .withMethod(PROTO, "fontsize", 0, NativeString::js_fontsize) + .withMethod(PROTO, "fontcolor", 0, NativeString::js_fontcolor) + .withMethod(PROTO, "link", 0, NativeString::js_link) + .withMethod(PROTO, "anchor", 0, NativeString::js_anchor) + .withMethod(PROTO, "equals", 1, NativeString::js_equals) + .withMethod(PROTO, "equalsIgnoreCase", 1, NativeString::js_equalsIgnoreCase) + .withMethod(PROTO, "match", 1, NativeString::js_match) + .withMethod(PROTO, "matchAll", 1, NativeString::js_matchAll) + .withMethod(PROTO, "search", 1, NativeString::js_search) + .withMethod(PROTO, "replace", 2, NativeString::js_replace) + .withMethod(PROTO, "replaceAll", 2, NativeString::js_replaceAll) + .withMethod(PROTO, "at", 1, NativeString::js_at) + .withMethod(PROTO, "localeCompare", 1, NativeString::js_localeCompare) + .withMethod( + PROTO, "toLocaleLowerCase", 0, NativeString::js_toLocaleLowerCase) + .withMethod( + PROTO, "toLocaleUpperCase", 0, NativeString::js_toLocaleUpperCase) + .withMethod(PROTO, "trim", 0, NativeString::js_trim) + .withMethod(PROTO, "trimLeft", 0, NativeString::js_trimLeft) + .withMethod(PROTO, "trimStart", 0, NativeString::js_trimLeft) + .withMethod(PROTO, "trimRight", 0, NativeString::js_trimRight) + .withMethod(PROTO, "trimEnd", 0, NativeString::js_trimRight) + .withMethod(PROTO, "includes", 1, NativeString::js_includes) + .withMethod(PROTO, "startsWith", 1, NativeString::js_startsWith) + .withMethod(PROTO, "endsWith", 1, NativeString::js_endsWith) + .withMethod(PROTO, "normalize", 0, NativeString::js_normalize) + .withMethod(PROTO, "repeat", 1, NativeString::js_repeat) + .withMethod(PROTO, "codePointAt", 1, NativeString::js_codePointAt) + .withMethod(PROTO, "padStart", 1, NativeString::js_padStart) + .withMethod(PROTO, "padEnd", 1, NativeString::js_padEnd) + .withMethod(PROTO, "isWellFormed", 0, NativeString::js_isWellFormed) + .withMethod(PROTO, "toWellFormed", 0, NativeString::js_toWellFormed) + .build(); + } + + private static BuiltInJSCodeExec forCtor(BuiltInJSCodeExec code) { + return (cx, f, nt, s, thisObj, args) -> { + Object[] realArgs; + Object realThis; + if (args.length > 0) { + realThis = ScriptRuntime.toObject(cx, s, ScriptRuntime.toCharSequence(args[0])); + realArgs = new Object[args.length - 1]; + System.arraycopy(args, 1, realArgs, 0, realArgs.length); + } else { + realThis = ScriptRuntime.toObject(cx, s, ScriptRuntime.toCharSequence(thisObj)); + realArgs = args; + } + return code.execute(cx, f, nt, s, realThis, realArgs); + }; + } + + static void init(Context cx, VarScope scope, boolean sealed) { + DESCRIPTOR.buildConstructor(cx, scope, new NativeString(""), sealed); } NativeString(CharSequence s) { @@ -192,52 +182,37 @@ public String getClassName() { return CLASS_NAME; } - private static SerializableCallable wrapConstructor(SerializableCallable target) { - return (Context cx, VarScope scope, Scriptable origThis, Object[] origArgs) -> { - Scriptable thisObj; - Object[] newArgs; - if (origArgs.length > 0) { - thisObj = - ScriptRuntime.toObject( - cx, scope, ScriptRuntime.toCharSequence(origArgs[0])); - newArgs = new Object[origArgs.length - 1]; - System.arraycopy(origArgs, 1, newArgs, 0, newArgs.length); - } else { - thisObj = ScriptRuntime.toObject(cx, scope, ScriptRuntime.toCharSequence(origThis)); - newArgs = origArgs; - } - return target.call(cx, scope, thisObj, newArgs); - }; - } - - private static Scriptable js_constructor(Context cx, VarScope scope, Object[] args) { - CharSequence s; + private static Scriptable js_constructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + CharSequence str; if (args.length == 0) { - s = ""; + str = ""; } else { - s = ScriptRuntime.toCharSequence(args[0]); + str = ScriptRuntime.toCharSequence(args[0]); } - return new NativeString(s); + var res = new NativeString(str); + ScriptRuntime.setBuiltinProtoAndParent(res, f, nt, s, TopLevel.Builtins.String); + return res; } private static Object js_constructorFunc( - Context cx, VarScope scope, Object thisObj, Object[] args) { - CharSequence s; + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + CharSequence str; if (args.length == 0) { - s = ""; + str = ""; } else if (ScriptRuntime.isSymbol(args[0])) { // 19.4.3.2 et.al. Convert a symbol to a string with String() but not // new String() - s = args[0].toString(); + str = args[0].toString(); } else { - s = ScriptRuntime.toCharSequence(args[0]); + str = ScriptRuntime.toCharSequence(args[0]); } // String(val) converts val to a string value. - return s instanceof String ? s : s.toString(); + return str instanceof String ? str : str.toString(); } private static Object js_fromCharCode( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { int n = args.length; if (n < 1) { return ""; @@ -250,7 +225,7 @@ private static Object js_fromCharCode( } private static Object js_fromCodePoint( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { int n = args.length; if (n < 1) { return ""; @@ -268,11 +243,13 @@ private static Object js_fromCodePoint( return new String(codePoints, 0, n); } - private static Object js_charAt(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_charAt( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return charAt(cx, thisObj, args, false); } - private static Object js_charCodeAt(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_charCodeAt( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return charAt(cx, thisObj, args, true); } @@ -291,7 +268,8 @@ private static Object charAt(Context cx, Object thisObj, Object[] args, boolean return ScriptRuntime.wrapInt(c); } - private static Object js_indexOf(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_indexOf( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String target = ScriptRuntime.toString(requireObjectCoercible(cx, thisObj, CLASS_NAME, "indexOf")); String searchStr = ScriptRuntime.toString(args, 0); @@ -307,7 +285,8 @@ private static Object js_indexOf(Context cx, VarScope scope, Object thisObj, Obj return target.indexOf(searchStr, (int) position); } - private static Object js_startsWith(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_startsWith( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String target = ScriptRuntime.toString( requireObjectCoercible(cx, thisObj, CLASS_NAME, "startsWith")); @@ -319,7 +298,8 @@ private static Object js_startsWith(Context cx, VarScope scope, Object thisObj, return target.startsWith(searchStr, (int) position); } - private static Object js_endsWith(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_endsWith( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String target = ScriptRuntime.toString(requireObjectCoercible(cx, thisObj, CLASS_NAME, "endsWith")); checkValidRegex(cx, args, 0, "endsWith"); @@ -333,7 +313,8 @@ private static Object js_endsWith(Context cx, VarScope scope, Object thisObj, Ob return target.substring(0, (int) position).endsWith(searchStr); } - private static Object js_includes(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_includes( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String target = ScriptRuntime.toString(requireObjectCoercible(cx, thisObj, CLASS_NAME, "includes")); String searchStr = ScriptRuntime.toString(args, 0); @@ -358,20 +339,21 @@ private static void checkValidRegex(Context cx, Object[] args, int pos, String f } } - private static Object js_split(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_split( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { // See ECMAScript spec 22.1.3.23 Object o = requireObjectCoercible(cx, thisObj, CLASS_NAME, "split"); if (cx.getLanguageVersion() <= Context.VERSION_1_8) { // Use old algorithm for backward compatibility return ScriptRuntime.checkRegExpProxy(cx) - .js_split(cx, scope, ScriptRuntime.toString(o), args); + .js_split(cx, s, ScriptRuntime.toString(o), args); } Object separator = args.length > 0 ? args[0] : Undefined.instance; Object limit = args.length > 1 ? args[1] : Undefined.instance; if (!Undefined.isUndefined(separator) && separator != null) { - Object splitter = ScriptRuntime.getObjectElem(separator, SymbolKey.SPLIT, cx, scope); + Object splitter = ScriptRuntime.getObjectElem(separator, SymbolKey.SPLIT, cx, s); // If method is not undefined, it should be a Callable if (splitter != null && !Undefined.isUndefined(splitter)) { if (!(splitter instanceof Callable)) { @@ -381,8 +363,8 @@ private static Object js_split(Context cx, VarScope scope, Object thisObj, Objec return ((Callable) splitter) .call( cx, - scope, - ScriptRuntime.toObject(scope, separator), + s, + ScriptRuntime.toObject(s, separator), new Object[] { o instanceof NativeString ? ((NativeString) o).string : o, limit, @@ -390,7 +372,7 @@ private static Object js_split(Context cx, VarScope scope, Object thisObj, Objec } } - String s = ScriptRuntime.toString(o); + String str = ScriptRuntime.toString(o); long lim; if (Undefined.isUndefined(limit)) { lim = Integer.MAX_VALUE; @@ -399,17 +381,17 @@ private static Object js_split(Context cx, VarScope scope, Object thisObj, Objec } String r = ScriptRuntime.toString(separator); if (lim == 0) { - return cx.newArray(scope, 0); + return cx.newArray(s, 0); } if (Undefined.isUndefined(separator)) { - return cx.newArray(scope, new Object[] {s}); + return cx.newArray(s, new Object[] {str}); } int separatorLength = r.length(); if (separatorLength == 0) { - int strLen = s.length(); + int strLen = str.length(); int outLen = ScriptRuntime.clamp((int) lim, 0, strLen); - String head = s.substring(0, outLen); + String head = str.substring(0, outLen); List codeUnits = new ArrayList<>(); for (int i = 0; i < head.length(); ) { @@ -417,49 +399,53 @@ private static Object js_split(Context cx, VarScope scope, Object thisObj, Objec codeUnits.add(Character.toString(c)); i += Character.charCount(c); } - return cx.newArray(scope, codeUnits.toArray()); + return cx.newArray(s, codeUnits.toArray()); } - if (s.isEmpty()) { - return cx.newArray(scope, new Object[] {s}); + if (str.isEmpty()) { + return cx.newArray(s, new Object[] {str}); } List substrings = new ArrayList<>(); int i = 0; - int j = s.indexOf(r); + int j = str.indexOf(r); while (j != -1) { - String t = s.substring(i, j); + String t = str.substring(i, j); substrings.add(t); if (substrings.size() >= lim) { - return cx.newArray(scope, substrings.toArray()); + return cx.newArray(s, substrings.toArray()); } i = j + separatorLength; - j = s.indexOf(r, i); + j = str.indexOf(r, i); } - String t = s.substring(i); + String t = str.substring(i); substrings.add(t); - return cx.newArray(scope, substrings.toArray()); + return cx.newArray(s, substrings.toArray()); } private static NativeString realThis(Object thisObj) { return LambdaConstructor.convertThisObject(thisObj, NativeString.class); } - 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 new NativeStringIterator( - scope, requireObjectCoercible(cx, thisObj, CLASS_NAME, "[Symbol.iterator]")); + f.getDeclarationScope(), + requireObjectCoercible(cx, thisObj, CLASS_NAME, "[Symbol.iterator]")); } - 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) { // ECMA 15.5.4.2: the toString function is not generic. CharSequence cs = realThis(thisObj).string; return cs instanceof String ? cs : cs.toString(); } - private static Object js_toSource(Context cx, VarScope scope, Object thisObj, Object[] args) { - CharSequence s = realThis(thisObj).string; - return "(new String(\"" + ScriptRuntime.escapeString(s.toString()) + "\"))"; + private static Object js_toSource( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { + CharSequence str = realThis(thisObj).string; + return "(new String(\"" + ScriptRuntime.escapeString(str.toString()) + "\"))"; } /* @@ -576,12 +562,13 @@ private ScriptableObject.DescriptorInfo defaultIndexPropertyDescriptor(Object va * See ECMA 22.1.3.13 * */ - private static Object js_match(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_match( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { Object o = requireObjectCoercible(cx, thisObj, CLASS_NAME, "match"); Object regexp = args.length > 0 ? args[0] : Undefined.instance; RegExpProxy regExpProxy = ScriptRuntime.checkRegExpProxy(cx); if (regexp != null && !Undefined.isUndefined(regexp)) { - Object matcher = ScriptRuntime.getObjectElem(regexp, SymbolKey.MATCH, cx, scope); + Object matcher = ScriptRuntime.getObjectElem(regexp, SymbolKey.MATCH, cx, s); // If method is not undefined, it should be a Callable if (matcher != null && !Undefined.isUndefined(matcher)) { if (!(matcher instanceof Callable)) { @@ -589,11 +576,11 @@ private static Object js_match(Context cx, VarScope scope, Object thisObj, Objec regexp, matcher, SymbolKey.MATCH.getName()); } return ((Callable) matcher) - .call(cx, scope, ScriptRuntime.toObject(scope, regexp), new Object[] {o}); + .call(cx, s, ScriptRuntime.toObject(s, regexp), new Object[] {o}); } } - String s = ScriptRuntime.toString(o); + String str = ScriptRuntime.toString(o); String regexpToString = Undefined.isUndefined(regexp) ? "" : ScriptRuntime.toString(regexp); String flags = null; @@ -604,13 +591,13 @@ private static Object js_match(Context cx, VarScope scope, Object thisObj, Objec } Object compiledRegExp = regExpProxy.compileRegExp(cx, regexpToString, flags); - Scriptable rx = regExpProxy.wrapRegExp(cx, scope, compiledRegExp); + Scriptable rx = regExpProxy.wrapRegExp(cx, s, compiledRegExp); - Object method = ScriptRuntime.getObjectElem(rx, SymbolKey.MATCH, cx, scope); + Object method = ScriptRuntime.getObjectElem(rx, SymbolKey.MATCH, cx, s); if (!(method instanceof Callable)) { throw ScriptRuntime.notFunctionError(rx, method, SymbolKey.MATCH.getName()); } - return ((Callable) method).call(cx, scope, rx, new Object[] {s}); + return ((Callable) method).call(cx, s, rx, new Object[] {str}); } /* @@ -619,7 +606,7 @@ private static Object js_match(Context cx, VarScope scope, Object thisObj, Objec * */ private static Object js_lastIndexOf( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String target = ScriptRuntime.toString( requireObjectCoercible(cx, thisObj, CLASS_NAME, "lastIndexOf")); @@ -635,7 +622,8 @@ private static Object js_lastIndexOf( /* * See ECMA 15.5.4.15 */ - private static Object js_substring(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_substring( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { CharSequence target = ScriptRuntime.toCharSequence( requireObjectCoercible(cx, thisObj, CLASS_NAME, "substring")); @@ -664,7 +652,7 @@ private static Object js_substring(Context cx, VarScope scope, Object thisObj, O } private static Object js_toLowerCase( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { // See ECMA 15.5.4.11 String thisStr = ScriptRuntime.toString( @@ -673,7 +661,7 @@ private static Object js_toLowerCase( } private static Object js_toUpperCase( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { // See ECMA 15.5.4.12 String thisStr = ScriptRuntime.toString( @@ -689,7 +677,7 @@ int getLength() { * Non-ECMA methods. */ private static CharSequence js_substr( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { CharSequence target = ScriptRuntime.toCharSequence( requireObjectCoercible(cx, thisObj, CLASS_NAME, "substr")); @@ -730,7 +718,8 @@ private static CharSequence js_substr( /* * Python-esque sequence operations. */ - private static String js_concat(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static String js_concat( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String target = ScriptRuntime.toString(requireObjectCoercible(cx, thisObj, CLASS_NAME, "concat")); int N = args.length; @@ -746,9 +735,9 @@ private static String js_concat(Context cx, VarScope scope, Object thisObj, Obje int size = target.length(); String[] argsAsStrings = new String[N]; for (int i = 0; i != N; ++i) { - String s = ScriptRuntime.toString(args[i]); - argsAsStrings[i] = s; - size += s.length(); + String str = ScriptRuntime.toString(args[i]); + argsAsStrings[i] = str; + size += str.length(); } StringBuilder result = new StringBuilder(size); @@ -759,7 +748,8 @@ private static String js_concat(Context cx, VarScope scope, Object thisObj, Obje return result.toString(); } - private static Object js_slice(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_slice( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { CharSequence target = ScriptRuntime.toCharSequence( requireObjectCoercible(cx, thisObj, CLASS_NAME, "slice")); @@ -788,7 +778,8 @@ private static Object js_slice(Context cx, VarScope scope, Object thisObj, Objec return target.subSequence((int) begin, (int) end); } - private static Object js_at(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_at( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String str = ScriptRuntime.toString(requireObjectCoercible(cx, thisObj, CLASS_NAME, "at")); Object targetArg = (args.length >= 1) ? args[0] : Undefined.instance; int len = str.length(); @@ -803,25 +794,27 @@ private static Object js_at(Context cx, VarScope scope, Object thisObj, Object[] return str.substring(k, k + 1); } - private static Object js_equals(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_equals( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String s1 = ScriptRuntime.toString(thisObj); String s2 = ScriptRuntime.toString(args, 0); return s1.equals(s2); } private static Object js_equalsIgnoreCase( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String s1 = ScriptRuntime.toString(thisObj); String s2 = ScriptRuntime.toString(args, 0); return s1.equalsIgnoreCase(s2); } - private static Object js_search(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_search( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { Object o = requireObjectCoercible(cx, thisObj, CLASS_NAME, "search"); Object regexp = args.length > 0 ? args[0] : Undefined.instance; RegExpProxy regExpProxy = ScriptRuntime.checkRegExpProxy(cx); if (regexp != null && !Undefined.isUndefined(regexp)) { - Object matcher = ScriptRuntime.getObjectElem(regexp, SymbolKey.SEARCH, cx, scope); + Object matcher = ScriptRuntime.getObjectElem(regexp, SymbolKey.SEARCH, cx, s); // If method is not undefined, it should be a Callable if (matcher != null && !Undefined.isUndefined(matcher)) { if (!(matcher instanceof Callable)) { @@ -829,11 +822,11 @@ private static Object js_search(Context cx, VarScope scope, Object thisObj, Obje regexp, matcher, SymbolKey.SEARCH.getName()); } return ((Callable) matcher) - .call(cx, scope, ScriptRuntime.toObject(scope, regexp), new Object[] {o}); + .call(cx, s, ScriptRuntime.toObject(s, regexp), new Object[] {o}); } } - String s = ScriptRuntime.toString(o); + String str = ScriptRuntime.toString(o); String regexpToString = Undefined.isUndefined(regexp) ? "" : ScriptRuntime.toString(regexp); String flags = null; @@ -843,23 +836,24 @@ private static Object js_search(Context cx, VarScope scope, Object thisObj, Obje } Object compiledRegExp = regExpProxy.compileRegExp(cx, regexpToString, flags); - Scriptable rx = regExpProxy.wrapRegExp(cx, scope, compiledRegExp); + Scriptable rx = regExpProxy.wrapRegExp(cx, s, compiledRegExp); - Object method = ScriptRuntime.getObjectElem(rx, SymbolKey.SEARCH, cx, scope); + Object method = ScriptRuntime.getObjectElem(rx, SymbolKey.SEARCH, cx, s); if (!(method instanceof Callable)) { throw ScriptRuntime.notFunctionError(rx, method, SymbolKey.SEARCH.getName()); } - return ((Callable) method).call(cx, scope, rx, new Object[] {s}); + return ((Callable) method).call(cx, s, rx, new Object[] {str}); } - private static Object js_replace(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_replace( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { // See ECMAScript spec 22.1.3.19 Object o = requireObjectCoercible(cx, thisObj, CLASS_NAME, "replace"); if (cx.getLanguageVersion() <= Context.VERSION_1_8) { // Use old algorithm for backward compatibility return ScriptRuntime.checkRegExpProxy(cx) - .action(cx, scope, thisObj, args, RegExpProxy.RA_REPLACE); + .action(cx, s, thisObj, args, RegExpProxy.RA_REPLACE); } // Spec-compliant algorithm @@ -868,8 +862,7 @@ private static Object js_replace(Context cx, VarScope scope, Object thisObj, Obj Object replaceValue = args.length > 1 ? args[1] : Undefined.instance; if (!Undefined.isUndefined(searchValue) && searchValue != null) { - Object replacer = - ScriptRuntime.getObjectElem(searchValue, SymbolKey.REPLACE, cx, scope); + Object replacer = ScriptRuntime.getObjectElem(searchValue, SymbolKey.REPLACE, cx, s); // If method is not undefined, it should be a Callable if (replacer != null && !Undefined.isUndefined(replacer)) { if (!(replacer instanceof Callable)) { @@ -879,8 +872,8 @@ private static Object js_replace(Context cx, VarScope scope, Object thisObj, Obj return ((Callable) replacer) .call( cx, - scope, - ScriptRuntime.toObject(scope, searchValue), + s, + ScriptRuntime.toObject(s, searchValue), new Object[] { o instanceof NativeString ? ((NativeString) o).string : o, replaceValue @@ -911,13 +904,13 @@ private static Object js_replace(Context cx, VarScope scope, Object thisObj, Obj String replacement; if (functionalReplace) { Scriptable callThis = - ScriptRuntime.getApplyOrCallThis(cx, scope, null, 0, (Function) replaceValue); + ScriptRuntime.getApplyOrCallThis(cx, s, null, 0, (Function) replaceValue); Object replacementObj = ((Callable) replaceValue) .call( cx, - scope, + s, callThis, new Object[] { searchString, position, string, @@ -928,7 +921,7 @@ private static Object js_replace(Context cx, VarScope scope, Object thisObj, Obj replacement = AbstractEcmaStringOperations.getSubstitution( cx, - scope, + s, searchString, string, position, @@ -939,7 +932,8 @@ private static Object js_replace(Context cx, VarScope scope, Object thisObj, Obj return preceding + replacement + following; } - private static Object js_replaceAll(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_replaceAll( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { // See ECMAScript spec 22.1.3.20 Object o = requireObjectCoercible(cx, thisObj, CLASS_NAME, "replaceAll"); @@ -949,9 +943,9 @@ private static Object js_replaceAll(Context cx, VarScope scope, Object thisObj, if (searchValue != null && !Undefined.isUndefined(searchValue)) { boolean isRegExp = searchValue instanceof Scriptable - && AbstractEcmaObjectOperations.isRegExp(cx, scope, searchValue); + && AbstractEcmaObjectOperations.isRegExp(cx, s, searchValue); if (isRegExp) { - Object flags = ScriptRuntime.getObjectProp(searchValue, "flags", cx, scope); + Object flags = ScriptRuntime.getObjectProp(searchValue, "flags", cx, s); requireObjectCoercible(cx, flags, CLASS_NAME, "replaceAll"); String flagsStr = ScriptRuntime.toString(flags); if (!flagsStr.contains("g")) { @@ -959,7 +953,7 @@ private static Object js_replaceAll(Context cx, VarScope scope, Object thisObj, } } - Object matcher = ScriptRuntime.getObjectElem(searchValue, SymbolKey.REPLACE, cx, scope); + Object matcher = ScriptRuntime.getObjectElem(searchValue, SymbolKey.REPLACE, cx, s); // If method is not undefined, it should be a Callable if (matcher != null && !Undefined.isUndefined(matcher)) { if (!(matcher instanceof Callable)) { @@ -969,8 +963,8 @@ private static Object js_replaceAll(Context cx, VarScope scope, Object thisObj, return ((Callable) matcher) .call( cx, - scope, - ScriptRuntime.toObject(scope, searchValue), + s, + ScriptRuntime.toObject(s, searchValue), new Object[] {o, replaceValue}); } } @@ -1006,14 +1000,13 @@ private static Object js_replaceAll(Context cx, VarScope scope, Object thisObj, String replacement; if (functionalReplace) { Scriptable callThis = - ScriptRuntime.getApplyOrCallThis( - cx, scope, null, 0, (Function) replaceValue); + ScriptRuntime.getApplyOrCallThis(cx, s, null, 0, (Function) replaceValue); Object replacementObj = ((Callable) replaceValue) .call( cx, - scope, + s, callThis, new Object[] { searchString, p, string, @@ -1024,7 +1017,7 @@ private static Object js_replaceAll(Context cx, VarScope scope, Object thisObj, replacement = AbstractEcmaStringOperations.getSubstitution( cx, - scope, + s, searchString, string, p, @@ -1042,14 +1035,15 @@ private static Object js_replaceAll(Context cx, VarScope scope, Object thisObj, return result.toString(); } - private static Object js_matchAll(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_matchAll( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { // See ECMAScript spec 22.1.3.14 Object o = requireObjectCoercible(cx, thisObj, CLASS_NAME, "matchAll"); Object regexp = args.length > 0 ? args[0] : Undefined.instance; if (regexp != null && !Undefined.isUndefined(regexp)) { - boolean isRegExp = AbstractEcmaObjectOperations.isRegExp(cx, scope, regexp); + boolean isRegExp = AbstractEcmaObjectOperations.isRegExp(cx, s, regexp); if (isRegExp) { - Object flags = ScriptRuntime.getObjectProp(regexp, "flags", cx, scope); + Object flags = ScriptRuntime.getObjectProp(regexp, "flags", cx, s); requireObjectCoercible(cx, flags, CLASS_NAME, "matchAll"); String flagsStr = ScriptRuntime.toString(flags); if (!flagsStr.contains("g")) { @@ -1057,7 +1051,7 @@ private static Object js_matchAll(Context cx, VarScope scope, Object thisObj, Ob } } - Object matcher = ScriptRuntime.getObjectElem(regexp, SymbolKey.MATCH_ALL, cx, scope); + Object matcher = ScriptRuntime.getObjectElem(regexp, SymbolKey.MATCH_ALL, cx, s); // If method is not undefined, it should be a Callable if (matcher != null && !Undefined.isUndefined(matcher)) { if (!(matcher instanceof Callable)) { @@ -1065,25 +1059,25 @@ private static Object js_matchAll(Context cx, VarScope scope, Object thisObj, Ob regexp, matcher, SymbolKey.MATCH_ALL.getName()); } return ((Callable) matcher) - .call(cx, scope, ScriptRuntime.toObject(scope, regexp), new Object[] {o}); + .call(cx, s, ScriptRuntime.toObject(s, regexp), new Object[] {o}); } } - String s = ScriptRuntime.toString(o); + String str = ScriptRuntime.toString(o); String regexpToString = Undefined.isUndefined(regexp) ? "" : ScriptRuntime.toString(regexp); RegExpProxy regExpProxy = ScriptRuntime.checkRegExpProxy(cx); Object compiledRegExp = regExpProxy.compileRegExp(cx, regexpToString, "g"); - Scriptable rx = regExpProxy.wrapRegExp(cx, scope, compiledRegExp); + Scriptable rx = regExpProxy.wrapRegExp(cx, s, compiledRegExp); - Object method = ScriptRuntime.getObjectElem(rx, SymbolKey.MATCH_ALL, cx, scope); + Object method = ScriptRuntime.getObjectElem(rx, SymbolKey.MATCH_ALL, cx, s); if (!(method instanceof Callable)) { throw ScriptRuntime.notFunctionError(rx, method, SymbolKey.MATCH_ALL.getName()); } - return ((Callable) method).call(cx, scope, rx, new Object[] {s}); + return ((Callable) method).call(cx, s, rx, new Object[] {str}); } private static Object js_localeCompare( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { // For now, create and configure a collator instance. I can't // actually imagine that this'd be slower than caching them // a la ClassCache, so we aren't trying to outsmart ourselves @@ -1098,7 +1092,7 @@ private static Object js_localeCompare( } private static Object js_toLocaleLowerCase( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String thisStr = ScriptRuntime.toString( requireObjectCoercible(cx, thisObj, CLASS_NAME, "toLocaleLowerCase")); @@ -1115,7 +1109,7 @@ private static Object js_toLocaleLowerCase( } private static Object js_toLocaleUpperCase( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String thisStr = ScriptRuntime.toString( requireObjectCoercible(cx, thisObj, CLASS_NAME, "toLocaleUpperCase")); @@ -1131,7 +1125,8 @@ private static Object js_toLocaleUpperCase( return thisStr.toUpperCase(locale); } - private static Object js_trim(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_trim( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String str = ScriptRuntime.toString(requireObjectCoercible(cx, thisObj, CLASS_NAME, "trim")); char[] chars = str.toCharArray(); @@ -1148,7 +1143,8 @@ private static Object js_trim(Context cx, VarScope scope, Object thisObj, Object return str.substring(start, end); } - private static Object js_trimLeft(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_trimLeft( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String str = ScriptRuntime.toString(requireObjectCoercible(cx, thisObj, CLASS_NAME, "trimLeft")); char[] chars = str.toCharArray(); @@ -1162,7 +1158,8 @@ private static Object js_trimLeft(Context cx, VarScope scope, Object thisObj, Ob return str.substring(start, end); } - private static Object js_trimRight(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_trimRight( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String str = ScriptRuntime.toString( requireObjectCoercible(cx, thisObj, CLASS_NAME, "trimRight")); @@ -1178,7 +1175,8 @@ private static Object js_trimRight(Context cx, VarScope scope, Object thisObj, O return str.substring(start, end); } - private static Object js_normalize(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_normalize( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { if (args.length == 0 || Undefined.isUndefined(args[0])) { return Normalizer.normalize( ScriptRuntime.toString( @@ -1203,7 +1201,8 @@ private static Object js_normalize(Context cx, VarScope scope, Object thisObj, O form); } - private static String js_repeat(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static String js_repeat( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String str = ScriptRuntime.toString(requireObjectCoercible(cx, thisObj, CLASS_NAME, "repeat")); double cnt = ScriptRuntime.toInteger(args, 0); @@ -1239,7 +1238,7 @@ private static String js_repeat(Context cx, VarScope scope, Object thisObj, Obje } private static Object js_codePointAt( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { String str = ScriptRuntime.toString( requireObjectCoercible(cx, thisObj, CLASS_NAME, "codePointAt")); @@ -1288,11 +1287,13 @@ private static String pad( return concat.insert(0, pad).toString(); } - private static Object js_padStart(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_padStart( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return pad(cx, thisObj, "padStart", args, true); } - private static Object js_padEnd(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_padEnd( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return pad(cx, thisObj, "padEnd", args, false); } @@ -1303,13 +1304,14 @@ private static Object js_padEnd(Context cx, VarScope scope, Object thisObj, Obje * *

22.1.2.4 String.raw [Draft ECMA-262 / April 28, 2021] */ - private static Object js_raw(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_raw( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { /* step 1-2 */ Object arg0 = args.length > 0 ? args[0] : Undefined.instance; - Scriptable cooked = ScriptRuntime.toObject(cx, scope, arg0); + Scriptable cooked = ScriptRuntime.toObject(cx, s, arg0); /* step 3 */ Object rawValue = ScriptRuntime.getObjectProp(cooked, "raw", cx); - Scriptable raw = ScriptRuntime.toObject(cx, scope, rawValue); + Scriptable raw = ScriptRuntime.toObject(cx, s, rawValue); /* step 4-5 */ long rawLength = NativeArray.getLengthProperty(cx, raw); if (rawLength > Integer.MAX_VALUE) { @@ -1341,7 +1343,7 @@ private static Object js_raw(Context cx, VarScope scope, Object thisObj, Object[ } private static Object js_isWellFormed( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { CharSequence str = ScriptRuntime.toCharSequence( requireObjectCoercible(cx, thisObj, CLASS_NAME, "isWellFormed")); @@ -1367,7 +1369,7 @@ private static Object js_isWellFormed( } private static Object js_toWellFormed( - Context cx, VarScope scope, Object thisObj, Object[] args) { + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { CharSequence str = ScriptRuntime.toCharSequence( requireObjectCoercible(cx, thisObj, CLASS_NAME, "toWellFormed")); @@ -1413,55 +1415,68 @@ private static Object js_toWellFormed( return sb.toString(); } - private static Object js_bold(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_bold( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "bold", "b", null, args); } - private static Object js_italics(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_italics( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "italics", "i", null, args); } - private static Object js_fixed(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_fixed( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "fixed", "tt", null, args); } - private static Object js_strike(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_strike( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "strike", "strike", null, args); } - private static Object js_small(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_small( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "small", "small", null, args); } - private static Object js_big(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_big( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "big", "big", null, args); } - private static Object js_blink(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_blink( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "blink", "blink", null, args); } - private static Object js_sup(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_sup( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "sup", "sup", null, args); } - private static Object js_sub(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_sub( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "sub", "sub", null, args); } - private static Object js_fontsize(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_fontsize( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "fontsize", "font", "size", args); } - private static Object js_fontcolor(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_fontcolor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "fontcolor", "font", "color", args); } - private static Object js_link(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_link( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "link", "a", "href", args); } - private static Object js_anchor(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_anchor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return tagify(cx, thisObj, "anchor", "a", "name", args); } } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeStringIterator.java b/rhino/src/main/java/org/mozilla/javascript/NativeStringIterator.java index 989aa3c5f1a..6c28cb541f8 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeStringIterator.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeStringIterator.java @@ -7,11 +7,15 @@ package org.mozilla.javascript; public final class NativeStringIterator extends ES6Iterator { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 6709689999500242078L; private static final String ITERATOR_TAG = "StringIterator"; - static void init(TopLevel scope, boolean sealed) { - ES6Iterator.init(scope, sealed, new NativeStringIterator(), ITERATOR_TAG); + private static final ClassDescriptor DESCRIPTOR = + ES6Iterator.makeDescriptor(ITERATOR_TAG, "String Iterator"); + + static void init(Context cx, VarScope scope, boolean sealed) { + ES6Iterator.initialize( + DESCRIPTOR, cx, (TopLevel) scope, new NativeStringIterator(), sealed, ITERATOR_TAG); } /** Only for constructing the prototype object. */ diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeSymbol.java b/rhino/src/main/java/org/mozilla/javascript/NativeSymbol.java index 372bc0f7c01..94f9e92b646 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeSymbol.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeSymbol.java @@ -46,6 +46,7 @@ public static void init(Context cx, VarScope scope, boolean sealed) { // Create all the predefined symbols and bind them to the scope. createStandardSymbol(scope, ctor, "iterator", SymbolKey.ITERATOR); + createStandardSymbol(scope, ctor, "asyncIterator", SymbolKey.ASYNC_ITERATOR); createStandardSymbol(scope, ctor, "species", SymbolKey.SPECIES); createStandardSymbol(scope, ctor, "toStringTag", SymbolKey.TO_STRING_TAG); createStandardSymbol(scope, ctor, "hasInstance", SymbolKey.HAS_INSTANCE); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeWeakMap.java b/rhino/src/main/java/org/mozilla/javascript/NativeWeakMap.java index 0b3c6c92a3c..d32419c33bf 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeWeakMap.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeWeakMap.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; + import java.io.IOException; import java.io.ObjectInputStream; import java.util.WeakHashMap; @@ -23,6 +27,27 @@ public class NativeWeakMap extends ScriptableObject { private static final String CLASS_NAME = "WeakMap"; + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + CLASS_NAME, + 0, + ClassDescriptor.typeError(), + NativeWeakMap::jsConstructor) + .withMethod(PROTO, "set", 2, NativeWeakMap::js_set) + .withMethod(PROTO, "delete", 1, NativeWeakMap::js_delete) + .withMethod(PROTO, "get", 1, NativeWeakMap::js_get) + .withMethod(PROTO, "has", 1, NativeWeakMap::js_has) + .withProp( + PROTO, + SymbolKey.TO_STRING_TAG, + value(CLASS_NAME, DONTENUM | READONLY)) + .withProp(CTOR, SymbolKey.SPECIES, ScriptRuntimeES6::symbolSpecies) + .build(); + } + private boolean instanceOfWeakMap = false; private transient WeakHashMap map = new WeakHashMap<>(); @@ -30,29 +55,7 @@ public class NativeWeakMap extends ScriptableObject { private static final Object NULL_VALUE = new Object(); static Object init(Context cx, VarScope scope, boolean sealed) { - LambdaConstructor constructor = - new LambdaConstructor( - scope, - CLASS_NAME, - 0, - LambdaConstructor.CONSTRUCTOR_NEW, - NativeWeakMap::jsConstructor); - constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); - - constructor.definePrototypeMethod(scope, "set", 2, NativeWeakMap::js_set); - constructor.definePrototypeMethod(scope, "delete", 1, NativeWeakMap::js_delete); - constructor.definePrototypeMethod(scope, "get", 1, NativeWeakMap::js_get); - constructor.definePrototypeMethod(scope, "has", 1, NativeWeakMap::js_has); - - constructor.definePrototypeProperty( - SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); - - ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor); - if (sealed) { - constructor.sealObject(); - ((ScriptableObject) constructor.getPrototypeProperty()).sealObject(); - } - return constructor; + return DESCRIPTOR.buildConstructor(cx, scope, new NativeObject(), sealed); } @Override @@ -60,16 +63,19 @@ public String getClassName() { return CLASS_NAME; } - private static Scriptable jsConstructor(Context cx, VarScope scope, Object[] args) { + private static Scriptable jsConstructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeWeakMap nm = new NativeWeakMap(); nm.instanceOfWeakMap = true; if (args.length > 0) { - NativeMap.loadFromIterable(cx, scope, nm, NativeMap.key(args)); + NativeMap.loadFromIterable(cx, f.getDeclarationScope(), nm, NativeMap.key(args)); } + ScriptRuntime.setBuiltinProtoAndParent(nm, f, nt, s, TopLevel.Builtins.WeakMap); return nm; } - private static Object js_delete(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_delete( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "delete").js_delete(NativeMap.key(args)); } @@ -80,7 +86,8 @@ private Object js_delete(Object key) { return map.remove(key) != null; } - private static Object js_get(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_get( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "get").js_get(NativeMap.key(args)); } @@ -97,7 +104,8 @@ private Object js_get(Object key) { return result; } - private static Object js_has(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_has( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "has").js_has(NativeMap.key(args)); } @@ -108,7 +116,8 @@ private Object js_has(Object key) { return map.containsKey(key); } - private static Object js_set(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_set( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { return realThis(thisObj, "set") .js_set(NativeMap.key(args), args.length > 1 ? args[1] : Undefined.instance); } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeWeakSet.java b/rhino/src/main/java/org/mozilla/javascript/NativeWeakSet.java index 292ec94d518..0c6330f018a 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeWeakSet.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeWeakSet.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; + import java.io.IOException; import java.io.ObjectInputStream; import java.util.WeakHashMap; @@ -21,33 +25,32 @@ public class NativeWeakSet extends ScriptableObject { private static final String CLASS_NAME = "WeakSet"; + private static final ClassDescriptor DESCRIPTOR; + + static { + DESCRIPTOR = + new ClassDescriptor.Builder( + CLASS_NAME, + 0, + ClassDescriptor.typeError(), + NativeWeakSet::jsConstructor) + .withMethod(PROTO, "add", 1, NativeWeakSet::js_add) + .withMethod(PROTO, "delete", 1, NativeWeakSet::js_delete) + .withMethod(PROTO, "has", 1, NativeWeakSet::js_has) + .withProp( + PROTO, + SymbolKey.TO_STRING_TAG, + value(CLASS_NAME, DONTENUM | READONLY)) + .withProp(CTOR, SymbolKey.SPECIES, ScriptRuntimeES6::symbolSpecies) + .build(); + } + private boolean instanceOfWeakSet = false; private transient WeakHashMap map = new WeakHashMap<>(); static Object init(Context cx, VarScope scope, boolean sealed) { - LambdaConstructor constructor = - new LambdaConstructor( - scope, - CLASS_NAME, - 0, - LambdaConstructor.CONSTRUCTOR_NEW, - NativeWeakSet::jsConstructor); - constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); - - constructor.definePrototypeMethod(scope, "add", 1, NativeWeakSet::js_add); - constructor.definePrototypeMethod(scope, "delete", 1, NativeWeakSet::js_delete); - constructor.definePrototypeMethod(scope, "has", 1, NativeWeakSet::js_has); - - constructor.definePrototypeProperty( - SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); - - ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor); - if (sealed) { - constructor.sealObject(); - ((ScriptableObject) constructor.getPrototypeProperty()).sealObject(); - } - return constructor; + return DESCRIPTOR.buildConstructor(cx, scope, new NativeObject(), sealed); } @Override @@ -55,16 +58,19 @@ public String getClassName() { return CLASS_NAME; } - private static Scriptable jsConstructor(Context cx, VarScope scope, Object[] args) { + private static Scriptable jsConstructor( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeWeakSet ns = new NativeWeakSet(); ns.instanceOfWeakSet = true; if (args.length > 0) { - NativeSet.loadFromIterable(cx, scope, ns, NativeMap.key(args)); + NativeSet.loadFromIterable(cx, f.getDeclarationScope(), ns, NativeMap.key(args)); } + ScriptRuntime.setBuiltinProtoAndParent(ns, f, nt, s, TopLevel.Builtins.WeakSet); return ns; } - private static Object js_add(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_add( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeWeakSet realThis = realThis(thisObj, "add"); var k = NativeMap.key(args); return realThis.js_add(k); @@ -84,7 +90,8 @@ private Object js_add(Object key) { return this; } - private static Object js_delete(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_delete( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeWeakSet realThis = realThis(thisObj, "add"); var arg = NativeMap.key(args); return realThis.js_delete(arg); @@ -97,7 +104,8 @@ private Object js_delete(Object key) { return map.remove(key) != null; } - private static Object js_has(Context cx, VarScope scope, Object thisObj, Object[] args) { + private static Object js_has( + Context cx, JSFunction f, Object nt, VarScope s, Object thisObj, Object[] args) { NativeWeakSet realThis = realThis(thisObj, "add"); var arg = NativeMap.key(args); return realThis.js_has(arg); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeWith.java b/rhino/src/main/java/org/mozilla/javascript/NativeWith.java deleted file mode 100644 index 234f3848c55..00000000000 --- a/rhino/src/main/java/org/mozilla/javascript/NativeWith.java +++ /dev/null @@ -1,205 +0,0 @@ -/* -*- 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 java.io.Serializable; - -/** - * This class implements the object lookup required for the {@code with} statement. It simply - * delegates every action to its prototype except for operations on its parent. - */ -public class NativeWith implements Scriptable, SymbolScriptable, IdFunctionCall, Serializable { - private static final long serialVersionUID = 1L; - - static void init(TopLevel scope, boolean sealed) { - NativeWith obj = new NativeWith(); - - obj.setParentScope(scope); - obj.setPrototype(ScriptableObject.getObjectPrototype(scope)); - - IdFunctionObject ctor = new IdFunctionObject(obj, FTAG, Id_constructor, "With", 0, scope); - ctor.markAsConstructor(obj); - if (sealed) { - ctor.sealObject(); - } - ctor.exportAsScopeProperty(); - } - - private NativeWith() {} - - protected NativeWith(VarScope parent, Scriptable prototype) { - this.parent = parent; - this.prototype = prototype; - } - - @Override - public String getClassName() { - return "With"; - } - - @Override - public boolean has(String id, Scriptable start) { - return prototype.has(id, prototype); - } - - @Override - public boolean has(Symbol key, Scriptable start) { - if (prototype instanceof SymbolScriptable) { - return ((SymbolScriptable) prototype).has(key, prototype); - } - return false; - } - - @Override - public boolean has(int index, Scriptable start) { - return prototype.has(index, prototype); - } - - @Override - public Object get(String id, Scriptable start) { - if (start == this) { - start = prototype; - } - return prototype.get(id, start); - } - - @Override - public Object get(Symbol key, Scriptable start) { - if (start == this) { - start = prototype; - } - if (prototype instanceof SymbolScriptable) { - return ((SymbolScriptable) prototype).get(key, start); - } - return Scriptable.NOT_FOUND; - } - - @Override - public Object get(int index, Scriptable start) { - if (start == this) { - start = prototype; - } - return prototype.get(index, start); - } - - @Override - public void put(String id, Scriptable start, Object value) { - if (start == this) start = prototype; - prototype.put(id, start, value); - } - - @Override - public void put(Symbol symbol, Scriptable start, Object value) { - if (start == this) { - start = prototype; - } - if (prototype instanceof SymbolScriptable) { - ((SymbolScriptable) prototype).put(symbol, start, value); - } - } - - @Override - public void put(int index, Scriptable start, Object value) { - if (start == this) start = prototype; - prototype.put(index, start, value); - } - - @Override - public void delete(String id) { - prototype.delete(id); - } - - @Override - public void delete(Symbol key) { - if (prototype instanceof SymbolScriptable) { - ((SymbolScriptable) prototype).delete(key); - } - } - - @Override - public void delete(int index) { - prototype.delete(index); - } - - @Override - public Scriptable getPrototype() { - return prototype; - } - - @Override - public void setPrototype(Scriptable prototype) { - this.prototype = prototype; - } - - @Override - public VarScope getParentScope() { - return parent; - } - - @Override - public void setParentScope(VarScope parent) { - this.parent = parent; - } - - @Override - public Object[] getIds() { - return prototype.getIds(); - } - - @Override - public Object getDefaultValue(Class typeHint) { - return prototype.getDefaultValue(typeHint); - } - - @Override - public boolean hasInstance(Scriptable value) { - return prototype.hasInstance(value); - } - - /** Must return null to continue looping or the final collection result. */ - protected Object updateDotQuery(boolean value) { - // NativeWith itself does not support it - throw new IllegalStateException(); - } - - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, VarScope scope, Scriptable thisObj, Object[] args) { - if (f.hasTag(FTAG)) { - if (f.methodId() == Id_constructor) { - throw Context.reportRuntimeErrorById("msg.cant.call.indirect", "With"); - } - } - throw f.unknown(); - } - - static boolean isWithFunction(Object functionObj) { - if (functionObj instanceof IdFunctionObject) { - IdFunctionObject f = (IdFunctionObject) functionObj; - return f.hasTag(FTAG) && f.methodId() == Id_constructor; - } - return false; - } - - static Object newWithSpecial(Context cx, VarScope scope, Object[] args) { - ScriptRuntime.checkDeprecated(cx, "With"); - TopLevel top = ScriptableObject.getTopLevelScope(scope); - var obj = - args.length == 0 - ? ScriptableObject.getObjectPrototype(scope) - : ScriptRuntime.toObject(cx, scope, args[0]); - var newWith = new WithScope(top, obj); - return newWith; - } - - private static final Object FTAG = "With"; - - private static final int Id_constructor = 1; - - protected Scriptable prototype; - protected VarScope parent; -} diff --git a/rhino/src/main/java/org/mozilla/javascript/Node.java b/rhino/src/main/java/org/mozilla/javascript/Node.java index e3d52b221ad..c2c1d288922 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Node.java +++ b/rhino/src/main/java/org/mozilla/javascript/Node.java @@ -8,6 +8,7 @@ import java.math.BigInteger; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; @@ -70,7 +71,19 @@ public class Node implements Iterable { SUPER_PROPERTY_ACCESS = 31, NUMBER_OF_SPREAD = 32, OBJECT_REST_PROP = 33, // marks a CALL node as object rest operation - LAST_PROP = OBJECT_REST_PROP, + SUPER_CONSTRUCTOR_CALL = 34, // marks a CALL as super() in derived constructor + LITERAL_INDEX_PROP = 35, // int index into the shared literals table + CLASS_METHODS_PROP = 36, // String[] of method names for class definitions + CLASS_STATIC_METHODS_PROP = 37, // String[] of static method names + CLASS_STATIC_FIELDS_PROP = 38, // String[] of static field names + CLASS_STATIC_COMPUTED_FIELDS_COUNT = 39, // int count of static computed fields + PRIVATE_FIELD_INIT_PROP = 40, // marks a synthesized init of a class private field + CLASS_COMPUTED_FIELD_KEYS_COUNT = 41, // int count of instance computed field keys + SUPER_CONSTRUCTOR_SPREAD_CALL = 42, // super() whose single argument is a spread + CLASS_METHOD_KINDS_PROP = 43, // int[] per-method kind: 0=method,1=getter,2=setter + CLASS_STATIC_METHOD_KINDS_PROP = 44, // int[] per-static-method kind + FIELD_KIND_PROP = 45, // 0=value, 1=getter, 2=setter (for DEFINE_FIELD) + LAST_PROP = FIELD_KIND_PROP, FIRST_PROP = FUNCTION_PROP; // values of ISNUMBER_PROP to specify @@ -78,8 +91,7 @@ public class Node implements Iterable { public static final int BOTH = 0, LEFT = 1, RIGHT = 2; public static final int // values for SPECIALCALL_PROP NON_SPECIALCALL = 0, - SPECIALCALL_EVAL = 1, - SPECIALCALL_WITH = 2; + SPECIALCALL_EVAL = 1; public static final int // flags for INCRDECR_PROP DECR_FLAG = 0x1, POST_FLAG = 0x2; @@ -460,6 +472,20 @@ static String propName(int propType) { return "number_of_spread"; case OBJECT_REST_PROP: return "object_rest_prop"; + case LITERAL_INDEX_PROP: + return "literal_index_prop"; + case PRIVATE_FIELD_INIT_PROP: + return "private_field_init_prop"; + case CLASS_COMPUTED_FIELD_KEYS_COUNT: + return "class_computed_field_keys_count"; + case SUPER_CONSTRUCTOR_SPREAD_CALL: + return "super_constructor_spread_call"; + case CLASS_METHOD_KINDS_PROP: + return "class_method_kinds_prop"; + case CLASS_STATIC_METHOD_KINDS_PROP: + return "class_static_method_kinds_prop"; + case FIELD_KIND_PROP: + return "field_kind_prop"; default: Kit.codeBug(); @@ -605,14 +631,20 @@ public static Node newTarget() { } public final int labelId() { - if ((type != Token.TARGET) && (type != Token.YIELD) && (type != Token.YIELD_STAR)) { + if ((type != Token.TARGET) + && (type != Token.YIELD) + && (type != Token.YIELD_STAR) + && (type != Token.AWAIT)) { Kit.codeBug(); } return getIntProp(LABEL_ID_PROP, -1); } public void labelId(int labelId) { - if ((type != Token.TARGET) && (type != Token.YIELD) && (type != Token.YIELD_STAR)) { + if ((type != Token.TARGET) + && (type != Token.YIELD) + && (type != Token.YIELD_STAR) + && (type != Token.AWAIT)) { Kit.codeBug(); } putIntProp(LABEL_ID_PROP, labelId); @@ -969,7 +1001,7 @@ public boolean hasSideEffects() { case Token.ASSIGN_EXP: case Token.ASSIGN_NULLISH: case Token.ENTERWITH: - case Token.LEAVEWITH: + case Token.LEAVE_SCOPE: case Token.RETURN: case Token.GOTO: case Token.IFEQ: @@ -1056,6 +1088,152 @@ private void resetTargets_r() { } } + /** + * Returns a deep copy of this node and every descendant, suitable for placing the copy at a + * second location in the same compilation unit (for example, inlining a finally block at + * multiple exit paths). + * + *

The copy is built in two phases: + * + *

    + *
  1. Each node is allocated via {@link #shallowCopy()} and its children are recursively + * cloned. The original-to-copy mapping is recorded in an identity map. + *
  2. The copy is walked and any cross-reference pointing at a node inside the original + * subtree is rewritten to its corresponding copy. Cross-references that point outside the + * subtree (for example a {@code break} whose target is in the surrounding loop) are + * preserved as-is. + *
+ * + *

{@code TARGET}, {@code YIELD}, {@code YIELD_STAR} and {@code AWAIT} nodes in the copy have + * their {@code labelId} reset to {@code -1} so that code generation will allocate new labels + * rather than colliding with the original. + * + *

This is a structural copy. {@link org.mozilla.javascript.ast.Symbol} objects in scope + * tables are shared with the original; the copy does not introduce a new lexical scope. If a + * subclass that carries cross-reference state has not overridden {@link #shallowCopy()}, this + * method will throw {@link UnsupportedOperationException}. + * + * @return the root of the freshly-cloned subtree, with no parent and no {@code next} sibling + */ + public final Node cloneTree() { + IdentityHashMap map = new IdentityHashMap<>(); + Node copy = cloneStructure(map); + copy.fixupReferences(map); + return copy; + } + + /** + * Recursively allocates a copy of this node and its descendants, recording each {@code + * original→copy} pair in {@code map}. Walks both the linked-list child chain and any named + * child fields declared by subclasses (via {@link #cloneNamedChildren}). + * + *

Public so {@link org.mozilla.javascript.ast.AstNode} subclasses (which sit in another + * package and access children through abstract supertype references) can recurse into their + * named-field children that are not part of the linked-list child chain. End users should call + * {@link #cloneTree()} instead. + */ + public Node cloneStructure(IdentityHashMap map) { + Node copy = shallowCopy(); + map.put(this, copy); + if (copy.type == Token.TARGET + || copy.type == Token.YIELD + || copy.type == Token.YIELD_STAR + || copy.type == Token.AWAIT) { + copy.putIntProp(LABEL_ID_PROP, -1); + } + Node prev = null; + for (Node child = first; child != null; child = child.next) { + Node childCopy = child.cloneStructure(map); + childCopy.next = null; + if (prev == null) { + copy.first = childCopy; + } else { + prev.next = childCopy; + } + prev = childCopy; + } + copy.last = prev; + cloneNamedChildren(copy, map); + return copy; + } + + /** + * Subclass hook to deep-clone named-field children that are not part of the linked-list child + * chain. Called after {@link #shallowCopy()} has populated {@code copy} with raw (shared) + * references and after the linked-list children have been cloned. + * + *

Implementations recurse via {@link #cloneStructure(IdentityHashMap)} on each named child + * of the original and assign the resulting copy onto the corresponding field of {@code copy}. + * The default implementation does nothing. + */ + protected void cloneNamedChildren(Node copy, IdentityHashMap map) {} + + /** + * Allocates a fresh node of the same dynamic type as {@code this}, copying scalar state (node + * type, line/column, property list) but not children, the {@code next} sibling, or any + * cross-reference fields. Cross-references are remapped by {@link #fixupReferences} after the + * full structure has been cloned. + * + *

Subclasses that carry typed fields beyond what {@link #copyBaseFields(Node, Node)} + * captures must override this method. The base implementation refuses to operate on subclass + * instances rather than silently producing a degraded copy. + */ + protected Node shallowCopy() { + if (getClass() != Node.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + Node copy = new Node(type); + copyBaseFields(this, copy); + return copy; + } + + /** + * Copies the scalar fields defined on {@link Node} (line, column, and a fresh chain of + * property-list entries with shared values) from {@code src} into {@code dst}. Property values + * that are themselves {@link Node} references are remapped later by {@link + * #fixupReferences(IdentityHashMap)}. + */ + protected static void copyBaseFields(Node src, Node dst) { + dst.lineno = src.lineno; + dst.column = src.column; + PropListItem head = null; + PropListItem tail = null; + for (PropListItem p = src.propListHead; p != null; p = p.next) { + PropListItem c = new PropListItem(); + c.type = p.type; + c.intValue = p.intValue; + c.objectValue = p.objectValue; + if (head == null) { + head = c; + } else { + tail.next = c; + } + tail = c; + } + dst.propListHead = head; + } + + /** + * Walks this node and all descendants of the cloned subtree, rewriting any cross-reference that + * lands in {@code map} (i.e. points at a node in the original subtree) to the corresponding + * copy. Subclasses with typed cross-references override this and chain to {@code + * super.fixupReferences(map)}. + */ + protected void fixupReferences(IdentityHashMap map) { + for (PropListItem p = propListHead; p != null; p = p.next) { + if (p.objectValue instanceof Node) { + Node mapped = map.get(p.objectValue); + if (mapped != null) { + p.objectValue = mapped; + } + } + } + for (Node child = first; child != null; child = child.next) { + child.fixupReferences(map); + } + } + @Override public String toString() { if (Token.printTrees) { @@ -1186,9 +1364,6 @@ private void toString(Map printIds, StringBuilder sb) { case SPECIALCALL_EVAL: sb.append("eval"); break; - case SPECIALCALL_WITH: - sb.append("with"); - break; default: // NON_SPECIALCALL should not be stored throw Kit.codeBug(); diff --git a/rhino/src/main/java/org/mozilla/javascript/NodeTransformer.java b/rhino/src/main/java/org/mozilla/javascript/NodeTransformer.java index 2f754175c0c..124523028ce 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NodeTransformer.java +++ b/rhino/src/main/java/org/mozilla/javascript/NodeTransformer.java @@ -62,6 +62,18 @@ private void transformCompilationUnit(ScriptNode tree, boolean inStrictMode) { // uncomment to print tree before transformation if (Token.printTrees) System.out.println(tree.toStringTree(tree)); transformCompilationUnit_r(tree, tree, tree, createScopeObjects, inStrictMode); + + // Generator parameter init block (default parameters and destructuring + // for generator / async functions) lives outside the main tree, but + // still needs the same transformations (e.g. lowering destructuring + // LETEXPR nodes into SCOPEEXPR / COMMA form). + if (tree instanceof FunctionNode) { + Node paramInitBlock = ((FunctionNode) tree).getGeneratorParamInitBlock(); + if (paramInitBlock != null) { + transformCompilationUnit_r( + tree, paramInitBlock, tree, createScopeObjects, inStrictMode); + } + } } private void transformCompilationUnit_r( @@ -118,7 +130,18 @@ private void transformCompilationUnit_r( { loops.push(node); Node leave = node.getNext(); - if (leave.getType() != Token.LEAVEWITH) { + if (leave.getType() != Token.LEAVE_SCOPE) { + Kit.codeBug(); + } + loopEnds.push(leave); + break; + } + + case Token.SCOPE_BLOCK: + { + loops.push(node); + Node leave = node.getNext(); + if (leave.getType() != Token.LEAVE_SCOPE) { Kit.codeBug(); } loopEnds.push(leave); @@ -138,7 +161,7 @@ private void transformCompilationUnit_r( } case Token.TARGET: - case Token.LEAVEWITH: + case Token.LEAVE_SCOPE: if (!loopEnds.isEmpty() && loopEnds.peek() == node) { loopEnds.pop(); loops.pop(); @@ -147,6 +170,7 @@ private void transformCompilationUnit_r( case Token.YIELD: case Token.YIELD_STAR: + case Token.AWAIT: ((FunctionNode) tree).addResumptionPoint(node); break; @@ -170,20 +194,25 @@ private void transformCompilationUnit_r( // Iterate from the top of the stack (most recently inserted) and down for (Node n : loops) { int elemtype = n.getType(); - if (elemtype == Token.TRY || elemtype == Token.WITH) { - Node unwind; - if (elemtype == Token.TRY) { - Jump jsrnode = new Jump(Token.JSR); - jsrnode.target = ((Jump) n).getFinally(); - unwind = jsrnode; - } else { - unwind = new Node(Token.LEAVEWITH); - } + if (elemtype == Token.TRY + || elemtype == Token.WITH + || elemtype == Token.SCOPE_BLOCK) { if (unwindBlock == null) { unwindBlock = new Node(Token.BLOCK); + } + if (elemtype == Token.TRY) { + Node returnTo = Node.newTarget(); + Node unwind = makeFinallyJump(n, returnTo); + unwind.setLineColumnNumber(node.getLineno(), node.getColumn()); + returnTo.setLineColumnNumber( + node.getLineno(), node.getColumn()); + unwindBlock.addChildToBack(unwind); + unwindBlock.addChildToBack(returnTo); + } else { + Node unwind = new Node(Token.LEAVE_SCOPE); unwind.setLineColumnNumber(node.getLineno(), node.getColumn()); + unwindBlock.addChildToBack(unwind); } - unwindBlock.addChildToBack(unwind); } } if (unwindBlock != null) { @@ -227,14 +256,18 @@ private void transformCompilationUnit_r( } int elemtype = n.getType(); - if (elemtype == Token.WITH) { - Node leave = new Node(Token.LEAVEWITH); + if (elemtype == Token.WITH + || elemtype == Token.SCOPEEXPR + || elemtype == Token.SCOPE_BLOCK) { + Node leave = new Node(Token.LEAVE_SCOPE); previous = addBeforeCurrent(parent, previous, node, leave); } else if (elemtype == Token.TRY) { - Jump tryNode = (Jump) n; - Jump jsrFinally = new Jump(Token.JSR); - jsrFinally.target = tryNode.getFinally(); - previous = addBeforeCurrent(parent, previous, node, jsrFinally); + Node returnTo = Node.newTarget(); + Node fjump = makeFinallyJump(n, returnTo); + fjump.setLineColumnNumber(node.getLineno(), node.getColumn()); + returnTo.setLineColumnNumber(node.getLineno(), node.getColumn()); + previous = addBeforeCurrent(parent, previous, node, fjump); + previous = addBeforeCurrent(parent, previous, node, returnTo); } } @@ -428,23 +461,42 @@ private void transformCompilationUnit_r( } } + private static Node makeFinallyJump(Node tryNode, Node returnTo) { + var finallyTarget = ((Jump) tryNode).getFinally(); + // We need to navigate from the finallyTarget to the the real + // finally block. The FINALLY node should be the next node, + // and then the block we want should be its child. + var finallyNode = finallyTarget.getNext(); + var finallyBlock = finallyNode.getFirstChild(); + var ourTarget = Node.newTarget(); + var returnGoto = new Jump(Token.GOTO); + returnGoto.target = returnTo; + + tryNode.addChildAfter(returnGoto, finallyNode); + tryNode.addChildAfter(finallyBlock.cloneTree(), finallyNode); + tryNode.addChildAfter(ourTarget, finallyNode); + Jump jumpNode = new Jump(Token.GOTO); + jumpNode.target = ourTarget; + + return jumpNode; + } + protected void visitNew(Node node, ScriptNode tree) {} protected void visitCall(Node node, ScriptNode tree) {} - protected Node visitLet(boolean createWith, Node parent, Node previous, Node scopeNode) { + protected Node visitLet(boolean createScope, Node parent, Node previous, Node scopeNode) { Node vars = scopeNode.getFirstChild(); Node body = vars.getNext(); scopeNode.removeChild(vars); scopeNode.removeChild(body); boolean isExpression = scopeNode.getType() == Token.LETEXPR; Node result; - Node newVars; - if (createWith) { - result = new Node(isExpression ? Token.WITHEXPR : Token.BLOCK); + Node newVars = new Node(Token.ENTER_SCOPE); + if (createScope) { + result = new Node(isExpression ? Token.SCOPEEXPR : Token.BLOCK); result = replaceCurrent(parent, previous, scopeNode, result); ArrayList list = new ArrayList<>(); - Node objectLiteral = new Node(Token.OBJECTLIT); for (Node v = vars.getFirstChild(); v != null; v = v.getNext()) { Node current = v; if (current.getType() == Token.LETEXPR) { @@ -464,7 +516,7 @@ protected Node visitLet(boolean createWith, Node parent, Node previous, Node sco if (destructuringNames != null) { list.addAll(destructuringNames); for (int i = 0; i < destructuringNames.size(); i++) { - objectLiteral.addChildToBack(new Node(Token.VOID, Node.newNumber(0.0))); + newVars.addChildToBack(new Node(Token.VOID, Node.newNumber(0.0))); } } // Process all NAME children of the inner LET node (not just the first) @@ -477,7 +529,7 @@ protected Node visitLet(boolean createWith, Node parent, Node previous, Node sco if (init == null) { init = new Node(Token.VOID, Node.newNumber(0.0)); } - objectLiteral.addChildToBack(init); + newVars.addChildToBack(init); } continue; // Already processed all children, move to next sibling of LETEXPR } @@ -487,13 +539,12 @@ protected Node visitLet(boolean createWith, Node parent, Node previous, Node sco if (init == null) { init = new Node(Token.VOID, Node.newNumber(0.0)); } - objectLiteral.addChildToBack(init); + newVars.addChildToBack(init); } - objectLiteral.putProp(Node.OBJECT_IDS_PROP, list.toArray()); - newVars = new Node(Token.ENTERWITH, objectLiteral); + newVars.putProp(Node.OBJECT_IDS_PROP, list.toArray()); result.addChildToBack(newVars); - result.addChildToBack(new Node(Token.WITH, body)); - result.addChildToBack(new Node(Token.LEAVEWITH)); + result.addChildToBack(new Node(Token.SCOPE_BLOCK, body)); + result.addChildToBack(new Node(Token.LEAVE_SCOPE)); } else { result = new Node(isExpression ? Token.COMMA : Token.BLOCK); result = replaceCurrent(parent, previous, scopeNode, result); diff --git a/rhino/src/main/java/org/mozilla/javascript/Parser.java b/rhino/src/main/java/org/mozilla/javascript/Parser.java index 2e5e1775f1d..1308a601452 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Parser.java +++ b/rhino/src/main/java/org/mozilla/javascript/Parser.java @@ -21,10 +21,12 @@ import org.mozilla.javascript.ast.Assignment; import org.mozilla.javascript.ast.AstNode; import org.mozilla.javascript.ast.AstRoot; +import org.mozilla.javascript.ast.AwaitExpression; import org.mozilla.javascript.ast.BigIntLiteral; import org.mozilla.javascript.ast.Block; import org.mozilla.javascript.ast.BreakStatement; import org.mozilla.javascript.ast.CatchClause; +import org.mozilla.javascript.ast.ClassNode; import org.mozilla.javascript.ast.Comment; import org.mozilla.javascript.ast.ComputedPropertyKey; import org.mozilla.javascript.ast.ConditionalExpression; @@ -144,9 +146,17 @@ public class Parser { // during function parsing. See PerFunctionVariables class below. ScriptNode currentScriptOrFn; private boolean insideMethod; + private boolean insideClassConstructor; Scope currentScope; private int endFlags; private boolean inForInit; // bound temporarily during forStatement() + private boolean + inSingleStatementContext; // true when parsing a single-statement body (if/while/for + private boolean inSingleStatementDeclContext; // true when parsing a + // single-statement body + // that allows lexical + // declarations. without + // braces) private Map labelSet; private List loopSet; private List loopAndSwitchSet; @@ -486,6 +496,11 @@ boolean insideFunctionBody() { return nestingOfFunction != 0; } + private boolean insideAsyncFunction() { + return currentScriptOrFn instanceof FunctionNode + && ((FunctionNode) currentScriptOrFn).isAsync(); + } + boolean insideFunctionParams() { return nestingOfFunctionParams != 0; } @@ -743,9 +758,10 @@ private AstNode parseFunctionBody(int type, FunctionNode fnNode) throws IOExcept reportError("msg.default.args.use.strict"); } // "use strict" not allowed with non-simple params - if (fnNode.hasRestParameter()) { + if (fnNode.hasRestParameter() || fnNode.hasDestructuring()) { reportError("msg.rest.param.use.strict"); } + inUseStrictDirective = true; fnNode.setInStrictMode(true); } @@ -893,9 +909,14 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { addError("msg.dup.param.strict", paramName); paramNames.add(paramName); } + if ("await".equals(paramName) && fnNode.isAsync()) { + reportError("msg.reserved.id", "await"); + } if (matchToken(Token.ASSIGN, true)) { - if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { + if (wasRest) { + reportError("msg.rest.default.value"); + } else if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { fnNode.putDefaultParams(paramName, assignExpr()); } else { reportError("msg.default.args"); @@ -935,11 +956,32 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { } private FunctionNode function(int type) throws IOException { - return function(type, false); + return function(type, false, false); } private FunctionNode function(int type, boolean isMethodDefiniton) throws IOException { - boolean isGenerator = false; + return function(type, isMethodDefiniton, false); + } + + private FunctionNode function(int type, boolean isMethodDefiniton, boolean isAsync) + throws IOException { + return function(type, isMethodDefiniton, isAsync, false, false); + } + + private FunctionNode function( + int type, boolean isMethodDefiniton, boolean isAsync, boolean isClassConstructor) + throws IOException { + return function(type, isMethodDefiniton, isAsync, false, isClassConstructor); + } + + private FunctionNode function( + int type, + boolean isMethodDefiniton, + boolean isAsync, + boolean isGeneratorMethod, + boolean isClassConstructor) + throws IOException { + boolean isGenerator = isGeneratorMethod; int syntheticType = type; int baseLineno = lineNumber(); // line number where source starts int functionSourceStart = ts.tokenBeg; // start of "function" kwd @@ -991,8 +1033,13 @@ private FunctionNode function(int type, boolean isMethodDefiniton) throws IOExce if (syntheticType != FunctionNode.FUNCTION_EXPRESSION && name != null && name.length() > 0) { - // Function statements define a symbol in the enclosing scope - defineSymbol(Token.FUNCTION, name.getIdentifier()); + if (type == FunctionNode.FUNCTION_BLOCK_SCOPED) { + // Block-scoped function in strict mode: define as let-like binding + defineSymbol(Token.LET, name.getIdentifier()); + } else { + // Function statements define a symbol in the enclosing scope + defineSymbol(Token.FUNCTION, name.getIdentifier()); + } } FunctionNode fnNode = new FunctionNode(functionSourceStart, name); @@ -1001,6 +1048,12 @@ private FunctionNode function(int type, boolean isMethodDefiniton) throws IOExce if (isGenerator) { fnNode.setIsES6Generator(); } + if (isAsync) { + fnNode.setIsAsync(); + } + if (isClassConstructor) { + fnNode.setIsClassConstructor(true); + } if (lpPos != -1) fnNode.setLp(lpPos - functionSourceStart); fnNode.setJsDocNode(getAndResetJsDoc()); @@ -1008,6 +1061,8 @@ private FunctionNode function(int type, boolean isMethodDefiniton) throws IOExce PerFunctionVariables savedVars = new PerFunctionVariables(fnNode); boolean wasInsideMethod = insideMethod; insideMethod = isMethodDefiniton; + boolean wasInsideClassConstructor = insideClassConstructor; + insideClassConstructor = isClassConstructor; try { parseFunctionParams(fnNode); AstNode body = parseFunctionBody(type, fnNode); @@ -1026,6 +1081,7 @@ private FunctionNode function(int type, boolean isMethodDefiniton) throws IOExce } finally { savedVars.restore(); insideMethod = wasInsideMethod; + insideClassConstructor = wasInsideClassConstructor; } if (memberExprNode != null) { @@ -1057,7 +1113,432 @@ private FunctionNode function(int type, boolean isMethodDefiniton) throws IOExce return fnNode; } - private AstNode arrowFunction(AstNode params, int startLine, int startColumn) + private ClassNode parseClass(boolean isStatement) throws IOException { + int pos = ts.tokenBeg; + int lineno = lineNumber(); + int column = columnNumber(); + + ClassNode classNode = new ClassNode(pos); + classNode.setLineColumnNumber(lineno, column); + classNode.setIsStatement(isStatement); + + // Parse optional class name (required for declarations) + Name className = null; + if (matchToken(Token.NAME, true)) { + className = createNameNode(true, Token.NAME); + classNode.setClassName(className); + } else if (isStatement) { + reportError("msg.unnamed.class.stmt"); + } + + // Parse optional extends clause + if (matchToken(Token.EXTENDS, true)) { + classNode.setSuperClass(assignExpr()); + } + + mustMatchToken(Token.LC, "msg.no.brace.class", true); + + // Parse class body - always strict mode + boolean savedStrict = inUseStrictDirective; + inUseStrictDirective = true; + + FunctionNode constructor = null; + + // Look for constructor in the class body + while (peekToken() != Token.RC && peekToken() != Token.EOF) { + if (peekToken() == Token.SEMI) { + consumeToken(); + continue; + } + + // Check for 'static' modifier. It is a Token.RESERVED in strict mode; other + // reserved words that reach this point are used as method/field names and are + // handled by the member name parsing below. + boolean isStatic = false; + if (peekToken() == Token.RESERVED && "static".equals(ts.getString())) { + consumeToken(); + int nextTt = peekToken(); + if (nextTt == Token.LP) { + // 'static' used as an instance method name: static() {} + FunctionNode method = function(FunctionNode.FUNCTION_EXPRESSION, true); + classNode.addMethod("static", method); + continue; + } else if (nextTt == Token.NAME + || nextTt == Token.PRIVATE_NAME + || nextTt == Token.STRING + || nextTt == Token.NUMBER + || nextTt == Token.BIGINT + || nextTt == Token.LB + || nextTt == Token.MUL + || isKeywordAsIdentifierName(nextTt)) { + isStatic = true; + } else { + reportError("msg.unexpected.token"); + continue; + } + } + + // Check for generator modifier '*' + boolean isGenerator = false; + if (peekToken() == Token.MUL) { + consumeToken(); + isGenerator = true; + } + + // Parse the member name: identifier, private #name, string, number, or [computed] + String memberName = null; + boolean isComputed = false; + boolean isStringOrNumericName = false; + boolean isPrivateName = false; + AstNode computedKeyExpr = null; + int tt = peekToken(); + + if (tt == Token.NAME) { + consumeToken(); + memberName = ts.getString(); + } else if (tt == Token.PRIVATE_NAME) { + consumeToken(); + memberName = ts.getString(); + isPrivateName = true; + } else if (tt == Token.STRING) { + consumeToken(); + memberName = ts.getString(); + isStringOrNumericName = true; + } else if (tt == Token.NUMBER || tt == Token.BIGINT) { + consumeToken(); + memberName = numericMemberName(tt); + isStringOrNumericName = true; + } else if (tt == Token.LB) { + // Computed property: [expression] + isComputed = true; + consumeToken(); + computedKeyExpr = assignExpr(); + mustMatchToken(Token.RB, "msg.no.bracket.index", true); + } else if (isKeywordAsIdentifierName(tt)) { + // Reserved words are valid IdentifierNames, so they can be used as + // method or field names (e.g., `class A { return() {} }`). + consumeToken(); + memberName = ts.getString(); + } else { + reportError("msg.unexpected.token"); + consumeToken(); + continue; + } + + // Check for 'async' modifier before method name + boolean isAsync = false; + if (!isComputed && "async".equals(memberName)) { + int nextTt = peekToken(); + if (nextTt == Token.NAME + || nextTt == Token.PRIVATE_NAME + || nextTt == Token.STRING + || nextTt == Token.NUMBER + || nextTt == Token.BIGINT + || nextTt == Token.LB + || nextTt == Token.MUL + || isKeywordAsIdentifierName(nextTt)) { + isAsync = true; + isStringOrNumericName = false; + // Check for async generator: async *method() + if (peekToken() == Token.MUL) { + consumeToken(); + isGenerator = true; + } + // Re-parse the actual member name + tt = peekToken(); + if (tt == Token.NAME) { + consumeToken(); + memberName = ts.getString(); + } else if (tt == Token.PRIVATE_NAME) { + consumeToken(); + memberName = ts.getString(); + isPrivateName = true; + } else if (tt == Token.STRING) { + consumeToken(); + memberName = ts.getString(); + isStringOrNumericName = true; + } else if (tt == Token.NUMBER || tt == Token.BIGINT) { + consumeToken(); + memberName = numericMemberName(tt); + isStringOrNumericName = true; + } else if (tt == Token.LB) { + isComputed = true; + memberName = null; + consumeToken(); + computedKeyExpr = assignExpr(); + mustMatchToken(Token.RB, "msg.no.bracket.index", true); + } else if (isKeywordAsIdentifierName(tt)) { + consumeToken(); + memberName = ts.getString(); + } + } + } + + // Check for 'get' or 'set' accessor modifier before method name + ClassNode.ElementKind accessorKind = ClassNode.ElementKind.METHOD; + if (!isComputed + && !isAsync + && !isGenerator + && !isPrivateName + && ("get".equals(memberName) || "set".equals(memberName))) { + int nextTt = peekToken(); + if (nextTt == Token.NAME + || nextTt == Token.PRIVATE_NAME + || nextTt == Token.STRING + || nextTt == Token.NUMBER + || nextTt == Token.BIGINT + || nextTt == Token.LB + || isKeywordAsIdentifierName(nextTt)) { + accessorKind = + "get".equals(memberName) + ? ClassNode.ElementKind.GETTER + : ClassNode.ElementKind.SETTER; + isStringOrNumericName = false; + // Re-parse the actual member name + tt = nextTt; + if (tt == Token.NAME) { + consumeToken(); + memberName = ts.getString(); + } else if (tt == Token.PRIVATE_NAME) { + consumeToken(); + memberName = ts.getString(); + isPrivateName = true; + } else if (tt == Token.STRING) { + consumeToken(); + memberName = ts.getString(); + isStringOrNumericName = true; + } else if (tt == Token.NUMBER || tt == Token.BIGINT) { + consumeToken(); + memberName = numericMemberName(tt); + isStringOrNumericName = true; + } else if (tt == Token.LB) { + isComputed = true; + memberName = null; + consumeToken(); + computedKeyExpr = assignExpr(); + mustMatchToken(Token.RB, "msg.no.bracket.index", true); + } else if (isKeywordAsIdentifierName(tt)) { + consumeToken(); + memberName = ts.getString(); + } + } + } + + // Check for forbidden property names: + // - non-static properties named "constructor" (except the actual constructor) + // - static properties named "prototype" + if (!isComputed && isStatic && "prototype".equals(memberName)) { + reportError("msg.unexpected.token"); + } + + if (!isComputed + && !isStatic + && !isAsync + && !isGenerator + && !isPrivateName + && accessorKind == ClassNode.ElementKind.METHOD + && "constructor".equals(memberName) + && peekToken() == Token.LP) { + if (constructor != null) { + reportError("msg.dup.ctor"); + } + constructor = function(FunctionNode.FUNCTION_EXPRESSION, true, false, true); + } else if (peekToken() == Token.LP) { + if (!isComputed && !isStatic && "constructor".equals(memberName)) { + // Non-static `constructor` cannot be a method name if it has + // modifiers (get/set/async/*); the real constructor is handled + // above. Static methods named `constructor` are allowed. + reportError("msg.unexpected.token"); + } + FunctionNode method = + function( + FunctionNode.FUNCTION_EXPRESSION, + true, + isAsync, + isGenerator, + false); + if (accessorKind == ClassNode.ElementKind.GETTER) { + if (isAsync || isGenerator) { + reportError("msg.unexpected.token"); + } + if (method.getParamCount() != 0) { + reportError("msg.getter.no.parms"); + } + method.setFunctionIsGetterMethod(); + } else if (accessorKind == ClassNode.ElementKind.SETTER) { + if (isAsync || isGenerator) { + reportError("msg.unexpected.token"); + } + if (method.getParamCount() != 1 || method.hasRestParameter()) { + reportError("msg.setter.one.parm"); + } + method.setFunctionIsSetterMethod(); + } + if (isComputed) { + if (isStatic) { + classNode.addStaticComputedMethod(computedKeyExpr, method, accessorKind); + } else { + classNode.addComputedMethod(computedKeyExpr, method, accessorKind); + } + } else if (isPrivateName) { + if (isStatic && accessorKind != ClassNode.ElementKind.METHOD) { + // TODO: static private accessors not yet supported + reportError("msg.unexpected.token"); + } else if (classNode.isDuplicatePrivateMember( + memberName, accessorKind, isStatic)) { + reportError("msg.dup.private.name", memberName); + } else if (isStatic) { + classNode.addStaticPrivateMember(memberName, method, accessorKind); + } else { + classNode.addPrivateMember(memberName, method, accessorKind); + } + } else if (isStatic) { + classNode.addStaticMethod(memberName, method, accessorKind); + } else { + classNode.addMethod(memberName, method, accessorKind); + } + } else { + // Field declaration: name = expr; or name; + // Non-static and static fields both forbid PropName "constructor" + // (ES spec: early error for FieldDefinition with PropName "constructor"). + if (!isComputed && "constructor".equals(memberName)) { + reportError("msg.unexpected.token"); + } + AstNode initializer = null; + if (peekToken() == Token.ASSIGN) { + consumeToken(); + initializer = assignExpr(); + } + if (peekToken() == Token.SEMI) { + consumeToken(); + } + if (isPrivateName) { + if (classNode.isDuplicatePrivateMember( + memberName, ClassNode.ElementKind.METHOD, isStatic)) { + reportError("msg.dup.private.name", memberName); + } else if (isStatic) { + classNode.addStaticPrivateField(memberName, initializer); + } else { + classNode.addPrivateField(memberName, initializer); + } + } else if (isStatic) { + if (isComputed) { + classNode.addStaticComputedField(computedKeyExpr, initializer); + } else if (isStringOrNumericName) { + StringLiteral keyLiteral = new StringLiteral(); + keyLiteral.setValue(memberName); + classNode.addStaticComputedField(keyLiteral, initializer); + } else { + classNode.addStaticField(memberName, initializer); + } + } else { + if (isComputed) { + classNode.addComputedField(computedKeyExpr, initializer); + } else if (isStringOrNumericName) { + StringLiteral keyLiteral = new StringLiteral(); + keyLiteral.setValue(memberName); + classNode.addComputedField(keyLiteral, initializer); + } else { + classNode.addField(memberName, initializer); + } + } + } + } + + mustMatchToken(Token.RC, "msg.no.brace.class", true); + + inUseStrictDirective = savedStrict; + + // Synthesize a default constructor if none was provided + if (constructor == null) { + constructor = createDefaultConstructor(pos, classNode.getSuperClass() != null); + constructor.setIsClassConstructor(true); + } + + classNode.setConstructor(constructor); + + // For class declarations, define the name as a let-like binding + if (isStatement && className != null) { + defineSymbol(Token.LET, className.getIdentifier()); + } + + int end = ts.tokenEnd; + classNode.setLength(end - pos); + + // Set the constructor's raw source bounds to cover the entire class declaration, + // so that toString() on the constructor returns the class source per the ES spec. + constructor.setRawSourceBounds(pos, end); + + return classNode; + } + + private String numericMemberName(int tt) { + // PropName of a NumericLiteral is ToString of its numeric value, so a literal + // like 0b101 or 0xff becomes "5" or "255" rather than its raw source form. + if (tt == Token.BIGINT) { + return ScriptRuntime.bigIntToString(ts.getBigInt(), 10); + } + return ScriptRuntime.toString(ts.getNumber()); + } + + private FunctionNode createDefaultConstructor(int pos, boolean isDerived) { + FunctionNode fn = new FunctionNode(pos); + fn.setFunctionType(FunctionNode.FUNCTION_EXPRESSION); + fn.setMethodDefinition(true); + fn.setIsClassConstructor(true); + + Block body = new Block(pos); + + if (isDerived) { + // Derived default constructor: constructor(...args) { super(...args); } + fn.setHasRestParameter(true); + Name argsParam = new Name(pos, "args"); + fn.addParam(argsParam); + fn.putSymbol(new Symbol(Token.LP, "args")); + + KeywordLiteral superTarget = new KeywordLiteral(pos, 0); + superTarget.setType(Token.SUPER); + + Spread spread = new Spread(pos, 0); + spread.setExpression(new Name(pos, "args")); + + FunctionCall superCall = new FunctionCall(pos); + superCall.setTarget(superTarget); + List callArgs = new ArrayList<>(1); + callArgs.add(spread); + superCall.setArguments(callArgs); + + body.addStatement(new ExpressionStatement(superCall)); + } + + fn.setBody(body); + fn.setLength(0); + + return fn; + } + + private ParenthesizedExpression callArgsToArrowParams(FunctionCall call) { + int absoluteLp = call.getPosition() + call.getLp(); + int absoluteRp = call.getPosition() + call.getRp(); + List args = call.getArguments(); + AstNode inner; + if (args.isEmpty()) { + inner = new EmptyExpression(absoluteLp + 1, 0); + } else { + inner = args.get(0); + for (int i = 1; i < args.size(); i++) { + inner = new InfixExpression(Token.COMMA, inner, args.get(i), 0); + } + } + ParenthesizedExpression paren = + new ParenthesizedExpression(absoluteLp, absoluteRp - absoluteLp + 1, inner); + if (call.getIntProp(Node.TRAILING_COMMA, 0) == 1) { + paren.putIntProp(Node.TRAILING_COMMA, 1); + } + return paren; + } + + private AstNode arrowFunction(AstNode params, int startLine, int startColumn, boolean isAsync) throws IOException { int baseLineno = lineNumber(); // line number where source starts int functionSourceStart = @@ -1066,6 +1547,9 @@ private AstNode arrowFunction(AstNode params, int startLine, int startColumn) FunctionNode fnNode = new FunctionNode(functionSourceStart); fnNode.setFunctionType(FunctionNode.ARROW_FUNCTION); fnNode.setJsDocNode(getAndResetJsDoc()); + if (isAsync) { + fnNode.setIsAsync(); + } // Would prefer not to call createDestructuringAssignment until codegen, // but the symbol definitions have to happen now, before body is parsed. @@ -1215,7 +1699,10 @@ private void arrowFunctionParams( } fnNode.setHasRestParameter(true); AstNode restParam = ((Spread) params).getExpression(); - if (restParam instanceof Name) { + if (restParam instanceof Assignment) { + reportError("msg.rest.default.value"); + fnNode.addParam(makeErrorNode()); + } else if (restParam instanceof Name) { fnNode.addParam(restParam); String paramName = ((Name) restParam).getIdentifier(); defineSymbol(Token.LP, paramName); @@ -1462,8 +1949,16 @@ private AstNode statementHelper() throws IOException { case Token.FUNCTION: consumeToken(); + if (inUseStrictDirective + && compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { + return function(FunctionNode.FUNCTION_BLOCK_SCOPED); + } return function(FunctionNode.FUNCTION_EXPRESSION_STATEMENT); + case Token.CLASS: + consumeToken(); + return parseClass(true); + case Token.DEFAULT: pn = defaultXmlNamespace(); break; @@ -1525,6 +2020,10 @@ private IfStatement ifStatement() throws IOException { int pos = ts.tokenBeg, lineno = lineNumber(), elsePos = -1, column = columnNumber(); IfStatement pn = new IfStatement(pos); ConditionData data = condition(); + boolean isES6 = compilerEnv.getLanguageVersion() >= Context.VERSION_ES6; + boolean savedInSingleStatementContext = inSingleStatementContext; + inSingleStatementContext = isES6; + inSingleStatementDeclContext = inSingleStatementContext; AstNode ifTrue = getNextStatementAfterInlineComments(pn), ifFalse = null; if (matchToken(Token.ELSE, true)) { int tt = peekToken(); @@ -1533,8 +2032,12 @@ private IfStatement ifStatement() throws IOException { consumeToken(); } elsePos = ts.tokenBeg - pos; + inSingleStatementContext = isES6; + inSingleStatementDeclContext = inSingleStatementContext; ifFalse = statement(); } + inSingleStatementContext = savedInSingleStatementContext; + inSingleStatementDeclContext = savedInSingleStatementContext; int end = getNodeEnd(ifFalse != null ? ifFalse : ifTrue); pn.setLength(end - pos); pn.setCondition(data.condition); @@ -1646,7 +2149,13 @@ private WhileLoop whileLoop() throws IOException { ConditionData data = condition(); pn.setCondition(data.condition); pn.setParens(data.lp - pos, data.rp - pos); + boolean savedInSingleStatementContext = inSingleStatementContext; + boolean savedInSingleStatementDeclContext = inSingleStatementDeclContext; + inSingleStatementContext = compilerEnv.getLanguageVersion() >= Context.VERSION_ES6; + inSingleStatementDeclContext = false; AstNode body = getNextStatementAfterInlineComments(pn); + inSingleStatementContext = savedInSingleStatementContext; + inSingleStatementDeclContext = savedInSingleStatementDeclContext; pn.setLength(getNodeEnd(body) - pos); restoreRelativeLoopPosition(pn); pn.setBody(body); @@ -1664,7 +2173,13 @@ private DoLoop doLoop() throws IOException { pn.setLineColumnNumber(lineNumber(), columnNumber()); enterLoop(pn); try { + boolean savedInSingleStatementContext = inSingleStatementContext; + boolean savedInSingleStatementDeclContext = inSingleStatementDeclContext; + inSingleStatementContext = compilerEnv.getLanguageVersion() >= Context.VERSION_ES6; + inSingleStatementDeclContext = false; AstNode body = getNextStatementAfterInlineComments(pn); + inSingleStatementContext = savedInSingleStatementContext; + inSingleStatementDeclContext = savedInSingleStatementDeclContext; mustMatchToken(Token.WHILE, "msg.no.while.do", true); pn.setWhilePosition(ts.tokenBeg - pos); ConditionData data = condition(); @@ -1723,7 +2238,7 @@ private Loop forLoop() throws IOException { if (currentToken != Token.FOR) codeBug(); consumeToken(); int forPos = ts.tokenBeg, lineno = lineNumber(), column = columnNumber(); - boolean isForEach = false, isForIn = false, isForOf = false; + boolean isForEach = false, isForIn = false, isForOf = false, isForAwaitOf = false; int eachPos = -1, inPos = -1, lp = -1, rp = -1; AstNode init = null; // init is also foo in 'foo in object' AstNode cond = null; // cond is also object in 'foo in object' @@ -1733,8 +2248,15 @@ private Loop forLoop() throws IOException { Scope tempScope = new Scope(); pushScope(tempScope); // decide below what AST class to use try { - // See if this is a for each () instead of just a for () - if (matchToken(Token.NAME, true)) { + // See if this is a for-await-of loop: only allowed inside an async function + if (peekToken() == Token.NAME && "await".equals(ts.getString())) { + if (!insideAsyncFunction()) { + reportError("msg.bad.await"); + } + consumeToken(); + isForAwaitOf = true; + } else if (matchToken(Token.NAME, true)) { + // See if this is a for each () instead of just a for () if ("each".equals(ts.getString())) { isForEach = true; eachPos = ts.tokenBeg - forPos; @@ -1804,12 +2326,16 @@ && matchToken(Token.NAME, true) if (isForOf && isForEach) { reportError("msg.invalid.for.each"); } + if (isForAwaitOf && !isForOf) { + reportError("msg.bad.for.await"); + } fis.setIterator(init); fis.setIteratedObject(cond); fis.setInPosition(inPos); fis.setIsForEach(isForEach); fis.setEachPosition(eachPos); fis.setIsForOf(isForOf); + fis.setIsForAwaitOf(isForAwaitOf); pn = fis; } else { ForLoop fl = new ForLoop(forPos); @@ -1828,7 +2354,13 @@ && matchToken(Token.NAME, true) // break/continue statements to find the enclosing loop. enterLoop(pn); try { + boolean savedInSingleStatementContext = inSingleStatementContext; + boolean savedInSingleStatementDeclContext = inSingleStatementDeclContext; + inSingleStatementContext = compilerEnv.getLanguageVersion() >= Context.VERSION_ES6; + inSingleStatementDeclContext = false; AstNode body = getNextStatementAfterInlineComments(pn); + inSingleStatementContext = savedInSingleStatementContext; + inSingleStatementDeclContext = savedInSingleStatementDeclContext; pn.setLength(getNodeEnd(body) - forPos); restoreRelativeLoopPosition(pn); pn.setBody(body); @@ -2279,6 +2811,19 @@ && nowAllSet(before, endFlags, Node.END_YIELDS | Node.END_RETURNS_VALUE)) { return ret; } + private AstNode awaitExpression() throws IOException { + if (!insideAsyncFunction()) { + reportError("msg.bad.await"); + } + consumeToken(); // consume the "await" NAME token + int pos = ts.tokenBeg; + int lineno = lineNumber(), column = columnNumber(); + AstNode value = assignExpr(); + AwaitExpression node = new AwaitExpression(pos, getNodeEnd(value) - pos, value); + node.setLineColumnNumber(lineno, column); + return node; + } + private AstNode block() throws IOException { if (currentToken != Token.LC) codeBug(); consumeToken(); @@ -2286,6 +2831,10 @@ private AstNode block() throws IOException { Scope block = new Scope(pos); block.setLineColumnNumber(lineNumber(), columnNumber()); pushScope(block); + boolean savedInSingleStatementContext = inSingleStatementContext; + boolean savedInSingleStatementDeclContext = inSingleStatementDeclContext; + inSingleStatementContext = false; + inSingleStatementDeclContext = false; try { statements(block); mustMatchToken(Token.RC, "msg.no.brace.block", true); @@ -2293,6 +2842,8 @@ private AstNode block() throws IOException { return block; } finally { popScope(); + inSingleStatementContext = savedInSingleStatementContext; + inSingleStatementDeclContext = savedInSingleStatementDeclContext; } } @@ -2357,6 +2908,18 @@ private AstNode nameOrLabel() throws IOException { currentFlaggedToken |= TI_CHECK_LABEL; AstNode expr = expr(false); + if (expr instanceof FunctionNode) { + FunctionNode fn = (FunctionNode) expr; + if (fn.isAsync() + && fn.getFunctionType() == FunctionNode.FUNCTION_EXPRESSION + && fn.getFunctionName() != null + && fn.getFunctionName().length() > 0) { + fn.setFunctionType(FunctionNode.FUNCTION_EXPRESSION_STATEMENT); + defineSymbol(Token.FUNCTION, fn.getFunctionName().getIdentifier()); + return fn; + } + } + if (expr.getType() != Token.LABEL) { AstNode n = new ExpressionStatement(expr, !insideFunctionBody()); n.setLineColumnNumber(expr.getLineno(), expr.getColumn()); @@ -2461,6 +3024,9 @@ private VariableDeclaration variables(int declType, int pos, boolean isStatement reportError("msg.bad.id.strict", id); } } + if ("await".equals(ts.getString()) && insideAsyncFunction()) { + reportError("msg.reserved.id", "await"); + } defineSymbol(declType, ts.getString(), inForInit); } @@ -2541,6 +3107,16 @@ void defineSymbol(int declType, String name) { } void defineSymbol(int declType, String name, boolean ignoreNotInBlock) { + boolean isES6 = compilerEnv.getLanguageVersion() >= Context.VERSION_ES6; + + if (isES6) { + defineSymbolES6(declType, name, ignoreNotInBlock); + } else { + defineSymbolLegacy(declType, name, ignoreNotInBlock); + } + } + + void defineSymbolLegacy(int declType, String name, boolean ignoreNotInBlock) { if (name == null) { if (compilerEnv.isIdeMode()) { // be robust in IDE-mode return; @@ -2606,6 +3182,178 @@ else if (symDeclType == Token.LP) { } } + void defineSymbolES6(int declType, String name, boolean ignoreNotInBlock) { + if (name == null) { + if (compilerEnv.isIdeMode()) { // be robust in IDE-mode + return; + } + codeBug(); + } else if ("undefined".equals(name)) { + hasUndefinedBeenRedefined = true; + } + Scope definingScope = currentScope.getDefiningScope(name); + Symbol symbol = definingScope != null ? definingScope.getSymbol(name) : null; + Symbol varSymbol = currentScope.getVarSymbol(name); + int symDeclType = symbol != null ? symbol.getDeclType() : -1; + if (!isValidES6Redeclaration( + declType, symDeclType, symbol, varSymbol, currentScope, definingScope)) { + addError( + symDeclType == Token.CONST + ? "msg.const.redecl" + : symDeclType == Token.LET + ? "msg.let.redecl" + : symDeclType == Token.VAR + ? "msg.var.redecl" + : symDeclType == Token.FUNCTION + ? "msg.fn.redecl" + : "msg.parm.redecl", + name); + return; + } + switch (declType) { + case Token.LET: + if (!ignoreNotInBlock + && (inSingleStatementContext || currentScope instanceof Loop)) { + addError("msg.let.decl.not.in.block"); + return; + } + currentScope.putSymbol(new Symbol(declType, name)); + return; + + case Token.CONST: + if (!ignoreNotInBlock + && (inSingleStatementContext || currentScope instanceof Loop)) { + addError("msg.const.decl.not.in.block"); + return; + } + currentScope.putSymbol(new Symbol(declType, name)); + return; + // fall through for pre-ES6: const is function-scoped like var + case Token.FUNCTION: + if (!ignoreNotInBlock + && !inSingleStatementDeclContext + && (inSingleStatementContext || currentScope instanceof Loop)) { + addError("msg.function.decl.not.in.block"); + return; + } + case Token.VAR: + if (symbol != null) { + if (symDeclType == Token.VAR) addStrictWarning("msg.var.redecl", name); + else if (symDeclType == Token.LP) { + addStrictWarning("msg.var.hides.arg", name); + } + } else { + varSymbol = new Symbol(declType, name); + varSymbol.setDeclaredScope(currentScope); + currentScriptOrFn.putSymbol(varSymbol); + currentScriptOrFn.putVarSymbol(varSymbol); + // Record in every scope from the declaration site up to and + // including the enclosing function/script so each intermediate + // block scope knows which var-hoisted names originate within it. + for (Scope s = currentScope; s != currentScriptOrFn; s = s.getParentScope()) { + s.putVarSymbol(varSymbol); + } + } + return; + + case Token.LP: + if (symbol != null) { + // must be duplicate parameter. Second parameter hides the + // first, so go ahead and add the second parameter + addWarning("msg.dup.parms", name); + } + currentScriptOrFn.putSymbol(new Symbol(declType, name)); + return; + + default: + throw codeBug(); + } + } + + private boolean isValidES6Redeclaration( + int newDeclType, + int oldDeclType, + Symbol symbol, + Symbol varSymbol, + Scope currentScope, + Scope definingScope) { + return isSimpleRedefinition( + newDeclType, oldDeclType, symbol, varSymbol, currentScope, definingScope) + || (!maskingLexicalDefinition( + newDeclType, + oldDeclType, + symbol, + varSymbol, + currentScope, + definingScope) + && !maskingVarDefinition( + newDeclType, + oldDeclType, + symbol, + varSymbol, + currentScope, + definingScope)); + } + + private boolean isValidLegacyRedeclaration( + int newDeclType, + int oldDeclType, + Symbol symbol, + Symbol varSymbol, + Scope currentScope, + Scope definingScope) { + return !(symbol != null + && (oldDeclType == Token.CONST + || newDeclType == Token.CONST + || (definingScope == currentScope && oldDeclType == Token.LET))); + } + + private boolean isSimpleRedefinition( + int newDeclType, + int oldDeclType, + Symbol symbol, + Symbol varSymbol, + Scope currentScope, + Scope definingScope) { + return ((symbol != null + && currentScope == symbol.getDeclaredScope() + && oldDeclType != Token.FUNCTION + && newDeclType != Token.FUNCTION) + || currentScope == currentScriptOrFn) + && !Symbol.isDeclTypeLexical(oldDeclType) + && !Symbol.isDeclTypeLexical(newDeclType); + } + + private boolean maskingLexicalDefinition( + int newDeclType, + int oldDeclType, + Symbol symbol, + Symbol varSymbol, + Scope currentScope, + Scope definingScope) { + return (currentScope == definingScope && Symbol.isDeclTypeLexical(oldDeclType)) + || ((symbol != null + && symbol.isDeclTypeLexical() + && !Symbol.isDeclTypeLexical(newDeclType)) + || (varSymbol != null && Symbol.isDeclTypeLexical(newDeclType))); + } + + private boolean maskingVarDefinition( + int newDeclType, + int oldDeclType, + Symbol symbol, + Symbol varSymbol, + Scope currentScope, + Scope definingScope) { + if (currentScope != currentScriptOrFn && varSymbol != null) { + return true; + } + return (!Symbol.isDeclTypeLexical(newDeclType) + && symbol != null + && !symbol.isDeclTypeLexical()) + && newDeclType != oldDeclType; + } + private AstNode expr(boolean allowTrailingComma) throws IOException { AstNode pn = assignExpr(); int pos = pn.getPosition(); @@ -2628,6 +3376,9 @@ private AstNode assignExpr() throws IOException { if (tt == Token.YIELD) { return returnOrYield(tt, true); } + if (tt == Token.NAME && "await".equals(ts.getString()) && insideAsyncFunction()) { + return awaitExpression(); + } // Intentionally not calling lineNumber/columnNumber here! // We have not consumed any token yet, so the position would be invalid @@ -2664,7 +3415,17 @@ private AstNode assignExpr() throws IOException { } } else if (!hasEOL && tt == Token.ARROW) { consumeToken(); - pn = arrowFunction(pn, startLine, startColumn); + boolean isAsyncArrow = false; + AstNode arrowParams = pn; + if (pn instanceof FunctionCall) { + FunctionCall call = (FunctionCall) pn; + AstNode target = call.getTarget(); + if (target instanceof Name && "async".equals(((Name) target).getIdentifier())) { + isAsyncArrow = true; + arrowParams = callArgsToArrowParams(call); + } + } + pn = arrowFunction(arrowParams, startLine, startColumn, isAsyncArrow); } else if (pn.getIntProp(Node.OBJECT_LITERAL_DESTRUCTURING, 0) == 1 && !inDestructuringAssignment) { reportError("msg.syntax"); @@ -2678,6 +3439,58 @@ private static boolean isNotValidSimpleAssignmentTarget(AstNode pn) { return pn.getType() == Token.QUESTION_DOT; } + /** + * Returns true if the token type represents a keyword whose lexeme is a valid IdentifierName. + * Per the ES spec, class method/property names and object literal property names may be any + * IdentifierName, which includes all reserved words. + */ + private static boolean isKeywordAsIdentifierName(int tt) { + switch (tt) { + case Token.BREAK: + case Token.CASE: + case Token.CATCH: + case Token.CLASS: + case Token.CONST: + case Token.CONTINUE: + case Token.DEBUGGER: + case Token.DEFAULT: + case Token.DELPROP: + case Token.DO: + case Token.ELSE: + case Token.EXPORT: + case Token.EXTENDS: + case Token.FALSE: + case Token.FINALLY: + case Token.FOR: + case Token.FUNCTION: + case Token.IF: + case Token.IMPORT: + case Token.IN: + case Token.INSTANCEOF: + case Token.LET: + case Token.NEW: + case Token.NULL: + case Token.RESERVED: + case Token.RETURN: + case Token.SUPER: + case Token.SWITCH: + case Token.THIS: + case Token.THROW: + case Token.TRUE: + case Token.TRY: + case Token.TYPEOF: + case Token.UNDEFINED: + case Token.VAR: + case Token.VOID: + case Token.WHILE: + case Token.WITH: + case Token.YIELD: + return true; + default: + return false; + } + } + private AstNode condExpr() throws IOException { AstNode pn = nullishCoalescingExpr(); if (matchToken(Token.HOOK, true)) { @@ -2937,7 +3750,16 @@ private AstNode unaryExpr() throws IOException { consumeToken(); line = lineNumber(); column = columnNumber(); - node = new UnaryExpression(tt, ts.tokenBeg, unaryExpr()); + AstNode delOperand = unaryExpr(); + AstNode unwrappedDelOperand = removeParens(delOperand); + if (unwrappedDelOperand instanceof PropertyGet) { + String propName = + ((PropertyGet) unwrappedDelOperand).getProperty().getIdentifier(); + if (propName != null && !propName.isEmpty() && propName.charAt(0) == '#') { + reportError("msg.delete.private.name"); + } + } + node = new UnaryExpression(tt, ts.tokenBeg, delOperand); node.setLineColumnNumber(line, column); return node; @@ -3055,6 +3877,20 @@ private AstNode memberExpr(boolean allowCallSyntax) throws IOException { } else { consumeToken(); int pos = ts.tokenBeg, lineno = lineNumber(), column = columnNumber(); + + // Detect new.target meta-property + if (peekToken() == Token.DOT) { + consumeToken(); // consume '.' + if (matchToken(Token.NAME, true) && "target".equals(ts.getString())) { + int end = ts.tokenEnd; + KeywordLiteral nt = new KeywordLiteral(pos, end - pos, Token.NEW_TARGET); + nt.setLineColumnNumber(lineno, column); + return memberExprTail(allowCallSyntax, nt); + } + reportError("msg.bad.new.dot"); + return makeErrorNode(); + } + NewExpression nx = new NewExpression(pos); AstNode target = memberExpr(false); @@ -3172,6 +4008,9 @@ private FunctionCall makeFunctionCall(AstNode pn, int pos, boolean isOptionalCha throws IOException { consumeToken(); checkCallRequiresActivation(pn); + if (pn.getType() == Token.SUPER && !insideClassConstructor) { + reportError("msg.super.call.not.in.ctor"); + } FunctionCall f = new FunctionCall(pos); f.setTarget(pn); f.setLp(ts.tokenBeg - pos); @@ -3235,6 +4074,15 @@ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) thro ref = propertyName(-1, memberTypeFlags); break; + case Token.PRIVATE_NAME: + // handles: #name (private field access). The Name identifier keeps the leading '#' + // so IRFactory can rewrite the access to a symbol-keyed element access. + if (pn.getType() == Token.SUPER) { + reportError("msg.super.private.name"); + } + ref = propertyName(-1, memberTypeFlags); + break; + case Token.MUL: if (compilerEnv.isXmlAvailable()) { // handles: *, *::name, *::*, *::[expr] @@ -3496,6 +4344,10 @@ private AstNode primaryExpr() throws IOException { consumeToken(); return function(FunctionNode.FUNCTION_EXPRESSION); + case Token.CLASS: + consumeToken(); + return parseClass(false); + case Token.LB: consumeToken(); return arrayLiteral(); @@ -3519,6 +4371,13 @@ private AstNode primaryExpr() throws IOException { case Token.NAME: consumeToken(); + if ("async".equals(ts.getString())) { + int nextTT = peekTokenOrEOL(); + if (nextTT == Token.FUNCTION) { + consumeToken(); + return function(FunctionNode.FUNCTION_EXPRESSION, false, true); + } + } return name(ttFlagged, tt); case Token.NUMBER: @@ -3685,6 +4544,9 @@ private AstNode name(int ttFlagged, int tt) throws IOException { if (0 != (ttFlagged & TI_CHECK_LABEL) && peekToken() == Token.COLON) { // Do not consume colon. It is used as an unwind indicator // to return to statementHelper. + if ("await".equals(nameString) && insideAsyncFunction()) { + reportError("msg.reserved.id", "await"); + } Label label = new Label(namePos, ts.tokenEnd - namePos); label.setName(nameString); label.setLineColumnNumber(lineNumber(), columnNumber()); @@ -3693,6 +4555,9 @@ private AstNode name(int ttFlagged, int tt) throws IOException { // Not a label. Unfortunately peeking the next token to check for // a colon has biffed ts.tokenBeg, ts.tokenEnd. We store the name's // bounds in instance vars and createNameNode uses them. + if ("await".equals(nameString) && insideAsyncFunction()) { + reportError("msg.reserved.id", "await"); + } saveNameTokenData(namePos, nameString, nameLineno, nameColumn); if (compilerEnv.isXmlAvailable()) { @@ -4049,9 +4914,15 @@ private ObjectLiteral objectLiteral() throws IOException { entryKind = GET_ENTRY; } else if ("set".equals(propertyName)) { entryKind = SET_ENTRY; + } else if ("async".equals(propertyName)) { + entryKind = METHOD_ENTRY; } } - if (entryKind == GET_ENTRY || entryKind == SET_ENTRY) { + boolean isAsyncMethod = + pname.getType() == Token.NAME + && "async".equals(propertyName) + && peeked != Token.LP; + if (entryKind == GET_ENTRY || entryKind == SET_ENTRY || isAsyncMethod) { pname = objliteralProperty(); if (pname == null) { reportError("msg.bad.prop"); @@ -4069,7 +4940,8 @@ private ObjectLiteral objectLiteral() throws IOException { pname, entryKind, pname instanceof GeneratorMethodDefinition, - true); + true, + isAsyncMethod); pname.setJsDocNode(jsdocNode); elems.add(objectProp); } @@ -4142,6 +5014,11 @@ private AstNode objliteralProperty() throws IOException { pname = createNameNode(); break; + case Token.PRIVATE_NAME: + // Private names (#foo) are only valid as class members, never in object literals. + reportError("msg.bad.prop"); + return null; + case Token.STRING: pname = createStringLiteral(); break; @@ -4196,7 +5073,10 @@ private AstNode objliteralProperty() throws IOException { int lineno = lineNumber(); int column = columnNumber(); pname = objliteralProperty(); - + if (pname == null) { + // Inner parse already reported the error; just abort this property. + return null; + } pname = new GeneratorMethodDefinition(pos, ts.tokenEnd - pos, pname); pname.setLineColumnNumber(lineno, column); } else { @@ -4252,9 +5132,15 @@ private ObjectProperty plainProperty(AstNode property, int ptt) throws IOExcepti } private ObjectProperty methodDefinition( - int pos, AstNode propName, int entryKind, boolean isGenerator, boolean isShorthand) + int pos, + AstNode propName, + int entryKind, + boolean isGenerator, + boolean isShorthand, + boolean isAsync) throws IOException { - FunctionNode fn = function(FunctionNode.FUNCTION_EXPRESSION, true); + FunctionNode fn = + function(FunctionNode.FUNCTION_EXPRESSION, true, isAsync, isGenerator, false); // We've already parsed the function name, so fn should be anonymous. Name name = fn.getFunctionName(); if (name != null && name.length() != 0) { @@ -4273,9 +5159,6 @@ private ObjectProperty methodDefinition( case METHOD_ENTRY: pn.setIsNormalMethod(); fn.setFunctionIsNormalMethod(); - if (isGenerator) { - fn.setIsES6Generator(); - } if (isShorthand) { fn.setIsShorthand(); } @@ -4660,101 +5543,151 @@ Node destructuringAssignmentHelper( try { pushScope(result); defineSymbol(Token.LET, tempName, true); - } finally { - popScope(); - } - Node comma = new Node(Token.COMMA); - result.addChildToBack(comma); - List destructuringNames = new ArrayList<>(); - boolean empty = true; - String iteratorName = null; - String lastResultName = null; - if (left instanceof ArrayLiteral) { - DestructuringArrayResult arrayResult = - destructuringArray( - (ArrayLiteral) left, - variableType, - tempName, - comma, - destructuringNames, - defaultValue, - transformer, - isFunctionParameter); - empty = arrayResult.empty; - iteratorName = arrayResult.iteratorName; - lastResultName = arrayResult.lastResultName; - } else if (left instanceof ObjectLiteral) { - empty = - destructuringObject( - (ObjectLiteral) left, - variableType, - tempName, - comma, - destructuringNames, - defaultValue, - transformer, - isFunctionParameter, - letNode, - result); - } else if (left.getType() == Token.GETPROP || left.getType() == Token.GETELEM) { - switch (variableType) { - case Token.CONST: - case Token.LET: - case Token.VAR: - reportError("msg.bad.assign.left"); - } - comma.addChildToBack(simpleAssignment(left, createName(tempName), transformer)); - } else { - reportError("msg.bad.assign.left"); - } - if (empty) { - // Don't want a COMMA node with no children. Just add a zero. - comma.addChildToBack(createNumber(0)); - } - - // Add iterator closing to the comma sequence if needed - // Generate: !lastResult.done ? ((f = iterator.return) !== undefined ? f.call(iterator) : - // undefined) : undefined - if (isFunctionParameter && iteratorName != null && lastResultName != null) { - // Allocate temp for return method - String returnMethodName = currentScriptOrFn.getNextTempName(); - defineSymbol(Token.LET, returnMethodName, true); + Node comma = new Node(Token.COMMA); + result.addChildToBack(comma); + List destructuringNames = new ArrayList<>(); + boolean empty = true; + String iteratorName = null; + String lastResultName = null; + Node iteratorAssignNode = null; + if (left instanceof ArrayLiteral) { + DestructuringArrayResult arrayResult = + destructuringArray( + (ArrayLiteral) left, + variableType, + tempName, + comma, + destructuringNames, + defaultValue, + transformer, + isFunctionParameter); + empty = arrayResult.empty; + iteratorName = arrayResult.iteratorName; + lastResultName = arrayResult.lastResultName; + iteratorAssignNode = arrayResult.iteratorAssignNode; + if (iteratorName != null) { + letNode.addChildToBack(createName(Token.NAME, iteratorName, null)); + } + if (lastResultName != null) { + letNode.addChildToBack(createName(Token.NAME, lastResultName, null)); + } + for (var name : arrayResult.tempResultName) { + letNode.addChildToBack(createName(Token.NAME, name, null)); + } + } else if (left instanceof ObjectLiteral) { + empty = + destructuringObject( + (ObjectLiteral) left, + variableType, + tempName, + comma, + destructuringNames, + defaultValue, + transformer, + isFunctionParameter, + letNode, + result); + } else if (left.getType() == Token.GETPROP || left.getType() == Token.GETELEM) { + switch (variableType) { + case Token.CONST: + case Token.LET: + case Token.VAR: + reportError("msg.bad.assign.left"); + } + comma.addChildToBack(simpleAssignment(left, createName(tempName), transformer)); + } else { + reportError("msg.bad.assign.left"); + } + if (empty) { + // Don't want a COMMA node with no children. Just add a zero. + comma.addChildToBack(createNumber(0)); + } - // Check if iterator is done: !lastResult.done - Node getDone = - new Node(Token.GETPROP, createName(lastResultName), Node.newString("done")); - Node notDone = new Node(Token.NOT, getDone); + // Close the iterator on normal completion, and via a finally block on abrupt + // completion (thrown exception or generator .return()). The try/finally ensures + // iter.return() is invoked even when the destructuring is interrupted by a yield + // inside it whose generator is subsequently closed. + if (iteratorName != null && lastResultName != null) { + // Allocate temp for return method used by the normal-path close + String returnMethodName = currentScriptOrFn.getNextTempName(); + defineSymbol(Token.LET, returnMethodName, true); + letNode.addChildToBack(createName(Token.NAME, returnMethodName, null)); + + // Allocate sentinel: set to true once the iterator is open, cleared after + // the normal-path close completes. The finally only runs the abrupt close + // when this is still set, so (a) a throw during iterator setup, (b) a throw + // during the normal-path close, and (c) successful normal completion all + // avoid an unwanted second call to return(). + String needsCloseName = currentScriptOrFn.getNextTempName(); + defineSymbol(Token.LET, needsCloseName, true); + letNode.addChildToBack(createName(Token.NAME, needsCloseName, null)); + + // Check if iterator is done: !lastResult.done + Node getDone = + new Node(Token.GETPROP, createName(lastResultName), Node.newString("done")); + Node notDone = new Node(Token.NOT, getDone); + + // Get iterator.return and store: f = iterator.return + Node getReturn = + new Node(Token.GETPROP, createName(iteratorName), Node.newString("return")); + Node assignReturn = + new Node( + Token.SETNAME, + createName(Token.BINDNAME, returnMethodName, null), + getReturn); - // Get iterator.return and store: f = iterator.return - Node getReturn = - new Node(Token.GETPROP, createName(iteratorName), Node.newString("return")); - Node assignReturn = - new Node( - Token.SETNAME, - createName(Token.BINDNAME, returnMethodName, null), - getReturn); + // Check if return method is not undefined: (f = iterator.return) !== undefined + Node notUndefined = new Node(Token.NE, assignReturn, new Node(Token.UNDEFINED)); - // Check if return method is not undefined: (f = iterator.return) !== undefined - Node notUndefined = new Node(Token.NE, assignReturn, new Node(Token.UNDEFINED)); + // Call return method: f.call(iterator) + Node getCall = + new Node( + Token.GETPROP, + createName(returnMethodName), + Node.newString("call")); + Node callReturn = new Node(Token.CALL, getCall); + callReturn.addChildToBack(createName(iteratorName)); // 'this' argument + + // Per spec (IteratorClose), the return() result is coerced to an Object, + // throwing a TypeError if it is null or undefined. + Node checkedCallReturn = new Node(Token.TO_OBJECT_COERCIBLE, callReturn); + + // Inner ternary: (f = iterator.return) !== undefined ? f.call(iterator) : undefined + Node innerTernary = + new Node( + Token.HOOK, + notUndefined, + checkedCallReturn, + new Node(Token.UNDEFINED)); - // Call return method: f.call(iterator) - Node getCall = - new Node(Token.GETPROP, createName(returnMethodName), Node.newString("call")); - Node callReturn = new Node(Token.CALL, getCall); - callReturn.addChildToBack(createName(iteratorName)); // 'this' argument + // Outer ternary: !lastResult.done ? innerTernary : undefined + Node normalClose = + new Node(Token.HOOK, notDone, innerTernary, new Node(Token.UNDEFINED)); - // Inner ternary: (f = iterator.return) !== undefined ? f.call(iterator) : undefined - Node innerTernary = - new Node(Token.HOOK, notUndefined, callReturn, new Node(Token.UNDEFINED)); + wrapIteratorUseInTryFinally( + comma, iteratorAssignNode, iteratorName, needsCloseName, normalClose); + } - // Outer ternary: !lastResult.done ? innerTernary : undefined - Node outerTernary = - new Node(Token.HOOK, notDone, innerTernary, new Node(Token.UNDEFINED)); + result.putProp(Node.DESTRUCTURING_NAMES, destructuringNames); - comma.addChildToBack(outerTernary); + // For let/const declarations, the user-visible destructured variables must + // live in the enclosing scope, not in any LETEXPR scope. Walk past all + // intermediate LETEXPR scopes (from nested destructuring) to find the + // real enclosing scope where the declaration lives. + if (variableType == Token.LET || variableType == Token.CONST) { + Scope targetScope = result.getParentScope(); + while (targetScope != null && targetScope.getType() == Token.LETEXPR) { + targetScope = targetScope.getParentScope(); + } + if (targetScope != null) { + for (String name : destructuringNames) { + result.moveSymbol(name, targetScope); + } + } + } + } finally { + popScope(); } - - result.putProp(Node.DESTRUCTURING_NAMES, destructuringNames); return result; } @@ -4762,14 +5695,158 @@ private static class DestructuringArrayResult { boolean empty; String iteratorName; String lastResultName; - - DestructuringArrayResult(boolean empty, String iteratorName, String lastResultName) { + List tempResultName; + // First iterator-related node added to `parent` (the SETNAME opening the iterator). + // Serves as the split point for wrapping iterator work in a try/finally. Null when + // the destructuring doesn't use the iterator protocol (pre-ES6, object destructuring). + Node iteratorAssignNode; + + DestructuringArrayResult( + boolean empty, + String iteratorName, + String lastResultName, + List tempResultName, + Node iteratorAssignNode) { this.empty = empty; this.iteratorName = iteratorName; this.lastResultName = lastResultName; + this.tempResultName = tempResultName; + this.iteratorAssignNode = iteratorAssignNode; } } + /** + * Restructures a destructuring {@code comma} body so the iterator-using portion (starting with + * {@code iteratorAssignNode}) runs inside a try/finally. The finally invokes IteratorClose's + * abrupt-completion branch when the {@code needsCloseName} sentinel is still set, ensuring + * iter.return() is called on exceptions and generator {@code .return()}. + * + *

Emitted structure (appended to {@code comma}, after any pre-iterator children that were + * left in place, e.g. default-value setup): + * + *

+     *   LOCAL_BLOCK {
+     *     TRY {
+     *       BLOCK {
+     *         iteratorAssign;        // iter = rhs[Symbol.iterator]()
+     *         needsClose = true;     // mark open
+     *         ...original iterator-using children...
+     *         needsClose = false;    // clear before normal close so a throw from return()
+     *                                // doesn't trigger a redundant abrupt close
+     *         normalClose;           // spec IteratorClose normal-completion path
+     *       }
+     *     } FINALLY {
+     *       needsClose ? ITERATOR_CLOSE_ABRUPT(iter) : undefined;
+     *     }
+     *   }
+     * 
+ */ + private void wrapIteratorUseInTryFinally( + Node comma, + Node iteratorAssignNode, + String iteratorName, + String needsCloseName, + Node normalClose) { + // Move iteratorAssignNode and everything after it from `comma` into a BLOCK that + // will become the try body. Each moved expression is wrapped in EXPR_VOID so it + // evaluates as a statement in the BLOCK context. + Node tryBody = new Node(Token.BLOCK); + Node cursor = iteratorAssignNode; + boolean insertedSentinelSet = false; + while (cursor != null) { + Node next = cursor.getNext(); + comma.removeChild(cursor); + tryBody.addChildToBack(new Node(Token.EXPR_VOID, cursor)); + if (!insertedSentinelSet) { + // Right after the iterator is obtained, mark it as needing close. + Node setSentinelTrue = + new Node( + Token.SETNAME, + createName(Token.BINDNAME, needsCloseName, null), + new Node(Token.TRUE)); + tryBody.addChildToBack(new Node(Token.EXPR_VOID, setSentinelTrue)); + insertedSentinelSet = true; + } + cursor = next; + } + + // Clear the sentinel before the normal-path close runs; a throw from return() on the + // normal path should propagate as-is rather than trigger a redundant abrupt close. + Node clearSentinel = + new Node( + Token.SETNAME, + createName(Token.BINDNAME, needsCloseName, null), + new Node(Token.FALSE)); + tryBody.addChildToBack(new Node(Token.EXPR_VOID, clearSentinel)); + + // Normal-path IteratorClose (HOOK already built by the caller). + tryBody.addChildToBack(new Node(Token.EXPR_VOID, normalClose)); + + // Finally body: needsClose ? ITERATOR_CLOSE_ABRUPT(iter) : undefined + Node abruptClose = new Node(Token.ITERATOR_CLOSE_ABRUPT, createName(iteratorName)); + Node finallyExpr = + new Node( + Token.HOOK, + createName(needsCloseName), + abruptClose, + new Node(Token.UNDEFINED)); + Node finallyBody = new Node(Token.BLOCK); + finallyBody.addChildToBack(new Node(Token.EXPR_VOID, finallyExpr)); + + // Build the try/finally IR (mirrors IRFactory.createTryCatchFinally's finally-only path). + Node handlerBlock = new Node(Token.LOCAL_BLOCK); + Jump tryJump = new Jump(Token.TRY, tryBody); + tryJump.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock); + + makeFinallyNode(finallyBody, handlerBlock, tryJump); + + handlerBlock.addChildToBack(tryJump); + + // Re-attach as a statement-level child of the outer COMMA. COMMA handling in the + // backends recognizes LOCAL_BLOCK children and generates them as statements. + comma.addChildToBack(handlerBlock); + + // Append a trailing expression so LOCAL_BLOCK is never the final child of COMMA. + // createDestructuringAssignment appends {@code createName(tempName)} after this helper + // returns for the outer case; inner/nested calls don't, so without this placeholder the + // inner COMMA would end with LOCAL_BLOCK, which COMMA's last-child handling evaluates as + // an expression. The value is either discarded (inner destructuring used only for + // side-effects) or overwritten by the outer caller's tempName reference. + comma.addChildToBack(new Node(Token.UNDEFINED)); + } + + public static void makeFinallyNode(Node finallyBody, Node handlerBlock, Jump tryJump) { + Node finallyTarget = Node.newTarget(); + tryJump.setFinally(finallyTarget); + + Node finallyNormal = Node.newTarget(); + Jump jumpToFinally = new Jump(Token.GOTO); + jumpToFinally.target = finallyNormal; + tryJump.addChildToBack(jumpToFinally); + + Node finallyEnd = Node.newTarget(); + + tryJump.addChildToBack(finallyTarget); + Node finallyNode = new Node(Token.FINALLY, finallyBody); + finallyNode.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock); + tryJump.addChildToBack(finallyNode); + tryJump.addChildToBack(finallyNormal); + tryJump.addChildToBack(finallyBody.cloneTree()); + // We put the goto here because we may need to add more copies + // of the finally code after this one. + Jump gotoEnd = new Jump(Token.GOTO); + gotoEnd.target = finallyEnd; + tryJump.addChildToBack(gotoEnd); + + tryJump.addChildToBack(finallyEnd); + } + + private Node buildAbruptCloseHook(String iteratorName, String needsCloseName) { + Node abruptClose = new Node(Token.ITERATOR_CLOSE_ABRUPT, createName(iteratorName)); + return new Node( + Token.HOOK, createName(needsCloseName), abruptClose, new Node(Token.UNDEFINED)); + } + DestructuringArrayResult destructuringArray( ArrayLiteral array, int variableType, @@ -4786,39 +5863,31 @@ DestructuringArrayResult destructuringArray( boolean iteratorSetup = false; String iteratorName = null; String lastResultName = null; + Node iteratorAssignNode = null; + List elemTempNames = new ArrayList<>(); for (AstNode n : array.getElements()) { - if (n.getType() == Token.EMPTY) { - index++; - continue; - } - - Node rightElem; - if (defaultValue != null && !defaultValuesSetup) { setupDefaultValues(tempName, parent, defaultValue, setOp, transformer); defaultValuesSetup = true; } - // Set up iterator for function parameters (after default value is applied) + // Set up iterator for array destructuring (after default value is applied) // Only use iterator protocol in ES6+; older versions use index-based access - if (isFunctionParameter - && !iteratorSetup - && compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { + if (!iteratorSetup && compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { // Allocate temp names for iterator tracking iteratorName = currentScriptOrFn.getNextTempName(); - lastResultName = currentScriptOrFn.getNextTempName(); // Define the iterator temp variables for strict mode defineSymbol(Token.LET, iteratorName, true); - defineSymbol(Token.LET, lastResultName, true); // Generate: iterator = tempName[Symbol.iterator]() - // Pure AST: CALL(GETELEM(tempName, GETPROP(NAME("Symbol"), "iterator"))) - Node symbolName = createName("Symbol"); - Node getIteratorProp = - new Node(Token.GETPROP, symbolName, Node.newString("iterator")); - Node getIteratorMethod = new Node(Token.GETELEM, createName(tempName)); - getIteratorMethod.addChildToBack(getIteratorProp); + // Load SymbolKey.ITERATOR directly from the shared literal table + // instead of evaluating the two-step Symbol.iterator name lookup. + Node symbolIterator = new Node(Token.LOAD_LITERAL); + symbolIterator.putIntProp( + Node.LITERAL_INDEX_PROP, currentScriptOrFn.addLiteral(SymbolKey.ITERATOR)); + Node getIteratorMethod = + new Node(Token.GETELEM, createName(tempName), symbolIterator); Node callIterator = new Node(Token.CALL, getIteratorMethod); Node iteratorAssign = new Node( @@ -4826,64 +5895,37 @@ DestructuringArrayResult destructuringArray( createName(Token.BINDNAME, iteratorName, null), callIterator); parent.addChildToBack(iteratorAssign); + iteratorAssignNode = iteratorAssign; iteratorSetup = true; empty = false; } - // Generate code to get element - if (isFunctionParameter && iteratorName != null) { - // ES6+: Call iterator.next() and store the full result to check done later - Node getNextProp = - new Node(Token.GETPROP, createName(iteratorName), Node.newString("next")); - Node callNext = new Node(Token.CALL, getNextProp); - Node storeResult = - new Node( - Token.SETNAME, - createName(Token.BINDNAME, lastResultName, null), - callNext); - parent.addChildToBack(storeResult); - // Extract .value from the result - String elemTempName = currentScriptOrFn.getNextTempName(); - // Define the element temp variable for strict mode - defineSymbol(Token.LET, elemTempName, true); - Node getValue = - new Node( - Token.GETPROP, createName(lastResultName), Node.newString("value")); - Node storeElem = - new Node( - Token.SETNAME, - createName(Token.BINDNAME, elemTempName, null), - getValue); - parent.addChildToBack(storeElem); - // Use the temp variable for element access - rightElem = createName(elemTempName); - empty = false; - } else { - // Regular index-based access for var/let/const - rightElem = new Node(Token.GETELEM, createName(tempName), createNumber(index)); + if (n.getType() == Token.EMPTY) { + // If using iterator protocol, advance the iterator past this position + if (iteratorName != null) { + if (lastResultName == null) { + lastResultName = currentScriptOrFn.getNextTempName(); + defineSymbol(Token.LET, lastResultName, true); + } + Node getNextProp = + new Node( + Token.GETPROP, + createName(iteratorName), + Node.newString("next")); + Node callNext = new Node(Token.CALL, getNextProp); + Node storeResult = + new Node( + Token.SETNAME, + createName(Token.BINDNAME, lastResultName, null), + callNext); + parent.addChildToBack(storeResult); + empty = false; + } + index++; + continue; } - if (n.getType() == Token.NAME) { - /* [x] = [1] */ - String name = n.getString(); - parent.addChildToBack( - new Node(setOp, createName(Token.BINDNAME, name, null), rightElem)); - if (variableType != -1) { - defineSymbol(variableType, name, true); - destructuringNames.add(name); - } - } else if (n.getType() == Token.ASSIGN) { - /* [x = 1] = [2] */ - processDestructuringDefaults( - variableType, - parent, - destructuringNames, - (Assignment) n, - rightElem, - setOp, - transformer, - isFunctionParameter); - } else if (n instanceof Spread) { + if (n instanceof Spread) { // [...rest] = [1, 2, 3] // rest element should be the last if (index < array.getElements().size() - 1) { @@ -4892,22 +5934,41 @@ DestructuringArrayResult destructuringArray( AstNode restTarget = ((Spread) n).getExpression(); - // call array.slice(index) to collect remaining elements - Node sliceCall = - new Node( - Token.CALL, - new Node( - Token.GETPROP, - createName(tempName), - Node.newString("slice"))); - sliceCall.addChildToBack(createNumber(index)); + Node restValue; + if (iteratorName != null) { + // When using the iterator protocol, collect remaining + // elements via Array.from(iterator). The iterator has + // not been advanced past this point, so Array.from + // collects exactly the remaining elements. Array.from + // also properly closes the iterator when done, so we + // clear the iterator tracking to skip the manual + // closing code generated after the loop. + restValue = + new Node( + Token.CALL, + new Node( + Token.GETPROP, + createName("Array"), + Node.newString("from"))); + restValue.addChildToBack(createName(iteratorName)); + } else { + // call array.slice(index) to collect remaining elements + restValue = + new Node( + Token.CALL, + new Node( + Token.GETPROP, + createName(tempName), + Node.newString("slice"))); + restValue.addChildToBack(createNumber(index)); + } if (restTarget.getType() == Token.NAME) { // [...rest] String name = restTarget.getString(); parent.addChildToBack( - new Node(setOp, createName(Token.BINDNAME, name, null), sliceCall)); + new Node(setOp, createName(Token.BINDNAME, name, null), restValue)); if (variableType != -1) { defineSymbol(variableType, name, true); @@ -4919,28 +5980,98 @@ DestructuringArrayResult destructuringArray( destructuringAssignmentHelper( variableType, restTarget, - sliceCall, + restValue, currentScriptOrFn.getNextTempName(), null, transformer, isFunctionParameter)); } + empty = false; } else { - parent.addChildToBack( - destructuringAssignmentHelper( - variableType, - n, - rightElem, - currentScriptOrFn.getNextTempName(), - null, - transformer, - isFunctionParameter)); + Node rightElem; + + // Generate code to get element (not needed for Spread, + // which collects remaining elements separately above) + if (iteratorName != null) { + if (lastResultName == null) { + lastResultName = currentScriptOrFn.getNextTempName(); + defineSymbol(Token.LET, lastResultName, true); + } + // ES6+: Call iterator.next() and store the full result + Node getNextProp = + new Node( + Token.GETPROP, + createName(iteratorName), + Node.newString("next")); + Node callNext = new Node(Token.CALL, getNextProp); + Node storeResult = + new Node( + Token.SETNAME, + createName(Token.BINDNAME, lastResultName, null), + callNext); + parent.addChildToBack(storeResult); + // Extract .value from the result + String elemTempName = currentScriptOrFn.getNextTempName(); + elemTempNames.add(elemTempName); + // Define the element temp variable for strict mode + defineSymbol(Token.LET, elemTempName, true); + Node getValue = + new Node( + Token.GETPROP, + createName(lastResultName), + Node.newString("value")); + Node storeElem = + new Node( + Token.SETNAME, + createName(Token.BINDNAME, elemTempName, null), + getValue); + parent.addChildToBack(storeElem); + // Use the temp variable for element access + rightElem = createName(elemTempName); + empty = false; + } else { + // Regular index-based access for var/let/const + rightElem = new Node(Token.GETELEM, createName(tempName), createNumber(index)); + } + + if (n.getType() == Token.NAME) { + /* [x] = [1] */ + String name = n.getString(); + parent.addChildToBack( + new Node(setOp, createName(Token.BINDNAME, name, null), rightElem)); + if (variableType != -1) { + defineSymbol(variableType, name, true); + destructuringNames.add(name); + } + } else if (n.getType() == Token.ASSIGN) { + /* [x = 1] = [2] */ + processDestructuringDefaults( + variableType, + parent, + destructuringNames, + (Assignment) n, + rightElem, + setOp, + transformer, + isFunctionParameter); + } else { + parent.addChildToBack( + destructuringAssignmentHelper( + variableType, + n, + rightElem, + currentScriptOrFn.getNextTempName(), + null, + transformer, + isFunctionParameter)); + } } index++; empty = false; } - return new DestructuringArrayResult(empty, iteratorName, lastResultName); + return new DestructuringArrayResult( + empty, iteratorName, lastResultName, elemTempNames, iteratorAssignNode); } private void processDestructuringDefaults( @@ -4956,11 +6087,7 @@ private void processDestructuringDefaults( Node right = null; if (left.getType() == Token.NAME) { String name = left.getString(); - // x = (x == undefined) ? - // (($1[0] == undefined) ? - // 1 - // : $1[0]) - // : x + // x = ($1[0] === undefined) ? defaultValue : $1[0] right = (transformer != null) ? transformer.transform(n.getRight()) : n.getRight(); @@ -4974,22 +6101,15 @@ private void processDestructuringDefaults( right, rightElem); - Node cond = - new Node( - Token.HOOK, - new Node( - Token.SHEQ, - new KeywordLiteral().setType(Token.UNDEFINED), - createName(name)), - cond_inner, - left); - // store it to be transformed later if (transformer == null) { - currentScriptOrFn.putDestructuringRvalues(cond_inner, right); + currentScriptOrFn.putDestructuringRvalues(cond_inner, right, new Name(0, name)); + } else { + inferFunctionName(name, right); } - parent.addChildToBack(new Node(setOp, createName(Token.BINDNAME, name, null), cond)); + parent.addChildToBack( + new Node(setOp, createName(Token.BINDNAME, name, null), cond_inner)); if (variableType != -1) { defineSymbol(variableType, name, true); destructuringNames.add(name); @@ -5028,6 +6148,23 @@ private void processDestructuringDefaults( } } + private void inferFunctionName(String name, Node right) { + if (compilerEnv.getLanguageVersion() < Context.VERSION_ES6) { + return; + } + if (right == null || right.getType() != Token.FUNCTION) { + return; + } + if (NativeObject.PROTO_PROPERTY.equals(name)) { + return; + } + int fnIndex = right.getExistingIntProp(Node.FUNCTION_PROP); + FunctionNode functionNode = currentScriptOrFn.getFunctionNode(fnIndex); + if (functionNode.getType() != 0 && functionNode.getFunctionName() == null) { + functionNode.setFunctionName(new Name(0, name)); + } + } + static Object getPropKey(Node id) { Object key; if (id instanceof Name) { @@ -5197,12 +6334,7 @@ boolean destructuringObject( letNode.addChildToBack(tempVar); // define the computed property temp - pushScope(letExprScope); - try { - defineSymbol(Token.LET, keyTempName, true); - } finally { - popScope(); - } + defineSymbol(Token.LET, keyTempName, true); Node keyRef = createName(keyTempName); extractedKeys.add(keyRef); @@ -5250,6 +6382,17 @@ boolean destructuringObject( empty = false; elementIndex++; } + + // RequireObjectCoercible: throw TypeError if the value is null or undefined. + // This must happen after default values are applied (setupDefaultValues above) + // so that e.g. function({} = {}) {} called with no arguments uses the default + // rather than throwing. + if (defaultValue != null && !defaultValuesSetup) { + setupDefaultValues(tempName, parent, defaultValue, setOp, transformer); + } + parent.addChildToBack(new Node(Token.TO_OBJECT_COERCIBLE, createName(tempName))); + empty = false; + return empty; } @@ -5409,7 +6552,8 @@ void markDestructuring(AstNode node) { reportError("msg.parm.after.rest"); } } - } else if (node instanceof ParenthesizedExpression) { + } else if (compilerEnv.getLanguageVersion() < Context.VERSION_ES6 + && node instanceof ParenthesizedExpression) { markDestructuring(((ParenthesizedExpression) node).getExpression()); } } diff --git a/rhino/src/main/java/org/mozilla/javascript/PolicySecurityController.java b/rhino/src/main/java/org/mozilla/javascript/PolicySecurityController.java index 73bbe1726a5..d17e25f18d6 100644 --- a/rhino/src/main/java/org/mozilla/javascript/PolicySecurityController.java +++ b/rhino/src/main/java/org/mozilla/javascript/PolicySecurityController.java @@ -88,7 +88,7 @@ public Object callWithDomain( Context cx, Callable callable, VarScope scope, - Scriptable thisObj, + Object thisObj, Object[] args) { // Run in doPrivileged as we might be checked for "getClassLoader" // runtime permission @@ -145,7 +145,7 @@ public Object run() throws Exception { public abstract static class SecureCaller { public abstract Object call( - Callable callable, Context cx, VarScope scope, Scriptable thisObj, Object[] args); + Callable callable, Context cx, VarScope scope, Object thisObj, Object[] args); } private static byte[] loadBytecode() { diff --git a/rhino/src/main/java/org/mozilla/javascript/PropHolder.java b/rhino/src/main/java/org/mozilla/javascript/PropHolder.java index 2bf940172bc..37721ec469e 100644 --- a/rhino/src/main/java/org/mozilla/javascript/PropHolder.java +++ b/rhino/src/main/java/org/mozilla/javascript/PropHolder.java @@ -46,4 +46,15 @@ public interface PropHolder> { * @return the parent scope */ public T getAncestor(); + + /** + * Get an array of property ids. + * + *

Not all property ids need be returned. Those properties whose ids are not returned are + * considered non-enumerable. + * + * @return an array of Objects. Each entry in the array is either a java.lang.String or a + * java.lang.Number + */ + public Object[] getIds(); } diff --git a/rhino/src/main/java/org/mozilla/javascript/RefCallable.java b/rhino/src/main/java/org/mozilla/javascript/RefCallable.java index b8437ea3a9e..ad788ccc028 100644 --- a/rhino/src/main/java/org/mozilla/javascript/RefCallable.java +++ b/rhino/src/main/java/org/mozilla/javascript/RefCallable.java @@ -17,5 +17,5 @@ public interface RefCallable extends Callable { * @param thisObj the JavaScript {@code this} object * @param args the array of arguments */ - public Ref refCall(Context cx, Scriptable thisObj, Object[] args); + public Ref refCall(Context cx, Object thisObj, Object[] args); } diff --git a/rhino/src/main/java/org/mozilla/javascript/RegExpProxy.java b/rhino/src/main/java/org/mozilla/javascript/RegExpProxy.java index 5f245b3c457..ba57557a027 100644 --- a/rhino/src/main/java/org/mozilla/javascript/RegExpProxy.java +++ b/rhino/src/main/java/org/mozilla/javascript/RegExpProxy.java @@ -18,7 +18,7 @@ public interface RegExpProxy { public static final int RA_REPLACE_ALL = 3; public static final int RA_SEARCH = 4; - public void register(TopLevel scope, boolean sealed); + public void register(Context cx, TopLevel scope, boolean sealed); public boolean isRegExp(Scriptable obj); diff --git a/rhino/src/main/java/org/mozilla/javascript/ScopeObject.java b/rhino/src/main/java/org/mozilla/javascript/ScopeObject.java index 9df71a5f9e8..9966fe3781c 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScopeObject.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScopeObject.java @@ -9,8 +9,8 @@ import java.io.ObjectOutputStream; import java.io.Serializable; -public class ScopeObject extends SlotMapOwner implements VarScope, Serializable { - private static final long serialVersionUID = -7471457301304454454L; +public class ScopeObject extends SlotMapOwner implements VarScope, Serializable { + private static final long serialVersionUID = 2283542168979106620L; private final VarScope parentScope; @@ -41,7 +41,7 @@ public ScopeObject getThis() { * @param value value to set the property to */ @Override - public void put(String name, Scriptable start, Object value) { + public void put(String name, VarScope start, Object value) { if (putOwnProperty(name, start, value, Context.isCurrentContextStrict())) return; if (start == this) throw Kit.codeBug(); @@ -55,7 +55,7 @@ public void put(String name, Scriptable start, Object value) { * * @param isThrow if true, throw an exception as if in strict mode */ - protected boolean putOwnProperty(String name, Scriptable start, Object value, boolean isThrow) { + protected boolean putOwnProperty(String name, VarScope start, Object value, boolean isThrow) { return putImpl(name, 0, start, value, isThrow); } @@ -68,7 +68,7 @@ protected boolean putOwnProperty(String name, Scriptable start, Object value, bo */ @SuppressWarnings("resource") @Override - public void put(int index, Scriptable start, Object value) { + public void put(int index, VarScope start, Object value) { if (putOwnProperty(index, start, value, Context.isCurrentContextStrict())) return; if (start == this) throw Kit.codeBug(); @@ -82,13 +82,13 @@ public void put(int index, Scriptable start, Object value) { * * @param isThrow if true, throw an exception as if in strict mode */ - protected boolean putOwnProperty(int index, Scriptable start, Object value, boolean isThrow) { + protected boolean putOwnProperty(int index, VarScope start, Object value, boolean isThrow) { return putImpl(null, index, start, value, isThrow); } /** Implementation of put required by SymbolScriptable objects. */ @Override - public void put(Symbol key, Scriptable start, Object value) { + public void put(Symbol key, VarScope start, Object value) { if (putOwnProperty(key, start, value, Context.isCurrentContextStrict())) return; if (start == this) throw Kit.codeBug(); @@ -102,7 +102,7 @@ public void put(Symbol key, Scriptable start, Object value) { * * @param isThrow if true, throw an exception as if in strict mode */ - protected boolean putOwnProperty(Symbol key, Scriptable start, Object value, boolean isThrow) { + protected boolean putOwnProperty(Symbol key, VarScope start, Object value, boolean isThrow) { return putImpl(key, 0, start, value, isThrow); } @@ -115,9 +115,8 @@ protected boolean putOwnProperty(Symbol key, Scriptable start, Object value, boo * @return false if this != start and no slot was found. true if this == start or this != start * and a READONLY slot was found. */ - private boolean putImpl( - Object key, int index, Scriptable start, Object value, boolean isThrow) { - Slot slot; + private boolean putImpl(Object key, int index, VarScope start, Object value, boolean isThrow) { + Slot slot; if (this != start) { slot = getMap().query(key, index); if (slot == null) { @@ -137,7 +136,7 @@ private boolean putImpl( * @return true if and only if the property was found in the object */ @Override - public boolean has(String name, Scriptable start) { + public boolean has(String name, VarScope start) { return null != getMap().query(name, 0); } @@ -149,13 +148,13 @@ public boolean has(String name, Scriptable start) { * @return true if and only if the property was found in the object */ @Override - public boolean has(int index, Scriptable start) { + public boolean has(int index, VarScope start) { return null != getMap().query(null, index); } /** A version of "has" that supports symbols. */ @Override - public boolean has(Symbol key, Scriptable start) { + public boolean has(Symbol key, VarScope start) { return null != getMap().query(key, 0); } @@ -169,7 +168,7 @@ public boolean has(Symbol key, Scriptable start) { * @return the value of the property (may be null), or NOT_FOUND */ @Override - public Object get(String name, Scriptable start) { + public Object get(String name, VarScope start) { var slot = getMap().query(name, 0); if (slot == null) { return Scriptable.NOT_FOUND; @@ -185,7 +184,7 @@ public Object get(String name, Scriptable start) { * @return the value of the property (may be null), or NOT_FOUND */ @Override - public Object get(int index, Scriptable start) { + public Object get(int index, VarScope start) { var slot = getMap().query(null, index); if (slot == null) { return Scriptable.NOT_FOUND; @@ -195,7 +194,7 @@ public Object get(int index, Scriptable start) { /** Another version of Get that supports Symbol keyed properties. */ @Override - public Object get(Symbol key, Scriptable start) { + public Object get(Symbol key, VarScope start) { var slot = getMap().query(key, 0); if (slot == null) { return Scriptable.NOT_FOUND; @@ -218,11 +217,6 @@ public void delete(Symbol key) { getMap().compute(this, key, 0, ScriptableObject::checkSlotRemoval); } - @Override - public Object[] getIds() { - return new Object[0]; - } - /** * Define a JavaScript property. * @@ -296,28 +290,20 @@ public void setAttributes(Symbol key, int attributes) { * @param value value to set the property to */ @Override - public void putConst(String name, Scriptable start, Object value) { + public void putConst(String name, VarScope start, Object value) { if (putConstImpl(name, 0, start, value, ScriptableObject.READONLY)) return; if (start == this) throw Kit.codeBug(); - if (start instanceof ConstProperties) { - @SuppressWarnings("unchecked") - var cstart = ((ConstProperties) start); - cstart.putConst(name, start, value); - } else start.put(name, start, value); + start.putConst(name, start, value); } @Override - public void defineConst(String name, Scriptable start) { + public void defineConst(String name, VarScope start) { if (putConstImpl(name, 0, start, Undefined.instance, ScriptableObject.UNINITIALIZED_CONST)) return; if (start == this) throw Kit.codeBug(); - if (start instanceof ConstProperties) { - @SuppressWarnings("unchecked") - var cstart = ((ConstProperties) start); - cstart.defineConst(name, start); - } + start.defineConst(name, start); } /** @@ -346,9 +332,9 @@ public boolean isConst(String name) { * and a READONLY slot was found. */ private boolean putConstImpl( - String name, int index, Scriptable start, Object value, int constFlag) { + String name, int index, VarScope start, Object value, int constFlag) { assert (constFlag != ScriptableObject.EMPTY); - Slot slot; + Slot slot; if (this != start) { slot = getMap().query(name, index); if (slot == null) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ScopeWrapper.java b/rhino/src/main/java/org/mozilla/javascript/ScopeWrapper.java index 955aae92cfd..cf6022dc6ae 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScopeWrapper.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScopeWrapper.java @@ -1,11 +1,11 @@ package org.mozilla.javascript; public class ScopeWrapper extends ScriptableObject { - private static final long serialVersionUID = -7471457301304454454L; + private static final long serialVersionUID = -3481312197060837332L; - private final Scriptable scope; + private final VarScope scope; - public ScopeWrapper(Scriptable scope) { + public ScopeWrapper(VarScope scope) { this.scope = scope; } @@ -39,7 +39,7 @@ public String getClassName() { return "scope"; } - public Scriptable getScope() { + public VarScope getScope() { return scope; } } diff --git a/rhino/src/main/java/org/mozilla/javascript/Script.java b/rhino/src/main/java/org/mozilla/javascript/Script.java index 49e9ea25c7a..ad469499c32 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Script.java +++ b/rhino/src/main/java/org/mozilla/javascript/Script.java @@ -24,7 +24,7 @@ public interface Script { // Maintained for backward compatibility of already-compiled classes @Deprecated(since = "1.8.1") default Object exec(Context cx, VarScope scope) { - return exec(cx, scope, scope); + return exec(cx, scope, Undefined.SCRIPTABLE_UNDEFINED); } /** @@ -42,7 +42,7 @@ default Object exec(Context cx, VarScope scope) { * @return the result of executing the script * @see org.mozilla.javascript.Context#initStandardObjects() */ - Object exec(Context cx, VarScope scope, Scriptable thisObj); + Object exec(Context cx, VarScope scope, Object thisObj); default JSDescriptor getDescriptor() { return null; diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java index f3b82f4dd0f..85fbf83c6da 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java @@ -6,6 +6,12 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ScriptableObject.DONTENUM; +import static org.mozilla.javascript.ScriptableObject.PERMANENT; +import static org.mozilla.javascript.ScriptableObject.READONLY; +import static org.mozilla.javascript.Symbol.Kind.REGULAR; +import static org.mozilla.javascript.UniqueTag.NOT_FOUND; + import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -22,6 +28,7 @@ import java.util.ServiceLoader; import java.util.function.BiConsumer; import java.util.function.Consumer; +import org.mozilla.javascript.ScriptableObject.DescriptorInfo; import org.mozilla.javascript.ast.FunctionNode; import org.mozilla.javascript.dtoa.DoubleFormatter; import org.mozilla.javascript.lc.type.TypeInfo; @@ -69,17 +76,26 @@ public static BaseFunction typeErrorThrower() { /** Returns representation of the [[ThrowTypeError]] object. See ECMA 5 spec, 13.2.3 */ public static BaseFunction typeErrorThrower(Context cx) { - if (cx.typeErrorThrower == null) { - BaseFunction thrower = new ThrowTypeError(cx.topCallScope); - cx.typeErrorThrower = thrower; + return typeErrorThrower(cx.topCallScope); + } + + private static final SymbolKey TYPE_ERROR_THROWER = new SymbolKey("TypeErrorThrower", REGULAR); + + public static BaseFunction typeErrorThrower(VarScope scope) { + TopLevel top = ScriptableObject.getTopLevelScope(scope); + var thrower = top.get(TYPE_ERROR_THROWER, top); + if (thrower == NOT_FOUND) { + thrower = new ThrowTypeError(top); + top.defineProperty(TYPE_ERROR_THROWER, thrower, DONTENUM | READONLY | PERMANENT); } - return cx.typeErrorThrower; + return (BaseFunction) thrower; } static final class ThrowTypeError extends BaseFunction { private static final long serialVersionUID = -5891740962154902286L; ThrowTypeError(VarScope scope) { + super(scope); setPrototype(ScriptableObject.getFunctionPrototype(scope)); setAttributes("length", DONTENUM | PERMANENT | READONLY); @@ -102,7 +118,7 @@ private static > Slot removeWithoutChecking( } @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { throwNotAllowed(); return null; } @@ -138,7 +154,7 @@ static class NoSuchMethodShim implements Callable { * @return the result of the call */ @Override - public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) { Object[] nestedArgs = new Object[2]; nestedArgs[0] = methodName; @@ -207,7 +223,7 @@ public static TopLevel initSafeStandardObjects(Context cx, TopLevel scope, boole : new LegacyCacheFactory.Concurrent(); typeFactory.associate(scope); - LambdaConstructor function = BaseFunction.init(cx, scope, sealed); + JSFunction function = BaseFunction.init(cx, scope, sealed); JSFunction obj = NativeObject.init(cx, scope, sealed); ScriptableObject objectPrototype = (ScriptableObject) obj.getPrototypeProperty(); @@ -229,23 +245,23 @@ public static TopLevel initSafeStandardObjects(Context cx, TopLevel scope, boole // representation NativeArray.setMaximumInitialCapacity(200000); } - NativeString.init(scope, sealed); + NativeString.init(cx, scope, sealed); NativeBoolean.init(cx, scope, sealed); - NativeNumber.init(scope, sealed); + NativeNumber.init(cx, scope, sealed); NativeDate.init(cx, scope, sealed); new LazilyLoadedCtor<>(scope, "Math", sealed, true, NativeMath::init); new LazilyLoadedCtor<>(scope, "JSON", sealed, true, NativeJSON::init); - NativeScript.init(cx, scope, sealed); + NativeScript.init2(cx, scope, sealed); NativeIterator.init(cx, scope, sealed); // Also initializes NativeGenerator & ES6Generator - NativeArrayIterator.init(scope, sealed); - NativeStringIterator.init(scope, sealed); + NativeArrayIterator.init(cx, scope, sealed); + NativeStringIterator.init(cx, scope, sealed); registerRegExp(cx, scope, sealed); - NativeJavaObject.init(scope, sealed); - NativeJavaMap.init(scope, sealed); + NativeJavaObject.init(cx, scope, sealed); + NativeJavaMap.init(cx, scope, sealed); // define lazy-loaded properties using their class name // Depends on the old reflection-based lazy loading mechanism @@ -282,8 +298,6 @@ public static TopLevel initSafeStandardObjects(Context cx, TopLevel scope, boole if (cx.getLanguageVersion() >= Context.VERSION_ES6) { NativeSymbol.init(cx, scope, sealed); - NativeCollectionIterator.init(scope, NativeSet.ITERATOR_TAG, sealed); - NativeCollectionIterator.init(scope, NativeMap.ITERATOR_TAG, sealed); new LazilyLoadedCtor<>(scope, "Map", sealed, true, NativeMap::init); new LazilyLoadedCtor<>(scope, "Promise", sealed, true, NativePromise::init); new LazilyLoadedCtor<>(scope, "Set", sealed, true, NativeSet::init); @@ -302,7 +316,7 @@ public static TopLevel initSafeStandardObjects(Context cx, TopLevel scope, boole private static void registerRegExp(Context cx, TopLevel scope, boolean sealed) { RegExpProxy regExpProxy = getRegExpProxy(cx); if (regExpProxy != null) { - regExpProxy.register(scope, sealed); + regExpProxy.register(cx, scope, sealed); } } @@ -990,7 +1004,7 @@ private static void copyProperty( } } else if (id instanceof String) { String propName = (String) id; - value = getObjectProp(source, propName, cx, (VarScope) scope); + value = getObjectProp(source, propName, cx, scope); if (value != Scriptable.NOT_FOUND) { result.put(propName, result, value); } @@ -1641,6 +1655,17 @@ public static Function getExistingCtor(Context cx, VarScope scope, String constr throw Context.reportRuntimeErrorById("msg.not.ctor", constructorName); } + public static Function getExistingCtor(Context cx, VarScope scope, SymbolKey constructorName) { + Object ctorVal = ScriptableObject.getProperty(scope, constructorName); + if (ctorVal instanceof Function) { + return (Function) ctorVal; + } + if (ctorVal == Scriptable.NOT_FOUND) { + throw Context.reportRuntimeErrorById("msg.ctor.not.found", constructorName); + } + throw Context.reportRuntimeErrorById("msg.not.ctor", constructorName); + } + /** * Return -1L if str is not an index, or the index value as lower 32 bits of the result. Note * that the result needs to be cast to an int in order to produce the actual index, which may be @@ -1845,6 +1870,9 @@ public static Object getObjectElem(Scriptable obj, Object elem, Context cx) { } if (result == Scriptable.NOT_FOUND) { + if (isPrivateSymbol(elem)) { + throw typeErrorById("msg.private.not.on.object", ((Symbol) elem).getName()); + } result = Undefined.instance; } return result; @@ -1879,6 +1907,9 @@ public static Object getSuperElem( } if (result == Scriptable.NOT_FOUND) { + if (isPrivateSymbol(elem)) { + throw typeErrorById("msg.private.not.on.object", ((Symbol) elem).getName()); + } result = Undefined.instance; } return result; @@ -2051,7 +2082,11 @@ public static Object setObjectElem(Scriptable obj, Object elem, Object value, Co if (obj instanceof XMLObject) { ((XMLObject) obj).put(cx, elem, value); } else if (isSymbol(elem)) { - ScriptableObject.putProperty(obj, (Symbol) elem, value); + Symbol sym = (Symbol) elem; + if (isPrivateSymbol(sym) && !ScriptableObject.hasProperty(obj, sym)) { + throw typeErrorById("msg.private.write.not.on.object", sym.getName()); + } + ScriptableObject.putProperty(obj, sym, value); } else { StringIdOrIndex s = toStringIdOrIndex(elem); if (s.stringId == null) { @@ -2087,8 +2122,11 @@ public static Object setSuperElem( Context cx) { // No XML support for super if (isSymbol(elem)) { - ScriptableObject.putSuperProperty( - superScriptable, thisScriptable, (Symbol) elem, value); + Symbol sym = (Symbol) elem; + if (isPrivateSymbol(sym) && !ScriptableObject.hasProperty(thisScriptable, sym)) { + throw typeErrorById("msg.private.write.not.on.object", sym.getName()); + } + ScriptableObject.putSuperProperty(superScriptable, thisScriptable, sym, value); } else { StringIdOrIndex s = toStringIdOrIndex(elem); if (s.stringId == null) { @@ -2383,8 +2421,8 @@ private static Object nameOrFunction( break; } } - } else if (scope instanceof NativeCall) { - // NativeCall does not prototype chain and Scriptable.get + } else if (scope instanceof CatchScope || scope instanceof NativeCall) { + // Scopes do not have a prototype chain and Scriptable.get // can be called directly. result = scope.get(name, scope); if (result != Scriptable.NOT_FOUND) { @@ -2467,7 +2505,7 @@ private static LookupResult nameOrFunction( break; } } - } else if (scope instanceof NativeCall) { + } else if (scope instanceof CatchScope || scope instanceof NativeCall) { // NativeCall does not prototype chain and Scriptable.get // can be called directly. result = scope.get(name, scope); @@ -2530,18 +2568,24 @@ public static VarScope bind(Context cx, VarScope scope, String id) { childScopesChecks: if (parent != null) { // Check for possibly nested "with" scopes first - while (scope instanceof WithScope) { - Scriptable withObj = ((WithScope) scope).getObject(); - if (withObj instanceof XMLObject) { - XMLObject xmlObject = (XMLObject) withObj; - if (xmlObject.has(cx, id)) { - return new WithScope(scope.getParentScope(), xmlObject); - } - if (firstXMLScope == null) { - firstXMLScope = scope; + while (scope.isNestedScope()) { + if (scope instanceof WithScope) { + Scriptable withObj = ((WithScope) scope).getObject(); + if (withObj instanceof XMLObject) { + XMLObject xmlObject = (XMLObject) withObj; + if (xmlObject.has(cx, id)) { + return new WithScope(scope.getParentScope(), xmlObject); + } + if (firstXMLScope == null) { + firstXMLScope = scope; + } + } else { + if (ScriptableObject.hasProperty(withObj, id)) { + return scope; + } } } else { - if (ScriptableObject.hasProperty(withObj, id)) { + if (scope.has(id, scope)) { return scope; } } @@ -2618,7 +2662,7 @@ public static Object strictSetName( } public static Object setConst(VarScope bound, Object value, Context cx, String id) { - ScriptableObject.putConstProperty((VarScope) bound, id, value); + ScriptableObject.putConstProperty(bound, id, value); return value; } @@ -2635,7 +2679,7 @@ public static Object setConst(VarScope bound, Object value, Context cx, String i * if a given property has already been enumerated. */ private static class IdEnumeration implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -8685189816346084720L; Scriptable obj; Object[] ids; HashSet used; @@ -2648,6 +2692,7 @@ private static class IdEnumeration implements Serializable { boolean enumNumbers; Scriptable iterator; + boolean isAsyncIteration; } public static Scriptable toIterator(Context cx, Scriptable obj, boolean keyOnly) { @@ -2684,6 +2729,7 @@ public static Object enumInit(Object value, Context cx, boolean enumValues) { public static final int ENUMERATE_VALUES_NO_ITERATOR = 4; public static final int ENUMERATE_ARRAY_NO_ITERATOR = 5; public static final int ENUMERATE_VALUES_IN_ORDER = 6; + public static final int ENUMERATE_VALUES_IN_ORDER_ASYNC = 7; /** * @deprecated Use {@link #enumInit(Object, Context, VarScope, int)} instead @@ -2817,6 +2863,7 @@ public static Boolean enumNext(Object enumObj, Context cx) { private static Boolean enumNextInOrder(IdEnumeration enumObj, Context cx) { Object v = ScriptableObject.getProperty(enumObj.iterator, ES6Iterator.NEXT_METHOD); if (!(v instanceof Callable)) { + new Error(String.format("%s", v)).printStackTrace(); throw notFunctionError(enumObj.iterator, ES6Iterator.NEXT_METHOD); } Callable f = (Callable) v; @@ -2879,6 +2926,105 @@ public static Object enumValue(Object enumObj, Context cx) { return result; } + /** + * Internal marker that wraps the operand of an {@code await} inside an async-generator body, so + * the driver can tell a yield point that is an {@code await} (resume generator with the + * resolved value) from a plain {@code yield} (resolve the consumer's pending request). + */ + public static final class AwaitMarker { + private final Object value; + + public AwaitMarker(Object value) { + this.value = value; + } + + public Object getValue() { + return value; + } + } + + /** Wrap {@code v} in an {@link AwaitMarker}. Invoked from generated code for {@code await}. */ + public static Object wrapAwait(Object v) { + return new AwaitMarker(v); + } + + /** + * Initialise async iteration for a for-await-of loop. Looks up {@code [Symbol.asyncIterator]} + * on the operand and falls back to {@code [Symbol.iterator]} when absent (per the spec's + * CreateAsyncFromSyncIterator coercion, simplified: the sync iterator's results will flow + * through the existing await machinery unchanged). + */ + public static Object enumInitAsyncIterator(Object value, Context cx, VarScope scope) { + IdEnumeration x = new IdEnumeration(); + x.obj = toObjectOrNull(cx, value, scope); + if (x.obj == null || !(x.obj instanceof SymbolScriptable)) { + throw typeErrorById("msg.not.iterable", toString(value)); + } + x.enumType = ENUMERATE_VALUES_IN_ORDER_ASYNC; + x.isAsyncIteration = true; + + Object iteratorFn = Scriptable.NOT_FOUND; + if (ScriptableObject.hasProperty(x.obj, SymbolKey.ASYNC_ITERATOR)) { + iteratorFn = ScriptableObject.getProperty(x.obj, SymbolKey.ASYNC_ITERATOR); + } + if (iteratorFn == Scriptable.NOT_FOUND + || iteratorFn == null + || Undefined.isUndefined(iteratorFn)) { + if (!ScriptableObject.hasProperty(x.obj, SymbolKey.ITERATOR)) { + throw typeErrorById("msg.not.iterable", toString(value)); + } + iteratorFn = ScriptableObject.getProperty(x.obj, SymbolKey.ITERATOR); + } + if (!(iteratorFn instanceof Callable)) { + throw typeErrorById("msg.not.iterable", toString(value)); + } + Callable f = (Callable) iteratorFn; + VarScope callScope = + (f instanceof Function) ? ((Function) f).getDeclarationScope() : cx.topCallScope; + Object v = f.call(cx, callScope, x.obj, emptyArgs); + if (!(v instanceof Scriptable)) { + throw typeErrorById("msg.not.iterable", toString(value)); + } + x.iterator = (Scriptable) v; + return x; + } + + /** + * Invoke {@code iterator.next()} on an async-iteration enumeration and return the raw result + * (which is typically a Promise of an IteratorResult, but may be a plain IteratorResult when + * the underlying iterator is a sync iterator coerced to async). The caller is expected to + * {@code await} this value and then pass the resolved IteratorResult to {@link + * #enumAsyncStep(Object, Object, Context)}. + */ + public static Object enumAsyncNext(Object enumObj, Context cx) { + IdEnumeration x = (IdEnumeration) enumObj; + Object nextFn = ScriptableObject.getProperty(x.iterator, ES6Iterator.NEXT_METHOD); + if (!(nextFn instanceof Callable)) { + throw notFunctionError(x.iterator, ES6Iterator.NEXT_METHOD); + } + Callable f = (Callable) nextFn; + VarScope callScope = + (f instanceof Function) ? ((Function) f).getDeclarationScope() : cx.topCallScope; + return f.call(cx, callScope, x.iterator, emptyArgs); + } + + /** + * After the async-iteration result has been awaited, process it: store the {@code .value} on + * the enumeration so a subsequent {@code ENUM_ID} reads it, and return {@code Boolean.TRUE} if + * iteration should continue (i.e. {@code !done}), else {@code Boolean.FALSE}. + */ + public static Boolean enumAsyncStep(Object enumObj, Object awaitedResult, Context cx) { + IdEnumeration x = (IdEnumeration) enumObj; + Scriptable resultObj = toObject(cx, cx.topCallScope, awaitedResult); + Object done = ScriptableObject.getProperty(resultObj, ES6Iterator.DONE_PROPERTY); + if (done != Scriptable.NOT_FOUND && toBoolean(done)) { + x.currentId = Undefined.instance; + return Boolean.FALSE; + } + x.currentId = ScriptableObject.getProperty(resultObj, ES6Iterator.VALUE_PROPERTY); + return Boolean.TRUE; + } + private static void enumChangeObject(IdEnumeration x) { Object[] ids = null; while (x.obj != null) { @@ -2984,7 +3130,9 @@ private static Callable getNameFunctionAndThisInner( } throw notFunctionError(result, name); } - // Top scope is not NativeWith or NativeCall => thisObj == scope + // Top scope is not a with scope or an activation frame, + // the called function will resolve `globalThis` in its + // realm. storeScriptable(cx, Undefined.SCRIPTABLE_UNDEFINED); return (Callable) result; } @@ -3024,7 +3172,9 @@ private static LookupResult getNameAndThisInner( throw notFoundError(scope, name); } } - // Top scope is not NativeWith or NativeCall => thisObj == scope + // If the scope is a wtih scope then `this` will be its + // object, otherwise the callee will resolve `globalThis` + // in its realm. return new LookupResult( result, scope instanceof WithScope @@ -3381,6 +3531,81 @@ public static Object callIterator(Object obj, Context cx, VarScope scope) { return getIterator.call(cx, scope, iterable, ScriptRuntime.emptyArgs); } + /** Result of {@link #callAsyncIterator}: the iterator and whether it is async. */ + public static final class AsyncIteratorResult { + private final Object iterator; + private final boolean isAsync; + + AsyncIteratorResult(Object iterator, boolean isAsync) { + this.iterator = iterator; + this.isAsync = isAsync; + } + + public Object getIterator() { + return iterator; + } + + public boolean isAsync() { + return isAsync; + } + } + + /** + * Get an async iterator for {@code obj} following the hint=async variant of GetIterator: try + * {@code [Symbol.asyncIterator]} first, and only fall back to {@code [Symbol.iterator]} when + * the async lookup does not produce a callable method. Used by {@code yield*} in async + * generator functions. + */ + public static AsyncIteratorResult callAsyncIterator(Object obj, Context cx, VarScope scope) { + Scriptable sobj = toObjectOrNull(cx, obj, scope); + if (sobj != null) { + Object asyncMethod = ScriptableObject.getProperty(sobj, SymbolKey.ASYNC_ITERATOR); + if (asyncMethod != Scriptable.NOT_FOUND + && asyncMethod != null + && !Undefined.isUndefined(asyncMethod)) { + if (!(asyncMethod instanceof Callable)) { + throw notFunctionError(sobj, SymbolKey.ASYNC_ITERATOR); + } + Callable f = (Callable) asyncMethod; + VarScope callScope = + (f instanceof Function) + ? ((Function) f).getDeclarationScope() + : cx.topCallScope; + Object iter = f.call(cx, callScope, sobj, emptyArgs); + return new AsyncIteratorResult(iter, true); + } + } + // Fall back to the sync iterator protocol. + Object iter = callIterator(obj, cx, scope); + return new AsyncIteratorResult(iter, false); + } + + /** + * Implements the abrupt-completion branch of the ECMAScript IteratorClose abstract operation. + * Called from destructuring (and similar) code during unwinding so the original abrupt + * completion is preserved: any exception thrown by iterator.return is swallowed, and a + * non-object return value does NOT raise a TypeError. If {@code iterator} is null or not a + * Scriptable (e.g. it was never opened) this is a no-op. + */ + public static void closeIteratorAbrupt(Object iterator, Context cx, VarScope scope) { + if (!(iterator instanceof Scriptable)) { + return; + } + Scriptable iter = (Scriptable) iterator; + try { + Object ret = ScriptableObject.getProperty(iter, "return"); + if (ret == Scriptable.NOT_FOUND || ret == Undefined.instance || ret == null) { + return; + } + if (!(ret instanceof Callable)) { + return; + } + ((Callable) ret).call(cx, scope, iter, ScriptRuntime.emptyArgs); + } catch (RuntimeException ignored) { + // Per spec: on abrupt completion, errors from return() are discarded. + } + } + /** * Given an iterator result, return true if and only if there is a "done" property that's true. */ @@ -3398,7 +3623,7 @@ public static boolean isIteratorDone(Context cx, Object result) { * times. The args array reference should not be stored in any object that can be GC-reachable * after this method returns. If this is necessary, store args.clone(), not args array itself. */ - public static Ref callRef(Callable function, Scriptable thisObj, Object[] args, Context cx) { + public static Ref callRef(Callable function, Object thisObj, Object[] args, Context cx) { if (function instanceof RefCallable) { RefCallable rfunction = (RefCallable) function; Ref ref = rfunction.refCall(cx, thisObj, args); @@ -3431,7 +3656,7 @@ public static Object callSpecial( Scriptable thisObj, Object[] args, VarScope scope, - Scriptable callerThis, + Object callerThis, int callType, String filename, int lineNumber, @@ -3442,7 +3667,7 @@ public static Object callSpecial( if (callType == Node.SPECIALCALL_EVAL) { if (thisObj.getParentScope() == null && NativeGlobal.isEvalFunction(fun)) { - return evalSpecial(cx, (VarScope) scope, callerThis, args, filename, lineNumber); + return evalSpecial(cx, scope, callerThis, args, filename, lineNumber); } } else { throw Kit.codeBug(); @@ -3470,7 +3695,7 @@ public static Object newSpecial( *

See Ecma 15.3.4.[34] */ public static Object applyOrCall( - boolean isApply, Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + boolean isApply, Context cx, VarScope scope, Object thisObj, Object[] args) { int L = args.length; Callable function = getCallable(thisObj); @@ -3534,7 +3759,7 @@ static boolean isArrayLike(Scriptable obj) { || ScriptableObject.hasProperty(obj, "length")); } - static Object[] getApplyArguments(Context cx, Object arg1) { + public static Object[] getApplyArguments(Context cx, Object arg1) { if (arg1 == null || Undefined.isUndefined(arg1)) { return ScriptRuntime.emptyArgs; } else if (arg1 instanceof Scriptable && isArrayLike((Scriptable) arg1)) { @@ -3546,14 +3771,16 @@ static Object[] getApplyArguments(Context cx, Object arg1) { } } - static Callable getCallable(Scriptable thisObj) { + static Callable getCallable(Object thisObj) { Callable function; if (thisObj instanceof Callable) { function = (Callable) thisObj; } else if (thisObj == null) { throw ScriptRuntime.notFunctionError(null, null); + } else if (!(thisObj instanceof Scriptable)) { + throw ScriptRuntime.notFunctionError(thisObj, null); } else { - Object value = thisObj.getDefaultValue(ScriptRuntime.FunctionClass); + Object value = ((Scriptable) thisObj).getDefaultValue(ScriptRuntime.FunctionClass); if (!(value instanceof Callable)) { throw ScriptRuntime.notFunctionError(value, thisObj); } @@ -4873,12 +5100,12 @@ public static TopLevel getTopCallScope(Context cx) { */ @Deprecated public static Object doTopCall( - Callable callable, Context cx, VarScope scope, Scriptable thisObj, Object[] args) { + Callable callable, Context cx, VarScope scope, Object thisObj, Object[] args) { return doTopCall(callable, cx, scope, thisObj, args, cx.isStrictMode()); } @Deprecated - public static Object doTopCall(Script script, Context cx, VarScope scope, Scriptable thisObj) { + public static Object doTopCall(Script script, Context cx, VarScope scope, Object thisObj) { return doTopCall(script, cx, scope, thisObj, cx.isStrictMode()); } @@ -4886,7 +5113,7 @@ public static Object doTopCall( Callable callable, Context cx, VarScope scope, - Scriptable thisObj, + Object thisObj, Object[] args, boolean isTopLevelStrict) { if (scope == null) throw new IllegalArgumentException(); @@ -4913,11 +5140,7 @@ public static Object doTopCall( } public static Object doTopCall( - Script script, - Context cx, - VarScope scope, - Scriptable thisObj, - boolean isTopLevelStrict) { + Script script, Context cx, VarScope scope, Object thisObj, boolean isTopLevelStrict) { if (scope == null) throw new IllegalArgumentException(); if (cx.topCallScope != null) throw new IllegalStateException(); @@ -4982,11 +5205,7 @@ public static void addInstructionCount(Context cx, int instructionsToAdd) { } public static void initScript( - ScriptOrFn execObj, - Scriptable thisObj, - Context cx, - VarScope scope, - boolean evalScript) { + ScriptOrFn execObj, Object thisObj, Context cx, VarScope scope, boolean evalScript) { if (cx.topCallScope == null) throw new IllegalStateException(); var desc = execObj.getDescriptor(); @@ -4997,7 +5216,7 @@ public static void initScript( VarScope varScope = scope; // Never define any variables from var statements inside with // object. See bug 38590. - while (varScope instanceof WithScope) { + while (varScope.isNestedScope()) { varScope = varScope.getParentScope(); } @@ -5088,9 +5307,9 @@ static NativeCall findFunctionActivation(Context cx, Function f) { return null; } - public static Scriptable newCatchScope( + public static VarScope newCatchScope( Throwable t, - Scriptable lastCatchScope, + VarScope lastCatchScope, String exceptionName, Context cx, VarScope scope) { @@ -5108,8 +5327,7 @@ public static Scriptable newCatchScope( // the previous scope object if (lastCatchScope != null) { - NativeObject last = (NativeObject) lastCatchScope; - obj = last.getAssociatedValue(t); + obj = ((DeclarationScope) lastCatchScope).getAssociatedValue(t); if (obj == null) Kit.codeBug(); break getObj; } @@ -5196,26 +5414,26 @@ public static Scriptable newCatchScope( obj = errorObject; } - NativeObject catchScopeObject = new NativeObject(); + var catchScope = new CatchScope(scope); // See ECMA 12.4 if (exceptionName != null) { - catchScopeObject.defineProperty(exceptionName, obj, ScriptableObject.PERMANENT); + catchScope.defineProperty(exceptionName, obj, ScriptableObject.PERMANENT); } if (cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS) && isVisible(cx, t)) { // Add special Rhino object __exception__ defined in the catch // scope that can be used to retrieve the Java exception associated // with the JavaScript exception (to get stack trace info, etc.) - catchScopeObject.defineProperty( + catchScope.defineProperty( "__exception__", Context.javaToJS(t, scope), ScriptableObject.PERMANENT | ScriptableObject.DONTENUM); } if (cacheObj) { - catchScopeObject.associateValue(t, obj); + catchScope.associateValue(t, obj); } - return catchScopeObject; + return catchScope; } public static Scriptable wrapException(Throwable t, VarScope scope, Context cx) { @@ -5312,9 +5530,12 @@ public static VarScope enterWith(Object obj, Context cx, VarScope scope) { return new WithScope(scope, sobj); } - public static VarScope leaveWith(VarScope scope) { - WithScope nw = (WithScope) scope; - return nw.getParentScope(); + public static VarScope enterScope(VarScope scope) { + return new LocalScope(scope); + } + + public static VarScope leaveScope(VarScope scope) { + return scope.getParentScope(); } public static VarScope enterDotQuery(Object value, VarScope scope) { @@ -5360,9 +5581,22 @@ public static void setFunctionProtoAndParent( public static void setFunctionProtoAndParent( BaseFunction fn, Context cx, VarScope scope, boolean es6GeneratorFunction) { + setFunctionProtoAndParent(fn, cx, scope, es6GeneratorFunction, false); + } + + public static void setFunctionProtoAndParent( + BaseFunction fn, + Context cx, + VarScope scope, + boolean es6GeneratorFunction, + boolean isAsync) { fn.setParentScope(scope); - if (es6GeneratorFunction) { + if (es6GeneratorFunction && isAsync) { + fn.setPrototype(ScriptableObject.getAsyncGeneratorFunctionPrototype(scope)); + } else if (es6GeneratorFunction) { fn.setPrototype(ScriptableObject.getGeneratorFunctionPrototype(scope)); + } else if (isAsync) { + fn.setPrototype(ScriptableObject.getAsyncFunctionPrototype(scope)); } else { fn.setPrototype(ScriptableObject.getFunctionPrototype(scope)); } @@ -5372,6 +5606,273 @@ public static void setFunctionProtoAndParent( } } + public static Scriptable superConstructorCall( + BaseFunction thisFn, Context cx, VarScope scope, Object[] args) { + Scriptable homeObject = thisFn.getHomeObject(); + if (homeObject == null || !(homeObject instanceof Constructable)) { + throw typeErrorById("msg.extends.not.ctor"); + } + if (homeObject instanceof JSFunction) { + // Pass new.target (the original constructor) through so the correct + // prototype is used for the new object. + return ((JSFunction) homeObject).construct(cx, thisFn, scope, null, args); + } + return ((Constructable) homeObject).construct(cx, scope, args); + } + + public static void setupClassPrototypeChain( + BaseFunction constructor, Scriptable superClass, VarScope scope) { + if (superClass == null) { + return; + } + + if (!(superClass instanceof ScriptableObject)) { + throw typeError("Super class must be an object"); + } + // Set constructor.__proto__ = superClass + // This enables static method inheritance (future) and super() resolution + constructor.setPrototype(superClass); + + // Set constructor.prototype.__proto__ = superClass.prototype + Object superProto = superClass.get("prototype", superClass); + Object constructorProto = constructor.getPrototypeProperty(); + if (constructorProto instanceof ScriptableObject && superProto instanceof Scriptable) { + ((ScriptableObject) constructorProto).setPrototype((Scriptable) superProto); + } + } + + public static void defineClassMethod(BaseFunction constructor, String name, Object methodFn) { + Object protoObj = constructor.getPrototypeProperty(); + if (protoObj instanceof ScriptableObject) { + // ES6 14.5.14: methods are {writable: true, enumerable: false, configurable: true} + ScriptableObject proto = (ScriptableObject) protoObj; + StringIdOrIndex s = toStringIdOrIndex(name); + if (s.stringId != null) { + proto.defineProperty(s.stringId, methodFn, ScriptableObject.DONTENUM); + } else { + proto.put(s.index, proto, methodFn); + proto.setAttributes(s.index, ScriptableObject.DONTENUM); + } + } + } + + public static void defineStaticClassMethod( + BaseFunction constructor, String name, Object methodFn) { + // Static methods are non-enumerable properties on the constructor itself + constructor.defineOwnProperty( + Context.getCurrentContext(), name, new DescriptorInfo(methodFn, DONTENUM, true)); + } + + private static void defineAccessorOn( + ScriptableObject target, String name, Object fn, boolean isSetter) { + StringIdOrIndex s = toStringIdOrIndex(name); + if (fn instanceof Callable) { + if (s.stringId != null) { + target.setGetterOrSetter(s.stringId, 0, (Callable) fn, isSetter); + target.setAttributes( + s.stringId, (target.getAttributes(s.stringId) | DONTENUM) & ~READONLY); + } else { + target.setGetterOrSetter(null, s.index, (Callable) fn, isSetter); + target.setAttributes( + s.index, (target.getAttributes(s.index) | DONTENUM) & ~READONLY); + } + } + } + + public static void defineClassGetter(BaseFunction constructor, String name, Object methodFn) { + Object protoObj = constructor.getPrototypeProperty(); + if (protoObj instanceof ScriptableObject) { + defineAccessorOn((ScriptableObject) protoObj, name, methodFn, false); + } + } + + public static void defineClassSetter(BaseFunction constructor, String name, Object methodFn) { + Object protoObj = constructor.getPrototypeProperty(); + if (protoObj instanceof ScriptableObject) { + defineAccessorOn((ScriptableObject) protoObj, name, methodFn, true); + } + } + + public static void defineStaticClassGetter( + BaseFunction constructor, String name, Object methodFn) { + defineAccessorOn(constructor, name, methodFn, false); + } + + public static void defineStaticClassSetter( + BaseFunction constructor, String name, Object methodFn) { + defineAccessorOn(constructor, name, methodFn, true); + } + + private static void defineMethodOn( + ScriptableObject target, Object key, Object methodFn, int attrs) { + if (key instanceof Symbol) { + ((SymbolScriptable) target).put((Symbol) key, target, methodFn); + target.setAttributes((Symbol) key, attrs); + return; + } + StringIdOrIndex s = toStringIdOrIndex(key); + if (s.stringId != null) { + target.defineProperty(s.stringId, methodFn, attrs); + } else { + target.put(s.index, target, methodFn); + target.setAttributes(s.index, attrs); + } + } + + private static void defineComputedAccessorOn( + ScriptableObject target, Object key, Object fn, boolean isSetter) { + if (!(fn instanceof Callable)) { + return; + } + Callable callable = (Callable) fn; + if (key instanceof Symbol) { + target.setGetterOrSetter((Symbol) key, 0, callable, isSetter); + target.setAttributes( + (Symbol) key, (target.getAttributes((Symbol) key) | DONTENUM) & ~READONLY); + return; + } + StringIdOrIndex s = toStringIdOrIndex(key); + if (s.stringId != null) { + target.setGetterOrSetter(s.stringId, 0, callable, isSetter); + target.setAttributes( + s.stringId, (target.getAttributes(s.stringId) | DONTENUM) & ~READONLY); + } else { + target.setGetterOrSetter(null, s.index, callable, isSetter); + target.setAttributes(s.index, (target.getAttributes(s.index) | DONTENUM) & ~READONLY); + } + } + + public static void defineClassComputedMethod( + BaseFunction constructor, Object key, Object methodFn) { + Object protoObj = constructor.getPrototypeProperty(); + if (protoObj instanceof ScriptableObject) { + defineMethodOn((ScriptableObject) protoObj, key, methodFn, ScriptableObject.DONTENUM); + } + } + + public static void defineStaticClassComputedMethod( + BaseFunction constructor, Object key, Object methodFn) { + defineMethodOn(constructor, key, methodFn, ScriptableObject.DONTENUM); + } + + public static void defineClassComputedGetter( + BaseFunction constructor, Object key, Object methodFn) { + Object protoObj = constructor.getPrototypeProperty(); + if (protoObj instanceof ScriptableObject) { + defineComputedAccessorOn((ScriptableObject) protoObj, key, methodFn, false); + } + } + + public static void defineClassComputedSetter( + BaseFunction constructor, Object key, Object methodFn) { + Object protoObj = constructor.getPrototypeProperty(); + if (protoObj instanceof ScriptableObject) { + defineComputedAccessorOn((ScriptableObject) protoObj, key, methodFn, true); + } + } + + public static void defineStaticClassComputedGetter( + BaseFunction constructor, Object key, Object methodFn) { + defineComputedAccessorOn(constructor, key, methodFn, false); + } + + public static void defineStaticClassComputedSetter( + BaseFunction constructor, Object key, Object methodFn) { + defineComputedAccessorOn(constructor, key, methodFn, true); + } + + public static void defineStaticClassField(BaseFunction constructor, String name, Object value) { + constructor.defineOwnProperty( + Context.getCurrentContext(), name, new DescriptorInfo(value, 0, true)); + } + + /** Associated-value key used to stash pre-evaluated instance computed field keys. */ + private static final Object CLASS_COMPUTED_KEYS_ASSOC = new Object(); + + /** + * Store the pre-evaluated instance computed-field keys on the class constructor so the + * constructor can retrieve them at instance creation time. Called at class declaration time. + */ + public static void storeClassComputedFieldKeys(BaseFunction constructor, Object[] keys) { + constructor.associateValue(CLASS_COMPUTED_KEYS_ASSOC, keys); + } + + /** Retrieve the i-th pre-evaluated instance computed-field key stored on {@code fn}. */ + public static Object getClassComputedFieldKey(ScriptOrFn fn, int index) { + if (fn instanceof BaseFunction) { + Object stored = ((BaseFunction) fn).getAssociatedValue(CLASS_COMPUTED_KEYS_ASSOC); + if (stored instanceof Object[]) { + return ((Object[]) stored)[index]; + } + } + throw Kit.codeBug(); + } + + public static void defineStaticClassComputedField( + BaseFunction constructor, Object key, Object value) { + if (key instanceof Symbol) { + Symbol sym = (Symbol) key; + ((SymbolScriptable) constructor).put(sym, constructor, value); + // Private-name SymbolKeys identify static private fields, which must be + // hidden from Object.getOwnPropertySymbols just like instance private fields. + String name = sym.getName(); + if (name != null && !name.isEmpty() && name.charAt(0) == '#') { + constructor.setAttributes( + sym, constructor.getAttributes(sym) | ScriptableObject.PRIVATE); + } + } else { + StringIdOrIndex s = toStringIdOrIndex(key); + if (s.stringId != null) { + constructor.put(s.stringId, constructor, value); + } else { + constructor.put(s.index, constructor, value); + } + } + } + + /** + * Initialize a private class field on {@code target} keyed by the given {@link Symbol}, with + * attribute {@link ScriptableObject#PRIVATE} so the slot is hidden from enumeration and {@code + * Object.getOwnPropertySymbols}. + */ + public static void definePrivateField( + Object target, Symbol key, Object value, Context cx, VarScope scope) { + Scriptable obj = toObjectOrNull(cx, target, scope); + if (obj == null) { + throw undefReadError(target, String.valueOf(key)); + } + SymbolScriptable sObj = (obj instanceof SymbolScriptable) ? (SymbolScriptable) obj : null; + if (sObj == null) { + throw typeErrorById("msg.ctor.not.found", String.valueOf(key)); + } + sObj.put(key, obj, value); + if (obj instanceof ScriptableObject) { + ((ScriptableObject) obj) + .setAttributes( + key, + ((ScriptableObject) obj).getAttributes(key) | ScriptableObject.PRIVATE); + } + } + + /** + * Install a private getter or setter on {@code target} keyed by the given {@link Symbol}. Marks + * the slot with {@link ScriptableObject#PRIVATE} so it's hidden from enumeration. + */ + public static void definePrivateAccessor( + Object target, Symbol key, Object fn, boolean isSetter, Context cx, VarScope scope) { + Scriptable obj = toObjectOrNull(cx, target, scope); + if (obj == null) { + throw undefReadError(target, String.valueOf(key)); + } + if (!(obj instanceof ScriptableObject)) { + throw typeErrorById("msg.ctor.not.found", String.valueOf(key)); + } + ScriptableObject so = (ScriptableObject) obj; + Callable accessor = (fn instanceof Callable) ? (Callable) fn : null; + so.setGetterOrSetter(key, 0, accessor, isSetter); + so.setAttributes(key, so.getAttributes(key) | ScriptableObject.PRIVATE); + } + public static void setObjectProtoAndParent(ScriptableObject object, VarScope scope) { // Compared with function it always sets the scope to top scope scope = ScriptableObject.getTopLevelScope(scope); @@ -5389,10 +5890,33 @@ public static void setBuiltinProtoAndParent( public static void setBuiltinProtoAndParent( ScriptableObject obj, JSFunction f, Object nt, VarScope s, TopLevel.Builtins type) { - obj.setPrototype((Scriptable) f.getPrototypeProperty()); + obj.setPrototype(findPrototype(f, nt, type)); + obj.setParentScope(s); } + public static Scriptable findPrototype(JSFunction f, Object nt, TopLevel.Builtins type) { + Object proto; + if (nt instanceof JSFunction) { + proto = ((JSFunction) nt).getPrototypeProperty(); + } else if (nt instanceof Scriptable + && nt instanceof Function + && ((Function) nt).isConstructor()) { + var sobj = ((Scriptable) nt); + proto = sobj.get("prototype", sobj); + } else { + nt = f; + proto = ((JSFunction) nt).getPrototypeProperty(); + } + + if (proto instanceof Scriptable) { + return (Scriptable) proto; + } else { + TopLevel top = ScriptableObject.getTopLevelScope(((Function) nt).getDeclarationScope()); + return TopLevel.getBuiltinPrototype(top, type); + } + } + public static void initFunction( Context cx, VarScope scope, JSFunction function, int type, boolean fromEvalCode) { if (type == FunctionNode.FUNCTION_STATEMENT) { @@ -5413,11 +5937,18 @@ public static void initFunction( // Always put function expression statements into initial // activation object ignoring the with statement to follow // SpiderMonkey - while (scope instanceof WithScope) { + while (scope.isNestedScope()) { scope = scope.getParentScope(); } scope.put(name, scope, function); } + } else if (type == FunctionNode.FUNCTION_BLOCK_SCOPED) { + String name = function.getFunctionName(); + if (name != null && name.length() != 0) { + // Block-scoped function in strict mode: bind in the current + // (block) scope only, do not walk up to the activation object. + scope.put(name, scope, function); + } } else { throw Kit.codeBug(); } @@ -5934,8 +6465,9 @@ public static Scriptable wrapRegExp(Context cx, VarScope scope, Object compiled) } public static Scriptable getTemplateLiteralCallSite( - Context cx, VarScope scope, Object[] strings, int index) { - Object callsite = strings[index]; + Context cx, VarScope scope, JSDescriptor descriptor, int index) { + Object[] literals = descriptor.getLiterals(); + Object callsite = literals[index]; if (callsite instanceof Scriptable) return (Scriptable) callsite; @@ -5961,7 +6493,7 @@ public static Scriptable getTemplateLiteralCallSite( AbstractEcmaObjectOperations.setIntegrityLevel( cx, siteObj, AbstractEcmaObjectOperations.INTEGRITY_LEVEL.FROZEN); - strings[index] = siteObj; + literals[index] = siteObj; return siteObj; } @@ -6084,6 +6616,14 @@ public static boolean isSymbol(Object obj) { || (obj instanceof SymbolKey); } + /** + * Returns whether {@code obj} is a {@link Symbol} that is the identity key of a JavaScript + * class private field or method. + */ + public static boolean isPrivateSymbol(Object obj) { + return isSymbol(obj) && ((Symbol) obj).getKind() == Symbol.Kind.PRIVATE; + } + /** * Return that the symbol was created by the constructor, or is a built-in Symbol, and was not * put in the registry using "for". @@ -6188,7 +6728,7 @@ public static Scriptable getThisForScope(VarScope scope, Object callThisArg) { } else if (callThisArg == null) { return null; } - return ScriptRuntime.toObject((VarScope) scope, callThisArg); + return ScriptRuntime.toObject(scope, callThisArg); } /** diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntimeES6.java b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntimeES6.java index 2d0c33b8357..a558ac5ebf8 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntimeES6.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntimeES6.java @@ -45,8 +45,7 @@ public static DescriptorInfo symbolSpecies( scope, "get [Symbol.species]", 0, - (Context lcx, VarScope lscope, Scriptable thisObj, Object[] args) -> - thisObj, + (Context lcx, VarScope lscope, Object thisObj, Object[] args) -> thisObj, false), ScriptableObject.NOT_FOUND, ScriptableObject.NOT_FOUND); diff --git a/rhino/src/main/java/org/mozilla/javascript/Scriptable.java b/rhino/src/main/java/org/mozilla/javascript/Scriptable.java index abe6f5e8f10..19d47767203 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Scriptable.java +++ b/rhino/src/main/java/org/mozilla/javascript/Scriptable.java @@ -288,6 +288,7 @@ default Scriptable getAncestor() { * @return an array of Objects. Each entry in the array is either a java.lang.String or a * java.lang.Number */ + @Override public Object[] getIds(); /** diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java b/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java index 436de5e1544..1c60c99061d 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java @@ -102,6 +102,10 @@ public abstract class ScriptableObject extends SlotMapOwner */ public static final int UNINITIALIZED_CONST = 0x08; + public static final int PRIVATE = 0x10; + + public static final int INTERNAL = 0x20; + public static final int CONST = PERMANENT | READONLY | UNINITIALIZED_CONST; /** The prototype of this object. */ @@ -138,7 +142,7 @@ protected void setCommonDescriptorProperties(int attributes, boolean defineWrita } static void checkValidAttributes(int attributes) { - final int mask = READONLY | DONTENUM | PERMANENT | UNINITIALIZED_CONST; + final int mask = READONLY | DONTENUM | PERMANENT | UNINITIALIZED_CONST | PRIVATE; if ((attributes & ~mask) != 0) { throw new IllegalArgumentException(String.valueOf(attributes)); } @@ -605,22 +609,6 @@ public void setParentScope(VarScope m) { parentScopeObject = m; } - /** - * Returns an array of ids for the properties of the object. - * - *

Any properties with the attribute DONTENUM are not listed. - * - * @return an array of java.lang.Objects with an entry for every listed property. Properties - * accessed via an integer index will have a corresponding Integer entry in the returned - * array. Properties accessed by a String will have a String entry in the returned array. - */ - @Override - public Object[] getIds() { - try (var map = startCompoundOp(false)) { - return getIds(map, false, false); - } - } - /** * Returns an array of ids of all non-Symbol properties of the object. * @@ -1206,13 +1194,14 @@ public static > void defineProperty( so.defineProperty(propertyName, value, attributes); } - public static void defineProperty( - Scriptable destination, SymbolKey propertyName, Object value, int attributes) { - if (!(destination instanceof ScriptableObject)) { - ((SymbolScriptable) destination).put(propertyName, destination, value); + public static > void defineProperty( + T destination, SymbolKey propertyName, Object value, int attributes) { + if (!(destination instanceof SlotMapOwner)) { + destination.put(propertyName, destination, value); return; } - ScriptableObject so = (ScriptableObject) destination; + @SuppressWarnings("unchecked") + SlotMapOwner so = (SlotMapOwner) destination; so.defineProperty(propertyName, value, attributes); } @@ -1270,10 +1259,11 @@ public void defineBuiltinProperty( * @param destination ScriptableObject to define the property on * @param propertyName the name of the property to define. */ - public static void defineConstProperty(Scriptable destination, String propertyName) { + public static > void defineConstProperty( + T destination, String propertyName) { if (destination instanceof ConstProperties) { @SuppressWarnings("unchecked") - var cp = (ConstProperties) destination; + var cp = (ConstProperties) destination; cp.defineConst(propertyName, destination); } else defineProperty(destination, propertyName, Undefined.instance, CONST); } @@ -2194,6 +2184,16 @@ public static Scriptable getGeneratorFunctionPrototype(VarScope scope) { getTopLevelScope(scope), TopLevel.Builtins.GeneratorFunction); } + public static Scriptable getAsyncFunctionPrototype(VarScope scope) { + return TopLevel.getBuiltinPrototype( + getTopLevelScope(scope), TopLevel.Builtins.AsyncFunction); + } + + public static Scriptable getAsyncGeneratorFunctionPrototype(VarScope scope) { + return TopLevel.getBuiltinPrototype( + getTopLevelScope(scope), TopLevel.Builtins.AsyncGeneratorFunction); + } + public static Scriptable getArrayPrototype(VarScope scope) { return TopLevel.getBuiltinPrototype(getTopLevelScope(scope), TopLevel.Builtins.Array); } @@ -2228,6 +2228,24 @@ public static Scriptable getClassPrototype(VarScope scope, String className) { return null; } + public static Scriptable getClassPrototype(VarScope scope, Symbol className) { + scope = getTopLevelScope(scope); + Object ctor = getProperty(scope, className); + Object proto; + if (ctor instanceof BaseFunction) { + proto = ((BaseFunction) ctor).getPrototypeProperty(); + } else if (ctor instanceof Scriptable) { + Scriptable ctorObj = (Scriptable) ctor; + proto = ctorObj.get("prototype", ctorObj); + } else { + return null; + } + if (proto instanceof Scriptable) { + return (Scriptable) proto; + } + return null; + } + /** * Get the global scope. * @@ -2323,7 +2341,15 @@ protected static void checkNotSealed(ScriptableObject obj, Object key, int index * @since 1.5R2 */ public static Object getProperty(Scriptable obj, String name) { - return getPropWalkingPrototypeChain(obj, name, obj); + return getPropWalkingPrototypeChain((Scriptable) obj, name, (Scriptable) obj); + } + + public static Object getProperty(VarScope obj, String name) { + return obj.get(name, obj); + } + + public static Object getProperty(VarScope obj, Symbol name) { + return obj.get(name, obj); } /** @@ -2463,6 +2489,10 @@ public static boolean hasProperty(Scriptable obj, String name) { return null != getBase(obj, name); } + public static boolean hasProperty(VarScope obj, String name) { + return null != getBase(obj, name); + } + /** * If hasProperty(obj, name) would return true, then if the property that was found is * compatible with the new property, this method just returns. If the property is not @@ -2474,14 +2504,22 @@ public static boolean hasProperty(Scriptable obj, String name) { public static void redefineProperty(Scriptable obj, String name, boolean isConst) { Scriptable base = getBase(obj, name); if (base == null) return; - if (base instanceof ConstProperties) { - ConstProperties cp = (ConstProperties) base; + if (base instanceof ConstProperties) { + @SuppressWarnings("unchecked") + var cp = (ConstProperties) base; if (cp.isConst(name)) throw ScriptRuntime.typeErrorById("msg.const.redecl", name); } if (isConst) throw ScriptRuntime.typeErrorById("msg.var.redecl", name); } + public static void redefineProperty(VarScope obj, String name, boolean isConst) { + VarScope base = getBase(obj, name); + if (base == null) return; + if (base.isConst(name)) throw ScriptRuntime.typeErrorById("msg.const.redecl", name); + if (isConst) throw ScriptRuntime.typeErrorById("msg.var.redecl", name); + } + /** * Returns whether an indexed property is defined in an object or any object in its prototype * chain. @@ -2522,6 +2560,10 @@ public static void putProperty(Scriptable obj, String name, Object value) { base.put(name, obj, value); } + public static void putProperty(VarScope obj, String name, Object value) { + obj.put(name, obj, value); + } + /** Variant of putProperty to handle super.name = value */ public static void putSuperProperty( Scriptable superObj, Scriptable thisObj, String name, Object value) { @@ -2571,6 +2613,10 @@ public static void putConstProperty(Scriptable obj, String name, Object value) { } } + public static void putConstProperty(VarScope obj, String name, Object value) { + obj.putConst(name, obj, value); + } + /** * Puts an indexed property in an object or in an object in its prototype chain. * @@ -2730,18 +2776,21 @@ public static Object callMethod(Context cx, Scriptable obj, String methodName, O } static Scriptable getBase(Scriptable start, String name) { - Scriptable obj = start; + Scriptable obj = (Scriptable) start; + Scriptable startObj = obj; do { - if (obj.has(name, start)) break; - if (obj instanceof VarScope) { - obj = null; - } else { - obj = obj.getPrototype(); + if (obj.has(name, startObj)) { + break; } + obj = obj.getPrototype(); } while (obj != null); return obj; } + static VarScope getBase(VarScope start, String name) { + return start.has(name, start) ? start : null; + } + static Scriptable getBase(Scriptable start, int index) { Scriptable obj = start; do { @@ -2869,6 +2918,7 @@ private boolean putConstImpl( return slot.setValue(value, this, start); } + @Override Object[] getIds( CompoundOperationMap map, boolean getNonEnumerable, boolean getSymbols) { Object[] a; @@ -2888,6 +2938,7 @@ Object[] getIds( int c = externalLen; for (var slot : map) { + if ((slot.getAttributes() & PRIVATE) != 0) continue; if ((getNonEnumerable || (slot.getAttributes() & DONTENUM) == 0) && (getSymbols || !(slot.name instanceof Symbol))) { if (c == externalLen) { @@ -3023,7 +3074,7 @@ public Object get(Object key) { } } - private static final Comparator KEY_COMPARATOR = new KeyComparator(); + static final Comparator KEY_COMPARATOR = new KeyComparator(); /** * This comparator sorts property fields in spec-compliant order. Numeric ids first, in numeric diff --git a/rhino/src/main/java/org/mozilla/javascript/SecurityController.java b/rhino/src/main/java/org/mozilla/javascript/SecurityController.java index 64a519ef73c..eadcdcbfffb 100644 --- a/rhino/src/main/java/org/mozilla/javascript/SecurityController.java +++ b/rhino/src/main/java/org/mozilla/javascript/SecurityController.java @@ -135,14 +135,14 @@ public Object callWithDomain( Context cx, Callable callable, VarScope scope, - Scriptable thisObj, + Object thisObj, Object[] args) { return execWithDomain( cx, scope, new Script() { @Override - public Object exec(Context cx, VarScope scope, Scriptable thisObjIgnored) { + public Object exec(Context cx, VarScope scope, Object thisObjIgnored) { return callable.call(cx, scope, thisObj, args); } }, @@ -154,7 +154,7 @@ public Object callWithDomain( Context cx, Script script, VarScope scope, - Scriptable thisObj, + Object thisObj, Object[] args) { return execWithDomain(cx, scope, script, securityDomain); } diff --git a/rhino/src/main/java/org/mozilla/javascript/SlotMapOwner.java b/rhino/src/main/java/org/mozilla/javascript/SlotMapOwner.java index e6a718254ab..6e9501e5d17 100644 --- a/rhino/src/main/java/org/mozilla/javascript/SlotMapOwner.java +++ b/rhino/src/main/java/org/mozilla/javascript/SlotMapOwner.java @@ -1,10 +1,14 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.ScriptableObject.DONTENUM; +import static org.mozilla.javascript.ScriptableObject.PRIVATE; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -13,7 +17,6 @@ import java.util.Objects; public abstract class SlotMapOwner> implements PropHolder { - private static final long serialVersionUID = 1L; /** * Maximum size of an {@link EmbeddedSlotMap} before it is promoted to a {@link HashSlotMap}. @@ -558,7 +561,8 @@ public final synchronized Object associateValue(Object key, Object value) { return Kit.initHash(h, key, value); } - void addLazilyInitializedValue(String name, int index, LazilyLoadedCtor init, int attributes) { + void addLazilyInitializedValue( + String name, int index, LazilyLoadedCtor init, int attributes) { if (name != null && index != 0) throw new IllegalArgumentException(name); checkNotSealed(name, index); var lslot = getMap().compute(this, name, index, ScriptableObject::ensureLazySlot); @@ -566,7 +570,8 @@ void addLazilyInitializedValue(String name, int index, LazilyLoadedCtor init, in lslot.value = init; } - void addLazilyInitializedValue(Symbol key, int index, LazilyLoadedCtor init, int attributes) { + void addLazilyInitializedValue( + Symbol key, int index, LazilyLoadedCtor init, int attributes) { if (key != null && index != 0) throw new IllegalArgumentException(key.toString()); checkNotSealed(key, index); LazyLoadSlot lslot = getMap().compute(this, key, index, ScriptableObject::ensureLazySlot); @@ -793,6 +798,55 @@ public void defineProperty(Symbol key, Object value, int attributes) { setAttributes(key, attributes); } + /** + * Returns an array of ids for the properties of the object. + * + *

Any properties with the attribute DONTENUM are not listed. + * + * @return an array of java.lang.Objects with an entry for every listed property. Properties + * accessed via an integer index will have a corresponding Integer entry in the returned + * array. Properties accessed by a String will have a String entry in the returned array. + */ + @Override + public Object[] getIds() { + try (var map = startCompoundOp(false)) { + return getIds(map, false, false); + } + } + + Object[] getIds(CompoundOperationMap map, boolean getNonEnumerable, boolean getSymbols) { + if (map.isEmpty()) { + return ScriptRuntime.emptyArgs; + } + + int c = 0; + Object[] a = new Object[map.dirtySize()]; + + for (var slot : map) { + if ((slot.getAttributes() & PRIVATE) != 0) continue; + if ((getNonEnumerable || (slot.getAttributes() & DONTENUM) == 0) + && (getSymbols || !(slot.name instanceof Symbol))) { + a[c++] = slot.name != null ? slot.name : Integer.valueOf(slot.indexOrHash); + } + } + + Object[] result; + if (c == a.length) { + result = a; + } else { + result = new Object[c]; + System.arraycopy(a, 0, result, 0, c); + } + + Context cx = Context.getCurrentContext(); + if ((cx != null) && cx.hasFeature(Context.FEATURE_ENUMERATE_IDS_FIRST)) { + // Move all the numeric IDs to the front in numeric order + Arrays.sort(result, ScriptableObject.KEY_COMPARATOR); + } + + return result; + } + public abstract T getThis(); private volatile Map associatedValues; diff --git a/rhino/src/main/java/org/mozilla/javascript/Symbol.java b/rhino/src/main/java/org/mozilla/javascript/Symbol.java index 5b71ab73e24..fe4933bf13c 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Symbol.java +++ b/rhino/src/main/java/org/mozilla/javascript/Symbol.java @@ -18,7 +18,8 @@ public interface Symbol { enum Kind { REGULAR, // A regular symbol is created using the constructor BUILT_IN, // A built-in symbol is one of the properties of the "Symbol" constructor - REGISTERED // A registered symbol was created using "Symbol.for" + REGISTERED, // A registered symbol was created using "Symbol.for" + PRIVATE // A private symbol is the identity key for a class private field or method } /** diff --git a/rhino/src/main/java/org/mozilla/javascript/SymbolKey.java b/rhino/src/main/java/org/mozilla/javascript/SymbolKey.java index 41c28eea6fc..d449728e181 100644 --- a/rhino/src/main/java/org/mozilla/javascript/SymbolKey.java +++ b/rhino/src/main/java/org/mozilla/javascript/SymbolKey.java @@ -15,6 +15,7 @@ public class SymbolKey implements Symbol, Serializable { // These are common SymbolKeys that are equivalent to well-known symbols // defined in ECMAScript. public static final SymbolKey ITERATOR = new SymbolKey("Symbol.iterator", BUILT_IN); + public static final SymbolKey ASYNC_ITERATOR = new SymbolKey("Symbol.asyncIterator", BUILT_IN); public static final SymbolKey TO_STRING_TAG = new SymbolKey("Symbol.toStringTag", BUILT_IN); public static final SymbolKey SPECIES = new SymbolKey("Symbol.species", BUILT_IN); public static final SymbolKey HAS_INSTANCE = new SymbolKey("Symbol.hasInstance", BUILT_IN); diff --git a/rhino/src/main/java/org/mozilla/javascript/Synchronizer.java b/rhino/src/main/java/org/mozilla/javascript/Synchronizer.java index 7c8de98eb22..aa2c8b1a09f 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Synchronizer.java +++ b/rhino/src/main/java/org/mozilla/javascript/Synchronizer.java @@ -51,7 +51,7 @@ public Synchronizer(Scriptable obj, Object syncObject) { * @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) { Object sync = syncObject != null ? syncObject : thisObj; synchronized (sync instanceof Wrapper ? ((Wrapper) sync).unwrap() : sync) { return ((Function) obj).call(cx, scope, thisObj, args); diff --git a/rhino/src/main/java/org/mozilla/javascript/Token.java b/rhino/src/main/java/org/mozilla/javascript/Token.java index 6513f1bdc4d..9c936d9d3f3 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Token.java +++ b/rhino/src/main/java/org/mozilla/javascript/Token.java @@ -41,8 +41,9 @@ public static enum CommentType { // Interpreter reuses the following as bytecodes FIRST_BYTECODE_TOKEN = EOL + 1, ENTERWITH = FIRST_BYTECODE_TOKEN, - LEAVEWITH = ENTERWITH + 1, - RETURN = LEAVEWITH + 1, + ENTER_SCOPE = ENTERWITH + 1, + LEAVE_SCOPE = ENTER_SCOPE + 1, + RETURN = LEAVE_SCOPE + 1, GOTO = RETURN + 1, IFEQ = GOTO + 1, IFNE = IFEQ + 1, @@ -132,10 +133,25 @@ public static enum CommentType { REF_NS_MEMBER = REF_MEMBER + 1, // Reference for x.ns::y, x..ns::y etc. REF_NAME = REF_NS_MEMBER + 1, // Reference for @y, @[y] etc. REF_NS_NAME = REF_NAME + 1, // Reference for ns::y, @ns::y@[y] etc. - BIGINT = REF_NS_NAME + 1; // ES2020 BigInt + BIGINT = REF_NS_NAME + 1, // ES2020 BigInt + NEW_TARGET = BIGINT + 1, // new.target meta-property + TO_OBJECT_COERCIBLE = NEW_TARGET + 1, // ES6 RequireObjectCoercible + // Async iteration support (for-await-of) + ENUM_INIT_ASYNC_ITERATOR = TO_OBJECT_COERCIBLE + 1, + ENUM_ASYNC_NEXT = ENUM_INIT_ASYNC_ITERATOR + 1, + ENUM_ASYNC_STEP = ENUM_ASYNC_NEXT + 1, + // Push a constant value stored in the shared literals table + LOAD_LITERAL = ENUM_ASYNC_STEP + 1, + // Abrupt-completion IteratorClose: calls iterator.return, swallowing errors. + // Produces undefined on the stack. Used inside the finally branch of destructuring + // (and similar) lowerings so the iterator is closed even on exception / + // generator-return. + ITERATOR_CLOSE_ABRUPT = LOAD_LITERAL + 1, + // Define a new private field: stack has target, key, value -> ... + DEFINE_FIELD = ITERATOR_CLOSE_ABRUPT + 1; // End of interpreter bytecodes - public static final int LAST_BYTECODE_TOKEN = BIGINT, + public static final int LAST_BYTECODE_TOKEN = DEFINE_FIELD, TRY = LAST_BYTECODE_TOKEN + 1, SEMI = TRY + 1, // semicolon LB = SEMI + 1, // left and right brackets @@ -209,7 +225,8 @@ public static enum CommentType { SETPROP_OP = USE_STACK + 1, // x.y op= something SETELEM_OP = SETPROP_OP + 1, // x[y] op= something LOCAL_BLOCK = SETELEM_OP + 1, - SET_REF_OP = LOCAL_BLOCK + 1, // *reference op= something + SCOPE_BLOCK = LOCAL_BLOCK + 1, + SET_REF_OP = SCOPE_BLOCK + 1, // *reference op= something // For XML support: DOTDOT = SET_REF_OP + 1, // member operator (..) @@ -230,7 +247,8 @@ public static enum CommentType { SETCONSTVAR = SETCONST + 1, ARRAYCOMP = SETCONSTVAR + 1, // array comprehension LETEXPR = ARRAYCOMP + 1, - WITHEXPR = LETEXPR + 1, + SCOPEEXPR = LETEXPR + 1, + WITHEXPR = SCOPEEXPR + 1, DEBUGGER = WITHEXPR + 1, COMMENT = DEBUGGER + 1, GENEXPR = COMMENT + 1, @@ -246,7 +264,13 @@ public static enum CommentType { NULLISH_COALESCING = DOTDOTDOT + 1, // nullish coalescing (??) QUESTION_DOT = NULLISH_COALESCING + 1, // optional chaining operator (?.) OBJECT_REST = QUESTION_DOT + 1, // ES6 object rest operation - LAST_TOKEN = OBJECT_REST + 1; + AWAIT = OBJECT_REST + 1, // ES2017 await expression + CLASS = AWAIT + 1, // ES6 class declaration/expression + EXTENDS = CLASS + 1, // ES6 extends clause + PRIVATE_NAME = EXTENDS + 1, // ES2022 private class member name: #foo + GET_CLASS_COMPUTED_KEY = + PRIVATE_NAME + 1, // retrieve a pre-evaluated class computed-field key + LAST_TOKEN = GET_CLASS_COMPUTED_KEY + 1; /** * Returns a name for the token. If Rhino is compiled with certain hardcoded debugging flags in @@ -277,8 +301,10 @@ public static String typeToName(int token) { return "EOL"; case ENTERWITH: return "ENTERWITH"; - case LEAVEWITH: - return "LEAVEWITH"; + case ENTER_SCOPE: + return "ENTER_SCOPE"; + case LEAVE_SCOPE: + return "LEAVE_SCOPE"; case RETURN: return "RETURN"; case GOTO: @@ -383,6 +409,10 @@ public static String typeToName(int token) { return "SHNE"; case REGEXP: return "REGEXP"; + case LOAD_LITERAL: + return "LOAD_LITERAL"; + case DEFINE_FIELD: + return "DEFINE_FIELD"; case BINDNAME: return "BINDNAME"; case THROW: @@ -579,6 +609,8 @@ public static String typeToName(int token) { return "SETELEM_OP"; case LOCAL_BLOCK: return "LOCAL_BLOCK"; + case SCOPE_BLOCK: + return "SCOPE_BLOCK"; case SET_REF_OP: return "SET_REF_OP"; case DOTDOT: @@ -599,6 +631,26 @@ public static String typeToName(int token) { return "TO_DOUBLE"; case OBJECT_REST: return "OBJECT_REST"; + case AWAIT: + return "AWAIT"; + case TO_OBJECT_COERCIBLE: + return "TO_OBJECT_COERCIBLE"; + case ITERATOR_CLOSE_ABRUPT: + return "ITERATOR_CLOSE_ABRUPT"; + case ENUM_INIT_ASYNC_ITERATOR: + return "ENUM_INIT_ASYNC_ITERATOR"; + case ENUM_ASYNC_NEXT: + return "ENUM_ASYNC_NEXT"; + case ENUM_ASYNC_STEP: + return "ENUM_ASYNC_STEP"; + case CLASS: + return "CLASS"; + case EXTENDS: + return "EXTENDS"; + case PRIVATE_NAME: + return "PRIVATE_NAME"; + case GET_CLASS_COMPUTED_KEY: + return "GET_CLASS_COMPUTED_KEY"; case GET: return "GET"; case SET: @@ -621,6 +673,8 @@ public static String typeToName(int token) { return "ARRAYCOMP"; case WITHEXPR: return "WITHEXPR"; + case SCOPEEXPR: + return "SCOPEEXPR"; case LETEXPR: return "LETEXPR"; case DEBUGGER: @@ -637,6 +691,8 @@ public static String typeToName(int token) { return "YIELD_STAR"; case BIGINT: return "BIGINT"; + case NEW_TARGET: + return "NEW_TARGET"; case TEMPLATE_LITERAL: return "TEMPLATE_LITERAL"; case STRING_CONCAT: @@ -734,6 +790,10 @@ public static String keywordToName(int token) { return "throw"; case Token.TRY: return "try"; + case Token.CLASS: + return "class"; + case Token.EXTENDS: + return "extends"; default: return null; } diff --git a/rhino/src/main/java/org/mozilla/javascript/TokenStream.java b/rhino/src/main/java/org/mozilla/javascript/TokenStream.java index b7290479a7b..19032c2c986 100644 --- a/rhino/src/main/java/org/mozilla/javascript/TokenStream.java +++ b/rhino/src/main/java/org/mozilla/javascript/TokenStream.java @@ -65,6 +65,7 @@ String tokenToString(int token) { case Token.STRING: case Token.REGEXP: case Token.NAME: + case Token.PRIVATE_NAME: return name + " `" + this.string + "'"; case Token.NUMBER: @@ -364,7 +365,7 @@ private static int stringToKeywordForES(String name, boolean isStrict) { Id_break = Token.BREAK, Id_case = Token.CASE, Id_catch = Token.CATCH, - Id_class = Token.RESERVED, + Id_class = Token.CLASS, Id_const = Token.CONST, Id_continue = Token.CONTINUE, Id_debugger = Token.DEBUGGER, @@ -373,7 +374,7 @@ private static int stringToKeywordForES(String name, boolean isStrict) { Id_do = Token.DO, Id_else = Token.ELSE, Id_export = Token.RESERVED, - Id_extends = Token.RESERVED, + Id_extends = Token.EXTENDS, Id_finally = Token.FINALLY, Id_for = Token.FOR, Id_function = Token.FUNCTION, @@ -396,7 +397,7 @@ private static int stringToKeywordForES(String name, boolean isStrict) { Id_yield = Token.YIELD, // 11.6.2.2 Future Reserved Words - Id_await = Token.RESERVED, + Id_await = Token.NAME, Id_enum = Token.RESERVED, // 11.6.2.2 NOTE Strict Future Reserved Words @@ -1156,6 +1157,69 @@ && peekChar() == '!' return Token.COMMENT; } + if (c == '#') { + int peek = peekChar(); + boolean startIsEscape = false; + if (peek == '\\') { + getChar(); // consume '\\' + if (peekChar() == 'u') { + getChar(); // consume 'u' + startIsEscape = true; + } else { + ungetChar('\\'); + } + } + if (startIsEscape + || Character.isUnicodeIdentifierStart(peek) + || peek == '$' + || peek == '_') { + stringBufferTop = 0; + addToString('#'); + boolean inEscape = startIsEscape; + boolean containsEscape = startIsEscape; + for (; ; ) { + if (inEscape) { + int escapeVal = readIdentifierUnicodeEscape(); + if (escapeVal < 0) { + return Token.ERROR; + } + addToString(escapeVal); + inEscape = false; + } else { + int pc = peekChar(); + if (pc == '\\') { + getChar(); // consume '\\' + if (peekChar() == 'u') { + getChar(); // consume 'u' + inEscape = true; + containsEscape = true; + } else { + parser.addError("msg.illegal.character", '\\'); + return Token.ERROR; + } + } else if (pc == EOF_CHAR + || pc == BYTE_ORDER_MARK + || !(Character.isUnicodeIdentifierPart(pc) || pc == '$')) { + break; + } else { + addToString(getChar()); + } + } + } + String str = getStringFromBuffer(); + if (containsEscape && !isValidIdentifierName(str.substring(1))) { + parser.reportError("msg.invalid.escape"); + return Token.ERROR; + } + this.string = internString(str); + cursor = sourceCursor; + tokenEnd = cursor; + return Token.PRIVATE_NAME; + } + parser.addError("msg.illegal.character", c); + return Token.ERROR; + } + switch (c) { case ';': return Token.SEMI; @@ -2163,6 +2227,40 @@ private void ungetChar(int c) { cursor--; } + // Read a unicode escape with the leading \\u already consumed. Supports both + // \\uXXXX and \\u{...} forms. Returns the code point (0 to 0x10FFFF) or -1 + // on error, in which case the error has already been reported. + private int readIdentifierUnicodeEscape() throws IOException { + int escapeVal = 0; + if (matchChar('{')) { + for (; ; ) { + int c = getChar(); + if (c == '}') { + break; + } + escapeVal = Kit.xDigitToInt(c, escapeVal); + if (escapeVal < 0) { + parser.reportError("msg.invalid.escape"); + return -1; + } + } + if (escapeVal > 0x10FFFF) { + parser.reportError("msg.invalid.escape"); + return -1; + } + return escapeVal; + } + for (int i = 0; i != 4; ++i) { + int c = getChar(); + escapeVal = Kit.xDigitToInt(c, escapeVal); + if (escapeVal < 0) { + parser.reportError("msg.invalid.escape"); + return -1; + } + } + return escapeVal; + } + private boolean matchChar(int test) throws IOException { int c = getCharIgnoreLineEnd(); if (c == test) { diff --git a/rhino/src/main/java/org/mozilla/javascript/TopLevel.java b/rhino/src/main/java/org/mozilla/javascript/TopLevel.java index 7b494281aeb..4d8fe32ce41 100644 --- a/rhino/src/main/java/org/mozilla/javascript/TopLevel.java +++ b/rhino/src/main/java/org/mozilla/javascript/TopLevel.java @@ -6,6 +6,8 @@ package org.mozilla.javascript; +import static org.mozilla.javascript.UniqueTag.NOT_FOUND; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -58,11 +60,20 @@ public enum Builtins { Symbol, /** The built-in GeneratorFunction type. */ GeneratorFunction, + /** The built-in AsyncFunction type. */ + AsyncFunction, + /** The built-in AsyncGeneratorFunction type. */ + AsyncGeneratorFunction, /** The built-in BigInt type. */ BigInt, /** The built-in Promise type. */ Promise, Date, + Map, + Set, + WeakMap, + WeakSet, + Proxy, ArrayBuffer, Int8Array, Uint8Array, @@ -75,7 +86,8 @@ public enum Builtins { BigUint64Array, Float32Array, Float64Array, - DataView + DataView, + Iterator } /** An enumeration of built-in native errors. [ECMAScript 5 - 15.11.6] */ @@ -170,11 +182,6 @@ public static TopLevel createIsolateCustomPrototypeChain( return isolate; } - @Override - public String getClassName() { - return "topLevel"; - } - /** * Cache the built-in ECMAScript objects to protect them against modifications by the script. * This method is called automatically by {@link ScriptRuntime#initStandardObjects @@ -200,7 +207,22 @@ public void cacheBuiltins(boolean sealed) { // Handle weird situation of "GeneratorFunction" being a real constructor // which is never registered in the top-level scope ctors.put( - builtin, (BaseFunction) BaseFunction.initAsGeneratorFunction(this, sealed)); + builtin, + (BaseFunction) + BaseFunction.initAsGeneratorFunction( + Context.getCurrentContext(), this, sealed)); + } else if (builtin == Builtins.AsyncFunction) { + ctors.put( + builtin, + (BaseFunction) + BaseFunction.initAsAsyncFunction( + Context.getCurrentContext(), this, sealed)); + } else if (builtin == Builtins.AsyncGeneratorFunction) { + ctors.put( + builtin, + (BaseFunction) + BaseFunction.initAsAsyncGeneratorFunction( + Context.getCurrentContext(), this, sealed)); } } errors = new EnumMap<>(NativeErrors.class); @@ -243,16 +265,24 @@ public static Function getBuiltinCtor(Context cx, VarScope scope, Builtins type) } } // fall back to normal constructor lookup - String typeName; + Object typeName; if (type == Builtins.GeneratorFunction) { // GeneratorFunction isn't stored in scope with that name, but in case // we end up falling back to this value then we have to // look this up using a hidden name. typeName = BaseFunction.GENERATOR_FUNCTION_CLASS; + } else if (type == Builtins.AsyncFunction) { + typeName = BaseFunction.ASYNC_FUNCTION_CLASS; + } else if (type == Builtins.AsyncGeneratorFunction) { + typeName = BaseFunction.ASYNC_GENERATOR_FUNCTION_CLASS; } else { typeName = type.name(); } - return ScriptRuntime.getExistingCtor(cx, scope, typeName); + if (typeName instanceof String) { + return ScriptRuntime.getExistingCtor(cx, scope, (String) typeName); + } else { + return ScriptRuntime.getExistingCtor(cx, scope, (SymbolKey) typeName); + } } /** @@ -294,17 +324,24 @@ public static Scriptable getBuiltinPrototype(TopLevel scope, Builtins type) { return result; } - // fall back to normal prototype lookup - String typeName; + Object typeName; if (type == Builtins.GeneratorFunction) { // GeneratorFunction isn't stored in scope with that name, but in case // we end up falling back to this value then we have to // look this up using a hidden name. typeName = BaseFunction.GENERATOR_FUNCTION_CLASS; + } else if (type == Builtins.AsyncFunction) { + typeName = BaseFunction.ASYNC_FUNCTION_CLASS; + } else if (type == Builtins.AsyncGeneratorFunction) { + typeName = BaseFunction.ASYNC_GENERATOR_FUNCTION_CLASS; } else { typeName = type.name(); } - return ScriptableObject.getClassPrototype(scope, typeName); + if (typeName instanceof String) { + return ScriptableObject.getClassPrototype(scope, (String) typeName); + } else { + return ScriptableObject.getClassPrototype(scope, (SymbolKey) typeName); + } } /** @@ -347,7 +384,7 @@ public ScriptableObject getGlobalThis() { } @Override - public Object get(String name, Scriptable start) { + public Object get(String name, VarScope start) { var res = super.get(name, start); if (res != NOT_FOUND) { return res; @@ -356,12 +393,12 @@ public Object get(String name, Scriptable start) { } @Override - public void put(String name, Scriptable start, Object value) { + public void put(String name, VarScope start, Object value) { ScriptableObject.putProperty(globalThis, name, value); } @Override - public boolean has(String name, Scriptable start) { + public boolean has(String name, VarScope start) { return super.has(name, start) || ScriptableObject.hasProperty(globalThis, name); } @@ -382,7 +419,8 @@ public void defineProperty(String propertyName, Object value, int attributes) { } @Override - void addLazilyInitializedValue(String name, int index, LazilyLoadedCtor init, int attributes) { + void addLazilyInitializedValue( + String name, int index, LazilyLoadedCtor init, int attributes) { globalThis.addLazilyInitializedValue(name, index, init, attributes); } @@ -420,12 +458,12 @@ public boolean isConst(String name) { } @Override - public void putConst(String name, Scriptable start, Object value) { + public void putConst(String name, VarScope start, Object value) { globalThis.putConst(name, globalThis, value); } @Override - public void defineConst(String name, Scriptable start) { + public void defineConst(String name, VarScope start) { globalThis.defineConst(name, globalThis); } diff --git a/rhino/src/main/java/org/mozilla/javascript/VarScope.java b/rhino/src/main/java/org/mozilla/javascript/VarScope.java index eb5ce0d4723..172fc090a90 100644 --- a/rhino/src/main/java/org/mozilla/javascript/VarScope.java +++ b/rhino/src/main/java/org/mozilla/javascript/VarScope.java @@ -12,38 +12,20 @@ * Interface that represents an the bindings of an ECMAScript environment record. Unlike {@link * Scriptable} scopes do not have prototypes. */ -public interface VarScope extends Scriptable, ConstProperties { +public interface VarScope extends PropHolder, ConstProperties { + /** + * Get the parent scope of this scope. + * + * @return the parent scope + */ + public VarScope getParentScope(); @Override - default String getClassName() { - Kit.codeBug("Attempt to get classname of scope."); - return null; + default VarScope getAncestor() { + return getParentScope(); } - @Override - default Object getDefaultValue(Class hint) { - Kit.codeBug("Attempt to get default value of scope."); - return null; - } - - @Override - default boolean hasInstance(Scriptable instance) { - Kit.codeBug("Attempt to do hasInstance of scope."); + default boolean isNestedScope() { return false; } - - @Override - default void setParentScope(VarScope parent) { - Kit.codeBug("Attempt to change parent of scope."); - } - - @Override - default void setPrototype(Scriptable prototype) { - Kit.codeBug("Attempt to set prototype of scope."); - } - - @Override - default Scriptable getPrototype() { - return null; - } } diff --git a/rhino/src/main/java/org/mozilla/javascript/WithScope.java b/rhino/src/main/java/org/mozilla/javascript/WithScope.java index 55d2cb187ff..4a5418af77a 100644 --- a/rhino/src/main/java/org/mozilla/javascript/WithScope.java +++ b/rhino/src/main/java/org/mozilla/javascript/WithScope.java @@ -1,8 +1,8 @@ package org.mozilla.javascript; -public class WithScope implements VarScope { - private static final long serialVersionUID = -7471457301304454454L; +import static org.mozilla.javascript.Scriptable.NOT_FOUND; +public class WithScope implements VarScope { // This cannot be final because XML dot queries mutate it! private Scriptable obj; private final VarScope parent; @@ -28,33 +28,33 @@ public void delete(int index) {} public void delete(Symbol arg0) {} @Override - public Object get(int index, Scriptable start) { + public Object get(int index, VarScope start) { return NOT_FOUND; } @Override - public Object get(Symbol arg0, Scriptable arg1) { + public Object get(Symbol arg0, VarScope arg1) { return NOT_FOUND; } @Override - public boolean has(int index, Scriptable start) { + public boolean has(int index, VarScope start) { return false; } @Override - public boolean has(Symbol arg0, Scriptable arg1) { + public boolean has(Symbol arg0, VarScope arg1) { return false; } @Override - public void put(int index, Scriptable start, Object value) {} + public void put(int index, VarScope start, Object value) {} @Override - public void put(Symbol arg0, Scriptable arg1, Object arg2) {} + public void put(Symbol arg0, VarScope arg1, Object arg2) {} @Override - public void defineConst(String name, Scriptable start) { + public void defineConst(String name, VarScope start) { Kit.codeBug("Attempt to define a const on a `with` scope."); } @@ -64,22 +64,22 @@ public boolean isConst(String name) { } @Override - public void putConst(String name, Scriptable start, Object value) { + public void putConst(String name, VarScope start, Object value) { Kit.codeBug("Attempt to define a const on a `with` scope."); } @Override - public Object get(String name, Scriptable start) { + public Object get(String name, VarScope start) { return ScriptableObject.getProperty(obj, name); } @Override - public void put(String name, Scriptable start, Object value) { + public void put(String name, VarScope start, Object value) { ScriptableObject.putProperty(obj, name, value); } @Override - public boolean has(String name, Scriptable start) { + public boolean has(String name, VarScope start) { return ScriptableObject.hasProperty(obj, name); } @@ -99,7 +99,11 @@ public Scriptable getObject() { /** Must return null to continue looping or the final collection result. */ protected Object updateDotQuery(boolean value) { - // NativeWith itself does not support it + // WithScope itself does not support it throw new IllegalStateException(); } + + public boolean isNestedScope() { + return true; + } } diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ArrayComprehension.java b/rhino/src/main/java/org/mozilla/javascript/ast/ArrayComprehension.java index c7ac2d92022..89f6cdf3284 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ArrayComprehension.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ArrayComprehension.java @@ -7,7 +7,9 @@ package org.mozilla.javascript.ast; import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.List; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** AST node for a JavaScript 1.7 Array comprehension. Node type is {@link Token#ARRAYCOMP}. */ @@ -137,6 +139,45 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != ArrayComprehension.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ArrayComprehension copy = new ArrayComprehension(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.copyScopeFieldsFrom(this); + copy.result = this.result; + copy.loops = this.loops; + copy.filter = this.filter; + copy.ifPosition = this.ifPosition; + copy.lp = this.lp; + copy.rp = this.rp; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + ArrayComprehension copy = (ArrayComprehension) copyNode; + if (this.result != null) { + copy.result = (AstNode) this.result.cloneStructure(map); + } + if (this.loops != null) { + List list = new ArrayList<>(this.loops.size()); + for (ArrayComprehensionLoop l : this.loops) { + list.add((ArrayComprehensionLoop) l.cloneStructure(map)); + } + copy.loops = list; + } + if (this.filter != null) { + copy.filter = (AstNode) this.filter.cloneStructure(map); + } + } + /** Visits this node, the result expression, the loops, and the optional filter. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ArrayComprehensionLoop.java b/rhino/src/main/java/org/mozilla/javascript/ast/ArrayComprehensionLoop.java index 13c69242a55..2d33625cc22 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ArrayComprehensionLoop.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ArrayComprehensionLoop.java @@ -6,6 +6,7 @@ package org.mozilla.javascript.ast; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -58,6 +59,22 @@ public String toSource(int depth) { + ")"; } + @Override + protected Node shallowCopy() { + if (getClass() != ArrayComprehensionLoop.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ArrayComprehensionLoop copy = new ArrayComprehensionLoop(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.copyScopeFieldsFrom(this); + copy.copyLoopFieldsFrom(this); + copy.copyForInLoopFieldsFrom(this); + return copy; + } + /** * Visits the iterator expression and the iterated object expression. There is no * body-expression for this loop type. diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ArrayLiteral.java b/rhino/src/main/java/org/mozilla/javascript/ast/ArrayLiteral.java index dc2fad9d381..c615bb04b50 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ArrayLiteral.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ArrayLiteral.java @@ -8,7 +8,9 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.IdentityHashMap; import java.util.List; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -169,6 +171,35 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != ArrayLiteral.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ArrayLiteral copy = new ArrayLiteral(); + copy.type = this.type; + copyAstFields(this, copy); + copy.elements = this.elements; + copy.destructuringLength = this.destructuringLength; + copy.skipCount = this.skipCount; + copy.isDestructuring = this.isDestructuring; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + ArrayLiteral copy = (ArrayLiteral) copyNode; + if (this.elements != null) { + List list = new ArrayList<>(this.elements.size()); + for (AstNode e : this.elements) { + list.add((AstNode) e.cloneStructure(map)); + } + copy.elements = list; + } + } + /** * Visits this node, then visits its element expressions in order. Any empty elements are * represented by {@link EmptyExpression} objects, so the callback will never be passed {@code diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/Assignment.java b/rhino/src/main/java/org/mozilla/javascript/ast/Assignment.java index b83dadb14a2..8b0b5595589 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/Assignment.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/Assignment.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import org.mozilla.javascript.Node; + /** * AST node representing the set of assignment operators such as {@code =}, {@code *=} and {@code * +=}. @@ -33,4 +35,17 @@ public Assignment(AstNode left, AstNode right) { public Assignment(int operator, AstNode left, AstNode right, int operatorPos) { super(operator, left, right, operatorPos); } + + @Override + protected Node shallowCopy() { + if (getClass() != Assignment.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + Assignment copy = new Assignment(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyInfixFieldsFrom(this); + return copy; + } } diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java b/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java index f7ebae8cb35..e28d9eee80e 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java @@ -138,7 +138,7 @@ public abstract class AstNode extends Node implements Comparable { } public static class PositionComparator implements Comparator, Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1551233591343412911L; /** * Sorts nodes by (relative) start position. The start positions are relative to their @@ -176,6 +176,19 @@ public AstNode(int pos, int len) { length = len; } + /** + * Copies the {@link AstNode}-level scalar fields ({@code position}, {@code length}, and {@code + * inlineComment}) from {@code src} to {@code dst} in addition to the {@link Node} base fields. + * Subclass {@link Node#shallowCopy()} implementations call this to populate the + * AstNode-specific state on a freshly-allocated copy. + */ + protected static void copyAstFields(AstNode src, AstNode dst) { + copyBaseFields(src, dst); + dst.position = src.position; + dst.length = src.length; + dst.inlineComment = src.inlineComment; + } + /** Returns relative position in parent */ public int getPosition() { return position; @@ -378,6 +391,7 @@ public boolean hasSideEffects() { case Token.CALL: case Token.CATCH: case Token.CATCH_SCOPE: + case Token.CLASS: case Token.CONST: case Token.CONTINUE: case Token.DEC: @@ -400,7 +414,7 @@ public boolean hasSideEffects() { case Token.INC: case Token.JSR: case Token.LABEL: - case Token.LEAVEWITH: + case Token.LEAVE_SCOPE: case Token.LET: case Token.LETEXPR: case Token.LOCAL_BLOCK: diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/AstRoot.java b/rhino/src/main/java/org/mozilla/javascript/ast/AstRoot.java index 379d5aee4b5..6d88bf2614d 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/AstRoot.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/AstRoot.java @@ -120,6 +120,24 @@ public String debugPrint() { return dpv.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != AstRoot.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + AstRoot copy = new AstRoot(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.copyScopeFieldsFrom(this); + copy.copyScriptNodeFieldsFrom(this); + if (this.comments != null) { + copy.comments = new TreeSet<>(this.comments); + } + return copy; + } + /** * Debugging function to check that the parser has set the parent link for every node in the * tree. diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/AwaitExpression.java b/rhino/src/main/java/org/mozilla/javascript/ast/AwaitExpression.java new file mode 100644 index 00000000000..7c8ffeb3da4 --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/ast/AwaitExpression.java @@ -0,0 +1,92 @@ +/* -*- 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.ast; + +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; +import org.mozilla.javascript.Token; + +/** + * AST node for an ES2017 {@code await} expression. Node type is {@link Token#AWAIT}. + * + *

AwaitExpression :
+ *   await UnaryExpression
+ */ +public class AwaitExpression extends AstNode { + + private AstNode value; + + public AwaitExpression() { + type = Token.AWAIT; + } + + public AwaitExpression(int pos) { + super(pos); + type = Token.AWAIT; + } + + public AwaitExpression(int pos, int len) { + super(pos, len); + type = Token.AWAIT; + } + + public AwaitExpression(int pos, int len, AstNode value) { + super(pos, len); + type = Token.AWAIT; + setValue(value); + } + + /** Returns the awaited expression. */ + public AstNode getValue() { + return value; + } + + /** + * Sets the awaited expression, and sets its parent to this node. + * + * @param expr the expression to await + */ + public void setValue(AstNode expr) { + this.value = expr; + if (expr != null) expr.setParent(this); + } + + @Override + public String toSource(int depth) { + return value == null ? "await" : "await " + value.toSource(0); + } + + @Override + protected Node shallowCopy() { + if (getClass() != AwaitExpression.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + AwaitExpression copy = new AwaitExpression(); + copy.type = this.type; + copyAstFields(this, copy); + copy.value = this.value; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + AwaitExpression copy = (AwaitExpression) copyNode; + if (this.value != null) { + copy.value = (AstNode) this.value.cloneStructure(map); + } + } + + /** Visits this node, and if present, the awaited value. */ + @Override + public void visit(NodeVisitor v) { + if (v.visit(this) && value != null) { + value.visit(v); + } + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/BigIntLiteral.java b/rhino/src/main/java/org/mozilla/javascript/ast/BigIntLiteral.java index 86751f13e4f..554b11fc436 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/BigIntLiteral.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/BigIntLiteral.java @@ -7,6 +7,7 @@ package org.mozilla.javascript.ast; import java.math.BigInteger; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** AST node for a BigInt literal. Node type is {@link Token#BIGINT}. */ @@ -74,6 +75,20 @@ public String toSource(int depth) { return makeIndent(depth) + (bigInt == null ? "" : bigInt.toString() + "n"); } + @Override + protected Node shallowCopy() { + if (getClass() != BigIntLiteral.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + BigIntLiteral copy = new BigIntLiteral(); + copy.type = this.type; + copyAstFields(this, copy); + copy.value = this.value; + copy.bigInt = this.bigInt; + return copy; + } + /** Visits this node. There are no children to visit. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/Block.java b/rhino/src/main/java/org/mozilla/javascript/ast/Block.java index 45a3c62b820..346ac9aa47c 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/Block.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/Block.java @@ -58,6 +58,18 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != Block.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + Block copy = new Block(); + copy.type = this.type; + copyAstFields(this, copy); + return copy; + } + @Override public void visit(NodeVisitor v) { if (v.visit(this)) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/BreakStatement.java b/rhino/src/main/java/org/mozilla/javascript/ast/BreakStatement.java index ad4a9589ab7..f36be4c4a9b 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/BreakStatement.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/BreakStatement.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -91,6 +93,44 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != BreakStatement.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + BreakStatement copy = new BreakStatement(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.breakLabel = this.breakLabel; + copy.targetNode = this.targetNode; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + BreakStatement copy = (BreakStatement) copyNode; + if (this.breakLabel != null) { + copy.breakLabel = (Name) this.breakLabel.cloneStructure(map); + } + } + + @Override + protected void fixupReferences(IdentityHashMap map) { + if (breakLabel != null) { + breakLabel.fixupReferences(map); + } + if (targetNode != null) { + Node mapped = map.get(targetNode); + if (mapped instanceof AstNode) { + targetNode = (AstNode) mapped; + } + } + super.fixupReferences(map); + } + /** Visits this node, then visits the break label if non-{@code null}. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/CatchClause.java b/rhino/src/main/java/org/mozilla/javascript/ast/CatchClause.java index e7983efd2f8..40101a0f915 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/CatchClause.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/CatchClause.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -159,6 +161,39 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != CatchClause.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + CatchClause copy = new CatchClause(); + copy.type = this.type; + copyAstFields(this, copy); + copy.varName = this.varName; + copy.catchCondition = this.catchCondition; + copy.body = this.body; + copy.ifPosition = this.ifPosition; + copy.lp = this.lp; + copy.rp = this.rp; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + CatchClause copy = (CatchClause) copyNode; + if (this.varName != null) { + copy.varName = (AstNode) this.varName.cloneStructure(map); + } + if (this.catchCondition != null) { + copy.catchCondition = (AstNode) this.catchCondition.cloneStructure(map); + } + if (this.body != null) { + copy.body = (Scope) this.body.cloneStructure(map); + } + } + /** * Visits this node, the catch var name node, the condition if non-{@code null}, and the catch * body. diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ClassComputedKeyRef.java b/rhino/src/main/java/org/mozilla/javascript/ast/ClassComputedKeyRef.java new file mode 100644 index 00000000000..252ae5370d8 --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ClassComputedKeyRef.java @@ -0,0 +1,41 @@ +/* -*- 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.ast; + +import org.mozilla.javascript.Node; +import org.mozilla.javascript.Token; + +/** + * Synthetic AST node emitted by {@code IRFactory} while injecting field initializers into a class + * constructor. It stands in for a computed field key that has already been evaluated at class + * declaration time; its type is {@link Token#GET_CLASS_COMPUTED_KEY} and the index property + * identifies which stored key to load. + */ +public class ClassComputedKeyRef extends AstNode { + + { + type = Token.GET_CLASS_COMPUTED_KEY; + } + + public ClassComputedKeyRef(int index) { + putIntProp(Node.LITERAL_INDEX_PROP, index); + } + + public int getIndex() { + return getExistingIntProp(Node.LITERAL_INDEX_PROP); + } + + @Override + public String toSource(int depth) { + return ""; + } + + @Override + public void visit(NodeVisitor v) { + v.visit(this); + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ClassNode.java b/rhino/src/main/java/org/mozilla/javascript/ast/ClassNode.java new file mode 100644 index 00000000000..424a3c35b6c --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ClassNode.java @@ -0,0 +1,663 @@ +/* -*- 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.ast; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.mozilla.javascript.Symbol; +import org.mozilla.javascript.SymbolKey; +import org.mozilla.javascript.Token; + +/** + * AST node for ES6 class declarations and expressions. + * + *
+ * ClassDeclaration :
+ *        class BindingIdentifier ClassTail
+ * ClassExpression :
+ *        class BindingIdentifieropt ClassTail
+ * ClassTail :
+ *        ClassHeritageopt { ClassBody }
+ * 
+ */ +public class ClassNode extends AstNode { + + /** Kind of a class member that is a function: normal method, getter, or setter. */ + public enum ElementKind { + METHOD, + GETTER, + SETTER + } + + public static class ClassField { + public final String name; + public final AstNode computedName; + public final AstNode initializer; + public final ElementKind kind; + + private ClassField( + String name, AstNode computedName, AstNode initializer, ElementKind kind) { + this.name = name; + this.computedName = computedName; + this.initializer = initializer; + this.kind = kind; + } + + public static ClassField makeFIeld(String name, AstNode initializer) { + return new ClassField(name, null, initializer, null); + } + } + + private List instanceFields = new ArrayList<>(); + // private List instanceMethods; + // private List privateInstanceFields; + // private List privateInstanceMethods; + // private List staticFields; + // private List staticMethods; + // private List privateStaticFields; + // private List privateStaticMethods; + + private Name className; + private AstNode superClass; + private FunctionNode constructor; + private boolean isStatement; + private List methodNames; + private List methods; + private List methodKinds; + private List methodComputedKeys; + private List staticMethodNames; + private List staticMethods; + private List staticMethodKinds; + private List staticMethodComputedKeys; + private List computedFieldKeys; + private List computedFieldInitializers; + private List staticFieldNames; + private List staticFieldInitializers; + private List staticComputedFieldKeys; + private List staticComputedFieldInitializers; + private List privateFieldNames; + private List privateFieldInitializers; + private List privateFieldKinds; + private List staticPrivateFieldNames; + private List staticPrivateFieldInitializers; + private List staticPrivateFieldKinds; + + /** Shared SymbolKey for each private name declared in this class. */ + private Map privateSymbols; + + { + type = Token.CLASS; + } + + public ClassNode() {} + + public ClassNode(int pos) { + super(pos); + } + + public ClassNode(int pos, int len) { + super(pos, len); + } + + public Name getClassName() { + return className; + } + + public void setClassName(Name className) { + this.className = className; + if (className != null) { + className.setParent(this); + } + } + + public AstNode getSuperClass() { + return superClass; + } + + public void setSuperClass(AstNode superClass) { + this.superClass = superClass; + if (superClass != null) { + superClass.setParent(this); + } + } + + public FunctionNode getConstructor() { + return constructor; + } + + public void setConstructor(FunctionNode constructor) { + this.constructor = constructor; + if (constructor != null) { + constructor.setParent(this); + } + } + + public boolean isStatement() { + return isStatement; + } + + public void setIsStatement(boolean isStatement) { + this.isStatement = isStatement; + } + + public void addMethod(String name, FunctionNode fn) { + addMethod(name, fn, ElementKind.METHOD); + } + + public void addMethod(String name, FunctionNode fn, ElementKind kind) { + addMethodInternal(name, null, fn, kind); + } + + /** + * Add an instance method whose property key is computed at runtime (e.g. {@code [expr]()}). The + * {@code name} is null; {@code keyExpr} holds the expression to evaluate. + */ + public void addComputedMethod(AstNode keyExpr, FunctionNode fn, ElementKind kind) { + addMethodInternal(null, keyExpr, fn, kind); + } + + private void addMethodInternal( + String name, AstNode keyExpr, FunctionNode fn, ElementKind kind) { + if (methodNames == null) { + methodNames = new ArrayList<>(); + methods = new ArrayList<>(); + methodKinds = new ArrayList<>(); + methodComputedKeys = new ArrayList<>(); + } + methodNames.add(name); + methods.add(fn); + methodKinds.add(kind); + methodComputedKeys.add(keyExpr); + fn.setParent(this); + if (keyExpr != null) { + keyExpr.setParent(this); + } + } + + public int getMethodCount() { + return methodNames == null ? 0 : methodNames.size(); + } + + public List getMethodNames() { + return methodNames == null ? Collections.emptyList() : methodNames; + } + + public List getMethods() { + return methods == null ? Collections.emptyList() : methods; + } + + public List getMethodKinds() { + return methodKinds == null ? Collections.emptyList() : methodKinds; + } + + public List getMethodComputedKeys() { + return methodComputedKeys == null ? Collections.emptyList() : methodComputedKeys; + } + + public void addStaticMethod(String name, FunctionNode fn) { + addStaticMethod(name, fn, ElementKind.METHOD); + } + + public void addStaticMethod(String name, FunctionNode fn, ElementKind kind) { + addStaticMethodInternal(name, null, fn, kind); + } + + /** + * Add a static method whose property key is computed at runtime (e.g. {@code static [expr]()}). + * The {@code name} is null; {@code keyExpr} holds the expression to evaluate. + */ + public void addStaticComputedMethod(AstNode keyExpr, FunctionNode fn, ElementKind kind) { + addStaticMethodInternal(null, keyExpr, fn, kind); + } + + private void addStaticMethodInternal( + String name, AstNode keyExpr, FunctionNode fn, ElementKind kind) { + if (staticMethodNames == null) { + staticMethodNames = new ArrayList<>(); + staticMethods = new ArrayList<>(); + staticMethodKinds = new ArrayList<>(); + staticMethodComputedKeys = new ArrayList<>(); + } + staticMethodNames.add(name); + staticMethods.add(fn); + staticMethodKinds.add(kind); + staticMethodComputedKeys.add(keyExpr); + fn.setParent(this); + if (keyExpr != null) { + keyExpr.setParent(this); + } + } + + public int getStaticMethodCount() { + return staticMethodNames == null ? 0 : staticMethodNames.size(); + } + + public List getStaticMethodNames() { + return staticMethodNames == null ? Collections.emptyList() : staticMethodNames; + } + + public List getStaticMethods() { + return staticMethods == null ? Collections.emptyList() : staticMethods; + } + + public List getStaticMethodKinds() { + return staticMethodKinds == null ? Collections.emptyList() : staticMethodKinds; + } + + public List getStaticMethodComputedKeys() { + return staticMethodComputedKeys == null + ? Collections.emptyList() + : staticMethodComputedKeys; + } + + public void addField(String name, AstNode initializer) { + var elem = ClassField.makeFIeld(name, initializer); + if (initializer != null) { + initializer.setParent(this); + } + instanceFields.add(elem); + } + + public int getFieldCount() { + return instanceFields.size(); + } + + public List getInstanceFields() { + return List.copyOf(instanceFields); + } + + public List getFieldNames() { + return instanceFields.stream().map(e -> e.name).collect(Collectors.toList()); + } + + public List getFieldInitializers() { + return instanceFields.stream().map(e -> e.initializer).collect(Collectors.toList()); + } + + public void addComputedField(AstNode keyExpr, AstNode initializer) { + if (computedFieldKeys == null) { + computedFieldKeys = new ArrayList<>(); + computedFieldInitializers = new ArrayList<>(); + } + computedFieldKeys.add(keyExpr); + computedFieldInitializers.add(initializer); + keyExpr.setParent(this); + if (initializer != null) { + initializer.setParent(this); + } + } + + public int getComputedFieldCount() { + return computedFieldKeys == null ? 0 : computedFieldKeys.size(); + } + + public List getComputedFieldKeys() { + return computedFieldKeys == null ? Collections.emptyList() : computedFieldKeys; + } + + public List getComputedFieldInitializers() { + return computedFieldInitializers == null + ? Collections.emptyList() + : computedFieldInitializers; + } + + public void addStaticField(String name, AstNode initializer) { + if (staticFieldNames == null) { + staticFieldNames = new ArrayList<>(); + staticFieldInitializers = new ArrayList<>(); + } + staticFieldNames.add(name); + staticFieldInitializers.add(initializer); + if (initializer != null) { + initializer.setParent(this); + } + } + + public int getStaticFieldCount() { + return staticFieldNames == null ? 0 : staticFieldNames.size(); + } + + public List getStaticFieldNames() { + return staticFieldNames == null ? Collections.emptyList() : staticFieldNames; + } + + public List getStaticFieldInitializers() { + return staticFieldInitializers == null ? Collections.emptyList() : staticFieldInitializers; + } + + public void addStaticComputedField(AstNode keyExpr, AstNode initializer) { + if (staticComputedFieldKeys == null) { + staticComputedFieldKeys = new ArrayList<>(); + staticComputedFieldInitializers = new ArrayList<>(); + } + staticComputedFieldKeys.add(keyExpr); + staticComputedFieldInitializers.add(initializer); + keyExpr.setParent(this); + if (initializer != null) { + initializer.setParent(this); + } + } + + public int getStaticComputedFieldCount() { + return staticComputedFieldKeys == null ? 0 : staticComputedFieldKeys.size(); + } + + public List getStaticComputedFieldKeys() { + return staticComputedFieldKeys == null ? Collections.emptyList() : staticComputedFieldKeys; + } + + public List getStaticComputedFieldInitializers() { + return staticComputedFieldInitializers == null + ? Collections.emptyList() + : staticComputedFieldInitializers; + } + + public void addPrivateField(String name, AstNode initializer) { + addPrivateMember(name, initializer, ElementKind.METHOD); + } + + public void addPrivateMember(String name, AstNode initializer, ElementKind kind) { + if (privateFieldNames == null) { + privateFieldNames = new ArrayList<>(); + privateFieldInitializers = new ArrayList<>(); + privateFieldKinds = new ArrayList<>(); + } + privateFieldNames.add(name); + privateFieldInitializers.add(initializer); + privateFieldKinds.add(kind); + if (initializer != null) { + initializer.setParent(this); + } + getOrCreatePrivateSymbol(name); + } + + public int getPrivateFieldCount() { + return privateFieldNames == null ? 0 : privateFieldNames.size(); + } + + public List getPrivateFieldNames() { + return privateFieldNames == null ? Collections.emptyList() : privateFieldNames; + } + + public List getPrivateFieldInitializers() { + return privateFieldInitializers == null + ? Collections.emptyList() + : privateFieldInitializers; + } + + public List getPrivateFieldKinds() { + return privateFieldKinds == null ? Collections.emptyList() : privateFieldKinds; + } + + public void addStaticPrivateField(String name, AstNode initializer) { + addStaticPrivateMember(name, initializer, ElementKind.METHOD); + } + + public void addStaticPrivateMember(String name, AstNode initializer, ElementKind kind) { + if (staticPrivateFieldNames == null) { + staticPrivateFieldNames = new ArrayList<>(); + staticPrivateFieldInitializers = new ArrayList<>(); + staticPrivateFieldKinds = new ArrayList<>(); + } + staticPrivateFieldNames.add(name); + staticPrivateFieldInitializers.add(initializer); + staticPrivateFieldKinds.add(kind); + if (initializer != null) { + initializer.setParent(this); + } + getOrCreatePrivateSymbol(name); + } + + public int getStaticPrivateFieldCount() { + return staticPrivateFieldNames == null ? 0 : staticPrivateFieldNames.size(); + } + + public List getStaticPrivateFieldNames() { + return staticPrivateFieldNames == null ? Collections.emptyList() : staticPrivateFieldNames; + } + + public List getStaticPrivateFieldInitializers() { + return staticPrivateFieldInitializers == null + ? Collections.emptyList() + : staticPrivateFieldInitializers; + } + + public List getStaticPrivateFieldKinds() { + return staticPrivateFieldKinds == null ? Collections.emptyList() : staticPrivateFieldKinds; + } + + /** + * Check whether adding a private member of the given kind with the given name would be a + * duplicate. Private getters and setters may share a name with each other within the same + * static/instance bucket, but any other combination (including instance vs static overlap) is a + * duplicate. + */ + public boolean isDuplicatePrivateMember(String name, ElementKind kind, boolean isStatic) { + // Any use of this name in the opposite bucket (instance vs static) is a duplicate. + List otherNames = isStatic ? privateFieldNames : staticPrivateFieldNames; + if (otherNames != null && otherNames.contains(name)) { + return true; + } + List names = isStatic ? staticPrivateFieldNames : privateFieldNames; + List kinds = isStatic ? staticPrivateFieldKinds : privateFieldKinds; + if (names == null) { + return false; + } + boolean sawGetter = false; + boolean sawSetter = false; + for (int i = 0; i < names.size(); i++) { + if (!name.equals(names.get(i))) { + continue; + } + ElementKind existing = kinds.get(i); + if (existing == ElementKind.GETTER) { + sawGetter = true; + } else if (existing == ElementKind.SETTER) { + sawSetter = true; + } else { + return true; + } + } + if (!sawGetter && !sawSetter) { + return false; + } + if (kind == ElementKind.GETTER) { + return sawGetter; + } + if (kind == ElementKind.SETTER) { + return sawSetter; + } + return true; + } + + /** Returns the (possibly empty) map of private-name -> shared SymbolKey. */ + public Map getPrivateSymbols() { + return privateSymbols == null ? Collections.emptyMap() : privateSymbols; + } + + /** + * Returns true if the given private name (including the leading {@code #}) has already been + * declared as any kind of private member of this class. + */ + public boolean hasPrivateName(String name) { + return privateSymbols != null && privateSymbols.containsKey(name); + } + + /** + * Returns the shared {@link SymbolKey} for the given private name, creating it on first use. + * Names include the leading {@code #}. + */ + public SymbolKey getOrCreatePrivateSymbol(String name) { + if (privateSymbols == null) { + privateSymbols = new LinkedHashMap<>(); + } + return privateSymbols.computeIfAbsent(name, n -> new SymbolKey(n, Symbol.Kind.PRIVATE)); + } + + @Override + public String toSource(int depth) { + StringBuilder sb = new StringBuilder(); + sb.append(makeIndent(depth)); + sb.append("class"); + if (className != null) { + sb.append(" "); + sb.append(className.toSource(0)); + } + if (superClass != null) { + sb.append(" extends "); + sb.append(superClass.toSource(0)); + } + sb.append(" {\n"); + for (var e : instanceFields) { + sb.append(makeIndent(depth + 1)); + sb.append(e.name); + AstNode init = e.initializer; + if (init != null) { + sb.append(" = "); + sb.append(init.toSource(0)); + } + sb.append(";\n"); + } + if (privateFieldNames != null) { + for (int i = 0; i < privateFieldNames.size(); i++) { + sb.append(makeIndent(depth + 1)); + sb.append(privateFieldNames.get(i)); + AstNode init = privateFieldInitializers.get(i); + if (init != null) { + sb.append(" = "); + sb.append(init.toSource(0)); + } + sb.append(";\n"); + } + } + if (computedFieldKeys != null) { + for (int i = 0; i < computedFieldKeys.size(); i++) { + sb.append(makeIndent(depth + 1)); + sb.append("["); + sb.append(computedFieldKeys.get(i).toSource(0)); + sb.append("]"); + AstNode init = computedFieldInitializers.get(i); + if (init != null) { + sb.append(" = "); + sb.append(init.toSource(0)); + } + sb.append(";\n"); + } + } + if (constructor != null) { + sb.append(makeIndent(depth + 1)); + sb.append("constructor"); + sb.append(constructor.toSource(0).substring(constructor.toSource(0).indexOf('('))); + sb.append("\n"); + } + if (methodNames != null) { + for (int i = 0; i < methodNames.size(); i++) { + sb.append(makeIndent(depth + 1)); + ElementKind kind = methodKinds.get(i); + if (kind == ElementKind.GETTER) { + sb.append("get "); + } else if (kind == ElementKind.SETTER) { + sb.append("set "); + } + String name = methodNames.get(i); + if (name == null) { + sb.append("["); + sb.append(methodComputedKeys.get(i).toSource(0)); + sb.append("]"); + } else { + sb.append(name); + } + String fnSrc = methods.get(i).toSource(0); + sb.append(fnSrc.substring(fnSrc.indexOf('('))); + sb.append("\n"); + } + } + if (staticMethodNames != null) { + for (int i = 0; i < staticMethodNames.size(); i++) { + sb.append(makeIndent(depth + 1)); + sb.append("static "); + ElementKind kind = staticMethodKinds.get(i); + if (kind == ElementKind.GETTER) { + sb.append("get "); + } else if (kind == ElementKind.SETTER) { + sb.append("set "); + } + String name = staticMethodNames.get(i); + if (name == null) { + sb.append("["); + sb.append(staticMethodComputedKeys.get(i).toSource(0)); + sb.append("]"); + } else { + sb.append(name); + } + String fnSrc = staticMethods.get(i).toSource(0); + sb.append(fnSrc.substring(fnSrc.indexOf('('))); + sb.append("\n"); + } + } + sb.append(makeIndent(depth)); + sb.append("}"); + return sb.toString(); + } + + @Override + public void visit(NodeVisitor v) { + if (v.visit(this)) { + if (className != null) { + className.visit(v); + } + if (superClass != null) { + superClass.visit(v); + } + for (var e : instanceFields) { + if (e.initializer != null) { + e.initializer.visit(v); + } + } + if (privateFieldInitializers != null) { + for (AstNode init : privateFieldInitializers) { + if (init != null) { + init.visit(v); + } + } + } + if (computedFieldKeys != null) { + for (int i = 0; i < computedFieldKeys.size(); i++) { + computedFieldKeys.get(i).visit(v); + AstNode init = computedFieldInitializers.get(i); + if (init != null) { + init.visit(v); + } + } + } + if (constructor != null) { + constructor.visit(v); + } + if (methods != null) { + for (int i = 0; i < methods.size(); i++) { + if (methodComputedKeys != null && methodComputedKeys.get(i) != null) { + methodComputedKeys.get(i).visit(v); + } + methods.get(i).visit(v); + } + } + if (staticMethods != null) { + for (int i = 0; i < staticMethods.size(); i++) { + if (staticMethodComputedKeys != null + && staticMethodComputedKeys.get(i) != null) { + staticMethodComputedKeys.get(i).visit(v); + } + staticMethods.get(i).visit(v); + } + } + } + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/Comment.java b/rhino/src/main/java/org/mozilla/javascript/ast/Comment.java index 6eb327524a4..94b2494e674 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/Comment.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/Comment.java @@ -6,6 +6,7 @@ package org.mozilla.javascript.ast; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -98,6 +99,18 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != Comment.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + Comment copy = new Comment(this.position, this.length, this.commentType, this.value); + copy.type = this.type; + copyAstFields(this, copy); + return copy; + } + /** * Comment nodes are not visited during normal visitor traversals, but comply with the {@link * AstNode#visit} interface. diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ComputedPropertyKey.java b/rhino/src/main/java/org/mozilla/javascript/ast/ComputedPropertyKey.java index eed44b53099..136261ab4e5 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ComputedPropertyKey.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ComputedPropertyKey.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** AST node for a computed property key, i.e. `[ Expression ]` in an object literal. */ @@ -42,6 +44,28 @@ public String toSource(int depth) { return makeIndent(depth) + '[' + expression.toSource(depth) + ']'; } + @Override + protected Node shallowCopy() { + if (getClass() != ComputedPropertyKey.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ComputedPropertyKey copy = new ComputedPropertyKey(this.position, this.length); + copy.type = this.type; + copyAstFields(this, copy); + copy.expression = this.expression; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + ComputedPropertyKey copy = (ComputedPropertyKey) copyNode; + if (this.expression != null) { + copy.expression = (AstNode) this.expression.cloneStructure(map); + } + } + /** Visits this node, then the expression. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ConditionalExpression.java b/rhino/src/main/java/org/mozilla/javascript/ast/ConditionalExpression.java index 5bbca69f67d..264c5af760f 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ConditionalExpression.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ConditionalExpression.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -140,6 +142,38 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != ConditionalExpression.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ConditionalExpression copy = new ConditionalExpression(); + copy.type = this.type; + copyAstFields(this, copy); + copy.testExpression = this.testExpression; + copy.trueExpression = this.trueExpression; + copy.falseExpression = this.falseExpression; + copy.questionMarkPosition = this.questionMarkPosition; + copy.colonPosition = this.colonPosition; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + ConditionalExpression copy = (ConditionalExpression) copyNode; + if (this.testExpression != null) { + copy.testExpression = (AstNode) this.testExpression.cloneStructure(map); + } + if (this.trueExpression != null) { + copy.trueExpression = (AstNode) this.trueExpression.cloneStructure(map); + } + if (this.falseExpression != null) { + copy.falseExpression = (AstNode) this.falseExpression.cloneStructure(map); + } + } + /** * Visits this node, then the test-expression, the true-expression, and the false-expression. */ diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ContinueStatement.java b/rhino/src/main/java/org/mozilla/javascript/ast/ContinueStatement.java index ac0ed149ceb..1511b14ff35 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ContinueStatement.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ContinueStatement.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -101,6 +103,44 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != ContinueStatement.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ContinueStatement copy = new ContinueStatement(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.label = this.label; + copy.targetLoop = this.targetLoop; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + ContinueStatement copy = (ContinueStatement) copyNode; + if (this.label != null) { + copy.label = (Name) this.label.cloneStructure(map); + } + } + + @Override + protected void fixupReferences(IdentityHashMap map) { + if (label != null) { + label.fixupReferences(map); + } + if (targetLoop != null) { + Node mapped = map.get(targetLoop); + if (mapped instanceof Loop) { + targetLoop = (Loop) mapped; + } + } + super.fixupReferences(map); + } + /** Visits this node, then visits the label if non-{@code null}. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/DoLoop.java b/rhino/src/main/java/org/mozilla/javascript/ast/DoLoop.java index 7930f4f9868..bb8f6d5c701 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/DoLoop.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/DoLoop.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -74,6 +76,32 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != DoLoop.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + DoLoop copy = new DoLoop(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.copyScopeFieldsFrom(this); + copy.copyLoopFieldsFrom(this); + copy.condition = this.condition; + copy.whilePosition = this.whilePosition; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + DoLoop copy = (DoLoop) copyNode; + if (this.condition != null) { + copy.condition = (AstNode) this.condition.cloneStructure(map); + } + } + /** Visits this node, the body, and then the while-expression. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ElementGet.java b/rhino/src/main/java/org/mozilla/javascript/ast/ElementGet.java index 6c301f579b6..c997a6f05dd 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ElementGet.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ElementGet.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -114,6 +116,34 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != ElementGet.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ElementGet copy = new ElementGet(); + copy.type = this.type; + copyAstFields(this, copy); + copy.target = this.target; + copy.element = this.element; + copy.lb = this.lb; + copy.rb = this.rb; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + ElementGet copy = (ElementGet) copyNode; + if (this.target != null) { + copy.target = (AstNode) this.target.cloneStructure(map); + } + if (this.element != null) { + copy.element = (AstNode) this.element.cloneStructure(map); + } + } + /** Visits this node, the target, and the index expression. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/EmptyExpression.java b/rhino/src/main/java/org/mozilla/javascript/ast/EmptyExpression.java index df7ff232af7..f82349cbb12 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/EmptyExpression.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/EmptyExpression.java @@ -6,6 +6,7 @@ package org.mozilla.javascript.ast; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -34,6 +35,18 @@ public String toSource(int depth) { return makeIndent(depth); } + @Override + protected Node shallowCopy() { + if (getClass() != EmptyExpression.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + EmptyExpression copy = new EmptyExpression(); + copy.type = this.type; + copyAstFields(this, copy); + return copy; + } + /** Visits this node. There are no children. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/EmptyStatement.java b/rhino/src/main/java/org/mozilla/javascript/ast/EmptyStatement.java index bcf7f0c7231..1297a583275 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/EmptyStatement.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/EmptyStatement.java @@ -6,6 +6,7 @@ package org.mozilla.javascript.ast; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** AST node for an empty statement. Node type is {@link Token#EMPTY}. */ @@ -32,6 +33,18 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != EmptyStatement.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + EmptyStatement copy = new EmptyStatement(); + copy.type = this.type; + copyAstFields(this, copy); + return copy; + } + /** Visits this node. There are no children. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ErrorNode.java b/rhino/src/main/java/org/mozilla/javascript/ast/ErrorNode.java index f852bef795d..1c555f26497 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ErrorNode.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ErrorNode.java @@ -6,6 +6,7 @@ package org.mozilla.javascript.ast; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** AST node representing a parse error or a warning. Node type is {@link Token#ERROR}. */ @@ -42,6 +43,19 @@ public String toSource(int depth) { return ""; } + @Override + protected Node shallowCopy() { + if (getClass() != ErrorNode.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ErrorNode copy = new ErrorNode(); + copy.type = this.type; + copyAstFields(this, copy); + copy.message = this.message; + return copy; + } + /** * Error nodes are not visited during normal visitor traversals, but comply with the {@link * AstNode#visit} interface. diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ExpressionStatement.java b/rhino/src/main/java/org/mozilla/javascript/ast/ExpressionStatement.java index b70cf46895a..9cb694fbea4 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ExpressionStatement.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ExpressionStatement.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -109,6 +111,28 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != ExpressionStatement.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ExpressionStatement copy = new ExpressionStatement(); + copy.type = this.type; + copyAstFields(this, copy); + copy.expr = this.expr; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + ExpressionStatement copy = (ExpressionStatement) copyNode; + if (this.expr != null) { + copy.expr = (AstNode) this.expr.cloneStructure(map); + } + } + /** Visits this node, then the wrapped statement. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ForInLoop.java b/rhino/src/main/java/org/mozilla/javascript/ast/ForInLoop.java index 68f27047e58..21224844b6b 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ForInLoop.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ForInLoop.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -29,6 +31,7 @@ public class ForInLoop extends Loop { protected int eachPosition = -1; protected boolean isForEach; protected boolean isForOf; + protected boolean isForAwaitOf; { type = Token.FOR; @@ -97,6 +100,19 @@ public void setIsForOf(boolean isForOf) { this.isForOf = isForOf; } + /** Returns whether the loop is a for-await-of loop */ + public boolean isForAwaitOf() { + return isForAwaitOf; + } + + /** + * Sets whether the loop is a for-await-of loop. A for-await-of loop implies {@link #isForOf()} + * is also true. + */ + public void setIsForAwaitOf(boolean isForAwaitOf) { + this.isForAwaitOf = isForAwaitOf; + } + /** Returns position of "in" or "of" keyword */ public int getInPosition() { return inPosition; @@ -131,7 +147,9 @@ public String toSource(int depth) { StringBuilder sb = new StringBuilder(); sb.append(makeIndent(depth)); sb.append("for "); - if (isForEach()) { + if (isForAwaitOf()) { + sb.append("await "); + } else if (isForEach()) { sb.append("each "); } sb.append("("); @@ -151,6 +169,45 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != ForInLoop.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ForInLoop copy = new ForInLoop(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.copyScopeFieldsFrom(this); + copy.copyLoopFieldsFrom(this); + copy.copyForInLoopFieldsFrom(this); + return copy; + } + + /** Copies {@link ForInLoop}-level fields. */ + protected void copyForInLoopFieldsFrom(ForInLoop source) { + this.iterator = source.iterator; + this.iteratedObject = source.iteratedObject; + this.inPosition = source.inPosition; + this.eachPosition = source.eachPosition; + this.isForEach = source.isForEach; + this.isForOf = source.isForOf; + this.isForAwaitOf = source.isForAwaitOf; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + ForInLoop copy = (ForInLoop) copyNode; + if (this.iterator != null) { + copy.iterator = (AstNode) this.iterator.cloneStructure(map); + } + if (this.iteratedObject != null) { + copy.iteratedObject = (AstNode) this.iteratedObject.cloneStructure(map); + } + } + /** Visits this node, the iterator, the iterated object, and the body. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ForLoop.java b/rhino/src/main/java/org/mozilla/javascript/ast/ForLoop.java index 138e3303fcb..2d9aaa1b37d 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ForLoop.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ForLoop.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -124,6 +126,44 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != ForLoop.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + ForLoop copy = new ForLoop(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.copyScopeFieldsFrom(this); + copy.copyLoopFieldsFrom(this); + copy.copyForLoopFieldsFrom(this); + return copy; + } + + /** Copies {@link ForLoop}-level fields. References shared until {@link #cloneNamedChildren}. */ + protected void copyForLoopFieldsFrom(ForLoop source) { + this.initializer = source.initializer; + this.condition = source.condition; + this.increment = source.increment; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + ForLoop copy = (ForLoop) copyNode; + if (this.initializer != null) { + copy.initializer = (AstNode) this.initializer.cloneStructure(map); + } + if (this.condition != null) { + copy.condition = (AstNode) this.condition.cloneStructure(map); + } + if (this.increment != null) { + copy.increment = (AstNode) this.increment.cloneStructure(map); + } + } + /** * Visits this node, the initializer expression, the loop condition expression, the increment * expression, and then the loop body. diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/FunctionCall.java b/rhino/src/main/java/org/mozilla/javascript/ast/FunctionCall.java index 1358980da4a..a7c79ee3393 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/FunctionCall.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/FunctionCall.java @@ -8,7 +8,9 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.IdentityHashMap; import java.util.List; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** AST node for a function call. Node type is {@link Token#CALL}. */ @@ -154,6 +156,47 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != FunctionCall.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + FunctionCall copy = new FunctionCall(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyFunctionCallFieldsFrom(this); + return copy; + } + + /** + * Copies {@link FunctionCall}-level fields. Children references are shared until {@link + * #cloneNamedChildren} runs. + */ + protected void copyFunctionCallFieldsFrom(FunctionCall source) { + this.target = source.target; + this.arguments = source.arguments; + this.lp = source.lp; + this.rp = source.rp; + this.optionalCall = source.optionalCall; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + FunctionCall copy = (FunctionCall) copyNode; + if (this.target != null) { + copy.target = (AstNode) this.target.cloneStructure(map); + } + if (this.arguments != null) { + List args = new ArrayList<>(this.arguments.size()); + for (AstNode a : this.arguments) { + args.add((AstNode) a.cloneStructure(map)); + } + copy.arguments = args; + } + } + /** Visits this node, the target object, and the arguments. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/FunctionNode.java b/rhino/src/main/java/org/mozilla/javascript/ast/FunctionNode.java index d7eeaea0138..3da6722c7f6 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/FunctionNode.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/FunctionNode.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import org.mozilla.javascript.Node; @@ -63,6 +64,7 @@ public class FunctionNode extends ScriptNode { public static final int FUNCTION_EXPRESSION = 2; public static final int FUNCTION_EXPRESSION_STATEMENT = 3; public static final int ARROW_FUNCTION = 4; + public static final int FUNCTION_BLOCK_SCOPED = 5; public static enum Form { FUNCTION, @@ -96,6 +98,12 @@ public void putDefaultParams(Object left, Object right) { defaultParams.add(right); } + @Override + public boolean hasDestructuring() { + return getProp(Node.DESTRUCTURING_PARAMS) != null + || ((destructuringRvalues != null) && !destructuringRvalues.isEmpty()); + } + @Override public List getDestructuringRvalues() { return destructuringRvalues; @@ -103,10 +111,15 @@ public List getDestructuringRvalues() { @Override public void putDestructuringRvalues(Node left, Node right) { + putDestructuringRvalues(left, right, null); + } + + @Override + public void putDestructuringRvalues(Node left, Node right, Name name) { if (destructuringRvalues == null) { destructuringRvalues = new ArrayList<>(); } - destructuringRvalues.add(new Node[] {left, right}); + destructuringRvalues.add(new Node[] {left, right, name}); } ArrayList defaultParams; @@ -118,10 +131,12 @@ public void putDestructuringRvalues(Node left, Node right) { private boolean requiresArgumentObject; private boolean isGenerator; private boolean isES6Generator; + private boolean isAsync; private List generatorResumePoints; private Map liveLocals; private Node generatorParamInitBlock; // IR block for default parameters init in generators private AstNode memberExprNode; + private boolean isClassConstructor; { type = Token.FUNCTION; @@ -327,6 +342,20 @@ public void setIsES6Generator() { needsActivation = true; } + public boolean isAsync() { + return isAsync; + } + + public void setIsAsync() { + isAsync = true; + needsActivation = true; + } + + /** Returns whether this is an {@code async function*} (async generator). */ + public boolean isAsyncGenerator() { + return isAsync && isES6Generator; + } + @Override public boolean hasRestParameter() { return hasRestParameter; @@ -345,6 +374,14 @@ public void setIsShorthand() { isShorthand = true; } + public boolean isClassConstructor() { + return isClassConstructor; + } + + public void setIsClassConstructor(boolean isClassConstructor) { + this.isClassConstructor = isClassConstructor; + } + public void addResumptionPoint(Node target) { if (generatorResumePoints == null) generatorResumePoints = new ArrayList<>(); generatorResumePoints.add(target); @@ -443,6 +480,9 @@ public String toSource(int depth) { boolean isArrow = functionType == ARROW_FUNCTION; if (!isMethod()) { sb.append(makeIndent(depth)); + if (isAsync) { + sb.append("async "); + } if (!isArrow) { sb.append("function"); } @@ -513,6 +553,69 @@ public void visit(NodeVisitor v) { } } + @Override + protected Node shallowCopy() { + if (getClass() != FunctionNode.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + FunctionNode copy = new FunctionNode(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.copyScopeFieldsFrom(this); + copy.copyScriptNodeFieldsFrom(this); + copy.copyFunctionNodeFieldsFrom(this); + return copy; + } + + /** Copies {@link FunctionNode}-level fields. */ + protected void copyFunctionNodeFieldsFrom(FunctionNode source) { + this.functionName = source.functionName; + this.params = source.params; + this.body = source.body; + this.isExpressionClosure = source.isExpressionClosure; + this.functionForm = source.functionForm; + this.lp = source.lp; + this.rp = source.rp; + this.hasRestParameter = source.hasRestParameter; + this.isShorthand = source.isShorthand; + this.defaultParams = source.defaultParams; + this.destructuringRvalues = source.destructuringRvalues; + this.functionType = source.functionType; + this.needsActivation = source.needsActivation; + this.requiresArgumentObject = source.requiresArgumentObject; + this.isGenerator = source.isGenerator; + this.isES6Generator = source.isES6Generator; + this.isAsync = source.isAsync; + this.generatorResumePoints = source.generatorResumePoints; + this.liveLocals = source.liveLocals; + this.generatorParamInitBlock = source.generatorParamInitBlock; + this.memberExprNode = source.memberExprNode; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + FunctionNode copy = (FunctionNode) copyNode; + if (this.functionName != null) { + copy.functionName = (Name) this.functionName.cloneStructure(map); + } + if (this.params != null) { + List list = new ArrayList<>(this.params.size()); + for (AstNode p : this.params) { + list.add((AstNode) p.cloneStructure(map)); + } + copy.params = list; + } + if (this.body != null) { + copy.body = (AstNode) this.body.cloneStructure(map); + } + if (this.memberExprNode != null) { + copy.memberExprNode = (AstNode) this.memberExprNode.cloneStructure(map); + } + } + /** * Calculate the arity (function.length) for a function. According to ECMAScript spec, the * length property should only count parameters up to (but not including) the first one with a diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorExpression.java b/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorExpression.java index 83ecedbcdd2..73ef078d04b 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorExpression.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorExpression.java @@ -7,7 +7,9 @@ package org.mozilla.javascript.ast; import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.List; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** */ @@ -137,6 +139,45 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != GeneratorExpression.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + GeneratorExpression copy = new GeneratorExpression(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.copyScopeFieldsFrom(this); + copy.result = this.result; + copy.loops = this.loops; + copy.filter = this.filter; + copy.ifPosition = this.ifPosition; + copy.lp = this.lp; + copy.rp = this.rp; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + GeneratorExpression copy = (GeneratorExpression) copyNode; + if (this.result != null) { + copy.result = (AstNode) this.result.cloneStructure(map); + } + if (this.loops != null) { + List list = new ArrayList<>(this.loops.size()); + for (GeneratorExpressionLoop l : this.loops) { + list.add((GeneratorExpressionLoop) l.cloneStructure(map)); + } + copy.loops = list; + } + if (this.filter != null) { + copy.filter = (AstNode) this.filter.cloneStructure(map); + } + } + /** Visits this node, the result expression, the loops, and the optional filter. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorExpressionLoop.java b/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorExpressionLoop.java index cffd11a6e84..119d8f73a8e 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorExpressionLoop.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorExpressionLoop.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import org.mozilla.javascript.Node; + /** */ public class GeneratorExpressionLoop extends ForInLoop { @@ -43,6 +45,22 @@ public String toSource(int depth) { + ")"; } + @Override + protected Node shallowCopy() { + if (getClass() != GeneratorExpressionLoop.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + GeneratorExpressionLoop copy = new GeneratorExpressionLoop(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.copyScopeFieldsFrom(this); + copy.copyLoopFieldsFrom(this); + copy.copyForInLoopFieldsFrom(this); + return copy; + } + /** * Visits the iterator expression and the iterated object expression. There is no * body-expression for this loop type. diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorMethodDefinition.java b/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorMethodDefinition.java index 51cb1ca2e32..5778331e507 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorMethodDefinition.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/GeneratorMethodDefinition.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -16,6 +18,10 @@ public class GeneratorMethodDefinition extends AstNode { private AstNode methodName; + private GeneratorMethodDefinition() { + setType(Token.MUL); + } + public GeneratorMethodDefinition(int pos, int len, AstNode methodName) { super(pos, len); setType(Token.MUL); @@ -37,6 +43,28 @@ public String toSource(int depth) { return makeIndent(depth) + '*' + methodName.toSource(depth); } + @Override + protected Node shallowCopy() { + if (getClass() != GeneratorMethodDefinition.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + GeneratorMethodDefinition copy = new GeneratorMethodDefinition(); + copy.type = this.type; + copyAstFields(this, copy); + copy.methodName = this.methodName; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + GeneratorMethodDefinition copy = (GeneratorMethodDefinition) copyNode; + if (this.methodName != null) { + copy.methodName = (AstNode) this.methodName.cloneStructure(map); + } + } + /** Visits this node, then the name. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/IfStatement.java b/rhino/src/main/java/org/mozilla/javascript/ast/IfStatement.java index 269ab1259eb..d26b23e2d91 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/IfStatement.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/IfStatement.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -182,4 +184,38 @@ public AstNode getElseKeyWordInlineComment() { public void setElseKeyWordInlineComment(AstNode elseKeyWordInlineComment) { this.elseKeyWordInlineComment = elseKeyWordInlineComment; } + + @Override + protected Node shallowCopy() { + if (getClass() != IfStatement.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + IfStatement copy = new IfStatement(); + copy.type = this.type; + copyAstFields(this, copy); + copy.condition = this.condition; + copy.thenPart = this.thenPart; + copy.elsePart = this.elsePart; + copy.elseKeyWordInlineComment = this.elseKeyWordInlineComment; + copy.elsePosition = this.elsePosition; + copy.lp = this.lp; + copy.rp = this.rp; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + IfStatement copy = (IfStatement) copyNode; + if (this.condition != null) { + copy.condition = (AstNode) this.condition.cloneStructure(map); + } + if (this.thenPart != null) { + copy.thenPart = (AstNode) this.thenPart.cloneStructure(map); + } + if (this.elsePart != null) { + copy.elsePart = (AstNode) this.elsePart.cloneStructure(map); + } + } } diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/InfixExpression.java b/rhino/src/main/java/org/mozilla/javascript/ast/InfixExpression.java index 7be56955d40..d2c11f2245c 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/InfixExpression.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/InfixExpression.java @@ -6,6 +6,8 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -161,6 +163,41 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != InfixExpression.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + InfixExpression copy = new InfixExpression(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyInfixFieldsFrom(this); + return copy; + } + + /** + * Copies {@link InfixExpression}-level fields ({@code left}, {@code right}, {@code + * operatorPosition}). Children references are shared until {@link #cloneNamedChildren} runs. + */ + protected void copyInfixFieldsFrom(InfixExpression source) { + this.left = source.left; + this.right = source.right; + this.operatorPosition = source.operatorPosition; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + InfixExpression copy = (InfixExpression) copyNode; + if (this.left != null) { + copy.left = (AstNode) this.left.cloneStructure(map); + } + if (this.right != null) { + copy.right = (AstNode) this.right.cloneStructure(map); + } + } + /** Visits this node, the left operand, and the right operand. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/Jump.java b/rhino/src/main/java/org/mozilla/javascript/ast/Jump.java index b71bb45cf89..3e0801f769b 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/Jump.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/Jump.java @@ -6,6 +6,7 @@ package org.mozilla.javascript.ast; +import java.util.IdentityHashMap; import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; @@ -94,6 +95,53 @@ public void setContinue(Node continueTarget) { target2 = continueTarget; } + @Override + protected Node shallowCopy() { + if (getClass() != Jump.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + Jump copy = new Jump(type); + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + return copy; + } + + /** + * Copies the {@link Jump}-level cross-reference fields ({@code target}, {@code target2}, {@code + * jumpNode}) from {@code source} into this node. Intended for use by subclass {@link + * Node#shallowCopy()} implementations. The values are copied raw; remapping happens later in + * {@link Jump#fixupReferences(IdentityHashMap)}. + */ + protected void copyJumpFieldsFrom(Jump source) { + this.target = source.target; + this.target2 = source.target2; + this.jumpNode = source.jumpNode; + } + + @Override + protected void fixupReferences(IdentityHashMap map) { + if (target != null) { + Node mapped = map.get(target); + if (mapped != null) { + target = mapped; + } + } + if (target2 != null) { + Node mapped = map.get(target2); + if (mapped != null) { + target2 = mapped; + } + } + if (jumpNode != null) { + Node mapped = map.get(jumpNode); + if (mapped instanceof Jump) { + jumpNode = (Jump) mapped; + } + } + super.fixupReferences(map); + } + /** * Jumps are only used directly during code generation, and do not support this interface. * diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/KeywordLiteral.java b/rhino/src/main/java/org/mozilla/javascript/ast/KeywordLiteral.java index 2cb92dc23dd..e783b22721c 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/KeywordLiteral.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/KeywordLiteral.java @@ -6,13 +6,14 @@ package org.mozilla.javascript.ast; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** * AST node for keyword literals: currently, {@code this}, {@code super}, {@code null}, {@code - * undefined}, {@code true}, {@code false}, and {@code debugger}. Node type is one of {@link - * Token#THIS}, {@link Token#SUPER}, {@link Token#NULL}, {@link Token#UNDEFINED}, {@link - * Token#TRUE}, {@link Token#FALSE}, or {@link Token#DEBUGGER}. + * undefined}, {@code true}, {@code false}, {@code debugger}, and {@code new.target}. Node type is + * one of {@link Token#THIS}, {@link Token#SUPER}, {@link Token#NULL}, {@link Token#UNDEFINED}, + * {@link Token#TRUE}, {@link Token#FALSE}, {@link Token#DEBUGGER}, or {@link Token#NEW_TARGET}. */ public class KeywordLiteral extends AstNode { @@ -49,7 +50,8 @@ public KeywordLiteral setType(int nodeType) { || nodeType == Token.UNDEFINED || nodeType == Token.TRUE || nodeType == Token.FALSE - || nodeType == Token.DEBUGGER)) + || nodeType == Token.DEBUGGER + || nodeType == Token.NEW_TARGET)) throw new IllegalArgumentException("Invalid node type: " + nodeType); type = nodeType; return this; @@ -86,12 +88,27 @@ public String toSource(int depth) { case Token.DEBUGGER: sb.append("debugger;\n"); break; + case Token.NEW_TARGET: + sb.append("new.target"); + break; default: throw new IllegalStateException("Invalid keyword literal type: " + getType()); } return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != KeywordLiteral.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + KeywordLiteral copy = new KeywordLiteral(); + copy.type = this.type; + copyAstFields(this, copy); + return copy; + } + /** Visits this node. There are no children to visit. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/Label.java b/rhino/src/main/java/org/mozilla/javascript/ast/Label.java index d1e8cc4df3d..6167afe8196 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/Label.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/Label.java @@ -6,6 +6,7 @@ package org.mozilla.javascript.ast; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -63,6 +64,20 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != Label.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + Label copy = new Label(); + copy.type = this.type; + copyAstFields(this, copy); + copy.copyJumpFieldsFrom(this); + copy.name = this.name; + return copy; + } + /** Visits this label. There are no children to visit. */ @Override public void visit(NodeVisitor v) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/LabeledStatement.java b/rhino/src/main/java/org/mozilla/javascript/ast/LabeledStatement.java index 5616c3adfce..df253206814 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/LabeledStatement.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/LabeledStatement.java @@ -7,7 +7,9 @@ package org.mozilla.javascript.ast; import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.List; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; /** @@ -114,6 +116,36 @@ public String toSource(int depth) { return sb.toString(); } + @Override + protected Node shallowCopy() { + if (getClass() != LabeledStatement.class) { + throw new UnsupportedOperationException( + "shallowCopy() not implemented for " + getClass().getName()); + } + LabeledStatement copy = new LabeledStatement(); + copy.type = this.type; + copyAstFields(this, copy); + copy.labels = this.labels; + copy.statement = this.statement; + return copy; + } + + @Override + protected void cloneNamedChildren(Node copyNode, IdentityHashMap map) { + super.cloneNamedChildren(copyNode, map); + LabeledStatement copy = (LabeledStatement) copyNode; + if (this.labels != null) { + List