--- a/js/public/Vector.h
+++ b/js/public/Vector.h
@@ -375,16 +375,33 @@ class Vector : private AllocPolicy
return *(end() - 1);
}
const T &back() const {
JS_ASSERT(!entered && !empty());
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. */
bool reserve(size_t capacity);
/*
* Destroy elements in the range [end() - incr, end()). Does not deallocate
* or unreserve storage for those elements.
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -185,64 +185,63 @@ EmitCheck(JSContext *cx, BytecodeEmitter
JS_ASSERT(newlength >= size_t(offset + delta));
bce->current->base = newbase;
bce->current->limit = newbase + newlength;
bce->current->next = newbase + offset;
}
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;
-
- pc = bce->code(target);
- op = (JSOp) *pc;
- cs = &js_CodeSpec[op];
- if ((cs->format & JOF_TMPSLOT_MASK)) {
+ jsbytecode *pc = bce->code(target);
+ JSOp op = (JSOp) *pc;
+ const JSCodeSpec *cs = &js_CodeSpec[op];
+
+
+ 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;
}
static inline void
UpdateDecomposeLength(BytecodeEmitter *bce, uintN start)
{
@@ -1491,16 +1490,26 @@ FlushPops(JSContext *cx, BytecodeEmitter
JS_ASSERT(*npops != 0);
if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
return JS_FALSE;
EMIT_UINT16_IMM_OP(JSOP_POPN, *npops);
*npops = 0;
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.
*/
static JSBool
EmitNonLocalJumpFixup(JSContext *cx, BytecodeEmitter *bce, StmtInfo *toStmt)
{
/*
* The non-local jump fixup we emit will unbalance bce->stackDepth, because
@@ -1528,44 +1537,58 @@ EmitNonLocalJumpFixup(JSContext *cx, Byt
FLUSH_POPS();
if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
return JS_FALSE;
if (Emit1(cx, bce, JSOP_LEAVEWITH) < 0)
return JS_FALSE;
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;
case STMT_SUBROUTINE:
/*
* There's a [exception or hole, retsub pc-index] pair on the
* stack that we need to pop.
*/
npops += 2;
break;
default:;
}
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);
+ }
}
}
FLUSH_POPS();
bce->stackDepth = depth;
return JS_TRUE;
#undef FLUSH_POPS
@@ -1923,41 +1946,48 @@ AdjustBlockSlot(JSContext *cx, BytecodeE
JSMSG_TOO_MANY_LOCALS);
slot = -1;
}
}
return 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);
for (uintN slot = base, limit = base + OBJ_BLOCK_COUNT(cx, blockObj); slot < limit; slot++) {
const Value &v = blockObj->getSlot(slot);
/* Beware the empty destructuring dummy. */
if (v.isUndefined()) {
JS_ASSERT(slot + 1 <= limit);
continue;
}
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);
JS_ASSERT(!(pnu->pn_dflags & PND_BOUND));
JS_ASSERT(pnu->pn_cookie.isFree());
}
#endif
@@ -3173,63 +3203,52 @@ EmitSwitch(JSContext *cx, BytecodeEmitte
ptrdiff_t top, off, defaultOffset;
ParseNode *pn2, *pn3, *pn4;
uint32_t caseCount, tableLength;
ParseNode **table;
int32_t i, low, high;
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. */
switchOp = JSOP_TABLESWITCH;
ok = JS_TRUE;
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 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)) {
- /*
- * 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);
+ 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;
-
- /* Emit JSOP_ENTERBLOCK before code to evaluate the discriminant. */
- if (!EmitEnterBlock(cx, pn2, bce))
+ if (!EmitEnterBlock(cx, bce, pn2, JSOP_ENTERLET1))
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 (!EmitTree(cx, bce, pn->pn_left))
- return JS_FALSE;
/* Switch bytecodes run from here till end of final case. */
top = bce->offset();
#if !JS_HAS_BLOCK_SCOPE
PushStatement(bce, &stmtInfo, STMT_SWITCH, top);
#else
if (pn2->isKind(PNK_STATEMENTLIST)) {
PushStatement(bce, &stmtInfo, STMT_SWITCH, top);
@@ -3681,17 +3700,17 @@ EmitSwitch(JSContext *cx, BytecodeEmitte
out:
if (table)
cx->free_(table);
if (ok) {
ok = PopStatementBCE(cx, bce);
#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;
bad:
ok = JS_FALSE;
goto out;
}
@@ -3767,16 +3786,31 @@ MaybeEmitVarDecl(JSContext *cx, Bytecode
return false;
}
if (result)
*result = atomIndex;
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
(*DestructuringDeclEmitter)(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn);
static JSBool
EmitDestructuringDecl(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn)
{
@@ -3812,33 +3846,66 @@ EmitDestructuringDecls(JSContext *cx, By
if (!emitter(cx, bce, prologOp, pn3))
return JS_FALSE;
}
}
return JS_TRUE;
}
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
* pop the matched value. Otherwise emit an lvalue bytecode sequence
* ending with a JSOP_ENUMELEM or equivalent op.
*/
if (pn->isKind(PNK_RB) || pn->isKind(PNK_RC)) {
- if (!EmitDestructuringOpsHelper(cx, bce, pn))
+ if (!EmitDestructuringOpsHelper(cx, bce, pn, emitOption))
return JS_FALSE;
- if (Emit1(cx, bce, JSOP_POP) < 0)
- 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;
if (pn->isConst() && !pn->isInitialized())
return Emit1(cx, bce, JSOP_POP) >= 0;
}
switch (pn->getOp()) {
@@ -3893,39 +3960,48 @@ EmitDestructuringLHS(JSContext *cx, Byte
}
}
return JS_TRUE;
}
/*
* 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;
#ifdef DEBUG
intN stackDepth = bce->stackDepth;
JS_ASSERT(stackDepth != 0);
JS_ASSERT(pn->isArity(PN_LIST));
JS_ASSERT(pn->isKind(PNK_RB) || pn->isKind(PNK_RC));
#endif
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;
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
/*
* Duplicate the value being destructured to use as a reference base.
* If dup is not the first one, annotate it for the decompiler.
*/
@@ -3971,34 +4047,67 @@ EmitDestructuringOpsHelper(JSContext *cx
if (doElemOp) {
/*
* Ok, get the value of the matching property name. This leaves
* that value on top of the value being destructured, so the stack
* is one deeper than when we started.
*/
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. */
if (pn3->isKind(PNK_COMMA) && pn3->isArity(PN_NULLARY)) {
JS_ASSERT(pn->isKind(PNK_RB));
JS_ASSERT(pn2 == pn3);
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;
- }
-
- JS_ASSERT(bce->stackDepth == stackDepth);
+
+ 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;
+ }
+ }
+ }
+
++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;
}
static ptrdiff_t
OpToDeclType(JSOp op)
{
switch (op) {
case JSOP_NOP:
@@ -4007,33 +4116,106 @@ OpToDeclType(JSOp op)
return SRC_DECL_CONST;
case JSOP_DEFVAR:
return SRC_DECL_VAR;
default:
return SRC_DECL_NONE;
}
}
+/*
+ * 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<Pair> notes;
+ bool groupAssign;
+ DebugOnly<bool> 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
* annotating the first JSOP_DUP that EmitDestructuringOpsHelper emits.
* 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
EmitGroupAssignment(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp,
ParseNode *lhs, ParseNode *rhs)
{
jsuint depth, limit, i, nslots;
ParseNode *pn;
@@ -4064,17 +4246,17 @@ EmitGroupAssignment(JSContext *cx, Bytec
if (slot < 0)
return JS_FALSE;
EMIT_UINT16_IMM_OP(JSOP_GETLOCAL, slot);
if (pn->isKind(PNK_COMMA) && pn->isArity(PN_NULLARY)) {
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;
}
}
nslots = limit - depth;
EMIT_UINT16_IMM_OP(JSOP_POPN, nslots);
bce->stackDepth = (uintN) depth;
return JS_TRUE;
@@ -4100,80 +4282,101 @@ MaybeEmitGroupAssignment(JSContext *cx,
lhs->pn_count <= rhs->pn_count) {
if (!EmitGroupAssignment(cx, bce, prologOp, lhs, rhs))
return JS_FALSE;
*pop = JSOP_NOP;
}
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)
-{
- bool forInVar, first;
- ptrdiff_t off, noteIndex, tmp;
- ParseNode *pn2, *pn3, *next;
- JSOp op;
- jsatomid atomIndex;
- uintN oldflags;
-
- /* 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<bool> 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;
+EmitVariables(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmitOption emitOption,
+ LetNotes *letNotes = NULL)
+{
+ JS_ASSERT(pn->isArity(PN_LIST));
+ JS_ASSERT(!!letNotes == (emitOption == PushInitialValues));
+
+ 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)) {
/*
* Emit variable binding ops, but not destructuring ops. The
* parser (see Parser::variables) has ensured that our caller
* will be the PNK_FOR/PNK_FORIN case in EmitTree, and that
* case will emit the destructuring code only after emitting an
* 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;
break;
}
#endif
/*
* A destructuring initialiser assignment preceded by var will
* never occur to the left of 'in' in a for-in loop. As with 'for
* (var x = i in o)...', this will cause the entire 'var [a, b] =
* i' to be hoisted out of the loop.
*/
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
* function f(){} precedes the var, detect simple name assignment
* here and initialize the name.
*/
#if !JS_HAS_DESTRUCTURING
JS_ASSERT(pn2->pn_left->isKind(PNK_NAME));
@@ -4182,52 +4385,66 @@ EmitVariables(JSContext *cx, BytecodeEmi
#endif
{
pn3 = pn2->pn_right;
pn2 = pn2->pn_left;
goto do_name;
}
#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,
* try to optimize to a group assignment. If we're in a let
* head, pass JSOP_POP rather than the pseudo-prolog JSOP_NOP
* 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;
}
}
-
- pn3 = pn2->pn_left;
- if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn3))
- return JS_FALSE;
-
- if (!EmitTree(cx, bce, pn2->pn_right))
- return JS_FALSE;
-
- /*
- * 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 (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;
+
+ 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;
+
+ 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
}
/*
* Load initializer early to share code above that jumps to do_name.
* NB: if this var redeclares an existing binding, then pn2 is linked
@@ -4235,114 +4452,103 @@ EmitVariables(JSContext *cx, BytecodeEmi
* pn_lexdef.
*/
pn3 = pn2->maybeExpr();
do_name:
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.
- */
+ /* If we are not initializing, nothing to pop. */
+ if (emitOption != InitializeVars) {
+ if (next)
+ continue;
+ break;
+ }
+
JS_ASSERT_IF(pn2->isDefn(), pn3 == pn2->pn_expr);
- if (forInVar) {
- JS_ASSERT(pn->pn_count == 1);
- JS_ASSERT(!pn3);
- 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)
- {
+ 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) {
if (Emit1(cx, bce, op) < 0)
return JS_FALSE;
} else if (!pn2->pn_cookie.isFree()) {
EMIT_UINT16_IMM_OP(op, atomIndex);
} else {
EMIT_INDEX_OP(op, atomIndex);
}
#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;
}
if (!next)
break;
off = tmp;
noteIndex = NewSrcNote2(cx, bce, SRC_PCDELTA, 0);
if (noteIndex < 0 || Emit1(cx, bce, JSOP_POP) < 0)
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
EmitAssignment(JSContext *cx, BytecodeEmitter *bce, ParseNode *lhs, JSOp op, ParseNode *rhs)
{
ptrdiff_t top = bce->offset();
/*
@@ -4513,34 +4719,34 @@ EmitAssignment(JSContext *cx, BytecodeEm
case PNK_LB:
case PNK_LP:
if (Emit1(cx, bce, JSOP_SETELEM) < 0)
return false;
break;
#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
#if JS_HAS_XML_SUPPORT
case PNK_XMLUNARY:
JS_ASSERT(!bce->inStrictMode());
if (Emit1(cx, bce, JSOP_SETXMLNAME) < 0)
return false;
break;
#endif
default:
JS_ASSERT(0);
}
return true;
}
-#if defined DEBUG_brendan || defined DEBUG_mrbkap
+#ifdef DEBUG
static JSBool
GettableNoteForNextOp(BytecodeEmitter *bce)
{
ptrdiff_t offset, target;
jssrcnote *sn, *end;
offset = 0;
target = bce->offset();
@@ -4731,17 +4937,17 @@ EmitCatch(JSContext *cx, BytecodeEmitter
if (pn->pn_kid2 && Emit1(cx, bce, JSOP_DUP) < 0)
return false;
ParseNode *pn2 = pn->pn_kid1;
switch (pn2->getKind()) {
#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;
break;
#endif
case PNK_NAME:
/* Inline and specialize BindNameToSlot for pn2. */
@@ -5101,53 +5307,119 @@ EmitIf(JSContext *cx, BytecodeEmitter *b
} else {
/* No else part, fixup the branch-if-false to come here. */
CHECK_AND_SET_JUMP_OFFSET_AT(cx, bce, beq);
}
return PopStatementBCE(cx, bce);
}
#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)
-{
- /*
- * 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;
- }
-
- /* 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();
-
- /* Thus non-null pn2 is the body of the let block or expression. */
- if (pn2 && !EmitTree(cx, bce, pn2))
- return false;
-
- if (noteIndex >= 0 && !SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 0, bce->offset() - tmp))
- return false;
-
- return true;
+EmitLet(JSContext *cx, BytecodeEmitter *bce, ParseNode *pnLet)
+{
+ 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;
+ }
+
+ StmtInfo stmtInfo;
+ PushBlockScope(bce, &stmtInfo, blockObj, bce->offset());
+
+ if (!letNotes.update(cx, bce, bce->offset()))
+ return false;
+
+ ptrdiff_t declNote = NewSrcNote(cx, bce, SRC_DECL);
+ if (declNote < 0)
+ return false;
+
+ 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
#if JS_HAS_XML_SUPPORT
static bool
EmitXMLTag(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
JS_ASSERT(!bce->inStrictMode());
@@ -5219,66 +5491,62 @@ EmitXMLProcessingInstruction(JSContext *
return false;
return true;
}
#endif
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))
- return false;
- }
-
- /* Emit the JSOP_LEAVEBLOCK or JSOP_LEAVEBLOCKEXPR opcode. */
- uintN count = OBJ_BLOCK_COUNT(cx, objbox->object);
- EMIT_UINT16_IMM_OP(op, count);
+ if (noteIndex >= 0) {
+ if (!SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 0, bce->offset() - bodyBegin))
+ return false;
+ }
+
+ EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, OBJ_BLOCK_COUNT(cx, blockObj));
return PopStatementBCE(cx, bce);
}
static bool
EmitWith(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
StmtInfo stmtInfo;
@@ -5333,27 +5601,58 @@ static bool
EmitForIn(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
{
StmtInfo stmtInfo;
PushStatement(bce, &stmtInfo, STMT_FOR_IN_LOOP, 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;
}
/* Compile the object expression to the right of 'in'. */
if (!EmitTree(cx, bce, forHead->pn_kid3))
return JS_FALSE;
@@ -5361,16 +5660,25 @@ EmitForIn(JSContext *cx, BytecodeEmitter
* Emit a bytecode to convert top of stack value to the iterator
* object depending on the loop variant (for-in, for-each-in, or
* destructuring for-in).
*/
JS_ASSERT(pn->isOp(JSOP_ITER));
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)
return false;
/*
* Jump down to the loop condition to minimize overhead assuming at
* least one iteration, as the other loop forms do.
@@ -5391,16 +5699,17 @@ EmitForIn(JSContext *cx, BytecodeEmitter
/*
* Emit code to get the next enumeration value and assign it to the
* left hand side. The JSOP_POP after this assignment is annotated
* so that the decompiler can distinguish 'for (x in y)' from
* 'for (var x in y)'.
*/
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))
? SRC_DECL_VAR
: SRC_DECL_LET) < 0) {
return false;
}
if (Emit1(cx, bce, JSOP_POP) < 0)
@@ -5431,24 +5740,40 @@ EmitForIn(JSContext *cx, BytecodeEmitter
/* Set the first srcnote offset so we can find the start of the loop body. */
if (!SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 0, tmp2 - jmp))
return false;
/* Set the second srcnote offset so we can find the closing jump. */
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;
-
- return Emit1(cx, bce, JSOP_ENDITER) >= 0;
+ if (Emit1(cx, bce, JSOP_ENDITER) < 0)
+ return false;
+
+ 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
EmitNormalFor(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
{
StmtInfo stmtInfo;
PushStatement(bce, &stmtInfo, STMT_FOR_LOOP, top);
@@ -5475,17 +5800,17 @@ EmitNormalFor(JSContext *cx, BytecodeEmi
return false;
if (pn3->isKind(PNK_VAR) || pn3->isKind(PNK_CONST) || pn3->isKind(PNK_LET)) {
/*
* Check whether a destructuring-initialized var decl
* was optimized to a group assignment. If so, we do
* 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;
}
}
bce->flags &= ~TCF_IN_FOR_INIT;
}
/*
@@ -6711,28 +7036,23 @@ EmitUnary(JSContext *cx, BytecodeEmitter
bce->flags |= oldflags & TCF_IN_FOR_INIT;
return Emit1(cx, bce, op) >= 0;
}
JSBool
frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
- ptrdiff_t top, off, tmp, jmp;
- ParseNode *pn2;
- JSAtom *atom;
- ptrdiff_t noteIndex;
- JSOp op;
+ JS_CHECK_RECURSION(cx, return JS_FALSE);
+
EmitLevelManager elm(bce);
- jsint sharpnum = -1;
-
- JS_CHECK_RECURSION(cx, return JS_FALSE);
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);
switch (pn->getKind()) {
case PNK_FUNCTION:
ok = EmitFunc(cx, bce, pn);
break;
@@ -6801,17 +7121,17 @@ frontend::EmitTree(JSContext *cx, Byteco
case PNK_CATCH:
if (!EmitCatch(cx, bce, pn))
return false;
break;
case PNK_VAR:
case PNK_CONST:
- if (!EmitVariables(cx, bce, pn, JS_FALSE, ¬eIndex))
+ if (!EmitVariables(cx, bce, pn, InitializeVars))
return JS_FALSE;
break;
case PNK_RETURN:
ok = EmitReturn(cx, bce, pn);
break;
#if JS_HAS_GENERATORS
@@ -6853,40 +7173,42 @@ frontend::EmitTree(JSContext *cx, Byteco
ok = EmitStatement(cx, bce, pn);
break;
case PNK_COLON:
ok = EmitLabel(cx, bce, 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;
}
if (!pn2->pn_next)
break;
off = tmp;
noteIndex = NewSrcNote2(cx, bce, SRC_PCDELTA, 0);
if (noteIndex < 0 ||
Emit1(cx, bce, JSOP_POP) < 0) {
return JS_FALSE;
}
}
break;
+ }
case PNK_ASSIGN:
case PNK_ADDASSIGN:
case PNK_SUBASSIGN:
case PNK_BITORASSIGN:
case PNK_BITXORASSIGN:
case PNK_BITANDASSIGN:
case PNK_LSHASSIGN:
@@ -6926,20 +7248,20 @@ frontend::EmitTree(JSContext *cx, Byteco
case PNK_LSH:
case PNK_RSH:
case PNK_URSH:
case PNK_STAR:
case PNK_DIV:
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;
if (Emit1(cx, bce, op) < 0)
return JS_FALSE;
}
} else {
#if JS_HAS_XML_SUPPORT
@@ -7018,32 +7340,34 @@ frontend::EmitTree(JSContext *cx, Byteco
break;
case PNK_DELETE:
ok = EmitDelete(cx, bce, pn);
break;
#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);
if (top < 0)
return JS_FALSE;
if (!EmitTree(cx, bce, pn->pn_right))
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, bce, jmp);
if (EmitJump(cx, bce, JSOP_ENDFILTER, top - bce->offset()) < 0)
return JS_FALSE;
break;
+ }
#endif
case PNK_DOT:
/*
* Pop a stack operand, convert it to object, get a property named by
* this bytecode's immediate-indexed atom operand, and push its value
* (not a reference to it).
*/
@@ -7071,18 +7395,19 @@ frontend::EmitTree(JSContext *cx, Byteco
break;
case PNK_LEXICALSCOPE:
ok = EmitLexicalScope(cx, bce, pn);
break;
#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
case PNK_ARRAYPUSH: {
jsint slot;
/*
* The array object's stack index is in bce->arrayCompDepth. See below
@@ -7107,18 +7432,19 @@ frontend::EmitTree(JSContext *cx, Byteco
break;
case PNK_RC:
ok = EmitObject(cx, bce, pn, -1);
break;
#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);
break;
}
# if JS_HAS_GENERATORS
if (pn->isKind(PNK_ARRAYCOMP)) {
ok = EmitArray(cx, bce, pn, sharpnum);
@@ -7129,16 +7455,17 @@ frontend::EmitTree(JSContext *cx, Byteco
ok = EmitObject(cx, bce, pn, sharpnum);
break;
}
if (!EmitTree(cx, bce, pn))
return JS_FALSE;
EMIT_UINT16PAIR_IMM_OP(JSOP_DEFSHARP, bce->sharpSlotBase, (jsatomid) sharpnum);
break;
+ }
case PNK_USESHARP:
JS_ASSERT(bce->hasSharps());
EMIT_UINT16PAIR_IMM_OP(JSOP_USESHARP, bce->sharpSlotBase, (jsatomid) pn->pn_num);
break;
#endif /* JS_HAS_SHARP_VARS */
case PNK_NAME:
@@ -7204,29 +7531,29 @@ frontend::EmitTree(JSContext *cx, Byteco
case PNK_XMLPTAGC:
case PNK_XMLSTAGO:
break;
default:
if (Emit1(cx, bce, JSOP_STARTXML) < 0)
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))
return JS_FALSE;
if (pn2 != pn->pn_head && Emit1(cx, bce, JSOP_ADD) < 0)
return JS_FALSE;
}
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;
EMIT_INDEX_OP(JSOP_STRING, index);
}
if (Emit1(cx, bce, pn->getOp()) < 0)
return JS_FALSE;
}
@@ -7243,17 +7570,17 @@ frontend::EmitTree(JSContext *cx, Byteco
return false;
break;
case PNK_XMLNAME:
JS_ASSERT(!bce->inStrictMode());
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))
return JS_FALSE;
if (pn2 != pn->pn_head && Emit1(cx, bce, JSOP_ADD) < 0)
return JS_FALSE;
}
} else {
@@ -7442,32 +7769,36 @@ frontend::AddToSrcNoteDelta(JSContext *c
}
static JSBool
SetSrcNoteOffset(JSContext *cx, BytecodeEmitter *bce, uintN index, uintN which, ptrdiff_t offset)
{
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;
}
/* Find the offset numbered which (i.e., skip exactly which offsets). */
sn = &bce->notes()[index];
JS_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
JS_ASSERT((intN) which < js_SrcNoteSpec[SN_TYPE(sn)].arity);
for (sn++; which; sn++, which--) {
if (*sn & SN_3BYTE_OFFSET_FLAG)
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. */
index = sn - bce->notes();
/*
* Test to see if the source note array must grow to accommodate
* either the first or second byte of additional storage required
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -937,26 +937,29 @@ enum SrcNoteType {
SRC_IF_ELSE = 2, /* JSOP_IFEQ bytecode is from an if-then-else */
SRC_FOR_IN = 2, /* JSOP_GOTO to for-in loop condition from
before loop (same arity as SRC_IF_ELSE) */
SRC_FOR = 3, /* JSOP_NOP or JSOP_POP in for(;;) loop head */
SRC_WHILE = 4, /* JSOP_GOTO to for or while loop condition
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 */
SRC_PCDELTA = 7, /* distance forward from comma-operator to
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
dangling else */
SRC_HIDDEN = 11, /* opcode shouldn't be decompiled */
SRC_PCBASE = 12, /* distance back from annotated getprop or
setprop op to left-most obj.prop.subprop
bytecode -- always a backward delta */
@@ -1025,16 +1028,18 @@ enum SrcNoteType {
/*
* Offset fields follow certain notes and are frequency-encoded: an offset in
* [0,0x7f] consumes one byte, an offset in [0x80,0x7fffff] takes three, and
* the high bit of the first byte is set.
*/
#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))
/* A source note array is terminated by an all-zero element. */
#define SN_MAKE_TERMINATOR(sn) (*(sn) = SRC_NULL)
#define SN_IS_TERMINATOR(sn) (*(sn) == SRC_NULL)
@@ -1097,16 +1102,38 @@ BytecodeEmitter::countFinalSourceNotes()
: SN_DELTA_MASK - (*sn & SN_DELTA_MASK);
}
if (diff > 0)
cnt += JS_HOWMANY(diff, SN_XDELTA_MASK);
}
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 {
const char *name; /* name for disassembly/debugging output */
int8_t arity; /* number of offset operands */
uint8_t offsetBias; /* bias of offset(s) from annotated pc */
int8_t isSpanDep; /* 1 or -1 if offsets could span extended ops,
0 otherwise; sign tells span direction */
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -840,16 +840,21 @@ struct ParseNode {
JSString *str = pn_atom;
return (pn_pos.begin.lineno == pn_pos.end.lineno &&
pn_pos.begin.index + str->length() + 2 == pn_pos.end.index);
}
/* 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.
*/
bool isGeneratorExpr() const {
if (getKind() == PNK_LP) {
ParseNode *callee = this->pn_head;
if (callee->getKind() == PNK_FUNCTION) {
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -832,29 +832,51 @@ js::DefineArg(ParseNode *pn, JSAtom *ato
* helper function signature in order to share code among destructuring and
* simple variable declaration parsers. In the destructuring case, the binder
* function is called indirectly from the variable declaration parser by way
* of CheckDestructuring and its friends.
*/
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) {}
ParseNode *pn; /* name node for definition processing and
error source coordinates */
JSOp op; /* prolog bytecode or nop */
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
BindLocalVariable(JSContext *cx, TreeContext *tc, ParseNode *pn, BindingKind kind)
{
JS_ASSERT(kind == VARIABLE || kind == CONSTANT);
/* 'arguments' can be bound as a local only via a destructuring formal parameter. */
@@ -1872,118 +1894,129 @@ MatchLabel(JSContext *cx, TokenStream *t
(void) ts->getToken();
*label = ts->currentToken().name();
} else {
*label = NULL;
}
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.
*
* Throw a SyntaxError if 'atom' is an invalid name. Otherwise create a
* property for the new variable on the block object, tc->blockChain;
* populate data->pn->pn_{op,cookie,defn,dflags}; and stash a pointer to
* data->pn in a slot of the block object.
*/
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
* upvar cookie whose skip tells the current static level. The emitter will
* adjust the node's slot based on its stack depth model -- and, for global
* and eval code, js::frontend::CompileScript will adjust the slot
* 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 <class Op>
+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);
}
static inline bool
OuterLet(TreeContext *tc, StmtInfo *stmt, JSAtom *atom)
{
while (stmt->downScope) {
@@ -2531,34 +2564,41 @@ BindDestructuringLHS(JSContext *cx, Pars
* CheckDestructuring, we require the pattern's property value
* positions to be simple names, and define them as appropriate to the
* context. For these calls, |data| points to the right sort of
* BindData.
*
* 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;
if (left->isKind(PNK_ARRAYCOMP)) {
ReportCompileErrorNumber(cx, TS(tc->parser), left, JSREPORT_ERROR,
JSMSG_ARRAY_COMP_LEFTSIDE);
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)) {
ReportCompileErrorNumber(cx, TS(tc->parser), pn, JSREPORT_ERROR,
JSMSG_NO_VARIABLE_NAME);
return false;
}
ok = BindDestructuringVar(cx, data, pn, tc);
@@ -2572,17 +2612,17 @@ CheckDestructuring(JSContext *cx, BindDa
}
} else {
JS_ASSERT(left->isKind(PNK_RC));
for (ParseNode *pair = left->pn_head; pair; pair = pair->pn_next) {
JS_ASSERT(pair->isKind(PNK_COLON));
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,
JSMSG_NO_VARIABLE_NAME);
return false;
}
ok = BindDestructuringVar(cx, data, pn, tc);
} else {
@@ -2598,33 +2638,38 @@ CheckDestructuring(JSContext *cx, BindDa
* that any operation that introduces a new scope (like a "let" or "with"
* block) increases the stack depth. This way, it is possible to restore
* the scope chain based on stack depth of the handler alone. "let" with
* an empty destructuring pattern like in
*
* 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.
+ *
+ * Furthermore, the decompiler needs an abstract stack location to store
+ * the decompilation of each let block/expr initializer. E.g., given:
*
- * 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 {}.
+ * 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;
}
/*
* Extend the pn_pos.end source coordinate of each name in a destructuring
* binding such as
@@ -2759,71 +2804,116 @@ Parser::returnOrYield(bool useAssignExpr
JSMSG_ANON_NO_RETURN_VALUE)) {
return NULL;
}
return pn;
}
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;
PushBlockScope(tc, stmt, obj, -1);
pn->setOp(JSOP_LEAVEBLOCK);
pn->pn_objbox = blockbox;
pn->pn_cookie.makeFree();
pn->pn_dflags = 0;
if (!GenerateBlockId(tc, stmt->blockid))
return NULL;
pn->pn_blockid = stmt->blockid;
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
* by an arguments list, it's ambiguous whether the let expression
* is the callee or the call is inside the let expression body.
*
* See bug 569464.
*/
@@ -2832,92 +2922,101 @@ Parser::letBlock(JSBool statement)
return NULL;
}
/*
* If this is really an expression in let statement guise, then we
* 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 */
static bool
PushBlocklikeStatement(StmtInfo *stmt, StmtType type, TreeContext *tc)
{
PushStatement(tc, stmt, type, -1);
return GenerateBlockId(tc, stmt->blockid);
}
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 ((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());
-
- /*
- * 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 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) {
+ 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);
+
+ 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;
if (atom == tc->parser->context->runtime->atomState.argumentsAtom)
tc->countArgumentsUse(pn);
return pn;
}
@@ -3038,50 +3137,44 @@ Parser::switchStatement()
return pn;
}
ParseNode *
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;
if (tokenStream.matchToken(TOK_NAME)) {
if (tokenStream.currentToken().name() == context->runtime->atomState.eachAtom)
pn->pn_iflags = JSITER_FOREACH;
else
tokenStream.ungetToken();
}
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;
{
TokenKind tt = tokenStream.peekToken(TSF_OPERAND);
if (tt == TOK_SEMI) {
if (pn->pn_iflags & JSITER_FOREACH) {
reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP);
@@ -3102,61 +3195,68 @@ Parser::forStatement()
* A side effect of this restriction is that (unparenthesized)
* expressions involving an 'in' operator are illegal in the init
* clause of an ordinary for loop.
*/
tc->flags |= TCF_IN_FOR_INIT;
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
else {
pn1 = expr();
}
tc->flags &= ~TCF_IN_FOR_INIT;
if (!pn1)
return NULL;
}
}
+ 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.
*
* Here pn1 is everything to the left of 'in'. At the end of this block,
* pn1 is a decl or NULL, pn2 is the assignment target that receives the
* 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
? (pn1->pn_count > 1 || pn1->isOp(JSOP_DEFCONST)
#if JS_HAS_DESTRUCTURING
|| (versionNumber() == JSVERSION_1_7 &&
pn->isOp(JSOP_ITER) &&
!(pn->pn_iflags & JSITER_FOREACH) &&
@@ -3209,67 +3309,92 @@ Parser::forStatement()
/*
* Declaration with initializer.
*
* Rewrite 'for (<decl> x = i in o)' where <decl> is 'var' or
* 'const' to hoist the initializer or the entire decl out of
* 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;
/*
* All of 'var x = i' is hoisted above 'for (x in o)',
* so clear PNX_FORINVAR.
*
* Request JSOP_POP here since the var is for a simple
* name (it is not a destructuring binding's left-hand
* side) and it has an initializer.
*/
pn1->pn_xflags &= ~PNX_FORINVAR;
pn1->pn_xflags |= PNX_POPVAR;
pnseq->initList(pn1);
+ pn1 = NULL;
#if JS_HAS_DESTRUCTURING
if (pn2->isKind(PNK_ASSIGN)) {
pn2 = pn2->pn_left;
JS_ASSERT(pn2->isKind(PNK_RB) || pn2->isKind(PNK_RC) ||
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;
if (!setAssignmentLhsOps(pn2, JSOP_NOP))
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'. */
NoteLValue(context, pn2, tc, dflag);
break;
#if JS_HAS_DESTRUCTURING
case PNK_ASSIGN:
@@ -3288,102 +3413,99 @@ Parser::forStatement()
pn->pn_iflags |= JSITER_FOREACH | JSITER_KEYVALUE;
}
break;
#endif
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_<BinaryNode>(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;
}
pn->setOp(JSOP_NOP);
/* 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();
if (!pn2)
return NULL;
}
/* 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();
if (!pn3)
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 *
Parser::tryStatement()
{
JS_ASSERT(tokenStream.isCurrentTokenType(TOK_TRY));
/*
@@ -3437,17 +3559,17 @@ Parser::tryStatement()
reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_CATCH_AFTER_GENERAL);
return NULL;
}
/*
* 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;
/*
* Legal catch forms are:
* catch (lhs)
* catch (lhs if <boolean_expression>)
@@ -3460,37 +3582,35 @@ Parser::tryStatement()
pnblock->pn_expr = pn2;
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH);
/*
* Contrary to ECMA Ed. 3, the catch variable is lexically
* 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;
switch (tt) {
#if JS_HAS_DESTRUCTURING
case TOK_LB:
case TOK_LC:
pn3 = destructuringExpr(&data, tt);
if (!pn3)
return NULL;
break;
#endif
case TOK_NAME:
{
JSAtom *label = tokenStream.currentToken().name();
- pn3 = NewBindingNode(label, tc, true);
+ pn3 = NewBindingNode(label, tc);
if (!pn3)
return NULL;
data.pn = pn3;
if (!data.binder(context, &data, label, tc))
return NULL;
break;
}
@@ -3606,22 +3726,26 @@ Parser::withStatement()
#if JS_HAS_BLOCK_SCOPE
ParseNode *
Parser::letStatement()
{
ParseNode *pn;
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;
}
/*
* This is a let declaration. We must be directly under a block per the
* proposed ES4 specs, but not an implicit block created due to
* 'for (let ...)'. If we pass this error test, make the enclosing
* StmtInfo be our scope. Further let declarations in this block will
@@ -3641,17 +3765,17 @@ Parser::letStatement()
if (stmt && (stmt->flags & SIF_SCOPE)) {
JS_ASSERT(tc->blockChain == stmt->blockObj);
} else {
if (!stmt || (stmt->flags & SIF_BODY_BLOCK)) {
/*
* 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;
break;
}
/*
* Some obvious assertions here, but they may help clarify the
@@ -3702,17 +3826,17 @@ Parser::letStatement()
pn1->setOp(JSOP_LEAVEBLOCK);
pn1->pn_pos = tc->blockNode->pn_pos;
pn1->pn_objbox = blockbox;
pn1->pn_expr = tc->blockNode;
pn1->pn_blockid = tc->blockNode->pn_blockid;
tc->blockNode = pn1;
}
- pn = variables(PNK_LET, false);
+ pn = variables(PNK_LET, tc->blockChain, HoistVars);
if (!pn)
return NULL;
pn->pn_xflags = PNX_POPVAR;
} while (0);
/* Check termination of this primitive statement. */
return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL;
}
@@ -4034,26 +4158,26 @@ Parser::statement()
}
break;
}
case TOK_WITH:
return withStatement();
case TOK_VAR:
- pn = variables(PNK_VAR, false);
+ pn = variables(PNK_VAR);
if (!pn)
return NULL;
/* Tell js_EmitTree to generate a final POP. */
pn->pn_xflags |= PNX_POPVAR;
break;
case TOK_CONST:
- pn = variables(PNK_CONST, false);
+ pn = variables(PNK_CONST);
if (!pn)
return NULL;
/* Tell js_EmitTree to generate a final POP. */
pn->pn_xflags |= PNX_POPVAR;
break;
#if JS_HAS_BLOCK_SCOPE
@@ -4147,65 +4271,50 @@ Parser::statement()
default:
return expressionStatement();
}
/* Check termination of this primitive 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:
* - PNK_VAR: We're parsing var declarations.
* - PNK_CONST: We're parsing const declarations.
* - PNK_LET: We are parsing a let declaration.
* - PNK_LP: We are parsing the head of a let block.
*/
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();
/*
* SpiderMonkey const is really "write once per initialization evaluation"
* 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 {
TokenKind tt = tokenStream.getToken();
#if JS_HAS_DESTRUCTURING
if (tt == TOK_LB || tt == TOK_LC) {
tc->flags |= TCF_DECL_DESTRUCTURING;
pn2 = primaryExpr(tt, JS_FALSE);
@@ -4218,30 +4327,17 @@ Parser::variables(ParseNodeKind kind, bo
if ((tc->flags & TCF_IN_FOR_INIT) && tokenStream.peekToken() == TOK_IN) {
pn->append(pn2);
continue;
}
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);
pn2 = ParseNode::newBinaryOrAppend(PNK_ASSIGN, JSOP_NOP, pn2, init, tc);
if (!pn2)
return NULL;
pn->append(pn2);
@@ -4251,42 +4347,30 @@ Parser::variables(ParseNodeKind kind, bo
if (tt != TOK_NAME) {
if (tt != TOK_ERROR)
reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NO_VARIABLE_NAME);
return NULL;
}
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)
pn2->pn_dflags |= PND_CONST;
data.pn = pn2;
if (!data.binder(context, &data, name, tc))
return NULL;
pn->append(pn2);
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;
if (pn2->isUsed()) {
pn2 = MakeAssignment(pn2, init, tc);
if (!pn2)
return NULL;
} else {
@@ -4305,17 +4389,17 @@ Parser::variables(ParseNodeKind kind, bo
NoteLValue(context, pn2, tc, data.fresh ? PND_INITIALIZED : PND_ASSIGNED);
/* The declarator's position must include the initializer. */
pn2->pn_pos.end = init->pn_pos.end;
if (tc->inFunction() && name == context->runtime->atomState.argumentsAtom) {
tc->noteArgumentsUse(pn2);
- if (!let)
+ if (!blockObj)
tc->flags |= TCF_FUN_HEAVYWEIGHT;
}
}
} while (tokenStream.matchToken(TOK_COMMA));
pn->pn_pos.end = pn->last()->pn_pos.end;
return pn;
}
@@ -5249,17 +5333,17 @@ Parser::comprehensionTail(ParseNode *kid
JS_ASSERT(tokenStream.currentToken().type == TOK_FOR);
if (kind == PNK_SEMI) {
/*
* Generator expression desugars to an immediately applied lambda that
* 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;
} else {
JS_ASSERT(kind == PNK_ARRAYPUSH);
/*
* Make a parse-node and literal object representing the block scope of
@@ -5269,37 +5353,35 @@ Parser::comprehensionTail(ParseNode *kid
* here, by calling PushLexicalScope.
*
* In the case of a comprehension expression that has nested blocks
* (e.g., let expressions), we will allocate a higher blockid but then
* slide all blocks "to the right" to make room for the comprehension's
* block scope.
*/
adjust = tc->blockid();
- pn = PushLexicalScope(context, &tokenStream, tc, &stmtInfo);
+ pn = PushLexicalScope(context, tc, &stmtInfo);
if (!pn)
return NULL;
JS_ASSERT(blockid <= pn->pn_blockid);
JS_ASSERT(blockid < tc->blockidGen);
JS_ASSERT(tc->bodyid < blockid);
pn->pn_blockid = stmtInfo.blockid = blockid;
JS_ASSERT(adjust < blockid);
adjust = blockid - adjust;
}
pnp = &pn->pn_expr;
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 {
/*
* FOR node is binary, left is loop control and right is body. Use
* index to count each block-local let-variable on the left-hand side
* of the IN.
*/
pn2 = BinaryNode::create(PNK_FOR, tc);
@@ -5337,17 +5419,17 @@ Parser::comprehensionTail(ParseNode *kid
/*
* Create a name node with pn_op JSOP_NAME. We can't set pn_op to
* JSOP_GETLOCAL here, because we don't yet know the block's depth
* in the operand stack frame. The code generator computes that,
* 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;
default:
reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NO_VARIABLE_NAME);
case TOK_ERROR:
@@ -6490,50 +6572,16 @@ Parser::parseXMLText(JSObject *chain, bo
}
tokenStream.setXMLOnlyMode(false);
return pn;
}
#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)
{
ParseNode *pn = NullaryNode::create(kind, tc);
if (!pn)
return NULL;
pn->setOp(op);
return pn;
@@ -6886,17 +6934,17 @@ Parser::primaryExpr(TokenKind tt, JSBool
end_obj_init:
pn->pn_pos.end = tokenStream.currentToken().pos.end;
return pn;
}
#if JS_HAS_BLOCK_SCOPE
case TOK_LET:
- pn = letBlock(JS_FALSE);
+ pn = letBlock(LetExpresion);
if (!pn)
return NULL;
break;
#endif
#if JS_HAS_SHARP_VARS
case TOK_DEFSHARP:
pn = UnaryNode::create(PNK_DEFSHARP, tc);
@@ -7043,36 +7091,18 @@ Parser::primaryExpr(TokenKind tt, JSBool
if (!tc->inFunction() &&
pn->pn_atom == context->runtime->atomState.argumentsAtom) {
tc->countArgumentsUse(pn);
}
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 {
AtomDefnAddPtr p = tc->lexdeps->lookupForAdd(pn->pn_atom);
if (p) {
dn = p.value();
} else {
/*
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -56,16 +56,18 @@
#define NUM_TEMP_FREELISTS 6U /* 32 to 2048 byte size classes (32 bit) */
typedef struct BindData BindData;
namespace js {
enum FunctionSyntaxKind { Expression, Statement };
+enum LetContext { LetExpresion, LetStatement };
+enum VarContext { HoistVars, DontHoistVars };
struct Parser : private AutoGCRooter
{
JSContext *const context; /* FIXME Bug 551291: use AutoGCRooter::context? */
void *tempFreeList[NUM_TEMP_FREELISTS];
TokenStream tokenStream;
void *tempPoolMark; /* initial JSContext.tempLifoAlloc mark */
JSPrincipals *principals; /* principals associated with source */
@@ -191,17 +193,17 @@ struct Parser : private AutoGCRooter
ParseNode *switchStatement();
ParseNode *forStatement();
ParseNode *tryStatement();
ParseNode *withStatement();
#if JS_HAS_BLOCK_SCOPE
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();
ParseNode *orExpr1();
ParseNode *andExpr1i();
ParseNode *andExpr1n();
ParseNode *bitOrExpr1i();
ParseNode *bitOrExpr1n();
@@ -235,17 +237,17 @@ struct Parser : private AutoGCRooter
ParseNode *unaryOpExpr(ParseNodeKind kind, JSOp op);
ParseNode *condition();
ParseNode *comprehensionTail(ParseNode *kid, uintN blockid, bool isGenexp,
ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP);
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);
#if JS_HAS_XML_SUPPORT
ParseNode *endBracketedExpr();
ParseNode *propertySelector();
ParseNode *qualifiedSuffix(ParseNode *pn);
--- a/js/src/jit-test/tests/basic/bug657975.js
+++ b/js/src/jit-test/tests/basic/bug657975.js
@@ -55,17 +55,17 @@ for (a in f8())
(function() {})()
// bug 659043
f9 = (function() {
for (let a = 0; a < 0; ++a) {
for each(let w in []) {}
}
})
-trap(f9, 22, undefined);
+trap(f9, 23, undefined);
for (b in f9())
(function() {})()
// bug 659233
f10 = (function() {
while (h) {
continue
}
--- a/js/src/jit-test/tests/basic/testBug666292.js
+++ b/js/src/jit-test/tests/basic/testBug666292.js
@@ -1,14 +1,14 @@
// |jit-test| debug
function f(){
this.zzz.zzz;
for(let d in []);
}
-trap(f, 18, '')
+trap(f, 16, '')
try {
f()
} catch(e) {
caught = true;
assertEq(""+e, "TypeError: this.zzz is undefined");
}
assertEq(caught, true);
--- a/js/src/jit-test/tests/basic/testBug683470.js
+++ b/js/src/jit-test/tests/basic/testBug683470.js
@@ -6,10 +6,10 @@ f = (function() {
Object.defineProperty(this, "x", ({}));
}
for each(let d in [0, 0]) {
try {
b(d);
} catch (e) {}
}
})
-trap(f, 39, undefined);
+trap(f, 40, undefined);
f()
new file mode 100644
--- /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()
new file mode 100644
--- /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) {}
new file mode 100644
--- /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;
+}
new file mode 100644
--- /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] ];
new file mode 100644
--- /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();
new file mode 100644
--- /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 + '';
+}
new file mode 100644
--- /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'});
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -114,21 +114,21 @@ ScriptAnalysis::checkAliasedName(JSConte
* current script, and mark that local/arg as escaping. We don't need to
* worry about marking locals/arguments in scripts this is nested in, as
* the escaping name will be caught by the parser and the nested local/arg
* will be marked as closed.
*/
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;
BindingKind kind = script->bindings.lookup(cx, atom, &index);
if (kind == ARGUMENT)
escapedSlots[ArgSlot(index)] = true;
else if (kind == VARIABLE)
@@ -384,16 +384,18 @@ ScriptAnalysis::analyzeBytecode(JSContex
isInlineable = canTrackVars = false;
break;
case JSOP_ENTERWITH:
addsScopeObjects_ = true;
isInlineable = canTrackVars = false;
break;
+ case JSOP_ENTERLET0:
+ case JSOP_ENTERLET1:
case JSOP_ENTERBLOCK:
case JSOP_LEAVEBLOCK:
addsScopeObjects_ = true;
isInlineable = false;
break;
case JSOP_THIS:
usesThisValue_ = true;
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -198,19 +198,16 @@ class Bytecode
};
static inline unsigned
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.
*/
switch (JSOp(*pc)) {
case JSOP_OR:
case JSOP_ORX:
case JSOP_AND:
@@ -222,30 +219,30 @@ GetDefCount(JSScript *script, unsigned o
/*
* Pick pops and pushes how deep it looks in the stack + 1
* items. i.e. if the stack were |a b[2] c[1] d[0]|, pick 2
* would pop b, c, and d to rearrange the stack to |a c[0]
* d[1] b[2]|.
*/
return (pc[1] + 1);
default:
- return js_CodeSpec[*pc].ndefs;
+ return StackDefs(script, pc);
}
}
static inline unsigned
GetUseCount(JSScript *script, unsigned offset)
{
JS_ASSERT(offset < script->length);
jsbytecode *pc = script->code + 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;
}
/*
* For opcodes which assign to a local variable or argument, track an extra def
* during SSA analysis for the value's use chain and assigned type.
*/
static inline bool
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -2027,31 +2027,31 @@ TypeCompartment::newAllocationSiteTypeOb
}
return res;
}
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);
}
bool
types::UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc)
{
JS_ASSERT(cx->typeInferenceEnabled());
@@ -3951,23 +3951,34 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
}
state.hasGetSet = false;
JS_ASSERT(!state.hasHole);
break;
}
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
* script.
*/
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
* 'for in' or 'for each' loops in the script. We need to mark the
* value pushed by the ITERNEXT appropriately, but don't track the SSA
* information to connect that ITERNEXT with the appropriate ITER.
* This loses some precision when a script mixes 'for in' and
* 'for each' loops together, oh well.
@@ -4016,16 +4027,19 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
case JSOP_DELNAME:
pushed[0].addType(cx, Type::BooleanType());
break;
case JSOP_LEAVEBLOCKEXPR:
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]);
break;
case JSOP_GENERATOR:
if (script->function()) {
if (script->hasGlobal()) {
@@ -4549,17 +4563,17 @@ AnalyzeNewScriptProperties(JSContext *cx
JSObject *obj = *pbaseobj;
if (op == JSOP_SETPROP && uses->u.which == 1) {
/*
* Don't use GetAtomId here, we need to watch for SETPROP on
* 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;
if (id == id_prototype(cx) || id == id___proto__(cx) || id == id_constructor(cx))
return false;
/*
* Ensure that if the properties named here could have a setter or
@@ -5416,16 +5430,18 @@ IgnorePushed(const jsbytecode *pc, unsig
case JSOP_HOLE:
return (index == 0);
case JSOP_FILTER:
return (index == 1);
/* 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. */
case JSOP_ITER:
case JSOP_ITERNEXT:
case JSOP_MOREITER:
case JSOP_ENDITER:
return true;
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -1852,19 +1852,16 @@ js::Interpret(JSContext *cx, StackFrame
switchOp = intN(op);
goto do_switch;
#endif
}
/* 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
ADD_EMPTY_CASE(JSOP_STARTXML)
ADD_EMPTY_CASE(JSOP_STARTXMLEXPR)
#endif
ADD_EMPTY_CASE(JSOP_LOOPHEAD)
END_EMPTY_CASES
@@ -5104,87 +5101,103 @@ BEGIN_CASE(JSOP_GETFUNNS)
if (!cx->fp()->scopeChain().getGlobal()->getFunctionNamespace(cx, &rval))
goto error;
PUSH_COPY(rval);
}
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());
/*
* The young end of fp->scopeChain may omit blocks if we haven't closed
* over them, but if there are any closure blocks on fp->scopeChain, they'd
* better be (clones of) ancestors of the block we're entering now;
* anything else we should have popped off fp->scopeChain when we left its
* static scope.
*/
JSObject *obj2 = ®s.fp()->scopeChain();
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;
while ((parent = parent->scopeChain()) != youngestProto)
JS_ASSERT(parent);
}
#endif
regs.fp()->setBlockChain(obj);
}
END_CASE(JSOP_ENTERBLOCK)
+BEGIN_CASE(JSOP_LEAVEBLOCK)
+BEGIN_CASE(JSOP_LEAVEFORLETIN)
BEGIN_CASE(JSOP_LEAVEBLOCKEXPR)
-BEGIN_CASE(JSOP_LEAVEBLOCK)
{
-#ifdef DEBUG
JS_ASSERT(regs.fp()->blockChain().isStaticBlock());
- uintN blockDepth = OBJ_BLOCK_DEPTH(cx, ®s.fp()->blockChain());
+ DebugOnly<uintN> 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
* the stack into the clone, and pop it off the chain.
*/
JSObject &obj = regs.fp()->scopeChain();
if (obj.getProto() == ®s.fp()->blockChain()) {
JS_ASSERT(obj.isClonedBlock());
if (!js_PutBlockObject(cx, JS_TRUE))
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)
#if JS_HAS_GENERATORS
BEGIN_CASE(JSOP_GENERATOR)
{
JS_ASSERT(!cx->isExceptionPending());
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3673,30 +3673,38 @@ block_setProperty(JSContext *cx, JSObjec
/*
* The value in *vp will be written back to the slot in obj that was
* allocated when this let binding was defined.
*/
return true;
}
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;
}
JSBool
JSObject::nonNativeSetProperty(JSContext *cx, jsid id, js::Value *vp, JSBool strict)
{
@@ -4186,18 +4194,21 @@ js_XDRBlockObject(JSXDRState *xdr, JSObj
*/
for (uintN i = 0; i < count; i++) {
JSAtom *atom;
/* XDR the real id. */
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);
shapes.growBy(count);
for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) {
shape = &r.front();
shapes[shape->shortid()] = shape;
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1336,17 +1336,17 @@ struct JSObject : js::gc::Cell
inline JSObject *thisObject(JSContext *cx);
static bool thisObject(JSContext *cx, const js::Value &v, js::Value *vp);
inline JSObject *getThrowTypeError() const;
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;
inline bool isNormalArguments() const;
inline bool isStrictArguments() const;
inline bool isArray() const;
inline bool isDenseArray() const;
inline bool isSlowArray() const;
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -152,18 +152,17 @@ GetJumpOffset(jsbytecode *pc, jsbytecode
type = JOF_OPTYPE(*pc);
if (JOF_TYPE_IS_EXTENDED_JUMP(type))
return GET_JUMPX_OFFSET(pc2);
return GET_JUMP_OFFSET(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);
/*
* We need to detect index base prefix. It presents when resetbase
* follows the bytecode.
*/
@@ -215,44 +214,66 @@ js_GetVariableBytecodeLength(jsbytecode
do_lookup:
/* Structure: default-jump case-count (case1-value case1-jump) ... */
pc += jmplen;
ncases = GET_UINT16(pc);
return 1 + jmplen + INDEX_LEN + ncases * (INDEX_LEN + jmplen);
}
}
+static uint32_t
+NumBlockSlots(JSScript *script, jsbytecode *pc)
+{
+ 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_GetVariableStackUses(JSOp op, jsbytecode *pc)
+js::StackUses(JSScript *script, jsbytecode *pc)
{
- JS_ASSERT(*pc == op);
+ 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:
return GET_UINT16(pc);
case JSOP_LEAVEBLOCK:
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 ||
op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
return 2 + GET_ARGC(pc);
}
}
uintN
-js_GetEnterBlockStackDefs(JSContext *cx, JSScript *script, jsbytecode *pc)
+js::StackDefs(JSScript *script, jsbytecode *pc)
{
- JSObject *obj;
-
- JS_ASSERT(*pc == JSOP_ENTERBLOCK);
- GET_OBJECT_FROM_BYTECODE(script, pc, 0, obj);
- return OBJ_BLOCK_COUNT(cx, obj);
+ JSOp op = (JSOp) *pc;
+ const JSCodeSpec &cs = js_CodeSpec[op];
+ if (cs.ndefs >= 0)
+ return cs.ndefs;
+
+ uint32_t n = NumBlockSlots(script, pc);
+ return op == JSOP_ENTERLET1 ? n + 1 : n;
}
static const char * countBaseNames[] = {
"interp",
"mjit",
"mjit_calls",
"mjit_code",
"mjit_pics"
@@ -478,18 +499,22 @@ ToDisassemblySource(JSContext *cx, jsval
if (clasp == &BlockClass) {
char *source = JS_sprintf_append(NULL, "depth %d {", OBJ_BLOCK_DEPTH(cx, obj));
if (!source)
return false;
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();
source = JS_sprintf_append(source, "%s: %d%s",
bytes.ptr(), shape.shortid(),
!r.empty() ? ", " : "");
if (!source)
return false;
@@ -567,17 +592,17 @@ js_Disassemble1(JSContext *cx, JSScript
ptrdiff_t off = GetJumpOffset(pc, pc);
Sprint(sp, " %u (%+d)", loc + (intN) off, (intN) off);
break;
}
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) {
v = script->getConst(index);
} else {
JSAtom *atom = script->getAtom(index);
v = STRING_TO_JSVAL(atom);
}
@@ -663,17 +688,17 @@ js_Disassemble1(JSContext *cx, JSScript
case JOF_LOCAL:
Sprint(sp, " %u", GET_SLOTNO(pc));
break;
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);
v = STRING_TO_JSVAL(atom);
} else {
v = OBJECT_TO_JSVAL(script->getObject(index));
}
@@ -1276,17 +1301,16 @@ GetOff(SprintStack *ss, uintN i)
ptrdiff_t off;
jsbytecode *pc;
char *bytes;
off = ss->offsets[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];
bytes = DecompileExpression(ss->sprinter.context, ss->printer->script,
ss->printer->fun, pc);
if (!bytes)
return 0;
if (bytes != FAILED_EXPRESSION_DECOMPILER) {
@@ -1356,16 +1380,25 @@ PushOff(SprintStack *ss, ptrdiff_t off,
: (op == JSOP_GETELEM2) ? JSOP_GETELEM
: op);
ss->bytecodes[top] = pc;
ss->top = ++top;
AddParenSlop(ss);
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)
{
uintN top;
const JSCodeSpec *topcs;
ptrdiff_t off;
if (ppc)
@@ -1668,17 +1701,19 @@ GetArgOrVarAtom(JSPrinter *jp, uintN slo
static const char *
GetLocalInSlot(SprintStack *ss, jsint i, jsint slot, JSObject *obj)
{
for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) {
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);
if (!rval)
return NULL;
RETRACT(&ss->sprinter, rval);
return rval;
@@ -1714,18 +1749,17 @@ GetLocal(SprintStack *ss, jsint i)
// hoping the right object is found.
if (off <= -2 && ss->printer->pcstack) {
jsbytecode *pc = ss->printer->pcstack[-2 - off];
JS_ASSERT(ss->printer->script->code <= pc);
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()) {
jsint depth = OBJ_BLOCK_DEPTH(cx, obj);
jsint count = OBJ_BLOCK_COUNT(cx, obj);
if (jsuint(i - depth) < jsuint(count))
return GetLocalInSlot(ss, i, jsint(i - depth), obj);
@@ -1768,27 +1802,42 @@ IsVarSlot(JSPrinter *jp, jsbytecode *pc,
JS_ASSERT(slot < StackDepth(jp->script));
*indexp = slot;
return JS_FALSE;
}
#define LOAD_ATOM(PCOFF) \
GET_ATOM_FROM_BYTECODE(jp->script, pc, PCOFF, atom)
+typedef Vector<JSAtom *, 8> 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;
const JSCodeSpec *cs;
uintN oplen;
jsint i;
const char *lval, *xval;
JSAtom *atom;
@@ -1799,34 +1848,84 @@ DecompileDestructuringLHS(SprintStack *s
switch (op) {
case JSOP_POP:
*hole = JS_TRUE;
if (SprintPut(&ss->sprinter, ", ", 2) < 0)
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);
if (!QuoteString(&ss->sprinter, atom, 0))
return NULL;
} else if (IsVarSlot(jp, pc, &i)) {
atom = GetArgOrVarAtom(jp, i);
LOCAL_ASSERT(atom);
@@ -1844,16 +1943,17 @@ DecompileDestructuringLHS(SprintStack *s
LOAD_OP_DATA(pc);
if (op == JSOP_POPN)
return pc;
LOCAL_ASSERT(op == JSOP_POP);
}
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
* opcode that would reduce the stack depth to (ss->top-1), which we
* pass to Decompile encoded as -(ss->top-1) - 1 or just -ss->top for
* the nb parameter.
*/
ptrdiff_t todo = ss->sprinter.offset;
@@ -1888,69 +1988,64 @@ DecompileDestructuringLHS(SprintStack *s
}
LOCAL_ASSERT(pc < endpc);
pc += oplen;
return pc;
}
/*
- * 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;
/* Handle the optimized number-pushing opcodes. */
case JSOP_ZERO: d = i = 0; goto do_getelem;
case JSOP_ONE: d = i = 1; goto do_getelem;
case JSOP_UINT16: d = i = GET_UINT16(pc); goto do_getelem;
case JSOP_UINT24: d = i = GET_UINT24(pc); goto do_getelem;
@@ -1958,17 +2053,18 @@ DecompileDestructuring(SprintStack *ss,
case JSOP_INT32: d = i = GET_INT32(pc); goto do_getelem;
case JSOP_DOUBLE:
GET_DOUBLE_FROM_BYTECODE(jp->script, pc, 0, d);
LOCAL_ASSERT(JSDOUBLE_IS_FINITE(d) && !JSDOUBLE_IS_NEGZERO(d));
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;
LOAD_OP_DATA(pc);
LOCAL_ASSERT(op == JSOP_GETELEM);
/* Distinguish object from array by opcode or source note. */
if (sn && SN_TYPE(sn) == SRC_INITPROP) {
@@ -1981,20 +2077,22 @@ DecompileDestructuring(SprintStack *ss,
/* Fill in any holes (holes at the end don't matter). */
while (++lasti < i) {
if (SprintPut(&ss->sprinter, ", ", 2) < 0)
return NULL;
}
}
break;
+ }
case JSOP_GETPROP:
case JSOP_LENGTH:
{
+ JSAtom *atom;
LOAD_ATOM(0);
*OFF2STR(&ss->sprinter, head) = '{';
#if JS_HAS_DESTRUCTURING_SHORTHAND
nameoff = ss->sprinter.offset;
#endif
if (!QuoteString(&ss->sprinter, atom, IsIdentifier(atom) ? 0 : (jschar)'\''))
return NULL;
if (SprintPut(&ss->sprinter, ": ", 2) < 0)
@@ -2010,17 +2108,18 @@ DecompileDestructuring(SprintStack *ss,
if (pc == endpc)
return pc;
/*
* Decompile the left-hand side expression whose bytecode starts at pc
* 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;
#if JS_HAS_DESTRUCTURING_SHORTHAND
if (nameoff >= 0) {
ptrdiff_t offset, initlen;
offset = ss->sprinter.offset;
@@ -2055,32 +2154,32 @@ DecompileDestructuring(SprintStack *ss,
/*
* We should stop if JSOP_DUP is either without notes or its note is
* not SRC_CONTINUE. The former happens when JSOP_DUP duplicates the
* last destructuring reference implementing an op= assignment like in
* '([t] = z).y += x'. In the latter case the note is SRC_DESTRUCT and
* 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;
}
if (!hole && SprintPut(&ss->sprinter, ", ", 2) < 0)
return NULL;
pc += JSOP_DUP_LENGTH;
}
out:
- lval = OFF2STR(&ss->sprinter, head);
+ const char *lval = OFF2STR(&ss->sprinter, head);
if (SprintPut(&ss->sprinter, (*lval == '[') ? "]" : "}", 1) < 0)
return NULL;
return pc;
}
static jsbytecode *
DecompileGroupAssignment(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
jssrcnote *sn, ptrdiff_t *todop)
@@ -2141,18 +2240,16 @@ DecompileGroupAssignment(SprintStack *ss
#undef LOCAL_ASSERT
#undef LOAD_OP_DATA
#endif /* JS_HAS_DESTRUCTURING */
#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, false)
-typedef Vector<JSAtom *, 8> 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
* reverse order of addition. This function takes care of putting the names
* back in declaration order.
*/
static bool
GetBlockNames(JSContext *cx, JSObject *blockObj, AtomVector *atoms)
@@ -2163,17 +2260,19 @@ GetBlockNames(JSContext *cx, JSObject *b
return false;
uintN i = numAtoms;
for (Shape::Range r = blockObj->lastProperty()->all(); !r.empty(); r.popFront()) {
const Shape &shape = r.front();
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);
return true;
}
static bool
PushBlockNames(JSContext *cx, SprintStack *ss, const AtomVector &atoms)
@@ -2181,19 +2280,38 @@ PushBlockNames(JSContext *cx, SprintStac
for (size_t i = 0; i < atoms.length(); i++) {
const char *name = QuoteString(&ss->sprinter, atoms[i], 0);
if (!name || !PushOff(ss, STR2OFF(&ss->sprinter, name), JSOP_ENTERBLOCK))
return false;
}
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);
jp->indent += 4;
if (!Decompile(ss, pc, bodyLength))
return -1;
jp->indent -= 4;
js_printf(jp, "\t}\n");
@@ -2240,26 +2358,25 @@ GetTokenForAssignment(JSPrinter *jp, jss
token = "";
}
*lastlvalpc = NULL;
*lastrvalpc = NULL;
return token;
}
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, ";");
/* Skip the JSOP_NOP or JSOP_POP bytecode. */
JS_ASSERT(*pc == JSOP_NOP || *pc == JSOP_POP);
pc += JSOP_NOP_LENGTH;
/* Get the cond, next, and loop-closing tail offsets. */
@@ -2517,26 +2634,26 @@ Decompile(SprintStack *ss, jsbytecode *p
pc += cs->length;
if (pc >= endpc)
break;
op = (JSOp) *pc;
cs = &js_CodeSpec[op];
}
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
* source note. But when nb < 0 we assume that the caller knows that
* Decompile would never meet such opcodes.
*/
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;
}
/*
* Save source literal associated with JS now before the following
* rewrite changes op. See bug 380197.
*/
@@ -2666,16 +2783,22 @@ Decompile(SprintStack *ss, jsbytecode *p
case 1:
rval = PopStrDupe(ss, op, &rvalpc);
todo = SprintCString(&ss->sprinter, token);
SprintOpcode(ss, rval, rvalpc, pc, todo);
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;
default:
todo = -2;
break;
}
} else {
@@ -2707,17 +2830,17 @@ Decompile(SprintStack *ss, jsbytecode *p
js_printf(jp, ");\n");
pc += tail;
len = js_CodeSpec[*pc].length;
todo = -2;
break;
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:
jp->indent -= 4;
js_printf(jp, "\t}\n");
break;
case SRC_FUNCDEF:
@@ -2870,18 +2993,17 @@ Decompile(SprintStack *ss, jsbytecode *p
* ss->sprinter.offset so that our consumer can find the
* empty group assignment decompilation.
*/
if (newtop == oldtop) {
ss->sprinter.offset = todo;
} 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);
ss->top = newtop;
}
end_groupassignment:
LOCAL_ASSERT(*pc == JSOP_POPN);
@@ -2903,41 +3025,19 @@ Decompile(SprintStack *ss, jsbytecode *p
pc2 = pc + oplen;
if (*pc2 == JSOP_NOP) {
sn = js_GetSrcNote(jp->script, pc2);
if (sn) {
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
* third part of for(;;) loop head. If the POPN's
* immediate operand is 0, then we may have no slot
* on the sprint-stack in which to push our result
* string. In this case the result can be recovered
* at ss->sprinter.base + ss->sprinter.offset.
@@ -2983,17 +3083,17 @@ Decompile(SprintStack *ss, jsbytecode *p
case JSOP_POPV:
sn = js_GetSrcNote(jp->script, pc);
switch (sn ? SN_TYPE(sn) : SRC_NULL) {
case SRC_FOR:
/* Force parens around 'in' expression at 'for' front. */
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:
/* Comma operator: use JSOP_POP for correct precedence. */
op = JSOP_POP;
/* Pop and save to avoid blowing stack depth budget. */
lval = PopStrDupe(ss, op, &lvalpc);
@@ -3018,32 +3118,21 @@ Decompile(SprintStack *ss, jsbytecode *p
SprintOpcode(ss, rval, rvalpc, pushpc, todo);
break;
case SRC_HIDDEN:
/* Hide this pop, it's from a goto in a with or for/in. */
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:
{
/* Turn off parens around a yield statement. */
if (ss->opcodes[ss->top-1] == JSOP_YIELD)
op = JSOP_NOP;
jsbytecode *rvalpc;
@@ -3231,16 +3320,167 @@ Decompile(SprintStack *ss, jsbytecode *p
top -= depth;
ss->top = top;
ss->sprinter.offset = GetOff(ss, top);
if (op == JSOP_LEAVEBLOCKEXPR)
todo = SprintCString(&ss->sprinter, rval);
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<const char *> 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:
{
if (!jp->fun)
jp->fun = jp->script->getCallerFunction();
if (!jp->localNames) {
JS_ASSERT(fun == jp->fun);
@@ -3586,17 +3826,17 @@ Decompile(SprintStack *ss, jsbytecode *p
ss->sprinter.offset = ss->offsets[ss->top] - PAREN_SLOP;
if (Sprint(&ss->sprinter, " %s (%s in %s)",
foreach ? js_for_each_str : js_for_str,
lval, rval) < 0) {
return NULL;
}
/*
- * 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.
*/
todo = ss->offsets[ss->top - 1];
} else {
todo = Sprint(&ss->sprinter, " %s (%s in %s)",
foreach ? js_for_each_str : js_for_str,
@@ -3852,35 +4092,116 @@ Decompile(SprintStack *ss, jsbytecode *p
return NULL;
}
/* FALL THROUGH */
case JSOP_DUP:
#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 (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;
+ if (SN_TYPE(sn) == SRC_DESTRUCT) {
+ pc = DecompileDestructuring(ss, pc, endpc);
+ if (!pc)
+ return NULL;
+
+ lval = POP_STR(); /* Pop the decompiler result. */
+ rval = POP_STR(); /* Pop the initializer expression. */
+
+ 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;
}
#endif
rval = GetStr(ss, ss->top-1);
saveop = (JSOp) ss->opcodes[ss->top-1];
todo = SprintCString(&ss->sprinter, rval);
@@ -5529,18 +5850,18 @@ js_ReconstructStackDepth(JSContext *cx,
}
#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, -1);
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));
/*
* Fill the slots that the opcode defines withs its pc unless it just
* reshuffles the stack. In the latter case we want to preserve the
* opcode that generated the original value.
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -327,92 +327,65 @@ js_puts(JSPrinter *jp, const char *s);
/*
* Get index operand from the bytecode using a bytecode analysis to deduce the
* the index register. This function is infallible, in spite of taking cx as
* its first parameter; it uses only cx->runtime when calling JS_GetTrapOpcode.
* The GET_*_FROM_BYTECODE macros that call it pick up cx from their caller'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
* the index segment register itself.
*/
#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).
- */
-extern uintN
-js_GetVariableStackUses(JSOp op, jsbytecode *pc);
+#ifdef __cplusplus
+namespace js {
-/*
- * 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);
+StackUses(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);
-}
+extern uintN
+StackDefs(JSScript *script, jsbytecode *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.
*/
extern JSBool
js_DecompileScript(JSPrinter *jp, JSScript *script);
extern JSBool
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -266,17 +266,18 @@ OPDEF(JSOP_DECARG, 98, "decarg",
OPDEF(JSOP_ARGINC, 99, "arginc", NULL, 3, 0, 1, 15, JOF_QARG |JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3)
OPDEF(JSOP_ARGDEC, 100, "argdec", NULL, 3, 0, 1, 15, JOF_QARG |JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3)
OPDEF(JSOP_INCLOCAL, 101,"inclocal", NULL, 3, 0, 1, 15, JOF_LOCAL|JOF_NAME|JOF_INC|JOF_TMPSLOT3)
OPDEF(JSOP_DECLOCAL, 102,"declocal", NULL, 3, 0, 1, 15, JOF_LOCAL|JOF_NAME|JOF_DEC|JOF_TMPSLOT3)
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)
OPDEF(JSOP_LABELX, 107,"labelx", NULL, 5, 0, 0, 0, JOF_JUMPX)
/* Like JSOP_FUNAPPLY but for f.call instead of f.apply. */
OPDEF(JSOP_FUNCALL, 108,"funcall", NULL, 3, -1, 1, 18, JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
@@ -435,18 +436,21 @@ OPDEF(JSOP_XMLTAGEXPR, 178,"xmltagexp
OPDEF(JSOP_XMLELTEXPR, 179,"xmleltexpr", NULL, 1, 1, 1, 0, JOF_BYTE)
OPDEF(JSOP_XMLCDATA, 180,"xmlcdata", NULL, 3, 0, 1, 19, JOF_ATOM)
OPDEF(JSOP_XMLCOMMENT, 181,"xmlcomment", NULL, 3, 0, 1, 19, JOF_ATOM)
OPDEF(JSOP_XMLPI, 182,"xmlpi", NULL, 3, 1, 1, 19, JOF_ATOM)
OPDEF(JSOP_DELDESC, 183,"deldesc", NULL, 1, 2, 1, 15, JOF_BYTE|JOF_ELEM|JOF_DEL)
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.
*/
OPDEF(JSOP_UINT24, 187,"uint24", NULL, 4, 0, 1, 16, JOF_UINT24)
/*
* Opcodes to allow 24-bit atom or object indexes. Whenever an index exceeds
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -1618,17 +1618,17 @@ class ASTSerializer
bool functionArgs(ParseNode *pn, ParseNode *pnargs, ParseNode *pndestruct, ParseNode *pnbody,
NodeVector &args);
bool sourceElement(ParseNode *pn, Value *dst);
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) {
dst->setMagic(JS_SERIALIZE_NO_NODE);
return true;
}
return statement(pn, dst);
}
@@ -1958,35 +1958,47 @@ ASTSerializer::variableDeclarator(ParseN
Value left, right;
return pattern(pnleft, pkind, &left) &&
optExpression(pnright, &right) &&
builder.variableDeclarator(left, right, &pn->pn_pos, 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
* not contain const declarations, declarators should never have PND_CONST set.
*/
if (!variableDeclarator(next, &kind, &child))
return false;
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
ASTSerializer::switchCase(ParseNode *pn, Value *dst)
{
NodeVector stmts(cx);
Value expr;
@@ -2073,55 +2085,48 @@ ASTSerializer::forInit(ParseNode *pn, Va
{
if (!pn) {
dst->setMagic(JS_SERIALIZE_NO_NODE);
return true;
}
return (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST))
? variableDeclaration(pn, false, dst)
- : pn->isKind(PNK_LET)
- ? variableDeclaration(pn, true, dst)
: expression(pn, dst);
}
bool
ASTSerializer::statement(ParseNode *pn, Value *dst)
{
JS_CHECK_RECURSION(cx, return false);
switch (pn->getKind()) {
case PNK_FUNCTION:
case PNK_VAR:
case PNK_CONST:
+ return declaration(pn, dst);
+
case PNK_LET:
- return declaration(pn, dst);
+ 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);
case PNK_SEMI:
if (pn->pn_kid) {
Value expr;
return expression(pn->pn_kid, &expr) &&
builder.expressionStatement(expr, &pn->pn_pos, dst);
}
return builder.emptyStatement(&pn->pn_pos, 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 */
case PNK_STATEMENTLIST:
return blockStatement(pn, dst);
case PNK_IF:
@@ -2171,19 +2176,19 @@ ASTSerializer::statement(ParseNode *pn,
bool isForEach = pn->pn_iflags & JSITER_FOREACH;
if (head->isKind(PNK_FORIN)) {
Value var, expr;
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);
}
Value init, test, update;
return forInit(head->pn_kid1, &init) &&
optExpression(head->pn_kid2, &test) &&
@@ -2628,27 +2633,18 @@ ASTSerializer::expression(ParseNode *pn,
case PNK_ARRAYCOMP:
/* NB: it's no longer the case that pn_count could be 2. */
LOCAL_ASSERT(pn->pn_count == 1);
LOCAL_ASSERT(pn->pn_head->isKind(PNK_LEXICALSCOPE));
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:
JS_ASSERT(pn->isOp(JSOP_XMLNAME) ||
pn->isOp(JSOP_SETXMLNAME) ||
pn->isOp(JSOP_BINDXMLNAME));
return expression(pn->pn_kid, dst);
--- a/js/src/jsxdrapi.h
+++ b/js/src/jsxdrapi.h
@@ -221,17 +221,17 @@ JS_XDRFindClassById(JSXDRState *xdr, uin
* Bytecode version number. Increment the subtrahend whenever JS bytecode
* changes incompatibly.
*
* This version number is XDR'd near the front of xdr bytecode and
* aborts deserialization if there is a mismatch between the current
* 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.
*/
extern JSBool
js_XDRAtom(JSXDRState *xdr, JSAtom **atomp);
JS_END_EXTERN_C
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -2673,16 +2673,18 @@ mjit::Compiler::generateMethod()
END_CASE(JSOP_STOP)
BEGIN_CASE(JSOP_GETXPROP)
if (!jsop_xname(script->getAtom(fullAtomIndex(PC))))
return Compile_Error;
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);
BEGIN_CASE(JSOP_LEAVEBLOCK)
leaveBlock();
END_CASE(JSOP_LEAVEBLOCK)
BEGIN_CASE(JSOP_CALLLOCAL)
@@ -7129,29 +7131,29 @@ mjit::Compiler::jumpAndRun(Jump j, jsbyt
}
void
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
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);
}
// Creates the new object expected for constructors, and places it in |thisv|.
// It is broken down into the following operations:
// CALLEE
--- a/js/src/methodjit/LoopState.cpp
+++ b/js/src/methodjit/LoopState.cpp
@@ -1864,17 +1864,17 @@ LoopState::analyzeLoopBody(unsigned fram
if (constrainedLoop && !definiteArrayAccess(objValue, elemValue))
constrainedLoop = false;
break;
}
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);
if (objTypes->unknownObject()) {
unknownModset = true;
break;
}
@@ -2180,17 +2180,17 @@ LoopState::getEntryValue(const CrossSSAV
if (!tmp)
return false;
*pslot = frame.outerSlot(tmp);
*pconstant = 0;
return true;
}
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);
if (!tmp)
return false;
*pslot = frame.outerSlot(tmp);
*pconstant = 0;
return true;
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -1816,22 +1816,25 @@ stubs::FastInstanceOf(VMFrame &f)
void JS_FASTCALL
stubs::EnterBlock(VMFrame &f, JSObject *obj)
{
FrameRegs ®s = f.regs;
StackFrame *fp = f.fp();
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;
JS_ASSERT(fp->maybeBlockChain() == obj->staticBlockScopeChain());
/*
* The young end of fp->scopeChain() may omit blocks if we haven't closed
* over them, but if there are any closure blocks on fp->scopeChain(), they'd
--- a/js/src/tests/js1_8/regress/regress-465567-01.js
+++ b/js/src/tests/js1_8/regress/regress-465567-01.js
@@ -44,20 +44,20 @@ var expect = '';
printBugNumber(BUGNUMBER);
printStatus (summary);
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;
}
}
jit(false);
reportCompare(expect, actual, summary);
--- a/js/src/tests/js1_8_5/extensions/reflect-parse.js
+++ b/js/src/tests/js1_8_5/extensions/reflect-parse.js
@@ -570,17 +570,17 @@ function testVarPatternCombinations(make
assertBlockDecl("let " + pattSrcs[i].join(",") + ";", letDecl(pattPatts[i]));
assertDecl("const " + pattSrcs[i].join(",") + ";", constDecl(pattPatts[i]));
// variable declarations in for-loop heads
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));
}
}
testVarPatternCombinations(function (n) ("{a" + n + ":x" + n + "," + "b" + n + ":y" + n + "," + "c" + n + ":z" + n + "} = 0"),
function (n) ({ id: objPatt([{ key: ident("a" + n), value: ident("x" + n) },
{ key: ident("b" + n), value: ident("y" + n) },
--- a/js/src/tests/js1_8_5/extensions/regress-672804-1.js
+++ b/js/src/tests/js1_8_5/extensions/regress-672804-1.js
@@ -1,12 +1,12 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
var a = 0;
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');
--- a/js/src/tests/js1_8_5/extensions/regress-672804-3.js
+++ b/js/src/tests/js1_8_5/extensions/regress-672804-3.js
@@ -1,11 +1,11 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
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');