mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
047f682044
commit
a3ac74a860
@ -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. */
|
||||||
|
@ -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:
|
||||||
|
@ -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);
|
||||||
|
@ -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");
|
||||||
|
@ -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) \
|
||||||
|
@ -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. */
|
||||||
|
@ -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);
|
||||||
})();
|
})();
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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) \
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
128
js/src/tests/ecma_6/LexicalEnvironment/for-loop.js
Normal file
128
js/src/tests/ecma_6/LexicalEnvironment/for-loop.js
Normal 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");
|
@ -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());
|
||||||
|
@ -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
|
||||||
|
@ -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 © = 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 ©
|
||||||
|
}
|
||||||
|
|
||||||
StaticBlockObject *
|
StaticBlockObject *
|
||||||
StaticBlockObject::create(ExclusiveContext *cx)
|
StaticBlockObject::create(ExclusiveContext *cx)
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user