diff --git a/js/src/jit-test/tests/debug/Frame-eval-30.js b/js/src/jit-test/tests/debug/Frame-eval-30.js new file mode 100644 index 00000000000..f99912c452b --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-30.js @@ -0,0 +1,19 @@ +// Test that Debugger.Frame.eval correctly throws on redeclaration. + +load(libdir + "evalInFrame.js"); + +let x; + +function f() { + evalInFrame(1, "var x;"); +} + +var log = ""; + +try { + f(); +} catch (e) { + log += "e"; +} + +assertEq(log, "e"); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 18a1a261a29..878510f205f 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -328,140 +328,6 @@ SetNameOperation(JSContext* cx, JSScript* script, jsbytecode* pc, HandleObject s return ok && result.checkStrictErrorOrWarning(cx, scope, id, strict); } -inline bool -CheckLexicalNameConflict(JSContext* cx, Handle lexicalScope, - HandleObject varObj, HandlePropertyName name) -{ - mozilla::Maybe redeclKind; - RootedId id(cx, NameToId(name)); - RootedShape shape(cx); - if ((shape = lexicalScope->lookup(cx, name))) { - redeclKind = mozilla::Some(shape->writable() ? frontend::Definition::LET - : frontend::Definition::CONSTANT); - } else if (varObj->isNative() && (shape = varObj->as().lookup(cx, name))) { - if (!shape->configurable()) - redeclKind = mozilla::Some(frontend::Definition::VAR); - } else { - Rooted desc(cx); - if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc)) - return false; - 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 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 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 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) -{ - // We don't need to check body-level lexical bindings for conflict. Eval - // scripts always execute under their own lexical scope. - if (script->bindings.numVars() == 0) - return true; - - RootedPropertyName name(cx); - RootedObject obj(cx, scopeChain); - Rooted 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()) { - lexicalScope = &obj->as(); - for (BindingIter bi(script); !bi.done(); bi++) { - name = bi->name(); - if (!CheckVarNameConflict(cx, lexicalScope, name)) - return false; - } - } - obj = obj->enclosingScope(); - } - - if (varObj->is()) { - Rooted callObj(cx, &varObj->as()); - 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 lexicalScope, HandleObject varObj, HandlePropertyName name, unsigned attrs) diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 3ed84c009d2..95de31a76c1 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -14,6 +14,8 @@ #include "builtin/ModuleObject.h" +#include "frontend/ParseNode.h" + #include "vm/ArgumentsObject.h" #include "vm/GlobalObject.h" #include "vm/ProxyObject.h" @@ -2923,6 +2925,152 @@ js::StaticScopeChainLength(JSObject* staticScope) return length; } +bool +js::CheckLexicalNameConflict(JSContext* cx, Handle lexicalScope, + HandleObject varObj, HandlePropertyName name) +{ + mozilla::Maybe redeclKind; + RootedId id(cx, NameToId(name)); + RootedShape shape(cx); + if ((shape = lexicalScope->lookup(cx, name))) { + redeclKind = mozilla::Some(shape->writable() ? frontend::Definition::LET + : frontend::Definition::CONSTANT); + } else if (varObj->isNative() && (shape = varObj->as().lookup(cx, name))) { + if (!shape->configurable()) + redeclKind = mozilla::Some(frontend::Definition::VAR); + } else { + Rooted desc(cx); + if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc)) + return false; + if (desc.object() && desc.hasConfigurable() && !desc.configurable()) + redeclKind = mozilla::Some(frontend::Definition::VAR); + } + + if (redeclKind.isSome()) { + ReportRuntimeRedeclaration(cx, name, *redeclKind); + return false; + } + + return true; +} + +bool +js::CheckVarNameConflict(JSContext* cx, Handle 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; +} + +static bool +CheckVarNameConflict(JSContext* cx, Handle 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; +} + +bool +js::CheckGlobalDeclarationConflicts(JSContext* cx, HandleScript script, + Handle 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; +} + +template +static bool +CheckVarNameConflictsInScope(JSContext* cx, HandleScript script, HandleObject obj) +{ + Rooted scope(cx); + + // We return true when the scope object is not ScopeT below, because + // ScopeT is either ClonedBlockObject or CallObject. No other scope + // objects can contain lexical bindings, and there are no other overloads + // for CheckVarNameConflict. + + if (obj->is()) + scope = &obj->as(); + else if (obj->is() && obj->as().scope().is()) + scope = &obj->as().scope().as(); + else + return true; + + RootedPropertyName name(cx); + + for (BindingIter bi(script); !bi.done(); bi++) { + name = bi->name(); + if (!CheckVarNameConflict(cx, scope, name)) + return false; + } + + return true; +} + +bool +js::CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script, + HandleObject scopeChain, HandleObject varObj) +{ + // We don't need to check body-level lexical bindings for conflict. Eval + // scripts always execute under their own lexical scope. + if (script->bindings.numVars() == 0) + return true; + + RootedObject obj(cx, scopeChain); + + // 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 (!CheckVarNameConflictsInScope(cx, script, obj)) + return false; + obj = obj->enclosingScope(); + } + + return CheckVarNameConflictsInScope(cx, script, varObj); +} + #ifdef DEBUG void diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index edeea5c65c1..064702619b2 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -1447,6 +1447,19 @@ CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain, bool HasNonSyntacticStaticScopeChain(JSObject* staticScope); uint32_t StaticScopeChainLength(JSObject* staticScope); +bool CheckVarNameConflict(JSContext* cx, Handle lexicalScope, + HandlePropertyName name); + +bool CheckLexicalNameConflict(JSContext* cx, Handle lexicalScope, + HandleObject varObj, HandlePropertyName name); + +bool CheckGlobalDeclarationConflicts(JSContext* cx, HandleScript script, + Handle lexicalScope, + HandleObject varObj); + +bool CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script, + HandleObject scopeChain, HandleObject varObj); + #ifdef DEBUG void DumpStaticScopeChain(JSScript* script); void DumpStaticScopeChain(JSObject* staticScope);