diff --git a/rhino/src/main/java/org/mozilla/javascript/ArrowFunction.java b/rhino/src/main/java/org/mozilla/javascript/ArrowFunction.java index 45044f8856..c29c881f41 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ArrowFunction.java +++ b/rhino/src/main/java/org/mozilla/javascript/ArrowFunction.java @@ -15,11 +15,17 @@ public class ArrowFunction extends BaseFunction { private final Callable targetFunction; private final Scriptable boundThis; + private final Scriptable boundHomeObject; public ArrowFunction( - Context cx, Scriptable scope, Callable targetFunction, Scriptable boundThis) { + Context cx, + Scriptable scope, + Callable targetFunction, + Scriptable boundThis, + Scriptable boundHomeObject) { this.targetFunction = targetFunction; this.boundThis = boundThis; + this.boundHomeObject = boundHomeObject; ScriptRuntime.setFunctionProtoAndParent(this, cx, scope, false); @@ -80,6 +86,10 @@ Scriptable getCallThis(Context cx) { return boundThis != null ? boundThis : ScriptRuntime.getTopCallScope(cx); } + Scriptable getBoundHomeObject() { + return this.boundHomeObject; + } + Callable getTargetFunction() { return targetFunction; } diff --git a/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java b/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java index 5229ac4d7e..a270b32507 100644 --- a/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java +++ b/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java @@ -656,6 +656,14 @@ protected int findPrototypeId(String s) { return id; } + public void setHomeObject(Scriptable homeObject) { + this.homeObject = homeObject; + } + + public Scriptable getHomeObject() { + return homeObject; + } + private static final int Id_constructor = 1, Id_toString = 2, Id_toSource = 3, @@ -668,6 +676,7 @@ protected int findPrototypeId(String s) { private Object argumentsObj = NOT_FOUND; private String nameValue = null; private boolean isGeneratorFunction = false; + private Scriptable homeObject = null; // For function object instances, attributes are // {configurable:false, enumerable:false}; diff --git a/rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java b/rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java index e9b70da565..8b57434bd4 100644 --- a/rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java +++ b/rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java @@ -566,6 +566,9 @@ private void visitExpression(Node node, int contextFlags) { throw Kit.codeBug(); } addIndexOp(Icode_CLOSURE_EXPR, fnIndex); + if (fn.isMethodDefinition()) { + addIcode(ICode_FN_STORE_HOME_OBJECT); + } stackChange(1); } break; @@ -626,6 +629,8 @@ private void visitExpression(Node node, int contextFlags) { addUint8(callType); addUint8(type == Token.NEW ? 1 : 0); addUint16(lineNumber & 0xFFFF); + } else if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + addIndexOp(Icode_CALL_ON_SUPER, argCount); } else { // Only use the tail call optimization if we're not in a try // or we're not generating debug info (since the @@ -718,6 +723,10 @@ private void visitExpression(Node node, int contextFlags) { addIcode(Icode_POP); addStringOp(Token.NAME, "undefined"); resolveForwardGoto(afterLabel); + } else if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + addStringOp( + type == Token.GETPROP ? Token.GETPROP_SUPER : Token.GETPROPNOWARN_SUPER, + child.getString()); } else { addStringOp(type, child.getString()); } @@ -728,7 +737,9 @@ private void visitExpression(Node node, int contextFlags) { visitExpression(child, 0); child = child.getNext(); visitExpression(child, 0); - if (isName) { + if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + addIcode(Icode_DELPROP_SUPER); + } else if (isName) { // special handling for delete name addIcode(Icode_DELNAME); } else { @@ -757,6 +768,10 @@ private void visitExpression(Node node, int contextFlags) { addIcode(Icode_POP); addStringOp(Token.NAME, "undefined"); resolveForwardGoto(afterLabel); + } else if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + visitExpression(child, 0); + addToken(Token.GETELEM_SUPER); + stackChange(-1); } else { finishGetElemGeneration(child); } @@ -843,7 +858,11 @@ private void visitExpression(Node node, int contextFlags) { stackChange(-1); } visitExpression(child, 0); - addStringOp(Token.SETPROP, property); + addStringOp( + node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1 + ? Token.SETPROP_SUPER + : Token.SETPROP, + property); stackChange(-1); } break; @@ -863,7 +882,10 @@ private void visitExpression(Node node, int contextFlags) { stackChange(-1); } visitExpression(child, 0); - addToken(Token.SETELEM); + addToken( + node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1 + ? Token.SETELEM_SUPER + : Token.SETELEM); stackChange(-2); break; @@ -996,6 +1018,7 @@ private void visitExpression(Node node, int contextFlags) { case Token.NULL: case Token.THIS: + case Token.SUPER: case Token.THISFN: case Token.FALSE: case Token.TRUE: @@ -1245,6 +1268,12 @@ private CompleteOptionalCallJump completeOptionalCallJump() { private void visitIncDec(Node node, Node child) { int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP); int childType = child.getType(); + + if (child.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + visitSuperIncDec(node, child, childType, incrDecrMask); + return; + } + switch (childType) { case Token.GETVAR: { @@ -1298,6 +1327,79 @@ private void visitIncDec(Node node, Node child) { } } + // Handles super.x++ and variants thereof. We don't want to create new icode in the interpreter + // for this edge case, so we will transform this into something like super.x = super.x + 1 + private void visitSuperIncDec(Node node, Node child, int childType, int incrDecrMask) { + Node object = child.getFirstChild(); + + // Push the old value on the stack + visitExpression(object, 0); // stack: [super] + switch (childType) { + case Token.GETPROP: + addStringOp(Token.GETPROP_SUPER, object.getNext().getString()); // stack: [p] + break; + + case Token.GETELEM: + { + Node index = object.getNext(); + visitExpression(index, 0); // stack: [super, elem] + addToken(Token.GETELEM_SUPER); // stack: [p] + stackChange(-1); + break; + } + + default: + throw badTree(node); + } + + // If it's a postfix expression, we copy the old value + // If it's postfix, we only need the _new_ value on the stack + if ((incrDecrMask & Node.POST_FLAG) != 0) { + addIcode(Icode_DUP); // stack: postfix [p, p], prefix: [p] + stackChange(+1); + } + + // We need, in order, super and then the new value + addToken(Token.SUPER); // stack: postfix [p, p, super], prefix: [p, super] + stackChange(+1); + addIcode(Icode_SWAP); // stack: postfix [p, super, p], prefix: [super, p] + + // Increment or decrement the new value + addIcode(Icode_ONE); // stack: prefix [p, super, p, 1], postfix: [super, p, 1] + stackChange(+1); + if ((incrDecrMask & Node.DECR_FLAG) == 0) { + addToken(Token.ADD); // stack: prefix [p, super, p+1], postfix: [super, p+1] + } else { + addToken(Token.SUB); // stack: prefix [p, super, p-1], postfix: [super, p-1] + } + stackChange(-1); + + // Assign the new value to the property + switch (childType) { + case Token.GETPROP: + addStringOp(Token.SETPROP_SUPER, object.getNext().getString()); + // stack: prefix [p, p+-1], postfix: [p+-1] + stackChange(-1); + break; + + case Token.GETELEM: + { + Node index = object.getNext(); + visitExpression(index, 0); + // stack: prefix [p, super, p+-1, elem], postfix: [super, p+-1, elem] + addToken(Token.SETELEM_SUPER); // stack: prefix [p, p+-1], postfix: [p+-1] + stackChange(-2); + break; + } + } + + // If it was a postfix, just drop the new value + if ((incrDecrMask & Node.POST_FLAG) != 0) { + addIcode(Icode_POP); // stack: [p] + stackChange(-1); + } + } + private void visitLiteral(Node node, Node child) { int type = node.getType(); if (type == Token.ARRAYLIT) { diff --git a/rhino/src/main/java/org/mozilla/javascript/CompilerEnvirons.java b/rhino/src/main/java/org/mozilla/javascript/CompilerEnvirons.java index 491eb647e5..d6bffde15f 100644 --- a/rhino/src/main/java/org/mozilla/javascript/CompilerEnvirons.java +++ b/rhino/src/main/java/org/mozilla/javascript/CompilerEnvirons.java @@ -221,6 +221,15 @@ public boolean getAllowSharpComments() { return allowSharpComments; } + /** Allows usage of "super" everywhere, simulating that we are inside a method. */ + public void setAllowSuper(boolean allowSuper) { + this.allowSuper = allowSuper; + } + + public boolean isAllowSuper() { + return allowSuper; + } + /** * Returns a {@code CompilerEnvirons} suitable for using Rhino in an IDE environment. Most * features are enabled by default. The {@link ErrorReporter} is set to an {@link @@ -257,5 +266,6 @@ public static CompilerEnvirons ideEnvirons() { private boolean warnTrailingComma; private boolean ideMode; private boolean allowSharpComments; + private boolean allowSuper; Set activationNames; } diff --git a/rhino/src/main/java/org/mozilla/javascript/Context.java b/rhino/src/main/java/org/mozilla/javascript/Context.java index ca9e460a92..a28a6e75fb 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Context.java +++ b/rhino/src/main/java/org/mozilla/javascript/Context.java @@ -28,6 +28,7 @@ import java.util.Optional; import java.util.Set; import java.util.TimeZone; +import java.util.function.Consumer; import java.util.function.UnaryOperator; import org.mozilla.classfile.ClassFileWriter.ClassFileFormatException; import org.mozilla.javascript.ast.AstRoot; @@ -1367,6 +1368,16 @@ public final Script compileReader( */ public final Script compileReader( Reader in, String sourceName, int lineno, Object securityDomain) throws IOException { + return compileReader(in, sourceName, lineno, securityDomain, null); + } + + public Script compileReader( + Reader in, + String sourceName, + int lineno, + Object securityDomain, + Consumer compilerEnvironsProcessor) + throws IOException { if (lineno < 0) { // For compatibility IllegalArgumentException can not be thrown here lineno = 0; @@ -1381,7 +1392,8 @@ public final Script compileReader( securityDomain, false, null, - null); + null, + compilerEnvironsProcessor); } /** @@ -1405,7 +1417,7 @@ public final Script compileString( // For compatibility IllegalArgumentException can not be thrown here lineno = 0; } - return compileString(source, null, null, sourceName, lineno, securityDomain); + return compileString(source, null, null, sourceName, lineno, securityDomain, null); } final Script compileString( @@ -1414,7 +1426,8 @@ final Script compileString( ErrorReporter compilationErrorReporter, String sourceName, int lineno, - Object securityDomain) { + Object securityDomain, + Consumer compilerEnvironsProcessor) { return (Script) compileImpl( null, @@ -1424,7 +1437,8 @@ final Script compileString( securityDomain, false, compiler, - compilationErrorReporter); + compilationErrorReporter, + compilerEnvironsProcessor); } /** @@ -1465,7 +1479,8 @@ final Function compileFunction( securityDomain, true, compiler, - compilationErrorReporter); + compilationErrorReporter, + null); } /** @@ -2424,7 +2439,8 @@ protected Object compileImpl( Object securityDomain, boolean returnFunction, Evaluator compiler, - ErrorReporter compilationErrorReporter) { + ErrorReporter compilationErrorReporter, + Consumer compilerEnvironProcessor) { if (sourceName == null) { sourceName = "unnamed script"; } @@ -2441,6 +2457,9 @@ protected Object compileImpl( if (compilationErrorReporter == null) { compilationErrorReporter = compilerEnv.getErrorReporter(); } + if (compilerEnvironProcessor != null) { + compilerEnvironProcessor.accept(compilerEnv); + } ScriptNode tree = parse( @@ -2524,7 +2543,8 @@ private ScriptNode parse( } } - IRFactory irf = new IRFactory(compilerEnv, sourceString, compilationErrorReporter); + IRFactory irf = + new IRFactory(compilerEnv, sourceName, sourceString, compilationErrorReporter); ScriptNode tree = irf.transformTree(ast); if (compilerEnv.isGeneratingSource()) { diff --git a/rhino/src/main/java/org/mozilla/javascript/IRFactory.java b/rhino/src/main/java/org/mozilla/javascript/IRFactory.java index 3902f95bf3..d114532cea 100644 --- a/rhino/src/main/java/org/mozilla/javascript/IRFactory.java +++ b/rhino/src/main/java/org/mozilla/javascript/IRFactory.java @@ -95,13 +95,24 @@ public final class IRFactory { private AstNodePosition astNodePos; public IRFactory(CompilerEnvirons env, String sourceString) { - this(env, sourceString, env.getErrorReporter()); + this(env, null, sourceString, env.getErrorReporter()); } + /** Use {@link #IRFactory(CompilerEnvirons, String, String, ErrorReporter)} */ + @Deprecated public IRFactory(CompilerEnvirons env, String sourceString, ErrorReporter errorReporter) { + this(env, null, sourceString, errorReporter); + } + + public IRFactory( + CompilerEnvirons env, + String sourceName, + String sourceString, + ErrorReporter errorReporter) { parser = new Parser(env, errorReporter); astNodePos = new AstNodePosition(sourceString); parser.currentPos = astNodePos; + parser.setSourceURI(sourceName); } /** Transforms the tree into a lower-level IR suitable for codegen. */ @@ -180,7 +191,9 @@ private Node transform(AstNode node) { case Token.NULL: case Token.DEBUGGER: return transformLiteral(node); - + case Token.SUPER: + parser.setRequiresActivation(); + return transformLiteral(node); case Token.NAME: return transformName((Name) node); case Token.NUMBER: @@ -533,6 +546,9 @@ private Node transformElementGet(ElementGet node) { if (node.type == Token.QUESTION_DOT) { getElem.putIntProp(Node.OPTIONAL_CHAINING, 1); } + if (target.getType() == Token.SUPER) { + getElem.putIntProp(Node.SUPER_PROPERTY_ACCESS, 1); + } return getElem; } @@ -660,17 +676,26 @@ private Node transformFunction(FunctionNode fn) { } private Node transformFunctionCall(FunctionCall node) { - Node call = createCallOrNew(Token.CALL, transform(node.getTarget())); - call.setLineColumnNumber(node.getLineno(), node.getColumn()); - List args = node.getArguments(); - for (int i = 0; i < args.size(); i++) { - AstNode arg = args.get(i); - call.addChildToBack(transform(arg)); - } - if (node.isOptionalCall()) { - call.putIntProp(Node.OPTIONAL_CHAINING, 1); + astNodePos.push(node); + try { + Node transformedTarget = transform(node.getTarget()); + Node call = createCallOrNew(Token.CALL, transformedTarget); + call.setLineColumnNumber(node.getLineno(), node.getColumn()); + List args = node.getArguments(); + for (int i = 0; i < args.size(); i++) { + AstNode arg = args.get(i); + call.addChildToBack(transform(arg)); + } + if (node.isOptionalCall()) { + call.putIntProp(Node.OPTIONAL_CHAINING, 1); + } + if (transformedTarget.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + call.putIntProp(Node.SUPER_PROPERTY_ACCESS, 1); + } + return call; + } finally { + astNodePos.pop(); } - return call; } private Node transformGenExpr(GeneratorExpression node) { @@ -863,6 +888,11 @@ 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) + parser.reportError("msg.super.shorthand.function"); return node; } @@ -969,8 +999,12 @@ private Node transformTemplateLiteral(TemplateLiteral node) { } private Node transformTemplateLiteralCall(TaggedTemplateLiteral node) { - Node call = createCallOrNew(Token.CALL, transform(node.getTarget())); + Node transformedTarget = transform(node.getTarget()); + Node call = createCallOrNew(Token.CALL, transformedTarget); call.setLineColumnNumber(node.getLineno(), node.getColumn()); + if (transformedTarget.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + call.putIntProp(Node.SUPER_PROPERTY_ACCESS, 1); + } TemplateLiteral templateLiteral = (TemplateLiteral) node.getTemplateLiteral(); List elems = templateLiteral.getElements(); call.addChildToBack(templateLiteral); @@ -1830,6 +1864,9 @@ private static Node createUnary(int nodeType, Node child) { // Always evaluate delete operand, see ES5 11.4.1 & bug #726121 n = new Node(nodeType, new Node(Token.TRUE), child); } + if (child.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + n.putIntProp(Node.SUPER_PROPERTY_ACCESS, 1); + } return n; } case Token.TYPEOF: @@ -1930,6 +1967,22 @@ private Node createPropertyGet( } parser.checkActivationName(name, Token.GETPROP); if (ScriptRuntime.isSpecialProperty(name)) { + if (target.getType() == Token.SUPER) { + // We have an access to super.__proto__ or super.__parent__. + // This needs to behave in the same way as this.__proto__ - it really is not + // obvious why, but you can test it in v8 or any other engine. So, we just + // replace SUPER with THIS in the AST. It's a bit hacky, but it works - see the + // test cases in SuperTest! + if (!(target instanceof KeywordLiteral)) { + throw Kit.codeBug(); + } + KeywordLiteral oldTarget = (KeywordLiteral) target; + target = + new KeywordLiteral( + oldTarget.getPosition(), oldTarget.getLength(), Token.THIS); + target.setLineColumnNumber(oldTarget.getLineno(), oldTarget.getColumn()); + } + Node ref = new Node(Token.REF_SPECIAL, target); ref.putProp(Node.NAME_PROP, name); Node getRef = new Node(Token.GET_REF, ref); @@ -1944,6 +1997,9 @@ private Node createPropertyGet( if (type == Token.QUESTION_DOT) { node.putIntProp(Node.OPTIONAL_CHAINING, 1); } + if (target.getType() == Token.SUPER) { + node.putIntProp(Node.SUPER_PROPERTY_ACCESS, 1); + } return node; } Node elem = Node.newString(name); @@ -2147,7 +2203,9 @@ private Node createAssignment(int assignType, Node left, Node right) { int assignOp; switch (assignType) { case Token.ASSIGN: - return parser.simpleAssignment(left, right); + { + return propagateSuperFromLhs(parser.simpleAssignment(left, right), left); + } case Token.ASSIGN_BITOR: assignOp = Token.BITOR; break; @@ -2203,7 +2261,7 @@ private Node createAssignment(int assignType, Node left, Node right) { { Node op = new Node(assignOp, left, right); Node lvalueLeft = Node.newString(Token.BINDNAME, left.getString()); - return new Node(Token.SETNAME, lvalueLeft, op); + return propagateSuperFromLhs(new Node(Token.SETNAME, lvalueLeft, op), left); } case Token.GETPROP: case Token.GETELEM: @@ -2215,7 +2273,7 @@ private Node createAssignment(int assignType, Node left, Node right) { Node opLeft = new Node(Token.USE_STACK); Node op = new Node(assignOp, opLeft, right); - return new Node(type, obj, id, op); + return propagateSuperFromLhs(new Node(type, obj, id, op), left); } case Token.GET_REF: { @@ -2223,13 +2281,20 @@ private Node createAssignment(int assignType, Node left, Node right) { parser.checkMutableReference(ref); Node opLeft = new Node(Token.USE_STACK); Node op = new Node(assignOp, opLeft, right); - return new Node(Token.SET_REF_OP, ref, op); + return propagateSuperFromLhs(new Node(Token.SET_REF_OP, ref, op), left); } } throw Kit.codeBug(); } + private Node propagateSuperFromLhs(Node result, Node left) { + if (left.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + result.putIntProp(Node.SUPER_PROPERTY_ACCESS, 1); + } + return result; + } + private static Node createUseLocal(Node localBlock) { if (Token.LOCAL_BLOCK != localBlock.getType()) throw Kit.codeBug(); Node result = new Node(Token.LOCAL_LOAD); diff --git a/rhino/src/main/java/org/mozilla/javascript/Icode.java b/rhino/src/main/java/org/mozilla/javascript/Icode.java index f835ae2197..f04c32e9cc 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Icode.java +++ b/rhino/src/main/java/org/mozilla/javascript/Icode.java @@ -20,8 +20,11 @@ abstract class Icode { // Stack: ... value2 value1 -> ... value2 value1 value2 value1 Icode_DUP2 = Icode_DUP - 1, + // Stack: ... value2 value1 -> ... value1 value2 + Icode_SWAP = Icode_DUP2 - 1, + // Stack: ... value1 -> ... - Icode_POP = Icode_DUP2 - 1, + Icode_POP = Icode_SWAP - 1, // Store stack top into return register and then pop it Icode_POP_RESULT = Icode_POP - 1, @@ -78,9 +81,10 @@ abstract class Icode { Icode_LITERAL_NEW_OBJECT = Icode_INTNUMBER - 1, Icode_LITERAL_NEW_ARRAY = Icode_LITERAL_NEW_OBJECT - 1, Icode_LITERAL_SET = Icode_LITERAL_NEW_ARRAY - 1, + ICode_FN_STORE_HOME_OBJECT = Icode_LITERAL_SET - 1, // Array literal with skipped index like [1,,2] - Icode_SPARE_ARRAYLIT = Icode_LITERAL_SET - 1, + Icode_SPARE_ARRAYLIT = ICode_FN_STORE_HOME_OBJECT - 1, // Load index register to prepare for the following index operation Icode_REG_IND_C0 = Icode_SPARE_ARRAYLIT - 1, @@ -151,8 +155,15 @@ abstract class Icode { // Jump if stack head is null or undefined Icode_IF_NULL_UNDEF = Icode_LITERAL_KEY_SET - 1, Icode_IF_NOT_NULL_UNDEF = Icode_IF_NULL_UNDEF - 1, + + // Call a method on the super object, i.e. super.foo() + Icode_CALL_ON_SUPER = Icode_IF_NOT_NULL_UNDEF - 1, + + // delete super.prop + Icode_DELPROP_SUPER = Icode_CALL_ON_SUPER - 1, + // Last icode - MIN_ICODE = Icode_IF_NOT_NULL_UNDEF; + MIN_ICODE = Icode_DELPROP_SUPER; static String bytecodeName(int bytecode) { if (!validBytecode(bytecode)) { @@ -174,6 +185,8 @@ static String bytecodeName(int bytecode) { return "DUP"; case Icode_DUP2: return "DUP2"; + case Icode_SWAP: + return "SWAP"; case Icode_POP: return "POP"; case Icode_POP_RESULT: @@ -240,6 +253,8 @@ static String bytecodeName(int bytecode) { return "LITERAL_NEW_ARRAY"; case Icode_LITERAL_SET: return "LITERAL_SET"; + case ICode_FN_STORE_HOME_OBJECT: + return "FN_STORE_HOME_OBJECT"; case Icode_SPARE_ARRAYLIT: return "SPARE_ARRAYLIT"; case Icode_REG_IND_C0: @@ -334,6 +349,10 @@ static String bytecodeName(int bytecode) { return "IF_NULL_UNDEF"; case Icode_IF_NOT_NULL_UNDEF: return "IF_NOT_NULL_UNDEF"; + case Icode_CALL_ON_SUPER: + return "CALL_ON_SUPER"; + case Icode_DELPROP_SUPER: + return "DELPROP_SUPER"; } // icode without name diff --git a/rhino/src/main/java/org/mozilla/javascript/Interpreter.java b/rhino/src/main/java/org/mozilla/javascript/Interpreter.java index ff9a9efc41..4e645da262 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Interpreter.java +++ b/rhino/src/main/java/org/mozilla/javascript/Interpreter.java @@ -121,7 +121,8 @@ void initializeArgs( Object[] args, double[] argsDbl, int argShift, - int argCount) { + int argCount, + Scriptable homeObject) { if (useActivation) { // Copy args to new array to pass to enterActivationFunction // or debuggerFrame.onEnter @@ -144,7 +145,8 @@ void initializeArgs( scope, args, idata.isStrict, - idata.argsHasRest); + idata.argsHasRest, + homeObject); } else { scope = ScriptRuntime.createFunctionActivation( @@ -153,7 +155,8 @@ void initializeArgs( scope, args, idata.isStrict, - idata.argsHasRest); + idata.argsHasRest, + homeObject); } } } else { @@ -609,6 +612,7 @@ static void dumpICode(InterpreterData idata) { out.println(tname + " " + idata.itsNestedFunctions[indexReg]); break; case Token.CALL: + case Icode_CALL_ON_SUPER: case Icode_TAIL_CALL: case Token.REF_CALL: case Token.NEW: @@ -1132,7 +1136,18 @@ static Object interpret( } } - CallFrame frame = initFrame(cx, scope, thisObj, args, null, 0, args.length, ifun, null); + CallFrame frame = + initFrame( + cx, + scope, + thisObj, + ifun.getHomeObject(), + args, + null, + 0, + args.length, + ifun, + null); frame.isContinuationsTopFrame = cx.isContinuationsTopCall; cx.isContinuationsTopCall = false; @@ -1524,6 +1539,16 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl sDbl[stackTop + 2] = sDbl[stackTop]; stackTop += 2; continue Loop; + case Icode_SWAP: + { + Object o = stack[stackTop]; + stack[stackTop] = stack[stackTop - 1]; + stack[stackTop - 1] = o; + double d = sDbl[stackTop]; + sDbl[stackTop] = sDbl[stackTop - 1]; + sDbl[stackTop - 1] = d; + continue Loop; + } case Token.RETURN: frame.result = stack[stackTop]; frame.resultDbl = sDbl[stackTop]; @@ -1628,6 +1653,11 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl stackTop = doDelName(cx, frame, op, stack, sDbl, stackTop); continue Loop; } + case Icode_DELPROP_SUPER: + stackTop -= 1; + stack[stackTop] = Boolean.FALSE; + ScriptRuntime.throwDeleteOnSuperPropertyNotAllowed(); + continue Loop; case Token.GETPROPNOWARN: { Object lhs = stack[stackTop]; @@ -1648,6 +1678,21 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl lhs, stringReg, cx, frame.scope); continue Loop; } + case Token.GETPROP_SUPER: + case Token.GETPROPNOWARN_SUPER: + { + Object superObject = stack[stackTop]; + if (superObject == DBL_MRK) Kit.codeBug(); + stack[stackTop] = + ScriptRuntime.getSuperProp( + superObject, + stringReg, + cx, + frame.scope, + frame.thisObj, + op == Token.GETPROPNOWARN_SUPER); + continue Loop; + } case Token.SETPROP: { Object rhs = stack[stackTop]; @@ -1662,6 +1707,24 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl lhs, stringReg, rhs, cx, frame.scope); continue Loop; } + case Token.SETPROP_SUPER: + { + Object rhs = stack[stackTop]; + if (rhs == DBL_MRK) + rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Object superObject = stack[stackTop]; + if (superObject == DBL_MRK) Kit.codeBug(); + stack[stackTop] = + ScriptRuntime.setSuperProp( + superObject, + stringReg, + rhs, + cx, + frame.scope, + frame.thisObj); + continue Loop; + } case Icode_PROP_INC_DEC: { Object lhs = stack[stackTop]; @@ -1682,11 +1745,21 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl stackTop = doGetElem(cx, frame, stack, sDbl, stackTop); continue Loop; } + case Token.GETELEM_SUPER: + { + stackTop = doGetElemSuper(cx, frame, stack, sDbl, stackTop); + continue Loop; + } case Token.SETELEM: { stackTop = doSetElem(cx, frame, stack, sDbl, stackTop); continue Loop; } + case Token.SETELEM_SUPER: + { + stackTop = doSetElemSuper(cx, frame, stack, sDbl, stackTop); + continue Loop; + } case Icode_ELEM_INC_DEC: { stackTop = @@ -1853,6 +1926,7 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl continue Loop; } case Token.CALL: + case Icode_CALL_ON_SUPER: case Icode_TAIL_CALL: case Token.REF_CALL: { @@ -1867,6 +1941,18 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl // are already Scriptable and Callable objects respectively Callable fun = (Callable) stack[stackTop]; Scriptable funThisObj = (Scriptable) stack[stackTop + 1]; + Scriptable funHomeObj = + (fun instanceof BaseFunction) + ? ((BaseFunction) fun).getHomeObject() + : null; + if (op == Icode_CALL_ON_SUPER) { + // funThisObj would have been the "super" object, which we + // used to lookup the function. Now that that's done, we + // discard it and invoke the function with the current + // "this". + funThisObj = frame.thisObj; + } + if (op == Token.REF_CALL) { Object[] outArgs = getArgsArray(stack, sDbl, stackTop + 2, indexReg); @@ -1893,6 +1979,7 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl ArrowFunction afun = (ArrowFunction) fun; fun = afun.getTargetFunction(); funThisObj = afun.getCallThis(cx); + funHomeObj = afun.getBoundHomeObject(); } else if (fun instanceof LambdaConstructor) { break; } else if (fun instanceof LambdaFunction) { @@ -2046,6 +2133,7 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl cx, calleeScope, funThisObj, + funHomeObj, stack, sDbl, stackTop + 2, @@ -2125,6 +2213,7 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl cx, frame.scope, newInstance, + newInstance, stack, sDbl, stackTop + 1, @@ -2273,6 +2362,28 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl case Token.THIS: stack[++stackTop] = frame.thisObj; continue Loop; + case Token.SUPER: + { + // See 9.1.1.3.5 GetSuperBase + + // If we are referring to "super", then we always have an + // activation + // (this is done in IrFactory). The home object is stored as + // part of the + // activation frame to propagate it correctly for nested + // functions. + Scriptable homeObject = getCurrentFrameHomeObject(frame); + if (homeObject == null) { + // This if is specified in the spec, but I cannot imagine + // how the home object will ever be null since `super` is + // legal _only_ in method definitions, where we do have a + // home object! + stack[++stackTop] = Undefined.instance; + } else { + stack[++stackTop] = homeObject.getPrototype(); + } + continue Loop; + } case Token.THISFN: stack[++stackTop] = frame.fnOrScript; continue Loop; @@ -2411,12 +2522,23 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl InterpretedFunction.createFunction( cx, frame.scope, frame.fnOrScript, indexReg); if (fn.idata.itsFunctionType == FunctionNode.ARROW_FUNCTION) { + Scriptable homeObject = getCurrentFrameHomeObject(frame); stack[++stackTop] = - new ArrowFunction(cx, frame.scope, fn, frame.thisObj); + new ArrowFunction( + cx, frame.scope, fn, frame.thisObj, homeObject); } else { stack[++stackTop] = fn; } continue Loop; + case ICode_FN_STORE_HOME_OBJECT: + { + // Stack contains: [object, keysArray, flagsArray, valuesArray, + // function] + InterpretedFunction fun = (InterpretedFunction) stack[stackTop]; + Scriptable homeObject = (Scriptable) stack[stackTop - 4]; + fun.setHomeObject(homeObject); + continue Loop; + } case Icode_CLOSURE_STMT: initFunction(cx, frame.scope, frame.fnOrScript, indexReg); continue Loop; @@ -2866,6 +2988,14 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl : ScriptRuntime.wrapNumber(interpreterResultDbl); } + private static Scriptable getCurrentFrameHomeObject(CallFrame frame) { + if (frame.scope instanceof NativeCall) { + return ((NativeCall) frame.scope).getHomeObject(); + } else { + return null; + } + } + private static int doInOrInstanceof( Context cx, int op, Object[] stack, double[] sDbl, int stackTop) { Object rhs = stack[stackTop]; @@ -2988,6 +3118,23 @@ private static int doGetElem( return stackTop; } + private static int doGetElemSuper( + Context cx, CallFrame frame, Object[] stack, double[] sDbl, int stackTop) { + --stackTop; + Object superObject = stack[stackTop]; + if (superObject == DOUBLE_MARK) Kit.codeBug(); + Object value; + Object id = stack[stackTop + 1]; + if (id != DOUBLE_MARK) { + value = ScriptRuntime.getSuperElem(superObject, id, cx, frame.scope, frame.thisObj); + } else { + double d = sDbl[stackTop + 1]; + value = ScriptRuntime.getSuperIndex(superObject, d, cx, frame.scope, frame.thisObj); + } + stack[stackTop] = value; + return stackTop; + } + private static int doSetElem( Context cx, CallFrame frame, Object[] stack, double[] sDbl, int stackTop) { stackTop -= 2; @@ -3011,6 +3158,31 @@ private static int doSetElem( return stackTop; } + private static int doSetElemSuper( + Context cx, CallFrame frame, Object[] stack, double[] sDbl, int stackTop) { + stackTop -= 2; + Object rhs = stack[stackTop + 2]; + if (rhs == DOUBLE_MARK) { + rhs = ScriptRuntime.wrapNumber(sDbl[stackTop + 2]); + } + Object superObject = stack[stackTop]; + if (superObject == DOUBLE_MARK) Kit.codeBug(); + Object value; + Object id = stack[stackTop + 1]; + if (id != DOUBLE_MARK) { + value = + ScriptRuntime.setSuperElem( + superObject, id, rhs, cx, frame.scope, frame.thisObj); + } else { + double d = sDbl[stackTop + 1]; + value = + ScriptRuntime.setSuperIndex( + superObject, d, rhs, cx, frame.scope, frame.thisObj); + } + stack[stackTop] = value; + return stackTop; + } + private static int doElemIncDec( Context cx, CallFrame frame, @@ -3487,6 +3659,7 @@ private static CallFrame initFrame( Context cx, Scriptable callerScope, Scriptable thisObj, + Scriptable homeObj, Object[] args, double[] argsDbl, int argShift, @@ -3494,7 +3667,7 @@ private static CallFrame initFrame( InterpretedFunction fnOrScript, CallFrame parentFrame) { CallFrame frame = new CallFrame(cx, thisObj, fnOrScript, parentFrame); - frame.initializeArgs(cx, callerScope, args, argsDbl, argShift, argCount); + frame.initializeArgs(cx, callerScope, args, argsDbl, argShift, argCount, homeObj); enterFrame(cx, frame, args, false); return frame; } @@ -3582,7 +3755,7 @@ private static void exitFrame(Context cx, CallFrame frame, Object throwable) { } private static void setCallResult(CallFrame frame, Object callResult, double callResultDbl) { - if (frame.savedCallOp == Token.CALL) { + if (frame.savedCallOp == Token.CALL || frame.savedCallOp == Icode_CALL_ON_SUPER) { frame.stack[frame.savedStackTop] = callResult; frame.sDbl[frame.savedStackTop] = callResultDbl; } else if (frame.savedCallOp == Token.NEW) { @@ -3621,7 +3794,7 @@ private static NativeContinuation captureContinuation( x.stack[i] = null; x.stackAttributes[i] = ScriptableObject.EMPTY; } - if (x.savedCallOp == Token.CALL) { + if (x.savedCallOp == Token.CALL || x.savedCallOp == Icode_CALL_ON_SUPER) { // the call will always overwrite the stack top with the result x.stack[x.savedStackTop] = null; } else { diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeCall.java b/rhino/src/main/java/org/mozilla/javascript/NativeCall.java index d8474761c3..2b1f0f1a06 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeCall.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeCall.java @@ -33,8 +33,11 @@ static void init(Scriptable scope, boolean sealed) { Object[] args, boolean isArrow, boolean isStrict, - boolean argsHasRest) { + boolean argsHasRest, + Scriptable homeObject) { this.function = function; + this.homeObject = homeObject; + this.isArrow = isArrow; setParentScope(scope); // leave prototype null @@ -143,12 +146,18 @@ public void defineAttributesForArguments() { } } + public Scriptable getHomeObject() { + return homeObject; + } + private static final int Id_constructor = 1, MAX_PROTOTYPE_ID = 1; NativeFunction function; Object[] originalArgs; boolean isStrict; private Arguments arguments; + boolean isArrow; + private Scriptable homeObject; transient NativeCall parentActivationCall; } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeScript.java b/rhino/src/main/java/org/mozilla/javascript/NativeScript.java index 1084010290..17e6a26d14 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeScript.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeScript.java @@ -162,7 +162,7 @@ private static Script compile(Context cx, String source) { } ErrorReporter reporter; reporter = DefaultErrorReporter.forEval(cx.getErrorReporter()); - return cx.compileString(source, null, reporter, filename, linep[0], null); + return cx.compileString(source, null, reporter, filename, linep[0], null, null); } @Override diff --git a/rhino/src/main/java/org/mozilla/javascript/Node.java b/rhino/src/main/java/org/mozilla/javascript/Node.java index 928426fef5..0b3f7ca4fc 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Node.java +++ b/rhino/src/main/java/org/mozilla/javascript/Node.java @@ -67,7 +67,8 @@ public class Node implements Iterable { TRAILING_COMMA = 28, OBJECT_LITERAL_DESTRUCTURING = 29, OPTIONAL_CHAINING = 30, - LAST_PROP = OPTIONAL_CHAINING; + SUPER_PROPERTY_ACCESS = 31, + LAST_PROP = SUPER_PROPERTY_ACCESS; // values of ISNUMBER_PROP to specify // which of the children are Number types @@ -438,6 +439,8 @@ private static final String propToString(int propType) { return "trailing comma"; case OPTIONAL_CHAINING: return "optional_chaining"; + case SUPER_PROPERTY_ACCESS: + return "super_property_access"; default: Kit.codeBug(); diff --git a/rhino/src/main/java/org/mozilla/javascript/Parser.java b/rhino/src/main/java/org/mozilla/javascript/Parser.java index 2a1fe44277..9fddf6bff5 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Parser.java +++ b/rhino/src/main/java/org/mozilla/javascript/Parser.java @@ -133,6 +133,7 @@ public class Parser { private Comment currentJsDocComment; protected int nestingOfFunction; + protected int nestingOfFunctionParams; private LabeledStatement currentLabel; private boolean inDestructuringAssignment; protected boolean inUseStrictDirective; @@ -140,6 +141,7 @@ public class Parser { // The following are per function variables and should be saved/restored // during function parsing. See PerFunctionVariables class below. ScriptNode currentScriptOrFn; + private boolean insideMethod; Scope currentScope; private int endFlags; private boolean inForInit; // bound temporarily during forStatement() @@ -479,10 +481,14 @@ public boolean eof() { return ts.eof(); } - boolean insideFunction() { + boolean insideFunctionBody() { return nestingOfFunction != 0; } + boolean insideFunctionParams() { + return nestingOfFunctionParams != 0; + } + void pushScope(Scope scope) { Scope parent = scope.getParentScope(); // During codegen, parent scope chain may already be initialized, @@ -773,152 +779,163 @@ private static String getDirective(AstNode n) { } private void parseFunctionParams(FunctionNode fnNode) throws IOException { - if (matchToken(Token.RP, true)) { - fnNode.setRp(ts.tokenBeg - fnNode.getPosition()); - return; - } - // Would prefer not to call createDestructuringAssignment until codegen, - // but the symbol definitions have to happen now, before body is parsed. - Map destructuring = null; - Map destructuringDefault = null; - - Set paramNames = new HashSet<>(); - do { - int tt = peekToken(); - if (tt == Token.RP) { - if (fnNode.hasRestParameter()) { - // Error: parameter after rest parameter - reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); - } - - fnNode.putIntProp(Node.TRAILING_COMMA, 1); - break; + ++nestingOfFunctionParams; + try { + if (matchToken(Token.RP, true)) { + fnNode.setRp(ts.tokenBeg - fnNode.getPosition()); + return; } - if (tt == Token.LB || tt == Token.LC) { - if (fnNode.hasRestParameter()) { - // Error: parameter after rest parameter - reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); - } - - AstNode expr = destructuringAssignExpr(); - if (destructuring == null) { - destructuring = new HashMap<>(); - } + // Would prefer not to call createDestructuringAssignment until codegen, + // but the symbol definitions have to happen now, before body is parsed. + Map destructuring = null; + Map destructuringDefault = null; - if (expr instanceof Assignment) { - // We have default arguments inside destructured function parameters - // eg: f([x = 1] = [2]) { ... }, transform this into: - // f(x) { - // if ($1 == undefined) - // var $1 = [2]; - // if (x == undefined) - // if (($1[0]) == undefined) - // var x = 1; - // else - // var x = $1[0]; - // } - // fnNode.addParam(name) - AstNode lhs = ((Assignment) expr).getLeft(); // [x = 1] - AstNode rhs = ((Assignment) expr).getRight(); // [2] - markDestructuring(lhs); - fnNode.addParam(lhs); - String pname = currentScriptOrFn.getNextTempName(); - defineSymbol(Token.LP, pname, false); - if (destructuringDefault == null) { - destructuringDefault = new HashMap<>(); - } - destructuring.put(pname, lhs); - destructuringDefault.put(pname, rhs); - } else { - markDestructuring(expr); - fnNode.addParam(expr); - // Destructuring assignment for parameters: add a dummy - // parameter name, and add a statement to the body to initialize - // variables from the destructuring assignment - String pname = currentScriptOrFn.getNextTempName(); - defineSymbol(Token.LP, pname, false); - destructuring.put(pname, expr); - } - } else { - boolean wasRest = false; - int restStartLineno = -1, restStartColumn = -1; - if (tt == Token.DOTDOTDOT) { + Set paramNames = new HashSet<>(); + do { + int tt = peekToken(); + if (tt == Token.RP) { if (fnNode.hasRestParameter()) { // Error: parameter after rest parameter reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); } - fnNode.setHasRestParameter(true); - wasRest = true; - consumeToken(); - restStartLineno = lineNumber(); - restStartColumn = columnNumber(); + fnNode.putIntProp(Node.TRAILING_COMMA, 1); + break; } - - if (mustMatchToken(Token.NAME, "msg.no.parm", true)) { - if (!wasRest && fnNode.hasRestParameter()) { + if (tt == Token.LB || tt == Token.LC) { + if (fnNode.hasRestParameter()) { // Error: parameter after rest parameter reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); } - Name paramNameNode = createNameNode(); - if (wasRest) { - paramNameNode.setLineColumnNumber(restStartLineno, restStartColumn); + AstNode expr = destructuringAssignExpr(); + if (destructuring == null) { + destructuring = new HashMap<>(); } - Comment jsdocNodeForName = getAndResetJsDoc(); - if (jsdocNodeForName != null) { - paramNameNode.setJsDocNode(jsdocNodeForName); + + if (expr instanceof Assignment) { + // We have default arguments inside destructured function parameters + // eg: f([x = 1] = [2]) { ... }, transform this into: + // f(x) { + // if ($1 == undefined) + // var $1 = [2]; + // if (x == undefined) + // if (($1[0]) == undefined) + // var x = 1; + // else + // var x = $1[0]; + // } + // fnNode.addParam(name) + AstNode lhs = ((Assignment) expr).getLeft(); // [x = 1] + AstNode rhs = ((Assignment) expr).getRight(); // [2] + markDestructuring(lhs); + fnNode.addParam(lhs); + String pname = currentScriptOrFn.getNextTempName(); + defineSymbol(Token.LP, pname, false); + if (destructuringDefault == null) { + destructuringDefault = new HashMap<>(); + } + destructuring.put(pname, lhs); + destructuringDefault.put(pname, rhs); + } else { + markDestructuring(expr); + fnNode.addParam(expr); + // Destructuring assignment for parameters: add a dummy + // parameter name, and add a statement to the body to initialize + // variables from the destructuring assignment + String pname = currentScriptOrFn.getNextTempName(); + defineSymbol(Token.LP, pname, false); + destructuring.put(pname, expr); } - fnNode.addParam(paramNameNode); - String paramName = ts.getString(); - defineSymbol(Token.LP, paramName); - if (this.inUseStrictDirective) { - if ("eval".equals(paramName) || "arguments".equals(paramName)) { - reportError("msg.bad.id.strict", paramName); + } else { + boolean wasRest = false; + int restStartLineno = -1, restStartColumn = -1; + if (tt == Token.DOTDOTDOT) { + if (fnNode.hasRestParameter()) { + // Error: parameter after rest parameter + reportError( + "msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); } - if (paramNames.contains(paramName)) - addError("msg.dup.param.strict", paramName); - paramNames.add(paramName); + + fnNode.setHasRestParameter(true); + wasRest = true; + consumeToken(); + restStartLineno = lineNumber(); + restStartColumn = columnNumber(); } - if (matchToken(Token.ASSIGN, true)) { - if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { - fnNode.putDefaultParams(paramName, assignExpr()); - } else { - reportError("msg.default.args"); + if (mustMatchToken(Token.NAME, "msg.no.parm", true)) { + if (!wasRest && fnNode.hasRestParameter()) { + // Error: parameter after rest parameter + reportError( + "msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); + } + + Name paramNameNode = createNameNode(); + if (wasRest) { + paramNameNode.setLineColumnNumber(restStartLineno, restStartColumn); + } + Comment jsdocNodeForName = getAndResetJsDoc(); + if (jsdocNodeForName != null) { + paramNameNode.setJsDocNode(jsdocNodeForName); } + fnNode.addParam(paramNameNode); + String paramName = ts.getString(); + defineSymbol(Token.LP, paramName); + if (this.inUseStrictDirective) { + if ("eval".equals(paramName) || "arguments".equals(paramName)) { + reportError("msg.bad.id.strict", paramName); + } + if (paramNames.contains(paramName)) + addError("msg.dup.param.strict", paramName); + paramNames.add(paramName); + } + + if (matchToken(Token.ASSIGN, true)) { + if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { + fnNode.putDefaultParams(paramName, assignExpr()); + } else { + reportError("msg.default.args"); + } + } + } else { + fnNode.addParam(makeErrorNode()); } - } else { - fnNode.addParam(makeErrorNode()); } - } - } while (matchToken(Token.COMMA, true)); - - if (destructuring != null) { - Node destructuringNode = new Node(Token.COMMA); - // Add assignment helper for each destructuring parameter - for (Map.Entry param : destructuring.entrySet()) { - AstNode defaultValue = null; - if (destructuringDefault != null) { - defaultValue = destructuringDefault.get(param.getKey()); + } while (matchToken(Token.COMMA, true)); + + if (destructuring != null) { + Node destructuringNode = new Node(Token.COMMA); + // Add assignment helper for each destructuring parameter + for (Map.Entry param : destructuring.entrySet()) { + AstNode defaultValue = null; + if (destructuringDefault != null) { + defaultValue = destructuringDefault.get(param.getKey()); + } + Node assign = + createDestructuringAssignment( + Token.VAR, + param.getValue(), + createName(param.getKey()), + defaultValue); + destructuringNode.addChildToBack(assign); } - Node assign = - createDestructuringAssignment( - Token.VAR, - param.getValue(), - createName(param.getKey()), - defaultValue); - destructuringNode.addChildToBack(assign); + fnNode.putProp(Node.DESTRUCTURING_PARAMS, destructuringNode); } - fnNode.putProp(Node.DESTRUCTURING_PARAMS, destructuringNode); - } - if (mustMatchToken(Token.RP, "msg.no.paren.after.parms", true)) { - fnNode.setRp(ts.tokenBeg - fnNode.getPosition()); + if (mustMatchToken(Token.RP, "msg.no.paren.after.parms", true)) { + fnNode.setRp(ts.tokenBeg - fnNode.getPosition()); + } + } finally { + --nestingOfFunctionParams; } } private FunctionNode function(int type) throws IOException { + return function(type, false); + } + + private FunctionNode function(int type, boolean isMethodDefiniton) throws IOException { boolean isGenerator = false; int syntheticType = type; int baseLineno = lineNumber(); // line number where source starts @@ -976,6 +993,7 @@ private FunctionNode function(int type) throws IOException { } FunctionNode fnNode = new FunctionNode(functionSourceStart, name); + fnNode.setMethodDefinition(isMethodDefiniton); fnNode.setFunctionType(type); if (isGenerator) { fnNode.setIsES6Generator(); @@ -985,6 +1003,8 @@ private FunctionNode function(int type) throws IOException { fnNode.setJsDocNode(getAndResetJsDoc()); PerFunctionVariables savedVars = new PerFunctionVariables(fnNode); + boolean wasInsideMethod = insideMethod; + insideMethod = isMethodDefiniton; try { parseFunctionParams(fnNode); AstNode body = parseFunctionBody(type, fnNode); @@ -1002,6 +1022,7 @@ private FunctionNode function(int type) throws IOException { } } finally { savedVars.restore(); + insideMethod = wasInsideMethod; } if (memberExprNode != null) { @@ -1050,6 +1071,8 @@ private AstNode arrowFunction(AstNode params, int startLine, int startColumn) Set paramNames = new HashSet<>(); PerFunctionVariables savedVars = new PerFunctionVariables(fnNode); + // Intentionally not overwriting "insideMethod" - we want to propagate this from the parent + // function or scope try { if (params instanceof ParenthesizedExpression) { fnNode.setParens(0, params.getLength()); @@ -1384,7 +1407,7 @@ private AstNode statementHelper() throws IOException { // We have not consumed any token yet, so the position would be invalid lineno = ts.getLineno(); column = ts.getTokenColumn(); - pn = new ExpressionStatement(expr(false), !insideFunction()); + pn = new ExpressionStatement(expr(false), !insideFunctionBody()); pn.setLineColumnNumber(lineno, column); break; } @@ -2049,7 +2072,7 @@ private static final boolean nowAllSet(int before, int after, int mask) { } private AstNode returnOrYield(int tt, boolean exprContext) throws IOException { - if (!insideFunction()) { + if (!insideFunctionBody()) { reportError(tt == Token.RETURN ? "msg.bad.return" : "msg.bad.yield"); } consumeToken(); @@ -2096,7 +2119,7 @@ private AstNode returnOrYield(int tt, boolean exprContext) throws IOException { if (nowAllSet(before, endFlags, Node.END_RETURNS | Node.END_RETURNS_VALUE)) addStrictWarning("msg.return.inconsistent", "", pos, end - pos); } else { - if (!insideFunction()) reportError("msg.bad.yield"); + if (!insideFunctionBody()) reportError("msg.bad.yield"); endFlags |= Node.END_YIELDS; ret = new Yield(pos, end - pos, e, yieldStar); setRequiresActivation(); @@ -2108,7 +2131,7 @@ private AstNode returnOrYield(int tt, boolean exprContext) throws IOException { } // see if we are mixing yields and value returns. - if (insideFunction() + if (insideFunctionBody() && nowAllSet(before, endFlags, Node.END_YIELDS | Node.END_RETURNS_VALUE)) { FunctionNode fn = (FunctionNode) currentScriptOrFn; if (!fn.isES6Generator()) { @@ -2204,7 +2227,7 @@ private AstNode nameOrLabel() throws IOException { AstNode expr = expr(false); if (expr.getType() != Token.LABEL) { - AstNode n = new ExpressionStatement(expr, !insideFunction()); + AstNode n = new ExpressionStatement(expr, !insideFunctionBody()); n.setLineColumnNumber(expr.getLineno(), expr.getColumn()); return n; } @@ -2218,7 +2241,7 @@ private AstNode nameOrLabel() throws IOException { currentFlaggedToken |= TI_CHECK_LABEL; expr = expr(false); if (expr.getType() != Token.LABEL) { - stmt = new ExpressionStatement(expr, !insideFunction()); + stmt = new ExpressionStatement(expr, !insideFunctionBody()); autoInsertSemicolon(stmt); break; } @@ -2367,7 +2390,7 @@ private AstNode let(boolean isStatement, int pos) throws IOException { pn.setBody(expr); if (isStatement) { // let expression in statement context - ExpressionStatement es = new ExpressionStatement(pn, !insideFunction()); + ExpressionStatement es = new ExpressionStatement(pn, !insideFunctionBody()); es.setLineColumnNumber(pn.getLineno(), pn.getColumn()); return es; } @@ -3050,6 +3073,11 @@ private AstNode taggedTemplateLiteral(AstNode pn) throws IOException { */ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) throws IOException { if (pn == null) codeBug(); + if (pn.getType() == Token.SUPER && isOptionalChain) { + reportError("msg.optional.super"); + return makeErrorNode(); + } + int memberTypeFlags = 0, lineno = lineNumber(), dotPos = ts.tokenBeg, @@ -3364,12 +3392,28 @@ private AstNode primaryExpr() throws IOException { case Token.THIS: case Token.FALSE: case Token.TRUE: - consumeToken(); - pos = ts.tokenBeg; - end = ts.tokenEnd; - KeywordLiteral keywordLiteral = new KeywordLiteral(pos, end - pos, tt); - keywordLiteral.setLineColumnNumber(lineNumber(), columnNumber()); - return keywordLiteral; + { + consumeToken(); + pos = ts.tokenBeg; + end = ts.tokenEnd; + KeywordLiteral keywordLiteral = new KeywordLiteral(pos, end - pos, tt); + keywordLiteral.setLineColumnNumber(lineNumber(), columnNumber()); + return keywordLiteral; + } + + case Token.SUPER: + if (((insideFunctionParams() || insideFunctionBody()) && insideMethod) + || compilerEnv.isAllowSuper()) { + consumeToken(); + pos = ts.tokenBeg; + end = ts.tokenEnd; + KeywordLiteral keywordLiteral = new KeywordLiteral(pos, end - pos, tt); + keywordLiteral.setLineColumnNumber(lineNumber(), columnNumber()); + return keywordLiteral; + } else { + reportError("msg.super.shorthand.function"); + } + break; case Token.TEMPLATE_LITERAL: consumeToken(); @@ -3795,6 +3839,7 @@ private ObjectLiteral objectLiteral() throws IOException { propertyName = null; } else { propertyName = ts.getString(); + // short-hand method definition ObjectProperty objectProp = methodDefinition(ppos, pname, entryKind); pname.setJsDocNode(jsdocNode); elems.add(objectProp); @@ -3944,7 +3989,7 @@ private ObjectProperty plainProperty(AstNode property, int ptt) throws IOExcepti private ObjectProperty methodDefinition(int pos, AstNode propName, int entryKind) throws IOException { - FunctionNode fn = function(FunctionNode.FUNCTION_EXPRESSION); + FunctionNode fn = function(FunctionNode.FUNCTION_EXPRESSION, true); // We've already parsed the function name, so fn should be anonymous. Name name = fn.getFunctionName(); if (name != null && name.length() != 0) { @@ -4090,7 +4135,7 @@ private AstNode createNumericLiteral(int tt, boolean isProperty) { } protected void checkActivationName(String name, int token) { - if (!insideFunction()) { + if (!insideFunctionBody()) { return; } boolean activation = false; @@ -4115,7 +4160,7 @@ protected void checkActivationName(String name, int token) { } protected void setRequiresActivation() { - if (insideFunction()) { + if (insideFunctionBody()) { ((FunctionNode) currentScriptOrFn).setRequiresActivation(); } } @@ -4128,7 +4173,7 @@ private void checkCallRequiresActivation(AstNode pn) { } protected void setIsGenerator() { - if (insideFunction()) { + if (insideFunctionBody()) { ((FunctionNode) currentScriptOrFn).setIsGenerator(); } } @@ -4759,6 +4804,10 @@ public void reportErrorsIfExists(int baseLineno) { } } + public void setSourceURI(String sourceURI) { + this.sourceURI = sourceURI; + } + public interface CurrentPositionReporter { public int getPosition(); diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java index 2fe205e15c..37efd760f0 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java @@ -18,6 +18,7 @@ import java.util.Optional; import java.util.ResourceBundle; import java.util.function.BiConsumer; +import java.util.function.Consumer; import org.mozilla.javascript.ast.FunctionNode; import org.mozilla.javascript.v8dtoa.DoubleConversion; import org.mozilla.javascript.v8dtoa.FastDtoa; @@ -1722,15 +1723,11 @@ public static Object getObjectElem(Object obj, Object elem, Context cx) { /** Call obj.[[Get]](id) */ public static Object getObjectElem(Object obj, Object elem, Context cx, Scriptable scope) { - Scriptable sobj = toObjectOrNull(cx, obj, scope); - if (sobj == null) { - throw undefReadError(obj, elem); - } + Scriptable sobj = asScriptableOrThrowUndefReadError(cx, scope, obj, elem); return getObjectElem(sobj, elem, cx); } public static Object getObjectElem(Scriptable obj, Object elem, Context cx) { - Object result; if (obj instanceof XMLObject) { @@ -1750,7 +1747,40 @@ public static Object getObjectElem(Scriptable obj, Object elem, Context cx) { if (result == Scriptable.NOT_FOUND) { result = Undefined.instance; } + return result; + } + + public static Object getSuperElem( + Object superObject, Object elem, Context cx, Scriptable scope, Object thisObject) { + Scriptable superScriptable = + asScriptableOrThrowUndefReadError(cx, scope, superObject, elem); + Scriptable thisScriptable = asScriptableOrThrowUndefReadError(cx, scope, thisObject, elem); + return getSuperElem(elem, superScriptable, thisScriptable); + } + + public static Object getSuperElem( + Object elem, Scriptable superScriptable, Scriptable thisScriptable) { + Object result; + // No XML support for super + if (isSymbol(elem)) { + result = + ScriptableObject.getSuperProperty( + superScriptable, thisScriptable, (Symbol) elem); + } else { + StringIdOrIndex s = toStringIdOrIndex(elem); + if (s.stringId == null) { + int index = s.index; + result = ScriptableObject.getSuperProperty(superScriptable, thisScriptable, index); + } else { + result = + ScriptableObject.getSuperProperty( + superScriptable, thisScriptable, s.stringId); + } + } + if (result == Scriptable.NOT_FOUND) { + result = Undefined.instance; + } return result; } @@ -1770,15 +1800,11 @@ public static Object getObjectProp(Object obj, String property, Context cx) { * @param scope the scope that should be used to resolve primitive prototype */ public static Object getObjectProp(Object obj, String property, Context cx, Scriptable scope) { - Scriptable sobj = toObjectOrNull(cx, obj, scope); - if (sobj == null) { - throw undefReadError(obj, property); - } + Scriptable sobj = asScriptableOrThrowUndefReadError(cx, scope, obj, property); return getObjectProp(sobj, property, cx); } public static Object getObjectProp(Scriptable obj, String property, Context cx) { - Object result = ScriptableObject.getProperty(obj, property); if (result == Scriptable.NOT_FOUND) { if (cx.hasFeature(Context.FEATURE_STRICT_MODE)) { @@ -1801,10 +1827,7 @@ public static Object getObjectPropNoWarn(Object obj, String property, Context cx public static Object getObjectPropNoWarn( Object obj, String property, Context cx, Scriptable scope) { - Scriptable sobj = toObjectOrNull(cx, obj, scope); - if (sobj == null) { - throw undefReadError(obj, property); - } + Scriptable sobj = asScriptableOrThrowUndefReadError(cx, scope, obj, property); Object result = ScriptableObject.getProperty(sobj, property); if (result == Scriptable.NOT_FOUND) { return Undefined.instance; @@ -1812,6 +1835,42 @@ public static Object getObjectPropNoWarn( return result; } + public static Object getSuperProp( + Object superObject, + String property, + Context cx, + Scriptable scope, + Object thisObject, + boolean noWarn) { + Scriptable superScriptable = + asScriptableOrThrowUndefReadError(cx, scope, superObject, property); + Scriptable thisScriptable = + asScriptableOrThrowUndefReadError(cx, scope, thisObject, property); + return getSuperProp(superScriptable, thisScriptable, property, cx, noWarn); + } + + private static Object getSuperProp( + Scriptable superScriptable, + Scriptable thisScriptable, + String property, + Context cx, + boolean noWarn) { + Object result = + ScriptableObject.getSuperProperty(superScriptable, thisScriptable, property); + + if (result == Scriptable.NOT_FOUND) { + if (noWarn) { + return Undefined.instance; + } + if (cx.hasFeature(Context.FEATURE_STRICT_MODE)) { + Context.reportWarning( + ScriptRuntime.getMessageById("msg.ref.undefined.prop", property)); + } + return Undefined.instance; + } + return result; + } + /** * A cheaper and less general version of the above for well-known argument types. * @@ -1824,10 +1883,7 @@ public static Object getObjectIndex(Object obj, double dblIndex, Context cx) { /** A cheaper and less general version of the above for well-known argument types. */ public static Object getObjectIndex(Object obj, double dblIndex, Context cx, Scriptable scope) { - Scriptable sobj = toObjectOrNull(cx, obj, scope); - if (sobj == null) { - throw undefReadError(obj, toString(dblIndex)); - } + Scriptable sobj = asScriptableOrThrowUndefReadError(cx, scope, obj, dblIndex); int index = (int) dblIndex; if (index == dblIndex && index >= 0) { @@ -1842,7 +1898,31 @@ public static Object getObjectIndex(Scriptable obj, int index, Context cx) { if (result == Scriptable.NOT_FOUND) { result = Undefined.instance; } + return result; + } + + public static Object getSuperIndex( + Object superObject, double dblIndex, Context cx, Scriptable scope, Object thisObject) { + Scriptable superScriptable = + asScriptableOrThrowUndefReadError(cx, scope, superObject, dblIndex); + Scriptable thisScriptable = + asScriptableOrThrowUndefReadError(cx, scope, thisObject, dblIndex); + + int index = (int) dblIndex; + if (index == dblIndex && index >= 0) { + return getSuperIndex(superScriptable, thisScriptable, index); + } + String s = toString(dblIndex); + return getSuperProp(superScriptable, thisScriptable, s, cx, false); + } + + private static Object getSuperIndex( + Scriptable superScriptable, Scriptable thisScriptable, int index) { + Object result = ScriptableObject.getSuperProperty(superScriptable, thisScriptable, index); + if (result == Scriptable.NOT_FOUND) { + return Undefined.instance; + } return result; } @@ -1859,10 +1939,7 @@ public static Object setObjectElem(Object obj, Object elem, Object value, Contex /** Call obj.[[Put]](id, value) */ public static Object setObjectElem( Object obj, Object elem, Object value, Context cx, Scriptable scope) { - Scriptable sobj = toObjectOrNull(cx, obj, scope); - if (sobj == null) { - throw undefWriteError(obj, elem, value); - } + Scriptable sobj = asScriptableOrThrowUndefWriteError(cx, scope, obj, elem, value); return setObjectElem(sobj, elem, value, cx); } @@ -1883,6 +1960,43 @@ public static Object setObjectElem(Scriptable obj, Object elem, Object value, Co return value; } + /** Call super.[[Put]](id, value) */ + public static Object setSuperElem( + Object superObject, + Object elem, + Object value, + Context cx, + Scriptable scope, + Object thisObject) { + Scriptable superScriptable = + asScriptableOrThrowUndefWriteError(cx, scope, superObject, elem, value); + Scriptable thisScriptable = + asScriptableOrThrowUndefWriteError(cx, scope, thisObject, elem, value); + return setSuperElem(superScriptable, thisScriptable, elem, value, cx); + } + + public static Object setSuperElem( + Scriptable superScriptable, + Scriptable thisScriptable, + Object elem, + Object value, + Context cx) { + // No XML support for super + if (isSymbol(elem)) { + ScriptableObject.putSuperProperty( + superScriptable, thisScriptable, (Symbol) elem, value); + } else { + StringIdOrIndex s = toStringIdOrIndex(elem); + if (s.stringId == null) { + ScriptableObject.putSuperProperty(superScriptable, thisScriptable, s.index, value); + } else { + ScriptableObject.putSuperProperty( + superScriptable, thisScriptable, s.stringId, value); + } + } + return value; + } + /** * Version of setObjectElem when elem is a valid JS identifier name. * @@ -1896,17 +2010,8 @@ public static Object setObjectProp(Object obj, String property, Object value, Co /** Version of setObjectElem when elem is a valid JS identifier name. */ public static Object setObjectProp( Object obj, String property, Object value, Context cx, Scriptable scope) { - if (!(obj instanceof Scriptable) - && cx.isStrictMode() - && cx.getLanguageVersion() >= Context.VERSION_1_8) { - throw undefWriteError(obj, property, value); - } - - Scriptable sobj = toObjectOrNull(cx, obj, scope); - if (sobj == null) { - throw undefWriteError(obj, property, value); - } - + verifyIsScriptableOrComplainWriteErrorInEs5Strict(obj, property, value, cx); + Scriptable sobj = asScriptableOrThrowUndefWriteError(cx, scope, obj, property, value); return setObjectProp(sobj, property, value, cx); } @@ -1915,6 +2020,35 @@ public static Object setObjectProp(Scriptable obj, String property, Object value return value; } + /** Version of setSuperElem when elem is a valid JS identifier name. */ + public static Object setSuperProp( + Object superObject, + String property, + Object value, + Context cx, + Scriptable scope, + Object thisObject) { + verifyIsScriptableOrComplainWriteErrorInEs5Strict(superObject, property, value, cx); + verifyIsScriptableOrComplainWriteErrorInEs5Strict(thisObject, property, value, cx); + + Scriptable superScriptable = + asScriptableOrThrowUndefWriteError(cx, scope, superObject, property, value); + Scriptable thisScriptable = + asScriptableOrThrowUndefWriteError(cx, scope, thisObject, property, value); + + return setSuperProp(superScriptable, thisScriptable, property, value, cx); + } + + public static Object setSuperProp( + Scriptable superScriptable, + Scriptable thisScriptable, + String property, + Object value, + Context cx) { + ScriptableObject.putSuperProperty(superScriptable, thisScriptable, property, value); + return value; + } + /** * A cheaper and less general version of the above for well-known argument types. * @@ -1928,11 +2062,7 @@ public static Object setObjectIndex(Object obj, double dblIndex, Object value, C /** A cheaper and less general version of the above for well-known argument types. */ public static Object setObjectIndex( Object obj, double dblIndex, Object value, Context cx, Scriptable scope) { - Scriptable sobj = toObjectOrNull(cx, obj, scope); - if (sobj == null) { - throw undefWriteError(obj, String.valueOf(dblIndex), value); - } - + Scriptable sobj = asScriptableOrThrowUndefWriteError(cx, scope, obj, dblIndex, value); int index = (int) dblIndex; if (index == dblIndex && index >= 0) { return setObjectIndex(sobj, index, value, cx); @@ -1946,6 +2076,37 @@ public static Object setObjectIndex(Scriptable obj, int index, Object value, Con return value; } + /** A cheaper and less general version of the above for well-known argument types. */ + public static Object setSuperIndex( + Object superObject, + double dblIndex, + Object value, + Context cx, + Scriptable scope, + Object thisObject) { + Scriptable superScriptable = + asScriptableOrThrowUndefWriteError(cx, scope, superObject, dblIndex, value); + Scriptable thisScriptable = + asScriptableOrThrowUndefWriteError(cx, scope, thisObject, dblIndex, value); + + int index = (int) dblIndex; + if (index == dblIndex && index >= 0) { + return setSuperIndex(superScriptable, thisScriptable, index, value, cx); + } + String s = toString(dblIndex); + return setSuperProp(superScriptable, thisScriptable, s, value, cx); + } + + public static Object setSuperIndex( + Scriptable superScriptable, + Scriptable thisScriptable, + int index, + Object value, + Context cx) { + ScriptableObject.putSuperProperty(superScriptable, thisScriptable, index, value); + return value; + } + public static boolean deleteObjectElem(Scriptable target, Object elem, Context cx) { if (isSymbol(elem)) { SymbolScriptable so = ScriptableObject.ensureSymbolScriptable(target); @@ -3069,7 +3230,25 @@ public static Object evalSpecial( // Compile with explicit interpreter instance to force interpreter // mode. - Script script = cx.compileString(x.toString(), evaluator, reporter, sourceName, 1, null); + Consumer compilerEnvironsProcessor = + compilerEnvs -> { + // If we are inside a method, we need to allow super. Methods have the home + // object set and propagated via the activation (i.e. the NativeCall), + // but non-methods will have the home object set to null. + boolean isInsideMethod = + scope instanceof NativeCall + && ((NativeCall) scope).getHomeObject() != null; + compilerEnvs.setAllowSuper(isInsideMethod); + }; + Script script = + cx.compileString( + x.toString(), + evaluator, + reporter, + sourceName, + 1, + null, + compilerEnvironsProcessor); evaluator.setEvalScriptFlag(script); Callable c = (Callable) script; Scriptable thisObject = @@ -3456,10 +3635,7 @@ public static Object propIncrDecr(Object obj, String id, Context cx, int incrDec public static Object propIncrDecr( Object obj, String id, Context cx, Scriptable scope, int incrDecrMask) { - Scriptable start = toObjectOrNull(cx, obj, scope); - if (start == null) { - throw undefReadError(obj, id); - } + Scriptable start = asScriptableOrThrowUndefReadError(cx, scope, obj, id); Scriptable target = start; Object value; @@ -4363,24 +4539,24 @@ public static void initScript( /** * @deprecated Use {@link #createFunctionActivation(NativeFunction, Context, Scriptable, - * Object[], boolean, boolean)} instead + * Object[], boolean, boolean, Scriptable)} instead */ @Deprecated public static Scriptable createFunctionActivation( NativeFunction funObj, Scriptable scope, Object[] args) { return createFunctionActivation( - funObj, Context.getCurrentContext(), scope, args, false, false); + funObj, Context.getCurrentContext(), scope, args, false, false, null); } /** * @deprecated Use {@link #createFunctionActivation(NativeFunction, Context, Scriptable, - * Object[], boolean, boolean)} instead + * Object[], boolean, boolean, Scriptable)} instead */ @Deprecated public static Scriptable createFunctionActivation( NativeFunction funObj, Scriptable scope, Object[] args, boolean isStrict) { return new NativeCall( - funObj, Context.getCurrentContext(), scope, args, false, isStrict, false); + funObj, Context.getCurrentContext(), scope, args, false, isStrict, false, null); } public static Scriptable createFunctionActivation( @@ -4389,19 +4565,20 @@ public static Scriptable createFunctionActivation( Scriptable scope, Object[] args, boolean isStrict, - boolean argsHasRest) { - return new NativeCall(funObj, cx, scope, args, false, isStrict, argsHasRest); + boolean argsHasRest, + Scriptable homeObject) { + return new NativeCall(funObj, cx, scope, args, false, isStrict, argsHasRest, homeObject); } /** * @deprecated Use {@link #createArrowFunctionActivation(NativeFunction, Context, Scriptable, - * Object[], boolean, boolean)} instead + * Object[], boolean, boolean, Scriptable)} instead */ @Deprecated public static Scriptable createArrowFunctionActivation( NativeFunction funObj, Scriptable scope, Object[] args, boolean isStrict) { return new NativeCall( - funObj, Context.getCurrentContext(), scope, args, true, isStrict, false); + funObj, Context.getCurrentContext(), scope, args, true, isStrict, false, null); } public static Scriptable createArrowFunctionActivation( @@ -4410,8 +4587,9 @@ public static Scriptable createArrowFunctionActivation( Scriptable scope, Object[] args, boolean isStrict, - boolean argsHasRest) { - return new NativeCall(funObj, cx, scope, args, true, isStrict, argsHasRest); + boolean argsHasRest, + Scriptable homeObject) { + return new NativeCall(funObj, cx, scope, args, true, isStrict, argsHasRest, homeObject); } public static void enterActivationFunction(Context cx, Scriptable scope) { @@ -5112,6 +5290,33 @@ public static EcmaError typeError3(String messageId, String arg1, String arg2, S return typeError(msg); } + private static Scriptable asScriptableOrThrowUndefReadError( + Context cx, Scriptable scope, Object obj, Object elem) { + Scriptable scriptable = toObjectOrNull(cx, obj, scope); + if (scriptable == null) { + throw undefReadError(obj, elem); + } + return scriptable; + } + + private static Scriptable asScriptableOrThrowUndefWriteError( + Context cx, Scriptable scope, Object obj, Object elem, Object value) { + Scriptable scriptable = toObjectOrNull(cx, obj, scope); + if (scriptable == null) { + throw undefWriteError(obj, elem, value); + } + return scriptable; + } + + private static void verifyIsScriptableOrComplainWriteErrorInEs5Strict( + Object obj, String property, Object value, Context cx) { + if (!(obj instanceof Scriptable) + && cx.isStrictMode() + && cx.getLanguageVersion() >= Context.VERSION_1_8) { + throw undefWriteError(obj, property, value); + } + } + public static RuntimeException undefReadError(Object object, Object id) { return typeErrorById("msg.undef.prop.read", toString(object), toString(id)); } @@ -5178,6 +5383,10 @@ public static EcmaError syntaxErrorById(String messageId, Object... args) { return syntaxError(msg); } + public static EcmaError referenceError(String message) { + return constructError("ReferenceError", message); + } + private static void warnAboutNonJSObject(Object nonJSObject) { final String omitParam = ScriptRuntime.getMessageById("params.omit.non.js.object.warning"); if (!"true".equals(omitParam)) { @@ -5333,6 +5542,11 @@ public static Scriptable lastStoredScriptable(Context cx) { return result; } + public static void discardLastStoredScriptable(Context cx) { + if (cx.scratchScriptable == null) throw new IllegalStateException(); + cx.scratchScriptable = null; + } + static String makeUrlForGeneratedScript( boolean isEval, String masterScriptUrl, int masterScriptLine) { if (isEval) { @@ -5401,6 +5615,11 @@ public static JavaScriptException throwCustomError( return new JavaScriptException(error, filename, linep[0]); } + /** Throws a ReferenceError "cannot delete a super property". See ECMAScript spec 13.5.1.2 */ + public static void throwDeleteOnSuperPropertyNotAllowed() { + throw referenceError("msg.delete.super"); + } + public static final Object[] emptyArgs = new Object[0]; public static final String[] emptyStrings = new String[0]; } diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java b/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java index 8fc9cd5eca..3b630dd273 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java @@ -2126,7 +2126,19 @@ private void checkNotSealed(Object key, int index) { * @since 1.5R2 */ public static Object getProperty(Scriptable obj, String name) { - Scriptable start = obj; + return getPropWalkingPrototypeChain(obj, name, obj); + } + + /** + * Gets a named property from super, walking the super's prototype chain, but passing the + * correct "this" to getter slots. + */ + public static Object getSuperProperty(Scriptable superObj, Scriptable thisObj, String name) { + return getPropWalkingPrototypeChain(superObj, name, thisObj); + } + + private static Object getPropWalkingPrototypeChain( + Scriptable obj, String name, Scriptable start) { Object result; do { result = obj.get(name, start); @@ -2138,7 +2150,16 @@ public static Object getProperty(Scriptable obj, String name) { /** This is a version of getProperty that works with Symbols. */ public static Object getProperty(Scriptable obj, Symbol key) { - Scriptable start = obj; + return getPropWalkingPrototypeChain(obj, obj, key); + } + + /** This is a version of getSuperProperty that works with Symbols. */ + public static Object getSuperProperty(Scriptable superObj, Scriptable thisObj, Symbol key) { + return getPropWalkingPrototypeChain(superObj, thisObj, key); + } + + private static Object getPropWalkingPrototypeChain( + Scriptable obj, Scriptable start, Symbol key) { Object result; do { result = ensureSymbolScriptable(obj).get(key, start); @@ -2190,7 +2211,19 @@ public static T getTypedProperty(Scriptable s, int index, Class type) { * @since 1.5R2 */ public static Object getProperty(Scriptable obj, int index) { - Scriptable start = obj; + return getPropWalkingPrototypeChain(obj, index, obj); + } + + /** + * Gets an indexed property from super, walking the super's prototype chain, but passing the + * correct "this" to getter slots. + */ + public static Object getSuperProperty(Scriptable superObj, Scriptable thisObj, int index) { + return getPropWalkingPrototypeChain(superObj, index, thisObj); + } + + private static Object getPropWalkingPrototypeChain( + Scriptable obj, int index, Scriptable start) { Object result; do { result = obj.get(index, start); @@ -2303,6 +2336,12 @@ public static void putProperty(Scriptable obj, String name, Object value) { base.put(name, obj, value); } + /** Variant of putProperty to handle super.name = value */ + public static void putSuperProperty( + Scriptable superObj, Scriptable thisObj, String name, Object value) { + superObj.put(name, thisObj, value); + } + /** This is a version of putProperty for Symbol keys. */ public static void putProperty(Scriptable obj, Symbol key, Object value) { Scriptable base = getBase(obj, key); @@ -2310,6 +2349,12 @@ public static void putProperty(Scriptable obj, Symbol key, Object value) { ensureSymbolScriptable(base).put(key, obj, value); } + /** Variant of putProperty to handle super[key] = value where key is a symbol */ + public static void putSuperProperty( + Scriptable superObj, Scriptable thisObj, Symbol key, Object value) { + ensureSymbolScriptable(superObj).put(key, thisObj, value); + } + /** * Puts a named property in an object or in an object in its prototype chain. * @@ -2352,6 +2397,12 @@ public static void putProperty(Scriptable obj, int index, Object value) { base.put(index, obj, value); } + /** Variant of putProperty to handle super[index] = value where index is integer */ + public static void putSuperProperty( + Scriptable superObj, Scriptable thisObj, int index, Object value) { + superObj.put(index, thisObj, value); + } + /** * Removes the property from an object or its prototype chain. * diff --git a/rhino/src/main/java/org/mozilla/javascript/Token.java b/rhino/src/main/java/org/mozilla/javascript/Token.java index 80bbc58384..2fac729e63 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Token.java +++ b/rhino/src/main/java/org/mozilla/javascript/Token.java @@ -71,10 +71,15 @@ public static enum CommentType { TYPEOF = DELPROP + 1, GETPROP = TYPEOF + 1, GETPROPNOWARN = GETPROP + 1, - SETPROP = GETPROPNOWARN + 1, - GETELEM = SETPROP + 1, - SETELEM = GETELEM + 1, - CALL = SETELEM + 1, + GETPROP_SUPER = GETPROPNOWARN + 1, + GETPROPNOWARN_SUPER = GETPROP_SUPER + 1, + SETPROP = GETPROPNOWARN_SUPER + 1, + SETPROP_SUPER = SETPROP + 1, + GETELEM = SETPROP_SUPER + 1, + GETELEM_SUPER = GETELEM + 1, + SETELEM = GETELEM_SUPER + 1, + SETELEM_SUPER = SETELEM + 1, + CALL = SETELEM_SUPER + 1, NAME = CALL + 1, NUMBER = NAME + 1, STRING = NUMBER + 1, @@ -110,7 +115,8 @@ public static enum CommentType { REF_CALL = DEL_REF + 1, // f(args) = something or f(args)++ REF_SPECIAL = REF_CALL + 1, // reference for special properties like __proto YIELD = REF_SPECIAL + 1, // JS 1.7 yield pseudo keyword - STRICT_SETNAME = YIELD + 1, + SUPER = YIELD + 1, // ES6 super keyword + STRICT_SETNAME = SUPER + 1, EXP = STRICT_SETNAME + 1, // Exponentiation Operator // For XML support: @@ -331,12 +337,22 @@ public static String typeToName(int token) { return "GETPROP"; case GETPROPNOWARN: return "GETPROPNOWARN"; + case GETPROP_SUPER: + return "GETPROP_SUPER"; + case GETPROPNOWARN_SUPER: + return "GETPROPNOWARN_SUPER"; case SETPROP: return "SETPROP"; + case SETPROP_SUPER: + return "SETPROP_SUPER"; case GETELEM: return "GETELEM"; + case GETELEM_SUPER: + return "GETELEM_SUPER"; case SETELEM: return "SETELEM"; + case SETELEM_SUPER: + return "SETELEM_SUPER"; case CALL: return "CALL"; case NAME: @@ -581,6 +597,8 @@ public static String typeToName(int token) { return "LET"; case YIELD: return "YIELD"; + case SUPER: + return "SUPER"; case EXP: return "EXP"; case CONST: @@ -686,6 +704,8 @@ public static String keywordToName(int token) { return "with"; case Token.YIELD: return "yield"; + case Token.SUPER: + return "super"; case Token.CATCH: return "catch"; case Token.CONST: diff --git a/rhino/src/main/java/org/mozilla/javascript/TokenStream.java b/rhino/src/main/java/org/mozilla/javascript/TokenStream.java index f133ac8f31..58d3bc359a 100644 --- a/rhino/src/main/java/org/mozilla/javascript/TokenStream.java +++ b/rhino/src/main/java/org/mozilla/javascript/TokenStream.java @@ -379,7 +379,7 @@ private static int stringToKeywordForES(String name, boolean isStrict) { Id_instanceof = Token.INSTANCEOF, Id_new = Token.NEW, Id_return = Token.RETURN, - Id_super = Token.RESERVED, + Id_super = Token.SUPER, Id_switch = Token.SWITCH, Id_this = Token.THIS, Id_throw = Token.THROW, 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 e760f2cbc6..975a3d0aba 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/KeywordLiteral.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/KeywordLiteral.java @@ -9,9 +9,10 @@ import org.mozilla.javascript.Token; /** - * AST node for keyword literals: currently, {@code this}, {@code null}, {@code true}, {@code - * false}, and {@code debugger}. Node type is one of {@link Token#THIS}, {@link Token#NULL}, {@link - * Token#TRUE}, {@link Token#FALSE}, or {@link Token#DEBUGGER}. + * AST node for keyword literals: currently, {@code this}, {@code super}, {@code null}, {@code + * true}, {@code false}, and {@code debugger}. Node type is one of {@link Token#THIS}, {@link + * Token#SUPER}, {@link Token#NULL}, {@link Token#TRUE}, {@link Token#FALSE}, or {@link + * Token#DEBUGGER}. */ public class KeywordLiteral extends AstNode { @@ -43,6 +44,7 @@ public KeywordLiteral(int pos, int len, int nodeType) { @Override public KeywordLiteral setType(int nodeType) { if (!(nodeType == Token.THIS + || nodeType == Token.SUPER || nodeType == Token.NULL || nodeType == Token.TRUE || nodeType == Token.FALSE @@ -65,6 +67,9 @@ public String toSource(int depth) { case Token.THIS: sb.append("this"); break; + case Token.SUPER: + sb.append("super"); + break; case Token.NULL: sb.append("null"); break; diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ScriptNode.java b/rhino/src/main/java/org/mozilla/javascript/ast/ScriptNode.java index bd23c869dc..bf18a32544 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ScriptNode.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ScriptNode.java @@ -38,6 +38,7 @@ public class ScriptNode extends Scope { private Object compilerData; private int tempNumber = 0; private boolean inStrictMode; + private boolean isMethodDefinition; { // during parsing, a ScriptNode or FunctionNode's top scope is itself @@ -329,6 +330,14 @@ public boolean isInStrictMode() { return inStrictMode; } + public boolean isMethodDefinition() { + return isMethodDefinition; + } + + public void setMethodDefinition(boolean methodDefinition) { + isMethodDefinition = methodDefinition; + } + @Override public void visit(NodeVisitor v) { if (v.visit(this)) { diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/BodyCodegen.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/BodyCodegen.java index b872eca764..447c7f43ec 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/BodyCodegen.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/BodyCodegen.java @@ -120,6 +120,12 @@ private void generateGenerator() { cfw.addALoad(argsLocal); cfw.addPush(scriptOrFn.isInStrictMode()); cfw.addPush(scriptOrFn.hasRestParameter()); + cfw.addLoadThis(); + cfw.addInvoke( + ByteCode.INVOKEVIRTUAL, + "org/mozilla/javascript/BaseFunction", + "getHomeObject", + "()Lorg/mozilla/javascript/Scriptable;"); addScriptRuntimeInvoke( "createFunctionActivation", "(Lorg/mozilla/javascript/NativeFunction;" @@ -128,6 +134,7 @@ private void generateGenerator() { + "[Ljava/lang/Object;" + "Z" + "Z" + + "Lorg/mozilla/javascript/Scriptable;" + ")Lorg/mozilla/javascript/Scriptable;"); cfw.addAStore(variableObjectLocal); @@ -207,6 +214,7 @@ private void initBodyGeneration() { epilogueLabel = -1; enterAreaStartLabel = -1; generatorStateLocal = -1; + savedHomeObjectLocal = -1; } /** Generate the prologue for a function or script. */ @@ -424,6 +432,39 @@ private void generatePrologue() { cfw.addALoad(argsLocal); cfw.addPush(scriptOrFn.isInStrictMode()); cfw.addPush(scriptOrFn.hasRestParameter()); + + if (!isArrow) { + // Just pass the home object of the function + cfw.addLoadThis(); + cfw.addInvoke( + ByteCode.INVOKEVIRTUAL, + "org/mozilla/javascript/BaseFunction", + "getHomeObject", + "()Lorg/mozilla/javascript/Scriptable;"); + } else { + // Propagate the home object from the activation scope (i.e. the NativeCall) + int putNull = cfw.acquireLabel(); + int after = cfw.acquireLabel(); + + cfw.addALoad(variableObjectLocal); + cfw.add(ByteCode.INSTANCEOF, "org/mozilla/javascript/NativeCall"); + cfw.add(ByteCode.IFEQ, putNull); + + cfw.addALoad(variableObjectLocal); + cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/NativeCall"); + cfw.addInvoke( + ByteCode.INVOKEVIRTUAL, + "org/mozilla/javascript/NativeCall", + "getHomeObject", + "()Lorg/mozilla/javascript/Scriptable;"); + cfw.add(ByteCode.GOTO, after); + + cfw.markLabel(putNull); + cfw.add(ByteCode.ACONST_NULL); + + cfw.markLabel(after); + } + String methodName = isArrow ? "createArrowFunctionActivation" : "createFunctionActivation"; addScriptRuntimeInvoke( @@ -434,6 +475,7 @@ private void generatePrologue() { + "[Ljava/lang/Object;" + "Z" + "Z" + + "Lorg/mozilla/javascript/Scriptable;" + ")Lorg/mozilla/javascript/Scriptable;"); cfw.addAStore(variableObjectLocal); cfw.addALoad(contextLocal); @@ -1064,6 +1106,46 @@ private void generateExpression(Node node, Node parent) { cfw.addALoad(thisObjLocal); break; + case Token.SUPER: + { + // See 9.1.1.3.5 GetSuperBase + + // Read home object from activation, which we know we'll always have because we + // set "requiresActivation" to any function that mentions "super" in IRFactory + cfw.addALoad(variableObjectLocal); + cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/NativeCall"); + cfw.addInvoke( + ByteCode.INVOKEVIRTUAL, + "org/mozilla/javascript/NativeCall", + "getHomeObject", + "()Lorg/mozilla/javascript/Scriptable;"); + + // If null, then put undefined + int after = cfw.acquireLabel(); + int putPrototype = cfw.acquireLabel(); + cfw.add(ByteCode.DUP); + cfw.add(ByteCode.IFNONNULL, putPrototype); + + cfw.add(ByteCode.POP); + cfw.add( + ByteCode.GETSTATIC, + "org/mozilla/javascript/Undefined", + "instance", + "Ljava/lang/Object;"); + cfw.add(ByteCode.GOTO, after); + + // Otherwise put the prototype + cfw.markLabel(putPrototype); + cfw.addInvoke( + ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getPrototype", + "()Lorg/mozilla/javascript/Scriptable;"); + + cfw.markLabel(after); + break; + } + case Token.THISFN: cfw.add(ByteCode.ALOAD_0); break; @@ -1473,14 +1555,27 @@ private void generateExpression(Node node, Node parent) { generateExpression(child, node); child = child.getNext(); generateExpression(child, node); - cfw.addALoad(contextLocal); - cfw.addPush(isName); - addScriptRuntimeInvoke( - "delete", - "(Ljava/lang/Object;" - + "Ljava/lang/Object;" - + "Lorg/mozilla/javascript/Context;" - + "Z)Ljava/lang/Object;"); + if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + // We have pushed `super` and the expression, but we need to remove them because + // we actually are just going to throw an error. However, delete is supposed to + // put a boolean on the stack and the class file writer would complain if we + // don't have only popped here. So we pop and the push 0 (false). Anyway, this + // is code that will always fail, so honestly no one will ever write something + // like this (delete super[foo]), so... even if this is not the most efficient + // bytecode, it's fine. + cfw.add(ByteCode.POP2); + cfw.addLoadConstant(0); + addScriptRuntimeInvoke("throwDeleteOnSuperPropertyNotAllowed", "()V"); + } else { + cfw.addALoad(contextLocal); + cfw.addPush(isName); + addScriptRuntimeInvoke( + "delete", + "(Ljava/lang/Object;" + + "Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Context;" + + "Z)Ljava/lang/Object;"); + } break; case Token.BINDNAME: @@ -1675,10 +1770,20 @@ private void finishGetElemGeneration(Node node, Node child) { generateExpression(child.getNext(), node); // id cfw.addALoad(contextLocal); cfw.addALoad(variableObjectLocal); - if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { - addDynamicInvoke("PROP:GETINDEX", Signatures.PROP_GET_INDEX); + + if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + cfw.addALoad(thisObjLocal); + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + addDynamicInvoke("PROP:GETINDEXSUPER", Signatures.PROP_GET_INDEX_SUPER); + } else { + addDynamicInvoke("PROP:GETELEMENTSUPER", Signatures.PROP_GET_ELEMENT_SUPER); + } } else { - addDynamicInvoke("PROP:GETELEMENT", Signatures.PROP_GET_ELEMENT); + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + addDynamicInvoke("PROP:GETINDEX", Signatures.PROP_GET_INDEX); + } else { + addDynamicInvoke("PROP:GETELEMENT", Signatures.PROP_GET_ELEMENT); + } } } @@ -1956,6 +2061,16 @@ private void visitFunction(OptFunctionNode ofn, int functionType) { + ")Lorg/mozilla/javascript/Function;"); } + if (ofn.fnode.isMethodDefinition()) { + cfw.add(ByteCode.DUP); + cfw.addALoad(savedHomeObjectLocal); + cfw.addInvoke( + ByteCode.INVOKEVIRTUAL, + "org/mozilla/javascript/BaseFunction", + "setHomeObject", + "(Lorg/mozilla/javascript/Scriptable;)V"); + } + if (functionType == FunctionNode.FUNCTION_EXPRESSION || functionType == FunctionNode.ARROW_FUNCTION) { // Leave closure object on stack and do not pass it to @@ -2054,10 +2169,7 @@ private void generateObjectLiteralFactory(Node node, int count) { } private void visitArrayLiteral(Node node, Node child, boolean topLevel) { - int count = 0; - for (Node cursor = child; cursor != null; cursor = cursor.getNext()) { - ++count; - } + int count = countArguments(child); // If code budget is tight swap out literals into separate method if (!topLevel @@ -2274,6 +2386,9 @@ private void visitObjectLiteral(Node node, Node child, boolean topLevel) { "newObject", "(Lorg/mozilla/javascript/Scriptable;)Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.DUP); + savedHomeObjectLocal = getNewWordLocal(); + cfw.addAStore(savedHomeObjectLocal); + cfw.add(ByteCode.DUP); addLoadProperty(node, child, properties, count); @@ -2393,7 +2508,43 @@ private void visitStandardCall(Node node, Node child) { String signature; Integer afterLabel = null; - if (firstArgChild == null) { + if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + // Just one general case, non optimized depending on the number of arguments + + int argCount = countArguments(firstArgChild); + generateFunctionAndThisObj(child, node); + + // stack: ... functionObj, superObj is stored on scratch last scriptable + // We discard the last scriptable, and then push the thisObj. So we have resolved the + // function on the super object, but we invoke it with the current this. + cfw.addALoad(contextLocal); + cfw.addInvoke( + ByteCode.INVOKESTATIC, + "org/mozilla/javascript/ScriptRuntime", + "discardLastStoredScriptable", + "(Lorg/mozilla/javascript/Context;)V"); + cfw.addALoad(thisObjLocal); + + if (argCount == 0) { + methodName = "call0"; + signature = SIGNATURE_CALL0; + } else if (argCount == 1) { + generateExpression(firstArgChild, node); + methodName = "call1"; + signature = SIGNATURE_CALL1; + } else { + if (argCount == 2) { + generateExpression(firstArgChild, node); + generateExpression(firstArgChild.getNext(), node); + methodName = "call2"; + signature = SIGNATURE_CALL2; + } else { + generateCallArgArray(node, firstArgChild, false); + methodName = "callN"; + signature = SIGNATURE_CALLN; + } + } + } else if (firstArgChild == null) { if (childType == Token.NAME) { // name() call String name = child.getString(); @@ -2424,12 +2575,7 @@ private void visitStandardCall(Node node, Node child) { generateFunctionAndThisObj(child, node); pushThisFromLastScriptable(); methodName = isOptionalChainingCall ? "call0Optional" : "call0"; - signature = - "(Lorg/mozilla/javascript/Callable;" - + "Lorg/mozilla/javascript/Scriptable;" - + "Lorg/mozilla/javascript/Context;" - + "Lorg/mozilla/javascript/Scriptable;" - + ")Ljava/lang/Object;"; + signature = SIGNATURE_CALL0; } } else if (childType == Token.NAME) { @@ -2466,13 +2612,7 @@ private void visitStandardCall(Node node, Node child) { pushThisFromLastScriptable(); methodName = "callN"; generateCallArgArray(node, firstArgChild, false); - signature = - "(Lorg/mozilla/javascript/Callable;" - + "Lorg/mozilla/javascript/Scriptable;" - + "[Ljava/lang/Object;" - + "Lorg/mozilla/javascript/Context;" - + "Lorg/mozilla/javascript/Scriptable;" - + ")Ljava/lang/Object;"; + signature = SIGNATURE_CALLN; } else { generateCallArgArray(node, firstArgChild, false); cfw.addPush(name); @@ -2485,12 +2625,9 @@ private void visitStandardCall(Node node, Node child) { + ")Ljava/lang/Object;"; } } else { - int argCount = 0; - for (Node arg = firstArgChild; arg != null; arg = arg.getNext()) { - ++argCount; - } + int argCount = countArguments(firstArgChild); generateFunctionAndThisObj(child, node); - // stack: ... functionObj thisObj + // stack: ... functionObj, thisObj is stored on scratch last scriptable if (isOptionalChainingCall) { // jump to afterLabel is name is not null and not undefined @@ -2517,36 +2654,17 @@ private void visitStandardCall(Node node, Node child) { if (argCount == 1) { generateExpression(firstArgChild, node); methodName = "call1"; - signature = - "(Lorg/mozilla/javascript/Callable;" - + "Lorg/mozilla/javascript/Scriptable;" - + "Ljava/lang/Object;" - + "Lorg/mozilla/javascript/Context;" - + "Lorg/mozilla/javascript/Scriptable;" - + ")Ljava/lang/Object;"; + signature = SIGNATURE_CALL1; } else { if (argCount == 2) { generateExpression(firstArgChild, node); generateExpression(firstArgChild.getNext(), node); methodName = "call2"; - signature = - "(Lorg/mozilla/javascript/Callable;" - + "Lorg/mozilla/javascript/Scriptable;" - + "Ljava/lang/Object;" - + "Ljava/lang/Object;" - + "Lorg/mozilla/javascript/Context;" - + "Lorg/mozilla/javascript/Scriptable;" - + ")Ljava/lang/Object;"; + signature = SIGNATURE_CALL2; } else { generateCallArgArray(node, firstArgChild, false); methodName = "callN"; - signature = - "(Lorg/mozilla/javascript/Callable;" - + "Lorg/mozilla/javascript/Scriptable;" - + "[Ljava/lang/Object;" - + "Lorg/mozilla/javascript/Context;" - + "Lorg/mozilla/javascript/Scriptable;" - + ")Ljava/lang/Object;"; + signature = SIGNATURE_CALLN; } } } @@ -2559,6 +2677,14 @@ private void visitStandardCall(Node node, Node child) { } } + private static int countArguments(Node firstArgChild) { + int argCount = 0; + for (Node arg = firstArgChild; arg != null; arg = arg.getNext()) { + ++argCount; + } + return argCount; + } + private void pushThisFromLastScriptable() { // Get thisObj prepared by get(Name|Prop|Elem|Value)FunctionAndThis cfw.addALoad(contextLocal); @@ -2588,6 +2714,10 @@ private void visitStandardNew(Node node, Node child) { } private void visitOptimizedCall(Node node, OptFunctionNode target, int type, Node child) { + // Only calls to top-level functions (i.e. non member functions) can be optimized, so they + // cannot be a super.foo() call + if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) Kit.codeBug(); + Node firstArgChild = child.getNext(); String className = codegen.mainClassName; @@ -2701,10 +2831,7 @@ private void visitOptimizedCall(Node node, OptFunctionNode target, int type, Nod } private void generateCallArgArray(Node node, Node argChild, boolean directCall) { - int argCount = 0; - for (Node child = argChild; child != null; child = child.getNext()) { - ++argCount; - } + int argCount = countArguments(argChild); // load array object to set arguments if (argCount == 1 && itsOneArgArray >= 0) { cfw.addALoad(itsOneArgArray); @@ -3450,6 +3577,12 @@ private void addInstructionCount(int count) { private void visitIncDec(Node node) { int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP); Node child = node.getFirstChild(); + + if (child.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + visitSuperIncDec(node, child, incrDecrMask); + return; + } + switch (child.getType()) { case Token.GETVAR: if (!hasVarsInRegs) Kit.codeBug(); @@ -3614,6 +3747,84 @@ private void visitIncDec(Node node) { } } + // Handles super.x++ and variants thereof. We don't want to create new icode in the interpreter + // for this edge case, so we will transform this into something like super.x = super.x + 1 + private void visitSuperIncDec(Node node, Node child, int incrDecrMask) { + Node object = child.getFirstChild(); + + // Push the old value on the stack + generateExpression(object, node); // [super] + cfw.add(ByteCode.DUP); // [super, super] + switch (child.getType()) { + case Token.GETPROP: + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(thisObjLocal); + cfw.addLoadConstant(0); // no_warn flag + addDynamicInvoke( + "PROP:GETSUPER:" + child.getFirstChild().getNext().getString(), + Signatures.PROP_GET_SUPER); + break; + + case Token.GETELEM: + generateExpression(object.getNext(), node); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(thisObjLocal); + addDynamicInvoke("PROP:GETELEMENTSUPER", Signatures.PROP_GET_ELEMENT_SUPER); + break; + + default: + Codegen.badTree(); + } + + // stack: [super, p] + if ((incrDecrMask & Node.POST_FLAG) != 0) { + // For postfix we want a copy of the old value on the stack + cfw.add(ByteCode.SWAP); // [p, super] + cfw.add(ByteCode.DUP2); // [p, super, p, super] + cfw.add(ByteCode.POP); // [p, super, p] + } + + // Increment or decrement the value + addObjectToDouble(); // Unbox + cfw.addPush(1.0); + if ((incrDecrMask & Node.DECR_FLAG) == 0) { + cfw.add(ByteCode.DADD); + } else { + cfw.add(ByteCode.DSUB); + } + addDoubleWrap(); // Box back + + // Assign the new value to the property + // Now stack is for prefix: [super, p+-1] and for postfix: [p, super, p+-1] + switch (child.getType()) { + case Token.GETPROP: + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(thisObjLocal); + addDynamicInvoke( + "PROP:SETSUPER:" + child.getFirstChild().getNext().getString(), + Signatures.PROP_SET_SUPER); + break; + + case Token.GETELEM: + generateExpression(object.getNext(), node); // [..., super, p+-1, elem] + cfw.add(ByteCode.SWAP); // [..., super, elem, p+-1] + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(thisObjLocal); + addDynamicInvoke("PROP:SETELEMENTSUPER", Signatures.PROP_SET_ELEMENT_SUPER); + break; + } + // Now stack is for prefix: [p+-1] and for postfix: [p, p+-1] + + // If it was a postfix, just drop the new value + if ((incrDecrMask & Node.POST_FLAG) != 0) { + cfw.add(ByteCode.POP); + } + } + private static boolean isArithmeticNode(Node node) { int type = node.getType(); return (type == Token.SUB) @@ -4176,19 +4387,22 @@ private void visitGetProp(Node node, Node child) { cfw.add(ByteCode.GOTO, after); cfw.markLabel(getExpr); - finishGetPropGeneration(node, child); + finishGetPropGeneration(node, child.getNext()); cfw.markLabel(after); } else { - finishGetPropGeneration(node, child); + finishGetPropGeneration(node, child.getNext()); } } - private void finishGetPropGeneration(Node node, Node child) { - Node nameChild = child.getNext(); + private void finishGetPropGeneration(Node node, Node nameChild) { cfw.addALoad(contextLocal); cfw.addALoad(variableObjectLocal); - if (node.getType() == Token.GETPROPNOWARN) { + if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + cfw.addALoad(thisObjLocal); + cfw.addLoadConstant(node.getType() == Token.GETPROPNOWARN ? 1 : 0); + addDynamicInvoke("PROP:GETSUPER:" + nameChild.getString(), Signatures.PROP_GET_SUPER); + } else if (node.getType() == Token.GETPROPNOWARN) { addDynamicInvoke("PROP:GETNOWARN:" + nameChild.getString(), Signatures.PROP_GET_NOWARN); } else { addDynamicInvoke("PROP:GET:" + nameChild.getString(), Signatures.PROP_GET); @@ -4201,15 +4415,18 @@ private void visitSetProp(int type, Node node, Node child) { Node nameChild = child; if (type == Token.SETPROP_OP) { cfw.add(ByteCode.DUP); - cfw.addALoad(contextLocal); - cfw.addALoad(variableObjectLocal); - addDynamicInvoke("PROP:GET:" + nameChild.getString(), Signatures.PROP_GET); + finishGetPropGeneration(node, nameChild); } child = child.getNext(); generateExpression(child, node); cfw.addALoad(contextLocal); cfw.addALoad(variableObjectLocal); - addDynamicInvoke("PROP:SET:" + nameChild.getString(), Signatures.PROP_SET); + if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + cfw.addALoad(thisObjLocal); + addDynamicInvoke("PROP:SETSUPER:" + nameChild.getString(), Signatures.PROP_SET_SUPER); + } else { + addDynamicInvoke("PROP:SET:" + nameChild.getString(), Signatures.PROP_SET); + } } private void visitSetElem(int type, Node node, Node child) { @@ -4221,6 +4438,7 @@ private void visitSetElem(int type, Node node, Node child) { generateExpression(child, node); child = child.getNext(); boolean indexIsNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1); + boolean isSuper = node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1; if (type == Token.SETELEM_OP) { if (indexIsNumber) { // stack: ... object object number @@ -4228,23 +4446,42 @@ private void visitSetElem(int type, Node node, Node child) { cfw.add(ByteCode.DUP2_X1); cfw.addALoad(contextLocal); cfw.addALoad(variableObjectLocal); - addDynamicInvoke("PROP:GETINDEX", Signatures.PROP_GET_INDEX); + if (isSuper) { + cfw.addALoad(thisObjLocal); + addDynamicInvoke("PROP:GETINDEXSUPER", Signatures.PROP_GET_INDEX_SUPER); + } else { + addDynamicInvoke("PROP:GETINDEX", Signatures.PROP_GET_INDEX); + } } else { // stack: ... object object indexObject // -> ... object indexObject object indexObject cfw.add(ByteCode.DUP_X1); cfw.addALoad(contextLocal); cfw.addALoad(variableObjectLocal); - addDynamicInvoke("PROP:GETELEMENT", Signatures.PROP_GET_ELEMENT); + if (isSuper) { + cfw.addALoad(thisObjLocal); + addDynamicInvoke("PROP:GETELEMENTSUPER", Signatures.PROP_GET_ELEMENT_SUPER); + } else { + addDynamicInvoke("PROP:GETELEMENT", Signatures.PROP_GET_ELEMENT); + } } } generateExpression(child, node); cfw.addALoad(contextLocal); cfw.addALoad(variableObjectLocal); - if (indexIsNumber) { - addDynamicInvoke("PROP:SETINDEX", Signatures.PROP_SET_INDEX); + if (isSuper) { + cfw.addALoad(thisObjLocal); + if (indexIsNumber) { + addDynamicInvoke("PROP:SETINDEXSUPER", Signatures.PROP_SET_INDEX_SUPER); + } else { + addDynamicInvoke("PROP:SETELEMENTSUPER", Signatures.PROP_SET_ELEMENT_SUPER); + } } else { - addDynamicInvoke("PROP:SETELEMENT", Signatures.PROP_SET_ELEMENT); + if (indexIsNumber) { + addDynamicInvoke("PROP:SETINDEX", Signatures.PROP_SET_INDEX); + } else { + addDynamicInvoke("PROP:SETELEMENT", Signatures.PROP_SET_ELEMENT); + } } } @@ -4460,6 +4697,35 @@ private void releaseWordLocal(int local) { locals[local] = 0; } + private static final String SIGNATURE_CALL1 = + "(Lorg/mozilla/javascript/Callable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + ")Ljava/lang/Object;"; + private static final String SIGNATURE_CALL2 = + "(Lorg/mozilla/javascript/Callable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/Object;" + + "Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + ")Ljava/lang/Object;"; + private static final String SIGNATURE_CALLN = + "(Lorg/mozilla/javascript/Callable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + ")Ljava/lang/Object;"; + private static final String SIGNATURE_CALL0 = + "(Lorg/mozilla/javascript/Callable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + ")Ljava/lang/Object;"; + static final int GENERATOR_TERMINATE = -1; static final int GENERATOR_START = 0; static final int GENERATOR_YIELD_START = 1; @@ -4500,6 +4766,7 @@ private void releaseWordLocal(int local) { private int itsZeroArgArray; private int itsOneArgArray; private int generatorStateLocal; + private int savedHomeObjectLocal; private boolean isGenerator; private int generatorSwitch; diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/Bootstrapper.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/Bootstrapper.java index 4d0fdfc7b5..f34dc4d996 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/Bootstrapper.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/Bootstrapper.java @@ -72,6 +72,11 @@ private static Operation parseOperation(String name) throws NoSuchMethodExceptio return RhinoOperation.GETNOWARN .withNamespace(StandardNamespace.PROPERTY) .named(getNameSegment(tokens, name, 2)); + case "GETSUPER": + // Get an object property from super with a constant name + return RhinoOperation.GETSUPER + .withNamespace(StandardNamespace.PROPERTY) + .named(getNameSegment(tokens, name, 2)); case "GETWITHTHIS": // Same but also return "this" so that it is found by "lastStoredScriptable" return RhinoOperation.GETWITHTHIS @@ -86,20 +91,38 @@ private static Operation parseOperation(String name) throws NoSuchMethodExceptio // Get the value of an element from a property that is on the stack,\ // as if using "[]" notation. Could be a String, number, or Symbol return RhinoOperation.GETELEMENT.withNamespace(StandardNamespace.PROPERTY); + case "GETELEMENTSUPER": + // Get the value of an element from a property that is on the stack,\ + // as if using "[]" notation. Could be a String, number, or Symbol + return RhinoOperation.GETELEMENTSUPER.withNamespace(StandardNamespace.PROPERTY); case "GETINDEX": // Same but the value is definitely a numeric index return RhinoOperation.GETINDEX.withNamespace(StandardNamespace.PROPERTY); + case "GETINDEXSUPER": + // Same but the value is definitely a numeric index + return RhinoOperation.GETINDEXSUPER.withNamespace(StandardNamespace.PROPERTY); case "SET": // Set an object property with a constant name return StandardOperation.SET .withNamespace(StandardNamespace.PROPERTY) .named(getNameSegment(tokens, name, 2)); + case "SETSUPER": + // Set an object property in super with a constant name + return RhinoOperation.SETSUPER + .withNamespace(StandardNamespace.PROPERTY) + .named(getNameSegment(tokens, name, 2)); case "SETELEMENT": // Set an object property as if by "[]", with a property on the stack return RhinoOperation.SETELEMENT.withNamespace(StandardNamespace.PROPERTY); + case "SETELEMENTSUPER": + // Set an object property in super as if by "[]", with a property on the stack + return RhinoOperation.SETELEMENTSUPER.withNamespace(StandardNamespace.PROPERTY); case "SETINDEX": // Same but the property name is definitely a number return RhinoOperation.SETINDEX.withNamespace(StandardNamespace.PROPERTY); + case "SETINDEXSUPER": + // Same but the property name is definitely a number + return RhinoOperation.SETINDEXSUPER.withNamespace(StandardNamespace.PROPERTY); } } else if ("NAME".equals(namespaceName)) { switch (opName) { diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java index 7b49bbcbf0..cd331757d7 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java @@ -89,6 +89,10 @@ private GuardedInvocation getPropertyInvocation( "getObjectPropNoWarn", 1, op.getName()); + } else if (op.isOperation(RhinoOperation.GETSUPER)) { + mh = + bindStringParameter( + lookup, mType, ScriptRuntime.class, "getSuperProp", 1, op.getName()); } else if (op.isOperation(RhinoOperation.GETWITHTHIS)) { mh = bindStringParameter( @@ -111,14 +115,26 @@ private GuardedInvocation getPropertyInvocation( mh = bindStringParameter( lookup, mType, ScriptRuntime.class, "setObjectProp", 1, op.getName()); + } else if (op.isOperation(RhinoOperation.SETSUPER)) { + mh = + bindStringParameter( + lookup, mType, ScriptRuntime.class, "setSuperProp", 1, op.getName()); } else if (op.isOperation(RhinoOperation.GETELEMENT)) { mh = lookup.findStatic(ScriptRuntime.class, "getObjectElem", mType); + } else if (op.isOperation(RhinoOperation.GETELEMENTSUPER)) { + mh = lookup.findStatic(ScriptRuntime.class, "getSuperElem", mType); } else if (op.isOperation(RhinoOperation.GETINDEX)) { mh = lookup.findStatic(ScriptRuntime.class, "getObjectIndex", mType); + } else if (op.isOperation(RhinoOperation.GETINDEXSUPER)) { + mh = lookup.findStatic(ScriptRuntime.class, "getSuperIndex", mType); } else if (op.isOperation(RhinoOperation.SETELEMENT)) { mh = lookup.findStatic(ScriptRuntime.class, "setObjectElem", mType); + } else if (op.isOperation(RhinoOperation.SETELEMENTSUPER)) { + mh = lookup.findStatic(ScriptRuntime.class, "setSuperElem", mType); } else if (op.isOperation(RhinoOperation.SETINDEX)) { mh = lookup.findStatic(ScriptRuntime.class, "setObjectIndex", mType); + } else if (op.isOperation(RhinoOperation.SETINDEXSUPER)) { + mh = lookup.findStatic(ScriptRuntime.class, "setSuperIndex", mType); } if (mh != null) { diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/OptRuntime.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/OptRuntime.java index 4a818d3d7c..311359e20d 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/OptRuntime.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/OptRuntime.java @@ -146,7 +146,7 @@ public static void initFunction( public static Function bindThis( NativeFunction fn, Context cx, Scriptable scope, Scriptable thisObj) { - return new ArrowFunction(cx, scope, fn, thisObj); + return new ArrowFunction(cx, scope, fn, thisObj, null); } public static Object callSpecial( diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/Optimizer.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/Optimizer.java index 9646f422dd..899f7b3bb6 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/Optimizer.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/Optimizer.java @@ -138,6 +138,10 @@ private int rewriteForNumberVariables(Node n, int desired) { case Token.DEC: { Node child = n.getFirstChild(); + if (child.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) { + // Don't optimize super.prop++ and related + return NoType; + } int type = rewriteForNumberVariables(child, NumberType); if (child.getType() == Token.GETVAR) { if (type == NumberType && !convertParameter(child)) { diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoOperation.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoOperation.java index f55aed4e24..9bcd8335ac 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoOperation.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoOperation.java @@ -10,14 +10,20 @@ public enum RhinoOperation implements Operation { BIND, GETNOWARN, + GETSUPER, GETWITHTHIS, GETWITHTHISOPTIONAL, GETELEMENT, + GETELEMENTSUPER, GETINDEX, + GETINDEXSUPER, SETSTRICT, SETCONST, + SETSUPER, SETELEMENT, + SETELEMENTSUPER, SETINDEX, + SETINDEXSUPER, ADD, EQ, SHALLOWEQ, diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/Signatures.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/Signatures.java index ffa1310759..ed8966ffc6 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/Signatures.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/Signatures.java @@ -28,6 +28,18 @@ interface Signatures { "(Ljava/lang/Object;Lorg/mozilla/javascript/Context;" + "Lorg/mozilla/javascript/Scriptable;)Ljava/lang/Object;"; + /** + * PROP:GET_SUPER:{name}: Looks up the super property named "name". Falls back to + * ScriptRuntime.getSuperProp. + */ + String PROP_GET_SUPER = + "(Ljava/lang/Object;" // superObj + + "Lorg/mozilla/javascript/Context;" // cx + + "Lorg/mozilla/javascript/Scriptable;" // scope + + "Ljava/lang/Object;" // thisObj + + "Z" // noWarn + + ")Ljava/lang/Object;"; + /** * PROP:GETWITHTHIS:{name}: Looks up an object property like PROP:GET, and sets "this" in the * "last stored scriptable." Falls back to ScriptRuntime.getPropFunctionAndThis. @@ -43,9 +55,23 @@ interface Signatures { * ScriptRuntime.getObjectIndex. */ String PROP_GET_INDEX = - "(Ljava/lang/Object;D" + "(Ljava/lang/Object;" + + "D" + "Lorg/mozilla/javascript/Context;" - + "Lorg/mozilla/javascript/Scriptable;)Ljava/lang/Object;"; + + "Lorg/mozilla/javascript/Scriptable;" + + ")Ljava/lang/Object;"; + + /** + * PROP:GETINDEXSUPER: Get a property from super based on a numeric index. Falls back to + * ScriptRuntime.getSuperIndex. + */ + String PROP_GET_INDEX_SUPER = + "(Ljava/lang/Object;" + + "D" + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/Object;" + + ")Ljava/lang/Object;"; /** * PROP:GETELEMENT: Get a property from an object based on an element ID, which could be a @@ -58,6 +84,18 @@ interface Signatures { + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;"; + /** + * PROP:GETELEMENTSUPER: Get a property from super based on an element ID, which could be a + * string, number, or symbol. Falls back to ScriptRuntime.getSuperElem. + */ + String PROP_GET_ELEMENT_SUPER = + "(Ljava/lang/Object;" // super + + "Ljava/lang/Object;" // elem + + "Lorg/mozilla/javascript/Context;" // cx + + "Lorg/mozilla/javascript/Scriptable;" // scope + + "Ljava/lang/Object;" // this + + ")Ljava/lang/Object;"; + /** * PROP:SET:{name}: Sets the named property on an object. Falls back to * ScriptRuntime.setObjectProp. @@ -66,6 +104,19 @@ interface Signatures { "(Ljava/lang/Object;Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;Lorg/mozilla/javascript/Scriptable;)Ljava/lang/Object;"; + /** + * PROP:SETSUPER:{name}: Sets the named property on super. Falls back to + * ScriptRuntime.setSuperProp. + */ + String PROP_SET_SUPER = + "(" + + "Ljava/lang/Object;" // superObj + + "Ljava/lang/Object;" // value + + "Lorg/mozilla/javascript/Context;" // cx + + "Lorg/mozilla/javascript/Scriptable;" // scope + + "Ljava/lang/Object;" // thisObj + + ")Ljava/lang/Object;"; + /** * PROP:SETINDEX: Set a property on an object based on a numeric index. Falls back to * ScriptRuntime.setObjectIndex. @@ -78,6 +129,19 @@ interface Signatures { + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;"; + /** + * PROP:SETINDEXSUPER: Set a property on super based on a numeric index. Falls back to + * ScriptRuntime.setSuperIndex. + */ + String PROP_SET_INDEX_SUPER = + "(Ljava/lang/Object;" + + "D" + + "Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/Object;" + + ")Ljava/lang/Object;"; + /** * PROP:SETELEMENT: Set a property on an object based on an identifier. Falls back to * ScriptRuntime.setObjectElem. @@ -90,6 +154,19 @@ interface Signatures { + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;"; + /** + * PROP:SETELEMENTSUPER: Set a property on super based on an identifier. Falls back to + * ScriptRuntime.setSuperElem. + */ + String PROP_SET_ELEMENT_SUPER = + "(Ljava/lang/Object;" // super + + "Ljava/lang/Object;" // elem + + "Ljava/lang/Object;" // value + + "Lorg/mozilla/javascript/Context;" // cx + + "Lorg/mozilla/javascript/Scriptable;" // scope + + "Ljava/lang/Object;" // this + + ")Ljava/lang/Object;"; + /** * NAME:GET:{name}: Looks up a the named value from the scope. Falls back to ScriptRuntime.name. * Compared to that function, this version of the signature puts the "scope" first in the diff --git a/rhino/src/main/resources/org/mozilla/javascript/resources/Messages.properties b/rhino/src/main/resources/org/mozilla/javascript/resources/Messages.properties index 07d3d50c9c..c183115283 100644 --- a/rhino/src/main/resources/org/mozilla/javascript/resources/Messages.properties +++ b/rhino/src/main/resources/org/mozilla/javascript/resources/Messages.properties @@ -453,6 +453,15 @@ msg.no.paren =\ msg.reserved.id =\ identifier is a reserved word: {0} +msg.super.shorthand.function =\ + super should be inside a shorthand function + +msg.optional.super =\ + super is not allowed in an optional chaining expression + +msg.super.delete =\ + cannot delete a super property + msg.no.paren.catch =\ missing ( before catch-block condition diff --git a/rhino/src/test/java/org/mozilla/javascript/SuperTest.java b/rhino/src/test/java/org/mozilla/javascript/SuperTest.java new file mode 100644 index 0000000000..6f2bb3e51b --- /dev/null +++ b/rhino/src/test/java/org/mozilla/javascript/SuperTest.java @@ -0,0 +1,1250 @@ +package org.mozilla.javascript; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mozilla.javascript.tests.Utils; + +class SuperTest { + @Nested + class LexerParser { + @Test + void superIsNotAKeywordUntilES6() { + try (Context cx = Context.enter()) { + cx.setLanguageVersion(Context.VERSION_1_8); + Script test = cx.compileString("var super = 42;", "test", 1, null); + assertNotNull(test); + } + } + + @Test + void superIsAKeywordInES6AndCannotBeUsedAsVariableName() { + assertIsSyntaxErrorES6("var super = 42;", "missing variable name (test#1)"); + } + + @Test + void isSyntaxErrorIfHasSuperCall() { + assertIsSyntaxErrorES6( + "({ method() { super(); }});", + "super should be inside a shorthand function (test#1)"); + } + + @Test + void superCannotBeUsedInAPropertyValue() { + assertIsSyntaxErrorES6( + "var o = { a: super.b }", + "super should be inside a shorthand function (test#1)"); + } + + @Test + void superCannotBeUsedInAComputedPropertyName() { + assertIsSyntaxErrorES6( + "var o = { [super.x]: 42 }", + "super should be inside a shorthand function (test#1)"); + } + + @Test + void superCannotBeUsedInANonShorthandMethod() { + assertIsSyntaxErrorES6( + "var o = { f: function() { super.x } }", + "super should be inside a shorthand function (test#1)"); + } + + @Test + void superCannotHaveOptionalPropertyAccess() { + assertIsSyntaxErrorES6( + "var o = { f() { super?.x } }", + "super is not allowed in an optional chaining expression (test#1)"); + } + + @Test + void superNestedInAFunctionInsideAMethodIsNotAllowed() { + assertIsSyntaxErrorES6( + "var o = { f() {\n" + " (function() { super.x; })() \n" + "} }", + "super should be inside a shorthand function (test#2)"); + } + + private void assertIsSyntaxErrorES6(String source, String expected) { + try (Context cx = Context.enter()) { + cx.setLanguageVersion(Context.VERSION_ES6); + EvaluatorException err = + assertThrows( + EvaluatorException.class, + () -> cx.compileString(source, "test", 1, null)); + assertEquals(expected, err.getMessage()); + } + } + } + + @Nested + class PropertyRead { + @Test + void byName() { + String script = + "" + + "const a = { x: 1 };\n" + + "const b = {\n" + + " x: 2,\n" + + " f() {\n" + + " return super.x;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.f();"; + Utils.assertWithAllOptimizationLevelsES6(1, script); + } + + @Test + void byIndex() { + String script = + "" + + "const a = { [42]: 1 };\n" + + "const b = {\n" + + " f() {\n" + + " return super[42];\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.f();"; + Utils.assertWithAllOptimizationLevelsES6(1, script); + } + + @Test + void byElementString() { + String script = + "" + + "const a = { x: 1 };\n" + + "const b = {\n" + + " x: 2,\n" + + " f() {\n" + + " return super['x'];\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.f();"; + Utils.assertWithAllOptimizationLevelsES6(1, script); + } + + @Test + void byElementIndex() { + String script = + "" + + "const a = { [42]: 1 };\n" + + "const b = {\n" + + " [42]: 2,\n" + + " f() {\n" + + " return super['42'];\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.f();"; + Utils.assertWithAllOptimizationLevelsES6(1, script); + } + + @Test + void byElementSymbol() { + String script = + "" + + "const s = Symbol();\n" + + "const a = { [s]: 1 };\n" + + "const b = {\n" + + " [s]: 2,\n" + + " f() {\n" + + " return super[s];\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.f();"; + Utils.assertWithAllOptimizationLevelsES6(1, script); + } + + @Test + void getter() { + String script = + "" + + "const a = { x: 1 };\n" + + "const b = {\n" + + " get f() {\n" + + " return super.x;\n" + + " }\n" + + "};\n" + + "\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.f;"; + Utils.assertWithAllOptimizationLevelsES6(1, script); + } + + @Test + void getterWithThis() { + String script = + "" + + "const a = { x: 'a', get y() { return this.x + 'x'; } };\n" + + "const b = { x: 'b', get y() { return super['y'] + 'y'; } };\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.y;"; + Utils.assertWithAllOptimizationLevelsES6("bxy", script); + } + + @Test + void superInDefaultArguments() { + String script = + "" + + "const a = { x: 'a'};\n" + + "const b = { x: 'b', f(p = super.x) { return p; } };\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.f();"; + Utils.assertWithAllOptimizationLevelsES6("a", script); + } + + @Test + void usesHomeObjectAndIgnoresThis1() { + String script = + "" + + "const a = { x: 1 };\n" + + "const b = {\n" + + " f() {\n" + + " return super.x;\n" + + " }\n" + + "};\n" + + "\n" + + "Object.setPrototypeOf(b, a);\n" + + "var fn = b.f;\n" + + "fn()\n"; + Utils.assertWithAllOptimizationLevelsES6(1, script); + } + + @Test + void usesHomeObjectAndIgnoresThis2() { + String script = + "" + + "const a = { x: 1 };\n" + + "const b = {\n" + + " f() {\n" + + " return super.x;\n" + + " }\n" + + "};\n" + + "\n" + + "Object.setPrototypeOf(b, a);\n" + + "var fn = b.f;\n" + + "var c = {x: 42};\n" + + "var d = { fn };\n" + + "Object.setPrototypeOf(d, c)\n" + + "d.fn()\n"; + Utils.assertWithAllOptimizationLevelsES6(1, script); + } + + @Test + void nestedObjects() { + String script = + "" + + "const protoX = { x: 'x' };\n" + + "const protoY = { y: 'y' };\n" + + "\n" + + "const obj = {\n" + + " f() {\n" + + " var nested = { g() { return super.x; } };\n" + + " Object.setPrototypeOf(nested, protoX);\n" + + " return nested.g() + super.y;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(obj, protoY);\n" + + "obj.f();"; + Utils.assertWithAllOptimizationLevelsES6("xy", script); + } + + @Test + void nestedObjectsLambda() { + String script = + "" + + "const protoX = { x: 'x' };\n" + + "const protoY = { y: 'y' };\n" + + "\n" + + "const obj = {\n" + + " f() {\n" + + " var nested = { g() { return () => super.x; } };\n" + + " Object.setPrototypeOf(nested, protoX);\n" + + " return nested.g()() + super.y;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(obj, protoY);\n" + + "obj.f();"; + Utils.assertWithAllOptimizationLevelsES6("xy", script); + } + + @Test + void getPropNoWarnPropertyFound() { + String script = + "" + + "const a = { x: 1 };\n" + + "const b = {\n" + + " x: 'a string',\n" + + " f() {\n" + + " return typeof super.x;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.f();"; + Utils.assertWithAllOptimizationLevelsES6("number", script); + } + + @Test + void getPropNoWarnPropertyMissing() { + String script = + "" + + "const a = {};\n" + + "const b = {\n" + + " x: 'a string',\n" + + " f() {\n" + + " return typeof super.x;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.f();"; + Utils.assertWithAllOptimizationLevelsES6("undefined", script); + } + + @Test + void getPropMissingWithContextFeatureStrictMode() { + ContextFactory factory = + new ContextFactory() { + @Override + protected boolean hasFeature(Context cx, int featureIndex) { + if (featureIndex == Context.FEATURE_STRICT_MODE) { + return true; + } + return super.hasFeature(cx, featureIndex); + } + }; + + Utils.runWithAllOptimizationLevels( + factory, + cx -> { + AtomicBoolean warningReported = new AtomicBoolean(false); + cx.setErrorReporter( + new ErrorReporter() { + @Override + public void warning( + String message, + String sourceName, + int line, + String lineSource, + int lineOffset) { + assertEquals( + "Reference to undefined property \"x\"", message); + warningReported.set(true); + } + + @Override + public void error( + String message, + String sourceName, + int line, + String lineSource, + int lineOffset) { + fail("should not have been called"); + } + + @Override + public EvaluatorException runtimeError( + String message, + String sourceName, + int line, + String lineSource, + int lineOffset) { + fail("should not have been called"); + return null; + } + }); + + cx.setLanguageVersion(Context.VERSION_ES6); + String script = + "" + + "const a = {};\n" + + "const b = {\n" + + " x: 'a string',\n" + + " f() {\n" + + " return super.x;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(b, a);\n" + + "b.f();"; + Object result = + cx.evaluateString( + cx.initStandardObjects(), script, "test", 1, null); + assertEquals(Undefined.instance, result); + + assertTrue(warningReported.get()); + return null; + }); + } + + @Test + void propertyNotFoundInSuper() { + // super is implicitly Object.prototype here + String script = + "" + + "const o = {\n" + + " f() {\n" + + " return super.x;\n" + + " }," + + " x: 42\n" + + "};\n" + + "o.f();"; + Utils.assertWithAllOptimizationLevelsES6(Undefined.instance, script); + } + + @Test + void prototypeIsNull() { + String script = + "" + + "var obj = {\n" + + " method() {\n" + + " super.x;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(obj, null);\n" + + "obj.method();"; + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + EcmaError error = + assertThrows( + EcmaError.class, + () -> + cx.evaluateString( + cx.initStandardObjects(), + script, + "test", + 1, + null)); + assertEquals( + "TypeError: Cannot read property \"x\" from null (test#3)", + error.getMessage()); + return null; + }); + } + } + + @Nested + class PropertyMutate { + @Test + void byName() { + String script = + "" + + "var proto = { x: 'proto' };" + + "var object = {\n" + + " x: 'obj',\n" + + " f() { super.x = 'new'; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f();" + + "object.x + ':' + proto.x"; + Utils.assertWithAllOptimizationLevelsES6("new:proto", script); + } + + @Test + void byIndex() { + String script = + "" + + "var proto = { [42]: 'proto' };" + + "var object = {\n" + + " [42]: 'obj',\n" + + " f() { super[42] = 'new'; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f();" + + "object[42] + ':' + proto[42]"; + Utils.assertWithAllOptimizationLevelsES6("new:proto", script); + } + + @Test + void byElementString() { + String script = + "" + + "var proto = { x: 'proto' };" + + "var object = {\n" + + " x: 'obj',\n" + + " f() { super['x'] = 'new'; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f();" + + "object.x+ ':' + proto.x"; + Utils.assertWithAllOptimizationLevelsES6("new:proto", script); + } + + @Test + void byElementIndex() { + String script = + "" + + "var proto = { [42]: 'proto' };" + + "var object = {\n" + + " [42]: 'obj',\n" + + " f() { super['42'] = 'new'; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f();" + + "object[42] + ':' + proto[42]"; + Utils.assertWithAllOptimizationLevelsES6("new:proto", script); + } + + @Test + void byElementSymbol() { + String script = + "" + + "const s = Symbol();" + + "var proto = { [s]: 'proto' };" + + "var object = {\n" + + " [s]: 'obj',\n" + + " f() { super[s] = 'new'; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f();" + + "object[s] + ':' + proto[s]"; + Utils.assertWithAllOptimizationLevelsES6("new:proto", script); + } + + @Test + void setter() { + String script = + "" + + "var proto = {\n" + + " x: 0\n" + + "};\n" + + "var object = {\n" + + " set f(v) {\n" + + " super.x = v;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f = 1;\n" + + "object.x + ':' + proto.x"; + Utils.assertWithAllOptimizationLevelsES6("1:0", script); + } + + @Test + void setterWithThis() { + String script = + "" + + "var proto = {\n" + + " _x: 0,\n" + + " set x(v) {\n" + + " return this._x = v;\n" + + " }\n" + + "};\n" + + "var object = {\n" + + " set x(v) {\n" + + " super.x = v;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.x = 1;\n" + + "object._x + ':' + proto._x"; + Utils.assertWithAllOptimizationLevelsES6("1:0", script); + } + + @Test + void propertyNotFoundInSuper() { + // super is implicitly Object.prototype here + String script = + "" + + "const o = {\n" + + " f() {\n" + + " super.x = 1;\n" + + " }," + + " x: 42\n" + + "};\n" + + "o.f();\n" + + "o.x + ':' + Object.prototype.x"; + Utils.assertWithAllOptimizationLevelsES6("1:undefined", script); + } + + @Test + void superPropertyNotWritableIgnoredSilentlyInNonStrictMode() { + String script = + "\n" + + "var proto = { x: 'proto' };\n" + + "var object = {\n" + + " x: 'obj',\n" + + " f() { super.x = 'new'; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "Object.defineProperty(proto, 'x', {writable: false});\n" + + "object.f();\n" + + "object.x + ':' + proto.x"; + ; + Utils.assertWithAllOptimizationLevelsES6("obj:proto", script); + } + + @Test + void thisPropertyNotWritableIgnoredSilentlyInNonStrictMode() { + String script = + "\n" + + "var proto = { x: 'proto' };\n" + + "var object = {\n" + + " x: 'obj',\n" + + " f() { super.x = 'new'; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "Object.defineProperty(object, 'x', {writable: false});\n" + + "object.f();\n" + + "object.x + ':' + proto.x"; + ; + Utils.assertWithAllOptimizationLevelsES6("obj:proto", script); + } + + @Test + void superPropertyNotWritableInStrictIsError() { + String script = + "\n" + + "'use strict';\n" + + "var proto = { x: 'proto' };\n" + + "var object = {\n" + + " x: 'obj',\n" + + " f() { super.x = 'new'; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "Object.defineProperty(proto, 'x', {writable: false});\n" + + "object.f();\n" + + "object.x + ':' + proto.x"; + ; + assertThrowsTypeErrorCannotWriteProperty(script); + } + + @Test + void thisPropertyNotWritableInStrictIsError() { + String script = + "\n" + + "'use strict';\n" + + "var proto = { x: 'proto' };\n" + + "var object = {\n" + + " x: 'obj',\n" + + " f() { super.x = 'new'; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "Object.defineProperty(object, 'x', {writable: false});\n" + + "object.f();\n" + + "object.x + ':' + proto.x"; + ; + assertThrowsTypeErrorCannotWriteProperty(script); + } + + private void assertThrowsTypeErrorCannotWriteProperty(String script) { + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + EcmaError error = + assertThrows( + EcmaError.class, + () -> + cx.evaluateString( + cx.initStandardObjects(), + script, + "test", + 1, + null)); + assertEquals( + "TypeError: Cannot modify readonly property: x. (test#6)", + error.getMessage()); + return null; + }); + } + + @Test + void prototypeIsNull() { + String script = + "" + + "var obj = {\n" + + " method() {\n" + + " super.x = 42;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(obj, null);\n" + + "obj.method();"; + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + EcmaError error = + assertThrows( + EcmaError.class, + () -> + cx.evaluateString( + cx.initStandardObjects(), + script, + "test", + 1, + null)); + assertEquals( + "TypeError: Cannot set property \"x\" of null to \"42\" (test#3)", + error.getMessage()); + return null; + }); + } + + @Test + void missingPropertyPrototypeSealedCreatesItOnTheThisObject() { + String script = + "\n" + + "var proto = {};\n" + + "var object = {\n" + + " f() { super.x = 1; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "Object.seal(proto);\n" + + "object.f();\n" + + "object.x + ':' + proto.x"; + ; + Utils.assertWithAllOptimizationLevelsES6("1:undefined", script); + } + + @Test + void missingPropertyThisSealedIsIgnoredSilentlyInNonStrictMode() { + String script = + "\n" + + "var proto = {};\n" + + "var object = {\n" + + " f() { super.x = 1; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "Object.seal(object);\n" + + "object.f();\n" + + "object.x + ':' + proto.x"; + ; + Utils.assertWithAllOptimizationLevelsES6("undefined:undefined", script); + } + + @Test + void missingPropertyThisSealedIsErrorInStrictMode() { + String script = + "\n" + + "'use strict';\n" + + "var proto = {};\n" + + "var object = {\n" + + " f() { super.x = 1; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "Object.seal(object);\n" + + "object.f();\n" + + "object.x + ':' + proto.x"; + ; + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + EcmaError error = + assertThrows( + EcmaError.class, + () -> + cx.evaluateString( + cx.initStandardObjects(), + script, + "test", + 1, + null)); + assertEquals( + "TypeError: Cannot add properties to this object because extensible is false. (test#5)", + error.getMessage()); + return null; + }); + } + + @Test + void modifyOperatorByName() { + // Equivalent to `super.x = super.x + 1`, so reads from super, writes in this + String script = + "" + + "var proto = { x: 'proto' };" + + "var object = {\n" + + " x: 'obj',\n" + + " f() { super.x += '1'; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f();" + + "object.x + ':' + proto.x"; + Utils.assertWithAllOptimizationLevelsES6("proto1:proto", script); + } + + @Test + void deleteNotAllowed() { + String script = + "" + + "var catchHit = false;\n" + + "var getterCalled = false;\n" + + "var proto = { get x() { getterCalled = true; } };" + + "var object = {\n" + + " f() {\n" + + " try {\n" + + " delete super.x;\n" + + " } catch (err) {\n" + + " catchHit = err instanceof ReferenceError;" + + " }\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f();\n" + + "catchHit + ':' + getterCalled"; + Utils.assertWithAllOptimizationLevelsES6("true:false", script); + } + + @Test + void deleteSuperFirstEvaluatesPropertyKey() { + String script = + "" + + "var catchHit = false;\n" + + "var gCalled = false;\n" + + "var proto = { x: 1 };\n" + + "function g() { gCalled = true; return 'x'; }\n" + + " object = {\n" + + " f() {\n" + + " try {\n" + + " delete super[g()];\n" + + " } catch (err) {\n" + + " catchHit = err instanceof ReferenceError;" + + " }\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f();\n" + + "catchHit + ':' + gCalled"; + Utils.assertWithAllOptimizationLevelsES6("true:true", script); + } + + @Test + void memberIncrementPostfix() { + String script = + "" + + "var proto = { x: 1 };" + + "var object = {\n" + + " x: 42,\n" + + " f() { return super.x++; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "var f = object.f();" + + "f + ':' + object.x + ':' + proto.x"; + Utils.assertWithAllOptimizationLevelsES6("1:2:1", script); + } + + @Test + void memberIncrementPrefix() { + String script = + "" + + "var proto = { x: 1 };" + + "var object = {\n" + + " x: 42,\n" + + " f() { return ++super.x; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "var f = object.f();" + + "f + ':' + object.x + ':' + proto.x"; + Utils.assertWithAllOptimizationLevelsES6("2:2:1", script); + } + + @Test + void elementDecrementPostfix() { + String script = + "" + + "var proto = { 0: 1 };" + + "var object = {\n" + + " 0: 42,\n" + + " f() { return super[0]--; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "var f = object.f();" + + "f + ':' + object[0] + ':' + proto[0]"; + Utils.assertWithAllOptimizationLevelsES6("1:0:1", script); + } + + @Test + void elementDecrementPrefix() { + String script = + "" + + "var proto = { 0: 1 };" + + "var object = {\n" + + " 0: 42,\n" + + " f() { return --super[0]; }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "var f = object.f();" + + "f + ':' + object[0] + ':' + proto[0]"; + Utils.assertWithAllOptimizationLevelsES6("0:0:1", script); + } + } + + @Nested + class MethodCall { + @Test + void methodsAreResolvedOnSuperObject() { + final String script = + "" + + "const proto = {\n" + + " f(x) {\n" + + " return 'prototype' + x;\n" + + " }\n" + + "};\n" + + "const obj = {\n" + + " f() {\n" + + " return super.f(1);\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(obj, proto);\n" + + "obj.f();"; + Utils.assertWithAllOptimizationLevelsES6("prototype1", script); + } + + // All the n-arguments variants are necessary because we have optimized code paths in + // compiled classes + // for 0, 1, 2, and N arguments + + @Test + void thisIsSetCorrectly0Args() { + String script = + "" + + "var proto = {\n" + + " x: 'proto',\n" + + " f() {\n" + + " return this.x;\n" + + " }\n" + + "};\n" + + "var object = {\n" + + " x: 'object',\n" + + " g() {\n" + + " return super.f();\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.g();"; + Utils.assertWithAllOptimizationLevelsES6("object", script); + } + + @Test + void thisIsSetCorrectly1Arg() { + String script = + "" + + "var proto = {\n" + + " x: 'proto',\n" + + " f(a) {\n" + + " return this.x + ':' + a;\n" + + " }\n" + + "};\n" + + "var object = {\n" + + " x: 'object',\n" + + " g() {\n" + + " return super.f('a');\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.g();"; + Utils.assertWithAllOptimizationLevelsES6("object:a", script); + } + + @Test + void thisIsSetCorrectly2Args() { + String script = + "" + + "var proto = {\n" + + " x: 'proto',\n" + + " f(a, b) {\n" + + " return this.x + ':' + a + ':' + b;\n" + + " }\n" + + "};\n" + + "var object = {\n" + + " x: 'object',\n" + + " g() {\n" + + " return super.f('a', 'b');\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.g();"; + Utils.assertWithAllOptimizationLevelsES6("object:a:b", script); + } + + @Test + void thisIsSetCorrectlyNArgs() { + String script = + "" + + "var proto = {\n" + + " x: 'proto',\n" + + " f(a, b, c) {\n" + + " return this.x + ':' + a + ':' + b + ':' + c;\n" + + " }\n" + + "};\n" + + "var object = {\n" + + " x: 'object',\n" + + " g() {\n" + + " return super.f('a', 'b', 'c');\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.g();"; + Utils.assertWithAllOptimizationLevelsES6("object:a:b:c", script); + } + + @Test + void lastScratchScriptableIsCleanedUpProperly() { + String script = + "" + + "function f1() { return 'f1'; }\n" + + "var proto = {\n" + + " f2() {\n" + + " return 'f2';\n" + + " }\n" + + "};\n" + + "var object = {\n" + + " f3() {\n" + + " return super.f2() + f1();\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f3();"; + Utils.assertWithAllOptimizationLevelsES6("f2f1", script); + } + + @Test + void thisIsSetCorrectlyForTemplateLiteralCall() { + String script = + "" + + "var proto = {\n" + + " x: 'proto',\n" + + " f() {\n" + + " return this.x;\n" + + " }\n" + + "};\n" + + "var object = {\n" + + " x: 'object',\n" + + " f() {\n" + + " return super.f`some ignored string`;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f();"; + Utils.assertWithAllOptimizationLevelsES6("object", script); + } + + @Test + void nestedLambdaCaptureSuper() { + String script = + "" + + "var proto = {\n" + + " x: 'proto',\n" + + " f() {\n" + + " return this.x;\n" + + " }\n" + + "};\n" + + "var object = {\n" + + " x: 'object',\n" + + " f() {\n" + + " return () => { return super.f(); } ;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f()();"; + Utils.assertWithAllOptimizationLevelsES6("object", script); + } + + @Test + void doublyNestedLambdaCaptureSuper() { + String script = + "" + + "var proto = {\n" + + " x: 'proto',\n" + + " f() {\n" + + " return this.x;\n" + + " }\n" + + "};\n" + + "var object = {\n" + + " x: 'object',\n" + + " f() {\n" + + " return () => { return () => { return super.f(); } } ;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f()()();"; + Utils.assertWithAllOptimizationLevelsES6("object", script); + } + } + + /** + * Test cases related to the special handling of REF in Rhino, which includes the special + * properties __proto__ and __parent__. It also includes XML stuff, but we do not support it for + * super. + */ + @Nested + class Ref { + @Test + void propertyGet() { + String script = + "" + + "var a = {x: 'a'};\n" + + "var b = {x: 'b'};\n" + + "var c = {x: 'c',\n" + + " f() {\n" + + " return super.__proto__.x;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(c, b);\n" + + "Object.setPrototypeOf(b, a);\n" + + "c.f();"; + Utils.assertWithAllOptimizationLevelsES6("b", script); + } + + @Test + void propertyGetter() { + String script = + "" + + "var a = {get x() { return 'a' + this.y }, y: 'a' };\n" + + "var b = {get x() { return 'b' + this.y }, y: 'b' };\n" + + "var c = {\n" + + " get x() { return 'c' + this.y }, y: 'c',\n" + + " f() { return super.__proto__.x }," + + "};\n" + + "Object.setPrototypeOf(c, b);\n" + + "Object.setPrototypeOf(b, a);\n" + + "c.f();"; + Utils.assertWithAllOptimizationLevelsES6("bb", script); + } + + @Test + void propertySet() { + String script = + "" + + "var a = {x: 'a'};\n" + + "var b = {x: 'b'};\n" + + "var c = {x: 'c',\n" + + " f() {\n" + + " super.__proto__ = a;\n" + + " return Object.getPrototypeOf(this).x;\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(c, b);\n" + + "Object.setPrototypeOf(b, a);\n" + + "c.f();"; + Utils.assertWithAllOptimizationLevelsES6("a", script); + } + } + + @Nested + class Eval { + @Test + void evalInsideMethodCanAccessSuper() { + String script = + "" + + "var proto = {\n" + + " x: 'proto'\n" + + "};\n" + + "var object = {\n" + + " x: 'object',\n" + + " f() {\n" + + " return eval('super.x');\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f();"; + Utils.assertWithAllOptimizationLevelsES6("proto", script); + } + + @Test + void evalFromLambdaInMethodCanAccessSuper() { + String script = + "" + + "var proto = {\n" + + " x: 'proto'\n" + + "};\n" + + "var object = {\n" + + " x: 'object',\n" + + " f() {\n" + + " return () => eval('super.x');\n" + + " }\n" + + "};\n" + + "Object.setPrototypeOf(object, proto);\n" + + "object.f()();"; + Utils.assertWithAllOptimizationLevelsES6("proto", script); + } + + @Test + void superCannotBeUsedAsMethodInEval() { + String script = + "" + + "o = {\n" + + " f() {\n" + + " eval('super(42)')" + + " }\n" + + "};" + + "o.f();"; + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + EcmaError error = + assertThrows( + EcmaError.class, + () -> + cx.evaluateString( + cx.initStandardObjects(), + script, + "test", + 1, + null)); + assertEquals( + "SyntaxError: super should be inside a shorthand function (test#3(eval)#1)", + error.getMessage()); + return null; + }); + } + + @Test + void evalOutsideMethodCannotAccessSuper() { + String script = "eval('super.x')"; + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + EcmaError error = + assertThrows( + EcmaError.class, + () -> + cx.evaluateString( + cx.initStandardObjects(), + script, + "test", + 1, + null)); + assertEquals( + "SyntaxError: super should be inside a shorthand function (test#1(eval)#1)", + error.getMessage()); + return null; + }); + } + + @Test + void evalInFunctionInsideMethodDoesNotAllowSuper() { + String script = + "" + + "o = {\n" + + " f() {\n" + + " (function() { eval('super(42)') })();" + + " }\n" + + "};" + + "o.f();"; + Utils.runWithAllOptimizationLevels( + cx -> { + cx.setLanguageVersion(Context.VERSION_ES6); + EcmaError error = + assertThrows( + EcmaError.class, + () -> + cx.evaluateString( + cx.initStandardObjects(), + script, + "test", + 1, + null)); + assertEquals( + "SyntaxError: super should be inside a shorthand function (test#3(eval)#1)", + error.getMessage()); + return null; + }); + } + } +} diff --git a/tests/src/test/java/org/mozilla/javascript/tests/ComputedPropertiesTest.java b/tests/src/test/java/org/mozilla/javascript/tests/ComputedPropertiesTest.java index 91d0c435ca..16d77b10d4 100644 --- a/tests/src/test/java/org/mozilla/javascript/tests/ComputedPropertiesTest.java +++ b/tests/src/test/java/org/mozilla/javascript/tests/ComputedPropertiesTest.java @@ -265,7 +265,7 @@ public void unsupportedInDestructuringInFunctionArguments() { public void unsupportedInDestructuringInVariableDeclaration() { String script = "var { [a]: b } = {};"; assertComputedPropertiesAreUnsupportedInDestructuring( - script, "Unsupported computed property in destructuring."); + script, "Unsupported computed property in destructuring. (test#1)"); } private void assertComputedPropertiesAreUnsupportedInDestructuring( diff --git a/tests/src/test/java/org/mozilla/javascript/tests/Test262SuiteTest.java b/tests/src/test/java/org/mozilla/javascript/tests/Test262SuiteTest.java index 8273bc1926..92304b01ad 100644 --- a/tests/src/test/java/org/mozilla/javascript/tests/Test262SuiteTest.java +++ b/tests/src/test/java/org/mozilla/javascript/tests/Test262SuiteTest.java @@ -111,7 +111,6 @@ public class Test262SuiteTest { "regexp-named-groups", "regexp-unicode-property-escapes", "resizable-arraybuffer", - "super", "tail-call-optimization", "u180e")); diff --git a/tests/testsrc/test262.properties b/tests/testsrc/test262.properties index a5b400211e..b2cc7ab91b 100644 --- a/tests/testsrc/test262.properties +++ b/tests/testsrc/test262.properties @@ -3512,16 +3512,13 @@ language/comments 9/52 (17.31%) multi-line-asi-line-separator.js multi-line-asi-paragraph-separator.js -language/computed-property-names 37/48 (77.08%) +language/computed-property-names 34/48 (70.83%) class/accessor 4/4 (100.0%) class/method 11/11 (100.0%) class/static 14/14 (100.0%) object/accessor/getter.js - object/accessor/getter-super.js object/accessor/setter.js - object/accessor/setter-super.js object/method/generator.js - object/method/super.js to-name-side-effects/class.js to-name-side-effects/numbers-class.js @@ -3555,7 +3552,7 @@ language/directive-prologue 18/62 (29.03%) 14.1-9-s.js {non-strict: [-1]} func-decl-inside-func-decl-parse.js non-strict -language/eval-code 253/347 (72.91%) +language/eval-code 241/347 (69.45%) direct/arrow-fn-a-following-parameter-is-named-arguments-arrow-func-declare-arguments-assign.js non-strict direct/arrow-fn-a-following-parameter-is-named-arguments-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js non-strict direct/arrow-fn-a-preceding-parameter-is-named-arguments-arrow-func-declare-arguments-assign.js non-strict @@ -3759,16 +3756,6 @@ language/eval-code 253/347 (72.91%) direct/new.target-arrow.js {unsupported: [new.target]} direct/new.target-fn.js {unsupported: [new.target]} direct/non-definable-global-var.js non-strict - direct/super-call.js {unsupported: [super]} - direct/super-call-arrow.js {unsupported: [super]} - direct/super-call-fn.js {unsupported: [super]} - direct/super-call-method.js {unsupported: [super]} - direct/super-prop.js {unsupported: [super]} - direct/super-prop-arrow.js {unsupported: [super]} - direct/super-prop-dot-no-home.js {unsupported: [super]} - direct/super-prop-expr-no-home.js {unsupported: [super]} - direct/super-prop-expr-no-home-no-eval.js {unsupported: [super]} - direct/super-prop-method.js {unsupported: [super]} direct/switch-case-decl-eval-source-is-strict-nostrict.js non-strict direct/switch-case-decl-eval-source-is-strict-onlystrict.js strict direct/switch-case-decl-onlystrict.js strict @@ -3801,8 +3788,6 @@ language/eval-code 253/347 (72.91%) indirect/non-definable-function-with-variable.js indirect/non-definable-global-var.js non-strict indirect/realm.js - indirect/super-call.js {unsupported: [super]} - indirect/super-prop.js {unsupported: [super]} indirect/switch-case-decl-strict.js indirect/switch-dflt-decl-strict.js indirect/var-env-func-init-global-update-configurable.js @@ -4967,7 +4952,7 @@ language/expressions/new 41/59 (69.49%) ~language/expressions/new.target -language/expressions/object 789/1169 (67.49%) +language/expressions/object 784/1169 (67.07%) dstr/async-gen-meth-ary-init-iter-close.js {unsupported: [async-iteration, async]} dstr/async-gen-meth-ary-init-iter-get-err.js {unsupported: [async-iteration]} dstr/async-gen-meth-ary-init-iter-get-err-array-prototype.js {unsupported: [async-iteration]} @@ -5633,7 +5618,7 @@ language/expressions/object 789/1169 (67.49%) method-definition/generator-prototype-prop.js method-definition/generator-return.js method-definition/generator-super-prop-body.js - method-definition/generator-super-prop-param.js {unsupported: [super]} + method-definition/generator-super-prop-param.js method-definition/meth-array-destructuring-param-strict-body.js method-definition/meth-dflt-params-duplicates.js non-strict method-definition/meth-dflt-params-ref-later.js @@ -5654,8 +5639,6 @@ language/expressions/object 789/1169 (67.49%) method-definition/name-prop-name-yield-expr.js non-strict method-definition/name-prop-name-yield-id.js non-strict method-definition/name-prototype-prop.js - method-definition/name-super-prop-body.js {unsupported: [super]} - method-definition/name-super-prop-param.js {unsupported: [super]} method-definition/object-method-returns-promise.js {unsupported: [async-functions]} method-definition/params-dflt-gen-meth-args-unmapped.js method-definition/params-dflt-gen-meth-ref-arguments.js @@ -5714,14 +5697,12 @@ language/expressions/object 789/1169 (67.49%) getter-body-strict-inside.js non-strict getter-body-strict-outside.js strict getter-param-dflt.js - getter-super-prop.js ident-name-prop-name-literal-await-static-init.js identifier-shorthand-await-strict-mode.js non-strict identifier-shorthand-static-init-await-valid.js let-non-strict-access.js non-strict let-non-strict-syntax.js non-strict literal-property-name-bigint.js {unsupported: [class]} - method.js object-spread-proxy-get-not-called-on-dontenum-keys.js {unsupported: [Proxy]} object-spread-proxy-no-excluded-keys.js {unsupported: [Proxy]} object-spread-proxy-ownkeys-returned-keys-order.js {unsupported: [Proxy]} @@ -5754,7 +5735,6 @@ language/expressions/object 789/1169 (67.49%) setter-length-dflt.js setter-param-arguments-strict-inside.js non-strict setter-param-eval-strict-inside.js non-strict - setter-super-prop.js yield-non-strict-access.js non-strict yield-non-strict-syntax.js non-strict @@ -5838,7 +5818,75 @@ language/expressions/strict-equals 0/30 (0.0%) language/expressions/subtraction 1/38 (2.63%) order-of-evaluation.js -~language/expressions/super +language/expressions/super 68/86 (79.07%) + call-arg-evaluation-err.js {unsupported: [class]} + call-bind-this-value.js {unsupported: [class]} + call-bind-this-value-twice.js {unsupported: [class]} + call-construct-error.js {unsupported: [class]} + call-construct-invocation.js {unsupported: [Reflect.construct, Reflect, new.target, class]} + call-expr-value.js {unsupported: [class]} + call-poisoned-underscore-proto.js {unsupported: [class]} + call-proto-not-ctor.js {unsupported: [class]} + call-spread-err-mult-err-expr-throws.js + call-spread-err-mult-err-iter-get-value.js + call-spread-err-mult-err-itr-get-call.js + call-spread-err-mult-err-itr-get-get.js + call-spread-err-mult-err-itr-step.js + call-spread-err-mult-err-itr-value.js + call-spread-err-mult-err-obj-unresolvable.js + call-spread-err-mult-err-unresolvable.js + call-spread-err-sngl-err-expr-throws.js + call-spread-err-sngl-err-itr-get-call.js + call-spread-err-sngl-err-itr-get-get.js + call-spread-err-sngl-err-itr-get-value.js + call-spread-err-sngl-err-itr-step.js + call-spread-err-sngl-err-itr-value.js + call-spread-err-sngl-err-obj-unresolvable.js + call-spread-err-sngl-err-unresolvable.js + call-spread-mult-empty.js + call-spread-mult-expr.js + call-spread-mult-iter.js + call-spread-mult-literal.js + call-spread-mult-obj-ident.js + call-spread-mult-obj-null.js + call-spread-mult-obj-undefined.js + call-spread-obj-getter-descriptor.js + call-spread-obj-getter-init.js + call-spread-obj-manipulate-outter-obj-in-getter.js + call-spread-obj-mult-spread.js + call-spread-obj-mult-spread-getter.js + call-spread-obj-null.js + call-spread-obj-override-immutable.js + call-spread-obj-overrides-prev-properties.js + call-spread-obj-skip-non-enumerable.js + call-spread-obj-spread-order.js + call-spread-obj-symbol-property.js + call-spread-obj-undefined.js + call-spread-obj-with-overrides.js + call-spread-sngl-empty.js + call-spread-sngl-expr.js + call-spread-sngl-iter.js + call-spread-sngl-literal.js + call-spread-sngl-obj-ident.js + prop-dot-cls-null-proto.js {unsupported: [class]} + prop-dot-cls-ref-strict.js {unsupported: [class]} + prop-dot-cls-ref-this.js + prop-dot-cls-this-uninit.js {unsupported: [class]} + prop-dot-cls-val.js {unsupported: [class]} + prop-dot-cls-val-from-arrow.js {unsupported: [class]} + prop-dot-cls-val-from-eval.js {unsupported: [class]} + prop-expr-cls-err.js {unsupported: [class]} + prop-expr-cls-key-err.js {unsupported: [class]} + prop-expr-cls-null-proto.js {unsupported: [class]} + prop-expr-cls-ref-strict.js {unsupported: [class]} + prop-expr-cls-ref-this.js + prop-expr-cls-this-uninit.js {unsupported: [class]} + prop-expr-cls-unresolvable.js {unsupported: [class]} + prop-expr-cls-val.js {unsupported: [class]} + prop-expr-cls-val-from-arrow.js {unsupported: [class]} + prop-expr-cls-val-from-eval.js {unsupported: [class]} + realm.js {unsupported: [Reflect]} + super-reference-resolution.js {unsupported: [class]} language/expressions/tagged-template 3/27 (11.11%) call-expression-context-strict.js strict @@ -6004,7 +6052,7 @@ language/future-reserved-words 7/55 (12.73%) public.js non-strict static.js non-strict -language/global-code 30/42 (71.43%) +language/global-code 26/42 (61.9%) block-decl-strict.js strict decl-lex.js decl-lex-configurable-global.js @@ -6028,10 +6076,6 @@ language/global-code 30/42 (71.43%) script-decl-var.js script-decl-var-collision.js script-decl-var-err.js non-strict - super-call.js {unsupported: [super]} - super-call-arrow.js {unsupported: [super]} - super-prop.js {unsupported: [super]} - super-prop-arrow.js {unsupported: [super]} switch-case-decl-strict.js strict switch-dflt-decl-strict.js strict yield-non-strict.js non-strict