From 17707f63841ea2850440b8b98731c1fcafc8cefa Mon Sep 17 00:00:00 2001 From: Andy Wingo Date: Tue, 4 Feb 2014 18:18:19 +0100 Subject: [PATCH] Bug 966912 - Part 2: Add StaticWithObject to the static scope chain r=luke --HG-- extra : rebase_source : c0ccbca2166c953dcac37e32aba9b30175399d25 --- js/src/frontend/BytecodeEmitter.cpp | 146 +++++++++++++--------- js/src/frontend/FoldConstants.cpp | 2 + js/src/frontend/FullParseHandler.h | 6 +- js/src/frontend/NameFunctions.cpp | 1 + js/src/frontend/ParseNode.cpp | 25 ++++ js/src/frontend/ParseNode.h | 33 ++++- js/src/frontend/Parser.cpp | 22 +++- js/src/frontend/SharedContext.h | 41 ++++--- js/src/jit/BaselineIC.cpp | 2 +- js/src/jsobj.cpp | 1 - js/src/jsscript.cpp | 43 ++++--- js/src/shell/jsheaptools.cpp | 3 +- js/src/vm/Debugger.cpp | 5 +- js/src/vm/Interpreter-inl.h | 4 +- js/src/vm/Interpreter.cpp | 27 ++-- js/src/vm/Opcodes.h | 2 +- js/src/vm/ScopeObject-inl.h | 17 ++- js/src/vm/ScopeObject.cpp | 183 ++++++++++++++++++++-------- js/src/vm/ScopeObject.h | 86 ++++++++----- js/src/vm/Shape-inl.h | 4 +- js/src/vm/Stack.cpp | 15 +-- js/src/vm/Xdr.h | 2 +- 22 files changed, 447 insertions(+), 223 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 1f7f27b5846..8f0b018424f 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -497,7 +497,7 @@ class NonLocalExitScope { StmtInfoBCE *stmt = bce->topStmt; while (1) { JS_ASSERT(stmt); - if (stmt->isBlockScope) { + if (stmt->isNestedScope) { openScopeIndex = stmt->blockScopeIndex; break; } @@ -512,18 +512,12 @@ class NonLocalExitScope { bce->stackDepth = savedDepth; } - bool popScopeForNonLocalExit(StaticBlockObject &blockObj, uint32_t blockScopeIndex) { + bool popScopeForNonLocalExit(uint32_t blockScopeIndex) { uint32_t scopeObjectIndex = bce->blockScopeList.findEnclosingScope(blockScopeIndex); uint32_t parent = openScopeIndex; - if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0) - return false; if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent)) return false; - if (blockObj.needsClone()) { - if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0) - return false; - } openScopeIndex = bce->blockScopeList.length() - 1; return true; } @@ -554,6 +548,9 @@ NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE *toStmt) FLUSH_POPS(); if (Emit1(cx, bce, JSOP_LEAVEWITH) < 0) return false; + JS_ASSERT(stmt->isNestedScope); + if (!popScopeForNonLocalExit(stmt->blockScopeIndex)) + return false; break; case STMT_FOR_OF_LOOP: @@ -578,9 +575,16 @@ NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE *toStmt) } if (stmt->isBlockScope) { + JS_ASSERT(stmt->isNestedScope); StaticBlockObject &blockObj = stmt->staticBlock(); - if (!popScopeForNonLocalExit(blockObj, stmt->blockScopeIndex)) + if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0) return false; + if (!popScopeForNonLocalExit(stmt->blockScopeIndex)) + return false; + if (blockObj.needsClone()) { + if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0) + return false; + } npops += blockObj.slotCount(); } } @@ -728,6 +732,55 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle scopeObj(cx, &objbox->object->as()); + uint32_t scopeObjectIndex = bce->objectList.add(objbox); + + switch (stmtType) { + case STMT_BLOCK: { + Rooted blockObj(cx, &scopeObj->as()); + + if (!ComputeAliasedSlots(cx, bce, blockObj)) + return false; + + if (blockObj->needsClone()) { + if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_PUSHBLOCKSCOPE, bce)) + return false; + } + break; + } + case STMT_WITH: + JS_ASSERT(scopeObj->is()); + if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_ENTERWITH, bce)) + return false; + break; + default: + MOZ_ASSUME_UNREACHABLE(); + } + + uint32_t parent = BlockScopeNote::NoBlockScopeIndex; + if (bce->staticScope) { + StmtInfoBCE *stmt = bce->topScopeStmt; + for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {} + parent = stmt->blockScopeIndex; + } + + stmt->blockScopeIndex = bce->blockScopeList.length(); + if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent)) + return false; + + PushStatementBCE(bce, stmt, stmtType, bce->offset()); + scopeObj->initEnclosingNestedScope(EnclosingStaticScope(bce)); + FinishPushNestedScope(bce, stmt, *scopeObj); + JS_ASSERT(stmt->isNestedScope); + stmt->isBlockScope = (stmtType == STMT_BLOCK); + + return true; +} + // ~ Block Scopes ~ // // A block scope is a region of a script with an additional set of named @@ -771,50 +824,26 @@ EmitInternedObjectOp(ExclusiveContext *cx, uint32_t index, JSOp op, BytecodeEmit // for-in loops. // // Summary: Enter block scopes with EnterBlockScope. It will emit -// PUSHBLOCKSCOPE if needed. Leave them with LeaveBlockScope, which will emit +// PUSHBLOCKSCOPE if needed. Leave them with LeaveNestedScope, which will emit // DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE. Pass EnterBlockScope a fresh // StmtInfoBCE object, and pass that same object to the corresponding -// LeaveBlockScope. Push locals before entering a scope, and pop them +// LeaveNestedScope. Push locals before entering a scope, and pop them // afterwards. Brush your teeth, and clean behind your ears! // static bool EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox, unsigned extraSlots) { - uint32_t parent = BlockScopeNote::NoBlockScopeIndex; - if (bce->staticScope) { - StmtInfoBCE *stmt = bce->topScopeStmt; - for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {} - parent = stmt->blockScopeIndex; - } - Rooted blockObj(cx, &objbox->object->as()); - uint32_t scopeObjectIndex = bce->objectList.add(objbox); - + // FIXME: Once bug 962599 lands, we won't care about the stack depth, so we + // won't have extraSlots and thus invocations of EnterBlockScope can become + // invocations of EnterNestedScope. int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots); JS_ASSERT(depth >= 0); blockObj->setStackDepth(depth); - if (!ComputeAliasedSlots(cx, bce, blockObj)) - return false; - - if (blockObj->needsClone()) { - if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_PUSHBLOCKSCOPE, bce)) - return false; - } - - stmt->blockScopeIndex = bce->blockScopeList.length(); - if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent)) - return false; - - PushStatementBCE(bce, stmt, STMT_BLOCK, bce->offset()); - blockObj->initEnclosingNestedScope(EnclosingStaticScope(bce)); - FinishPushBlockScope(bce, stmt, *blockObj); - - JS_ASSERT(stmt->isBlockScope); - - return true; + return EnterNestedScope(cx, bce, stmt, objbox, STMT_BLOCK); } // Patches |breaks| and |continues| unless the top statement info record @@ -835,10 +864,11 @@ PopStatementBCE(ExclusiveContext *cx, BytecodeEmitter *bce) } static bool -LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce) +LeaveNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt) { - StmtInfoBCE *stmt = bce->topStmt; - JS_ASSERT(stmt->isBlockScope); + JS_ASSERT(stmt == bce->topStmt); + JS_ASSERT(stmt->isNestedScope); + JS_ASSERT(stmt->isBlockScope == !(stmt->type == STMT_WITH)); uint32_t blockScopeIndex = stmt->blockScopeIndex; #ifdef DEBUG @@ -848,20 +878,18 @@ LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce) NestedScopeObject *staticScope = &blockObjBox->object->as(); JS_ASSERT(stmt->staticScope == staticScope); JS_ASSERT(staticScope == bce->staticScope); + JS_ASSERT_IF(!stmt->isBlockScope, staticScope->is()); #endif - JS_ASSERT(bce->staticScope->is()); - bool blockOnChain = bce->staticScope->as().needsClone(); - if (!PopStatementBCE(cx, bce)) return false; - if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0) + if (Emit1(cx, bce, stmt->isBlockScope ? JSOP_DEBUGLEAVEBLOCK : JSOP_LEAVEWITH) < 0) return false; bce->blockScopeList.recordEnd(blockScopeIndex, bce->offset()); - if (blockOnChain) { + if (stmt->isBlockScope && stmt->staticScope->as().needsClone()) { if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0) return false; } @@ -1120,8 +1148,7 @@ EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter * while (!b->containsVarAtDepth(depth)) { if (b->needsClone()) skippedScopes++; - b = b->enclosingBlock(); - JS_ASSERT(b); + b = &b->enclosingNestedScope()->as(); } if (!AssignHops(bce, pn, skippedScopes, &sc)) return false; @@ -1736,6 +1763,7 @@ CheckSideEffects(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool CheckSideEffects(cx, bce, pn->pn_kid3, answer); case PN_BINARY: + case PN_BINARY_OBJ: if (pn->isAssignment()) { /* * Assignment is presumed to be useful, even if the next operation @@ -1888,7 +1916,7 @@ BytecodeEmitter::needsImplicitThis() } else { JSObject *scope = sc->asGlobalSharedContext()->scopeChain(); while (scope) { - if (scope->is()) + if (scope->is()) return true; scope = scope->enclosingScope(); } @@ -2719,7 +2747,7 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) } if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) { - if (!LeaveBlockScope(cx, bce)) + if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; EMIT_UINT16_IMM_OP(JSOP_POPN, blockObj->slotCount()); } else { @@ -4269,7 +4297,7 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) if (!EmitTree(cx, bce, letBody->pn_expr)) return false; - if (!LeaveBlockScope(cx, bce)) + if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; JSOp leaveOp = letBody->getOp(); @@ -4305,7 +4333,7 @@ EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) if (!EmitTree(cx, bce, pn->pn_expr)) return false; - if (!LeaveBlockScope(cx, bce)) + if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; EMIT_UINT16_IMM_OP(JSOP_POPN, slots); @@ -4319,15 +4347,13 @@ EmitWith(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) StmtInfoBCE stmtInfo(cx); if (!EmitTree(cx, bce, pn->pn_left)) return false; - PushStatementBCE(bce, &stmtInfo, STMT_WITH, bce->offset()); - if (Emit1(cx, bce, JSOP_ENTERWITH) < 0) + if (!EnterNestedScope(cx, bce, &stmtInfo, pn->pn_binary_obj, STMT_WITH)) return false; - if (!EmitTree(cx, bce, pn->pn_right)) return false; - if (Emit1(cx, bce, JSOP_LEAVEWITH) < 0) + if (!LeaveNestedScope(cx, bce, &stmtInfo)) return false; - return PopStatementBCE(cx, bce); + return true; } static bool @@ -4474,7 +4500,7 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t return false; if (letDecl) { - if (!LeaveBlockScope(cx, bce)) + if (!LeaveNestedScope(cx, bce, &letStmt)) return false; } @@ -4637,7 +4663,7 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t return false; if (letDecl) { - if (!LeaveBlockScope(cx, bce)) + if (!LeaveNestedScope(cx, bce, &letStmt)) return false; EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount); } diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 991222f089c..956505420bd 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -49,6 +49,7 @@ ContainsVarOrConst(ParseNode *pn) return pnt; return ContainsVarOrConst(pn->pn_kid3); case PN_BINARY: + case PN_BINARY_OBJ: /* * Limit recursion if pn is a binary expression, which can't contain a * var statement. @@ -327,6 +328,7 @@ Fold(ExclusiveContext *cx, ParseNode **pnp, break; case PN_BINARY: + case PN_BINARY_OBJ: if (pn->isKind(PNK_OR) || pn->isKind(PNK_AND)) { // Propagate Condition context through logical connectives. SyntacticContext kidsc = SyntacticContext::Other; diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index b9356efcf4d..f93dd7a3226 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -385,8 +385,10 @@ class FullParseHandler return new_(PNK_RETURN, JSOP_RETURN, pos, expr); } - ParseNode *newWithStatement(uint32_t begin, ParseNode *expr, ParseNode *body) { - return new_(PNK_WITH, JSOP_NOP, TokenPos(begin, body->pn_pos.end), expr, body); + ParseNode *newWithStatement(uint32_t begin, ParseNode *expr, ParseNode *body, + ObjectBox *staticWith) { + return new_(PNK_WITH, JSOP_NOP, TokenPos(begin, body->pn_pos.end), + expr, body, staticWith); } ParseNode *newLabeledStatement(PropertyName *label, ParseNode *stmt, uint32_t begin) { diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index 3a156f6edc2..29c0eb9a962 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -310,6 +310,7 @@ class NameResolver return false; break; case PN_BINARY: + case PN_BINARY_OBJ: if (!resolve(cur->pn_left, prefix)) return false; diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 0d9e9917283..346d4d4292d 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -170,6 +170,7 @@ PushNodeChildren(ParseNode *pn, NodeStack *stack) stack->pushUnlessNull(pn->pn_kid3); break; case PN_BINARY: + case PN_BINARY_OBJ: if (pn->pn_left != pn->pn_right) stack->pushUnlessNull(pn->pn_left); stack->pushUnlessNull(pn->pn_right); @@ -382,6 +383,15 @@ Parser::cloneParseTree(ParseNode *opn) pn->pn_iflags = opn->pn_iflags; break; + case PN_BINARY_OBJ: + NULLCHECK(pn->pn_left = cloneParseTree(opn->pn_left)); + if (opn->pn_right != opn->pn_left) + NULLCHECK(pn->pn_right = cloneParseTree(opn->pn_right)); + else + pn->pn_right = pn->pn_left; + pn->pn_binary_obj = opn->pn_binary_obj; + break; + case PN_UNARY: NULLCHECK(pn->pn_kid = cloneParseTree(opn->pn_kid)); break; @@ -548,6 +558,9 @@ ParseNode::dump(int indent) case PN_BINARY: ((BinaryNode *) this)->dump(indent); break; + case PN_BINARY_OBJ: + ((BinaryObjNode *) this)->dump(indent); + break; case PN_TERNARY: ((TernaryNode *) this)->dump(indent); break; @@ -618,6 +631,18 @@ BinaryNode::dump(int indent) fprintf(stderr, ")"); } +void +BinaryObjNode::dump(int indent) +{ + const char *name = parseNodeNames[getKind()]; + fprintf(stderr, "(%s ", name); + indent += strlen(name) + 2; + DumpParseTree(pn_left, indent); + IndentNewLine(indent); + DumpParseTree(pn_right, indent); + fprintf(stderr, ")"); +} + void TernaryNode::dump(int indent) { diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 10874e384bd..45df351ee5f 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -288,7 +288,7 @@ enum ParseNodeKind * pn_kid3: catch block statements * PNK_BREAK name pn_atom: label or null * PNK_CONTINUE name pn_atom: label or null - * PNK_WITH binary pn_left: head expr, pn_right: body + * PNK_WITH binary-obj pn_left: head expr; pn_right: body; pn_binary_obj: StaticWithObject * PNK_VAR, list pn_head: list of PNK_NAME or PNK_ASSIGN nodes * PNK_CONST each name node has either * pn_used: false @@ -420,6 +420,7 @@ enum ParseNodeArity PN_NULLARY, /* 0 kids, only pn_atom/pn_dval/etc. */ PN_UNARY, /* one kid, plus a couple of scalars */ PN_BINARY, /* two kids, plus a couple of scalars */ + PN_BINARY_OBJ, /* two kids, plus an objbox */ PN_TERNARY, /* three kids */ PN_CODE, /* module or function definition node */ PN_LIST, /* generic singly linked list */ @@ -516,7 +517,10 @@ class ParseNode struct { /* two kids if binary */ ParseNode *left; ParseNode *right; - unsigned iflags; /* JSITER_* flags for PNK_FOR node */ + union { + unsigned iflags; /* JSITER_* flags for PNK_FOR node */ + ObjectBox *objbox; /* Only for PN_BINARY_OBJ */ + }; } binary; struct { /* one kid if unary */ ParseNode *kid; @@ -570,6 +574,7 @@ class ParseNode #define pn_right pn_u.binary.right #define pn_pval pn_u.binary.pval #define pn_iflags pn_u.binary.iflags +#define pn_binary_obj pn_u.binary.objbox #define pn_kid pn_u.unary.kid #define pn_prologue pn_u.unary.prologue #define pn_atom pn_u.name.atom @@ -905,6 +910,30 @@ struct BinaryNode : public ParseNode #endif }; +struct BinaryObjNode : public ParseNode +{ + BinaryObjNode(ParseNodeKind kind, JSOp op, const TokenPos &pos, ParseNode *left, ParseNode *right, + ObjectBox *objbox) + : ParseNode(kind, op, PN_BINARY_OBJ, pos) + { + pn_left = left; + pn_right = right; + pn_binary_obj = objbox; + } + + static inline BinaryObjNode *create(ParseNodeKind kind, FullParseHandler *handler) { + return (BinaryObjNode *) ParseNode::create(kind, PN_BINARY_OBJ, handler); + } + + static bool test(const ParseNode &node) { + return node.isArity(PN_BINARY_OBJ); + } + +#ifdef DEBUG + void dump(int indent); +#endif +}; + struct TernaryNode : public ParseNode { TernaryNode(ParseNodeKind kind, JSOp op, ParseNode *kid1, ParseNode *kid2, ParseNode *kid3) diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 0c8faf4c90a..8fc7b4552ab 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -535,7 +535,7 @@ FunctionBox::FunctionBox(ExclusiveContext *cx, ObjectBox* traceListHead, JSFunct // JSObject *scope = outerpc->sc->asGlobalSharedContext()->scopeChain(); while (scope) { - if (scope->is()) + if (scope->is()) inWith = true; scope = scope->enclosingScope(); } @@ -2774,7 +2774,7 @@ static void PopStatementPC(TokenStream &ts, ParseContext *pc) { RootedNestedScopeObject scopeObj(ts.context(), pc->topStmt->staticScope); - JS_ASSERT(!!scopeObj == (pc->topStmt->isBlockScope)); + JS_ASSERT(!!scopeObj == pc->topStmt->isNestedScope); FinishPopStatement(pc); @@ -3196,7 +3196,8 @@ Parser::pushLexicalScope(HandleStaticBlockObject blockObj, StmtInf PushStatementPC(pc, stmt, STMT_BLOCK); blockObj->initEnclosingNestedScopeFromParser(pc->staticScope); - FinishPushBlockScope(pc, stmt, *blockObj.get()); + FinishPushNestedScope(pc, stmt, *blockObj.get()); + stmt->isBlockScope = true; Node pn = handler.newLexicalScope(blockbox); if (!pn) @@ -3593,7 +3594,7 @@ Parser::letDeclaration() * lacks the SIF_SCOPE flag, it must be a try, catch, or finally * block. */ - stmt->isBlockScope = true; + stmt->isBlockScope = stmt->isNestedScope = true; stmt->downScope = pc->topScopeStmt; pc->topScopeStmt = stmt; @@ -4896,9 +4897,16 @@ Parser::withStatement() StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_WITH); + Rooted staticWith(context, StaticWithObject::create(context)); + if (!staticWith) + return null(); + staticWith->initEnclosingNestedScopeFromParser(pc->staticScope); + FinishPushNestedScope(pc, &stmtInfo, *staticWith); + Node innerBlock = statement(); if (!innerBlock) return null(); + PopStatementPC(tokenStream, pc); pc->sc->setBindingsAccessedDynamically(); @@ -4914,7 +4922,10 @@ Parser::withStatement() handler.deoptimizeUsesWithin(lexdep, TokenPos(begin, pos().begin)); } - return handler.newWithStatement(begin, objectExpr, innerBlock); + ObjectBox *staticWithBox = newObjectBox(staticWith); + if (!staticWithBox) + return null(); + return handler.newWithStatement(begin, objectExpr, innerBlock, staticWithBox); } template <> @@ -5860,6 +5871,7 @@ CompExprTransplanter::transplant(ParseNode *pn) break; case PN_BINARY: + case PN_BINARY_OBJ: if (!transplant(pn->pn_left)) return false; diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index cefabf1c10d..d0d1fe2ffbf 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -389,22 +389,30 @@ enum StmtType { // work with both types. struct StmtInfoBase { - uint16_t type; /* statement type */ + // Statement type (StmtType). + uint16_t type; - /* - * True if type is STMT_BLOCK, STMT_TRY, STMT_SWITCH, or - * STMT_FINALLY and the block contains at least one let-declaration. - */ + // True if type is STMT_BLOCK, STMT_TRY, STMT_SWITCH, or STMT_FINALLY and + // the block contains at least one let-declaration, or if type is + // STMT_CATCH. bool isBlockScope:1; - /* for (let ...) induced block scope */ + // True if isBlockScope or type == STMT_WITH. + bool isNestedScope:1; + + // for (let ...) induced block scope bool isForLetBlock:1; - RootedAtom label; /* name of LABEL */ - Rooted staticScope; /* scope object */ + // Block label. + RootedAtom label; + + // Compile-time scope chain node for this scope. Only set if + // isNestedScope. + Rooted staticScope; StmtInfoBase(ExclusiveContext *cx) - : isBlockScope(false), isForLetBlock(false), label(cx), staticScope(cx) + : isBlockScope(false), isNestedScope(false), isForLetBlock(false), + label(cx), staticScope(cx) {} bool maybeScope() const { @@ -412,12 +420,12 @@ struct StmtInfoBase { } bool linksScope() const { - return (STMT_WITH <= type && type <= STMT_CATCH) || isBlockScope; + return isNestedScope; } StaticBlockObject& staticBlock() const { + JS_ASSERT(isNestedScope); JS_ASSERT(isBlockScope); - JS_ASSERT(staticScope); return staticScope->as(); } @@ -437,6 +445,7 @@ PushStatement(ContextT *ct, typename ContextT::StmtInfo *stmt, StmtType type) { stmt->type = type; stmt->isBlockScope = false; + stmt->isNestedScope = false; stmt->isForLetBlock = false; stmt->label = nullptr; stmt->staticScope = nullptr; @@ -452,9 +461,9 @@ PushStatement(ContextT *ct, typename ContextT::StmtInfo *stmt, StmtType type) template void -FinishPushBlockScope(ContextT *ct, typename ContextT::StmtInfo *stmt, NestedScopeObject &staticScope) +FinishPushNestedScope(ContextT *ct, typename ContextT::StmtInfo *stmt, NestedScopeObject &staticScope) { - stmt->isBlockScope = true; + stmt->isNestedScope = true; stmt->downScope = ct->topScopeStmt; ct->topScopeStmt = stmt; ct->staticScope = &staticScope; @@ -472,8 +481,10 @@ FinishPopStatement(ContextT *ct) ct->topStmt = stmt->down; if (stmt->linksScope()) { ct->topScopeStmt = stmt->downScope; - if (stmt->isBlockScope) - ct->staticScope = stmt->staticBlock().enclosingBlock(); + if (stmt->isNestedScope) { + JS_ASSERT(stmt->staticScope); + ct->staticScope = stmt->staticScope->enclosingNestedScope(); + } } } diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 24923c5bd3d..fa5afad545d 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -5663,7 +5663,7 @@ TryAttachScopeNameStub(JSContext *cx, HandleScript script, ICGetName_Fallback *s return true; } - if (!scopeChain->is() || scopeChain->is()) + if (!scopeChain->is() || scopeChain->is()) return true; // Check for an 'own' property on the scope. There is no need to diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index e870ba144e1..f8d1c043ffc 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -5952,4 +5952,3 @@ JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Objects #endif } } - diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index ee2ca67fb08..cf8d3e06c83 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -433,8 +433,9 @@ SaveSharedScriptData(ExclusiveContext *, Handle, SharedScriptData *, enum XDRClassKind { CK_BlockObject = 0, - CK_JSFunction = 1, - CK_JSObject = 2 + CK_WithObject = 1, + CK_JSFunction = 2, + CK_JSObject = 3 }; template @@ -766,6 +767,8 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc JSObject *obj = *objp; if (obj->is()) classk = CK_BlockObject; + else if (obj->is()) + classk = CK_WithObject; else if (obj->is()) classk = CK_JSFunction; else if (obj->is() || obj->is()) @@ -778,32 +781,40 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc return false; switch (classk) { - case CK_BlockObject: { + case CK_BlockObject: + case CK_WithObject: { /* Code the nested block's enclosing scope. */ - uint32_t blockEnclosingScopeIndex = 0; + uint32_t enclosingStaticScopeIndex = 0; if (mode == XDR_ENCODE) { NestedScopeObject &scope = (*objp)->as(); if (NestedScopeObject *enclosing = scope.enclosingNestedScope()) - blockEnclosingScopeIndex = FindScopeObjectIndex(script, *enclosing); + enclosingStaticScopeIndex = FindScopeObjectIndex(script, *enclosing); else - blockEnclosingScopeIndex = UINT32_MAX; + enclosingStaticScopeIndex = UINT32_MAX; } - if (!xdr->codeUint32(&blockEnclosingScopeIndex)) + if (!xdr->codeUint32(&enclosingStaticScopeIndex)) return false; - Rooted blockEnclosingScope(cx); + Rooted enclosingStaticScope(cx); if (mode == XDR_DECODE) { - if (blockEnclosingScopeIndex != UINT32_MAX) { - JS_ASSERT(blockEnclosingScopeIndex < i); - blockEnclosingScope = script->objects()->vector[blockEnclosingScopeIndex]; + if (enclosingStaticScopeIndex != UINT32_MAX) { + JS_ASSERT(enclosingStaticScopeIndex < i); + enclosingStaticScope = script->objects()->vector[enclosingStaticScopeIndex]; } else { - blockEnclosingScope = fun; + enclosingStaticScope = fun; } } - Rooted tmp(cx, static_cast(objp->get())); - if (!XDRStaticBlockObject(xdr, blockEnclosingScope, tmp.address())) - return false; - *objp = tmp; + if (classk == CK_BlockObject) { + Rooted tmp(cx, static_cast(objp->get())); + if (!XDRStaticBlockObject(xdr, enclosingStaticScope, tmp.address())) + return false; + *objp = tmp; + } else { + Rooted tmp(cx, static_cast(objp->get())); + if (!XDRStaticWithObject(xdr, enclosingStaticScope, tmp.address())) + return false; + *objp = tmp; + } break; } diff --git a/js/src/shell/jsheaptools.cpp b/js/src/shell/jsheaptools.cpp index fec0cba9eb4..f18ebe59b85 100644 --- a/js/src/shell/jsheaptools.cpp +++ b/js/src/shell/jsheaptools.cpp @@ -396,7 +396,8 @@ class ReferenceFinder { /* Certain classes of object are for internal use only. */ if (object->is() || object->is() || - object->is() || + object->is() || + object->is() || object->is()) { return JSVAL_VOID; } diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index e82e2f0290b..5d3804aa451 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -5591,7 +5591,8 @@ IsDeclarative(Env *env) static bool IsWith(Env *env) { - return env->is() && env->as().scope().is(); + return env->is() && + env->as().scope().is(); } static bool @@ -5641,7 +5642,7 @@ DebuggerEnv_getObject(JSContext *cx, unsigned argc, Value *vp) JSObject *obj; if (IsWith(env)) { - obj = &env->as().scope().as().object(); + obj = &env->as().scope().as().object(); } else { obj = env; JS_ASSERT(!obj->is()); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index adf18e955d7..6eb4a387c0f 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -183,8 +183,8 @@ FetchName(JSContext *cx, HandleObject obj, HandleObject obj2, HandlePropertyName return false; } else { Rooted normalized(cx, obj); - if (normalized->getClass() == &WithObject::class_ && !shape->hasDefaultGetter()) - normalized = &normalized->as().object(); + if (normalized->is() && !shape->hasDefaultGetter()) + normalized = &normalized->as().object(); if (shape->isDataDescriptor() && shape->hasDefaultGetter()) { /* Fast path for Object instance properties. */ JS_ASSERT(shape->hasSlot()); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 3723abbfbad..df764a33ef2 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -832,8 +832,10 @@ js::TypeOfValue(const Value &v) * of the with block with sp + stackIndex. */ static bool -EnterWith(JSContext *cx, AbstractFramePtr frame, HandleValue val, uint32_t stackDepth) +EnterWith(JSContext *cx, AbstractFramePtr frame, HandleValue val, uint32_t stackDepth, + HandleObject staticWith) { + JS_ASSERT(staticWith->is()); RootedObject obj(cx); if (val.isObject()) { obj = &val.toObject(); @@ -844,7 +846,8 @@ EnterWith(JSContext *cx, AbstractFramePtr frame, HandleValue val, uint32_t stack } RootedObject scopeChain(cx, frame.scopeChain()); - WithObject *withobj = WithObject::create(cx, obj, scopeChain, stackDepth); + DynamicWithObject *withobj = DynamicWithObject::create(cx, obj, scopeChain, stackDepth, + staticWith); if (!withobj) return false; @@ -867,7 +870,7 @@ js::UnwindScope(JSContext *cx, ScopeIter &si, uint32_t stackDepth) si.frame().popBlock(cx); break; case ScopeIter::With: - if (si.scope().as().stackDepth() < stackDepth) + if (si.scope().as().stackDepth() < stackDepth) return; si.frame().popWith(cx); break; @@ -1727,13 +1730,6 @@ END_CASE(JSOP_POP) CASE(JSOP_POPN) JS_ASSERT(GET_UINT16(REGS.pc) <= REGS.stackDepth()); REGS.sp -= GET_UINT16(REGS.pc); -#ifdef DEBUG - if (NestedScopeObject *scope = script->getStaticScope(REGS.pc + JSOP_POPN_LENGTH)) { - JS_ASSERT(scope->is()); - StaticBlockObject &blockObj = scope->as(); - JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount()); - } -#endif END_CASE(JSOP_POPN) CASE(JSOP_POPNV) @@ -1742,13 +1738,6 @@ CASE(JSOP_POPNV) Value val = REGS.sp[-1]; REGS.sp -= GET_UINT16(REGS.pc); REGS.sp[-1] = val; -#ifdef DEBUG - if (NestedScopeObject *scope = script->getStaticScope(REGS.pc + JSOP_POPNV_LENGTH)) { - JS_ASSERT(scope->is()); - StaticBlockObject &blockObj = scope->as(); - JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount()); - } -#endif } END_CASE(JSOP_POPNV) @@ -1759,9 +1748,11 @@ END_CASE(JSOP_SETRVAL) CASE(JSOP_ENTERWITH) { RootedValue &val = rootValue0; + RootedObject &staticWith = rootObject0; val = REGS.sp[-1]; + staticWith = script->getObject(REGS.pc); - if (!EnterWith(cx, REGS.fp(), val, REGS.stackDepth() - 1)) + if (!EnterWith(cx, REGS.fp(), val, REGS.stackDepth() - 1, staticWith)) goto error; /* diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 180f6e5a33e..9ad491244bc 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -49,7 +49,7 @@ /* Long-standing JavaScript bytecodes. */ \ macro(JSOP_UNDEFINED, 1, js_undefined_str, "", 1, 0, 1, JOF_BYTE) \ macro(JSOP_UNUSED2, 2, "unused2", NULL, 1, 1, 0, JOF_BYTE) \ - macro(JSOP_ENTERWITH, 3, "enterwith", NULL, 1, 1, 1, JOF_BYTE) \ + macro(JSOP_ENTERWITH, 3, "enterwith", NULL, 5, 1, 1, JOF_OBJECT) \ macro(JSOP_LEAVEWITH, 4, "leavewith", NULL, 1, 1, 0, JOF_BYTE) \ macro(JSOP_RETURN, 5, "return", NULL, 1, 1, 0, JOF_BYTE) \ macro(JSOP_GOTO, 6, "goto", NULL, 5, 0, 0, JOF_JUMP) \ diff --git a/js/src/vm/ScopeObject-inl.h b/js/src/vm/ScopeObject-inl.h index ed5b35661b5..efaf6fbac43 100644 --- a/js/src/vm/ScopeObject-inl.h +++ b/js/src/vm/ScopeObject-inl.h @@ -56,7 +56,7 @@ StaticScopeIter::operator++(int) } else { onNamedLambda = true; } - JS_ASSERT_IF(obj, obj->template is() || obj->template is()); + JS_ASSERT_IF(obj, obj->template is() || obj->template is()); JS_ASSERT_IF(onNamedLambda, obj->template is()); } @@ -66,7 +66,8 @@ StaticScopeIter::hasDynamicScopeObject() const { return obj->template is() ? obj->template as().needsClone() - : obj->template as().isHeavyweight(); + : (obj->template is() || + obj->template as().isHeavyweight()); } template @@ -88,7 +89,9 @@ StaticScopeIter::type() const { if (onNamedLambda) return NAMED_LAMBDA; - return obj->template is() ? BLOCK : FUNCTION; + return obj->template is() + ? BLOCK + : (obj->template is() ? WITH : FUNCTION); } template @@ -99,6 +102,14 @@ StaticScopeIter::block() const return obj->template as(); } +template +inline StaticWithObject & +StaticScopeIter::staticWith() const +{ + JS_ASSERT(type() == WITH); + return obj->template as(); +} + template inline JSScript * StaticScopeIter::funScript() const diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 45abea6864a..5aa3cba64d9 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -346,14 +346,72 @@ DeclEnvObject::create(JSContext *cx, HandleObject enclosing, HandleFunction call return &obj->as(); } -WithObject * -WithObject::create(JSContext *cx, HandleObject proto, HandleObject enclosing, uint32_t depth) +template +bool +js::XDRStaticWithObject(XDRState *xdr, HandleObject enclosingScope, StaticWithObject **objp) { - RootedTypeObject type(cx, cx->getNewType(&class_, proto.get())); + if (mode == XDR_DECODE) { + JSContext *cx = xdr->cx(); + Rooted obj(cx, StaticWithObject::create(cx)); + if (!obj) + return false; + obj->initEnclosingNestedScope(enclosingScope); + *objp = obj; + } + // For encoding, there is nothing to do. The only information that is + // encoded by a StaticWithObject is its presence on the scope chain, and the + // script XDR handler already takes care of that. + + return true; +} + +template bool +js::XDRStaticWithObject(XDRState *, HandleObject, StaticWithObject **); + +template bool +js::XDRStaticWithObject(XDRState *, HandleObject, StaticWithObject **); + +StaticWithObject * +StaticWithObject::create(ExclusiveContext *cx) +{ + RootedTypeObject type(cx, cx->getNewType(&class_, nullptr)); if (!type) return nullptr; - RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(proto), + RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr), + nullptr, nullptr, FINALIZE_KIND)); + if (!shape) + return nullptr; + + RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, type)); + if (!obj) + return nullptr; + + return &obj->as(); +} + +static JSObject * +CloneStaticWithObject(JSContext *cx, HandleObject enclosingScope, Handle srcWith) +{ + Rooted clone(cx, StaticWithObject::create(cx)); + if (!clone) + return nullptr; + + clone->initEnclosingNestedScope(enclosingScope); + + return clone; +} + +DynamicWithObject * +DynamicWithObject::create(JSContext *cx, HandleObject object, HandleObject enclosing, uint32_t depth, + HandleObject staticWith) +{ + JS_ASSERT(staticWith->is()); + RootedTypeObject type(cx, cx->getNewType(&class_, staticWith.get())); + if (!type) + return nullptr; + + RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(staticWith), &enclosing->global(), nullptr, FINALIZE_KIND)); if (!shape) @@ -363,23 +421,23 @@ WithObject::create(JSContext *cx, HandleObject proto, HandleObject enclosing, ui if (!obj) return nullptr; - obj->as().setEnclosingScope(enclosing); - obj->setReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth)); - - JSObject *thisp = JSObject::thisObject(cx, proto); + JSObject *thisp = JSObject::thisObject(cx, object); if (!thisp) return nullptr; + obj->as().setEnclosingScope(enclosing); + obj->setReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth)); + obj->setFixedSlot(OBJECT_SLOT, ObjectValue(*object)); obj->setFixedSlot(THIS_SLOT, ObjectValue(*thisp)); - return &obj->as(); + return &obj->as(); } static bool with_LookupGeneric(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp, MutableHandleShape propp) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::lookupGeneric(cx, actual, id, objp, propp); } @@ -413,7 +471,7 @@ static bool with_GetGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id, MutableHandleValue vp) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::getGeneric(cx, actual, actual, id, vp); } @@ -447,7 +505,7 @@ static bool with_SetGeneric(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp, bool strict) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::setGeneric(cx, actual, actual, id, vp, strict); } @@ -455,7 +513,7 @@ static bool with_SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, MutableHandleValue vp, bool strict) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::setProperty(cx, actual, actual, name, vp, strict); } @@ -463,7 +521,7 @@ static bool with_SetElement(JSContext *cx, HandleObject obj, uint32_t index, MutableHandleValue vp, bool strict) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::setElement(cx, actual, actual, index, vp, strict); } @@ -471,21 +529,21 @@ static bool with_SetSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, MutableHandleValue vp, bool strict) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::setSpecial(cx, actual, actual, sid, vp, strict); } static bool with_GetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::getGenericAttributes(cx, actual, id, attrsp); } static bool with_SetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::setGenericAttributes(cx, actual, id, attrsp); } @@ -493,7 +551,7 @@ static bool with_DeleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, bool *succeeded) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::deleteProperty(cx, actual, name, succeeded); } @@ -501,7 +559,7 @@ static bool with_DeleteElement(JSContext *cx, HandleObject obj, uint32_t index, bool *succeeded) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::deleteElement(cx, actual, index, succeeded); } @@ -509,27 +567,33 @@ static bool with_DeleteSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, bool *succeeded) { - RootedObject actual(cx, &obj->as().object()); + RootedObject actual(cx, &obj->as().object()); return JSObject::deleteSpecial(cx, actual, sid, succeeded); } -static bool -with_Enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op, - MutableHandleValue statep, MutableHandleId idp) -{ - RootedObject actual(cx, &obj->as().object()); - return JSObject::enumerate(cx, actual, enum_op, statep, idp); -} - static JSObject * with_ThisObject(JSContext *cx, HandleObject obj) { - return &obj->as().withThis(); + return &obj->as().withThis(); } -const Class WithObject::class_ = { +const Class StaticWithObject::class_ = { + "WithTemplate", + JSCLASS_IMPLEMENTS_BARRIERS | + JSCLASS_HAS_RESERVED_SLOTS(StaticWithObject::RESERVED_SLOTS) | + JSCLASS_IS_ANONYMOUS, + JS_PropertyStub, /* addProperty */ + JS_DeletePropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub +}; + +const Class DynamicWithObject::class_ = { "With", - JSCLASS_HAS_RESERVED_SLOTS(WithObject::RESERVED_SLOTS) | + JSCLASS_HAS_RESERVED_SLOTS(DynamicWithObject::RESERVED_SLOTS) | JSCLASS_IS_ANONYMOUS, JS_PropertyStub, /* addProperty */ JS_DeletePropertyStub, /* delProperty */ @@ -569,7 +633,7 @@ const Class WithObject::class_ = { with_DeleteSpecial, nullptr, nullptr, /* watch/unwatch */ nullptr, /* slice */ - with_Enumerate, + nullptr, /* enumerate (native enumeration of target doesn't work) */ with_ThisObject, } }; @@ -845,9 +909,13 @@ CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle srcBlock) { - JS_ASSERT(srcBlock->is()); - Rooted blockObj(cx, &srcBlock->as()); - return CloneStaticBlockObject(cx, enclosingScope, blockObj); + if (srcBlock->is()) { + Rooted blockObj(cx, &srcBlock->as()); + return CloneStaticBlockObject(cx, enclosingScope, blockObj); + } else { + Rooted withObj(cx, &srcBlock->as()); + return CloneStaticWithObject(cx, enclosingScope, withObj); + } } /*****************************************************************************/ @@ -929,14 +997,17 @@ ScopeIter::operator++() frame_ = NullFramePtr(); break; case Block: - staticScope_ = staticScope_->as().enclosingBlock(); + JS_ASSERT(staticScope_ && staticScope_->is()); + staticScope_ = staticScope_->as().enclosingNestedScope(); if (hasScopeObject_) cur_ = &cur_->as().enclosingScope(); settle(); break; case With: + JS_ASSERT(staticScope_ && staticScope_->is()); JS_ASSERT(hasScopeObject_); - cur_ = &cur_->as().enclosingScope(); + staticScope_ = staticScope_->as().enclosingNestedScope(); + cur_ = &cur_->as().enclosingScope(); settle(); break; case StrictEvalScope: @@ -976,6 +1047,8 @@ ScopeIter::settle() */ if (frame_.isNonEvalFunctionFrame() && !frame_.fun()->isHeavyweight()) { if (staticScope_) { + // If staticScope_ were a StaticWithObject, the function would be + // heavyweight. JS_ASSERT(staticScope_->is()); type_ = Block; hasScopeObject_ = staticScope_->as().needsClone(); @@ -998,18 +1071,18 @@ ScopeIter::settle() } else if (frame_.isStrictEvalFrame() && !frame_.hasCallObj()) { JS_ASSERT(cur_ == frame_.evalPrevScopeChain(cx)); frame_ = NullFramePtr(); - } else if (cur_->is()) { - JS_ASSERT_IF(frame_.isFunctionFrame(), frame_.fun()->isHeavyweight()); - JS_ASSERT_IF(staticScope_, staticScope_->as().needsClone()); - JS_ASSERT_IF(staticScope_, - staticScope_->as().stackDepth() < - cur_->as().stackDepth()); - type_ = With; - hasScopeObject_ = true; } else if (staticScope_) { - type_ = Block; - hasScopeObject_ = staticScope_->as().needsClone(); - JS_ASSERT_IF(hasScopeObject_, cur_->as().staticBlock() == *staticScope_); + if (staticScope_->is()) { + JS_ASSERT(cur_); + JS_ASSERT(cur_->as().staticScope() == staticScope_); + type_ = With; + hasScopeObject_ = true; + } else { + type_ = Block; + hasScopeObject_ = staticScope_->as().needsClone(); + JS_ASSERT_IF(hasScopeObject_, + cur_->as().staticBlock() == *staticScope_); + } } else if (cur_->is()) { CallObject &callobj = cur_->as(); type_ = callobj.isForEval() ? StrictEvalScope : Call; @@ -1211,7 +1284,7 @@ class DebugScopeProxy : public BaseProxyHandler } /* The rest of the internal scopes do not have unaliased vars. */ - JS_ASSERT(scope->is() || scope->is() || + JS_ASSERT(scope->is() || scope->is() || scope->as().isForEval()); return false; } @@ -1385,7 +1458,15 @@ class DebugScopeProxy : public BaseProxyHandler return false; } - if (!GetPropertyNames(cx, scope, flags, &props)) + // DynamicWithObject isn't a very good proxy. It doesn't have a + // JSNewEnumerateOp implementation, because if it just delegated to the + // target object, the object would indicate that native enumeration is + // the thing to do, but native enumeration over the DynamicWithObject + // wrapper yields no properties. So instead here we hack around the + // issue, and punch a hole through to the with object target. + Rooted target(cx, (scope->is() + ? &scope->as().object() : scope)); + if (!GetPropertyNames(cx, target, flags, &props)) return false; /* @@ -1928,7 +2009,7 @@ DebugScopes::onPopWith(AbstractFramePtr frame) { DebugScopes *scopes = frame.compartment()->debugScopes; if (scopes) - scopes->liveScopes.remove(&frame.scopeChain()->as()); + scopes->liveScopes.remove(&frame.scopeChain()->as()); } void diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index 0549fc865d1..ba782924ac4 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -18,6 +18,8 @@ namespace js { namespace frontend { struct Definition; } +class StaticWithObject; + /*****************************************************************************/ /* @@ -65,14 +67,16 @@ class StaticScopeIter : obj(cx, obj), onNamedLambda(false) { JS_STATIC_ASSERT(allowGC == CanGC); - JS_ASSERT_IF(obj, obj->is() || obj->is()); + JS_ASSERT_IF(obj, obj->is() || obj->is() || + obj->is()); } StaticScopeIter(JSObject *obj) : obj((ExclusiveContext *) nullptr, obj), onNamedLambda(false) { JS_STATIC_ASSERT(allowGC == NoGC); - JS_ASSERT_IF(obj, obj->is() || obj->is()); + JS_ASSERT_IF(obj, obj->is() || obj->is() || + obj->is()); } bool done() const; @@ -82,10 +86,11 @@ class StaticScopeIter bool hasDynamicScopeObject() const; Shape *scopeShape() const; - enum Type { BLOCK, FUNCTION, NAMED_LAMBDA }; + enum Type { WITH, BLOCK, FUNCTION, NAMED_LAMBDA }; Type type() const; StaticBlockObject &block() const; + StaticWithObject &staticWith() const; JSScript *funScript() const; }; @@ -159,9 +164,11 @@ ScopeCoordinateFunctionScript(JSScript *script, jsbytecode *pc); * \ CallObject Scope of entire function or strict eval * \ * NestedScopeObject Scope created for a statement - * \ \ - * \ WithObject with - * \ + * \ \ \ + * \ \ StaticWithObject Template for "with" object in static scope chain + * \ \ + * \ DynamicWithObject Run-time "with" object on scope chain + * \ * BlockObject Shared interface of cloned/static block objects * \ \ * \ ClonedBlockObject let, switch, catch, for @@ -316,6 +323,15 @@ class NestedScopeObject : public ScopeObject */ inline NestedScopeObject *enclosingNestedScope() const; + // Return true if this object is a compile-time scope template. + inline bool isStatic() { return !getProto(); } + + // Return the static scope corresponding to this scope chain object. + inline NestedScopeObject* staticScope() { + JS_ASSERT(!isStatic()); + return &getProto()->as(); + } + // At compile-time it's possible for the scope chain to be null. JSObject *enclosingScopeForStaticScopeIter() { return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); @@ -342,30 +358,46 @@ class NestedScopeObject : public ScopeObject } }; -class WithObject : public NestedScopeObject +// With scope template objects on the static scope chain. +class StaticWithObject : public NestedScopeObject { - static const unsigned THIS_SLOT = 2; + public: + static const unsigned RESERVED_SLOTS = 2; + static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND; - /* Use WithObject::object() instead. */ - JSObject *getProto() const; + static const Class class_; + + static StaticWithObject *create(ExclusiveContext *cx); +}; + +// With scope objects on the run-time scope chain. +class DynamicWithObject : public NestedScopeObject +{ + static const unsigned OBJECT_SLOT = 2; + static const unsigned THIS_SLOT = 3; public: - static const unsigned RESERVED_SLOTS = 3; + static const unsigned RESERVED_SLOTS = 4; static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND; static const Class class_; - static WithObject * - create(JSContext *cx, HandleObject proto, HandleObject enclosing, uint32_t depth); + static DynamicWithObject * + create(JSContext *cx, HandleObject object, HandleObject enclosing, uint32_t depth, + HandleObject staticWith); - /* Return object for the 'this' class hook. */ - JSObject &withThis() const { - return getReservedSlot(THIS_SLOT).toObject(); + StaticWithObject& staticWith() const { + return getProto()->as(); } /* Return the 'o' in 'with (o)'. */ JSObject &object() const { - return *JSObject::getProto(); + return getReservedSlot(OBJECT_SLOT).toObject(); + } + + /* Return object for the 'this' class hook. */ + JSObject &withThis() const { + return getReservedSlot(THIS_SLOT).toObject(); } }; @@ -412,12 +444,6 @@ class StaticBlockObject : public BlockObject public: static StaticBlockObject *create(ExclusiveContext *cx); - /* - * A refinement of enclosingScope that returns nullptr if the enclosing - * static scope is a JSFunction. - */ - inline StaticBlockObject *enclosingBlock() const; - /* * Return whether this StaticBlockObject contains a variable stored at * the given stack depth (i.e., fp->base()[depth]). @@ -520,6 +546,11 @@ bool XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, StaticBlockObject **objp); +template +bool +XDRStaticWithObject(XDRState *xdr, HandleObject enclosingScope, + StaticWithObject **objp); + extern JSObject * CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle src); @@ -806,7 +837,7 @@ template<> inline bool JSObject::is() const { - return is() || is(); + return is() || is() || is(); } template<> @@ -867,13 +898,6 @@ NestedScopeObject::enclosingNestedScope() const return obj && obj->is() ? &obj->as() : nullptr; } -inline StaticBlockObject * -StaticBlockObject::enclosingBlock() const -{ - JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); - return obj && obj->is() ? &obj->as() : nullptr; -} - #ifdef DEBUG bool AnalyzeEntrainedVariables(JSContext *cx, HandleScript script); diff --git a/js/src/vm/Shape-inl.h b/js/src/vm/Shape-inl.h index c5fb0461542..79fe5f0ca24 100644 --- a/js/src/vm/Shape-inl.h +++ b/js/src/vm/Shape-inl.h @@ -133,8 +133,8 @@ Shape::set(JSContext* cx, HandleObject obj, HandleObject receiver, bool strict, * |with (it) color='red';| ends up here. * Avoid exposing the With object to native setters. */ - if (obj->is()) { - RootedObject nobj(cx, &obj->as().object()); + if (obj->is()) { + RootedObject nobj(cx, &obj->as().object()); return CallJSPropertyOpSetter(cx, self->setterOp(), nobj, id, strict, vp); } diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 4c63012ec77..42d9af6b2e5 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -182,18 +182,15 @@ AssertDynamicScopeMatchesStaticScope(JSContext *cx, JSScript *script, JSObject * RootedObject enclosingScope(cx, script->enclosingStaticScope()); for (StaticScopeIter i(enclosingScope); !i.done(); i++) { if (i.hasDynamicScopeObject()) { - /* - * 'with' does not participate in the static scope of the script, - * but it does in the dynamic scope, so skip them here. - */ - while (scope->is()) - scope = &scope->as().enclosingScope(); - switch (i.type()) { case StaticScopeIter::BLOCK: - JS_ASSERT(i.block() == scope->as().staticBlock()); + JS_ASSERT(&i.block() == scope->as().staticScope()); scope = &scope->as().enclosingScope(); break; + case StaticScopeIter::WITH: + JS_ASSERT(&i.staticWith() == scope->as().staticScope()); + scope = &scope->as().enclosingScope(); + break; case StaticScopeIter::FUNCTION: JS_ASSERT(scope->as().callee().nonLazyScript() == i.funScript()); scope = &scope->as().enclosingScope(); @@ -350,7 +347,7 @@ StackFrame::popWith(JSContext *cx) if (MOZ_UNLIKELY(cx->compartment()->debugMode())) DebugScopes::onPopWith(this); - JS_ASSERT(scopeChain()->is()); + JS_ASSERT(scopeChain()->is()); popOffScopeChain(); } diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 0b86067d82b..1b14b51488d 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -23,7 +23,7 @@ namespace js { * and saved versions. If deserialization fails, the data should be * invalidated if possible. */ -static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 165); +static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 166); class XDRBuffer { public: