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
013aeebb92
commit
7111f4b468
@ -5047,19 +5047,30 @@ BytecodeEmitter::emitNormalFor(ParseNode *pn, ptrdiff_t top)
|
||||
ParseNode *forBody = pn->pn_right;
|
||||
|
||||
/* C-style for (init; cond; update) ... loop. */
|
||||
JSOp op = JSOP_POP;
|
||||
ParseNode *pn3 = forHead->pn_kid1;
|
||||
if (!pn3) {
|
||||
// No initializer, but emit a nop so that there's somewhere to put the
|
||||
bool forLoopRequiresFreshening = false;
|
||||
JSOp op;
|
||||
ParseNode *init = forHead->pn_kid1;
|
||||
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.
|
||||
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 {
|
||||
emittingForInit = true;
|
||||
if (!updateSourceCoordNotes(pn3->pn_pos.begin))
|
||||
if (!updateSourceCoordNotes(init->pn_pos.begin))
|
||||
return false;
|
||||
if (!emitTree(pn3))
|
||||
if (!emitTree(init))
|
||||
return 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. */
|
||||
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;
|
||||
do {
|
||||
stmt->update = offset();
|
||||
} 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). */
|
||||
pn3 = forHead->pn_kid3;
|
||||
if (pn3) {
|
||||
if (!updateSourceCoordNotes(pn3->pn_pos.begin))
|
||||
if (ParseNode *update = forHead->pn_kid3) {
|
||||
if (!updateSourceCoordNotes(update->pn_pos.begin))
|
||||
return false;
|
||||
op = JSOP_POP;
|
||||
if (!emitTree(pn3))
|
||||
if (!emitTree(update))
|
||||
return false;
|
||||
|
||||
/* 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_FOROF:
|
||||
case PNK_FORHEAD:
|
||||
case PNK_FRESHENBLOCK:
|
||||
case PNK_CLASSMETHOD:
|
||||
case PNK_CLASSMETHODLIST:
|
||||
case PNK_CLASSNAMES:
|
||||
|
@ -499,6 +499,10 @@ class FullParseHandler
|
||||
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) {
|
||||
TokenPos pos(begin, caseList->pn_pos.end);
|
||||
return new_<BinaryNode>(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList);
|
||||
|
@ -208,6 +208,7 @@ PushNodeChildren(ParseNode *pn, NodeStack *stack)
|
||||
case PNK_DEBUGGER:
|
||||
case PNK_EXPORT_BATCH_SPEC:
|
||||
case PNK_OBJECT_PROPERTY_NAME:
|
||||
case PNK_FRESHENBLOCK:
|
||||
MOZ_ASSERT(pn->isArity(PN_NULLARY));
|
||||
MOZ_ASSERT(!pn->isUsed(), "handle non-trivial cases separately");
|
||||
MOZ_ASSERT(!pn->isDefn(), "handle non-trivial cases separately");
|
||||
|
@ -146,6 +146,7 @@ class UpvarCookie
|
||||
F(FORIN) \
|
||||
F(FOROF) \
|
||||
F(FORHEAD) \
|
||||
F(FRESHENBLOCK) \
|
||||
F(ARGSBODY) \
|
||||
F(SPREAD) \
|
||||
F(MUTATEPROTO) \
|
||||
|
@ -4709,11 +4709,50 @@ Parser<FullParseHandler>::forStatement()
|
||||
MOZ_ASSERT_IF(isForDecl, pn1->isArity(PN_LIST));
|
||||
MOZ_ASSERT(!!blockObj == (isForDecl && pn1->isOp(JSOP_NOP)));
|
||||
|
||||
// The form 'for (let <vars>; <expr2>; <expr3>) <stmt>' generates an
|
||||
// implicit block even if stmt is not a BlockStatement.
|
||||
// If the loop has that exact form, then:
|
||||
// - forLetImpliedBlock is the node for the implicit block scope.
|
||||
// - forLetDecl is the node for the decl 'let <vars>'.
|
||||
// All forms of for-loop (for(;;), for-in, for-of) generate an implicit
|
||||
// block to store any lexical variables declared by the loop-head. We
|
||||
// implement this by desugaring such loops. These:
|
||||
//
|
||||
// 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.
|
||||
ParseNode *forLetImpliedBlock = nullptr;
|
||||
ParseNode *forLetDecl = nullptr;
|
||||
@ -4885,23 +4924,45 @@ Parser<FullParseHandler>::forStatement()
|
||||
headKind = PNK_FORHEAD;
|
||||
|
||||
if (blockObj) {
|
||||
/*
|
||||
* Desugar 'for (let A; B; C) D' into 'let (A) { for (; B; C) D }'
|
||||
* to induce the correct scoping for A. Ensure here that the previously
|
||||
* unchecked assignment mandate for const declarations holds.
|
||||
*/
|
||||
// Ensure here that the previously-unchecked assignment mandate for
|
||||
// const declarations holds.
|
||||
if (!checkForHeadConstInitializers(pn1)) {
|
||||
report(ParseError, false, nullptr, JSMSG_BAD_CONST_DECL);
|
||||
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);
|
||||
if (!forLetImpliedBlock)
|
||||
return null();
|
||||
letStmt.isForLetBlock = true;
|
||||
|
||||
forLetDecl = pn1;
|
||||
pn1 = nullptr;
|
||||
|
||||
// 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;
|
||||
} else {
|
||||
pn1 = handler.newFreshenBlock(pn1->pn_pos);
|
||||
if (!pn1)
|
||||
return null();
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse the loop condition or null into pn2. */
|
||||
|
@ -2,5 +2,5 @@
|
||||
var x;
|
||||
for (let j = 0; j < 1; j = j + 1)
|
||||
x = function() { return j; };
|
||||
assertEq(x(), 1);
|
||||
assertEq(x(), 0);
|
||||
})();
|
||||
|
@ -3011,7 +3011,6 @@ static const VMFunction PopBlockScopeInfo = FunctionInfo<PopBlockScopeFn>(jit::P
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_POPBLOCKSCOPE()
|
||||
{
|
||||
// Call a stub to pop the block from the block chain.
|
||||
prepareVMCall();
|
||||
|
||||
masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
|
||||
@ -3020,6 +3019,21 @@ BaselineCompiler::emit_JSOP_POPBLOCKSCOPE()
|
||||
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 *);
|
||||
static const VMFunction DebugLeaveBlockInfo = FunctionInfo<DebugLeaveBlockFn>(jit::DebugLeaveBlock);
|
||||
|
||||
|
@ -172,6 +172,7 @@ namespace jit {
|
||||
_(JSOP_RETSUB) \
|
||||
_(JSOP_PUSHBLOCKSCOPE) \
|
||||
_(JSOP_POPBLOCKSCOPE) \
|
||||
_(JSOP_FRESHENBLOCKSCOPE) \
|
||||
_(JSOP_DEBUGLEAVEBLOCK) \
|
||||
_(JSOP_EXCEPTION) \
|
||||
_(JSOP_DEBUGGER) \
|
||||
|
@ -45,6 +45,13 @@ BaselineFrame::popWith(JSContext *cx)
|
||||
popOffScopeChain();
|
||||
}
|
||||
|
||||
inline void
|
||||
BaselineFrame::replaceInnermostScope(ScopeObject &scope)
|
||||
{
|
||||
MOZ_ASSERT(scope.enclosingScope() == scopeChain_->as<ScopeObject>().enclosingScope());
|
||||
scopeChain_ = &scope;
|
||||
}
|
||||
|
||||
inline bool
|
||||
BaselineFrame::pushBlock(JSContext *cx, Handle<StaticBlockObject *> block)
|
||||
{
|
||||
@ -66,6 +73,18 @@ BaselineFrame::popBlock(JSContext *cx)
|
||||
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 &
|
||||
BaselineFrame::callObj() const
|
||||
{
|
||||
|
@ -133,6 +133,7 @@ class BaselineFrame
|
||||
|
||||
inline void pushOnScopeChain(ScopeObject &scope);
|
||||
inline void popOffScopeChain();
|
||||
inline void replaceInnermostScope(ScopeObject &scope);
|
||||
|
||||
inline void popWith(JSContext *cx);
|
||||
|
||||
@ -249,6 +250,7 @@ class BaselineFrame
|
||||
|
||||
inline bool pushBlock(JSContext *cx, Handle<StaticBlockObject *> block);
|
||||
inline void popBlock(JSContext *cx);
|
||||
inline bool freshenBlock(JSContext *cx);
|
||||
|
||||
bool strictEvalPrologue(JSContext *cx);
|
||||
bool heavyweightFunPrologue(JSContext *cx);
|
||||
|
@ -2005,6 +2005,17 @@ IonBuilder::inspectOpcode(JSOp op)
|
||||
// Just fall through to the unsupported bytecode case.
|
||||
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:
|
||||
break;
|
||||
}
|
||||
@ -3150,6 +3161,7 @@ IonBuilder::forLoop(JSOp op, jssrcnote *sn)
|
||||
// body:
|
||||
// ; [body]
|
||||
// [increment:]
|
||||
// [FRESHENBLOCKSCOPE, if needed by a cloned block]
|
||||
// ; [increment]
|
||||
// [cond:]
|
||||
// LOOPENTRY
|
||||
@ -3157,6 +3169,10 @@ IonBuilder::forLoop(JSOp op, jssrcnote *sn)
|
||||
//
|
||||
// If there is a condition (condpc != ifne), this acts similar to a while
|
||||
// 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 *bodyEnd = updatepc;
|
||||
jsbytecode *loopEntry = condpc;
|
||||
|
@ -994,6 +994,12 @@ PopBlockScope(JSContext *cx, BaselineFrame *frame)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
FreshenBlockScope(JSContext *cx, BaselineFrame *frame)
|
||||
{
|
||||
return frame->freshenBlock(cx);
|
||||
}
|
||||
|
||||
bool
|
||||
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 PopBlockScope(JSContext *cx, BaselineFrame *frame);
|
||||
bool FreshenBlockScope(JSContext *cx, BaselineFrame *frame);
|
||||
bool DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc);
|
||||
|
||||
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.
|
||||
*/
|
||||
bool isSingleton() const {
|
||||
@ -180,7 +180,7 @@ class JSObject : public js::gc::Cell
|
||||
js::HandleObjectGroup group);
|
||||
|
||||
// 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
|
||||
// can occur.
|
||||
inline void setInitialShapeMaybeNonNative(js::Shape *shape);
|
||||
|
@ -2560,7 +2560,10 @@ ASTSerializer::statement(ParseNode *pn, MutableHandleValue dst)
|
||||
|
||||
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_kid3, &update) &&
|
||||
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_UNUSED191)
|
||||
CASE(JSOP_UNUSED192)
|
||||
CASE(JSOP_UNUSED196)
|
||||
CASE(JSOP_UNUSED209)
|
||||
CASE(JSOP_UNUSED210)
|
||||
CASE(JSOP_UNUSED211)
|
||||
@ -3440,6 +3439,13 @@ CASE(JSOP_DEBUGLEAVEBLOCK)
|
||||
}
|
||||
END_CASE(JSOP_DEBUGLEAVEBLOCK)
|
||||
|
||||
CASE(JSOP_FRESHENBLOCKSCOPE)
|
||||
{
|
||||
if (!REGS.fp()->freshenBlock(cx))
|
||||
goto error;
|
||||
}
|
||||
END_CASE(JSOP_FRESHENBLOCKSCOPE)
|
||||
|
||||
CASE(JSOP_GENERATOR)
|
||||
{
|
||||
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_UNUSED196, 196,"unused196", NULL, 1, 0, 0, JOF_BYTE) \
|
||||
\
|
||||
/*
|
||||
* 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
|
||||
@ -1661,9 +1659,21 @@
|
||||
* Operands:
|
||||
* 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. */ \
|
||||
/*
|
||||
* 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.
|
||||
* 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::create(ExclusiveContext *cx)
|
||||
{
|
||||
|
@ -669,6 +669,12 @@ class ClonedBlockObject : public BlockObject
|
||||
|
||||
/* Copy in all the unaliased formals and locals. */
|
||||
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
|
||||
|
@ -207,6 +207,14 @@ InterpreterFrame::popOffScopeChain()
|
||||
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
|
||||
InterpreterFrame::hasCallObj() const
|
||||
{
|
||||
@ -799,6 +807,14 @@ AbstractFramePtr::thisValue() const
|
||||
return asRematerializedFrame()->thisValue();
|
||||
}
|
||||
|
||||
inline bool
|
||||
AbstractFramePtr::freshenBlock(JSContext *cx) const
|
||||
{
|
||||
if (isInterpreterFrame())
|
||||
return asInterpreterFrame()->freshenBlock(cx);
|
||||
return asBaselineFrame()->freshenBlock(cx);
|
||||
}
|
||||
|
||||
inline void
|
||||
AbstractFramePtr::popBlock(JSContext *cx) const
|
||||
{
|
||||
|
@ -293,6 +293,19 @@ InterpreterFrame::pushBlock(JSContext *cx, StaticBlockObject &block)
|
||||
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
|
||||
InterpreterFrame::popBlock(JSContext *cx)
|
||||
{
|
||||
|
@ -28,6 +28,7 @@ class AsmJSModule;
|
||||
class InterpreterRegs;
|
||||
class CallObject;
|
||||
class ScopeObject;
|
||||
class ClonedBlockObject;
|
||||
class ScriptFrameIter;
|
||||
class SPSProfiler;
|
||||
class InterpreterFrame;
|
||||
@ -234,6 +235,8 @@ class AbstractFramePtr
|
||||
|
||||
bool hasPushedSPSFrame() const;
|
||||
|
||||
inline bool freshenBlock(JSContext *cx) const;
|
||||
|
||||
inline void popBlock(JSContext *cx) const;
|
||||
inline void popWith(JSContext *cx) const;
|
||||
|
||||
@ -587,14 +590,18 @@ class InterpreterFrame
|
||||
|
||||
inline void pushOnScopeChain(ScopeObject &scope);
|
||||
inline void popOffScopeChain();
|
||||
inline void replaceInnermostScope(ScopeObject &scope);
|
||||
|
||||
/*
|
||||
* 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);
|
||||
void popBlock(JSContext *cx);
|
||||
bool freshenBlock(JSContext *cx);
|
||||
|
||||
/*
|
||||
* With
|
||||
|
Loading…
Reference in New Issue
Block a user