Bug 589199 - Implement all-or-nothing redeclaration checks for global and eval scripts. (r=efaust)

This commit is contained in:
Shu-yu Guo 2015-10-06 14:00:30 -07:00
parent 371566ca0b
commit 2848530604
22 changed files with 425 additions and 74 deletions

View File

@ -92,7 +92,9 @@ class MOZ_STACK_CLASS BytecodeCompiler
bool maybeSetSourceMap(TokenStream& tokenStream);
bool maybeSetSourceMapFromOptions();
bool emitFinalReturn();
bool initGlobalBindings(ParseContext<FullParseHandler>& pc);
bool initGlobalOrEvalBindings(ParseContext<FullParseHandler>& pc,
Handle<TraceableVector<Binding>> vars,
Handle<TraceableVector<Binding>> lexicals);
bool maybeCompleteCompressSource();
AutoCompilationTraceLogger traceLogger;
@ -522,19 +524,35 @@ BytecodeCompiler::emitFinalReturn()
}
bool
BytecodeCompiler::initGlobalBindings(ParseContext<FullParseHandler>& pc)
BytecodeCompiler::initGlobalOrEvalBindings(ParseContext<FullParseHandler>& pc,
Handle<TraceableVector<Binding>> vars,
Handle<TraceableVector<Binding>> lexicals)
{
// Global/eval script bindings are always empty (all names are added to the
// scope dynamically via JSOP_DEFFUN/VAR). They may have block-scoped
// locals, however, which are allocated to the fixed part of the stack
// frame.
Rooted<Bindings> bindings(cx, script->bindings);
if (!Bindings::initWithTemporaryStorage(cx, &bindings, 0, 0, 0,
pc.blockScopeDepth, 0, 0, nullptr))
{
Binding* packedBindings = alloc->newArrayUninitialized<Binding>(vars.length() +
lexicals.length());
if (!packedBindings) {
ReportOutOfMemory(cx);
return false;
}
// Bindings for global and eval scripts are used solely for redeclaration
// checks in the prologue. Neither 'true' nor 'false' accurately describe
// their aliased-ness. These bindings don't live in CallObjects or the
// frame, but either on the global object and the global lexical
// scope. Force aliased to be false to avoid confusing other analyses in
// the engine that assumes the frame has a call object if there are
// aliased bindings.
Binding* packedIter = packedBindings;
for (const Binding& b: vars)
*packedIter++ = Binding(b.name(), b.kind(), false);
for (const Binding& b: lexicals)
*packedIter++ = Binding(b.name(), b.kind(), false);
if (!Bindings::initWithTemporaryStorage(cx, &bindings, 0, vars.length(), lexicals.length(),
pc.blockScopeDepth, 0, 0, packedBindings))
return false;
script->bindings = bindings;
return true;
}
@ -559,9 +577,16 @@ BytecodeCompiler::compileScript(HandleObject scopeChain, HandleScript evalCaller
if (!createEmitter(&globalsc, evalCaller, isNonGlobalEvalCompilationUnit()))
return nullptr;
Rooted<TraceableVector<Binding>> vars(cx, TraceableVector<Binding>(cx));
Rooted<TraceableVector<Binding>> lexicals(cx, TraceableVector<Binding>(cx));
// Syntax parsing may cause us to restart processing of top level
// statements in the script. Use Maybe<> so that the parse context can be
// reset when this occurs.
//
// WARNING: ParseContext contains instances of Rooted and may be
// reset(). Do not make any new Rooted instances below this point to avoid
// violating the Rooted LIFO invariant.
Maybe<ParseContext<FullParseHandler>> pc;
if (!createParseContext(pc, globalsc))
return nullptr;
@ -584,6 +609,9 @@ BytecodeCompiler::compileScript(HandleObject scopeChain, HandleScript evalCaller
if (!prepareAndEmitTree(&pn, *pc))
return nullptr;
if (!pc->drainGlobalOrEvalBindings(cx, &vars, &lexicals))
return nullptr;
parser->handler.freeTree(pn);
} else {
bool canHaveDirectives = true;
@ -616,6 +644,9 @@ BytecodeCompiler::compileScript(HandleObject scopeChain, HandleScript evalCaller
if (!prepareAndEmitTree(&pn, *pc))
return nullptr;
if (!pc->drainGlobalOrEvalBindings(cx, &vars, &lexicals))
return nullptr;
parser->handler.freeTree(pn);
}
}
@ -625,7 +656,7 @@ BytecodeCompiler::compileScript(HandleObject scopeChain, HandleScript evalCaller
!maybeSetSourceMap(parser->tokenStream) ||
!maybeSetSourceMapFromOptions() ||
!emitFinalReturn() ||
!initGlobalBindings(pc.ref()) ||
!initGlobalOrEvalBindings(pc.ref(), vars, lexicals) ||
!JSScript::fullyInitFromEmitter(cx, script, emitter.ptr()))
{
return nullptr;

View File

@ -237,10 +237,8 @@ struct BytecodeEmitter
StmtInfoBCE* innermostStmt() const { return stmtStack.innermost(); }
StmtInfoBCE* innermostScopeStmt() const { return stmtStack.innermostScopeStmt(); }
JSObject* innermostStaticScope() const;
JSObject* blockScopeOfDef(ParseNode* pn) const {
MOZ_ASSERT(pn->resolve());
unsigned blockid = pn->resolve()->pn_blockid;
return parser->blockScopes[blockid];
JSObject* blockScopeOfDef(Definition* dn) const {
return parser->blockScopes[dn->pn_blockid];
}
bool atBodyLevel() const;

View File

@ -242,13 +242,22 @@ ParseContext<FullParseHandler>::define(TokenStream& ts,
break;
case Definition::VAR:
if (!sc->isGlobalContext()) {
// Unlike args, var bindings keep the blockid of where the statement
// was found until ParseContext::generateBindings. In practice, this
// means when we emit bytecode for function scripts, var Definition
// nodes will have their static scopes correctly set to the static
// scope of the body. For global scripts, vars are dynamically defined
// on the global object and their static scope is never consulted.
if (!vars_.append(dn))
return false;
// We always track vars for redeclaration checks, but only non-global
// and non-deoptimized (e.g., inside a with scope) vars live in frame
// or CallObject slots.
if (!sc->isGlobalContext() && !dn->isDeoptimized()) {
dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETLOCAL : JSOP_GETLOCAL);
dn->pn_blockid = bodyid;
dn->pn_dflags |= PND_BOUND;
if (!dn->pn_scopecoord.setSlot(ts, vars_.length()))
return false;
if (!vars_.append(dn))
if (!dn->pn_scopecoord.setSlot(ts, vars_.length() - 1))
return false;
if (!checkLocalsOverflow(ts))
return false;
@ -336,7 +345,7 @@ ParseContext<ParseHandler>::prepareToAddDuplicateArg(HandlePropertyName name, De
template <typename ParseHandler>
void
ParseContext<ParseHandler>::updateDecl(JSAtom* atom, Node pn)
ParseContext<ParseHandler>::updateDecl(TokenStream& ts, JSAtom* atom, Node pn)
{
Definition* oldDecl = decls_.lookupFirst(atom);
@ -344,8 +353,24 @@ ParseContext<ParseHandler>::updateDecl(JSAtom* atom, Node pn)
Definition* newDecl = (Definition*)pn;
decls_.updateFirst(atom, newDecl);
if (sc->isGlobalContext()) {
if (sc->isGlobalContext() || oldDecl->isDeoptimized()) {
MOZ_ASSERT(newDecl->isFreeVar());
// Global 'var' bindings have no slots, but are still tracked for
// redeclaration checks.
for (uint32_t i = 0; i < vars_.length(); i++) {
if (vars_[i] == oldDecl) {
// Terribly, deoptimized bindings may be updated with
// optimized bindings due to hoisted function statements, so
// give the new declaration a slot.
if (oldDecl->isDeoptimized() && !newDecl->isDeoptimized()) {
newDecl->pn_dflags |= PND_BOUND;
newDecl->pn_scopecoord.setSlot(ts, i);
newDecl->setOp(JSOP_GETLOCAL);
}
vars_[i] = newDecl;
break;
}
}
return;
}
@ -438,6 +463,12 @@ ParseContext<ParseHandler>::generateBindings(ExclusiveContext* cx, TokenStream&
if (UINT32_MAX - args_.length() <= vars_.length() + bodyLevelLexicals_.length())
return ts.reportError(JSMSG_TOO_MANY_LOCALS);
// Fix up the blockids of vars, whose static scope is always at the body
// level. This could not be done up front in ParseContext::Define, as
// the original blockids are used for redeclaration checks.
for (size_t i = 0; i < vars_.length(); i++)
vars_[i]->pn_blockid = bodyid;
// Fix up the slots of body-level lets to come after the vars now that we
// know how many vars there are.
for (size_t i = 0; i < bodyLevelLexicals_.length(); i++) {
@ -467,6 +498,32 @@ ParseContext<ParseHandler>::generateBindings(ExclusiveContext* cx, TokenStream&
packedBindings, sc->isModuleBox());
}
template <>
bool
ParseContext<FullParseHandler>::drainGlobalOrEvalBindings(ExclusiveContext* cx,
MutableHandle<TraceableVector<Binding>> vars,
MutableHandle<TraceableVector<Binding>> lexicals)
{
MOZ_ASSERT(sc->isGlobalContext());
uint32_t newVarsPos = vars.length();
uint32_t newLexicalsPos = lexicals.length();
if (!vars.growBy(vars_.length()))
return false;
AppendPackedBindings(this, vars_, vars.begin() + newVarsPos);
vars_.clear();
if (!sc->staticScope()->is<StaticEvalObject>()) {
if (!lexicals.growBy(bodyLevelLexicals_.length()))
return false;
AppendPackedBindings(this, bodyLevelLexicals_, lexicals.begin() + newLexicalsPos);
}
bodyLevelLexicals_.clear();
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::reportHelper(ParseReportKind kind, bool strict, uint32_t offset,
@ -1224,10 +1281,10 @@ Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHan
/* See comment for use in Parser::functionDef. */
template <>
bool
Parser<FullParseHandler>::makeDefIntoUse(Definition* dn, ParseNode* pn, JSAtom* atom)
Parser<FullParseHandler>::makeDefIntoUse(Definition* dn, ParseNode* pn, HandleAtom atom)
{
/* Turn pn into a definition. */
pc->updateDecl(atom, pn);
pc->updateDecl(tokenStream, atom, pn);
/* Change all uses of dn to be uses of pn. */
for (ParseNode* pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) {
@ -3263,7 +3320,7 @@ Parser<FullParseHandler>::bindLexical(BindData<FullParseHandler>* data,
ExclusiveContext* cx = parser->context;
Rooted<StaticBlockObject*> blockObj(cx, data->letData().blockObj);
uint32_t index;
uint32_t index = StaticBlockObject::LOCAL_INDEX_LIMIT;
if (blockObj) {
// Leave the scope coordinate free on global lexicals.
//
@ -3310,7 +3367,10 @@ Parser<FullParseHandler>::bindLexical(BindData<FullParseHandler>* data,
* define() right now. Otherwise, delay define until pushLetScope.
*/
if (data->letData().varContext == HoistVars) {
if (dn && dn->pn_blockid == pc->blockid())
// The reason we compare using >= instead of == on the block id is to
// detect redeclarations where a 'var' binding first appeared in a
// nested block: |{ var x; } let x;|
if (dn && dn->pn_blockid >= pc->blockid())
return parser->reportRedeclaration(pn, dn->kind(), name);
if (!pc->define(parser->tokenStream, name, pn, bindingKind))
return false;
@ -3518,21 +3578,27 @@ Parser<ParseHandler>::bindVar(BindData<ParseHandler>* data,
}
/*
* This definition isn't being added to the parse context's
* declarations, so make sure to indicate the need to deoptimize
* the script's arguments object. Mark the function as if it
* contained a debugger statement, which will deoptimize arguments
* as much as possible.
* Make sure to indicate the need to deoptimize the script's arguments
* object. Mark the function as if it contained a debugger statement,
* which will deoptimize arguments as much as possible.
*/
if (name == cx->names().arguments)
pc->sc->setHasDebuggerStatement();
return true;
// Find the nearest enclosing non-with scope that defined name, if
// any, for redeclaration checks below.
while (stmt && stmt->type == StmtType::WITH) {
if (stmt->enclosingScope)
stmt = LexicalLookup(pc, name, stmt->enclosingScope);
else
stmt = nullptr;
}
}
DefinitionList::Range defs = pc->decls().lookupMulti(name);
MOZ_ASSERT_IF(stmt, !defs.empty());
// TODOshu: ES6 Annex B.3.5 is not implemented.
if (defs.empty())
return pc->define(parser->tokenStream, name, pn, Definition::VAR);

View File

@ -195,7 +195,7 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext
void prepareToAddDuplicateArg(HandlePropertyName name, DefinitionNode prevDecl);
/* See the sad story in MakeDefIntoUse. */
void updateDecl(JSAtom* atom, Node newDecl);
void updateDecl(TokenStream& ts, JSAtom* atom, Node newDecl);
/*
* After a function body or module has been parsed, the parser generates the
@ -214,6 +214,21 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext
bool generateBindings(ExclusiveContext* cx, TokenStream& ts, LifoAlloc& alloc,
MutableHandle<Bindings> bindings) const;
// All global names in global scripts are added to the scope dynamically
// via JSOP_DEF{FUN,VAR,LET,CONST}, but ES6 15.1.8 specifies that if there
// are name conflicts in the script, *no* bindings from the script are
// instantiated. So, record the vars and lexical bindings to check for
// redeclarations in the prologue.
//
// Eval scripts do not need this mechanism as they always have a
// non-extensible lexical scope.
//
// Global and eval scripts may have block-scoped locals, however, which
// are allocated to the fixed part of the stack frame.
bool drainGlobalOrEvalBindings(ExclusiveContext* cx,
MutableHandle<TraceableVector<Binding>> vars,
MutableHandle<TraceableVector<Binding>> lexicals);
private:
ParseContext** parserPC; /* this points to the Parser's active pc
and holds either |this| or one of
@ -774,7 +789,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
bool matchInOrOf(bool* isForInp, bool* isForOfp);
bool checkFunctionArguments();
bool makeDefIntoUse(Definition* dn, Node pn, JSAtom* atom);
bool makeDefIntoUse(Definition* dn, Node pn, HandleAtom atom);
bool checkFunctionDefinition(HandlePropertyName funName, Node* pn, FunctionSyntaxKind kind,
bool* pbodyProcessed);
bool finishFunctionDefinition(Node pn, FunctionBox* funbox, Node body);

View File

@ -44,6 +44,18 @@ function isParseError(str)
assertEq(caught, true);
}
function isRuntimeParseError(str, arg)
{
var caught = false;
try {
(new Function("x", str))(arg);
} catch(e) {
assertEq(e instanceof TypeError || e instanceof SyntaxError, true);
caught = true;
}
assertEq(caught, true);
}
function isReferenceError(str)
{
var caught = false;
@ -141,7 +153,7 @@ test('let (y = x[1]) {let (x = x[0]) {try {let (y = "unicorns") {throw y;}} catc
isParseError('let (x = x) {try {let (x = "unicorns") eval("throw x");} catch (e) {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);
isRuntimeParseError('let (y = x) {eval("var y = 2");return y;}', 'ponies');
test('"use strict";let (y = x) {eval("var y = 2");return y;}');
test('this.y = x;let (y = 1) {return this.eval("y");}');
isParseError('let (x = 1, x = 2) {x}');
@ -215,7 +227,7 @@ 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);
isRuntimeParseError('if (x) {let y = x;eval("var y = 2");return y;}', 'ponies');
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;");}');

View File

@ -34,7 +34,6 @@ var cases = [
// dynamic bindings
"function f(s) { eval(s); @@ } f('var x = VAL');",
"function f(s) { let (x = 'fail') { eval(s); } x = VAL; @@ } f('var x;');",
"var x = VAL; function f(s) { eval('var x = 0;'); eval(s); @@ } f('delete x;');",
"function f(obj) { with (obj) { @@ } } f({x: VAL});",
"function f(obj) { with (obj) { @@ } } f(Object.create({x: VAL}));",

View File

@ -428,15 +428,15 @@ BaselineCompiler::emitPrologue()
// the scope chain is initialized.
prologueOffset_ = CodeOffsetLabel(masm.currentOffset());
// When compiling with Debugger instrumentation, set the debuggeeness of
// the frame before any operation that can call into the VM.
emitIsDebuggeeCheck();
// Initialize the scope chain before any operation that may
// call into the VM and trigger a GC.
if (!initScopeChain())
return false;
// When compiling with Debugger instrumentation, set the debuggeeness of
// the frame before any operation that can call into the VM.
emitIsDebuggeeCheck();
if (!emitStackCheck())
return false;
@ -639,9 +639,9 @@ BaselineCompiler::emitDebugPrologue()
return true;
}
typedef bool (*InitStrictEvalScopeObjectsFn)(JSContext*, BaselineFrame*);
static const VMFunction InitStrictEvalScopeObjectsInfo =
FunctionInfo<InitStrictEvalScopeObjectsFn>(jit::InitStrictEvalScopeObjects);
typedef bool (*InitGlobalOrEvalScopeObjectsFn)(JSContext*, BaselineFrame*);
static const VMFunction InitGlobalOrEvalScopeObjectsInfo =
FunctionInfo<InitGlobalOrEvalScopeObjectsFn>(jit::InitGlobalOrEvalScopeObjects);
typedef bool (*InitFunctionScopeObjectsFn)(JSContext*, BaselineFrame*);
static const VMFunction InitFunctionScopeObjectsInfo =
@ -682,18 +682,17 @@ BaselineCompiler::initScopeChain()
masm.storePtr(scope, frame.addressOfScopeChain());
} else {
// ScopeChain pointer in BaselineFrame has already been initialized
// in prologue.
// in prologue, but we need to do two more things:
//
// 1. Check for redeclaration errors
// 2. Possibly create a new call object for strict eval.
if (script->isForEval() && script->strict()) {
// Strict eval needs its own call object.
prepareVMCall();
prepareVMCall();
masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
pushArg(R0.scratchReg());
masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
pushArg(R0.scratchReg());
if (!callVMNonOp(InitStrictEvalScopeObjectsInfo, phase))
return false;
}
if (!callVMNonOp(InitGlobalOrEvalScopeObjectsInfo, phase))
return false;
}
return true;

View File

@ -10263,6 +10263,17 @@ CodeGenerator::visitThrowUninitializedLexical(LThrowUninitializedLexical* ins)
callVM(ThrowUninitializedLexicalInfo, ins);
}
typedef bool (*GlobalNameConflictsCheckFromIonFn)(JSContext*, HandleScript);
static const VMFunction GlobalNameConflictsCheckFromIonInfo =
FunctionInfo<GlobalNameConflictsCheckFromIonFn>(GlobalNameConflictsCheckFromIon);
void
CodeGenerator::visitGlobalNameConflictsCheck(LGlobalNameConflictsCheck* ins)
{
pushArg(ImmGCPtr(ins->mirRaw()->block()->info().script()));
callVM(GlobalNameConflictsCheckFromIonInfo, ins);
}
void
CodeGenerator::visitDebugger(LDebugger* ins)
{

View File

@ -331,6 +331,7 @@ class CodeGenerator : public CodeGeneratorSpecific
void visitAsmJSVoidReturn(LAsmJSVoidReturn* ret);
void visitLexicalCheck(LLexicalCheck* ins);
void visitThrowUninitializedLexical(LThrowUninitializedLexical* ins);
void visitGlobalNameConflictsCheck(LGlobalNameConflictsCheck* ins);
void visitDebugger(LDebugger* ins);
void visitNewTarget(LNewTarget* ins);
void visitArrowNewTarget(LArrowNewTarget* ins);

View File

@ -1239,6 +1239,16 @@ IonBuilder::initScopeChain(MDefinition* callee)
MOZ_ASSERT(!script()->isForEval());
MOZ_ASSERT(!script()->hasNonSyntacticScope());
scope = constant(ObjectValue(script()->global().lexicalScope()));
// Check for redeclaration errors for global scripts.
if (script()->bindings.numBodyLevelLocals() > 0) {
MGlobalNameConflictsCheck* redeclCheck = MGlobalNameConflictsCheck::New(alloc());
current->add(redeclCheck);
MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint());
if (!entryRpCopy)
return false;
redeclCheck->setResumePoint(entryRpCopy);
}
}
current->setScopeChain(scope);

View File

@ -4271,6 +4271,14 @@ LIRGenerator::visitThrowUninitializedLexical(MThrowUninitializedLexical* ins)
assignSafepoint(lir, ins);
}
void
LIRGenerator::visitGlobalNameConflictsCheck(MGlobalNameConflictsCheck* ins)
{
LGlobalNameConflictsCheck* lir = new(alloc()) LGlobalNameConflictsCheck();
add(lir, ins);
assignSafepoint(lir, ins);
}
void
LIRGenerator::visitDebugger(MDebugger* ins)
{

View File

@ -301,6 +301,7 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitUnknownValue(MUnknownValue* ins);
void visitLexicalCheck(MLexicalCheck* ins);
void visitThrowUninitializedLexical(MThrowUninitializedLexical* ins);
void visitGlobalNameConflictsCheck(MGlobalNameConflictsCheck* ins);
void visitDebugger(MDebugger* ins);
void visitNewTarget(MNewTarget* ins);
void visitArrowNewTarget(MArrowNewTarget* ins);

View File

@ -7279,6 +7279,21 @@ class MThrowUninitializedLexical : public MNullaryInstruction
}
};
// In the prologues of global and eval scripts, check for redeclarations.
class MGlobalNameConflictsCheck : public MNullaryInstruction
{
MGlobalNameConflictsCheck() {
setGuard();
}
public:
INSTRUCTION_HEADER(GlobalNameConflictsCheck)
static MGlobalNameConflictsCheck* New(TempAllocator& alloc) {
return new(alloc) MGlobalNameConflictsCheck();
}
};
// If not defined, set a global variable to |undefined|.
class MDefVar
: public MUnaryInstruction,

View File

@ -274,6 +274,7 @@ namespace jit {
_(UnknownValue) \
_(LexicalCheck) \
_(ThrowUninitializedLexical) \
_(GlobalNameConflictsCheck) \
_(Debugger) \
_(NewTarget) \
_(ArrowNewTarget)

View File

@ -845,9 +845,41 @@ GeneratorThrowOrClose(JSContext* cx, BaselineFrame* frame, Handle<GeneratorObjec
}
bool
InitStrictEvalScopeObjects(JSContext* cx, BaselineFrame* frame)
InitGlobalOrEvalScopeObjects(JSContext* cx, BaselineFrame* frame)
{
return frame->initStrictEvalScopeObjects(cx);
RootedScript script(cx, frame->script());
RootedObject varObj(cx, frame->scopeChain());
while (!varObj->isQualifiedVarObj())
varObj = varObj->enclosingScope();
if (script->isForEval()) {
// Strict eval needs its own call object.
//
// Non-strict eval may introduce 'var' bindings that conflict with
// lexical bindings in an enclosing lexical scope.
if (script->strict()) {
if (!frame->initStrictEvalScopeObjects(cx))
return false;
} else {
RootedObject scopeChain(cx, frame->scopeChain());
if (!CheckEvalDeclarationConflicts(cx, script, scopeChain, varObj))
return false;
}
} else {
Rooted<ClonedBlockObject*> lexicalScope(cx,
&NearestEnclosingExtensibleLexicalScope(frame->scopeChain()));
if (!CheckGlobalDeclarationConflicts(cx, script, lexicalScope, varObj))
return false;
}
return true;
}
bool
GlobalNameConflictsCheckFromIon(JSContext* cx, HandleScript script)
{
Rooted<ClonedBlockObject*> lexicalScope(cx, &cx->global()->lexicalScope());
return CheckGlobalDeclarationConflicts(cx, script, lexicalScope, cx->global());
}
bool

View File

@ -658,7 +658,8 @@ bool DebugAfterYield(JSContext* cx, BaselineFrame* frame);
bool GeneratorThrowOrClose(JSContext* cx, BaselineFrame* frame, Handle<GeneratorObject*> genObj,
HandleValue arg, uint32_t resumeKind);
bool InitStrictEvalScopeObjects(JSContext* cx, BaselineFrame* frame);
bool GlobalNameConflictsCheckFromIon(JSContext* cx, HandleScript script);
bool InitGlobalOrEvalScopeObjects(JSContext* cx, BaselineFrame* frame);
bool InitFunctionScopeObjects(JSContext* cx, BaselineFrame* frame);
bool NewArgumentsObject(JSContext* cx, BaselineFrame* frame, MutableHandleValue res);

View File

@ -7131,6 +7131,16 @@ class LThrowUninitializedLexical : public LCallInstructionHelper<0, 0, 0>
}
};
class LGlobalNameConflictsCheck : public LInstructionHelper<0, 0, 0>
{
public:
LIR_HEADER(GlobalNameConflictsCheck)
MGlobalNameConflictsCheck* mir() {
return mir_->toGlobalNameConflictsCheck();
}
};
class LMemoryBarrier : public LInstructionHelper<0, 0, 0>
{
private:

View File

@ -365,6 +365,7 @@
_(AssertResultT) \
_(LexicalCheck) \
_(ThrowUninitializedLexical) \
_(GlobalNameConflictsCheck) \
_(Debugger) \
_(NewTarget) \
_(ArrowNewTarget)

View File

@ -320,6 +320,13 @@ Bindings::bindingIsAliased(uint32_t bindingIndex)
return bindingArray()[bindingIndex].aliased();
}
void
Binding::trace(JSTracer* trc)
{
PropertyName* name = this->name();
TraceManuallyBarrieredEdge(trc, &name, "binding");
}
void
Bindings::trace(JSTracer* trc)
{
@ -334,10 +341,8 @@ Bindings::trace(JSTracer* trc)
if (bindingArrayUsingTemporaryStorage())
return;
for (const Binding& b : *this) {
PropertyName* name = b.name();
TraceManuallyBarrieredEdge(trc, &name, "bindingArray");
}
for (Binding& b : *this)
b.trace(trc);
}
template<XDRMode mode>

View File

@ -164,7 +164,7 @@ class YieldOffsetArray {
}
};
class Binding
class Binding : public JS::Traceable
{
// One JSScript stores one Binding per formal/variable so we use a
// packed-word representation.
@ -200,6 +200,9 @@ class Binding
bool aliased() const {
return bool(bits_ & ALIASED_BIT);
}
static void trace(Binding* self, JSTracer* trc) { self->trace(trc); }
void trace(JSTracer* trc);
};
JS_STATIC_ASSERT(sizeof(Binding) == sizeof(uintptr_t));

View File

@ -300,11 +300,9 @@ SetNameOperation(JSContext* cx, JSScript* script, jsbytecode* pc, HandleObject s
}
inline bool
DefLexicalOperation(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
HandleObject varObj, HandlePropertyName name, unsigned attrs)
CheckLexicalNameConflict(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
HandleObject varObj, HandlePropertyName name)
{
// Due to the extensibility of the global lexical scope, we must check for
// redeclaring a binding.
mozilla::Maybe<frontend::Definition::Kind> redeclKind;
RootedId id(cx, NameToId(name));
RootedShape shape(cx);
@ -321,11 +319,127 @@ DefLexicalOperation(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
if (desc.object() && desc.hasConfigurable() && !desc.configurable())
redeclKind = mozilla::Some(frontend::Definition::VAR);
}
if (redeclKind.isSome()) {
ReportRuntimeRedeclaration(cx, name, *redeclKind);
return false;
}
return true;
}
inline bool
CheckVarNameConflict(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
HandlePropertyName name)
{
if (Shape* shape = lexicalScope->lookup(cx, name)) {
ReportRuntimeRedeclaration(cx, name, shape->writable() ? frontend::Definition::LET
: frontend::Definition::CONSTANT);
return false;
}
return true;
}
inline bool
CheckVarNameConflict(JSContext* cx, Handle<CallObject*> callObj, HandlePropertyName name)
{
RootedFunction fun(cx, &callObj->callee());
RootedScript script(cx, fun->nonLazyScript());
uint32_t bodyLevelLexicalsStart = script->bindings.numVars();
for (BindingIter bi(script); !bi.done(); bi++) {
if (name == bi->name() &&
bi.isBodyLevelLexical() &&
bi.localIndex() >= bodyLevelLexicalsStart)
{
ReportRuntimeRedeclaration(cx, name,
bi->kind() == Binding::CONSTANT
? frontend::Definition::CONSTANT
: frontend::Definition::LET);
return false;
}
}
return true;
}
inline bool
CheckGlobalDeclarationConflicts(JSContext* cx, HandleScript script,
Handle<ClonedBlockObject*> lexicalScope,
HandleObject varObj)
{
// Due to the extensibility of the global lexical scope, we must check for
// redeclaring a binding.
//
// In the case of non-syntactic scope chains, we are checking
// redeclarations against the non-syntactic lexical scope and the
// variables object that the lexical scope corresponds to.
RootedPropertyName name(cx);
BindingIter bi(script);
for (uint32_t i = 0; i < script->bindings.numVars(); i++, bi++) {
name = bi->name();
if (!CheckVarNameConflict(cx, lexicalScope, name))
return false;
}
for (uint32_t i = 0; i < script->bindings.numBodyLevelLexicals(); i++, bi++) {
name = bi->name();
if (!CheckLexicalNameConflict(cx, lexicalScope, varObj, name))
return false;
}
return true;
}
inline bool
CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script,
HandleObject scopeChain, HandleObject varObj)
{
MOZ_ASSERT(script->bindings.numBodyLevelLexicals() == 0);
if (script->bindings.numVars() == 0)
return true;
RootedPropertyName name(cx);
RootedObject obj(cx, scopeChain);
Rooted<ClonedBlockObject*> lexicalScope(cx);
// ES6 18.2.1.2 step d
//
// Check that a direct eval will not hoist 'var' bindings over lexical
// bindings with the same name.
while (obj != varObj) {
if (obj->is<ClonedBlockObject>()) {
lexicalScope = &obj->as<ClonedBlockObject>();
for (BindingIter bi(script); !bi.done(); bi++) {
name = bi->name();
if (!CheckVarNameConflict(cx, lexicalScope, name))
return false;
}
}
obj = obj->enclosingScope();
}
if (varObj->is<CallObject>()) {
Rooted<CallObject*> callObj(cx, &varObj->as<CallObject>());
for (BindingIter bi(script); !bi.done(); bi++) {
name = bi->name();
if (!CheckVarNameConflict(cx, callObj, name))
return false;
}
}
return true;
}
inline bool
DefLexicalOperation(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
HandleObject varObj, HandlePropertyName name, unsigned attrs)
{
// Redeclaration checks should have already been done.
MOZ_ASSERT(CheckLexicalNameConflict(cx, lexicalScope, varObj, name));
RootedId id(cx, NameToId(name));
RootedValue uninitialized(cx, MagicValue(JS_UNINITIALIZED_LEXICAL));
return NativeDefineProperty(cx, lexicalScope, id, uninitialized, nullptr, nullptr, attrs);
}
@ -380,15 +494,15 @@ DefVarOperation(JSContext* cx, HandleObject varobj, HandlePropertyName dn, unsig
{
MOZ_ASSERT(varobj->isQualifiedVarObj());
// Due to the extensibility of the global lexical scope, we must check for
// redeclaring a lexical binding.
if (varobj == cx->global()) {
if (Shape* shape = cx->global()->lexicalScope().lookup(cx, dn)) {
ReportRuntimeRedeclaration(cx, dn, shape->writable() ? frontend::Definition::LET
: frontend::Definition::CONSTANT);
return false;
}
#ifdef DEBUG
// Per spec, it is an error to redeclare a lexical binding. This should
// have already been checked.
if (JS_HasExtensibleLexicalScope(varobj)) {
Rooted<ClonedBlockObject*> lexicalScope(cx);
lexicalScope = &JS_ExtensibleLexicalScope(varobj)->as<ClonedBlockObject>();
MOZ_ASSERT(CheckVarNameConflict(cx, lexicalScope, dn));
}
#endif
RootedShape prop(cx);
RootedObject obj2(cx);

View File

@ -220,12 +220,30 @@ InterpreterFrame::prologue(JSContext* cx)
return false;
pushOnScopeChain(*callobj);
flags_ |= HAS_CALL_OBJ;
} else {
// Non-strict eval may introduce var bindings that conflict with
// lexical bindings in an enclosing lexical scope.
RootedObject varObjRoot(cx, &varObj());
if (!CheckEvalDeclarationConflicts(cx, script, scopeChain(), varObjRoot))
return false;
}
return probes::EnterScript(cx, script, nullptr, this);
}
if (isGlobalFrame())
if (isGlobalFrame()) {
Rooted<ClonedBlockObject*> lexicalScope(cx);
RootedObject varObjRoot(cx);
if (script->hasNonSyntacticScope()) {
lexicalScope = &extensibleLexicalScope();
varObjRoot = &varObj();
} else {
lexicalScope = &cx->global()->lexicalScope();
varObjRoot = cx->global();
}
if (!CheckGlobalDeclarationConflicts(cx, script, lexicalScope, varObjRoot))
return false;
return probes::EnterScript(cx, script, nullptr, this);
}
AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain());