From 7862c914b893aaee3b93c913b64878c09624b203 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 7 Oct 2011 12:02:50 -0700 Subject: [PATCH] Bug Bug 692274, part 4 - Rewrite parsing, emitting and decompiling of let to fix scoping properly (r=jorendorff) --- js/public/Vector.h | 17 + js/src/frontend/BytecodeEmitter.cpp | 915 ++++++++++++------ js/src/frontend/BytecodeEmitter.h | 33 +- js/src/frontend/ParseNode.h | 5 + js/src/frontend/Parser.cpp | 720 +++++++------- js/src/frontend/Parser.h | 6 +- js/src/jit-test/tests/basic/bug657975.js | 2 +- js/src/jit-test/tests/basic/testBug666292.js | 2 +- js/src/jit-test/tests/basic/testBug683470.js | 2 +- .../jit-test/tests/basic/testBug692274-1.js | 6 + .../jit-test/tests/basic/testBug692274-2.js | 7 + .../jit-test/tests/basic/testBug692274-3.js | 16 + .../jit-test/tests/basic/testBug692274-4.js | 4 + js/src/jit-test/tests/basic/testBug703857.js | 12 + js/src/jit-test/tests/basic/testBug709633.js | 9 + js/src/jit-test/tests/basic/testLet.js | 342 +++++++ js/src/jsanalyze.cpp | 6 +- js/src/jsanalyze.h | 7 +- js/src/jsinfer.cpp | 24 +- js/src/jsinterp.cpp | 57 +- js/src/jsobj.cpp | 29 +- js/src/jsobj.h | 2 +- js/src/jsopcode.cpp | 569 ++++++++--- js/src/jsopcode.h | 53 +- js/src/jsopcode.tbl | 10 +- js/src/jsreflect.cpp | 58 +- js/src/jsxdrapi.h | 2 +- js/src/methodjit/Compiler.cpp | 8 +- js/src/methodjit/LoopState.cpp | 4 +- js/src/methodjit/StubCalls.cpp | 15 +- .../tests/js1_8/regress/regress-465567-01.js | 2 +- .../tests/js1_8_5/extensions/reflect-parse.js | 2 +- .../js1_8_5/extensions/regress-672804-1.js | 2 +- .../js1_8_5/extensions/regress-672804-3.js | 2 +- 34 files changed, 2048 insertions(+), 902 deletions(-) create mode 100644 js/src/jit-test/tests/basic/testBug692274-1.js create mode 100644 js/src/jit-test/tests/basic/testBug692274-2.js create mode 100644 js/src/jit-test/tests/basic/testBug692274-3.js create mode 100644 js/src/jit-test/tests/basic/testBug692274-4.js create mode 100644 js/src/jit-test/tests/basic/testBug703857.js create mode 100644 js/src/jit-test/tests/basic/testBug709633.js create mode 100644 js/src/jit-test/tests/basic/testLet.js diff --git a/js/public/Vector.h b/js/public/Vector.h index 96e455e93101..cbfedab7223d 100644 --- a/js/public/Vector.h +++ b/js/public/Vector.h @@ -380,6 +380,23 @@ class Vector : private AllocPolicy return *(end() - 1); } + class Range { + friend class Vector; + T *cur, *end; + Range(T *cur, T *end) : cur(cur), end(end) {} + public: + Range() {} + bool empty() const { return cur == end; } + size_t remain() const { return end - cur; } + T &front() const { return *cur; } + void popFront() { JS_ASSERT(!empty()); ++cur; } + T popCopyFront() { JS_ASSERT(!empty()); return *cur++; } + }; + + Range all() { + return Range(begin(), end()); + } + /* mutators */ /* If reserve(length() + N) succeeds, the N next appends are guaranteed to succeed. */ diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index b7eeb2803f87..ad2dfd5c0b81 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -190,54 +190,53 @@ EmitCheck(JSContext *cx, BytecodeEmitter *bce, ptrdiff_t delta) return offset; } +static JSObject * +CurrentBlock(BytecodeEmitter *bce) +{ + JS_ASSERT(bce->topStmt->type == STMT_BLOCK || bce->topStmt->type == STMT_SWITCH); + JS_ASSERT(bce->topStmt->blockObj->isStaticBlock()); + return bce->topStmt->blockObj; +} + static void UpdateDepth(JSContext *cx, BytecodeEmitter *bce, ptrdiff_t target) { - jsbytecode *pc; - JSOp op; - const JSCodeSpec *cs; - uintN nuses; - intN ndefs; + jsbytecode *pc = bce->code(target); + JSOp op = (JSOp) *pc; + const JSCodeSpec *cs = &js_CodeSpec[op]; - pc = bce->code(target); - op = (JSOp) *pc; - cs = &js_CodeSpec[op]; - if ((cs->format & JOF_TMPSLOT_MASK)) { + + if (cs->format & JOF_TMPSLOT_MASK) { + /* + * An opcode may temporarily consume stack space during execution. + * Account for this in maxStackDepth separately from uses/defs here. + */ uintN depth = (uintN) bce->stackDepth + ((cs->format & JOF_TMPSLOT_MASK) >> JOF_TMPSLOT_SHIFT); if (depth > bce->maxStackDepth) bce->maxStackDepth = depth; } - nuses = js_GetStackUses(cs, op, pc); + /* + * Specially handle any case that would call js_GetIndexFromBytecode since + * it requires a well-formed script. This allows us to safely pass NULL as + * the 'script' parameter. + */ + intN nuses, ndefs; + if (op == JSOP_ENTERBLOCK) { + nuses = 0; + ndefs = OBJ_BLOCK_COUNT(cx, CurrentBlock(bce)); + } else if (op == JSOP_ENTERLET0) { + nuses = ndefs = OBJ_BLOCK_COUNT(cx, CurrentBlock(bce)); + } else if (op == JSOP_ENTERLET1) { + nuses = ndefs = OBJ_BLOCK_COUNT(cx, CurrentBlock(bce)) + 1; + } else { + nuses = StackUses(NULL, pc); + ndefs = StackDefs(NULL, pc); + } + bce->stackDepth -= nuses; JS_ASSERT(bce->stackDepth >= 0); - if (bce->stackDepth < 0) { - char numBuf[12]; - TokenStream *ts; - - JS_snprintf(numBuf, sizeof numBuf, "%d", target); - ts = &bce->parser->tokenStream; - JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING, - js_GetErrorMessage, NULL, - JSMSG_STACK_UNDERFLOW, - ts->getFilename() ? ts->getFilename() : "stdin", - numBuf); - } - ndefs = cs->ndefs; - if (ndefs < 0) { - JSObject *blockObj; - - /* We just executed IndexParsedObject */ - JS_ASSERT(op == JSOP_ENTERBLOCK); - JS_ASSERT(nuses == 0); - blockObj = bce->objectList.lastbox->object; - JS_ASSERT(blockObj->isStaticBlock()); - JS_ASSERT(blockObj->getSlot(JSSLOT_BLOCK_DEPTH).isUndefined()); - - OBJ_SET_BLOCK_DEPTH(cx, blockObj, bce->stackDepth); - ndefs = OBJ_BLOCK_COUNT(cx, blockObj); - } bce->stackDepth += ndefs; if ((uintN)bce->stackDepth > bce->maxStackDepth) bce->maxStackDepth = bce->stackDepth; @@ -1496,6 +1495,16 @@ FlushPops(JSContext *cx, BytecodeEmitter *bce, intN *npops) return JS_TRUE; } +static bool +PopIterator(JSContext *cx, BytecodeEmitter *bce) +{ + if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0) + return false; + if (Emit1(cx, bce, JSOP_ENDITER) < 0) + return false; + return true; +} + /* * Emit additional bytecode(s) for non-local jumps. */ @@ -1533,13 +1542,8 @@ EmitNonLocalJumpFixup(JSContext *cx, BytecodeEmitter *bce, StmtInfo *toStmt) break; case STMT_FOR_IN_LOOP: - /* - * The iterator and the object being iterated need to be popped. - */ FLUSH_POPS(); - if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0) - return JS_FALSE; - if (Emit1(cx, bce, JSOP_ENDITER) < 0) + if (!PopIterator(cx, bce)) return JS_FALSE; break; @@ -1555,12 +1559,31 @@ EmitNonLocalJumpFixup(JSContext *cx, BytecodeEmitter *bce, StmtInfo *toStmt) } if (stmt->flags & SIF_SCOPE) { - /* There is a Block object with locals on the stack to pop. */ FLUSH_POPS(); - if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0) - return JS_FALSE; - uintN i = OBJ_BLOCK_COUNT(cx, stmt->blockObj); - EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, i); + uintN blockObjCount = OBJ_BLOCK_COUNT(cx, stmt->blockObj); + if (stmt->flags & SIF_FOR_BLOCK) { + /* + * For a for-let-in statement, pushing/popping the block is + * interleaved with JSOP_(END)ITER. Just handle both together + * here and skip over the enclosing STMT_FOR_IN_LOOP. + */ + JS_ASSERT(stmt->down->type == STMT_FOR_IN_LOOP); + stmt = stmt->down; + if (stmt == toStmt) + break; + if (Emit1(cx, bce, JSOP_LEAVEFORLETIN) < 0) + return JS_FALSE; + if (!PopIterator(cx, bce)) + return JS_FALSE; + if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0) + return JS_FALSE; + EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount); + } else { + /* There is a Block object with locals on the stack to pop. */ + if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0) + return JS_FALSE; + EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, blockObjCount); + } } } @@ -1928,15 +1951,22 @@ AdjustBlockSlot(JSContext *cx, BytecodeEmitter *bce, jsint slot) } static bool -EmitEnterBlock(JSContext *cx, ParseNode *pn, BytecodeEmitter *bce) +EmitEnterBlock(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSOp op) { JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); - if (!EmitObjectOp(cx, pn->pn_objbox, JSOP_ENTERBLOCK, bce)) + if (!EmitObjectOp(cx, pn->pn_objbox, op, bce)) return false; JSObject *blockObj = pn->pn_objbox->object; - jsint depth = AdjustBlockSlot(cx, bce, OBJ_BLOCK_DEPTH(cx, blockObj)); - if (depth < 0) + JS_ASSERT(blockObj->isStaticBlock()); + JS_ASSERT(blockObj->getSlot(JSSLOT_BLOCK_DEPTH).isUndefined()); + + int depth = bce->stackDepth - + (OBJ_BLOCK_COUNT(cx, blockObj) + ((op == JSOP_ENTERLET1) ? 1 : 0)); + JS_ASSERT(depth >= 0); + OBJ_SET_BLOCK_DEPTH(cx, blockObj, depth); + int depthPlusFixed = AdjustBlockSlot(cx, bce, depth); + if (depthPlusFixed < 0) return false; uintN base = JSSLOT_FREE(&BlockClass); @@ -1951,8 +1981,8 @@ EmitEnterBlock(JSContext *cx, ParseNode *pn, BytecodeEmitter *bce) Definition *dn = (Definition *) v.toPrivate(); JS_ASSERT(dn->isDefn()); - JS_ASSERT(uintN(dn->frameSlot() + depth) < JS_BIT(16)); - dn->pn_cookie.set(dn->pn_cookie.level(), uint16_t(dn->frameSlot() + depth)); + JS_ASSERT(uintN(dn->frameSlot() + depthPlusFixed) < JS_BIT(16)); + dn->pn_cookie.set(dn->pn_cookie.level(), uint16_t(dn->frameSlot() + depthPlusFixed)); #ifdef DEBUG for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { JS_ASSERT(pnu->pn_lexdef == dn); @@ -3178,9 +3208,6 @@ EmitSwitch(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) intN noteIndex; size_t switchSize, tableSize; jsbytecode *pc, *savepc; -#if JS_HAS_BLOCK_SCOPE - int count; -#endif StmtInfo stmtInfo; /* Try for most optimal, fall back if not dense ints, and per ECMAv2. */ @@ -3189,43 +3216,35 @@ EmitSwitch(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) hasDefault = constPropagated = JS_FALSE; defaultOffset = -1; - /* - * If the switch contains let variables scoped by its body, model the - * resulting block on the stack first, before emitting the discriminant's - * bytecode (in case the discriminant contains a stack-model dependency - * such as a let expression). - */ pn2 = pn->pn_right; #if JS_HAS_BLOCK_SCOPE - if (pn2->isKind(PNK_LEXICALSCOPE)) { - /* - * Push the body's block scope before discriminant code-gen to reflect - * the order of slots on the stack. The block's locals must lie under - * the discriminant on the stack so that case-dispatch bytecodes can - * find the discriminant on top of stack. - */ - count = OBJ_BLOCK_COUNT(cx, pn2->pn_objbox->object); - PushBlockScope(bce, &stmtInfo, pn2->pn_objbox->object, -1); - stmtInfo.type = STMT_SWITCH; - - /* Emit JSOP_ENTERBLOCK before code to evaluate the discriminant. */ - if (!EmitEnterBlock(cx, pn2, bce)) - return JS_FALSE; - } -#ifdef __GNUC__ - else { - count = 0; - } -#endif -#endif - /* - * Emit code for the discriminant first (or nearly first, in the case of a - * switch whose body is a block scope). + * If there are hoisted let declarations, their stack slots go under the + * discriminant's value so push their slots now and enter the block later. */ + uint32_t blockCount = 0; + if (pn2->isKind(PNK_LEXICALSCOPE)) { + blockCount = OBJ_BLOCK_COUNT(cx, pn2->pn_objbox->object); + for (uint32_t i = 0; i < blockCount; ++i) { + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return JS_FALSE; + } + } +#endif + + /* Push the discriminant. */ if (!EmitTree(cx, bce, pn->pn_left)) return JS_FALSE; +#if JS_HAS_BLOCK_SCOPE + if (pn2->isKind(PNK_LEXICALSCOPE)) { + PushBlockScope(bce, &stmtInfo, pn2->pn_objbox->object, -1); + stmtInfo.type = STMT_SWITCH; + if (!EmitEnterBlock(cx, bce, pn2, JSOP_ENTERLET1)) + return JS_FALSE; + } +#endif + /* Switch bytecodes run from here till end of final case. */ top = bce->offset(); #if !JS_HAS_BLOCK_SCOPE @@ -3686,7 +3705,7 @@ out: #if JS_HAS_BLOCK_SCOPE if (ok && pn->pn_right->isKind(PNK_LEXICALSCOPE)) - EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, count); + EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, blockCount); #endif } return ok; @@ -3772,6 +3791,21 @@ MaybeEmitVarDecl(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode * return true; } +/* + * This enum tells EmitVariables and the destructuring functions how emit the + * given Parser::variables parse tree. In the base case, DefineVars, the caller + * only wants variables to be defined in the prologue (if necessary). For + * PushInitialValues, variable initializer expressions are evaluated and left + * on the stack. For InitializeVars, the initializer expressions values are + * assigned (to local variables) and popped. + */ +enum VarEmitOption +{ + DefineVars = 0, + PushInitialValues = 1, + InitializeVars = 2 +}; + #if JS_HAS_DESTRUCTURING typedef JSBool @@ -3817,11 +3851,26 @@ EmitDestructuringDecls(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp, Parse } static JSBool -EmitDestructuringOpsHelper(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn); +EmitDestructuringOpsHelper(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, + VarEmitOption emitOption); +/* + * EmitDestructuringLHS assumes the to-be-destructured value has been pushed on + * the stack and emits code to destructure a single lhs expression (either a + * name or a compound []/{} expression). + * + * If emitOption is InitializeVars, the to-be-destructured value is assigned to + * locals and ultimately the initial slot is popped (-1 total depth change). + * + * If emitOption is PushInitialValues, the to-be-destructured value is replaced + * with the initial values of the N (where 0 <= N) variables assigned in the + * lhs expression. (Same post-condition as EmitDestructuringOpsHelper) + */ static JSBool -EmitDestructuringLHS(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) +EmitDestructuringLHS(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmitOption emitOption) { + JS_ASSERT(emitOption != DefineVars); + /* * Now emit the lvalue opcode sequence. If the lvalue is a nested * destructuring initialiser-form, call ourselves to handle it, then @@ -3829,11 +3878,29 @@ EmitDestructuringLHS(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) * ending with a JSOP_ENUMELEM or equivalent op. */ if (pn->isKind(PNK_RB) || pn->isKind(PNK_RC)) { - if (!EmitDestructuringOpsHelper(cx, bce, pn)) - return JS_FALSE; - if (Emit1(cx, bce, JSOP_POP) < 0) + if (!EmitDestructuringOpsHelper(cx, bce, pn, emitOption)) return JS_FALSE; + if (emitOption == InitializeVars) { + /* + * Per its post-condition, EmitDestructuringOpsHelper has left the + * to-be-destructured value on top of the stack. + */ + if (Emit1(cx, bce, JSOP_POP) < 0) + return JS_FALSE; + } } else { + if (emitOption == PushInitialValues) { + /* + * The lhs is a simple name so the to-be-destructured value is + * its initial value and there is nothing to do. + */ + JS_ASSERT(pn->getOp() == JSOP_SETLOCAL); + JS_ASSERT(pn->pn_dflags & PND_BOUND); + return JS_TRUE; + } + + /* All paths below must pop after assigning to the lhs. */ + if (pn->isKind(PNK_NAME)) { if (!BindNameToSlot(cx, bce, pn)) return JS_FALSE; @@ -3898,14 +3965,23 @@ EmitDestructuringLHS(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) /* * Recursive helper for EmitDestructuringOps. + * EmitDestructuringOpsHelper assumes the to-be-destructured value has been + * pushed on the stack and emits code to destructure each part of a [] or {} + * lhs expression. * - * Given a value to destructure on the stack, walk over an object or array - * initialiser at pn, emitting bytecodes to match property values and store - * them in the lvalues identified by the matched property names. + * If emitOption is InitializeVars, the initial to-be-destructured value is + * left untouched on the stack and the overall depth is not changed. + * + * If emitOption is PushInitialValues, the to-be-destructured value is replaced + * with the initial values of the N (where 0 <= N) variables assigned in the + * lhs expression. (Same post-condition as EmitDestructuringLHS) */ static JSBool -EmitDestructuringOpsHelper(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) +EmitDestructuringOpsHelper(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, + VarEmitOption emitOption) { + JS_ASSERT(emitOption != DefineVars); + jsuint index; ParseNode *pn2, *pn3; JSBool doElemOp; @@ -3919,8 +3995,8 @@ EmitDestructuringOpsHelper(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (pn->pn_count == 0) { /* Emit a DUP;POP sequence for the decompiler. */ - return Emit1(cx, bce, JSOP_DUP) >= 0 && - Emit1(cx, bce, JSOP_POP) >= 0; + if (Emit1(cx, bce, JSOP_DUP) < 0 || Emit1(cx, bce, JSOP_POP) < 0) + return JS_FALSE; } index = 0; @@ -3976,7 +4052,7 @@ EmitDestructuringOpsHelper(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) */ if (!EmitElemOpBase(cx, bce, JSOP_GETELEM)) return JS_FALSE; - JS_ASSERT(bce->stackDepth == stackDepth + 1); + JS_ASSERT(bce->stackDepth >= stackDepth + 1); } /* Nullary comma node makes a hole in the array destructurer. */ @@ -3986,14 +4062,47 @@ EmitDestructuringOpsHelper(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (Emit1(cx, bce, JSOP_POP) < 0) return JS_FALSE; } else { - if (!EmitDestructuringLHS(cx, bce, pn3)) + intN depthBefore = bce->stackDepth; + if (!EmitDestructuringLHS(cx, bce, pn3, emitOption)) return JS_FALSE; + + if (emitOption == PushInitialValues) { + /* + * After '[x,y]' in 'let ([[x,y], z] = o)', the stack is + * | to-be-decompiled-value | x | y | + * The goal is: + * | x | y | z | + * so emit a pick to produce the intermediate state + * | x | y | to-be-decompiled-value | + * before destructuring z. This gives the loop invariant that + * the to-be-compiled-value is always on top of the stack. + */ + JS_ASSERT((bce->stackDepth - bce->stackDepth) >= -1); + uintN pickDistance = (uintN)((bce->stackDepth + 1) - depthBefore); + if (pickDistance > 0) { + if (pickDistance > jsbytecode(-1)) { + ReportCompileErrorNumber(cx, bce->tokenStream(), pn3, JSREPORT_ERROR, + JSMSG_TOO_MANY_LOCALS); + return JS_FALSE; + } + if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)pickDistance) < 0) + return false; + } + } } - JS_ASSERT(bce->stackDepth == stackDepth); ++index; } + if (emitOption == PushInitialValues) { + /* + * Per the above loop invariant, to-be-decompiled-value is at the top + * of the stack. To achieve the post-condition, pop it. + */ + if (Emit1(cx, bce, JSOP_POP) < 0) + return JS_FALSE; + } + return JS_TRUE; } @@ -4012,8 +4121,74 @@ OpToDeclType(JSOp op) } } +/* + * This utility accumulates a set of SRC_DESTRUCTLET notes which need to be + * backpatched with the offset from JSOP_DUP to JSOP_LET0. + * + * Also record whether the let head was a group assignment ([x,y] = [a,b]) + * (which implies no SRC_DESTRUCTLET notes). + */ +class LetNotes +{ + struct Pair { + ptrdiff_t dup; + uintN index; + Pair(ptrdiff_t dup, uintN index) : dup(dup), index(index) {} + }; + Vector notes; + bool groupAssign; + DebugOnly updateCalled; + + public: + LetNotes(JSContext *cx) : notes(cx), groupAssign(false), updateCalled(false) {} + + ~LetNotes() { + JS_ASSERT_IF(!notes.allocPolicy().context()->isExceptionPending(), updateCalled); + } + + void setGroupAssign() { + JS_ASSERT(notes.empty()); + groupAssign = true; + } + + bool isGroupAssign() const { + return groupAssign; + } + + bool append(JSContext *cx, BytecodeEmitter *bce, ptrdiff_t dup, uintN index) { + JS_ASSERT(!groupAssign); + JS_ASSERT(SN_TYPE(bce->notes() + index) == SRC_DESTRUCTLET); + if (!notes.append(Pair(dup, index))) + return false; + + /* + * Pessimistically inflate each srcnote. That way, there is no danger + * of inflation during update() (which would invalidate all indices). + */ + if (!SetSrcNoteOffset(cx, bce, index, 0, SN_MAX_OFFSET)) + return false; + JS_ASSERT(bce->notes()[index + 1] & SN_3BYTE_OFFSET_FLAG); + return true; + } + + /* This should be called exactly once, right before JSOP_ENTERLET0. */ + bool update(JSContext *cx, BytecodeEmitter *bce, ptrdiff_t offset) { + JS_ASSERT(!updateCalled); + for (size_t i = 0; i < notes.length(); ++i) { + JS_ASSERT(offset > notes[i].dup); + JS_ASSERT(*bce->code(notes[i].dup) == JSOP_DUP); + JS_ASSERT(bce->notes()[notes[i].index + 1] & SN_3BYTE_OFFSET_FLAG); + if (!SetSrcNoteOffset(cx, bce, notes[i].index, 0, offset - notes[i].dup)) + return false; + } + updateCalled = true; + return true; + } +}; + static JSBool -EmitDestructuringOps(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn) +EmitDestructuringOps(JSContext *cx, BytecodeEmitter *bce, ptrdiff_t declType, ParseNode *pn, + LetNotes *letNotes = NULL) { /* * If we're called from a variable declaration, help the decompiler by @@ -4021,14 +4196,21 @@ EmitDestructuringOps(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNo * If the destructuring initialiser is empty, our helper will emit a * JSOP_DUP followed by a JSOP_POP for the decompiler. */ - if (NewSrcNote2(cx, bce, SRC_DESTRUCT, OpToDeclType(prologOp)) < 0) - return JS_FALSE; + if (letNotes) { + ptrdiff_t index = NewSrcNote2(cx, bce, SRC_DESTRUCTLET, 0); + if (index < 0 || !letNotes->append(cx, bce, bce->offset(), (uintN)index)) + return JS_FALSE; + } else { + if (NewSrcNote2(cx, bce, SRC_DESTRUCT, declType) < 0) + return JS_FALSE; + } /* * Call our recursive helper to emit the destructuring assignments and * related stack manipulations. */ - return EmitDestructuringOpsHelper(cx, bce, pn); + VarEmitOption emitOption = letNotes ? PushInitialValues : InitializeVars; + return EmitDestructuringOpsHelper(cx, bce, pn, emitOption); } static JSBool @@ -4069,7 +4251,7 @@ EmitGroupAssignment(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp, if (Emit1(cx, bce, JSOP_POP) < 0) return JS_FALSE; } else { - if (!EmitDestructuringLHS(cx, bce, pn)) + if (!EmitDestructuringLHS(cx, bce, pn, InitializeVars)) return JS_FALSE; } } @@ -4105,42 +4287,63 @@ MaybeEmitGroupAssignment(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp, Par return JS_TRUE; } +/* + * Like MaybeEmitGroupAssignment, but for 'let ([x,y] = [a,b]) ...'. + * + * Instead of issuing a sequence |dup|eval-rhs|set-lhs|pop| (which doesn't work + * since the bound vars don't yet have slots), just eval/push each rhs element + * just like what EmitLet would do for 'let (x = a, y = b) ...'. While shorter, + * simpler and more efficient than MaybeEmitGroupAssignment, it is harder to + * decompile so we restrict the ourselves to cases where the lhs and rhs are in + * 1:1 correspondence and lhs elements are simple names. + */ +static bool +MaybeEmitLetGroupDecl(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, + LetNotes *letNotes, JSOp *pop) +{ + JS_ASSERT(pn->isKind(PNK_ASSIGN)); + JS_ASSERT(pn->isOp(JSOP_NOP)); + JS_ASSERT(*pop == JSOP_POP || *pop == JSOP_POPV); + + ParseNode *lhs = pn->pn_left; + ParseNode *rhs = pn->pn_right; + if (lhs->isKind(PNK_RB) && rhs->isKind(PNK_RB) && + !(rhs->pn_xflags & PNX_HOLEY) && + !(lhs->pn_xflags & PNX_HOLEY) && + lhs->pn_count == rhs->pn_count) + { + for (ParseNode *l = lhs->pn_head; l; l = l->pn_next) { + if (l->getOp() != JSOP_SETLOCAL) + return true; + } + + for (ParseNode *r = rhs->pn_head; r; r = r->pn_next) { + if (!EmitTree(cx, bce, r)) + return false; + } + + letNotes->setGroupAssign(); + *pop = JSOP_NOP; + } + return true; +} + #endif /* JS_HAS_DESTRUCTURING */ static JSBool -EmitVariables(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool inLetHead, - ptrdiff_t *headNoteIndex) +EmitVariables(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmitOption emitOption, + LetNotes *letNotes = NULL) { - bool forInVar, first; - ptrdiff_t off, noteIndex, tmp; - ParseNode *pn2, *pn3, *next; - JSOp op; - jsatomid atomIndex; - uintN oldflags; + JS_ASSERT(pn->isArity(PN_LIST)); + JS_ASSERT(!!letNotes == (emitOption == PushInitialValues)); - /* Default in case of JS_HAS_BLOCK_SCOPE early return, below. */ - *headNoteIndex = -1; - - /* - * Let blocks and expressions have a parenthesized head in which the new - * scope is not yet open. Initializer evaluation uses the parent node's - * lexical scope. If popScope is true below, then we hide the top lexical - * block from any calls to BindNameToSlot hiding in pn2->pn_expr so that - * it won't find any names in the new let block. - * - * The same goes for let declarations in the head of any kind of for loop. - * Unlike a let declaration 'let x = i' within a block, where x is hoisted - * to the start of the block, a 'for (let x = i...) ...' loop evaluates i - * in the containing scope, and puts x in the loop body's scope. - */ - DebugOnly let = (pn->isOp(JSOP_NOP)); - forInVar = (pn->pn_xflags & PNX_FORINVAR) != 0; - - off = noteIndex = -1; - for (pn2 = pn->pn_head; ; pn2 = next) { - first = pn2 == pn->pn_head; + ptrdiff_t off = -1, noteIndex = -1; + ParseNode *next; + for (ParseNode *pn2 = pn->pn_head; ; pn2 = next) { + bool first = pn2 == pn->pn_head; next = pn2->pn_next; + ParseNode *pn3; if (!pn2->isKind(PNK_NAME)) { #if JS_HAS_DESTRUCTURING if (pn2->isKind(PNK_RB) || pn2->isKind(PNK_RC)) { @@ -4152,7 +4355,7 @@ EmitVariables(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool inLetHe * enumerating opcode and a branch that tests whether the * enumeration ended. */ - JS_ASSERT(forInVar); + JS_ASSERT(emitOption == DefineVars); JS_ASSERT(pn->pn_count == 1); if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn2)) return JS_FALSE; @@ -4168,7 +4371,7 @@ EmitVariables(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool inLetHe */ JS_ASSERT(pn2->isKind(PNK_ASSIGN)); JS_ASSERT(pn2->isOp(JSOP_NOP)); - JS_ASSERT(!forInVar); + JS_ASSERT(emitOption != DefineVars); /* * To allow the front end to rewrite var f = x; as f = x; when a @@ -4187,6 +4390,8 @@ EmitVariables(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool inLetHe } #if JS_HAS_DESTRUCTURING + ptrdiff_t stackDepthBefore = bce->stackDepth; + JSOp op = JSOP_POP; if (pn->pn_count == 1) { /* * If this is the only destructuring assignment in the list, @@ -4195,34 +4400,46 @@ EmitVariables(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool inLetHe * in pn->pn_op, to suppress a second (and misplaced) 'let'. */ JS_ASSERT(noteIndex < 0 && !pn2->pn_next); - op = JSOP_POP; - if (!MaybeEmitGroupAssignment(cx, bce, - inLetHead ? JSOP_POP : pn->getOp(), - pn2, &op)) { - return JS_FALSE; - } - if (op == JSOP_NOP) { - pn->pn_xflags = (pn->pn_xflags & ~PNX_POPVAR) | PNX_GROUPINIT; - break; + if (letNotes) { + if (!MaybeEmitLetGroupDecl(cx, bce, pn2, letNotes, &op)) + return JS_FALSE; + } else { + if (!MaybeEmitGroupAssignment(cx, bce, pn->getOp(), pn2, &op)) + return JS_FALSE; } } + if (op == JSOP_NOP) { + pn->pn_xflags = (pn->pn_xflags & ~PNX_POPVAR) | PNX_GROUPINIT; + } else { + pn3 = pn2->pn_left; + if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn3)) + return JS_FALSE; - pn3 = pn2->pn_left; - if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn3)) - return JS_FALSE; + if (!EmitTree(cx, bce, pn2->pn_right)) + return JS_FALSE; - if (!EmitTree(cx, bce, pn2->pn_right)) - return JS_FALSE; + /* Only the first list element should print 'let' or 'var'. */ + ptrdiff_t declType = pn2 == pn->pn_head + ? OpToDeclType(pn->getOp()) + : SRC_DECL_NONE; - /* - * Veto pn->pn_op if inLetHead to avoid emitting a SRC_DESTRUCT - * that's redundant with respect to the SRC_DECL/SRC_DECL_LET that - * we will emit at the bottom of this function. - */ - if (!EmitDestructuringOps(cx, bce, - inLetHead ? JSOP_POP : pn->getOp(), - pn3)) { - return JS_FALSE; + if (!EmitDestructuringOps(cx, bce, declType, pn3, letNotes)) + return JS_FALSE; + } + ptrdiff_t stackDepthAfter = bce->stackDepth; + + /* Give let ([] = x) a slot (see CheckDestructuring). */ + JS_ASSERT(stackDepthBefore <= stackDepthAfter); + if (letNotes && stackDepthBefore == stackDepthAfter) { + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return JS_FALSE; + } + + /* If we are not initializing, nothing to pop. */ + if (emitOption != InitializeVars) { + if (next) + continue; + break; } goto emit_note_pop; #endif @@ -4240,67 +4457,60 @@ EmitVariables(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool inLetHe if (!BindNameToSlot(cx, bce, pn2)) return JS_FALSE; + JSOp op; + jsatomid atomIndex; + op = pn2->getOp(); if (op == JSOP_ARGUMENTS) { /* JSOP_ARGUMENTS => no initializer */ - JS_ASSERT(!pn3 && !let); + JS_ASSERT(!pn3 && !letNotes); pn3 = NULL; -#ifdef __GNUC__ - atomIndex = 0; /* quell GCC overwarning */ -#endif + atomIndex = 0; } else { JS_ASSERT(op != JSOP_CALLEE); - JS_ASSERT(!pn2->pn_cookie.isFree() || !let); + JS_ASSERT(!pn2->pn_cookie.isFree() || !pn->isOp(JSOP_NOP)); if (!MaybeEmitVarDecl(cx, bce, pn->getOp(), pn2, &atomIndex)) return JS_FALSE; if (pn3) { - JS_ASSERT(!forInVar); - if (op == JSOP_SETNAME) { - JS_ASSERT(!let); + JS_ASSERT(emitOption != DefineVars); + JS_ASSERT_IF(emitOption == PushInitialValues, op == JSOP_SETLOCAL); + if (op == JSOP_SETNAME) EMIT_INDEX_OP(JSOP_BINDNAME, atomIndex); - } else if (op == JSOP_SETGNAME) { - JS_ASSERT(!let); + else if (op == JSOP_SETGNAME) EMIT_INDEX_OP(JSOP_BINDGNAME, atomIndex); - } if (pn->isOp(JSOP_DEFCONST) && !DefineCompileTimeConstant(cx, bce, pn2->pn_atom, pn3)) { return JS_FALSE; } - oldflags = bce->flags; + uintN oldflags = bce->flags; bce->flags &= ~TCF_IN_FOR_INIT; if (!EmitTree(cx, bce, pn3)) return JS_FALSE; bce->flags |= oldflags & TCF_IN_FOR_INIT; + } else if (letNotes) { + /* JSOP_ENTERLETx expects at least 1 slot to have been pushed. */ + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return JS_FALSE; } } - /* - * The parser rewrites 'for (var x = i in o)' to hoist 'var x = i' -- - * likewise 'for (let x = i in o)' becomes 'i; for (let x in o)' using - * a PNK_SEQ node to make the two statements appear as one. Therefore - * if this declaration is part of a for-in loop head, we do not need to - * emit op or any source note. Our caller, the PNK_FOR/PNK_IN case in - * EmitTree, will annotate appropriately. - */ - JS_ASSERT_IF(pn2->isDefn(), pn3 == pn2->pn_expr); - if (forInVar) { - JS_ASSERT(pn->pn_count == 1); - JS_ASSERT(!pn3); + /* If we are not initializing, nothing to pop. */ + if (emitOption != InitializeVars) { + if (next) + continue; break; } - if (first && - !inLetHead && - NewSrcNote2(cx, bce, SRC_DECL, - (pn->isOp(JSOP_DEFCONST)) - ? SRC_DECL_CONST - : (pn->isOp(JSOP_DEFVAR)) - ? SRC_DECL_VAR - : SRC_DECL_LET) < 0) - { + JS_ASSERT_IF(pn2->isDefn(), pn3 == pn2->pn_expr); + if (first && NewSrcNote2(cx, bce, SRC_DECL, + (pn->isOp(JSOP_DEFCONST)) + ? SRC_DECL_CONST + : (pn->isOp(JSOP_DEFVAR)) + ? SRC_DECL_VAR + : SRC_DECL_LET) < 0) { return JS_FALSE; } if (op == JSOP_ARGUMENTS) { @@ -4315,7 +4525,7 @@ EmitVariables(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool inLetHe #if JS_HAS_DESTRUCTURING emit_note_pop: #endif - tmp = bce->offset(); + ptrdiff_t tmp = bce->offset(); if (noteIndex >= 0) { if (!SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 0, tmp-off)) return JS_FALSE; @@ -4328,16 +4538,12 @@ EmitVariables(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool inLetHe return JS_FALSE; } - /* If this is a let head, emit and return a srcnote on the pop. */ - if (inLetHead) { - *headNoteIndex = NewSrcNote(cx, bce, SRC_DECL); - if (*headNoteIndex < 0) + if (pn->pn_xflags & PNX_POPVAR) { + if (Emit1(cx, bce, JSOP_POP) < 0) return JS_FALSE; - if (!(pn->pn_xflags & PNX_POPVAR)) - return Emit1(cx, bce, JSOP_NOP) >= 0; } - return !(pn->pn_xflags & PNX_POPVAR) || Emit1(cx, bce, JSOP_POP) >= 0; + return JS_TRUE; } static bool @@ -4518,7 +4724,7 @@ EmitAssignment(JSContext *cx, BytecodeEmitter *bce, ParseNode *lhs, JSOp op, Par #if JS_HAS_DESTRUCTURING case PNK_RB: case PNK_RC: - if (!EmitDestructuringOps(cx, bce, JSOP_SETNAME, lhs)) + if (!EmitDestructuringOps(cx, bce, SRC_DECL_NONE, lhs)) return false; break; #endif @@ -4535,7 +4741,7 @@ EmitAssignment(JSContext *cx, BytecodeEmitter *bce, ParseNode *lhs, JSOp op, Par return true; } -#if defined DEBUG_brendan || defined DEBUG_mrbkap +#ifdef DEBUG static JSBool GettableNoteForNextOp(BytecodeEmitter *bce) { @@ -4736,7 +4942,7 @@ EmitCatch(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) #if JS_HAS_DESTRUCTURING case PNK_RB: case PNK_RC: - if (!EmitDestructuringOps(cx, bce, JSOP_NOP, pn2)) + if (!EmitDestructuringOps(cx, bce, SRC_DECL_NONE, pn2)) return false; if (Emit1(cx, bce, JSOP_POP) < 0) return false; @@ -5106,43 +5312,109 @@ EmitIf(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) } #if JS_HAS_BLOCK_SCOPE +/* + * pnLet represents one of: + * + * let-expression: (let (x = y) EXPR) + * let-statement: let (x = y) { ... } + * + * For a let-expression 'let (x = a, [y,z] = b) e', EmitLet produces: + * + * bytecode stackDepth srcnotes + * evaluate a +1 + * evaluate b +1 + * dup +1 SRC_DESTRUCTLET + offset to enterlet0 + * destructure y + * pick 1 + * dup +1 SRC_DESTRUCTLET + offset to enterlet0 + * pick + * destructure z + * pick 1 + * pop -1 + * enterlet0 SRC_DECL + offset to leaveblockexpr + * evaluate e +1 + * leaveblockexpr -3 SRC_PCBASE + offset to evaluate a + * + * Note that, since enterlet0 simply changes fp->blockChain and does not + * otherwise touch the stack, evaluation of the let-var initializers must leave + * the initial value in the let-var's future slot. + * + * The SRC_DESTRUCTLET distinguish JSOP_DUP as the beginning of a destructuring + * let initialization and the offset allows the decompiler to find the block + * object from which to find let var names. These forward offsets require + * backpatching, which is handled by LetNotes. + * + * The SRC_DECL offset allows recursive decompilation of 'e'. + * + * The SRC_PCBASE allows js_DecompileValueGenerator to walk backwards from + * JSOP_LEAVEBLOCKEXPR to the beginning of the let and is only needed for + * let-expressions. + */ static bool -EmitLet(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) +EmitLet(JSContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) { - /* - * pn represents one of these syntactic constructs: - * let-expression: (let (x = y) EXPR) - * let-statement: let (x = y) { ... } - * let-declaration in statement context: let x = y; - * let-declaration in for-loop head: for (let ...) ... - * - * Let-expressions and let-statements are represented as binary nodes - * with their variable declarations on the left and the body on the - * right. - */ - ParseNode *pn2; - if (pn->isArity(PN_BINARY)) { - pn2 = pn->pn_right; - pn = pn->pn_left; - } else { - pn2 = NULL; + JS_ASSERT(pnLet->isArity(PN_BINARY)); + ParseNode *varList = pnLet->pn_left; + JS_ASSERT(varList->isArity(PN_LIST)); + ParseNode *letBody = pnLet->pn_right; + JS_ASSERT(letBody->isLet() && letBody->isKind(PNK_LEXICALSCOPE)); + JSObject *blockObj = letBody->pn_objbox->object; + JS_ASSERT(blockObj->isStaticBlock()); + + ptrdiff_t letHeadOffset = bce->offset(); + intN letHeadDepth = bce->stackDepth; + + LetNotes letNotes(cx); + if (!EmitVariables(cx, bce, varList, PushInitialValues, &letNotes)) + return false; + + /* Push storage for hoisted let decls (e.g. 'let (x) { let y }'). */ + uint32_t alreadyPushed = uintN(bce->stackDepth - letHeadDepth); + uint32_t blockObjCount = OBJ_BLOCK_COUNT(cx, blockObj); + for (uint32_t i = alreadyPushed; i < blockObjCount; ++i) { + /* Tell the decompiler not to print the decl in the let head. */ + if (NewSrcNote(cx, bce, SRC_CONTINUE) < 0) + return false; + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return false; } - /* Non-null pn2 means that pn is the variable list from a let head. */ - JS_ASSERT(pn->isArity(PN_LIST)); - ptrdiff_t noteIndex; - if (!EmitVariables(cx, bce, pn, pn2 != NULL, ¬eIndex)) - return false; - ptrdiff_t tmp = bce->offset(); + StmtInfo stmtInfo; + PushBlockScope(bce, &stmtInfo, blockObj, bce->offset()); - /* Thus non-null pn2 is the body of the let block or expression. */ - if (pn2 && !EmitTree(cx, bce, pn2)) + if (!letNotes.update(cx, bce, bce->offset())) return false; - if (noteIndex >= 0 && !SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 0, bce->offset() - tmp)) + ptrdiff_t declNote = NewSrcNote(cx, bce, SRC_DECL); + if (declNote < 0) return false; - return true; + ptrdiff_t bodyBegin = bce->offset(); + if (!EmitEnterBlock(cx, bce, letBody, JSOP_ENTERLET0)) + return false; + + if (!EmitTree(cx, bce, letBody->pn_expr)) + return false; + + JSOp leaveOp = letBody->getOp(); + if (leaveOp == JSOP_LEAVEBLOCKEXPR) { + if (NewSrcNote2(cx, bce, SRC_PCBASE, bce->offset() - letHeadOffset) < 0) + return false; + } + + JS_ASSERT(leaveOp == JSOP_LEAVEBLOCK || leaveOp == JSOP_LEAVEBLOCKEXPR); + EMIT_UINT16_IMM_OP(leaveOp, OBJ_BLOCK_COUNT(cx, blockObj)); + + ptrdiff_t bodyEnd = bce->offset(); + JS_ASSERT(bodyEnd > bodyBegin); + + if (!PopStatementBCE(cx, bce)) + return false; + + ptrdiff_t o = PackLetData((bodyEnd - bodyBegin) - + (JSOP_ENTERLET0_LENGTH + JSOP_LEAVEBLOCK_LENGTH), + letNotes.isGroupAssign()); + return SetSrcNoteOffset(cx, bce, declNote, 0, o); } #endif @@ -5224,56 +5496,52 @@ EmitXMLProcessingInstruction(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) static bool EmitLexicalScope(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) { + JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); + JS_ASSERT(pn->getOp() == JSOP_LEAVEBLOCK); + StmtInfo stmtInfo; - StmtInfo *stmt; ObjectBox *objbox = pn->pn_objbox; - PushBlockScope(bce, &stmtInfo, objbox->object, bce->offset()); + JSObject *blockObj = objbox->object; + JS_ASSERT(blockObj->isStaticBlock()); + PushBlockScope(bce, &stmtInfo, blockObj, bce->offset()); /* - * If this lexical scope is not for a catch block, let block or let - * expression, or any kind of for loop (where the scope starts in the - * head after the first part if for (;;), else in the body if for-in); - * and if our container is top-level but not a function body, or else - * a block statement; then emit a SRC_BRACE note. All other container - * statements get braces by default from the decompiler. + * For compound statements (i.e. { stmt-list }), the decompiler does not + * emit curlies by default. However, if this stmt-list contains a let + * declaration, this is semantically invalid so we need to add a srcnote to + * enterblock to tell the decompiler to add curlies. This condition + * shouldn't be so complicated; try to find a simpler condition. */ ptrdiff_t noteIndex = -1; - ParseNodeKind kind = pn->expr()->getKind(); - if (kind != PNK_CATCH && kind != PNK_LET && kind != PNK_FOR && - (!(stmt = stmtInfo.down) - ? !bce->inFunction() - : stmt->type == STMT_BLOCK)) + if (pn->expr()->getKind() != PNK_FOR && + pn->expr()->getKind() != PNK_CATCH && + (stmtInfo.down + ? stmtInfo.down->type == STMT_BLOCK && + (!stmtInfo.down->down || stmtInfo.down->down->type != STMT_FOR_IN_LOOP) + : !bce->inFunction())) { -#if defined DEBUG_brendan || defined DEBUG_mrbkap /* There must be no source note already output for the next op. */ JS_ASSERT(bce->noteCount() == 0 || bce->lastNoteOffset() != bce->offset() || !GettableNoteForNextOp(bce)); -#endif noteIndex = NewSrcNote2(cx, bce, SRC_BRACE, 0); if (noteIndex < 0) return false; } - ptrdiff_t top = bce->offset(); - if (!EmitEnterBlock(cx, pn, bce)) + ptrdiff_t bodyBegin = bce->offset(); + if (!EmitEnterBlock(cx, bce, pn, JSOP_ENTERBLOCK)) return false; if (!EmitTree(cx, bce, pn->pn_expr)) return false; - JSOp op = pn->getOp(); - if (op == JSOP_LEAVEBLOCKEXPR) { - if (NewSrcNote2(cx, bce, SRC_PCBASE, bce->offset() - top) < 0) - return false; - } else { - if (noteIndex >= 0 && !SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 0, bce->offset() - top)) + if (noteIndex >= 0) { + if (!SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 0, bce->offset() - bodyBegin)) return false; } - /* Emit the JSOP_LEAVEBLOCK or JSOP_LEAVEBLOCKEXPR opcode. */ - uintN count = OBJ_BLOCK_COUNT(cx, objbox->object); - EMIT_UINT16_IMM_OP(op, count); + EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, OBJ_BLOCK_COUNT(cx, blockObj)); return PopStatementBCE(cx, bce); } @@ -5338,17 +5606,48 @@ EmitForIn(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) ParseNode *forHead = pn->pn_left; ParseNode *forBody = pn->pn_right; + ParseNode *pn1 = forHead->pn_kid1; + bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE); + JS_ASSERT_IF(letDecl, pn1->isLet()); + + JSObject *blockObj = letDecl ? pn1->pn_objbox->object : NULL; + uint32_t blockObjCount = blockObj ? OBJ_BLOCK_COUNT(cx, blockObj) : 0; + + if (letDecl) { + /* + * The let's slot(s) will be under the iterator, but the block must not + * be entered (i.e. fp->blockChain set) until after evaluating the rhs. + * Thus, push to reserve space and enterblock after. The same argument + * applies when leaving the loop. Thus, a for-let-in loop looks like: + * + * push x N + * eval rhs + * iter + * enterlet1 + * goto + * ... loop body + * ifne + * leaveforinlet + * enditer + * popn(N) + */ + for (uint32_t i = 0; i < blockObjCount; ++i) { + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return false; + } + } + /* * If the left part is 'var x', emit code to define x if necessary - * using a prolog opcode, but do not emit a pop. If the left part - * was originally 'var x = i', the parser will have rewritten it; - * see Parser::forStatement. 'for (let x = i in o)' is mercifully - * banned. + * using a prolog opcode, but do not emit a pop. If the left part was + * originally 'var x = i', the parser will have rewritten it; see + * Parser::forStatement. 'for (let x = i in o)' is mercifully banned. */ - if (ParseNode *decl = forHead->pn_kid1) { + if (pn1) { + ParseNode *decl = letDecl ? pn1->pn_expr : pn1; JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET)); bce->flags |= TCF_IN_FOR_INIT; - if (!EmitTree(cx, bce, decl)) + if (!EmitVariables(cx, bce, decl, DefineVars)) return false; bce->flags &= ~TCF_IN_FOR_INIT; } @@ -5366,6 +5665,15 @@ EmitForIn(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) if (Emit2(cx, bce, JSOP_ITER, (uint8_t) pn->pn_iflags) < 0) return false; + /* Enter the block before the loop body, after evaluating the obj. */ + StmtInfo letStmt; + if (letDecl) { + PushBlockScope(bce, &letStmt, blockObj, bce->offset()); + letStmt.flags |= SIF_FOR_BLOCK; + if (!EmitEnterBlock(cx, bce, pn1, JSOP_ENTERLET1)) + return false; + } + /* Annotate so the decompiler can find the loop-closing jump. */ intN noteIndex = NewSrcNote(cx, bce, SRC_FOR_IN); if (noteIndex < 0) @@ -5396,6 +5704,7 @@ EmitForIn(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) */ if (!EmitAssignment(cx, bce, forHead->pn_kid2, JSOP_NOP, NULL)) return false; + ptrdiff_t tmp2 = bce->offset(); if (forHead->pn_kid1 && NewSrcNote2(cx, bce, SRC_DECL, (forHead->pn_kid1->isOp(JSOP_DEFVAR)) @@ -5436,14 +5745,30 @@ EmitForIn(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) if (!SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 1, beq - jmp)) return false; - /* Now fixup all breaks and continues (before the JSOP_ENDITER). */ + /* Fixup breaks and continues before JSOP_ITER (and JSOP_LEAVEFORINLET). */ if (!PopStatementBCE(cx, bce)) return false; + if (letDecl) { + if (!PopStatementBCE(cx, bce)) + return false; + if (Emit1(cx, bce, JSOP_LEAVEFORLETIN) < 0) + return false; + } + if (!NewTryNote(cx, bce, JSTRY_ITER, bce->stackDepth, top, bce->offset())) return false; + if (Emit1(cx, bce, JSOP_ENDITER) < 0) + return false; - return Emit1(cx, bce, JSOP_ENDITER) >= 0; + if (letDecl) { + /* Tell the decompiler to pop but not to print. */ + if (NewSrcNote(cx, bce, SRC_CONTINUE) < 0) + return false; + EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount); + } + + return true; } static bool @@ -5480,7 +5805,7 @@ EmitNormalFor(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) * not need to emit a pop below, so switch to a nop, * just for the decompiler. */ - JS_ASSERT(pn3->isArity(PN_LIST)); + JS_ASSERT(pn3->isArity(PN_LIST) || pn3->isArity(PN_BINARY)); if (pn3->pn_xflags & PNX_GROUPINIT) op = JSOP_NOP; } @@ -6716,18 +7041,13 @@ EmitUnary(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) JSBool frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) { - ptrdiff_t top, off, tmp, jmp; - ParseNode *pn2; - JSAtom *atom; - ptrdiff_t noteIndex; - JSOp op; - EmitLevelManager elm(bce); - jsint sharpnum = -1; - JS_CHECK_RECURSION(cx, return JS_FALSE); + EmitLevelManager elm(bce); + JSBool ok = true; - pn->pn_offset = top = bce->offset(); + ptrdiff_t top = bce->offset(); + pn->pn_offset = top; /* Emit notes to tell the current bytecode's source line number. */ UPDATE_LINE_NUMBER_NOTES(cx, bce, pn->pn_pos.begin.lineno); @@ -6806,7 +7126,7 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) case PNK_VAR: case PNK_CONST: - if (!EmitVariables(cx, bce, pn, JS_FALSE, ¬eIndex)) + if (!EmitVariables(cx, bce, pn, InitializeVars)) return JS_FALSE; break; @@ -6858,16 +7178,17 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) break; case PNK_COMMA: + { /* * Emit SRC_PCDELTA notes on each JSOP_POP between comma operands. * These notes help the decompiler bracket the bytecodes generated * from each sub-expression that follows a comma. */ - off = noteIndex = -1; - for (pn2 = pn->pn_head; ; pn2 = pn2->pn_next) { + ptrdiff_t off = -1, noteIndex = -1; + for (ParseNode *pn2 = pn->pn_head; ; pn2 = pn2->pn_next) { if (!EmitTree(cx, bce, pn2)) return JS_FALSE; - tmp = bce->offset(); + ptrdiff_t tmp = bce->offset(); if (noteIndex >= 0) { if (!SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 0, tmp-off)) return JS_FALSE; @@ -6882,6 +7203,7 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) } } break; + } case PNK_ASSIGN: case PNK_ADDASSIGN: @@ -6931,10 +7253,10 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) case PNK_MOD: if (pn->isArity(PN_LIST)) { /* Left-associative operator chain: avoid too much recursion. */ - pn2 = pn->pn_head; + ParseNode *pn2 = pn->pn_head; if (!EmitTree(cx, bce, pn2)) return JS_FALSE; - op = pn->getOp(); + JSOp op = pn->getOp(); while ((pn2 = pn2->pn_next) != NULL) { if (!EmitTree(cx, bce, pn2)) return JS_FALSE; @@ -7023,11 +7345,12 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) #if JS_HAS_XML_SUPPORT case PNK_FILTER: + { JS_ASSERT(!bce->inStrictMode()); if (!EmitTree(cx, bce, pn->pn_left)) return JS_FALSE; - jmp = EmitJump(cx, bce, JSOP_FILTER, 0); + ptrdiff_t jmp = EmitJump(cx, bce, JSOP_FILTER, 0); if (jmp < 0) return JS_FALSE; top = EmitTraceOp(cx, bce, pn->pn_right); @@ -7039,6 +7362,7 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (EmitJump(cx, bce, JSOP_ENDFILTER, top - bce->offset()) < 0) return JS_FALSE; break; + } #endif case PNK_DOT: @@ -7076,8 +7400,9 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) #if JS_HAS_BLOCK_SCOPE case PNK_LET: - if (!EmitLet(cx, bce, pn)) - return false; + ok = pn->isArity(PN_BINARY) + ? EmitLet(cx, bce, pn) + : EmitVariables(cx, bce, pn, InitializeVars); break; #endif /* JS_HAS_BLOCK_SCOPE */ #if JS_HAS_GENERATORS @@ -7112,8 +7437,9 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) #if JS_HAS_SHARP_VARS case PNK_DEFSHARP: + { JS_ASSERT(bce->hasSharps()); - sharpnum = pn->pn_num; + int sharpnum = pn->pn_num; pn = pn->pn_kid; if (pn->isKind(PNK_RB)) { ok = EmitArray(cx, bce, pn, sharpnum); @@ -7134,6 +7460,7 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) return JS_FALSE; EMIT_UINT16PAIR_IMM_OP(JSOP_DEFSHARP, bce->sharpSlotBase, (jsatomid) sharpnum); break; + } case PNK_USESHARP: JS_ASSERT(bce->hasSharps()); @@ -7209,7 +7536,7 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) return JS_FALSE; } - for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + for (ParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { if (pn2->isKind(PNK_XMLCURLYEXPR) && Emit1(cx, bce, JSOP_STARTXMLEXPR) < 0) return JS_FALSE; if (!EmitTree(cx, bce, pn2)) @@ -7221,7 +7548,7 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (pn->pn_xflags & PNX_XMLROOT) { if (pn->pn_count == 0) { JS_ASSERT(pn->isKind(PNK_XMLLIST)); - atom = cx->runtime->atomState.emptyAtom; + JSAtom *atom = cx->runtime->atomState.emptyAtom; jsatomid index; if (!bce->makeAtomIndex(atom, &index)) return JS_FALSE; @@ -7248,7 +7575,7 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (pn->isArity(PN_LIST)) { JS_ASSERT(pn->pn_count != 0); - for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + for (ParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { if (pn2->isKind(PNK_XMLCURLYEXPR) && Emit1(cx, bce, JSOP_STARTXMLEXPR) < 0) return JS_FALSE; if (!EmitTree(cx, bce, pn2)) @@ -7447,7 +7774,7 @@ SetSrcNoteOffset(JSContext *cx, BytecodeEmitter *bce, uintN index, uintN which, jssrcnote *sn; ptrdiff_t diff; - if ((jsuword)offset >= (jsuword)((ptrdiff_t)SN_3BYTE_OFFSET_FLAG << 16)) { + if (size_t(offset) > SN_MAX_OFFSET) { ReportStatementTooLarge(cx, bce); return JS_FALSE; } @@ -7461,8 +7788,12 @@ SetSrcNoteOffset(JSContext *cx, BytecodeEmitter *bce, uintN index, uintN which, sn += 2; } - /* See if the new offset requires three bytes. */ - if (offset > (ptrdiff_t)SN_3BYTE_OFFSET_MASK) { + /* + * See if the new offset requires three bytes either by being too big or if + * the offset has already been inflated (in which case, we need to stay big + * to not break the srcnote encoding if this isn't the last srcnote). + */ + if (offset > (ptrdiff_t)SN_3BYTE_OFFSET_MASK || (*sn & SN_3BYTE_OFFSET_FLAG)) { /* Maybe this offset was already set to a three-byte value. */ if (!(*sn & SN_3BYTE_OFFSET_FLAG)) { /* Losing, need to insert another two bytes for this offset. */ diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index e337ce17c8f4..7b6433a2618f 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -942,9 +942,10 @@ enum SrcNoteType { from before loop, else JSOP_NOP at top of do-while loop */ SRC_CONTINUE = 5, /* JSOP_GOTO is a continue, not a break; - also used on JSOP_ENDINIT if extra comma - at end of array literal: [1,2,,]; - JSOP_DUP continuing destructuring pattern */ + JSOP_ENDINIT needs extra comma at end of + array literal: [1,2,,]; + JSOP_DUP continuing destructuring pattern; + JSOP_POP at end of for-in */ SRC_DECL = 6, /* type of a declaration (var, const, let*) */ SRC_DESTRUCT = 6, /* JSOP_DUP starting a destructuring assignment operation, with SRC_DECL_* offset operand */ @@ -952,6 +953,8 @@ enum SrcNoteType { next POP, or from CONDSWITCH to first CASE opcode, etc. -- always a forward delta */ SRC_GROUPASSIGN = 7, /* SRC_DESTRUCT variant for [a, b] = [c, d] */ + SRC_DESTRUCTLET = 7, /* JSOP_DUP starting a destructuring let + operation, with offset to JSOP_ENTERLET0 */ SRC_ASSIGNOP = 8, /* += or another assign-op follows */ SRC_COND = 9, /* JSOP_IFEQ is from conditional ?: operator */ SRC_BRACE = 10, /* mandatory brace, for scope or to avoid @@ -1030,6 +1033,8 @@ enum SrcNoteType { #define SN_3BYTE_OFFSET_FLAG 0x80 #define SN_3BYTE_OFFSET_MASK 0x7f +#define SN_MAX_OFFSET ((size_t)((ptrdiff_t)SN_3BYTE_OFFSET_FLAG << 16) - 1) + #define SN_LENGTH(sn) ((js_SrcNoteSpec[SN_TYPE(sn)].arity == 0) ? 1 \ : js_SrcNoteLength(sn)) #define SN_NEXT(sn) ((sn) + SN_LENGTH(sn)) @@ -1102,6 +1107,28 @@ BytecodeEmitter::countFinalSourceNotes() return cnt; } +/* + * To avoid offending js_SrcNoteSpec[SRC_DECL].arity, pack the two data needed + * to decompile let into one ptrdiff_t: + * offset: offset to the LEAVEBLOCK(EXPR) op (not including ENTER/LEAVE) + * groupAssign: whether this was an optimized group assign ([x,y] = [a,b]) + */ +inline ptrdiff_t PackLetData(size_t offset, bool groupAssign) +{ + JS_ASSERT(offset <= (size_t(-1) >> 1)); + return ptrdiff_t(offset << 1) | ptrdiff_t(groupAssign); +} + +inline size_t LetDataToOffset(ptrdiff_t w) +{ + return size_t(w) >> 1; +} + +inline bool LetDataToGroupAssign(ptrdiff_t w) +{ + return size_t(w) & 1; +} + } /* namespace js */ struct JSSrcNoteSpec { diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 425ef9e394f1..c633c1dc50f5 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -845,6 +845,11 @@ struct ParseNode { /* Return true if this node appears in a Directive Prologue. */ bool isDirectivePrologueMember() const { return pn_prologue; } +#ifdef JS_HAS_DESTRUCTURING + /* Return true if this represents a hole in an array literal. */ + bool isArrayHole() const { return isKind(PNK_COMMA) && isArity(PN_NULLARY); } +#endif + #ifdef JS_HAS_GENERATOR_EXPRS /* * True if this node is a desugared generator expression. diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 65a305838738..c58efa1e08c9 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -837,6 +837,12 @@ js::DefineArg(ParseNode *pn, JSAtom *atom, uintN i, TreeContext *tc) typedef JSBool (*Binder)(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *tc); +static JSBool +BindLet(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *tc); + +static JSBool +BindVarOrConst(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *tc); + struct BindData { BindData() : fresh(true) {} @@ -846,10 +852,26 @@ struct BindData { Binder binder; /* binder, discriminates u */ union { struct { + VarContext varContext; + JSObject *blockObj; uintN overflow; } let; }; bool fresh; + + void initLet(VarContext varContext, JSObject *blockObj, uintN overflow) { + this->pn = NULL; + this->op = JSOP_NOP; + this->binder = BindLet; + this->let.varContext = varContext; + this->let.blockObj = blockObj; + this->let.overflow = overflow; + } + + void initVarOrConst(JSOp op) { + this->op = op; + this->binder = BindVarOrConst; + } }; static bool @@ -1877,6 +1899,19 @@ MatchLabel(JSContext *cx, TokenStream *ts, PropertyName **label) return true; } +static bool +ReportRedeclaration(JSContext *cx, TreeContext *tc, ParseNode *pn, bool isConst, JSAtom *atom) +{ + JSAutoByteString name; + if (js_AtomToPrintableString(cx, atom, &name)) { + ReportCompileErrorNumber(cx, TS(tc->parser), pn, + JSREPORT_ERROR, JSMSG_REDECLARED_VAR, + isConst ? "const" : "variable", + name.ptr()); + } + return false; +} + /* * Define a let-variable in a block, let-expression, or comprehension scope. tc * must already be in such a scope. @@ -1889,46 +1924,30 @@ MatchLabel(JSContext *cx, TokenStream *ts, PropertyName **label) static JSBool BindLet(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *tc) { - ParseNode *pn; - JSObject *blockObj; - jsint n; - - /* - * Body-level 'let' is the same as 'var' currently -- this may change in a - * successor standard to ES5 that specifies 'let'. - */ - JS_ASSERT(!tc->atBodyLevel()); - - pn = data->pn; + ParseNode *pn = data->pn; if (!CheckStrictBinding(cx, tc, atom->asPropertyName(), pn)) return false; - blockObj = tc->blockChain; - Definition *dn = tc->decls.lookupFirst(atom); - if (dn && dn->pn_blockid == tc->blockid()) { - JSAutoByteString name; - if (js_AtomToPrintableString(cx, atom, &name)) { - ReportCompileErrorNumber(cx, TS(tc->parser), pn, - JSREPORT_ERROR, JSMSG_REDECLARED_VAR, - dn->isConst() ? "const" : "variable", - name.ptr()); - } - return false; - } - - n = OBJ_BLOCK_COUNT(cx, blockObj); - if (n == JS_BIT(16)) { + JSObject *blockObj = data->let.blockObj; + uintN blockCount = OBJ_BLOCK_COUNT(cx, blockObj); + if (blockCount == JS_BIT(16)) { ReportCompileErrorNumber(cx, TS(tc->parser), pn, JSREPORT_ERROR, data->let.overflow); return false; } /* - * Pass push = true to Define so it pushes an ale ahead of any outer scope. - * This is balanced by PopStatement, defined immediately below. + * For bindings that are hoisted to the beginning of the block/function, + * Define() right now. For the rest, delay Define() until PushLetScope. */ - if (!Define(pn, atom, tc, true)) - return false; + if (data->let.varContext == HoistVars) { + JS_ASSERT(!tc->atBodyLevel()); + Definition *dn = tc->decls.lookupFirst(atom); + if (dn && dn->pn_blockid == tc->blockid()) + return ReportRedeclaration(cx, tc, pn, dn->isConst(), atom); + if (!Define(pn, atom, tc, true)) + return false; + } /* * Assign block-local index to pn->pn_cookie right away, encoding it as an @@ -1938,47 +1957,61 @@ BindLet(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *tc) * again to include script->nfixed. */ pn->setOp(JSOP_GETLOCAL); - pn->pn_cookie.set(tc->staticLevel, uint16_t(n)); + pn->pn_cookie.set(tc->staticLevel, uint16_t(blockCount)); pn->pn_dflags |= PND_LET | PND_BOUND; /* * Define the let binding's property before storing pn in the the binding's - * slot indexed by n off the class-reserved slot base. + * slot indexed by blockCount off the class-reserved slot base. */ - const Shape *shape = blockObj->defineBlockVariable(cx, ATOM_TO_JSID(atom), n); - if (!shape) + bool redeclared; + jsid id = ATOM_TO_JSID(atom); + const Shape *shape = blockObj->defineBlockVariable(cx, id, blockCount, &redeclared); + if (!shape) { + if (redeclared) + ReportRedeclaration(cx, tc, pn, false, atom); return false; + } /* - * Store pn temporarily in what would be shape-mapped slots in a cloned - * block object (once the prototype's final population is known, after all - * 'let' bindings for this block have been parsed). We free these slots in - * BytecodeEmitter.cpp:EmitEnterBlock so they don't tie up unused space - * in the so-called "static" prototype Block. + * Store pn temporarily in the shape-mapped slots in the static block + * object. This value is clobbered in EmitEnterBlock. */ blockObj->setSlot(shape->slot(), PrivateValue(pn)); return true; } +template +static inline bool +ForEachLetDef(TreeContext *tc, JSObject *blockObj, Op op) +{ + for (Shape::Range r = blockObj->lastProperty()->all(); !r.empty(); r.popFront()) { + const Shape &shape = r.front(); + + /* Beware the destructuring dummy slots. */ + if (JSID_IS_INT(shape.propid())) + continue; + + if (!op(tc, blockObj, shape, JSID_TO_ATOM(shape.propid()))) + return false; + } + return true; +} + +struct RemoveDecl { + bool operator()(TreeContext *tc, JSObject *, const Shape &, JSAtom *atom) { + tc->decls.remove(atom); + return true; + } +}; + static void PopStatement(TreeContext *tc) { - StmtInfo *stmt = tc->topStmt; - - if (stmt->flags & SIF_SCOPE) { - JSObject *obj = stmt->blockObj; - JS_ASSERT(!obj->isClonedBlock()); - - for (Shape::Range r = obj->lastProperty()->all(); !r.empty(); r.popFront()) { - JSAtom *atom = JSID_TO_ATOM(r.front().propid()); - - /* Beware the empty destructuring dummy. */ - if (atom == tc->parser->context->runtime->atomState.emptyAtom) - continue; - tc->decls.remove(atom); - } - + if (tc->topStmt->flags & SIF_SCOPE) { + JSObject *obj = tc->topStmt->blockObj; JS_ASSERT(!obj->inDictionaryMode()); + ForEachLetDef(tc, obj, RemoveDecl()); } PopStatementTC(tc); } @@ -2536,9 +2569,13 @@ BindDestructuringLHS(JSContext *cx, ParseNode *pn, TreeContext *tc) * See also UndominateInitializers, immediately below. If you change * either of these functions, you might have to change the other to * match. + * + * The 'toplevel' is a private detail of the recursive strategy used by + * CheckDestructuring and callers should use the default value. */ static bool -CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, TreeContext *tc) +CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, TreeContext *tc, + bool toplevel = true) { bool ok; @@ -2548,12 +2585,15 @@ CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, TreeContext * return false; } + JSObject *blockObj = data && data->binder == BindLet ? data->let.blockObj : NULL; + uint32_t blockCountBefore = blockObj ? OBJ_BLOCK_COUNT(cx, blockObj) : 0; + if (left->isKind(PNK_RB)) { for (ParseNode *pn = left->pn_head; pn; pn = pn->pn_next) { /* Nullary comma is an elision; binary comma is an expression.*/ - if (!pn->isKind(PNK_COMMA) || !pn->isArity(PN_NULLARY)) { + if (!pn->isArrayHole()) { if (pn->isKind(PNK_RB) || pn->isKind(PNK_RC)) { - ok = CheckDestructuring(cx, data, pn, tc); + ok = CheckDestructuring(cx, data, pn, tc, false); } else { if (data) { if (!pn->isKind(PNK_NAME)) { @@ -2577,7 +2617,7 @@ CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, TreeContext * ParseNode *pn = pair->pn_right; if (pn->isKind(PNK_RB) || pn->isKind(PNK_RC)) { - ok = CheckDestructuring(cx, data, pn, tc); + ok = CheckDestructuring(cx, data, pn, tc, false); } else if (data) { if (!pn->isKind(PNK_NAME)) { ReportCompileErrorNumber(cx, TS(tc->parser), pn, JSREPORT_ERROR, @@ -2603,23 +2643,28 @@ CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, TreeContext * * let [] = 1; * * would violate this assumption as the there would be no let locals to - * store on the stack. To satisfy it we add an empty property to such - * blocks so that OBJ_BLOCK_COUNT(cx, blockObj), which gives the number of - * slots, would be always positive. + * store on the stack. * - * Note that we add such a property even if the block has locals due to - * later let declarations in it. We optimize for code simplicity here, - * not the fastest runtime performance with empty [] or {}. + * Furthermore, the decompiler needs an abstract stack location to store + * the decompilation of each let block/expr initializer. E.g., given: + * + * let (x = 1, [[]] = b, y = 3, {a:[]} = c) { ... } + * + * four slots are needed. + * + * To satisfy both constraints, we push a dummy slot (and add a + * corresponding dummy property to the block object) for each initializer + * that doesn't introduce at least one binding. */ - if (data && - data->binder == BindLet && - OBJ_BLOCK_COUNT(cx, tc->blockChain) == 0 && - !DefineNativeProperty(cx, tc->blockChain, - ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), - UndefinedValue(), NULL, NULL, - JSPROP_ENUMERATE | JSPROP_PERMANENT, - Shape::HAS_SHORTID, 0)) { - return false; + if (toplevel && blockObj && blockCountBefore == OBJ_BLOCK_COUNT(cx, blockObj)) { + if (!DefineNativeProperty(cx, blockObj, + INT_TO_JSID(blockCountBefore), + UndefinedValue(), NULL, NULL, + JSPROP_ENUMERATE | JSPROP_PERMANENT, + Shape::HAS_SHORTID, blockCountBefore)) { + return false; + } + JS_ASSERT(OBJ_BLOCK_COUNT(cx, blockObj) == blockCountBefore + 1); } return true; @@ -2764,16 +2809,12 @@ Parser::returnOrYield(bool useAssignExpr) } static ParseNode * -PushLexicalScope(JSContext *cx, TokenStream *ts, TreeContext *tc, StmtInfo *stmt) +PushLexicalScope(JSContext *cx, TreeContext *tc, JSObject *obj, StmtInfo *stmt) { ParseNode *pn = LexicalScopeNode::create(PNK_LEXICALSCOPE, tc); if (!pn) return NULL; - JSObject *obj = js_NewBlockObject(cx); - if (!obj) - return NULL; - ObjectBox *blockbox = tc->parser->newObjectBox(obj); if (!blockbox) return NULL; @@ -2789,36 +2830,85 @@ PushLexicalScope(JSContext *cx, TokenStream *ts, TreeContext *tc, StmtInfo *stmt return pn; } +static ParseNode * +PushLexicalScope(JSContext *cx, TreeContext *tc, StmtInfo *stmt) +{ + JSObject *obj = js_NewBlockObject(cx); + if (!obj) + return NULL; + + return PushLexicalScope(cx, tc, obj, stmt); +} + #if JS_HAS_BLOCK_SCOPE +struct AddDecl +{ + uint32_t blockid; + + AddDecl(uint32_t blockid) : blockid(blockid) {} + + bool operator()(TreeContext *tc, JSObject *blockObj, const Shape &shape, JSAtom *atom) + { + ParseNode *def = (ParseNode *) blockObj->getSlot(shape.slot()).toPrivate(); + def->pn_blockid = blockid; + return Define(def, atom, tc, true); + } +}; + +static ParseNode * +PushLetScope(JSContext *cx, TreeContext *tc, JSObject *blockObj, StmtInfo *stmt) +{ + ParseNode *pn = PushLexicalScope(cx, tc, blockObj, stmt); + if (!pn) + return NULL; + + /* Tell codegen to emit JSOP_ENTERLETx (not JSOP_ENTERBLOCK). */ + pn->pn_dflags |= PND_LET; + + /* Populate the new scope with decls found in the head with updated blockid. */ + if (!ForEachLetDef(tc, blockObj, AddDecl(stmt->blockid))) + return NULL; + + return pn; +} + +/* + * Parse a let block statement or let expression (determined by 'letContext'). + * In both cases, bindings are not hoisted to the top of the enclosing block + * and thus must be carefully injected between variables() and the let body. + */ ParseNode * -Parser::letBlock(JSBool statement) +Parser::letBlock(LetContext letContext) { JS_ASSERT(tokenStream.currentToken().type == TOK_LET); - /* Create the let binary node. */ ParseNode *pnlet = BinaryNode::create(PNK_LET, tc); if (!pnlet) return NULL; + JSObject *blockObj = js_NewBlockObject(context); + if (!blockObj) + return NULL; + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_LET); - /* This is a let block or expression of the form: let (a, b, c) .... */ - StmtInfo stmtInfo; - ParseNode *pnblock = PushLexicalScope(context, &tokenStream, tc, &stmtInfo); - if (!pnblock) + ParseNode *vars = variables(PNK_LET, blockObj, DontHoistVars); + if (!vars) return NULL; - ParseNode *pn = pnblock; - pn->pn_expr = pnlet; - - pnlet->pn_left = variables(PNK_LP, true); - if (!pnlet->pn_left) - return NULL; - pnlet->pn_left->pn_xflags = PNX_POPVAR; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_LET); - if (statement && !tokenStream.matchToken(TOK_LC, TSF_OPERAND)) { + StmtInfo stmtInfo; + ParseNode *block = PushLetScope(context, tc, blockObj, &stmtInfo); + if (!block) + return NULL; + + pnlet->pn_left = vars; + pnlet->pn_right = block; + + ParseNode *ret; + if (letContext == LetStatement && !tokenStream.matchToken(TOK_LC, TSF_OPERAND)) { /* * Strict mode eliminates a grammar ambiguity with unparenthesized * LetExpressions in an ExpressionStatement. If followed immediately @@ -2837,33 +2927,35 @@ Parser::letBlock(JSBool statement) * need to wrap the TOK_LET node in a TOK_SEMI node so that we pop * the return value of the expression. */ - pn = UnaryNode::create(PNK_SEMI, tc); - if (!pn) + ParseNode *semi = UnaryNode::create(PNK_SEMI, tc); + if (!semi) return NULL; - pn->pn_num = -1; - pn->pn_kid = pnblock; - statement = JS_FALSE; + semi->pn_num = -1; + semi->pn_kid = pnlet; + + letContext = LetExpresion; + ret = semi; + } else { + ret = pnlet; } - if (statement) { - pnlet->pn_right = statements(); - if (!pnlet->pn_right) + if (letContext == LetStatement) { + JS_ASSERT(block->getOp() == JSOP_LEAVEBLOCK); + block->pn_expr = statements(); + if (!block->pn_expr) return NULL; MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LET); } else { - /* - * Change pnblock's opcode to the variant that propagates the last - * result down after popping the block, and clear statement. - */ - pnblock->setOp(JSOP_LEAVEBLOCKEXPR); - pnlet->pn_right = assignExpr(); - if (!pnlet->pn_right) + JS_ASSERT(letContext == LetExpresion); + block->setOp(JSOP_LEAVEBLOCKEXPR); + block->pn_expr = assignExpr(); + if (!block->pn_expr) return NULL; } PopStatement(tc); - return pn; + return ret; } #endif /* JS_HAS_BLOCK_SCOPE */ @@ -2876,43 +2968,50 @@ PushBlocklikeStatement(StmtInfo *stmt, StmtType type, TreeContext *tc) } static ParseNode * -NewBindingNode(JSAtom *atom, TreeContext *tc, bool let = false) +NewBindingNode(JSAtom *atom, TreeContext *tc, JSObject *blockObj = NULL, + VarContext varContext = HoistVars) { - ParseNode *pn; - AtomDefnPtr removal; + /* + * If this name is being injected into an existing block/function, see if + * it has already been declared or if it resolves an outstanding lexdep. + * Otherwise, this is a let block/expr that introduces a new scope and thus + * shadows existing decls and doesn't resolve existing lexdeps. Duplicate + * names are caught by BindLet. + */ + if (!blockObj || varContext == HoistVars) { + ParseNode *pn = tc->decls.lookupFirst(atom); + AtomDefnPtr removal; + if (pn) { + JS_ASSERT(!pn->isPlaceholder()); + } else { + removal = tc->lexdeps->lookup(atom); + pn = removal ? removal.value() : NULL; + JS_ASSERT_IF(pn, pn->isPlaceholder()); + } - if ((pn = tc->decls.lookupFirst(atom))) { - JS_ASSERT(!pn->isPlaceholder()); - } else { - removal = tc->lexdeps->lookup(atom); - pn = removal ? removal.value() : NULL; - JS_ASSERT_IF(pn, pn->isPlaceholder()); - } + if (pn) { + JS_ASSERT(pn->isDefn()); - if (pn) { - JS_ASSERT(pn->isDefn()); + /* + * A let binding at top level becomes a var before we get here, so if + * pn and tc have the same blockid then that id must not be the bodyid. + * If pn is a forward placeholder definition from the same or a higher + * block then we claim it. + */ + JS_ASSERT_IF(blockObj && pn->pn_blockid == tc->blockid(), + pn->pn_blockid != tc->bodyid); - /* - * A let binding at top level becomes a var before we get here, so if - * pn and tc have the same blockid then that id must not be the bodyid. - * If pn is a forward placeholder definition from the same or a higher - * block then we claim it. - */ - JS_ASSERT_IF(let && pn->pn_blockid == tc->blockid(), - pn->pn_blockid != tc->bodyid); - - if (pn->isPlaceholder() && pn->pn_blockid >= (let ? tc->blockid() : tc->bodyid)) { - if (let) + if (pn->isPlaceholder() && pn->pn_blockid >= tc->blockid()) { pn->pn_blockid = tc->blockid(); - - tc->lexdeps->remove(removal); - return pn; + tc->lexdeps->remove(removal); + return pn; + } } } /* Make a new node for this declarator name (or destructuring pattern). */ JS_ASSERT(tc->parser->tokenStream.currentToken().type == TOK_NAME); - pn = NameNode::create(PNK_NAME, atom, tc); + ParseNode *pn = NameNode::create(PNK_NAME, atom, tc); if (!pn) return NULL; @@ -3043,18 +3142,13 @@ Parser::forStatement() { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); - ParseNode *pnseq = NULL; -#if JS_HAS_BLOCK_SCOPE - ParseNode *pnlet = NULL; - StmtInfo blockInfo; -#endif - /* A FOR node is binary, left is loop control and right is the body. */ ParseNode *pn = BinaryNode::create(PNK_FOR, tc); if (!pn) return NULL; - StmtInfo stmtInfo; - PushStatement(tc, &stmtInfo, STMT_FOR_LOOP, -1); + + StmtInfo forStmt; + PushStatement(tc, &forStmt, STMT_FOR_LOOP, -1); pn->setOp(JSOP_ITER); pn->pn_iflags = 0; @@ -3067,16 +3161,15 @@ Parser::forStatement() MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); -#ifdef JS_HAS_BLOCK_SCOPE - bool let = false; -#endif - /* * True if we have 'for (var/let/const ...)', except in the oddball case * where 'let' begins a let-expression in 'for (let (...) ...)'. */ bool forDecl = false; + /* Non-null when forDecl is true for a 'for (let ...)' statement. */ + JSObject *blockObj = NULL; + /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */ ParseNode *pn1; @@ -3107,21 +3200,19 @@ Parser::forStatement() if (tt == TOK_VAR || tt == TOK_CONST) { forDecl = true; tokenStream.consumeKnownToken(tt); - pn1 = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST, false); + pn1 = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST); } #if JS_HAS_BLOCK_SCOPE else if (tt == TOK_LET) { - let = true; (void) tokenStream.getToken(); if (tokenStream.peekToken() == TOK_LP) { - pn1 = letBlock(JS_FALSE); + pn1 = letBlock(LetExpresion); } else { forDecl = true; - pnlet = PushLexicalScope(context, &tokenStream, tc, &blockInfo); - if (!pnlet) + blockObj = js_NewBlockObject(context); + if (!blockObj) return NULL; - blockInfo.flags |= SIF_FOR_BLOCK; - pn1 = variables(PNK_LET, false); + pn1 = variables(PNK_LET, blockObj, DontHoistVars); } } #endif @@ -3134,14 +3225,23 @@ Parser::forStatement() } } + JS_ASSERT_IF(forDecl, pn1->isArity(PN_LIST)); + JS_ASSERT(!!blockObj == (forDecl && pn1->isOp(JSOP_NOP))); + + const TokenPos pos = tokenStream.currentToken().pos; + + /* If non-null, the parent that should be returned instead of forHead. */ + ParseNode *forParent = NULL; + /* * We can be sure that it's a for/in loop if there's still an 'in' * keyword here, even if JavaScript recognizes 'in' as an operator, * as we've excluded 'in' from being parsed in RelExpr by setting * the TCF_IN_FOR_INIT flag in our TreeContext. */ - TokenPos pos = tokenStream.currentToken().pos; - ParseNode *pn2, *pn3, *pn4; + ParseNode *forHead; /* initialized by both branches. */ + StmtInfo letStmt; /* used if blockObj != NULL. */ + ParseNode *pn2, *pn3; /* forHead->pn_kid1 and pn_kid2. */ if (pn1 && tokenStream.matchToken(TOK_IN)) { /* * Parse the rest of the for/in head. @@ -3151,7 +3251,7 @@ Parser::forStatement() * enumeration value each iteration, and pn3 is the rhs of 'in'. */ pn->pn_iflags |= JSITER_ENUMERATE; - stmtInfo.type = STMT_FOR_IN_LOOP; + forStmt.type = STMT_FOR_IN_LOOP; /* Check that the left side of the 'in' is valid. */ if (forDecl @@ -3214,16 +3314,15 @@ Parser::forStatement() * the loop head. */ #if JS_HAS_BLOCK_SCOPE - if (let) { + if (blockObj) { reportErrorNumber(pn2, JSREPORT_ERROR, JSMSG_INVALID_FOR_IN_INIT); return NULL; } #endif /* JS_HAS_BLOCK_SCOPE */ - pnseq = ListNode::create(PNK_SEQ, tc); + ParseNode *pnseq = ListNode::create(PNK_SEQ, tc); if (!pnseq) return NULL; - pnseq->pn_pos.begin = pn->pn_pos.begin; dflag = PND_INITIALIZED; @@ -3238,6 +3337,7 @@ Parser::forStatement() pn1->pn_xflags &= ~PNX_FORINVAR; pn1->pn_xflags |= PNX_POPVAR; pnseq->initList(pn1); + pn1 = NULL; #if JS_HAS_DESTRUCTURING if (pn2->isKind(PNK_ASSIGN)) { @@ -3246,18 +3346,12 @@ Parser::forStatement() pn2->isKind(PNK_NAME)); } #endif - pn1 = NULL; + pnseq->append(pn); + forParent = pnseq; } - - /* - * pn2 is part of a declaration. Make a copy that can be passed to - * EmitAssignment. - */ - pn2 = CloneLeftHandSide(pn2, tc); - if (!pn2) - return NULL; } else { /* Not a declaration. */ + JS_ASSERT(!blockObj); pn2 = pn1; pn1 = NULL; @@ -3265,6 +3359,37 @@ Parser::forStatement() return NULL; } + pn3 = expr(); + if (!pn3) + return NULL; + + if (blockObj) { + /* + * Now that the pn3 has been parsed, push the let scope. To hold + * the blockObj for the emitter, wrap the TOK_LEXICALSCOPE node + * created by PushLetScope around the for's initializer. This also + * serves to indicate the let-decl to the emitter. + */ + ParseNode *block = PushLetScope(context, tc, blockObj, &letStmt); + if (!block) + return NULL; + letStmt.flags |= SIF_FOR_BLOCK; + block->pn_expr = pn1; + pn1 = block; + } + + if (forDecl) { + /* + * pn2 is part of a declaration. Make a copy that can be passed to + * EmitAssignment. Take care to do this after PushLetScope has + * Define's the new binding since this pn2->isDefn() which tells + * CloneLeftHandSide to make the new pn2 a use. + */ + pn2 = CloneLeftHandSide(pn2, tc); + if (!pn2) + return NULL; + } + switch (pn2->getKind()) { case PNK_NAME: /* Beware 'for (arguments in ...)' with or without a 'var'. */ @@ -3293,28 +3418,29 @@ Parser::forStatement() default:; } - /* - * Parse the object expression as the right operand of 'in', first - * removing the top statement from the statement-stack if this is a - * 'for (let x in y)' loop. - */ -#if JS_HAS_BLOCK_SCOPE - StmtInfo *save = tc->topStmt; - if (let) - tc->topStmt = save->down; -#endif - pn3 = expr(); - if (!pn3) - return NULL; -#if JS_HAS_BLOCK_SCOPE - if (let) - tc->topStmt = save; -#endif - - pn4 = TernaryNode::create(PNK_FORIN, tc); - if (!pn4) + forHead = TernaryNode::create(PNK_FORIN, tc); + if (!forHead) return NULL; } else { + if (blockObj) { + /* + * Desugar 'for (let A; B; C) D' into 'let (A) { for (; B; C) D }' + * to induce the correct scoping for A. + */ + ParseNode *block = PushLetScope(context, tc, blockObj, &letStmt); + if (!block) + return NULL; + letStmt.flags |= SIF_FOR_BLOCK; + + ParseNode *let = new_(PNK_LET, JSOP_NOP, pos, pn1, block); + if (!let) + return NULL; + + pn1 = NULL; + block->pn_expr = pn; + forParent = let; + } + if (pn->pn_iflags & JSITER_FOREACH) { reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP); return NULL; @@ -3323,8 +3449,7 @@ Parser::forStatement() /* Parse the loop condition or null into pn2. */ MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT); - TokenKind tt = tokenStream.peekToken(TSF_OPERAND); - if (tt == TOK_SEMI) { + if (tokenStream.peekToken(TSF_OPERAND) == TOK_SEMI) { pn2 = NULL; } else { pn2 = expr(); @@ -3334,8 +3459,7 @@ Parser::forStatement() /* Parse the update expression or null into pn3. */ MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_COND); - tt = tokenStream.peekToken(TSF_OPERAND); - if (tt == TOK_RP) { + if (tokenStream.peekToken(TSF_OPERAND) == TOK_RP) { pn3 = NULL; } else { pn3 = expr(); @@ -3343,42 +3467,40 @@ Parser::forStatement() return NULL; } - pn4 = TernaryNode::create(PNK_FORHEAD, tc); - if (!pn4) + forHead = TernaryNode::create(PNK_FORHEAD, tc); + if (!forHead) return NULL; } - pn4->pn_pos = pos; - pn4->setOp(JSOP_NOP); - pn4->pn_kid1 = pn1; - pn4->pn_kid2 = pn2; - pn4->pn_kid3 = pn3; - pn->pn_left = pn4; + + forHead->pn_pos = pos; + forHead->setOp(JSOP_NOP); + forHead->pn_kid1 = pn1; + forHead->pn_kid2 = pn2; + forHead->pn_kid3 = pn3; + pn->pn_left = forHead; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); - /* Parse the loop body into pn->pn_right. */ - pn2 = statement(); - if (!pn2) + /* Parse the loop body. */ + ParseNode *body = statement(); + if (!body) return NULL; - pn->pn_right = pn2; /* Record the absolute line number for source note emission. */ - pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_pos.end = body->pn_pos.end; + pn->pn_right = body; + + if (forParent) { + forParent->pn_pos.begin = pn->pn_pos.begin; + forParent->pn_pos.end = pn->pn_pos.end; + } #if JS_HAS_BLOCK_SCOPE - if (pnlet) { + if (blockObj) PopStatement(tc); - pnlet->pn_expr = pn; - pn = pnlet; - } #endif - if (pnseq) { - pnseq->pn_pos.end = pn->pn_pos.end; - pnseq->append(pn); - pn = pnseq; - } PopStatement(tc); - return pn; + return forParent ? forParent : pn; } ParseNode * @@ -3442,7 +3564,7 @@ Parser::tryStatement() * Create a lexical scope node around the whole catch clause, * including the head. */ - pnblock = PushLexicalScope(context, &tokenStream, tc, &stmtInfo); + pnblock = PushLexicalScope(context, tc, &stmtInfo); if (!pnblock) return NULL; stmtInfo.type = STMT_CATCH; @@ -3465,10 +3587,8 @@ Parser::tryStatement() * scoped, not a property of a new Object instance. This is * an intentional change that anticipates ECMA Ed. 4. */ - data.pn = NULL; - data.op = JSOP_NOP; - data.binder = BindLet; - data.let.overflow = JSMSG_TOO_MANY_CATCH_VARS; + data.initLet(HoistVars, tc->blockChain, JSMSG_TOO_MANY_CATCH_VARS); + JS_ASSERT(data.let.blockObj && data.let.blockObj == pnblock->pn_objbox->object); tt = tokenStream.getToken(); ParseNode *pn3; @@ -3485,7 +3605,7 @@ Parser::tryStatement() case TOK_NAME: { JSAtom *label = tokenStream.currentToken().name(); - pn3 = NewBindingNode(label, tc, true); + pn3 = NewBindingNode(label, tc); if (!pn3) return NULL; data.pn = pn3; @@ -3611,12 +3731,16 @@ Parser::letStatement() do { /* Check for a let statement or let expression. */ if (tokenStream.peekToken() == TOK_LP) { - pn = letBlock(JS_TRUE); - if (!pn || pn->isOp(JSOP_LEAVEBLOCK)) + pn = letBlock(LetStatement); + if (!pn) + return NULL; + + JS_ASSERT(pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI)); + if (pn->isKind(PNK_LET) && pn->pn_expr->getOp() == JSOP_LEAVEBLOCK) return pn; /* Let expressions require automatic semicolon insertion. */ - JS_ASSERT(pn->isKind(PNK_SEMI) || pn->isOp(JSOP_LEAVEBLOCKEXPR)); + JS_ASSERT(pn->isKind(PNK_SEMI) || pn->isOp(JSOP_NOP)); break; } @@ -3646,7 +3770,7 @@ Parser::letStatement() * ES4 specifies that let at top level and at body-block scope * does not shadow var, so convert back to var. */ - pn = variables(PNK_VAR, false); + pn = variables(PNK_VAR); if (!pn) return NULL; pn->pn_xflags |= PNX_POPVAR; @@ -3707,7 +3831,7 @@ Parser::letStatement() tc->blockNode = pn1; } - pn = variables(PNK_LET, false); + pn = variables(PNK_LET, tc->blockChain, HoistVars); if (!pn) return NULL; pn->pn_xflags = PNX_POPVAR; @@ -4039,7 +4163,7 @@ Parser::statement() return withStatement(); case TOK_VAR: - pn = variables(PNK_VAR, false); + pn = variables(PNK_VAR); if (!pn) return NULL; @@ -4048,7 +4172,7 @@ Parser::statement() break; case TOK_CONST: - pn = variables(PNK_CONST, false); + pn = variables(PNK_CONST); if (!pn) return NULL; @@ -4152,8 +4276,13 @@ Parser::statement() return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL; } +/* + * The 'blockObj' parameter is non-null when parsing the 'vars' in a let + * expression, block statement, non-top-level let declaration in statement + * context, and the let-initializer of a for-statement. + */ ParseNode * -Parser::variables(ParseNodeKind kind, bool inLetHead) +Parser::variables(ParseNodeKind kind, JSObject *blockObj, VarContext varContext) { /* * The four options here are: @@ -4164,29 +4293,11 @@ Parser::variables(ParseNodeKind kind, bool inLetHead) */ JS_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || kind == PNK_LP); - bool let = (kind == PNK_LET || kind == PNK_LP); - -#if JS_HAS_BLOCK_SCOPE - bool popScope = (inLetHead || (let && (tc->flags & TCF_IN_FOR_INIT))); - StmtInfo *save = tc->topStmt, *saveScope = tc->topScopeStmt; -#endif - - /* Make sure that statement set up the tree context correctly. */ - StmtInfo *scopeStmt = tc->topScopeStmt; - if (let) { - while (scopeStmt && !(scopeStmt->flags & SIF_SCOPE)) { - JS_ASSERT(!STMT_MAYBE_SCOPE(scopeStmt)); - scopeStmt = scopeStmt->downScope; - } - JS_ASSERT(scopeStmt); - } - - BindData data; - data.op = let ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST; ParseNode *pn = ListNode::create(kind, tc); if (!pn) return NULL; - pn->setOp(data.op); + + pn->setOp(blockObj ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST); pn->makeEmpty(); /* @@ -4194,13 +4305,11 @@ Parser::variables(ParseNodeKind kind, bool inLetHead) * var, whereas let is block scoped. ES-Harmony wants block-scoped const so * this code will change soon. */ - if (let) { - JS_ASSERT(tc->blockChain == scopeStmt->blockObj); - data.binder = BindLet; - data.let.overflow = JSMSG_TOO_MANY_LOCALS; - } else { - data.binder = BindVarOrConst; - } + BindData data; + if (blockObj) + data.initLet(varContext, blockObj, JSMSG_TOO_MANY_LOCALS); + else + data.initVarOrConst(pn->getOp()); ParseNode *pn2; do { @@ -4223,20 +4332,7 @@ Parser::variables(ParseNodeKind kind, bool inLetHead) MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL); JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NOP); -#if JS_HAS_BLOCK_SCOPE - if (popScope) { - tc->topStmt = save->down; - tc->topScopeStmt = saveScope->downScope; - } -#endif ParseNode *init = assignExpr(); -#if JS_HAS_BLOCK_SCOPE - if (popScope) { - tc->topStmt = save; - tc->topScopeStmt = saveScope; - } -#endif - if (!init) return NULL; UndominateInitializers(pn2, init->pn_pos.end, tc); @@ -4256,7 +4352,7 @@ Parser::variables(ParseNodeKind kind, bool inLetHead) } PropertyName *name = tokenStream.currentToken().name(); - pn2 = NewBindingNode(name, tc, let); + pn2 = NewBindingNode(name, tc, blockObj, varContext); if (!pn2) return NULL; if (data.op == JSOP_DEFCONST) @@ -4269,19 +4365,7 @@ Parser::variables(ParseNodeKind kind, bool inLetHead) if (tokenStream.matchToken(TOK_ASSIGN)) { JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NOP); -#if JS_HAS_BLOCK_SCOPE - if (popScope) { - tc->topStmt = save->down; - tc->topScopeStmt = saveScope->downScope; - } -#endif ParseNode *init = assignExpr(); -#if JS_HAS_BLOCK_SCOPE - if (popScope) { - tc->topStmt = save; - tc->topScopeStmt = saveScope; - } -#endif if (!init) return NULL; @@ -4310,7 +4394,7 @@ Parser::variables(ParseNodeKind kind, bool inLetHead) if (tc->inFunction() && name == context->runtime->atomState.argumentsAtom) { tc->noteArgumentsUse(pn2); - if (!let) + if (!blockObj) tc->flags |= TCF_FUN_HEAVYWEIGHT; } } @@ -5254,7 +5338,7 @@ Parser::comprehensionTail(ParseNode *kid, uintN blockid, bool isGenexp, * yields the next value from a for-in loop (possibly nested, and with * optional if guard). Make pn be the TOK_LC body node. */ - pn = PushLexicalScope(context, &tokenStream, tc, &stmtInfo); + pn = PushLexicalScope(context, tc, &stmtInfo); if (!pn) return NULL; adjust = pn->pn_blockid - blockid; @@ -5274,7 +5358,7 @@ Parser::comprehensionTail(ParseNode *kid, uintN blockid, bool isGenexp, * block scope. */ adjust = tc->blockid(); - pn = PushLexicalScope(context, &tokenStream, tc, &stmtInfo); + pn = PushLexicalScope(context, tc, &stmtInfo); if (!pn) return NULL; @@ -5291,10 +5375,8 @@ Parser::comprehensionTail(ParseNode *kid, uintN blockid, bool isGenexp, CompExprTransplanter transplanter(kid, tc, kind == PNK_SEMI, adjust); transplanter.transplant(kid); - data.pn = NULL; - data.op = JSOP_NOP; - data.binder = BindLet; - data.let.overflow = JSMSG_ARRAY_INIT_TOO_BIG; + JS_ASSERT(tc->blockChain && tc->blockChain == pn->pn_objbox->object); + data.initLet(HoistVars, tc->blockChain, JSMSG_ARRAY_INIT_TOO_BIG); do { /* @@ -5342,7 +5424,7 @@ Parser::comprehensionTail(ParseNode *kid, uintN blockid, bool isGenexp, * and it tries to bind all names to slots, so we must let it do * the deed. */ - pn3 = NewBindingNode(name, tc, true); + pn3 = NewBindingNode(name, tc); if (!pn3) return NULL; break; @@ -6495,40 +6577,6 @@ Parser::parseXMLText(JSObject *chain, bool allowList) #endif /* JS_HAS_XMLSUPPORT */ -#if JS_HAS_BLOCK_SCOPE -/* - * Check whether blockid is an active scoping statement in tc. This code is - * necessary to qualify tc->decls.lookup() hits in primaryExpr's TOK_NAME case - * (below) where the hits come from Scheme-ish let bindings in for loop heads - * and let blocks and expressions (not let declarations). - * - * Unlike let declarations ("let as the new var"), which is a kind of letrec - * due to hoisting, let in a for loop head, let block, or let expression acts - * like Scheme's let: initializers are evaluated without the new let bindings - * being in scope. - * - * Name binding analysis is eager with fixups, rather than multi-pass, and let - * bindings push on the front of the tc->decls AtomDecls (either the singular - * list or on a hash chain -- see JSAtomMultiList::add*) in order to shadow - * outer scope bindings of the same name. - * - * This simplifies binding lookup code at the price of a linear search here, - * but only if code uses let (var predominates), and even then this function's - * loop iterates more than once only in crazy cases. - */ -static inline bool -BlockIdInScope(uintN blockid, TreeContext *tc) -{ - if (blockid > tc->blockid()) - return false; - for (StmtInfo *stmt = tc->topScopeStmt; stmt; stmt = stmt->downScope) { - if (stmt->blockid == blockid) - return true; - } - return false; -} -#endif - static ParseNode * PrimaryExprNode(ParseNodeKind kind, JSOp op, TreeContext *tc) { @@ -6891,7 +6939,7 @@ Parser::primaryExpr(TokenKind tt, JSBool afterDot) #if JS_HAS_BLOCK_SCOPE case TOK_LET: - pn = letBlock(JS_FALSE); + pn = letBlock(LetExpresion); if (!pn) return NULL; break; @@ -7048,26 +7096,8 @@ Parser::primaryExpr(TokenKind tt, JSBool afterDot) StmtInfo *stmt = LexicalLookup(tc, pn->pn_atom, NULL); MultiDeclRange mdl = tc->decls.lookupMulti(pn->pn_atom); + Definition *dn; - - if (!mdl.empty()) { - dn = mdl.front(); -#if JS_HAS_BLOCK_SCOPE - /* - * Skip out-of-scope let bindings along an ALE list or hash - * chain. These can happen due to |let (x = x) x| block and - * expression bindings, where the x on the right of = comes - * from an outer scope. See bug 496532. - */ - while (dn->isLet() && !BlockIdInScope(dn->pn_blockid, tc)) { - mdl.popFront(); - if (mdl.empty()) - break; - dn = mdl.front(); - } -#endif - } - if (!mdl.empty()) { dn = mdl.front(); } else { diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 19f0ea7ba11a..03a296588d64 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -61,6 +61,8 @@ typedef struct BindData BindData; namespace js { enum FunctionSyntaxKind { Expression, Statement }; +enum LetContext { LetExpresion, LetStatement }; +enum VarContext { HoistVars, DontHoistVars }; struct Parser : private AutoGCRooter { @@ -196,7 +198,7 @@ struct Parser : private AutoGCRooter ParseNode *letStatement(); #endif ParseNode *expressionStatement(); - ParseNode *variables(ParseNodeKind kind, bool inLetHead); + ParseNode *variables(ParseNodeKind kind, JSObject *blockObj = NULL, VarContext varContext = HoistVars); ParseNode *expr(); ParseNode *assignExpr(); ParseNode *condExpr1(); @@ -240,7 +242,7 @@ struct Parser : private AutoGCRooter ParseNode *generatorExpr(ParseNode *kid); JSBool argumentList(ParseNode *listNode); ParseNode *bracketedExpr(); - ParseNode *letBlock(JSBool statement); + ParseNode *letBlock(LetContext letContext); ParseNode *returnOrYield(bool useAssignExpr); ParseNode *destructuringExpr(BindData *data, TokenKind tt); diff --git a/js/src/jit-test/tests/basic/bug657975.js b/js/src/jit-test/tests/basic/bug657975.js index 534785643411..f3046ff5ac5f 100644 --- a/js/src/jit-test/tests/basic/bug657975.js +++ b/js/src/jit-test/tests/basic/bug657975.js @@ -60,7 +60,7 @@ f9 = (function() { for each(let w in []) {} } }) -trap(f9, 22, undefined); +trap(f9, 23, undefined); for (b in f9()) (function() {})() diff --git a/js/src/jit-test/tests/basic/testBug666292.js b/js/src/jit-test/tests/basic/testBug666292.js index ed0e8fd8dc3c..12238e75ca35 100644 --- a/js/src/jit-test/tests/basic/testBug666292.js +++ b/js/src/jit-test/tests/basic/testBug666292.js @@ -4,7 +4,7 @@ function f(){ this.zzz.zzz; for(let d in []); } -trap(f, 18, '') +trap(f, 16, '') try { f() } catch(e) { diff --git a/js/src/jit-test/tests/basic/testBug683470.js b/js/src/jit-test/tests/basic/testBug683470.js index 83b0945a1b60..e6e27346bea5 100644 --- a/js/src/jit-test/tests/basic/testBug683470.js +++ b/js/src/jit-test/tests/basic/testBug683470.js @@ -11,5 +11,5 @@ f = (function() { } catch (e) {} } }) -trap(f, 39, undefined); +trap(f, 40, undefined); f() diff --git a/js/src/jit-test/tests/basic/testBug692274-1.js b/js/src/jit-test/tests/basic/testBug692274-1.js new file mode 100644 index 000000000000..953a1cbda8aa --- /dev/null +++ b/js/src/jit-test/tests/basic/testBug692274-1.js @@ -0,0 +1,6 @@ +// |jit-test| debug; error: TypeError +function f() { + ""(this.z) +} +trap(f, 0, '') +f() diff --git a/js/src/jit-test/tests/basic/testBug692274-2.js b/js/src/jit-test/tests/basic/testBug692274-2.js new file mode 100644 index 000000000000..dff5c905236b --- /dev/null +++ b/js/src/jit-test/tests/basic/testBug692274-2.js @@ -0,0 +1,7 @@ +function f() { + var ss = [new f("abc"), new String("foobar"), new String("quux")]; + for (let a6 = this ;; ) {} +} +try { + f(); +} catch (e) {} diff --git a/js/src/jit-test/tests/basic/testBug692274-3.js b/js/src/jit-test/tests/basic/testBug692274-3.js new file mode 100644 index 000000000000..a8c0afe65f03 --- /dev/null +++ b/js/src/jit-test/tests/basic/testBug692274-3.js @@ -0,0 +1,16 @@ +var x = -false; +switch(x) { + case 11: + let y = 42; +} +switch(x) { + case 11: + let y = 42; + let z = 'ponies'; +} +switch(x) { + case 11: + let y = 42; + let z = 'ponies'; + let a = false; +} diff --git a/js/src/jit-test/tests/basic/testBug692274-4.js b/js/src/jit-test/tests/basic/testBug692274-4.js new file mode 100644 index 000000000000..8c02cdc11380 --- /dev/null +++ b/js/src/jit-test/tests/basic/testBug692274-4.js @@ -0,0 +1,4 @@ +// |jit-test| error: TypeError +var obj = {}; +let ([] = print) 3; +let ( i = "a" ) new i [ obj[i] ]; diff --git a/js/src/jit-test/tests/basic/testBug703857.js b/js/src/jit-test/tests/basic/testBug703857.js new file mode 100644 index 000000000000..0f230e461ab6 --- /dev/null +++ b/js/src/jit-test/tests/basic/testBug703857.js @@ -0,0 +1,12 @@ +Function.prototype.X = 42; +function ownProperties() { + var props = {}; + var r = function () {}; + for (var a in r) { + let (a = function() { for (var r=0;r<6;++r) ++a; }) { + a(); + } + props[a] = true; + } +} +ownProperties(); diff --git a/js/src/jit-test/tests/basic/testBug709633.js b/js/src/jit-test/tests/basic/testBug709633.js new file mode 100644 index 000000000000..1ce097b85d46 --- /dev/null +++ b/js/src/jit-test/tests/basic/testBug709633.js @@ -0,0 +1,9 @@ +test(); +function test() { + var f; + f = function() { (let(x) {y: z}) } + let (f = function() { + for (var t=0;t<6;++t) ++f; + }) { f(); } // { } + actual = f + ''; +} diff --git a/js/src/jit-test/tests/basic/testLet.js b/js/src/jit-test/tests/basic/testLet.js new file mode 100644 index 000000000000..eb9860e7cccd --- /dev/null +++ b/js/src/jit-test/tests/basic/testLet.js @@ -0,0 +1,342 @@ +function test(str, arg, result) +{ + arg = arg || 'ponies'; + result = result || 'ponies'; + + var fun = new Function('x', str); + + var got = fun.toSource().replace(/\n/g,''); + var expect = '(function anonymous(x) {' + str + '})'; + if (got !== expect) { + print("GOT: " + got); + print("EXPECT: " + expect); + assertEq(got, expect); + } + + Reflect.parse(got); + + var got = fun(arg); + var expect = result; + if (got !== expect) { + print("GOT:" + got); + print("EXPECT: " + expect); + assertEq(got, expect); + } +} + +function isError(str) +{ + var caught = false; + try { + new Function(str); + } catch(e) { + assertEq(String(e).indexOf('TypeError') == 0 || String(e).indexOf('SyntaxError') == 0, true); + caught = true; + } + assertEq(caught, true); +} + +// let expr +test('return let (y) x;'); +test('return let (x) "" + x;', 'unicorns', 'undefined'); +test('return let (y = x) (y++, "" + y);', 'unicorns', 'NaN'); +test('return let (y = 1) (y = x, y);'); +test('return let ([] = x) x;'); +test('return let (x = {a: x}) x.a;'); +test('return let ({a: x} = {a: x}) x;'); +test('return let ([x] = {0: x}) x;'); +test('return let ({0: x} = [x]) x;'); +test('return let ({0: []} = []) x;'); +test('return let ([, ] = x) x;'); +test('return let ([, , , , ] = x) x;'); +test('return let ([[]] = x) x;'); +test('return let ([[[[[[[[[[[[[]]]]]]]]]]]]] = x) x;'); +test('return let ([[], []] = x) x;'); +test('return let ([[[[]]], [], , [], [[]]] = x) x;'); +test('return let ({x: []} = x) x;'); +test('return let ({x: [], y: {x: []}} = x) "ponies";', {y:{}}); +test('return let ({x: []} = x, [{x: []}] = x) "ponies";'); +test('return let (x = x) x;'); +test('return let (x = eval("x")) x;'); +test('return let (x = (let (x = x + 1) x) + 1) x;', 1, 3); +test('return let (x = (let (x = eval("x") + 1) eval("x")) + 1) eval("x");', 1, 3); +test('return let (x = x + 1, y = x) y;'); +test('return let (x = x + 1, [] = x, [[, , ]] = x, y = x) y;'); +test('return let ([{a: x}] = x, [, {b: y}] = x) let (x = x + 1, y = y + 2) x + y;', [{a:"p"},{b:"p"}], "p1p2"); +test('return let ([] = []) x;'); +test('return let ([] = [x]) x;'); +test('return let ([x] = [x]) x;'); +test('return let ([[a, [b, c]]] = [[x, []]]) a;'); +test('return let ([x, y] = [x, x + 1]) x + y;', 1, 3); +test('return let ([x, y, z] = [x, x + 1, x + 2]) x + y + z;', 1, 6); +test('return let ([[x]] = [[x]]) x;'); +test('return let ([x, y] = [x, x + 1]) x;'); +test('return let ([x, [y, z]] = [x, x + 1]) x;'); +test('return let ([{x: [x]}, {y1: y, z1: z}] = [x, x + 1]) x;',{x:['ponies']}); +test('return let (x = (3, x)) x;'); +test('return let (x = x + "s") x;', 'ponie'); +test('return let ([x] = (3, [x])) x;'); +test('return let ([] = [[]] = {}) x;'); +test('return let (y = x) function () {return eval("y");}();'); +test('return eval("let (y = x) y");'); +test('return let (y = x) (eval("var y = 2"), y);', 'ponies', 2); +test('"use strict";return let (y = x) (eval("var y = 2"), y);'); +test('this.y = x;return let (y = 1) this.eval("y");'); +test('try {let (x = x) eval("throw x");} catch (e) {return e;}'); +test('try {return let (x = eval("throw x")) x;} catch (e) {return e;}'); +isError('let (x = 1, x = 2) x'); +isError('let ([x, y] = a, {a:x} = b) x'); +isError('let ([x, y, x] = a) x'); +isError('let ([x, [y, [x]]] = a) x'); +isError('let (x = function() { return x}) x()return x;'); +isError('(let (x = function() { return x}) x())return x;'); + +// let block +test('let (y) {return x;}'); +test('let (y = x) {y++;return "" + y;}', 'unicorns', 'NaN'); +test('let (y = 1) {y = x;return y;}'); +test('let (x) {return "" + x;}', 'unicorns', 'undefined'); +test('let ([] = x) {return x;}'); +test('let (x) {}return x;'); +test('let (x = {a: x}) {return x.a;}'); +test('let ({a: x} = {a: x}) {return x;}'); +test('let ([x] = {0: x}) {return x;}'); +test('let ({0: x} = [x]) {return x;}'); +test('let ({0: []} = []) {return x;}'); +test('let ([, ] = x) {return x;}'); +test('let ([, , , , ] = x) {return x;}'); +test('let ([[]] = x) {return x;}'); +test('let ([[[[[[[[[[[[[]]]]]]]]]]]]] = x) {return x;}'); +test('let ([[], []] = x) {return x;}'); +test('let ([[[[]]], [], , [], [[]]] = x) {return x;}'); +test('let ({x: []} = x) {return x;}'); +test('let ({x: [], y: {x: []}} = x) {return "ponies";}', {y:{}}); +test('let ({x: []} = x, [{x: []}] = x) {return "ponies";}'); +test('let (x = x) {return x;}'); +test('let (x = eval("x")) {return x;}'); +test('let (x = (let (x = x + 1) x) + 1) {return x;}', 1, 3); +test('let (x = (let (x = eval("x") + 1) eval("x")) + 1) {return eval("x");}', 1, 3); +test('let (x = x + 1, y = x) {return y;}'); +test('let (x = x + 1, [] = x, [[, , ]] = x, y = x) {return y;}'); +test('let ([{a: x}] = x, [, {b: y}] = x) {let (x = x + 1, y = y + 2) {return x + y;}}', [{a:"p"},{b:"p"}], "p1p2"); +test('let ([] = []) {return x;}'); +test('let ([] = [x]) {return x;}'); +test('let ([x] = [x]) {return x;}'); +test('let ([[a, [b, c]]] = [[x, []]]) {return a;}'); +test('let ([x, y] = [x, x + 1]) {return x + y;}', 1, 3); +test('let ([x, y, z] = [x, x + 1, x + 2]) {return x + y + z;}', 1, 6); +test('let ([[x]] = [[x]]) {return x;}'); +test('let ([x, y] = [x, x + 1]) {return x;}'); +test('let ([x, [y, z]] = [x, x + 1]) {return x;}'); +test('let ([{x: [x]}, {y1: y, z1: z}] = [x, x + 1]) {return x;}',{x:['ponies']}); +test('let (y = x[1]) {let (x = x[0]) {try {let (y = "unicorns") {throw y;}} catch (e) {return x + y;}}}', ['pon','ies']); +test('let (x = x) {try {let (x = "unicorns") eval("throw x");} catch (e) {return x;}}'); +test('let ([] = [[]] = {}) {return x;}'); +test('let (y = x) {return function () {return eval("y");}();}'); +test('return eval("let (y = x) {y;}");'); +test('let (y = x) {eval("var y = 2");return y;}', 'ponies', 2); +test('"use strict";let (y = x) {eval("var y = 2");return y;}'); +test('this.y = x;let (y = 1) {return this.eval("y");}'); +isError('let (x = 1, x = 2) {x}'); +isError('let ([x, y] = a, {a:x} = b) {x}'); +isError('let ([x, y, x] = a) {x}'); +isError('let ([x, [y, [x]]] = a) {x}'); + +// var declarations +test('var y;return x;'); +test('var y = x;return x;'); +test('var [] = x;return x;'); +test('var [, ] = x;return x;'); +test('var [, , , , ] = x;return x;'); +test('var [[]] = x;return x;'); +test('var [[[[[[[[[[[[[]]]]]]]]]]]]] = x;return x;'); +test('var [[], []] = x;return x;'); +test('var [[[[]]], [], , [], [[]]] = x;return x;'); +test('var {x: []} = x;return x;'); +test('var {x: [], y: {x: []}} = x;return "ponies";', {y:{}}); +test('var {x: []} = x, [{x: []}] = x;return "ponies";'); +test('var x = x;return x;'); +test('var y = y;return "" + y;', 'unicorns', 'undefined'); +test('var x = eval("x");return x;'); +test('var x = (let (x = x + 1) x) + 1;return x;', 1, 3); +test('var x = (let (x = eval("x") + 1) eval("x")) + 1;return eval("x");', 1, 3); +test('var X = x + 1, y = x;return y;'); +test('var X = x + 1, [] = X, [[, , ]] = X, y = x;return y;'); +test('var [{a: X}] = x, [, {b: y}] = x;var X = X + 1, y = y + 2;return X + y;', [{a:"p"},{b:"p"}], "p1p2"); +test('var [x] = [x];return x;'); +test('var [[a, [b, c]]] = [[x, []]];return a;'); +test('var [y] = [x];return y;'); +test('var [x, y] = [x, x + 1];return x + y;', 1, 3); +test('var [x, y, z] = [x, x + 1, x + 2];return x + y + z;', 1, 6); +test('var [[x]] = [[x]];return x;'); +test('var [x, y] = [x, x + 1];return x;'); +test('var [x, [y, z]] = [x, x + 1];return x;'); +test('var [{x: [x]}, {y1: y, z1: z}] = [x, x + 1];return x;',{x:['ponies']}); +test('var [] = [[]] = {};return x;'); +test('if (x) {var y = x;return x;}'); +test('if (x) {y = x;var y = y;return y;}'); +test('if (x) {var z = y;var [y] = x;z += y;}return z;', ['-'], 'undefined-'); + +// let declaration in context +test('if (x) {let y;return x;}'); +test('if (x) {let x;return "" + x;}', 'unicorns', 'undefined'); +test('if (x) {let y = x;return x;}'); +test('if (x) {y = x;let y = y;return y;}'); +test('if (x) {var z = y;let [y] = x;z += y;}return z;', ['-'], 'undefined-'); +test('if (x) {let y = x;return x;}'); +test('if (x) {let [] = x;return x;}'); +test('if (x) {let [, ] = x;return x;}'); +test('if (x) {let [, , , , ] = x;return x;}'); +test('if (x) {let [[]] = x;return x;}'); +test('if (x) {let [[[[[[[[[[[[[]]]]]]]]]]]]] = x;return x;}'); +test('if (x) {let [[], []] = x;return x;}'); +test('if (x) {let [[[[]]], [], , [], [[]]] = x;return x;}'); +test('if (x) {let {x: []} = x;return x;}'); +test('if (x) {let {x: [], y: {x: []}} = x;return "ponies";}', {y:{}}); +test('if (x) {let {x: []} = x, [{x: []}] = x;return "ponies";}'); +test('if (x) {let x = x;return "" + x;}', 'unicorns', 'undefined'); +test('if (x) {let y = y;return "" + y;}', 'unicorns', 'undefined'); +test('if (x) {let x = eval("x");return "" + x;}', 'unicorns', 'undefined'); +test('if (x) {let y = (let (x = x + 1) x) + 1;return y;}', 1, 3); +test('if (x) {let y = (let (x = eval("x") + 1) eval("x")) + 1;return eval("y");}', 1, 3); +test('if (x) {let X = x + 1, y = x;return y;}'); +test('if (x) {let X = x + 1, [] = X, [[, , ]] = X, y = x;return y;}'); +test('if (x) {let [{a: X}] = x, [, {b: Y}] = x;var XX = X + 1, YY = Y + 2;return XX + YY;}', [{a:"p"},{b:"p"}], "p1p2"); +test('if (x) {let [[a, [b, c]]] = [[x, []]];return a;}'); +test('if (x) {let [X] = [x];return X;}'); +test('if (x) {let [y] = [x];return y;}'); +test('if (x) {let [X, y] = [x, x + 1];return X + y;}', 1, 3); +test('if (x) {let [X, y, z] = [x, x + 1, x + 2];return X + y + z;}', 1, 6); +test('if (x) {let [[X]] = [[x]];return X;}'); +test('if (x) {let [X, y] = [x, x + 1];return X;}'); +test('if (x) {let [X, [y, z]] = [x, x + 1];return X;}'); +test('if (x) {let [{x: [X]}, {y1: y, z1: z}] = [x, x + 1];return X;}',{x:['ponies']}); +test('if (x) {let y = x;try {let x = 1;throw 2;} catch (e) {return y;}}'); +test('if (x) {let [] = [[]] = {};return x;}'); +test('let (y, [] = x) {}try {let a = b(), b;} catch (e) {return x;}'); +test('try {let x = 1;throw 2;} catch (e) {return x;}'); +test('let (y = x) {let x;return y;}'); +test('let (y = x) {let x = y;return x;}'); +test('let ([y, z] = x) {let a = x, b = y;return a;}'); +test('let ([y, z] = x, a = x, [] = x) {let b = x, c = y;return a;}'); +test('function f() {return unicorns;}try {let (x = 1) {let a, b;f();}} catch (e) {return x;}'); +test('function f() {return unicorns;}try {let (x = 1) {let a, b;}f();} catch (e) {return x;}'); +test('x.foo;{let y = x;return y;}'); +test('x.foo;if (x) {x.bar;let y = x;return y;}'); +test('if (x) {let y = x;return function () {return eval("y");}();}'); +test('return eval("let y = x; y");'); +test('if (x) {let y = x;eval("var y = 2");return y;}', 'ponies', 2); +test('"use strict";if (x) {let y = x;eval("var y = 2");return y;}'); +test('"use strict";if (x) {let y = x;eval("let y = 2");return y;}'); +test('"use strict";if (x) {let y = 1;return eval("let y = x;y;");}'); +test('this.y = x;if (x) {let y = 1;return this.eval("y");}'); +isError('if (x) {let (x = 1, x = 2) {x}}'); +isError('if (x) {let ([x, y] = a, {a:x} = b) {x}}'); +isError('if (x) {let ([x, y, x] = a) {x}}'); +isError('if (x) {let ([x, [y, [x]]] = a) {x}}'); +isError('let ([x, y] = x) {let x;}'); + +// for(;;) +test('for (;;) {return x;}'); +test('for (let y = 1;;) {return x;}'); +test('for (let y = 1;; ++y) {return x;}'); +test('for (let y = 1; ++y;) {return x;}'); +test('for (let (x = 1) x; x != 1; ++x) {return x;}'); +test('for (let [, {a: [], b: []}] = x, [] = x; x;) {return x;}'); +test('for (let x = 1, [y, z] = x, a = x; z < 4; ++z) {return x + y;}', [2,3], 3); +test('for (let (x = 1, [{a: b, c: d}] = [{a: 1, c: 2}]) x; x != 1; ++x) {return x;}'); +test('for (let [[a, [b, c]]] = [[x, []]];;) {return a;}'); +test('var sum = 0;for (let y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6); +test('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;', 1, 6); +test('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;', 1, 1); +test('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;', 1, 6); +test('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); +test('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); +test('for (var y = 1;;) {return x;}'); +test('for (var y = 1;; ++y) {return x;}'); +test('for (var y = 1; ++y;) {return x;}'); +test('for (var [, {a: [], b: []}] = x, [] = x; x;) {return x;}'); +test('for (var X = 1, [y, z] = x, a = x; z < 4; ++z) {return X + y;}', [2,3], 3); +test('var sum = 0;for (var y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6); +test('var sum = 0;for (var X = x, y = 10; X < 4; ++X) {sum += X;}return sum;', 1, 6); +test('var sum = 0;for (var X = x; X < 4; ++X) {sum += X;}return x;', 1, 1); +test('var sum = 0;for (var X = eval("x"); X < 4; ++X) {sum += X;}return sum;', 1, 6); +test('var sum = 0;for (var X = x; eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6); +test('var sum = 0;for (var X = eval("x"); eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6); +test('try {for (let x = eval("throw x");;) {}} catch (e) {return e;}'); +test('try {for (let x = x + "s"; eval("throw x");) {}} catch (e) {return e;}', 'ponie'); +test('for (let y = x;;) {let x;return y;}'); +test('for (let y = x;;) {let y;return x;}'); +test('for (let y;;) {let y;return x;}'); +test('for (let a = x;;) {let c = x, d = x;return c;}'); +test('for (let [a, b] = x;;) {let c = x, d = x;return c;}'); +test('for (let [] = [[]] = {};;) {return x;}'); +isError('for (let x = 1, x = 2;;) {}'); +isError('for (let [x, y] = a, {a:x} = b;;) {}'); +isError('for (let [x, y, x] = a;;) {}'); +isError('for (let [x, [y, [x]]] = a;;) {}'); + +// for(in) +test('for (let i in x) {return x;}'); +test('for (let i in x) {let y;return x;}'); +test('for each (let [a, b] in x) {let y;return x;}'); +test('for (let i in x) {let (i = x) {return i;}}'); +test('for (let i in x) {let i = x;return i;}'); +test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']]); +test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']]); +test('var s = "";for (let a in x) {for (let b in x) {s += a + b;}}return s;', [1,2], '00011011'); +test('var res = "";for (let i in x) {res += x[i];}return res;'); +test('var res = "";for (var i in x) {res += x[i];}return res;'); +test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}'); +test('for (let x in eval("x")) {return x;}', {ponies:true}); +test('for (let x in x) {return eval("x");}', {ponies:true}); +test('for (let x in eval("x")) {return eval("x");}', {ponies:true}); +test('for ((let (x = {y: true}) x).y in eval("x")) {return eval("x");}'); +test('for (let i in x) {break;}return x;'); +test('for (let i in x) {break;}return eval("x");'); +test('for (let x in x) {break;}return x;'); +test('for (let x in x) {break;}return eval("x");'); +test('a:for (let i in x) {for (let j in x) {break a;}}return x;'); +test('a:for (let i in x) {for (let j in x) {break a;}}return eval("x");'); +test('var j;for (let i in x) {j = i;break;}return j;', {ponies:true}); +test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}'); +test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies']); +isError('for (let [x, x] in o) {}'); +isError('for (let [x, y, x] in o) {}'); +isError('for (let [x, [y, [x]]] in o) {}'); + +// genexps +test('return (i for (i in x)).next();', {ponies:true}); +test('return (eval("i") for (i in x)).next();', {ponies:true}); +test('return (eval("i") for (i in eval("x"))).next();', {ponies:true}); +test('try {return (eval("throw i") for (i in x)).next();} catch (e) {return e;}', {ponies:true}); + +// array comprehension +test('return [i for (i in x)][0];', {ponies:true}); +test('return [eval("i") for (i in x)][0];', {ponies:true}); +test('return [eval("i") for (i in eval("x"))][0];', {ponies:true}); +test('try {return [eval("throw i") for (i in x)][0];} catch (e) {return e;}', {ponies:true}); + +// don't forget about switch craziness +test('var y = 3;switch (function () {return eval("y");}()) {case 3:let y;return x;default:;}'); +test('switch (x) {case 3:let y;return 3;case 4:let z;return 4;default:return x;}'); +test('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}'); +test('switch (x) {case 3:default:let y;let (y = x) {return y;}}'); +isError('switch (x) {case 3:let y;return 3;case 4:let y;return 4;default:;}'); + +// test weird cases where the decompiler changes tokens +function testWeird(str, printedAs, arg, result) +{ + var fun = new Function('x', str); + + // this is lame and doesn't normalize whitespace so if an assert fails + // here, see if its just whitespace and fix the caller + assertEq(fun.toSource(), '(function anonymous(x) {' + printedAs + '})'); + + test(printedAs, arg, result); +} + +testWeird('let y = x;return x;', 'var y = x;return x;'); +testWeird('let y = 1, y = x;return y;', 'var y = 1, y = x;return y;'); +testWeird('return let ({x:x, y:y} = x) x + y', 'return let ({x, y} = x) x + y;', {x:'pon', y:'ies'}); +testWeird('let ({x:x, y:y} = x) {return x + y;}', 'let ({x, y} = x) {return x + y;}', {x:'pon', y:'ies'}); diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index 4c77bfa86124..7f47bcc3ecb4 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -119,11 +119,11 @@ ScriptAnalysis::checkAliasedName(JSContext *cx, jsbytecode *pc) JSAtom *atom; if (JSOp(*pc) == JSOP_DEFFUN) { - JSFunction *fun = script->getFunction(js_GetIndexFromBytecode(cx, script, pc, 0)); + JSFunction *fun = script->getFunction(js_GetIndexFromBytecode(script, pc, 0)); atom = fun->atom; } else { JS_ASSERT(JOF_TYPE(js_CodeSpec[*pc].format) == JOF_ATOM); - atom = script->getAtom(js_GetIndexFromBytecode(cx, script, pc, 0)); + atom = script->getAtom(js_GetIndexFromBytecode(script, pc, 0)); } uintN index; @@ -389,6 +389,8 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) isInlineable = canTrackVars = false; break; + case JSOP_ENTERLET0: + case JSOP_ENTERLET1: case JSOP_ENTERBLOCK: case JSOP_LEAVEBLOCK: addsScopeObjects_ = true; diff --git a/js/src/jsanalyze.h b/js/src/jsanalyze.h index d1d24405da0c..021ebe0235f3 100644 --- a/js/src/jsanalyze.h +++ b/js/src/jsanalyze.h @@ -203,9 +203,6 @@ GetDefCount(JSScript *script, unsigned offset) JS_ASSERT(offset < script->length); jsbytecode *pc = script->code + offset; - if (js_CodeSpec[*pc].ndefs == -1) - return js_GetEnterBlockStackDefs(NULL, script, pc); - /* * Add an extra pushed value for OR/AND opcodes, so that they are included * in the pushed array of stack values for type inference. @@ -227,7 +224,7 @@ GetDefCount(JSScript *script, unsigned offset) */ return (pc[1] + 1); default: - return js_CodeSpec[*pc].ndefs; + return StackDefs(script, pc); } } @@ -240,7 +237,7 @@ GetUseCount(JSScript *script, unsigned offset) if (JSOp(*pc) == JSOP_PICK) return (pc[1] + 1); if (js_CodeSpec[*pc].nuses == -1) - return js_GetVariableStackUses(JSOp(*pc), pc); + return StackUses(script, pc); return js_CodeSpec[*pc].nuses; } diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 06ce15384420..cdcde6c2496b 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -2032,21 +2032,21 @@ TypeCompartment::newAllocationSiteTypeObject(JSContext *cx, const AllocationSite static inline jsid GetAtomId(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset) { - unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, offset); + unsigned index = js_GetIndexFromBytecode(script, (jsbytecode*) pc, offset); return MakeTypeId(cx, ATOM_TO_JSID(script->getAtom(index))); } static inline JSObject * GetScriptObject(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset) { - unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, offset); + unsigned index = js_GetIndexFromBytecode(script, (jsbytecode*) pc, offset); return script->getObject(index); } static inline const Value & GetScriptConst(JSContext *cx, JSScript *script, const jsbytecode *pc) { - unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, 0); + unsigned index = js_GetIndexFromBytecode(script, (jsbytecode*) pc, 0); return script->getConst(index); } @@ -3956,6 +3956,7 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, case JSOP_ENTERWITH: case JSOP_ENTERBLOCK: + case JSOP_ENTERLET0: /* * Scope lookups can occur on the values being pushed here. We don't track * the value or its properties, and just monitor all name opcodes in the @@ -3963,6 +3964,16 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, */ break; + case JSOP_ENTERLET1: + /* + * JSOP_ENTERLET1 enters a let block with an unrelated value on top of + * the stack (such as the condition to a switch) whose constraints must + * be propagated. The other values are ignored for the same reason as + * JSOP_ENTERLET0. + */ + poppedTypes(pc, 0)->addSubset(cx, &pushed[defCount - 1]); + break; + case JSOP_ITER: { /* * Use a per-script type set to unify the possible target types of all @@ -4021,6 +4032,9 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, poppedTypes(pc, 0)->addSubset(cx, &pushed[0]); break; + case JSOP_LEAVEFORLETIN: + break; + case JSOP_CASE: case JSOP_CASEX: poppedTypes(pc, 1)->addSubset(cx, &pushed[0]); @@ -4554,7 +4568,7 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun, JSO * integer properties and bail out. We can't mark the aggregate * JSID_VOID type property as being in a definite slot. */ - unsigned index = js_GetIndexFromBytecode(cx, script, pc, 0); + unsigned index = js_GetIndexFromBytecode(script, pc, 0); jsid id = ATOM_TO_JSID(script->getAtom(index)); if (MakeTypeId(cx, id) != id) return false; @@ -5421,6 +5435,8 @@ IgnorePushed(const jsbytecode *pc, unsigned index) /* Storage for 'with' and 'let' blocks not monitored. */ case JSOP_ENTERWITH: case JSOP_ENTERBLOCK: + case JSOP_ENTERLET0: + case JSOP_ENTERLET1: return true; /* We don't keep track of the iteration state for 'for in' or 'for each in' loops. */ diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index a1565be9c067..eefd68b0cfb8 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -1857,9 +1857,6 @@ js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode) /* No-ops for ease of decompilation. */ ADD_EMPTY_CASE(JSOP_NOP) ADD_EMPTY_CASE(JSOP_UNUSED0) -ADD_EMPTY_CASE(JSOP_UNUSED1) -ADD_EMPTY_CASE(JSOP_UNUSED2) -ADD_EMPTY_CASE(JSOP_UNUSED3) ADD_EMPTY_CASE(JSOP_CONDSWITCH) ADD_EMPTY_CASE(JSOP_TRY) #if JS_HAS_XML_SUPPORT @@ -5109,16 +5106,28 @@ END_CASE(JSOP_GETFUNNS) #endif /* JS_HAS_XML_SUPPORT */ BEGIN_CASE(JSOP_ENTERBLOCK) +BEGIN_CASE(JSOP_ENTERLET0) +BEGIN_CASE(JSOP_ENTERLET1) { JSObject *obj; LOAD_OBJECT(0, obj); JS_ASSERT(obj->isStaticBlock()); - JS_ASSERT(regs.fp()->base() + OBJ_BLOCK_DEPTH(cx, obj) == regs.sp); - Value *vp = regs.sp + OBJ_BLOCK_COUNT(cx, obj); - JS_ASSERT(regs.sp < vp); - JS_ASSERT(vp <= regs.fp()->slots() + script->nslots); - SetValueRangeToUndefined(regs.sp, vp); - regs.sp = vp; + JS_ASSERT(regs.fp()->maybeBlockChain() == obj->staticBlockScopeChain()); + + if (op == JSOP_ENTERBLOCK) { + JS_ASSERT(regs.fp()->base() + OBJ_BLOCK_DEPTH(cx, obj) == regs.sp); + Value *vp = regs.sp + OBJ_BLOCK_COUNT(cx, obj); + JS_ASSERT(regs.sp < vp); + JS_ASSERT(vp <= regs.fp()->slots() + script->nslots); + SetValueRangeToUndefined(regs.sp, vp); + regs.sp = vp; + } else if (op == JSOP_ENTERLET0) { + JS_ASSERT(regs.fp()->base() + OBJ_BLOCK_DEPTH(cx, obj) + OBJ_BLOCK_COUNT(cx, obj) + == regs.sp); + } else if (op == JSOP_ENTERLET1) { + JS_ASSERT(regs.fp()->base() + OBJ_BLOCK_DEPTH(cx, obj) + OBJ_BLOCK_COUNT(cx, obj) + == regs.sp - 1); + } #ifdef DEBUG JS_ASSERT(regs.fp()->maybeBlockChain() == obj->staticBlockScopeChain()); @@ -5134,7 +5143,8 @@ BEGIN_CASE(JSOP_ENTERBLOCK) while (obj2->isWith()) obj2 = obj2->internalScopeChain(); if (obj2->isBlock() && - obj2->getPrivate() == js_FloatingFrameIfGenerator(cx, regs.fp())) { + obj2->getPrivate() == js_FloatingFrameIfGenerator(cx, regs.fp())) + { JSObject *youngestProto = obj2->getProto(); JS_ASSERT(youngestProto->isStaticBlock()); JSObject *parent = obj; @@ -5147,14 +5157,14 @@ BEGIN_CASE(JSOP_ENTERBLOCK) } END_CASE(JSOP_ENTERBLOCK) -BEGIN_CASE(JSOP_LEAVEBLOCKEXPR) BEGIN_CASE(JSOP_LEAVEBLOCK) +BEGIN_CASE(JSOP_LEAVEFORLETIN) +BEGIN_CASE(JSOP_LEAVEBLOCKEXPR) { -#ifdef DEBUG JS_ASSERT(regs.fp()->blockChain().isStaticBlock()); - uintN blockDepth = OBJ_BLOCK_DEPTH(cx, ®s.fp()->blockChain()); + DebugOnly blockDepth = OBJ_BLOCK_DEPTH(cx, ®s.fp()->blockChain()); JS_ASSERT(blockDepth <= StackDepth(script)); -#endif + /* * If we're about to leave the dynamic scope of a block that has been * cloned onto fp->scopeChain, clear its private data, move its locals from @@ -5167,19 +5177,22 @@ BEGIN_CASE(JSOP_LEAVEBLOCK) goto error; } - /* Pop the block chain, too. */ regs.fp()->setBlockChain(regs.fp()->blockChain().staticBlockScopeChain()); - /* Move the result of the expression to the new topmost stack slot. */ - Value *vp = NULL; /* silence GCC warnings */ - if (op == JSOP_LEAVEBLOCKEXPR) - vp = ®s.sp[-1]; - regs.sp -= GET_UINT16(regs.pc); - if (op == JSOP_LEAVEBLOCKEXPR) { + if (op == JSOP_LEAVEBLOCK) { + /* Pop the block's slots. */ + regs.sp -= GET_UINT16(regs.pc); + JS_ASSERT(regs.fp()->base() + blockDepth == regs.sp); + } else if (op == JSOP_LEAVEBLOCKEXPR) { + /* Pop the block's slots maintaining the topmost expr. */ + Value *vp = ®s.sp[-1]; + regs.sp -= GET_UINT16(regs.pc); JS_ASSERT(regs.fp()->base() + blockDepth == regs.sp - 1); regs.sp[-1] = *vp; } else { - JS_ASSERT(regs.fp()->base() + blockDepth == regs.sp); + /* Another op will pop; nothing to do here. */ + len = JSOP_LEAVEFORLETIN_LENGTH; + DO_NEXT_OP(len); } } END_CASE(JSOP_LEAVEBLOCK) diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 11837e87669a..708f2616bf1d 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3678,20 +3678,28 @@ block_setProperty(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *v } const Shape * -JSObject::defineBlockVariable(JSContext *cx, jsid id, intN index) +JSObject::defineBlockVariable(JSContext *cx, jsid id, intN index, bool *redeclared) { JS_ASSERT(isStaticBlock()); + *redeclared = false; + + /* Inline JSObject::addProperty in order to trap the redefinition case. */ + Shape **spp = nativeSearch(cx, id, true); + if (SHAPE_FETCH(spp)) { + *redeclared = true; + return NULL; + } + /* - * Use JSPROP_ENUMERATE to aid the disassembler, and don't convert this - * object to dictionary mode so that we can clone the block's shape later. + * Don't convert this object to dictionary mode so that we can clone the + * block's shape later. */ uint32_t slot = JSSLOT_FREE(&BlockClass) + index; - const Shape *shape = addProperty(cx, id, - block_getProperty, block_setProperty, - slot, JSPROP_ENUMERATE | JSPROP_PERMANENT, - Shape::HAS_SHORTID, index, - /* allowDictionary = */ false); + const Shape *shape = addPropertyInternal(cx, id, block_getProperty, block_setProperty, + slot, JSPROP_ENUMERATE | JSPROP_PERMANENT, + Shape::HAS_SHORTID, index, spp, + /* allowDictionary = */ false); if (!shape) return NULL; return shape; @@ -4191,8 +4199,11 @@ js_XDRBlockObject(JSXDRState *xdr, JSObject **objp) if (!js_XDRAtom(xdr, &atom)) return false; - if (!obj->defineBlockVariable(cx, ATOM_TO_JSID(atom), i)) + bool redeclared; + if (!obj->defineBlockVariable(cx, ATOM_TO_JSID(atom), i, &redeclared)) { + JS_ASSERT(!redeclared); return false; + } } } else { AutoShapeVector shapes(cx); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 55ab602c490b..dcd9398a7f2d 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1341,7 +1341,7 @@ struct JSObject : js::gc::Cell bool swap(JSContext *cx, JSObject *other); - const js::Shape *defineBlockVariable(JSContext *cx, jsid id, intN index); + const js::Shape *defineBlockVariable(JSContext *cx, jsid id, intN index, bool *redeclared); inline bool isArguments() const; inline bool isArrayBuffer() const; diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 03ec9628ed0b..0bda5f52dd40 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -157,8 +157,7 @@ GetJumpOffset(jsbytecode *pc, jsbytecode *pc2) } uintN -js_GetIndexFromBytecode(JSContext *cx, JSScript *script, jsbytecode *pc, - ptrdiff_t pcoff) +js_GetIndexFromBytecode(JSScript *script, jsbytecode *pc, ptrdiff_t pcoff) { JSOp op = JSOp(*pc); JS_ASSERT(js_CodeSpec[op].length >= 1 + pcoff + UINT16_LEN); @@ -220,10 +219,26 @@ js_GetVariableBytecodeLength(jsbytecode *pc) } } -uintN -js_GetVariableStackUses(JSOp op, jsbytecode *pc) +static uint32_t +NumBlockSlots(JSScript *script, jsbytecode *pc) { - JS_ASSERT(*pc == op); + JS_ASSERT(*pc == JSOP_ENTERBLOCK || *pc == JSOP_ENTERLET0 || *pc == JSOP_ENTERLET1); + JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET0_LENGTH); + JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET1_LENGTH); + + JSObject *obj = NULL; + GET_OBJECT_FROM_BYTECODE(script, pc, 0, obj); + return OBJ_BLOCK_COUNT(NULL, obj); +} + +uintN +js::StackUses(JSScript *script, jsbytecode *pc) +{ + JSOp op = (JSOp) *pc; + const JSCodeSpec &cs = js_CodeSpec[op]; + if (cs.nuses >= 0) + return cs.nuses; + JS_ASSERT(js_CodeSpec[op].nuses == -1); switch (op) { case JSOP_POPN: @@ -232,6 +247,10 @@ js_GetVariableStackUses(JSOp op, jsbytecode *pc) return GET_UINT16(pc); case JSOP_LEAVEBLOCKEXPR: return GET_UINT16(pc) + 1; + case JSOP_ENTERLET0: + return NumBlockSlots(script, pc); + case JSOP_ENTERLET1: + return NumBlockSlots(script, pc) + 1; default: /* stack: fun, this, [argc arguments] */ JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL || @@ -241,13 +260,15 @@ js_GetVariableStackUses(JSOp op, jsbytecode *pc) } uintN -js_GetEnterBlockStackDefs(JSContext *cx, JSScript *script, jsbytecode *pc) +js::StackDefs(JSScript *script, jsbytecode *pc) { - JSObject *obj; + JSOp op = (JSOp) *pc; + const JSCodeSpec &cs = js_CodeSpec[op]; + if (cs.ndefs >= 0) + return cs.ndefs; - JS_ASSERT(*pc == JSOP_ENTERBLOCK); - GET_OBJECT_FROM_BYTECODE(script, pc, 0, obj); - return OBJ_BLOCK_COUNT(cx, obj); + uint32_t n = NumBlockSlots(script, pc); + return op == JSOP_ENTERLET1 ? n + 1 : n; } static const char * countBaseNames[] = { @@ -483,8 +504,12 @@ ToDisassemblySource(JSContext *cx, jsval v, JSAutoByteString *bytes) Shape::Range r = obj->lastProperty()->all(); while (!r.empty()) { const Shape &shape = r.front(); + JSAtom *atom = JSID_IS_INT(shape.propid()) + ? cx->runtime->atomState.emptyAtom + : JSID_TO_ATOM(shape.propid()); + JSAutoByteString bytes; - if (!js_AtomToPrintableString(cx, JSID_TO_ATOM(shape.propid()), &bytes)) + if (!js_AtomToPrintableString(cx, atom, &bytes)) return false; r.popFront(); @@ -572,7 +597,7 @@ js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc, case JOF_ATOM: case JOF_OBJECT: case JOF_REGEXP: { - uintN index = js_GetIndexFromBytecode(cx, script, pc, 0); + uintN index = js_GetIndexFromBytecode(script, pc, 0); jsval v; if (type == JOF_ATOM) { if (op == JSOP_DOUBLE) { @@ -668,7 +693,7 @@ js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc, case JOF_SLOTATOM: case JOF_SLOTOBJECT: { Sprint(sp, " %u", GET_SLOTNO(pc)); - uintN index = js_GetIndexFromBytecode(cx, script, pc, SLOTNO_LEN); + uintN index = js_GetIndexFromBytecode(script, pc, SLOTNO_LEN); jsval v; if (type == JOF_SLOTATOM) { JSAtom *atom = script->getAtom(index); @@ -1281,7 +1306,6 @@ GetOff(SprintStack *ss, uintN i) if (off >= 0) return off; - JS_ASSERT(off <= -2); JS_ASSERT(ss->printer->pcstack); if (off <= -2 && ss->printer->pcstack) { pc = ss->printer->pcstack[-2 - off]; @@ -1361,6 +1385,15 @@ PushOff(SprintStack *ss, ptrdiff_t off, JSOp op, jsbytecode *pc = NULL) return JS_TRUE; } +static bool +PushStr(SprintStack *ss, const char *str, JSOp op) +{ + ptrdiff_t off = SprintCString(&ss->sprinter, str); + if (off < 0) + return false; + return PushOff(ss, off, op); +} + static ptrdiff_t PopOffPrec(SprintStack *ss, uint8_t prec, jsbytecode **ppc = NULL) { @@ -1673,7 +1706,9 @@ GetLocalInSlot(SprintStack *ss, jsint i, jsint slot, JSObject *obj) const Shape &shape = r.front(); if (shape.shortid() == slot) { - LOCAL_ASSERT(JSID_IS_ATOM(shape.propid())); + /* Ignore the empty destructuring dummy. */ + if (!JSID_IS_ATOM(shape.propid())) + continue; JSAtom *atom = JSID_TO_ATOM(shape.propid()); const char *rval = QuoteString(&ss->sprinter, atom, 0); @@ -1719,8 +1754,7 @@ GetLocal(SprintStack *ss, jsint i) JS_ASSERT(pc < (ss->printer->script->code + ss->printer->script->length)); if (JSOP_ENTERBLOCK == (JSOp)*pc) { - jsatomid j = js_GetIndexFromBytecode(ss->sprinter.context, - ss->printer->script, pc, 0); + jsatomid j = js_GetIndexFromBytecode(ss->printer->script, pc, 0); JSObject *obj = script->getObject(j); if (obj->isBlock()) { @@ -1773,17 +1807,32 @@ IsVarSlot(JSPrinter *jp, jsbytecode *pc, jsint *indexp) #define LOAD_ATOM(PCOFF) \ GET_ATOM_FROM_BYTECODE(jp->script, pc, PCOFF, atom) +typedef Vector AtomVector; +typedef AtomVector::Range AtomRange; + #if JS_HAS_DESTRUCTURING #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, NULL) #define LOAD_OP_DATA(pc) (oplen = (cs = &js_CodeSpec[op=(JSOp)*pc])->length) static jsbytecode * -DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc); +DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, + AtomRange *letNames = NULL); +/* + * Decompile a single element of a compound {}/[] destructuring lhs, sprinting + * the result in-place (without pushing/popping the stack) and advancing the pc + * to either the next element or the final pop. + * + * For normal (SRC_DESTRUCT) destructuring, the names of assigned/initialized + * variables are read from their slots. However, for SRC_DESTRUCTLET, the slots + * have not been pushed yet; the caller must pass the names to use via + * 'letNames'. Each variable initialized in this destructuring lhs results in + * popping a name from 'letNames'. + */ static jsbytecode * -DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, - JSBool *hole) +DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, JSBool *hole, + AtomRange *letNames = NULL) { JSPrinter *jp; JSOp op; @@ -1804,24 +1853,74 @@ DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, return NULL; break; + case JSOP_PICK: + /* + * For 'let ([x, y] = y)', the emitter generates + * + * push evaluation of y + * dup + * 1 one + * 2 getelem + * 3 pick + * 4 two + * getelem + * pick + * pop + * + * Thus 'x' consists of 1 - 3. The caller (DecompileDestructuring or + * DecompileGroupAssignment) will have taken care of 1 - 2, so pc is + * now pointing at 3. The pick indicates a primitive let var init so + * pop a name and advance the pc to 4. + */ + LOCAL_ASSERT(letNames && !letNames->empty()); + if (!QuoteString(&ss->sprinter, letNames->popCopyFront(), 0)) + return NULL; + break; + case JSOP_DUP: - pc = DecompileDestructuring(ss, pc, endpc); + { + /* Compound lhs, e.g., '[x,y]' in 'let [[x,y], z] = a;'. */ + pc = DecompileDestructuring(ss, pc, endpc, letNames); if (!pc) return NULL; if (pc == endpc) return pc; LOAD_OP_DATA(pc); + + /* + * By its post-condition, DecompileDestructuring pushed one string + * containing the whole decompiled lhs. Our post-condition is to sprint + * in-place so pop/concat this pushed string. + */ lval = PopStr(ss, JSOP_NOP); if (SprintCString(&ss->sprinter, lval) < 0) return NULL; + LOCAL_ASSERT(*pc == JSOP_POP); + + /* + * To put block slots in the right place, the emitter follows a + * compound lhs with a pick (if at least one slot was pushed). The pick + * is not part of the compound lhs so DecompileDestructuring did not + * advance over it but it is part of the lhs so advance over it here. + */ + jsbytecode *nextpc = pc + JSOP_POP_LENGTH; + LOCAL_ASSERT(nextpc <= endpc); + if (letNames && *nextpc == JSOP_PICK) { + LOCAL_ASSERT(nextpc < endpc); + pc = nextpc; + LOAD_OP_DATA(pc); + } break; + } case JSOP_SETARG: case JSOP_SETLOCAL: + LOCAL_ASSERT(!letNames); LOCAL_ASSERT(pc[oplen] == JSOP_POP || pc[oplen] == JSOP_POPN); /* FALL THROUGH */ case JSOP_SETLOCALPOP: + LOCAL_ASSERT(!letNames); if (op == JSOP_SETARG) { atom = GetArgOrVarAtom(jp, GET_SLOTNO(pc)); LOCAL_ASSERT(atom); @@ -1849,6 +1948,7 @@ DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, break; default: { + LOCAL_ASSERT(!letNames); /* * We may need to auto-parenthesize the left-most value decompiled * here, so add back PAREN_SLOP temporarily. Then decompile until the @@ -1893,59 +1993,54 @@ DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, } /* - * Starting with a SRC_DESTRUCT-annotated JSOP_DUP, decompile a destructuring - * left-hand side object or array initialiser, including nested destructuring - * initialisers. On successful return, the decompilation will be pushed on ss - * and the return value will point to the POP or GROUP bytecode following the - * destructuring expression. + * Decompile a destructuring lhs object or array initialiser, including nested + * destructuring initialisers. On return a single string is pushed containing + * the entire lhs (regardless of how many variables were bound). Thus, the + * caller must take care of fixing up the decompiler stack. * - * At any point, if pc is equal to endpc and would otherwise advance, we stop - * immediately and return endpc. + * See DecompileDestructuringLHS for description of 'letNames'. */ static jsbytecode * -DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc) +DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, + AtomRange *letNames) { - ptrdiff_t head; - JSContext *cx; - JSPrinter *jp; - JSOp op; - const JSCodeSpec *cs; - uintN oplen; - jsint i, lasti; - jsdouble d; - const char *lval; - JSAtom *atom; - jssrcnote *sn; - JSBool hole; - LOCAL_ASSERT(*pc == JSOP_DUP); pc += JSOP_DUP_LENGTH; + JSContext *cx = ss->sprinter.context; + JSPrinter *jp = ss->printer; + jsbytecode *startpc = pc; + /* * Set head so we can rewrite '[' to '{' as needed. Back up PAREN_SLOP * chars so the destructuring decompilation accumulates contiguously in * ss->sprinter starting with "[". */ - head = SprintPut(&ss->sprinter, "[", 1); + ptrdiff_t head = SprintPut(&ss->sprinter, "[", 1); if (head < 0 || !PushOff(ss, head, JSOP_NOP)) return NULL; ss->sprinter.offset -= PAREN_SLOP; LOCAL_ASSERT(head == ss->sprinter.offset - 1); LOCAL_ASSERT(*OFF2STR(&ss->sprinter, head) == '['); - cx = ss->sprinter.context; - jp = ss->printer; - lasti = -1; + int lasti = -1; while (pc < endpc) { #if JS_HAS_DESTRUCTURING_SHORTHAND ptrdiff_t nameoff = -1; #endif + const JSCodeSpec *cs; + uintN oplen; + JSOp op; LOAD_OP_DATA(pc); + int i; + double d; switch (op) { case JSOP_POP: + /* Empty destructuring lhs. */ + LOCAL_ASSERT(startpc == pc); pc += oplen; goto out; @@ -1963,7 +2058,8 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc) i = (jsint)d; do_getelem: - sn = js_GetSrcNote(jp->script, pc); + { + jssrcnote *sn = js_GetSrcNote(jp->script, pc); pc += oplen; if (pc == endpc) return pc; @@ -1986,10 +2082,12 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc) } } break; + } case JSOP_GETPROP: case JSOP_LENGTH: { + JSAtom *atom; LOAD_ATOM(0); *OFF2STR(&ss->sprinter, head) = '{'; #if JS_HAS_DESTRUCTURING_SHORTHAND @@ -2015,7 +2113,8 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc) * and continues for a bounded number of bytecodes or stack operations * (and which in any event stops before endpc). */ - pc = DecompileDestructuringLHS(ss, pc, endpc, &hole); + JSBool hole; + pc = DecompileDestructuringLHS(ss, pc, endpc, &hole, letNames); if (!pc) return NULL; @@ -2060,11 +2159,11 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc) * means another destructuring initialiser abuts this one like in * '[a] = [b] = c'. */ - sn = js_GetSrcNote(jp->script, pc); + jssrcnote *sn = js_GetSrcNote(jp->script, pc); if (!sn) break; if (SN_TYPE(sn) != SRC_CONTINUE) { - LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT); + LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT || SN_TYPE(sn) == SRC_DESTRUCTLET); break; } @@ -2075,7 +2174,7 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc) } out: - lval = OFF2STR(&ss->sprinter, head); + const char *lval = OFF2STR(&ss->sprinter, head); if (SprintPut(&ss->sprinter, (*lval == '[') ? "]" : "}", 1) < 0) return NULL; return pc; @@ -2146,8 +2245,6 @@ DecompileGroupAssignment(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, false) -typedef Vector AtomVector; - /* * The names of the vars of a let block/expr are stored as the ids of the * shapes of the block object. Shapes are stored in a singly-linked list in @@ -2168,7 +2265,9 @@ GetBlockNames(JSContext *cx, JSObject *blockObj, AtomVector *atoms) LOCAL_ASSERT(shape.hasShortID()); --i; LOCAL_ASSERT((uintN)shape.shortid() == i); - (*atoms)[i] = JSID_TO_ATOM(shape.propid()); + (*atoms)[i] = JSID_IS_INT(shape.propid()) + ? cx->runtime->atomState.emptyAtom + : JSID_TO_ATOM(shape.propid()); } LOCAL_ASSERT(i == 0); @@ -2186,9 +2285,28 @@ PushBlockNames(JSContext *cx, SprintStack *ss, const AtomVector &atoms) return true; } +/* + * In the scope of a let, the variables' (decompiler) stack slots must contain + * the corresponding variable's name. This function updates the N top slots + * with the N variable names stored in 'atoms'. + */ +static bool +AssignBlockNamesToPushedSlots(JSContext *cx, SprintStack *ss, const AtomVector &atoms) +{ + /* For simplicity, just pop and push. */ + LOCAL_ASSERT(atoms.length() <= (uintN)ss->top); + for (size_t i = 0; i < atoms.length(); ++i) + PopStr(ss, JSOP_NOP); + return PushBlockNames(cx, ss, atoms); +} + +static const char SkipString[] = "/*skip*/"; +static const char DestructuredString[] = "/*destructured*/"; +static const unsigned DestructuredStringLength = ArrayLength(DestructuredString) - 1; + static ptrdiff_t -SprintLet(JSContext *cx, JSPrinter *jp, SprintStack *ss, jsbytecode *pc, ptrdiff_t bodyLength, - const char *headChars) +SprintLetBody(JSContext *cx, JSPrinter *jp, SprintStack *ss, jsbytecode *pc, ptrdiff_t bodyLength, + const char *headChars) { if (pc[bodyLength] == JSOP_LEAVEBLOCK) { js_printf(jp, "\tlet (%s) {\n", headChars); @@ -2245,16 +2363,15 @@ GetTokenForAssignment(JSPrinter *jp, jssrcnote *sn, JSOp lastop, } static ptrdiff_t -SprintNormalFor(JSContext *cx, JSPrinter *jp, SprintStack *ss, - const char *init, jsbytecode *initpc, - jsbytecode **ppc, ptrdiff_t *plen) +SprintNormalFor(JSContext *cx, JSPrinter *jp, SprintStack *ss, const char *initPrefix, + const char *init, jsbytecode *initpc, jsbytecode **ppc, ptrdiff_t *plen) { jsbytecode *pc = *ppc; jssrcnote *sn = js_GetSrcNote(jp->script, pc); JS_ASSERT(SN_TYPE(sn) == SRC_FOR); /* Print the keyword and the possibly empty init-part. */ - js_printf(jp, "\tfor ("); + js_printf(jp, "\tfor (%s", initPrefix); SprintOpcodePermanent(jp, init, initpc); js_printf(jp, ";"); @@ -2522,7 +2639,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) } saveop = op; len = oplen = cs->length; - nuses = js_GetStackUses(cs, op, pc); + nuses = StackUses(jp->script, pc); /* * Here it is possible that nuses > ss->top when the op has a hidden @@ -2531,7 +2648,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) */ if (nb < 0) { LOCAL_ASSERT(ss->top >= nuses); - uintN ndefs = js_GetStackDefs(cx, cs, op, jp->script, pc); + uintN ndefs = StackDefs(jp->script, pc); if ((uintN) -(nb + 1) == ss->top - nuses + ndefs) return pc; } @@ -2671,6 +2788,12 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) break; case 0: + sn = js_GetSrcNote(jp->script, pc); + if (sn && SN_TYPE(sn) == SRC_CONTINUE) { + /* Hoisted let decl (e.g. 'y' in 'let (x) { let y; }'). */ + todo = SprintCString(&ss->sprinter, SkipString); + break; + } todo = SprintCString(&ss->sprinter, token); break; @@ -2712,7 +2835,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) case SRC_FOR: /* for loop with empty initializer. */ - todo = SprintNormalFor(cx, jp, ss, "", NULL, &pc, &len); + todo = SprintNormalFor(cx, jp, ss, "", "", NULL, &pc, &len); break; case SRC_ENDBRACE: @@ -2875,8 +2998,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) } else { /* * Kill newtop before the end_groupassignment: label by - * retracting/popping early. Control will either jump - * to do_letheadbody: or else break from our case. + * retracting/popping early. */ LOCAL_ASSERT(newtop < oldtop); ss->sprinter.offset = GetOff(ss, newtop); @@ -2908,31 +3030,9 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) if (SN_TYPE(sn) == SRC_FOR) { op = JSOP_NOP; pc = pc2; - todo = SprintNormalFor(cx, jp, ss, rval, rvalpc, &pc, &len); + todo = SprintNormalFor(cx, jp, ss, "", rval, rvalpc, &pc, &len); break; } - - if (SN_TYPE(sn) == SRC_DECL) { - if (ss->top == StackDepth(jp->script)) { - /* - * This must be an empty destructuring - * in the head of a let whose body block - * is also empty. - */ - pc = pc2 + JSOP_NOP_LENGTH; - len = js_GetSrcNoteOffset(sn, 0); - LOCAL_ASSERT(pc[len] == JSOP_LEAVEBLOCK); - js_printf(jp, "\tlet (%s) {\n", rval); - js_printf(jp, "\t}\n"); - break; - } - todo = SprintCString(&ss->sprinter, rval); - if (todo < 0 || !PushOff(ss, todo, JSOP_NOP)) - return NULL; - op = JSOP_POP; - pc = pc2 + JSOP_NOP_LENGTH; - goto do_letheadbody; - } } else { /* * An unnannotated NOP following a POPN must be the @@ -2988,7 +3088,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) if (ss->opcodes[ss->top-1] == JSOP_IN) op = JSOP_LSH; rval = PopStr(ss, op, &rvalpc); - todo = SprintNormalFor(cx, jp, ss, rval, rvalpc, &pc, &len); + todo = SprintNormalFor(cx, jp, ss, "", rval, rvalpc, &pc, &len); break; case SRC_PCDELTA: @@ -3023,22 +3123,11 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) todo = -2; break; - case SRC_DECL: - { - /* This pop is at the end of the let block/expr head. */ - pc += JSOP_POP_LENGTH; -#if JS_HAS_DESTRUCTURING - do_letheadbody: -#endif - DupBuffer head(cx); - if (!Dup(POP_STR(), &head)) - return NULL; - - len = js_GetSrcNoteOffset(sn, 0); - saveop = (JSOp) pc[len]; - todo = SprintLet(cx, jp, ss, pc, len, head.begin()); - } - break; + case SRC_CONTINUE: + /* Pop the stack, don't print: end of a for-let-in. */ + (void) PopOff(ss, op); + todo = -2; + break; default: { @@ -3236,6 +3325,157 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) break; } + case JSOP_ENTERLET0: + { + LOAD_OBJECT(0); + + AtomVector atoms(cx); + if (!GetBlockNames(cx, obj, &atoms)) + return NULL; + + sn = js_GetSrcNote(jp->script, pc); + LOCAL_ASSERT(SN_TYPE(sn) == SRC_DECL); + ptrdiff_t letData = js_GetSrcNoteOffset(sn, 0); + bool groupAssign = LetDataToGroupAssign(letData); + uintN letDepth = OBJ_BLOCK_DEPTH(cx, obj); + LOCAL_ASSERT(letDepth == (uintN)ss->top - OBJ_BLOCK_COUNT(cx, obj)); + LOCAL_ASSERT(atoms.length() == OBJ_BLOCK_COUNT(cx, obj)); + + /* + * Build the list of decompiled rhs expressions. Do this before + * sprinting the let-head since GetStr can inject stuff on top + * of the stack (in case js_DecompileValueGenerator). + */ + Vector rhsExprs(cx); + if (!rhsExprs.resize(atoms.length())) + return false; + for (size_t i = 0; i < rhsExprs.length(); ++i) { + rhsExprs[i] = GetStr(ss, letDepth + i); + if (!rhsExprs[i]) + return false; + } + + /* Build the let head starting at headBegin. */ + ptrdiff_t headBegin = ss->sprinter.offset; + + /* + * For group assignment, prepend the '[lhs-vars] = [' here, + * append rhsExprs in the next loop and append ']' after. + */ + if (groupAssign) { + if (Sprint(&ss->sprinter, "[") < 0) + return false; + for (size_t i = 0; i < atoms.length(); ++i) { + if (i && Sprint(&ss->sprinter, ", ") < 0) + return false; + if (!QuoteString(&ss->sprinter, atoms[i], 0)) + return false; + } + if (Sprint(&ss->sprinter, "] = [") < 0) + return false; + } + + for (size_t i = 0; i < atoms.length(); ++i) { + const char *rhs = rhsExprs[i]; + if (!strcmp(rhs, SkipString)) + continue; + + if (i && Sprint(&ss->sprinter, ", ") < 0) + return false; + + if (groupAssign) { + if (SprintCString(&ss->sprinter, rhs) < 0) + return false; + } else if (!strncmp(rhs, DestructuredString, DestructuredStringLength)) { + if (SprintCString(&ss->sprinter, rhs + DestructuredStringLength) < 0) + return false; + } else { + JS_ASSERT(atoms[i] != cx->runtime->atomState.emptyAtom); + if (!QuoteString(&ss->sprinter, atoms[i], 0)) + return false; + if (*rhs) { + uint8_t prec = js_CodeSpec[ss->opcodes[letDepth + i]].prec; + const char *fmt = prec && prec < js_CodeSpec[JSOP_SETLOCAL].prec + ? " = (%s)" + : " = %s"; + if (Sprint(&ss->sprinter, fmt, rhs) < 0) + return false; + } + } + } + + if (groupAssign && Sprint(&ss->sprinter, "]") < 0) + return false; + + /* Clone the let head chars before clobbering the stack. */ + DupBuffer head(cx); + if (!Dup(OFF2STR(&ss->sprinter, headBegin), &head)) + return NULL; + if (!AssignBlockNamesToPushedSlots(cx, ss, atoms)) + return NULL; + + /* Detect 'for (let ...)' desugared into 'let (...) {for}'. */ + jsbytecode *nextpc = pc + JSOP_ENTERLET0_LENGTH; + if (*nextpc == JSOP_NOP) { + jssrcnote *nextsn = js_GetSrcNote(jp->script, nextpc); + if (nextsn && SN_TYPE(nextsn) == SRC_FOR) { + pc = nextpc; + todo = SprintNormalFor(cx, jp, ss, "let ", head.begin(), pc, &pc, &len); + break; + } + } + + /* Decompile the body and then complete the let block/expr. */ + len = LetDataToOffset(letData); + pc = nextpc; + saveop = (JSOp) pc[len]; + todo = SprintLetBody(cx, jp, ss, pc, len, head.begin()); + break; + } + + /* + * With 'for (let lhs in rhs)' and 'switch (c) { let-decl }', + * placeholder slots have already been pushed (by JSOP_UNDEFINED). + * In both the for-let-in and switch-hoisted-let cases: + * - there is a non-let slot on top of the stack (hence enterlet1) + * - there is no further special let-handling required: + * for-let-in will decompile the let head when it decompiles + * the loop body prologue; there is no let head to decompile + * with switch. + * Hence, the only thing to do is update the let vars' slots with + * their names, taking care to preserve the iter/condition value + * on top of the stack. + */ + case JSOP_ENTERLET1: + { + LOAD_OBJECT(0); + + AtomVector atoms(cx); + if (!GetBlockNames(cx, obj, &atoms)) + return NULL; + + LOCAL_ASSERT(js_GetSrcNote(jp->script, pc) == NULL); + LOCAL_ASSERT(ss->top - 1 == OBJ_BLOCK_DEPTH(cx, obj) + OBJ_BLOCK_COUNT(cx, obj)); + jsbytecode *nextpc = pc + JSOP_ENTERLET1_LENGTH; + if (*nextpc == JSOP_GOTO || *nextpc == JSOP_GOTOX) { + LOCAL_ASSERT(SN_TYPE(js_GetSrcNote(jp->script, nextpc)) == SRC_FOR_IN); + } else { + LOCAL_ASSERT(*nextpc == JSOP_CONDSWITCH || + *nextpc == JSOP_TABLESWITCH || *nextpc == JSOP_TABLESWITCHX || + *nextpc == JSOP_LOOKUPSWITCH || *nextpc == JSOP_LOOKUPSWITCHX); + } + + DupBuffer rhs(cx); + if (!Dup(PopStr(ss, JSOP_NOP), &rhs)) + return NULL; + if (!AssignBlockNamesToPushedSlots(cx, ss, atoms)) + return NULL; + if (!PushStr(ss, rhs.begin(), op)) + return NULL; + todo = -2; + break; + } + case JSOP_GETFCSLOT: case JSOP_CALLFCSLOT: { @@ -3591,7 +3831,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) } /* - * Do not AddParentSlop here, as we will push the + * Do not AddParenSlop here, as we will push the * top-most offset again, which will add paren slop * for us. We must push to balance the stack budget * when nesting for heads in a comprehension. @@ -3857,25 +4097,106 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) #if JS_HAS_DESTRUCTURING sn = js_GetSrcNote(jp->script, pc); if (sn) { - LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT); - pc = DecompileDestructuring(ss, pc, endpc); - if (!pc) - return NULL; - len = 0; - lval = POP_STR(); - op = saveop = JSOP_ENUMELEM; - rval = POP_STR(); + if (SN_TYPE(sn) == SRC_DESTRUCT) { + pc = DecompileDestructuring(ss, pc, endpc); + if (!pc) + return NULL; - if (strcmp(rval, forelem_cookie) == 0) { - todo = Sprint(&ss->sprinter, ss_format, - VarPrefix(sn), lval); + lval = POP_STR(); /* Pop the decompiler result. */ + rval = POP_STR(); /* Pop the initializer expression. */ - // Skip POP so the SRC_FOR_IN code can pop for itself. - if (*pc == JSOP_POP) - len = JSOP_POP_LENGTH; + if (strcmp(rval, forelem_cookie) == 0) { + todo = Sprint(&ss->sprinter, ss_format, + VarPrefix(sn), lval); + + /* Skip POP so the SRC_FOR_IN code can pop for itself. */ + if (*pc == JSOP_POP) + len = JSOP_POP_LENGTH; + } else { + todo = Sprint(&ss->sprinter, "%s%s = %s", + VarPrefix(sn), lval, rval); + } + + op = saveop = JSOP_ENUMELEM; + len = 0; } else { - todo = Sprint(&ss->sprinter, "%s%s = %s", - VarPrefix(sn), lval, rval); + LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCTLET); + + ptrdiff_t offsetToLet = js_GetSrcNoteOffset(sn, 0); + LOCAL_ASSERT(*(pc + offsetToLet) == JSOP_ENTERLET0); + + GET_OBJECT_FROM_BYTECODE(jp->script, pc + offsetToLet, 0, obj); + + uint32_t blockDepth = OBJ_BLOCK_DEPTH(cx, obj); + LOCAL_ASSERT(blockDepth < ss->top); + LOCAL_ASSERT(ss->top <= blockDepth + OBJ_BLOCK_COUNT(cx, obj)); + + AtomVector atoms(cx); + if (!GetBlockNames(cx, obj, &atoms)) + return NULL; + + /* + * Skip any initializers preceding this one. E.g., in + * let (w=1, x=2, [y,z] = a) { ... } + * skip 'w' and 'x' for the JSOP_DUP of '[y,z] = a'. + */ + AtomRange letNames = atoms.all(); + uint32_t curDepth = ss->top - 1 /* initializer */; + for (uint32_t i = blockDepth; i < curDepth; ++i) + letNames.popFront(); + + /* + * Pop and copy the rhs before it gets clobbered. + * Use JSOP_SETLOCAL's precedence since this is =. + */ + DupBuffer rhs(cx); + if (!Dup(PopStr(ss, JSOP_SETLOCAL), &rhs)) + return NULL; + + /* Destructure, tracking how many vars were bound. */ + size_t remainBefore = letNames.remain(); + pc = DecompileDestructuring(ss, pc, endpc, &letNames); + if (!pc) + return NULL; + size_t remainAfter = letNames.remain(); + + /* + * Merge the lhs and rhs and prefix with a cookie to + * tell enterlet0 not to prepend "name = ". + */ + const char *lhs = PopStr(ss, JSOP_NOP); + ptrdiff_t off = Sprint(&ss->sprinter, "%s%s = %s", + DestructuredString, lhs, rhs.begin()); + if (off < 0 || !PushOff(ss, off, JSOP_NOP)) + return NULL; + + /* + * Only one slot has been pushed (holding the entire + * decompiled destructuring expression). However, the + * abstract depth needs one slot per bound var, so push + * empty strings for the remainder. We don't have to + * worry about empty destructuring because the parser + * ensures that there is always at least one pushed + * slot for each destructuring lhs. + */ + LOCAL_ASSERT(remainBefore >= remainAfter); + LOCAL_ASSERT(remainBefore > remainAfter || remainAfter > 0); + for (size_t i = remainBefore - 1; i > remainAfter; --i) { + if (!PushStr(ss, SkipString, JSOP_NOP)) + return NULL; + } + + LOCAL_ASSERT(*pc == JSOP_POP); + pc += JSOP_POP_LENGTH; + + /* Eat up the JSOP_UNDEFINED following empty destructuring. */ + if (remainBefore == remainAfter) { + LOCAL_ASSERT(*pc == JSOP_UNDEFINED); + pc += JSOP_UNDEFINED_LENGTH; + } + + len = 0; + todo = -2; } break; } @@ -5534,8 +5855,8 @@ static intN SimulateOp(JSContext *cx, JSScript *script, JSOp op, const JSCodeSpec *cs, jsbytecode *pc, jsbytecode **pcstack, uintN &pcdepth) { - uintN nuses = js_GetStackUses(cs, op, pc); - uintN ndefs = js_GetStackDefs(cx, cs, op, script, pc); + uintN nuses = StackUses(script, pc); + uintN ndefs = StackDefs(script, pc); LOCAL_ASSERT(pcdepth >= nuses); pcdepth -= nuses; LOCAL_ASSERT(pcdepth + ndefs <= StackDepth(script)); diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index 90fe87a7fa8a..bc9b312873b3 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -332,8 +332,7 @@ js_puts(JSPrinter *jp, const char *s); * lexical environments. */ uintN -js_GetIndexFromBytecode(JSContext *cx, JSScript *script, jsbytecode *pc, - ptrdiff_t pcoff); +js_GetIndexFromBytecode(JSScript *script, jsbytecode *pc, ptrdiff_t pcoff); /* * A slower version of GET_ATOM when the caller does not want to maintain @@ -342,72 +341,46 @@ js_GetIndexFromBytecode(JSContext *cx, JSScript *script, jsbytecode *pc, #define GET_ATOM_FROM_BYTECODE(script, pc, pcoff, atom) \ JS_BEGIN_MACRO \ JS_ASSERT(*(pc) != JSOP_DOUBLE); \ - uintN index_ = js_GetIndexFromBytecode(cx, (script), (pc), (pcoff)); \ + uintN index_ = js_GetIndexFromBytecode((script), (pc), (pcoff)); \ (atom) = (script)->getAtom(index_); \ JS_END_MACRO #define GET_DOUBLE_FROM_BYTECODE(script, pc, pcoff, dbl) \ JS_BEGIN_MACRO \ - uintN index_ = js_GetIndexFromBytecode(cx, (script), (pc), (pcoff)); \ + uintN index_ = js_GetIndexFromBytecode((script), (pc), (pcoff)); \ JS_ASSERT(index_ < (script)->consts()->length); \ (dbl) = (script)->getConst(index_).toDouble(); \ JS_END_MACRO #define GET_OBJECT_FROM_BYTECODE(script, pc, pcoff, obj) \ JS_BEGIN_MACRO \ - uintN index_ = js_GetIndexFromBytecode(cx, (script), (pc), (pcoff)); \ + uintN index_ = js_GetIndexFromBytecode((script), (pc), (pcoff)); \ obj = (script)->getObject(index_); \ JS_END_MACRO #define GET_FUNCTION_FROM_BYTECODE(script, pc, pcoff, fun) \ JS_BEGIN_MACRO \ - uintN index_ = js_GetIndexFromBytecode(cx, (script), (pc), (pcoff)); \ + uintN index_ = js_GetIndexFromBytecode((script), (pc), (pcoff)); \ fun = (script)->getFunction(index_); \ JS_END_MACRO #define GET_REGEXP_FROM_BYTECODE(script, pc, pcoff, obj) \ JS_BEGIN_MACRO \ - uintN index_ = js_GetIndexFromBytecode(cx, (script), (pc), (pcoff)); \ + uintN index_ = js_GetIndexFromBytecode((script), (pc), (pcoff)); \ obj = (script)->getRegExp(index_); \ JS_END_MACRO -/* - * Find the number of stack slots used by a variadic opcode such as JSOP_CALL - * (for such ops, JSCodeSpec.nuses is -1). - */ +#ifdef __cplusplus +namespace js { + extern uintN -js_GetVariableStackUses(JSOp op, jsbytecode *pc); +StackUses(JSScript *script, jsbytecode *pc); -/* - * Find the number of stack slots defined by JSOP_ENTERBLOCK (for this op, - * JSCodeSpec.ndefs is -1). - */ extern uintN -js_GetEnterBlockStackDefs(JSContext *cx, JSScript *script, jsbytecode *pc); +StackDefs(JSScript *script, jsbytecode *pc); -#ifdef __cplusplus /* Aargh, libgjs, bug 492720. */ -static JS_INLINE uintN -js_GetStackUses(const JSCodeSpec *cs, JSOp op, jsbytecode *pc) -{ - JS_ASSERT(cs == &js_CodeSpec[op]); - if (cs->nuses >= 0) - return cs->nuses; - return js_GetVariableStackUses(op, pc); -} - -static JS_INLINE uintN -js_GetStackDefs(JSContext *cx, const JSCodeSpec *cs, JSOp op, JSScript *script, - jsbytecode *pc) -{ - JS_ASSERT(cs == &js_CodeSpec[op]); - if (cs->ndefs >= 0) - return cs->ndefs; - - /* Only JSOP_ENTERBLOCK has a variable number of stack defs. */ - JS_ASSERT(op == JSOP_ENTERBLOCK); - return js_GetEnterBlockStackDefs(cx, script, pc); -} -#endif +} /* namespace js */ +#endif /* __cplusplus */ /* * Decompilers, for script, function, and expression pretty-printing. diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl index 01248a20cec1..2d8e00c35788 100644 --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -271,7 +271,8 @@ OPDEF(JSOP_DECLOCAL, 102,"declocal", NULL, 3, 0, 1, 15, JOF_LOCAL| OPDEF(JSOP_LOCALINC, 103,"localinc", NULL, 3, 0, 1, 15, JOF_LOCAL|JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3) OPDEF(JSOP_LOCALDEC, 104,"localdec", NULL, 3, 0, 1, 15, JOF_LOCAL|JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3) -OPDEF(JSOP_UNUSED1, 105,"unused0", NULL, 1, 0, 0, 0, JOF_BYTE) +/* Leave a for-let-in block leaving its storage pushed (to be popped after enditer). */ +OPDEF(JSOP_LEAVEFORLETIN, 105,"leaveforletin",NULL, 1, 0, 0, 0, JOF_BYTE) /* The argument is the offset to the next statement and is used by IonMonkey. */ OPDEF(JSOP_LABEL, 106,"label", NULL, 3, 0, 0, 0, JOF_JUMP) @@ -440,8 +441,11 @@ OPDEF(JSOP_DELDESC, 183,"deldesc", NULL, 1, 2, 1, 15, JOF_BYTE|J OPDEF(JSOP_CALLPROP, 184,"callprop", NULL, 3, 1, 2, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_CALLOP|JOF_TMPSLOT3) -OPDEF(JSOP_UNUSED2, 185,"unused1", NULL, 1, 0, 0, 0, JOF_BYTE) -OPDEF(JSOP_UNUSED3, 186,"unused2", NULL, 1, 0, 0, 0, JOF_BYTE) +/* Enter a let block/expr whose slots are at the top of the stack. */ +OPDEF(JSOP_ENTERLET0, 185,"enterlet0", NULL, 3, -1, -1, 0, JOF_OBJECT) + +/* Enter a let block/expr whose slots are 1 below the top of the stack. */ +OPDEF(JSOP_ENTERLET1, 186,"enterlet1", NULL, 3, -1, -1, 0, JOF_OBJECT) /* * Opcode to hold 24-bit immediate integer operands. diff --git a/js/src/jsreflect.cpp b/js/src/jsreflect.cpp index 269d5b9f52e5..77504c2f78c2 100644 --- a/js/src/jsreflect.cpp +++ b/js/src/jsreflect.cpp @@ -1623,7 +1623,7 @@ class ASTSerializer bool declaration(ParseNode *pn, Value *dst); bool variableDeclaration(ParseNode *pn, bool let, Value *dst); bool variableDeclarator(ParseNode *pn, VarDeclKind *pkind, Value *dst); - bool letHead(ParseNode *pn, NodeVector &dtors); + bool let(ParseNode *pn, bool expr, Value *dst); bool optStatement(ParseNode *pn, Value *dst) { if (!pn) { @@ -1963,14 +1963,21 @@ ASTSerializer::variableDeclarator(ParseNode *pn, VarDeclKind *pkind, Value *dst) } bool -ASTSerializer::letHead(ParseNode *pn, NodeVector &dtors) +ASTSerializer::let(ParseNode *pn, bool expr, Value *dst) { - if (!dtors.reserve(pn->pn_count)) + ParseNode *letHead = pn->pn_left; + LOCAL_ASSERT(letHead->isArity(PN_LIST)); + + ParseNode *letBody = pn->pn_right; + LOCAL_ASSERT(letBody->isKind(PNK_LEXICALSCOPE)); + + NodeVector dtors(cx); + if (!dtors.reserve(letHead->pn_count)) return false; VarDeclKind kind = VARDECL_LET_HEAD; - for (ParseNode *next = pn->pn_head; next; next = next->pn_next) { + for (ParseNode *next = letHead->pn_head; next; next = next->pn_next) { Value child; /* * Unlike in |variableDeclaration|, this does not update |kind|; since let-heads do @@ -1981,7 +1988,12 @@ ASTSerializer::letHead(ParseNode *pn, NodeVector &dtors) dtors.infallibleAppend(child); } - return true; + Value v; + return expr + ? expression(letBody->pn_expr, &v) && + builder.letExpression(dtors, v, &pn->pn_pos, dst) + : statement(letBody->pn_expr, &v) && + builder.letStatement(dtors, v, &pn->pn_pos, dst); } bool @@ -2078,8 +2090,6 @@ ASTSerializer::forInit(ParseNode *pn, Value *dst) return (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST)) ? variableDeclaration(pn, false, dst) - : pn->isKind(PNK_LET) - ? variableDeclaration(pn, true, dst) : expression(pn, dst); } @@ -2091,9 +2101,13 @@ ASTSerializer::statement(ParseNode *pn, Value *dst) case PNK_FUNCTION: case PNK_VAR: case PNK_CONST: - case PNK_LET: return declaration(pn, dst); + case PNK_LET: + return pn->isArity(PN_BINARY) + ? let(pn, false, dst) + : declaration(pn, dst); + case PNK_NAME: LOCAL_ASSERT(pn->isUsed()); return statement(pn->pn_lexdef, dst); @@ -2108,15 +2122,6 @@ ASTSerializer::statement(ParseNode *pn, Value *dst) case PNK_LEXICALSCOPE: pn = pn->pn_expr; - if (pn->isKind(PNK_LET)) { - NodeVector dtors(cx); - Value stmt; - - return letHead(pn->pn_left, dtors) && - statement(pn->pn_right, &stmt) && - builder.letStatement(dtors, stmt, &pn->pn_pos, dst); - } - if (!pn->isKind(PNK_STATEMENTLIST)) return statement(pn, dst); /* FALL THROUGH */ @@ -2176,9 +2181,9 @@ ASTSerializer::statement(ParseNode *pn, Value *dst) return (!head->pn_kid1 ? pattern(head->pn_kid2, NULL, &var) - : variableDeclaration(head->pn_kid1, - head->pn_kid1->isKind(PNK_LET), - &var)) && + : head->pn_kid1->isKind(PNK_LEXICALSCOPE) + ? variableDeclaration(head->pn_kid1->pn_expr, true, &var) + : variableDeclaration(head->pn_kid1, false, &var)) && expression(head->pn_kid3, &expr) && builder.forInStatement(var, expr, stmt, isForEach, &pn->pn_pos, dst); } @@ -2633,17 +2638,8 @@ ASTSerializer::expression(ParseNode *pn, Value *dst) return comprehension(pn->pn_head->pn_expr, dst); - case PNK_LEXICALSCOPE: - { - pn = pn->pn_expr; - - NodeVector dtors(cx); - Value expr; - - return letHead(pn->pn_left, dtors) && - expression(pn->pn_right, &expr) && - builder.letExpression(dtors, expr, &pn->pn_pos, dst); - } + case PNK_LET: + return let(pn, true, dst); #ifdef JS_HAS_XML_SUPPORT case PNK_XMLUNARY: diff --git a/js/src/jsxdrapi.h b/js/src/jsxdrapi.h index ab3072da4336..8c57cdd0ca0f 100644 --- a/js/src/jsxdrapi.h +++ b/js/src/jsxdrapi.h @@ -226,7 +226,7 @@ JS_XDRFindClassById(JSXDRState *xdr, uint32_t id); * and saved versions. If deserialization fails, the data should be * invalidated if possible. */ -#define JSXDR_BYTECODE_VERSION (0xb973c0de - 99) +#define JSXDR_BYTECODE_VERSION (0xb973c0de - 100) /* * Library-private functions. diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 0d5d94896c73..01555b690f01 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -2678,6 +2678,8 @@ mjit::Compiler::generateMethod() END_CASE(JSOP_GETXPROP) BEGIN_CASE(JSOP_ENTERBLOCK) + BEGIN_CASE(JSOP_ENTERLET0) + BEGIN_CASE(JSOP_ENTERLET1) enterBlock(script->getObject(fullAtomIndex(PC))); END_CASE(JSOP_ENTERBLOCK); @@ -7134,9 +7136,9 @@ mjit::Compiler::enterBlock(JSObject *obj) /* For now, don't bother doing anything for this opcode. */ frame.syncAndForgetEverything(); masm.move(ImmPtr(obj), Registers::ArgReg1); - uint32_t n = js_GetEnterBlockStackDefs(cx, script, PC); INLINE_STUBCALL(stubs::EnterBlock, REJOIN_NONE); - frame.enterBlock(n); + if (*PC == JSOP_ENTERBLOCK) + frame.enterBlock(StackDefs(script, PC)); } void @@ -7146,7 +7148,7 @@ mjit::Compiler::leaveBlock() * Note: After bug 535912, we can pass the block obj directly, inline * PutBlockObject, and do away with the muckiness in PutBlockObject. */ - uint32_t n = js_GetVariableStackUses(JSOP_LEAVEBLOCK, PC); + uint32_t n = StackUses(script, PC); prepareStubCall(Uses(n)); INLINE_STUBCALL(stubs::LeaveBlock, REJOIN_NONE); frame.leaveBlock(n); diff --git a/js/src/methodjit/LoopState.cpp b/js/src/methodjit/LoopState.cpp index 865bd5246905..116ed20b856b 100644 --- a/js/src/methodjit/LoopState.cpp +++ b/js/src/methodjit/LoopState.cpp @@ -1869,7 +1869,7 @@ LoopState::analyzeLoopBody(unsigned frame) case JSOP_SETPROP: case JSOP_SETMETHOD: { - JSAtom *atom = script->getAtom(js_GetIndexFromBytecode(cx, script, pc, 0)); + JSAtom *atom = script->getAtom(js_GetIndexFromBytecode(script, pc, 0)); jsid id = MakeTypeId(cx, ATOM_TO_JSID(atom)); TypeSet *objTypes = analysis->poppedTypes(pc, 1); @@ -2185,7 +2185,7 @@ LoopState::getEntryValue(const CrossSSAValue &iv, uint32_t *pslot, int32_t *pcon } case JSOP_GETPROP: { - JSAtom *atom = script->getAtom(js_GetIndexFromBytecode(cx, script, pc, 0)); + JSAtom *atom = script->getAtom(js_GetIndexFromBytecode(script, pc, 0)); jsid id = ATOM_TO_JSID(atom); CrossSSAValue objcv(cv.frame, analysis->poppedValue(v.pushedOffset(), 0)); FrameEntry *tmp = invariantProperty(objcv, id); diff --git a/js/src/methodjit/StubCalls.cpp b/js/src/methodjit/StubCalls.cpp index fd448bb90785..39dbd8bf64f1 100644 --- a/js/src/methodjit/StubCalls.cpp +++ b/js/src/methodjit/StubCalls.cpp @@ -1821,12 +1821,15 @@ stubs::EnterBlock(VMFrame &f, JSObject *obj) JS_ASSERT(!f.regs.inlined()); JS_ASSERT(obj->isStaticBlock()); - JS_ASSERT(fp->base() + OBJ_BLOCK_DEPTH(cx, obj) == regs.sp); - Value *vp = regs.sp + OBJ_BLOCK_COUNT(cx, obj); - JS_ASSERT(regs.sp < vp); - JS_ASSERT(vp <= fp->slots() + fp->script()->nslots); - SetValueRangeToUndefined(regs.sp, vp); - regs.sp = vp; + + if (*regs.pc == JSOP_ENTERBLOCK) { + JS_ASSERT(fp->base() + OBJ_BLOCK_DEPTH(cx, obj) == regs.sp); + Value *vp = regs.sp + OBJ_BLOCK_COUNT(cx, obj); + JS_ASSERT(regs.sp < vp); + JS_ASSERT(vp <= fp->slots() + fp->script()->nslots); + SetValueRangeToUndefined(regs.sp, vp); + regs.sp = vp; + } #ifdef DEBUG JSContext *cx = f.cx; diff --git a/js/src/tests/js1_8/regress/regress-465567-01.js b/js/src/tests/js1_8/regress/regress-465567-01.js index 38310e37f9df..8736d5565593 100644 --- a/js/src/tests/js1_8/regress/regress-465567-01.js +++ b/js/src/tests/js1_8/regress/regress-465567-01.js @@ -49,11 +49,11 @@ expect = '99999'; jit(true); for (let j = 0; j < 5; ++j) { + var e; e = 9; print(actual += '' + e); e = 47; if (e & 0) { - var e; let e; } } diff --git a/js/src/tests/js1_8_5/extensions/reflect-parse.js b/js/src/tests/js1_8_5/extensions/reflect-parse.js index 701382fc530f..879c60e3a909 100644 --- a/js/src/tests/js1_8_5/extensions/reflect-parse.js +++ b/js/src/tests/js1_8_5/extensions/reflect-parse.js @@ -575,7 +575,7 @@ function testVarPatternCombinations(makePattSrc, makePattPatt) { assertStmt("for (var " + pattSrcs[i].join(",") + "; foo; bar);", forStmt(varDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); assertStmt("for (let " + pattSrcs[i].join(",") + "; foo; bar);", - forStmt(letDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); + letStmt(pattPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt))); assertStmt("for (const " + pattSrcs[i].join(",") + "; foo; bar);", forStmt(constDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); } diff --git a/js/src/tests/js1_8_5/extensions/regress-672804-1.js b/js/src/tests/js1_8_5/extensions/regress-672804-1.js index 2197f89278e8..be8cc82f311a 100644 --- a/js/src/tests/js1_8_5/extensions/regress-672804-1.js +++ b/js/src/tests/js1_8_5/extensions/regress-672804-1.js @@ -6,7 +6,7 @@ function f() { let (a = let (x = 1) x) {} } -trap(f, 3, 'assertEq(evalInFrame(1, "a"), 0)'); +trap(f, 4, 'assertEq(evalInFrame(1, "a"), 0)'); f(); reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-672804-3.js b/js/src/tests/js1_8_5/extensions/regress-672804-3.js index 029e82f07a3a..3d60fc36e81b 100644 --- a/js/src/tests/js1_8_5/extensions/regress-672804-3.js +++ b/js/src/tests/js1_8_5/extensions/regress-672804-3.js @@ -5,7 +5,7 @@ var e = [], x = {b: []}; function f() { let (a = [[] for (x in e)], {b: []} = x) {} } -trap(f, 4, ''); +trap(f, 3, ''); f(); reportCompare(0, 0, 'ok');