Skip to content

Commit ddf877c

Browse files
authored
Implement Symbol.toStringTag for Generators
* Fix default parameter evaluation for generators * Compile default values for arguments in generators * Implement Symbol.toStringTag for generators
1 parent c16a6f2 commit ddf877c

11 files changed

Lines changed: 164 additions & 35 deletions

File tree

rhino/src/main/java/org/mozilla/javascript/BaseFunction.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,21 @@ protected synchronized Object setupDefaultPrototype(Scriptable scope) {
689689
// wacky case of a user defining a function Object(), we don't
690690
// get an infinite loop trying to find the prototype.
691691
prototypeProperty = obj;
692-
Scriptable proto = getObjectPrototype(this);
692+
Scriptable proto;
693+
if (isGeneratorFunction()) {
694+
// For generator functions, the .prototype property's [[Prototype]]
695+
// should be %GeneratorPrototype%, not Object.prototype
696+
Scriptable top = ScriptableObject.getTopLevelScope(scope);
697+
Object generatorProto =
698+
ScriptableObject.getTopScopeValue(top, ES6Generator.GENERATOR_TAG);
699+
if (generatorProto instanceof Scriptable) {
700+
proto = (Scriptable) generatorProto;
701+
} else {
702+
proto = getObjectPrototype(this); // fallback
703+
}
704+
} else {
705+
proto = getObjectPrototype(this);
706+
}
693707
if (proto != obj) {
694708
// not the one we just made, it must remain grounded
695709
obj.setPrototype(proto);

rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,32 @@ private void generateFunctionICode() {
102102
CodeGenUtils.setConstructor(builder, theFunction);
103103

104104
if (theFunction.isGenerator()) {
105+
// For generators with default parameters, generate parameter initialization
106+
// bytecode BEFORE Icode_GENERATOR so defaults are evaluated before generator
107+
// object creation. Ref: Ecma 2026, 10.2.11 FunctionDeclarationInstantiation
108+
Node paramInitBlock = theFunction.getGeneratorParamInitBlock();
109+
if (paramInitBlock != null) {
110+
// Generate bytecode for each parameter initialization statement
111+
Node paramInit = paramInitBlock.getFirstChild();
112+
while (paramInit != null) {
113+
visitStatement(paramInit, 0);
114+
paramInit = paramInit.getNext();
115+
}
116+
}
117+
118+
// For generators, nested function declarations must be instantiated after
119+
// parameter initialization to prevent them from shadowing the 'arguments' object
120+
// during default parameter evaluation. See test262 arguments-with-arguments-fn.js
121+
int functionCount = theFunction.getFunctionCount();
122+
if (functionCount > 0) {
123+
for (int i = 0; i < functionCount; i++) {
124+
FunctionNode fn = theFunction.getFunctionNode(i);
125+
if (fn.getFunctionType() == FunctionNode.FUNCTION_STATEMENT) {
126+
addIndexOp(Icode_CLOSURE_STMT, i);
127+
}
128+
}
129+
}
130+
105131
addIcode(Icode_GENERATOR);
106132
addUint16(theFunction.getBaseLineno() & 0xFFFF);
107133
}

rhino/src/main/java/org/mozilla/javascript/ES6Generator.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ static ES6Generator init(ScriptableObject scope, boolean sealed) {
4040
new LambdaFunction(scope, "[Symbol.iterator]", 0, ES6Generator::js_iterator);
4141
prototype.defineProperty(SymbolKey.ITERATOR, iterator, DONTENUM);
4242

43+
prototype.defineProperty(SymbolKey.TO_STRING_TAG, "Generator", DONTENUM | READONLY);
44+
4345
if (sealed) {
4446
prototype.sealObject();
4547
}
@@ -61,14 +63,22 @@ private ES6Generator() {}
6163
public ES6Generator(Scriptable scope, JSFunction function, Object savedState) {
6264
this.function = function;
6365
this.savedState = savedState;
64-
// Set parent and prototype properties. Since we don't have a
65-
// "Generator" constructor in the top scope, we stash the
66-
// prototype in the top scope's associated value.
66+
// Set parent and prototype properties.
6767
Scriptable top = ScriptableObject.getTopLevelScope(scope);
6868
this.setParentScope(top);
69-
ES6Generator prototype =
70-
(ES6Generator) ScriptableObject.getTopScopeValue(top, GENERATOR_TAG);
71-
this.setPrototype(prototype);
69+
// Per ES6 spec, generator instance's [[Prototype]] should be
70+
// the generator function's .prototype property.
71+
Object functionPrototype = ScriptableObject.getProperty(function, "prototype");
72+
if (functionPrototype instanceof Scriptable) {
73+
this.setPrototype((Scriptable) functionPrototype);
74+
} else {
75+
// If function.prototype is not an Object, use the intrinsic default prototype
76+
// Ref: Ecma 2026, 10.1.14 GetPrototypeFromConstructor step 4.
77+
// See test262: language/statements/generators/default-proto.js
78+
ES6Generator prototype =
79+
(ES6Generator) ScriptableObject.getTopScopeValue(top, GENERATOR_TAG);
80+
this.setPrototype(prototype);
81+
}
7282
}
7383

7484
@Override

rhino/src/main/java/org/mozilla/javascript/IRFactory.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,12 +657,13 @@ private Node transformFunction(FunctionNode fn) {
657657
/* Process simple default parameters */
658658
List<Object> defaultParams = fn.getDefaultParams();
659659
if (defaultParams != null) {
660+
Node paramInitBlock = null;
660661
for (int i = defaultParams.size() - 1; i > 0; ) {
661662
if (defaultParams.get(i) instanceof AstNode
662663
&& defaultParams.get(i - 1) instanceof String) {
663664
AstNode rhs = (AstNode) defaultParams.get(i);
664665
String name = (String) defaultParams.get(i - 1);
665-
body.addChildToFront(
666+
Node paramInit =
666667
createIf(
667668
createBinary(
668669
Token.SHEQ,
@@ -678,10 +679,21 @@ private Node transformFunction(FunctionNode fn) {
678679
body.getColumn()),
679680
null,
680681
body.getLineno(),
681-
body.getColumn()));
682+
body.getColumn());
683+
if (fn.isGenerator()) {
684+
if (paramInitBlock == null) {
685+
paramInitBlock = new Node(Token.BLOCK);
686+
}
687+
paramInitBlock.addChildToFront(paramInit);
688+
} else {
689+
body.addChildToFront(paramInit);
690+
}
682691
}
683692
i -= 2;
684693
}
694+
if (fn.isGenerator() && paramInitBlock != null) {
695+
fn.setGeneratorParamInitBlock(paramInitBlock);
696+
}
685697
}
686698

687699
/* transform nodes used as default parameters */

rhino/src/main/java/org/mozilla/javascript/Interpreter.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,11 @@ void initializeArgs(
296296
ScriptRuntime.initScript(fnOrScript, thisObj, cx, scope, desc.isEvalFunction());
297297
}
298298

299-
if (desc.getFunctionCount() != 0) {
299+
// Defer default parameters and nested function declarations until activation scope
300+
// creation
301+
// Ref: Ecma 2026, 10.2.11, FunctionDeclarationInstantiation
302+
303+
if (desc.getFunctionCount() != 0 && !desc.isES6Generator()) {
300304
if (desc.getFunctionType() != 0 && !desc.requiresActivationFrame()) Kit.codeBug();
301305
for (int i = 0; i < desc.getFunctionCount(); i++) {
302306
JSDescriptor fdesc = desc.getFunction(i);

rhino/src/main/java/org/mozilla/javascript/JSFunction.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ protected boolean hasPrototypeProperty() {
6969
return true;
7070
}
7171

72+
@Override
73+
protected boolean isGeneratorFunction() {
74+
return descriptor.isES6Generator();
75+
}
76+
7277
@Override
7378
public int getLength() {
7479
int arity = descriptor.getArity();

rhino/src/main/java/org/mozilla/javascript/ast/FunctionNode.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public void putDestructuringRvalues(Node left, Node right) {
120120
private boolean isES6Generator;
121121
private List<Node> generatorResumePoints;
122122
private Map<Node, int[]> liveLocals;
123+
private Node generatorParamInitBlock; // IR block for default parameters init in generators
123124
private AstNode memberExprNode;
124125

125126
{
@@ -362,6 +363,14 @@ public void addLiveLocals(Node node, int[] locals) {
362363
liveLocals.put(node, locals);
363364
}
364365

366+
public Node getGeneratorParamInitBlock() {
367+
return generatorParamInitBlock;
368+
}
369+
370+
public void setGeneratorParamInitBlock(Node block) {
371+
generatorParamInitBlock = block;
372+
}
373+
365374
@Override
366375
public int addFunction(FunctionNode fnNode) {
367376
int result = super.addFunction(fnNode);

rhino/src/main/java/org/mozilla/javascript/optimizer/BodyCodegen.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,19 +129,33 @@ private void generateGenerator() {
129129
+ ")Lorg/mozilla/javascript/Scriptable;");
130130
cfw.addAStore(variableObjectLocal);
131131

132+
// Evaluate default params for generators after creating activation scope
133+
// so defaults have access to arguments and prior params.
134+
// See Ecma 2026, 10.2.11 FunctionDeclarationInstantiation
135+
Node paramInitBlock = ((FunctionNode) scriptOrFn).getGeneratorParamInitBlock();
136+
if (paramInitBlock != null) {
137+
Node paramInit = paramInitBlock.getFirstChild();
138+
while (paramInit != null) {
139+
generateStatement(paramInit);
140+
paramInit = paramInit.getNext();
141+
}
142+
}
143+
132144
generateNestedFunctionInits();
133145

134146
// create the NativeGenerator object that we return
135-
cfw.addALoad(funObjLocal);
147+
cfw.addALoad(contextLocal);
136148
cfw.addALoad(variableObjectLocal);
137149
cfw.addALoad(thisObjLocal);
150+
cfw.addALoad(funObjLocal);
138151
cfw.addLoadConstant(maxLocals);
139152
cfw.addLoadConstant(maxStack);
140153
addOptRuntimeInvoke(
141154
"createNativeGenerator",
142-
"(Lorg/mozilla/javascript/JSFunction;"
155+
"(Lorg/mozilla/javascript/Context;"
156+
+ "Lorg/mozilla/javascript/Scriptable;"
143157
+ "Lorg/mozilla/javascript/Scriptable;"
144-
+ "Lorg/mozilla/javascript/Scriptable;II"
158+
+ "Lorg/mozilla/javascript/JSFunction;II"
145159
+ ")Lorg/mozilla/javascript/Scriptable;");
146160

147161
cfw.add(ByteCode.ARETURN);

rhino/src/main/java/org/mozilla/javascript/optimizer/OptRuntime.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,14 @@ public static void throwStopIteration(Object scope, Object genState) {
268268
}
269269

270270
public static Scriptable createNativeGenerator(
271-
JSFunction funObj, Scriptable scope, Scriptable thisObj, int maxLocals, int maxStack) {
271+
Context cx,
272+
Scriptable scope,
273+
Scriptable thisObj,
274+
JSFunction funObj,
275+
int maxLocals,
276+
int maxStack) {
272277
GeneratorState gs = new GeneratorState(scope, thisObj, maxLocals, maxStack);
273-
if (Context.getCurrentContext().getLanguageVersion() >= Context.VERSION_ES6) {
278+
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
274279
return new ES6Generator(scope, funObj, gs);
275280
} else {
276281
return new NativeGenerator(scope, funObj, gs);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.mozilla.javascript.tests;
2+
3+
import org.junit.Test;
4+
import org.mozilla.javascript.testutils.Utils;
5+
6+
/**
7+
* Test that default parameter evaluation happens _before_ generator object is created. Ref: Ecma
8+
* 2026, 10.2.11 FunctionDeclarationInstantiation
9+
*/
10+
public class GeneratorDefaultParamsTest {
11+
12+
@Test
13+
public void testGeneratorCreatedAfterDeclInst() {
14+
// Default parameter evaluation modifies g.prototype to null
15+
// Generator instance should be created with the old prototype value
16+
Utils.assertWithAllModes_ES6(
17+
Boolean.FALSE,
18+
"var g = function*(a = (g.prototype = null)) {};"
19+
+ "var oldPrototype = g.prototype;"
20+
+ "var it = g();"
21+
+ "Object.getPrototypeOf(it) === oldPrototype;");
22+
}
23+
24+
@Test
25+
public void testArgumentsCaptureWithShadowing() {
26+
// The 'arguments' object should be captured in default params
27+
// even when shadowed by a function declaration
28+
Utils.assertWithAllModes_ES6(
29+
0,
30+
"var args;"
31+
+ "var g = function* (x = args = arguments) {"
32+
+ " function arguments() {}"
33+
+ "};"
34+
+ "g().next();"
35+
+ "args.length;");
36+
}
37+
38+
@Test
39+
public void testDefaultParamException() {
40+
// Exceptions in default parameter evaluation should propagate
41+
Utils.assertJavaScriptException_ES6(
42+
"Error:",
43+
"var g = function*(_ = (function() { throw new Error('test'); }())) {};" + "g();");
44+
}
45+
}

0 commit comments

Comments
 (0)