Bug 1193583 - Fix eval to always execute under a non-extensible lexical scope. (r=jorendorff)

This commit is contained in:
Shu-yu Guo 2015-08-30 15:08:19 -07:00
parent f48bd0b3d2
commit 9228fad4d3
8 changed files with 161 additions and 56 deletions

View File

@ -74,7 +74,8 @@ class MOZ_STACK_CLASS BytecodeCompiler
bool createScript(bool savedCallerFun = false);
bool createEmitter(SharedContext* sharedContext, HandleScript evalCaller = nullptr,
bool insideNonGlobalEval = false);
bool isInsideNonGlobalEval();
bool isEvalCompilationUnit();
bool isNonGlobalEvalCompilationUnit();
bool createParseContext(Maybe<ParseContext<FullParseHandler>>& parseContext,
SharedContext& globalsc, uint32_t blockScopeDepth = 0);
bool saveCallerFun(HandleScript evalCaller, ParseContext<FullParseHandler>& parseContext);
@ -82,7 +83,7 @@ class MOZ_STACK_CLASS BytecodeCompiler
Maybe<ParseContext<FullParseHandler>>& parseContext,
SharedContext& globalsc);
bool handleParseFailure(const Directives& newDirectives);
bool prepareAndEmitTree(ParseNode** pn);
bool prepareAndEmitTree(ParseNode** pn, ParseContext<FullParseHandler>& pc);
bool checkArgumentsWithinEval(JSContext* cx, HandleFunction fun);
bool maybeCheckEvalFreeVariables(HandleScript evalCaller, HandleObject scopeChain,
ParseContext<FullParseHandler>& pc);
@ -275,10 +276,17 @@ BytecodeCompiler::createEmitter(SharedContext* sharedContext, HandleScript evalC
return emitter->init();
}
bool BytecodeCompiler::isInsideNonGlobalEval()
bool
BytecodeCompiler::isEvalCompilationUnit()
{
return enclosingStaticScope && enclosingStaticScope->is<StaticEvalObject>() &&
enclosingStaticScope->as<StaticEvalObject>().enclosingScopeForStaticScopeIter();
return enclosingStaticScope && enclosingStaticScope->is<StaticEvalObject>();
}
bool
BytecodeCompiler::isNonGlobalEvalCompilationUnit()
{
return isEvalCompilationUnit() &&
enclosingStaticScope->as<StaticEvalObject>().enclosingScopeForStaticScopeIter();
}
bool
@ -365,8 +373,13 @@ BytecodeCompiler::handleParseFailure(const Directives& newDirectives)
}
bool
BytecodeCompiler::prepareAndEmitTree(ParseNode** ppn)
BytecodeCompiler::prepareAndEmitTree(ParseNode** ppn, ParseContext<FullParseHandler>& pc)
{
// Accumulate the maximum block scope depth, so that emitTree can assert
// when emitting JSOP_GETLOCAL that the local is indeed within the fixed
// part of the stack frame.
script->bindings.updateNumBlockScoped(pc.blockScopeDepth);
if (!FoldConstants(cx, ppn, parser.ptr()) ||
!NameFunctions(cx, *ppn) ||
!emitter->updateLocalsToFrameSlots() ||
@ -532,7 +545,7 @@ BytecodeCompiler::compileScript(HandleObject scopeChain, HandleScript evalCaller
return nullptr;
GlobalSharedContext globalsc(cx, enclosingStaticScope, directives, options.extraWarningsOption);
if (!createEmitter(&globalsc, evalCaller, isInsideNonGlobalEval()))
if (!createEmitter(&globalsc, evalCaller, isNonGlobalEvalCompilationUnit()))
return nullptr;
// Syntax parsing may cause us to restart processing of top level
@ -545,42 +558,55 @@ BytecodeCompiler::compileScript(HandleObject scopeChain, HandleScript evalCaller
if (savedCallerFun && !saveCallerFun(evalCaller, pc.ref()))
return nullptr;
bool canHaveDirectives = true;
for (;;) {
TokenKind tt;
if (!parser->tokenStream.peekToken(&tt, TokenStream::Operand))
return nullptr;
if (tt == TOK_EOF)
break;
parser->tokenStream.tell(&startPosition);
ParseNode* pn = parser->statement(YieldIsName, canHaveDirectives);
if (!pn) {
if (!handleStatementParseFailure(scopeChain, evalCaller, pc, globalsc))
// Global scripts are parsed incrementally, statement by statement.
//
// Eval scripts cannot be, as the block depth needs to be computed for all
// lexical bindings in the entire eval script.
if (isEvalCompilationUnit()) {
ParseNode* pn;
do {
pn = parser->evalBody();
if (!pn && !handleStatementParseFailure(scopeChain, evalCaller, pc, globalsc))
return nullptr;
} while (!pn);
pn = parser->statement(YieldIsName);
if (!pn) {
MOZ_ASSERT(!parser->hadAbortedSyntaxParse());
return nullptr;
}
}
// Accumulate the maximum block scope depth, so that emitTree can assert
// when emitting JSOP_GETLOCAL that the local is indeed within the fixed
// part of the stack frame.
script->bindings.updateNumBlockScoped(pc->blockScopeDepth);
if (canHaveDirectives) {
if (!parser->maybeParseDirective(/* stmtList = */ nullptr, pn, &canHaveDirectives))
return nullptr;
}
if (!prepareAndEmitTree(&pn))
if (!prepareAndEmitTree(&pn, *pc))
return nullptr;
parser->handler.freeTree(pn);
} else {
bool canHaveDirectives = true;
for (;;) {
TokenKind tt;
if (!parser->tokenStream.peekToken(&tt, TokenStream::Operand))
return nullptr;
if (tt == TOK_EOF)
break;
parser->tokenStream.tell(&startPosition);
ParseNode* pn = parser->statement(YieldIsName, canHaveDirectives);
if (!pn) {
if (!handleStatementParseFailure(scopeChain, evalCaller, pc, globalsc))
return nullptr;
pn = parser->statement(YieldIsName);
if (!pn) {
MOZ_ASSERT(!parser->hadAbortedSyntaxParse());
return nullptr;
}
}
if (canHaveDirectives) {
if (!parser->maybeParseDirective(/* stmtList = */ nullptr, pn, &canHaveDirectives))
return nullptr;
}
if (!prepareAndEmitTree(&pn, *pc))
return nullptr;
parser->handler.freeTree(pn);
}
}
if (!maybeCheckEvalFreeVariables(evalCaller, scopeChain, *pc) ||

View File

@ -1345,6 +1345,23 @@ BytecodeEmitter::emitVarIncDec(ParseNode* pn)
return true;
}
bool
BytecodeEmitter::atBodyLevel() const
{
// 'eval' scripts are always under an invisible lexical scope, but
// since it is not syntactic, it should still be considered at body
// level.
if (sc->staticScope() && sc->staticScope()->is<StaticEvalObject>()) {
bool bl = !innermostStmt()->enclosing;
MOZ_ASSERT_IF(bl, innermostStmt()->type == StmtType::BLOCK);
MOZ_ASSERT_IF(bl, innermostStmt()->staticScope
->as<StaticBlockObject>()
.maybeEnclosingEval() == sc->staticScope());
return bl;
}
return !innermostStmt() || sc->isModuleBox();
}
uint32_t
BytecodeEmitter::computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut)
{
@ -3023,6 +3040,16 @@ bool
BytecodeEmitter::enterBlockScope(StmtInfoBCE* stmtInfo, ObjectBox* objbox, JSOp initialValueOp,
unsigned alreadyPushed)
{
// This is so terrible. The eval body-level lexical scope needs to be
// emitted in the prologue so DEFFUN can pick up the right scope chain.
bool isEvalBodyLexicalScope = sc->staticScope() &&
sc->staticScope()->is<StaticEvalObject>() &&
!innermostStmt();
if (isEvalBodyLexicalScope) {
MOZ_ASSERT(code().length() == 0);
switchToPrologue();
}
// Initial values for block-scoped locals. Whether it is undefined or the
// JS_UNINITIALIZED_LEXICAL magic value depends on the context. The
// current way we emit for-in and for-of heads means its let bindings will
@ -3037,6 +3064,9 @@ BytecodeEmitter::enterBlockScope(StmtInfoBCE* stmtInfo, ObjectBox* objbox, JSOp
if (!initializeBlockScopedLocalsFromStack(blockObj))
return false;
if (isEvalBodyLexicalScope)
switchToMain();
return true;
}
@ -5886,7 +5916,7 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
if (sc->isGlobalContext()) {
MOZ_ASSERT(pn->pn_scopecoord.isFree());
MOZ_ASSERT(pn->getOp() == JSOP_NOP);
MOZ_ASSERT(!innermostStmt() || sc->isModuleBox());
MOZ_ASSERT(atBodyLevel());
switchToPrologue();
if (!emitIndex32(JSOP_DEFFUN, index))
return false;
@ -6365,16 +6395,10 @@ bool
BytecodeEmitter::emitStatementList(ParseNode* pn, ptrdiff_t top)
{
MOZ_ASSERT(pn->isArity(PN_LIST));
StmtInfoBCE stmtInfo(cx);
pushStatement(&stmtInfo, StmtType::BLOCK, top);
for (ParseNode* pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
if (!emitTree(pn2))
return false;
}
popStatement();
return true;
}

View File

@ -241,6 +241,7 @@ struct BytecodeEmitter
return parser->blockScopes[blockid];
}
bool atBodyLevel() const;
uint32_t computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut);
bool isAliasedName(BytecodeEmitter* bceOfDef, ParseNode* pn);
bool computeDefinitionIsAliased(BytecodeEmitter* bceOfDef, Definition* dn, JSOp* op);

View File

@ -885,6 +885,39 @@ Parser<SyntaxParseHandler>::standaloneModule(HandleModuleObject module)
return SyntaxParseHandler::NodeFailure;
}
template <>
ParseNode*
Parser<FullParseHandler>::evalBody()
{
AutoPushStmtInfoPC stmtInfo(*this, StmtType::BLOCK);
ParseNode* block = pushLexicalScope(stmtInfo);
if (!block)
return nullptr;
// For parsing declarations and directives, eval scripts must be
// considered body level despite having a lexical scope.
MOZ_ASSERT(pc->atBodyLevel());
ParseNode* body = statements(YieldIsName);
if (!body)
return nullptr;
// The statements() call above breaks on TOK_RC, so make sure we've
// reached EOF here.
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return nullptr;
if (tt != TOK_EOF) {
report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
"expression", TokenKindToDesc(tt));
return nullptr;
}
block->pn_expr = body;
block->pn_pos = body->pn_pos;
return block;
}
template <>
ParseNode*
Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun,
@ -4296,7 +4329,7 @@ Parser<FullParseHandler>::checkAndPrepareLexical(bool isConst, const TokenPos& e
static StaticBlockObject*
CurrentLexicalStaticBlock(ParseContext<FullParseHandler>* pc)
{
return pc->atBodyLevel() ? nullptr :
return !pc->innermostStmt() ? nullptr :
&pc->innermostStmt()->staticScope->as<StaticBlockObject>();
}

View File

@ -302,6 +302,17 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext
// if (cond) { function f3() { if (cond) { function f4() { } } } }
//
bool atBodyLevel() {
// 'eval' scripts are always under an invisible lexical scope, but
// since it is not syntactic, it should still be considered at body
// level.
if (sc->staticScope() && sc->staticScope()->is<StaticEvalObject>()) {
bool bl = !innermostStmt()->enclosing;
MOZ_ASSERT_IF(bl, innermostStmt()->type == StmtType::BLOCK);
MOZ_ASSERT_IF(bl, innermostStmt()->staticScope
->template as<StaticBlockObject>()
.maybeEnclosingEval() == sc->staticScope());
return bl;
}
return !innermostStmt();
}
@ -560,6 +571,12 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
bool maybeParseDirective(Node list, Node pn, bool* cont);
// Parse the body of an eval. It is distinguished from global scripts in
// that in ES6, per 18.2.1.1 steps 9 and 10, all eval scripts are executed
// under a fresh lexical scope.
Node evalBody();
// Parse a module.
Node standaloneModule(Handle<ModuleObject*> module);
// Parse a function, given only its body. Used for the Function and

View File

@ -3375,11 +3375,9 @@ IsFunctionCloneable(HandleFunction fun, HandleObject dynamicScope)
// If the script is an indirect eval that is immediately scoped under
// the global, we can clone it.
if (scope->is<StaticEvalObject>() &&
!scope->as<StaticEvalObject>().isDirect() &&
!scope->as<StaticEvalObject>().isStrict())
{
return true;
if (scope->is<StaticBlockObject>()) {
if (StaticEvalObject* staticEval = scope->as<StaticBlockObject>().maybeEnclosingEval())
return !staticEval->isDirect();
}
// Any other enclosing static scope (e.g., function, block) cannot be

View File

@ -2842,7 +2842,7 @@ JSScript::fullyInitFromEmitter(ExclusiveContext* cx, HandleScript script, Byteco
if (bce->tryNoteList.length() != 0)
bce->tryNoteList.finish(script->trynotes());
if (bce->blockScopeList.length() != 0)
bce->blockScopeList.finish(script->blockScopes());
bce->blockScopeList.finish(script->blockScopes(), prologueLength);
script->strict_ = bce->sc->strict();
script->explicitUseStrict_ = bce->sc->hasExplicitUseStrict();
script->bindingsAccessedDynamically_ = bce->sc->bindingsAccessedDynamically();
@ -3772,10 +3772,7 @@ JSScript::getStaticBlockScope(jsbytecode* pc)
if (!hasBlockScopes())
return nullptr;
if (pc < main())
return nullptr;
size_t offset = pc - main();
size_t offset = pc - code();
BlockScopeArray* scopes = blockScopes();
NestedScopeObject* blockChain = nullptr;

View File

@ -438,6 +438,7 @@ class StaticEvalObject : public ScopeObject
// Indirect evals terminate in the global at run time, and has no static
// enclosing scope.
bool isDirect() const {
MOZ_ASSERT_IF(!getReservedSlot(SCOPE_CHAIN_SLOT).isObject(), !isStrict());
return getReservedSlot(SCOPE_CHAIN_SLOT).isObject();
}
};
@ -643,6 +644,14 @@ class StaticBlockObject : public BlockObject
*/
inline StaticBlockObject* enclosingBlock() const;
StaticEvalObject* maybeEnclosingEval() const {
if (JSObject* enclosing = enclosingStaticScope()) {
if (enclosing->is<StaticEvalObject>())
return &enclosing->as<StaticEvalObject>();
}
return nullptr;
}
uint32_t localOffset() {
return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32();
}