Bug 854037 - Make lexical declarations in the initializing component of a for(;;) loop create a fresh binding for each iteration of the loop. r=shu

This commit is contained in:
Jeff Walden 2015-03-27 12:29:50 -04:00
parent 047f682044
commit a3ac74a860
24 changed files with 407 additions and 32 deletions

View File

@ -5047,19 +5047,30 @@ BytecodeEmitter::emitNormalFor(ParseNode *pn, ptrdiff_t top)
ParseNode *forBody = pn->pn_right; ParseNode *forBody = pn->pn_right;
/* C-style for (init; cond; update) ... loop. */ /* C-style for (init; cond; update) ... loop. */
JSOp op = JSOP_POP; bool forLoopRequiresFreshening = false;
ParseNode *pn3 = forHead->pn_kid1; JSOp op;
if (!pn3) { ParseNode *init = forHead->pn_kid1;
// No initializer, but emit a nop so that there's somewhere to put the if (!init) {
// If there's no init, emit a nop so that there's somewhere to put the
// SRC_FOR annotation that IonBuilder will look for. // SRC_FOR annotation that IonBuilder will look for.
op = JSOP_NOP; op = JSOP_NOP;
} else if (init->isKind(PNK_FRESHENBLOCK)) {
// Also emit a nop, as above.
op = JSOP_NOP;
// The loop's init declaration was hoisted into an enclosing lexical
// scope node. Note that the block scope must be freshened each
// iteration.
forLoopRequiresFreshening = true;
} else { } else {
emittingForInit = true; emittingForInit = true;
if (!updateSourceCoordNotes(pn3->pn_pos.begin)) if (!updateSourceCoordNotes(init->pn_pos.begin))
return false; return false;
if (!emitTree(pn3)) if (!emitTree(init))
return false; return false;
emittingForInit = false; emittingForInit = false;
op = JSOP_POP;
} }
/* /*
@ -5099,19 +5110,38 @@ BytecodeEmitter::emitNormalFor(ParseNode *pn, ptrdiff_t top)
/* Set the second note offset so we can find the update part. */ /* Set the second note offset so we can find the update part. */
ptrdiff_t tmp2 = offset(); ptrdiff_t tmp2 = offset();
/* Set loop and enclosing "update" offsets, for continue. */ // Set loop and enclosing "update" offsets, for continue. Note that we
// continue to immediately *before* the block-freshening: continuing must
// refresh the block.
StmtInfoBCE *stmt = &stmtInfo; StmtInfoBCE *stmt = &stmtInfo;
do { do {
stmt->update = offset(); stmt->update = offset();
} while ((stmt = stmt->down) != nullptr && stmt->type == STMT_LABEL); } while ((stmt = stmt->down) != nullptr && stmt->type == STMT_LABEL);
// Freshen the block on the scope chain to expose distinct bindings for each loop
// iteration.
if (forLoopRequiresFreshening) {
// The scope chain only includes an actual block *if* the scope object
// is captured and therefore requires cloning. Get the static block
// object from the parent let-block statement (which *must* be the
// let-statement for the guarding condition to have held) and freshen
// if the block object needs cloning.
StmtInfoBCE *parent = stmtInfo.down;
MOZ_ASSERT(parent->type == STMT_BLOCK);
MOZ_ASSERT(parent->isBlockScope);
if (parent->staticScope->as<StaticBlockObject>().needsClone()) {
if (!emit1(JSOP_FRESHENBLOCKSCOPE))
return false;
}
}
/* Check for update code to do before the condition (if any). */ /* Check for update code to do before the condition (if any). */
pn3 = forHead->pn_kid3; if (ParseNode *update = forHead->pn_kid3) {
if (pn3) { if (!updateSourceCoordNotes(update->pn_pos.begin))
if (!updateSourceCoordNotes(pn3->pn_pos.begin))
return false; return false;
op = JSOP_POP; op = JSOP_POP;
if (!emitTree(pn3)) if (!emitTree(update))
return false; return false;
/* Always emit the POP or NOP to help IonBuilder. */ /* Always emit the POP or NOP to help IonBuilder. */

View File

@ -415,6 +415,7 @@ ContainsHoistedDeclaration(ExclusiveContext *cx, ParseNode *node, bool *result)
case PNK_FORIN: case PNK_FORIN:
case PNK_FOROF: case PNK_FOROF:
case PNK_FORHEAD: case PNK_FORHEAD:
case PNK_FRESHENBLOCK:
case PNK_CLASSMETHOD: case PNK_CLASSMETHOD:
case PNK_CLASSMETHODLIST: case PNK_CLASSMETHODLIST:
case PNK_CLASSNAMES: case PNK_CLASSNAMES:

View File

@ -499,6 +499,10 @@ class FullParseHandler
return new_<TernaryNode>(kind, JSOP_NOP, pn1, pn2, pn3, pos); return new_<TernaryNode>(kind, JSOP_NOP, pn1, pn2, pn3, pos);
} }
ParseNode *newFreshenBlock(const TokenPos &pos) {
return new_<NullaryNode>(PNK_FRESHENBLOCK, pos);
}
ParseNode *newSwitchStatement(uint32_t begin, ParseNode *discriminant, ParseNode *caseList) { ParseNode *newSwitchStatement(uint32_t begin, ParseNode *discriminant, ParseNode *caseList) {
TokenPos pos(begin, caseList->pn_pos.end); TokenPos pos(begin, caseList->pn_pos.end);
return new_<BinaryNode>(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList); return new_<BinaryNode>(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList);

View File

@ -208,6 +208,7 @@ PushNodeChildren(ParseNode *pn, NodeStack *stack)
case PNK_DEBUGGER: case PNK_DEBUGGER:
case PNK_EXPORT_BATCH_SPEC: case PNK_EXPORT_BATCH_SPEC:
case PNK_OBJECT_PROPERTY_NAME: case PNK_OBJECT_PROPERTY_NAME:
case PNK_FRESHENBLOCK:
MOZ_ASSERT(pn->isArity(PN_NULLARY)); MOZ_ASSERT(pn->isArity(PN_NULLARY));
MOZ_ASSERT(!pn->isUsed(), "handle non-trivial cases separately"); MOZ_ASSERT(!pn->isUsed(), "handle non-trivial cases separately");
MOZ_ASSERT(!pn->isDefn(), "handle non-trivial cases separately"); MOZ_ASSERT(!pn->isDefn(), "handle non-trivial cases separately");

View File

@ -146,6 +146,7 @@ class UpvarCookie
F(FORIN) \ F(FORIN) \
F(FOROF) \ F(FOROF) \
F(FORHEAD) \ F(FORHEAD) \
F(FRESHENBLOCK) \
F(ARGSBODY) \ F(ARGSBODY) \
F(SPREAD) \ F(SPREAD) \
F(MUTATEPROTO) \ F(MUTATEPROTO) \

View File

@ -4709,11 +4709,50 @@ Parser<FullParseHandler>::forStatement()
MOZ_ASSERT_IF(isForDecl, pn1->isArity(PN_LIST)); MOZ_ASSERT_IF(isForDecl, pn1->isArity(PN_LIST));
MOZ_ASSERT(!!blockObj == (isForDecl && pn1->isOp(JSOP_NOP))); MOZ_ASSERT(!!blockObj == (isForDecl && pn1->isOp(JSOP_NOP)));
// The form 'for (let <vars>; <expr2>; <expr3>) <stmt>' generates an // All forms of for-loop (for(;;), for-in, for-of) generate an implicit
// implicit block even if stmt is not a BlockStatement. // block to store any lexical variables declared by the loop-head. We
// If the loop has that exact form, then: // implement this by desugaring such loops. These:
// - forLetImpliedBlock is the node for the implicit block scope. //
// - forLetDecl is the node for the decl 'let <vars>'. // for (let/const <pattern-and-assigns>; <test>; <update>) <stmt>
// for (let <pattern> in <expr>) <stmt>
// for (let <pattern> of <expr>) <stmt>
//
// transform into almost these desugarings:
//
// let (<pattern-and-assigns>) { for (; <test>; <update>) <stmt> }
// let (<pattern>) { for (<pattern> in <expr>) <stmt> }
// let (<pattern>) { for (<pattern> of <expr>) <stmt> }
//
// This desugaring is not *quite* correct. Assignments in the head of a
// let-block are evaluated *outside* the scope of the variables declared by
// the let-block-head. But ES6 mandates that they be evaluated in the same
// scope, triggering used-before-initialization temporal dead zone errors
// as necessary. Bug 1069480 will fix this.
//
// Additionally, ES6 mandates that *each iteration* of a for-loop create a
// fresh binding of loop variables. For example:
//
// var funcs = [];
// for (let i = 0; i < 2; i++)
// funcs.push(function() { return i; });
// assertEq(funcs[0](), 0);
// assertEq(funcs[1](), 1);
//
// These semantics are implemented by "freshening" the implicit block --
// changing the scope chain to a fresh clone of the instantaneous block
// object -- each iteration, just before evaluating the "update" in
// for(;;) loops. (We don't implement this freshening for for-in/of loops,
// but soon: bug 449811.) No freshening occurs in for (const ...;;) as
// there's no point (you can't reassign consts), and moreover the spec
// requires it (which fact isn't exposed in-language but can be observed
// through the Debugger API).
//
// If the for-loop head includes a lexical declaration, then we create an
// implicit block scope, and:
//
// * forLetImpliedBlock is the node for the implicit block scope.
// * forLetDecl is the node for the decl 'let/const <pattern>'.
//
// Otherwise both are null. // Otherwise both are null.
ParseNode *forLetImpliedBlock = nullptr; ParseNode *forLetImpliedBlock = nullptr;
ParseNode *forLetDecl = nullptr; ParseNode *forLetDecl = nullptr;
@ -4885,23 +4924,45 @@ Parser<FullParseHandler>::forStatement()
headKind = PNK_FORHEAD; headKind = PNK_FORHEAD;
if (blockObj) { if (blockObj) {
/* // Ensure here that the previously-unchecked assignment mandate for
* Desugar 'for (let A; B; C) D' into 'let (A) { for (; B; C) D }' // const declarations holds.
* to induce the correct scoping for A. Ensure here that the previously
* unchecked assignment mandate for const declarations holds.
*/
if (!checkForHeadConstInitializers(pn1)) { if (!checkForHeadConstInitializers(pn1)) {
report(ParseError, false, nullptr, JSMSG_BAD_CONST_DECL); report(ParseError, false, nullptr, JSMSG_BAD_CONST_DECL);
return null(); return null();
} }
// Desugar
//
// for (let INIT; TEST; UPDATE) STMT
//
// into
//
// let (INIT) { for (; TEST; UPDATE) STMT }
//
// to provide a block scope for INIT.
forLetImpliedBlock = pushLetScope(blockObj, &letStmt); forLetImpliedBlock = pushLetScope(blockObj, &letStmt);
if (!forLetImpliedBlock) if (!forLetImpliedBlock)
return null(); return null();
letStmt.isForLetBlock = true; letStmt.isForLetBlock = true;
forLetDecl = pn1; forLetDecl = pn1;
// The above transformation isn't enough to implement |INIT|
// scoping, because each loop iteration must see separate bindings
// of |INIT|. We handle this by replacing the block on the scope
// chain with a new block, copying the old one's contents, each
// iteration. We supply a special PNK_FRESHENBLOCK node as the
// |let INIT| node for |for(let INIT;;)| loop heads to distinguish
// such nodes from *actual*, non-desugared use of the above syntax.
// (We don't do this for PNK_CONST nodes because the spec says no
// freshening happens -- observable with the Debugger API.)
if (pn1->isKind(PNK_CONST)) {
pn1 = nullptr; pn1 = nullptr;
} else {
pn1 = handler.newFreshenBlock(pn1->pn_pos);
if (!pn1)
return null();
}
} }
/* Parse the loop condition or null into pn2. */ /* Parse the loop condition or null into pn2. */

View File

@ -2,5 +2,5 @@
var x; var x;
for (let j = 0; j < 1; j = j + 1) for (let j = 0; j < 1; j = j + 1)
x = function() { return j; }; x = function() { return j; };
assertEq(x(), 1); assertEq(x(), 0);
})(); })();

View File

@ -3011,7 +3011,6 @@ static const VMFunction PopBlockScopeInfo = FunctionInfo<PopBlockScopeFn>(jit::P
bool bool
BaselineCompiler::emit_JSOP_POPBLOCKSCOPE() BaselineCompiler::emit_JSOP_POPBLOCKSCOPE()
{ {
// Call a stub to pop the block from the block chain.
prepareVMCall(); prepareVMCall();
masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
@ -3020,6 +3019,21 @@ BaselineCompiler::emit_JSOP_POPBLOCKSCOPE()
return callVM(PopBlockScopeInfo); return callVM(PopBlockScopeInfo);
} }
typedef bool (*FreshenBlockScopeFn)(JSContext *, BaselineFrame *);
static const VMFunction FreshenBlockScopeInfo =
FunctionInfo<FreshenBlockScopeFn>(jit::FreshenBlockScope);
bool
BaselineCompiler::emit_JSOP_FRESHENBLOCKSCOPE()
{
prepareVMCall();
masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
pushArg(R0.scratchReg());
return callVM(FreshenBlockScopeInfo);
}
typedef bool (*DebugLeaveBlockFn)(JSContext *, BaselineFrame *, jsbytecode *); typedef bool (*DebugLeaveBlockFn)(JSContext *, BaselineFrame *, jsbytecode *);
static const VMFunction DebugLeaveBlockInfo = FunctionInfo<DebugLeaveBlockFn>(jit::DebugLeaveBlock); static const VMFunction DebugLeaveBlockInfo = FunctionInfo<DebugLeaveBlockFn>(jit::DebugLeaveBlock);

View File

@ -172,6 +172,7 @@ namespace jit {
_(JSOP_RETSUB) \ _(JSOP_RETSUB) \
_(JSOP_PUSHBLOCKSCOPE) \ _(JSOP_PUSHBLOCKSCOPE) \
_(JSOP_POPBLOCKSCOPE) \ _(JSOP_POPBLOCKSCOPE) \
_(JSOP_FRESHENBLOCKSCOPE) \
_(JSOP_DEBUGLEAVEBLOCK) \ _(JSOP_DEBUGLEAVEBLOCK) \
_(JSOP_EXCEPTION) \ _(JSOP_EXCEPTION) \
_(JSOP_DEBUGGER) \ _(JSOP_DEBUGGER) \

View File

@ -45,6 +45,13 @@ BaselineFrame::popWith(JSContext *cx)
popOffScopeChain(); popOffScopeChain();
} }
inline void
BaselineFrame::replaceInnermostScope(ScopeObject &scope)
{
MOZ_ASSERT(scope.enclosingScope() == scopeChain_->as<ScopeObject>().enclosingScope());
scopeChain_ = &scope;
}
inline bool inline bool
BaselineFrame::pushBlock(JSContext *cx, Handle<StaticBlockObject *> block) BaselineFrame::pushBlock(JSContext *cx, Handle<StaticBlockObject *> block)
{ {
@ -66,6 +73,18 @@ BaselineFrame::popBlock(JSContext *cx)
popOffScopeChain(); popOffScopeChain();
} }
inline bool
BaselineFrame::freshenBlock(JSContext *cx)
{
Rooted<ClonedBlockObject*> current(cx, &scopeChain_->as<ClonedBlockObject>());
ClonedBlockObject *clone = ClonedBlockObject::clone(cx, current);
if (!clone)
return false;
replaceInnermostScope(*clone);
return true;
}
inline CallObject & inline CallObject &
BaselineFrame::callObj() const BaselineFrame::callObj() const
{ {

View File

@ -133,6 +133,7 @@ class BaselineFrame
inline void pushOnScopeChain(ScopeObject &scope); inline void pushOnScopeChain(ScopeObject &scope);
inline void popOffScopeChain(); inline void popOffScopeChain();
inline void replaceInnermostScope(ScopeObject &scope);
inline void popWith(JSContext *cx); inline void popWith(JSContext *cx);
@ -249,6 +250,7 @@ class BaselineFrame
inline bool pushBlock(JSContext *cx, Handle<StaticBlockObject *> block); inline bool pushBlock(JSContext *cx, Handle<StaticBlockObject *> block);
inline void popBlock(JSContext *cx); inline void popBlock(JSContext *cx);
inline bool freshenBlock(JSContext *cx);
bool strictEvalPrologue(JSContext *cx); bool strictEvalPrologue(JSContext *cx);
bool heavyweightFunPrologue(JSContext *cx); bool heavyweightFunPrologue(JSContext *cx);

View File

@ -2005,6 +2005,17 @@ IonBuilder::inspectOpcode(JSOp op)
// Just fall through to the unsupported bytecode case. // Just fall through to the unsupported bytecode case.
break; break;
#ifdef DEBUG
case JSOP_PUSHBLOCKSCOPE:
case JSOP_FRESHENBLOCKSCOPE:
case JSOP_POPBLOCKSCOPE:
// These opcodes are currently unhandled by Ion, but in principle
// there's no reason they couldn't be. Whenever this happens, OSR will
// have to consider that JSOP_FRESHENBLOCK mutates the scope chain --
// right now it caches the scope chain in MBasicBlock::scopeChain().
// That stale value will have to be updated when JSOP_FRESHENBLOCK is
// encountered.
#endif
default: default:
break; break;
} }
@ -3150,6 +3161,7 @@ IonBuilder::forLoop(JSOp op, jssrcnote *sn)
// body: // body:
// ; [body] // ; [body]
// [increment:] // [increment:]
// [FRESHENBLOCKSCOPE, if needed by a cloned block]
// ; [increment] // ; [increment]
// [cond:] // [cond:]
// LOOPENTRY // LOOPENTRY
@ -3157,6 +3169,10 @@ IonBuilder::forLoop(JSOp op, jssrcnote *sn)
// //
// If there is a condition (condpc != ifne), this acts similar to a while // If there is a condition (condpc != ifne), this acts similar to a while
// loop otherwise, it acts like a do-while loop. // loop otherwise, it acts like a do-while loop.
//
// Note that currently Ion does not compile pushblockscope/popblockscope as
// necessary prerequisites to freshenblockscope. So the code below doesn't
// and needn't consider the implications of freshenblockscope.
jsbytecode *bodyStart = pc; jsbytecode *bodyStart = pc;
jsbytecode *bodyEnd = updatepc; jsbytecode *bodyEnd = updatepc;
jsbytecode *loopEntry = condpc; jsbytecode *loopEntry = condpc;

View File

@ -994,6 +994,12 @@ PopBlockScope(JSContext *cx, BaselineFrame *frame)
return true; return true;
} }
bool
FreshenBlockScope(JSContext *cx, BaselineFrame *frame)
{
return frame->freshenBlock(cx);
}
bool bool
DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc) DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc)
{ {

View File

@ -728,6 +728,7 @@ bool LeaveWith(JSContext *cx, BaselineFrame *frame);
bool PushBlockScope(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block); bool PushBlockScope(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block);
bool PopBlockScope(JSContext *cx, BaselineFrame *frame); bool PopBlockScope(JSContext *cx, BaselineFrame *frame);
bool FreshenBlockScope(JSContext *cx, BaselineFrame *frame);
bool DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc); bool DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc);
bool InitBaselineFrameForOsr(BaselineFrame *frame, InterpreterFrame *interpFrame, bool InitBaselineFrameForOsr(BaselineFrame *frame, InterpreterFrame *interpFrame,

View File

@ -147,7 +147,7 @@ class JSObject : public js::gc::Cell
} }
/* /*
* Whether this is the only object which has its specified gbroup. This * Whether this is the only object which has its specified group. This
* object will have its group constructed lazily as needed by analysis. * object will have its group constructed lazily as needed by analysis.
*/ */
bool isSingleton() const { bool isSingleton() const {
@ -180,7 +180,7 @@ class JSObject : public js::gc::Cell
js::HandleObjectGroup group); js::HandleObjectGroup group);
// Set the shape of an object. This pointer is valid for native objects and // Set the shape of an object. This pointer is valid for native objects and
// some non-native objects. After creating an object, tobjects for which // some non-native objects. After creating an object, the objects for which
// the shape pointer is invalid need to overwrite this pointer before a GC // the shape pointer is invalid need to overwrite this pointer before a GC
// can occur. // can occur.
inline void setInitialShapeMaybeNonNative(js::Shape *shape); inline void setInitialShapeMaybeNonNative(js::Shape *shape);

View File

@ -2560,7 +2560,10 @@ ASTSerializer::statement(ParseNode *pn, MutableHandleValue dst)
RootedValue init(cx), test(cx), update(cx); RootedValue init(cx), test(cx), update(cx);
return forInit(head->pn_kid1, &init) && return forInit(head->pn_kid1 && !head->pn_kid1->isKind(PNK_FRESHENBLOCK)
? head->pn_kid1
: nullptr,
&init) &&
optExpression(head->pn_kid2, &test) && optExpression(head->pn_kid2, &test) &&
optExpression(head->pn_kid3, &update) && optExpression(head->pn_kid3, &update) &&
builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst); builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst);

View File

@ -0,0 +1,128 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
var gTestfile = "for-loop.js";
//-----------------------------------------------------------------------------
var BUGNUMBER = 985733;
var summary =
"ES6 for-loop semantics for for(;;) loops whose heads contain lexical "
"declarations";
print(BUGNUMBER + ": " + summary);
/**************
* BEGIN TEST *
**************/
function isError(code, type)
{
try
{
Function(code);
throw new Error("didn't throw");
}
catch (e)
{
assertEq(e instanceof type, true,
"unexpected error for `" + code + "`: got " + e);
}
}
function isOK(code)
{
Function(code);
}
isError("for (const x; ; ) ;", SyntaxError);
isError("for (const x = 5, y; ; ) ;", SyntaxError);
isError("for (const [z]; ; ) ;", SyntaxError);
//isError("for (const [z, z]; ; ) ;", SyntaxError);
//isError("for (const [z, z] = [0, 1]; ; ) ;", SyntaxError);
isOK("for (let x; ; ) ;");
isOK("for (let x = 5, y; ; ) ;");
// I'm fairly sure this is supposed to work: the negative-lookahead rules in
// IterationStatement ensure that |for (let| *always* is a loop header starting
// with a lexical declaration. But I'm not 100% certain, so these tests might
// need to be fixed when we implement the negative-lookahead restrictions.
isOK("for (let [z] = [3]; ; ) ;");
isError("for (let [z, z]; ; ) ;", SyntaxError); // because missing initializer
// This is wrong! Per 13.2.1.1, "It is a Syntax Error if the BoundNames of
// BindingList contains any duplicate entries." But we don't implement this
// yet, so it becomes a TypeError at runtime.
isError("for (let [z, z] = [0, 1]; ; ) ;", TypeError);
// A for-loop with lexical declarations, with a mixture of bindings that are and
// aren't aliased. (The mixture stress-tests any code that incorrectly assumes
// all bindings are aliased.)
var funcs = [];
for (let [i, j, k] = [0, 1, 2]; i < 10; i++)
funcs.push(() => i);
assertEq(funcs[0](), 0);
assertEq(funcs[1](), 1);
assertEq(funcs[2](), 2);
assertEq(funcs[3](), 3);
assertEq(funcs[4](), 4);
assertEq(funcs[5](), 5);
assertEq(funcs[6](), 6);
assertEq(funcs[7](), 7);
assertEq(funcs[8](), 8);
assertEq(funcs[9](), 9);
var outer = "OUTER V IGNORE";
var save;
for (let outer = (save = function() { return outer; }); ; )
break;
assertEq(save(), "OUTER V IGNORE",
"this is actually a bug: fix for(;;) loops to evaluate init RHSes " +
"in the block scope containing all the LHS bindings!");
var funcs = [];
function t(i, name, expect)
{
assertEq(funcs[i].name, name);
assertEq(funcs[i](), expect);
}
if (save() !== "OUTER V IGNORE")
{
var v = "OUTER V IGNORE";
var i = 0;
for (let v = (funcs.push(function init() { return v; }),
0);
v = (funcs.push(function test() { return v; }),
v + 1);
v = (funcs.push(function incr() { return v; }),
v + 1))
{
v = (funcs.push(function body() { return v; }),
v + 1);
i++;
if (i >= 3)
break;
}
t(0, "init", 2);
t(1, "test", 2);
t(2, "body", 2);
t(3, "incr", 5);
t(4, "test", 5);
t(5, "body", 5);
t(6, "incr", 8);
t(7, "test", 8);
t(8, "body", 8);
assertEq(funcs.length, 9);
}
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete");

View File

@ -1681,7 +1681,6 @@ CASE(JSOP_UNUSED189)
CASE(JSOP_UNUSED190) CASE(JSOP_UNUSED190)
CASE(JSOP_UNUSED191) CASE(JSOP_UNUSED191)
CASE(JSOP_UNUSED192) CASE(JSOP_UNUSED192)
CASE(JSOP_UNUSED196)
CASE(JSOP_UNUSED209) CASE(JSOP_UNUSED209)
CASE(JSOP_UNUSED210) CASE(JSOP_UNUSED210)
CASE(JSOP_UNUSED211) CASE(JSOP_UNUSED211)
@ -3440,6 +3439,13 @@ CASE(JSOP_DEBUGLEAVEBLOCK)
} }
END_CASE(JSOP_DEBUGLEAVEBLOCK) END_CASE(JSOP_DEBUGLEAVEBLOCK)
CASE(JSOP_FRESHENBLOCKSCOPE)
{
if (!REGS.fp()->freshenBlock(cx))
goto error;
}
END_CASE(JSOP_FRESHENBLOCKSCOPE)
CASE(JSOP_GENERATOR) CASE(JSOP_GENERATOR)
{ {
MOZ_ASSERT(!cx->isExceptionPending()); MOZ_ASSERT(!cx->isExceptionPending());

View File

@ -1649,8 +1649,6 @@
*/ \ */ \
macro(JSOP_GETXPROP, 195,"getxprop", NULL, 5, 1, 1, JOF_ATOM|JOF_PROP|JOF_TYPESET) \ macro(JSOP_GETXPROP, 195,"getxprop", NULL, 5, 1, 1, JOF_ATOM|JOF_PROP|JOF_TYPESET) \
\ \
macro(JSOP_UNUSED196, 196,"unused196", NULL, 1, 0, 0, JOF_BYTE) \
\
/* /*
* Pops the top stack value as 'val' and pushes 'typeof val'. Note that * Pops the top stack value as 'val' and pushes 'typeof val'. Note that
* this opcode isn't used when, in the original source code, 'val' is a * this opcode isn't used when, in the original source code, 'val' is a
@ -1661,9 +1659,21 @@
* Operands: * Operands:
* Stack: val => (typeof val) * Stack: val => (typeof val)
*/ \ */ \
macro(JSOP_TYPEOFEXPR, 197,"typeofexpr", NULL, 1, 1, 1, JOF_BYTE|JOF_DETECTING) \ macro(JSOP_TYPEOFEXPR, 196,"typeofexpr", NULL, 1, 1, 1, JOF_BYTE|JOF_DETECTING) \
\ \
/* Block-local scope support. */ \ /* Block-local scope support. */ \
/*
* Replaces the current block on the scope chain with a fresh block
* that copies all the bindings in the bock. This operation implements the
* behavior of inducing a fresh block scope for every iteration of a
* for(let ...; ...; ...) loop, if any declarations induced by such a loop
* are captured within the loop.
* Category: Variables and Scopes
* Type: Block-local Scope
* Operands:
* Stack: =>
*/ \
macro(JSOP_FRESHENBLOCKSCOPE,197,"freshenblockscope", NULL, 1, 0, 0, JOF_BYTE) \
/* /*
* Pushes block onto the scope chain. * Pushes block onto the scope chain.
* Category: Variables and Scopes * Category: Variables and Scopes

View File

@ -660,6 +660,35 @@ ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame)
} }
} }
/* static */ ClonedBlockObject *
ClonedBlockObject::clone(ExclusiveContext *cx, Handle<ClonedBlockObject*> block)
{
RootedObject enclosing(cx, &block->enclosingScope());
MOZ_ASSERT(block->getClass() == &BlockObject::class_);
RootedObjectGroup cloneGroup(cx, block->group());
RootedShape cloneShape(cx, block->lastProperty());
JSObject *obj = JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, cloneShape, cloneGroup);
if (!obj)
return nullptr;
ClonedBlockObject &copy = obj->as<ClonedBlockObject>();
MOZ_ASSERT(!copy.inDictionaryMode());
MOZ_ASSERT(copy.isDelegate());
MOZ_ASSERT(block->slotSpan() == copy.slotSpan());
MOZ_ASSERT(copy.slotSpan() >= copy.numVariables() + RESERVED_SLOTS);
copy.setReservedSlot(SCOPE_CHAIN_SLOT, block->getReservedSlot(SCOPE_CHAIN_SLOT));
for (uint32_t i = 0, count = copy.numVariables(); i < count; i++)
copy.setVar(i, block->var(i, DONT_CHECK_ALIASING), DONT_CHECK_ALIASING);
return &copy;
}
StaticBlockObject * StaticBlockObject *
StaticBlockObject::create(ExclusiveContext *cx) StaticBlockObject::create(ExclusiveContext *cx)
{ {

View File

@ -669,6 +669,12 @@ class ClonedBlockObject : public BlockObject
/* Copy in all the unaliased formals and locals. */ /* Copy in all the unaliased formals and locals. */
void copyUnaliasedValues(AbstractFramePtr frame); void copyUnaliasedValues(AbstractFramePtr frame);
/*
* Create a new ClonedBlockObject with the same enclosing scope and
* variable values as this.
*/
static ClonedBlockObject *clone(ExclusiveContext *cx, Handle<ClonedBlockObject*> block);
}; };
// Internal scope object used by JSOP_BINDNAME upon encountering an // Internal scope object used by JSOP_BINDNAME upon encountering an

View File

@ -207,6 +207,14 @@ InterpreterFrame::popOffScopeChain()
scopeChain_ = &scopeChain_->as<ScopeObject>().enclosingScope(); scopeChain_ = &scopeChain_->as<ScopeObject>().enclosingScope();
} }
inline void
InterpreterFrame::replaceInnermostScope(ScopeObject &scope)
{
MOZ_ASSERT(flags_ & HAS_SCOPECHAIN);
MOZ_ASSERT(scope.enclosingScope() == scopeChain_->as<ScopeObject>().enclosingScope());
scopeChain_ = &scope;
}
bool bool
InterpreterFrame::hasCallObj() const InterpreterFrame::hasCallObj() const
{ {
@ -799,6 +807,14 @@ AbstractFramePtr::thisValue() const
return asRematerializedFrame()->thisValue(); return asRematerializedFrame()->thisValue();
} }
inline bool
AbstractFramePtr::freshenBlock(JSContext *cx) const
{
if (isInterpreterFrame())
return asInterpreterFrame()->freshenBlock(cx);
return asBaselineFrame()->freshenBlock(cx);
}
inline void inline void
AbstractFramePtr::popBlock(JSContext *cx) const AbstractFramePtr::popBlock(JSContext *cx) const
{ {

View File

@ -293,6 +293,19 @@ InterpreterFrame::pushBlock(JSContext *cx, StaticBlockObject &block)
return true; return true;
} }
bool
InterpreterFrame::freshenBlock(JSContext *cx)
{
MOZ_ASSERT(flags_ & HAS_SCOPECHAIN);
Rooted<ClonedBlockObject*> block(cx, &scopeChain_->as<ClonedBlockObject>());
ClonedBlockObject *fresh = ClonedBlockObject::clone(cx, block);
if (!fresh)
return false;
replaceInnermostScope(*fresh);
return true;
}
void void
InterpreterFrame::popBlock(JSContext *cx) InterpreterFrame::popBlock(JSContext *cx)
{ {

View File

@ -28,6 +28,7 @@ class AsmJSModule;
class InterpreterRegs; class InterpreterRegs;
class CallObject; class CallObject;
class ScopeObject; class ScopeObject;
class ClonedBlockObject;
class ScriptFrameIter; class ScriptFrameIter;
class SPSProfiler; class SPSProfiler;
class InterpreterFrame; class InterpreterFrame;
@ -234,6 +235,8 @@ class AbstractFramePtr
bool hasPushedSPSFrame() const; bool hasPushedSPSFrame() const;
inline bool freshenBlock(JSContext *cx) const;
inline void popBlock(JSContext *cx) const; inline void popBlock(JSContext *cx) const;
inline void popWith(JSContext *cx) const; inline void popWith(JSContext *cx) const;
@ -587,14 +590,18 @@ class InterpreterFrame
inline void pushOnScopeChain(ScopeObject &scope); inline void pushOnScopeChain(ScopeObject &scope);
inline void popOffScopeChain(); inline void popOffScopeChain();
inline void replaceInnermostScope(ScopeObject &scope);
/* /*
* For blocks with aliased locals, these interfaces push and pop entries on * For blocks with aliased locals, these interfaces push and pop entries on
* the scope chain. * the scope chain. The "freshen" operation replaces the current block
* with a fresh copy of it, to implement semantics providing distinct
* bindings per iteration of a for-loop.
*/ */
bool pushBlock(JSContext *cx, StaticBlockObject &block); bool pushBlock(JSContext *cx, StaticBlockObject &block);
void popBlock(JSContext *cx); void popBlock(JSContext *cx);
bool freshenBlock(JSContext *cx);
/* /*
* With * With