Skip to content

Commit

Permalink
Implement super (#1735)
Browse files Browse the repository at this point in the history
This implements the super operator - which can actually be used outside of classes and will do something that intuitively is "refer to the prototype". The actual details are rather complicated; the MDN page is very clear and detailed. There are detailed notes in the PR description that help document what was fixed here.

Co-authored-by: Satish Srinivasan <[email protected]>
  • Loading branch information
andreabergia and 0xe authored Dec 10, 2024
1 parent a8696c7 commit 9d33f5b
Show file tree
Hide file tree
Showing 30 changed files with 2,883 additions and 354 deletions.
12 changes: 11 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/ArrowFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -80,6 +86,10 @@ Scriptable getCallThis(Context cx) {
return boundThis != null ? boundThis : ScriptRuntime.getTopCallScope(cx);
}

Scriptable getBoundHomeObject() {
return this.boundHomeObject;
}

Callable getTargetFunction() {
return targetFunction;
}
Expand Down
9 changes: 9 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/BaseFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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};
Expand Down
108 changes: 105 additions & 3 deletions rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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());
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
{
Expand Down Expand Up @@ -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) {
Expand Down
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
Expand Up @@ -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
Expand Down Expand Up @@ -257,5 +266,6 @@ public static CompilerEnvirons ideEnvirons() {
private boolean warnTrailingComma;
private boolean ideMode;
private boolean allowSharpComments;
private boolean allowSuper;
Set<String> activationNames;
}
34 changes: 27 additions & 7 deletions rhino/src/main/java/org/mozilla/javascript/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -1381,7 +1392,8 @@ public final Script compileReader(
securityDomain,
false,
null,
null);
null,
compilerEnvironsProcessor);
}

/**
Expand All @@ -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(
Expand All @@ -1414,7 +1426,8 @@ final Script compileString(
ErrorReporter compilationErrorReporter,
String sourceName,
int lineno,
Object securityDomain) {
Object securityDomain,
Consumer<CompilerEnvirons> compilerEnvironsProcessor) {
return (Script)
compileImpl(
null,
Expand All @@ -1424,7 +1437,8 @@ final Script compileString(
securityDomain,
false,
compiler,
compilationErrorReporter);
compilationErrorReporter,
compilerEnvironsProcessor);
}

/**
Expand Down Expand Up @@ -1465,7 +1479,8 @@ final Function compileFunction(
securityDomain,
true,
compiler,
compilationErrorReporter);
compilationErrorReporter,
null);
}

/**
Expand Down Expand Up @@ -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";
}
Expand All @@ -2441,6 +2457,9 @@ protected Object compileImpl(
if (compilationErrorReporter == null) {
compilationErrorReporter = compilerEnv.getErrorReporter();
}
if (compilerEnvironProcessor != null) {
compilerEnvironProcessor.accept(compilerEnv);
}

ScriptNode tree =
parse(
Expand Down Expand Up @@ -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()) {
Expand Down
Loading

0 comments on commit 9d33f5b

Please sign in to comment.