Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow super in eval
Browse files Browse the repository at this point in the history
Co-authored-by: Satish Srinivasan <[email protected]>
andreabergia and 0xe committed Nov 27, 2024
1 parent b5a3170 commit 3f6e5d7
Showing 8 changed files with 192 additions and 18 deletions.
10 changes: 10 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/CompilerEnvirons.java
Original file line number Diff line number Diff line change
@@ -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<String> activationNames;
}
31 changes: 25 additions & 6 deletions rhino/src/main/java/org/mozilla/javascript/Context.java
Original file line number Diff line number Diff line change
@@ -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<CompilerEnvirons> 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<CompilerEnvirons> 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<CompilerEnvirons> 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(
6 changes: 3 additions & 3 deletions rhino/src/main/java/org/mozilla/javascript/IRFactory.java
Original file line number Diff line number Diff line change
@@ -889,9 +889,9 @@ private Node transformLetNode(LetNode node) {

private Node transformLiteral(AstNode node) {
// Trying to call super as a function. See 15.4.2 Static Semantics: HasDirectSuper
if (node.getParent() instanceof FunctionCall
&& node.getType() == Token.SUPER
&& parser.currentScriptOrFn.isMethodDefinition())
// 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;
}
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion rhino/src/main/java/org/mozilla/javascript/Parser.java
Original file line number Diff line number Diff line change
@@ -3390,7 +3390,7 @@ private AstNode primaryExpr() throws IOException {
}

case Token.SUPER:
if (insideFunction() && insideMethod) {
if ((insideFunction() && insideMethod) || compilerEnv.isAllowSuper()) {
consumeToken();
pos = ts.tokenBeg;
end = ts.tokenEnd;
21 changes: 20 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java
Original file line number Diff line number Diff line change
@@ -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;
@@ -3229,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<CompilerEnvirons> 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 =
131 changes: 130 additions & 1 deletion rhino/src/test/java/org/mozilla/javascript/SuperTest.java
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ void superIsAKeywordInES6AndCannotBeUsedAsVariableName() {
}

@Test
void isSyntaxErrorIfHasDirectSuperOfMethodDefinitionIsTrue() {
void isSyntaxErrorIfHasSuperCall() {
assertIsSyntaxErrorES6(
"({ method() { super(); }});",
"super should be inside a shorthand function (test#1)");
@@ -63,6 +63,13 @@ void superCannotHaveOptionalPropertyAccess() {
"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);
@@ -942,4 +949,126 @@ void doublyNestedLambdaCaptureSuper() {
Utils.assertWithAllOptimizationLevelsES6("object", 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;
});
}
}
}
7 changes: 2 additions & 5 deletions tests/testsrc/test262.properties
Original file line number Diff line number Diff line change
@@ -3552,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 242/347 (69.74%)
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
@@ -3756,7 +3756,6 @@ language/eval-code 242/347 (69.74%)
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-prop-method.js
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
@@ -5820,7 +5819,7 @@ language/expressions/strict-equals 0/30 (0.0%)
language/expressions/subtraction 1/38 (2.63%)
order-of-evaluation.js

language/expressions/super 70/86 (81.4%)
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]}
@@ -5877,7 +5876,6 @@ language/expressions/super 70/86 (81.4%)
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-dot-obj-val-from-eval.js
prop-expr-cls-err.js {unsupported: [class]}
prop-expr-cls-key-err.js {unsupported: [class]}
prop-expr-cls-null-proto.js {unsupported: [class]}
@@ -5888,7 +5886,6 @@ language/expressions/super 70/86 (81.4%)
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]}
prop-expr-obj-val-from-eval.js
realm.js {unsupported: [Reflect]}
super-reference-resolution.js {unsupported: [class]}

0 comments on commit 3f6e5d7

Please sign in to comment.