mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-28 12:45:27 +00:00
Bug 1193583 - Fix eval to always execute under a non-extensible lexical scope. (r=jorendorff)
This commit is contained in:
parent
f48bd0b3d2
commit
9228fad4d3
@ -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) ||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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>();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user