From 31d13da12c8ddb59bb29f6d876d83afe1402ab3b Mon Sep 17 00:00:00 2001 From: Shu-yu Guo Date: Tue, 6 Oct 2015 14:00:29 -0700 Subject: [PATCH] Bug 1202902 - Support non-syntactic extensible lexical scopes. (r=billm) --- js/public/MemoryMetrics.h | 3 +- js/src/builtin/Eval.cpp | 15 +- js/src/builtin/TestingFunctions.cpp | 24 ++- ...NewScript-ExecuteInGlobalAndReturnScope.js | 6 +- js/src/jit/BaselineCompiler.cpp | 7 +- js/src/jit/BaselineIC.cpp | 7 +- js/src/jit/CodeGenerator.cpp | 2 +- js/src/jit/IonBuilder.cpp | 2 + js/src/jit/IonCaches.cpp | 3 +- js/src/jit/VMFunctions.cpp | 21 +++ js/src/jit/VMFunctions.h | 2 + js/src/jsapi.cpp | 37 ++++ js/src/jsapi.h | 9 + js/src/jscompartment.cpp | 52 +++++- js/src/jscompartment.h | 16 +- js/src/jsfriendapi.h | 9 +- js/src/vm/Debugger.cpp | 13 +- js/src/vm/Interpreter-inl.h | 30 ++-- js/src/vm/Interpreter.cpp | 18 +- js/src/vm/MemoryMetrics.cpp | 3 +- js/src/vm/ScopeObject-inl.h | 8 + js/src/vm/ScopeObject.cpp | 48 ++++- js/src/vm/ScopeObject.h | 166 +++++++++++++++--- js/src/vm/Stack-inl.h | 8 +- js/src/vm/Stack.cpp | 6 +- js/src/vm/Stack.h | 4 +- js/src/vm/TypeInference.cpp | 6 +- js/xpconnect/loader/mozJSComponentLoader.cpp | 59 ++++++- js/xpconnect/loader/mozJSComponentLoader.h | 3 + js/xpconnect/src/XPCShellImpl.cpp | 1 + testing/xpcshell/head.js | 4 +- 31 files changed, 507 insertions(+), 85 deletions(-) diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index 309691cd74a..7fae00000a0 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -696,7 +696,8 @@ struct CompartmentStats macro(Other, MallocHeap, objectMetadataTable) \ macro(Other, MallocHeap, crossCompartmentWrappersTable) \ macro(Other, MallocHeap, regexpCompartment) \ - macro(Other, MallocHeap, savedStacksSet) + macro(Other, MallocHeap, savedStacksSet) \ + macro(Other, MallocHeap, nonSyntacticLexicalScopesTable) CompartmentStats() : FOR_EACH_SIZE(ZERO_SIZE) diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp index 85fb4bdd6c3..6129cd127b5 100644 --- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -495,8 +495,10 @@ js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScri MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope()); RootedScript script(cx, scriptArg); + Rooted globalRoot(cx, &global->as()); if (script->compartment() != cx->compartment()) { - Rooted staticScope(cx, StaticNonSyntacticScopeObjects::create(cx, nullptr)); + Rooted staticScope(cx, &globalRoot->lexicalScope().staticBlock()); + staticScope = StaticNonSyntacticScopeObjects::create(cx, staticScope); if (!staticScope) return false; script = CloneGlobalScript(cx, staticScope, script); @@ -506,8 +508,15 @@ js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScri Debugger::onNewScript(cx, script); } - Rooted globalRoot(cx, &global->as()); - Rooted scope(cx, NonSyntacticVariablesObject::create(cx, globalRoot)); + Rooted globalLexical(cx, &globalRoot->lexicalScope()); + Rooted scope(cx, NonSyntacticVariablesObject::create(cx, globalLexical)); + if (!scope) + return false; + + // Unlike the non-syntactic scope chain API used by the subscript loader, + // this API creates a fresh block scope each time. + RootedObject enclosingStaticScope(cx, script->enclosingStaticScope()); + scope = ClonedBlockObject::createNonSyntactic(cx, enclosingStaticScope, scope); if (!scope) return false; diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index edf6e318309..665d9970f6b 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -2344,7 +2344,8 @@ EvalReturningScope(JSContext* cx, unsigned argc, Value* vp) global = JS::CurrentGlobalOrNull(cx); } - RootedObject scope(cx); + RootedObject varObj(cx); + RootedObject lexicalScope(cx); { // If we're switching globals here, ExecuteInGlobalAndReturnScope will @@ -2352,14 +2353,29 @@ EvalReturningScope(JSContext* cx, unsigned argc, Value* vp) // executing it. AutoCompartment ac(cx, global); - if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &scope)) + if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &lexicalScope)) return false; + + varObj = lexicalScope->enclosingScope(); } - if (!cx->compartment()->wrap(cx, &scope)) + RootedObject rv(cx, JS_NewPlainObject(cx)); + if (!rv) return false; - args.rval().setObject(*scope); + RootedValue varObjVal(cx, ObjectValue(*varObj)); + if (!cx->compartment()->wrap(cx, &varObjVal)) + return false; + if (!JS_SetProperty(cx, rv, "vars", varObjVal)) + return false; + + RootedValue lexicalScopeVal(cx, ObjectValue(*lexicalScope)); + if (!cx->compartment()->wrap(cx, &lexicalScopeVal)) + return false; + if (!JS_SetProperty(cx, rv, "lexicals", lexicalScopeVal)) + return false; + + args.rval().setObject(*rv); return true; } diff --git a/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js b/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js index a0dca8a3fe3..7fb6ebf3a35 100644 --- a/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js +++ b/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js @@ -25,8 +25,8 @@ dbg.onDebuggerStatement = function (frame) { }; assertEq(log, ''); -var evalScope = g.evalReturningScope("canary = 'dead'; debugger; // nee", g2); +var evalScopes = g.evalReturningScope("canary = 'dead'; let lex = 42; debugger; // nee", g2); assertEq(log, 'ecbd'); assertEq(canary, 42); -assertEq(evalScope.canary, 'dead'); - +assertEq(evalScopes.vars.canary, 'dead'); +assertEq(evalScopes.lexicals.lex, 42); diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index c4daec484a1..166e6c97839 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -2481,8 +2481,8 @@ BaselineCompiler::emit_JSOP_DEFVAR() return callVM(DefVarInfo); } -typedef bool (*DefLexicalFn)(JSContext*, HandlePropertyName, unsigned); -static const VMFunction DefLexicalInfo = FunctionInfo(DefLexicalOperation); +typedef bool (*DefLexicalFn)(JSContext*, HandlePropertyName, unsigned, HandleObject); +static const VMFunction DefLexicalInfo = FunctionInfo(DefLexical); bool BaselineCompiler::emit_JSOP_DEFCONST() @@ -2500,8 +2500,11 @@ BaselineCompiler::emit_JSOP_DEFLET() attrs |= JSPROP_READONLY; MOZ_ASSERT(attrs <= UINT32_MAX); + masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); + prepareVMCall(); + pushArg(R0.scratchReg()); pushArg(Imm32(attrs)); pushArg(ImmGCPtr(script->getName(pc))); diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index fa20f1f7b5e..cac90c6e9de 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -7617,7 +7617,12 @@ DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_ obj->as().setAliasedVar(cx, ScopeCoordinate(pc), name, rhs); } else if (op == JSOP_INITGLEXICAL) { RootedValue v(cx, rhs); - InitGlobalLexicalOperation(cx, script, pc, v); + ClonedBlockObject* lexicalScope; + if (script->hasNonSyntacticScope()) + lexicalScope = &NearestEnclosingExtensibleLexicalScope(frame->scopeChain()); + else + lexicalScope = &cx->global()->lexicalScope(); + InitGlobalLexicalOperation(cx, lexicalScope, script, pc, v); } else { MOZ_ASSERT(op == JSOP_SETPROP || op == JSOP_STRICTSETPROP); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 6cf16cff7ad..0795880d7b8 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -3729,7 +3729,7 @@ CodeGenerator::visitDefVar(LDefVar* lir) } typedef bool (*DefLexicalFn)(JSContext*, HandlePropertyName, unsigned); -static const VMFunction DefLexicalInfo = FunctionInfo(DefLexicalOperation); +static const VMFunction DefLexicalInfo = FunctionInfo(DefGlobalLexical); void CodeGenerator::visitDefLexical(LDefLexical* lir) diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index c518e7b6062..4b18fc0b94a 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1768,6 +1768,7 @@ IonBuilder::inspectOpcode(JSOp op) return true; case JSOP_INITGLEXICAL: { + MOZ_ASSERT(!script()->hasNonSyntacticScope()); MDefinition* value = current->pop(); current->push(constant(ObjectValue(script()->global().lexicalScope()))); current->push(value); @@ -12661,6 +12662,7 @@ IonBuilder::jsop_defvar(uint32_t index) bool IonBuilder::jsop_deflexical(uint32_t index) { + MOZ_ASSERT(!script()->hasNonSyntacticScope()); MOZ_ASSERT(JSOp(*pc) == JSOP_DEFLET || JSOp(*pc) == JSOP_DEFCONST); PropertyName* name = script()->getName(index); diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index 84dc42ea6c1..5a664eda745 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -3341,7 +3341,8 @@ SetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex RootedScript script(cx); jsbytecode* pc; cache.getScriptedLocation(&script, &pc); - InitGlobalLexicalOperation(cx, script, pc, value); + MOZ_ASSERT(!script->hasNonSyntacticScope()); + InitGlobalLexicalOperation(cx, &cx->global()->lexicalScope(), script, pc, value); } else { if (!SetProperty(cx, obj, name, value, cache.strict(), cache.pc())) return false; diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 6f6185b2c51..8b05380bb4d 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -177,6 +177,27 @@ DefVar(JSContext* cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeC return DefVarOperation(cx, obj, dn, attrs); } +bool +DefLexical(JSContext* cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain) +{ + // Find the extensible lexical scope. + Rooted lexical(cx, &NearestEnclosingExtensibleLexicalScope(scopeChain)); + + // Find the variables object. + RootedObject varObj(cx, scopeChain); + while (!varObj->isQualifiedVarObj()) + varObj = varObj->enclosingScope(); + + return DefLexicalOperation(cx, lexical, varObj, dn, attrs); +} + +bool +DefGlobalLexical(JSContext* cx, HandlePropertyName dn, unsigned attrs) +{ + Rooted globalLexical(cx, &cx->global()->lexicalScope()); + return DefLexicalOperation(cx, globalLexical, cx->global(), dn, attrs); +} + bool MutatePrototype(JSContext* cx, HandlePlainObject obj, HandleValue value) { diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 14373f243e9..1af58e0ad82 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -589,6 +589,8 @@ bool CheckOverRecursedWithExtra(JSContext* cx, BaselineFrame* frame, uint32_t extra, uint32_t earlyCheck); bool DefVar(JSContext* cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain); +bool DefLexical(JSContext* cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain); +bool DefGlobalLexical(JSContext* cx, HandlePropertyName dn, unsigned attrs); bool MutatePrototype(JSContext* cx, HandlePlainObject obj, HandleValue value); bool InitProp(JSContext* cx, HandleObject obj, HandlePropertyName name, HandleValue value, jsbytecode* pc); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 1e4e5861b44..0e796827174 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1458,6 +1458,30 @@ JS_IsGlobalObject(JSObject* obj) return obj->is(); } +extern JS_PUBLIC_API(JSObject*) +JS_GlobalLexicalScope(JSObject* obj) +{ + return &obj->as().lexicalScope(); +} + +extern JS_PUBLIC_API(bool) +JS_HasExtensibleLexicalScope(JSObject* obj) +{ + return obj->is() || obj->compartment()->getNonSyntacticLexicalScope(obj); +} + +extern JS_PUBLIC_API(JSObject*) +JS_ExtensibleLexicalScope(JSObject* obj) +{ + JSObject* lexical = nullptr; + if (obj->is()) + lexical = JS_GlobalLexicalScope(obj); + else + lexical = obj->compartment()->getNonSyntacticLexicalScope(obj); + MOZ_ASSERT(lexical); + return lexical; +} + JS_PUBLIC_API(JSObject*) JS_GetGlobalForCompartmentOrNull(JSContext* cx, JSCompartment* c) { @@ -3456,6 +3480,19 @@ CreateNonSyntacticScopeChain(JSContext* cx, AutoObjectVector& scopeChain, // See JSObject::isQualifiedVarObj. if (!dynamicScopeObj->setQualifiedVarObj(cx)) return false; + + // Also get a non-syntactic lexical scope to capture 'let' and 'const' + // bindings. To persist lexical bindings, we have a 1-1 mapping with + // the final unwrapped dynamic scope object (the scope that stores the + // 'var' bindings) and the lexical scope. + // + // TODOshu: disallow the subscript loader from using non-distinguished + // objects as dynamic scopes. + dynamicScopeObj.set( + cx->compartment()->getOrCreateNonSyntacticLexicalScope(cx, staticScopeObj, + dynamicScopeObj)); + if (!dynamicScopeObj) + return false; } return true; diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 176d6191b89..9e19ca7365e 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1572,6 +1572,15 @@ JS_GetGlobalForObject(JSContext* cx, JSObject* obj); extern JS_PUBLIC_API(bool) JS_IsGlobalObject(JSObject* obj); +extern JS_PUBLIC_API(JSObject*) +JS_GlobalLexicalScope(JSObject* obj); + +extern JS_PUBLIC_API(bool) +JS_HasExtensibleLexicalScope(JSObject* obj); + +extern JS_PUBLIC_API(JSObject*) +JS_ExtensibleLexicalScope(JSObject* obj); + /* * May return nullptr, if |c| never had a global (e.g. the atoms compartment), * or if |c|'s global has been collected. diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index cb64f02b3a5..936fec6eedf 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -66,6 +66,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = selfHostingScriptSource(nullptr), objectMetadataTable(nullptr), lazyArrayBuffers(nullptr), + nonSyntacticLexicalScopes_(nullptr), gcIncomingGrayPointers(nullptr), gcPreserveJitCode(options.preserveJitCode()), debugModeBits(0), @@ -104,6 +105,7 @@ JSCompartment::~JSCompartment() js_delete(debugScopes); js_delete(objectMetadataTable); js_delete(lazyArrayBuffers); + js_delete(nonSyntacticLexicalScopes_), js_free(enumerators); runtime_->numCompartments--; @@ -494,6 +496,48 @@ JSCompartment::wrap(JSContext* cx, MutableHandle desc) return wrap(cx, desc.value()); } +ClonedBlockObject* +JSCompartment::getOrCreateNonSyntacticLexicalScope(JSContext* cx, + HandleObject enclosingStatic, + HandleObject enclosingScope) +{ + if (!nonSyntacticLexicalScopes_) { + nonSyntacticLexicalScopes_ = cx->new_(cx); + if (!nonSyntacticLexicalScopes_ || !nonSyntacticLexicalScopes_->init()) + return nullptr; + } + + // The key is the unwrapped dynamic scope, as we may be creating different + // DynamicWithObject wrappers each time. + MOZ_ASSERT(!enclosingScope->as().isSyntactic()); + RootedObject key(cx, &enclosingScope->as().object()); + RootedObject lexicalScope(cx, nonSyntacticLexicalScopes_->lookup(key)); + + if (!lexicalScope) { + lexicalScope = ClonedBlockObject::createNonSyntactic(cx, enclosingStatic, enclosingScope); + if (!lexicalScope) + return nullptr; + if (!nonSyntacticLexicalScopes_->add(cx, key, lexicalScope)) + return nullptr; + } + + return &lexicalScope->as(); +} + +ClonedBlockObject* +JSCompartment::getNonSyntacticLexicalScope(JSObject* enclosingScope) const +{ + if (!nonSyntacticLexicalScopes_) + return nullptr; + if (!enclosingScope->is()) + return nullptr; + JSObject* key = &enclosingScope->as().object(); + JSObject* lexicalScope = nonSyntacticLexicalScopes_->lookup(key); + if (!lexicalScope) + return nullptr; + return &lexicalScope->as(); +} + void JSCompartment::traceOutgoingCrossCompartmentWrappers(JSTracer* trc) { @@ -599,6 +643,9 @@ JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime t MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around"); } } + + if (nonSyntacticLexicalScopes_) + nonSyntacticLexicalScopes_->trace(trc); } void @@ -1052,7 +1099,8 @@ JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* objectMetadataTablesArg, size_t* crossCompartmentWrappersArg, size_t* regexpCompartment, - size_t* savedStacksSet) + size_t* savedStacksSet, + size_t* nonSyntacticLexicalScopesArg) { *compartmentObject += mallocSizeOf(this); objectGroups.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables, @@ -1068,6 +1116,8 @@ JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, *crossCompartmentWrappersArg += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf); *regexpCompartment += regExps.sizeOfExcludingThis(mallocSizeOf); *savedStacksSet += savedStacks_.sizeOfExcludingThis(mallocSizeOf); + if (nonSyntacticLexicalScopes_) + *nonSyntacticLexicalScopesArg += nonSyntacticLexicalScopes_->sizeOfIncludingThis(mallocSizeOf); } void diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 69ce4af297c..11b89b48e04 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -29,6 +29,7 @@ template class ComponentFinder; } // namespace gc struct NativeIterator; +class ClonedBlockObject; /* * A single-entry cache for some base-10 double-to-string conversions. This @@ -397,7 +398,8 @@ struct JSCompartment size_t* objectMetadataTables, size_t* crossCompartmentWrappers, size_t* regexpCompartment, - size_t* savedStacksSet); + size_t* savedStacksSet, + size_t* nonSyntacticLexicalScopes); /* * Shared scope property tree, and arena-pool for allocating its nodes. @@ -442,6 +444,13 @@ struct JSCompartment // All unboxed layouts in the compartment. mozilla::LinkedList unboxedLayouts; + private: + // All non-syntactic lexical scopes in the compartment. These are kept in + // a map because when loading scripts into a non-syntactic scope, we need + // to use the same lexical scope to persist lexical bindings. + js::ObjectWeakMap* nonSyntacticLexicalScopes_; + + public: /* During GC, stores the index of this compartment in rt->compartments. */ unsigned gcIndex; @@ -511,6 +520,11 @@ struct JSCompartment explicit WrapperEnum(JSCompartment* c) : js::WrapperMap::Enum(c->crossCompartmentWrappers) {} }; + js::ClonedBlockObject* getOrCreateNonSyntacticLexicalScope(JSContext* cx, + js::HandleObject enclosingStatic, + js::HandleObject enclosingScope); + js::ClonedBlockObject* getNonSyntacticLexicalScope(JSObject* enclosingScope) const; + /* * This method traces data that is live iff we know that this compartment's * global is still live. diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 2dce032dd6e..2f5e5127b29 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -2795,13 +2795,12 @@ SetJitExceptionHandler(JitExceptionHandler handler); #endif /* - * Get the object underlying the object environment (in the ES - * NewObjectEnvironment) sense for a given function. If the function is not - * scripted or does not have an object environment, just returns the function's - * parent. + * Get the nearest enclosing with scope object for a given function. If the + * function is not scripted or is not enclosed by a with scope, returns the + * global. */ extern JS_FRIEND_API(JSObject*) -GetObjectEnvironmentObjectForFunction(JSFunction* fun); +GetNearestEnclosingWithScopeObjectForFunction(JSFunction* fun); /* * Get the first SavedFrame object in this SavedFrame stack whose principals are diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index b631e8c077b..23a5de92b1a 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -6525,7 +6525,12 @@ EvaluateInEnv(JSContext* cx, Handle env, HandleValue thisv, AbstractFrameP */ Rooted enclosingStaticScope(cx); if (!IsGlobalLexicalScope(env)) { - enclosingStaticScope = StaticNonSyntacticScopeObjects::create(cx, nullptr); + // If we are doing a global evalWithBindings, we will still need to + // link the static global lexical scope to the static non-syntactic + // scope. + if (IsGlobalLexicalScope(env->enclosingScope())) + enclosingStaticScope = &cx->global()->lexicalScope().staticBlock(); + enclosingStaticScope = StaticNonSyntacticScopeObjects::create(cx, enclosingStaticScope); if (!enclosingStaticScope) return false; } else { @@ -6675,9 +6680,9 @@ DebuggerGenericEval(JSContext* cx, const char* fullMethodName, const Value& code return false; } else { /* - * Use the global as 'this'. If the global is an inner object, it - * should have a thisObject hook that returns the appropriate outer - * object. + * Use the global lexical scope as 'this'. If the global is an inner + * object, it should have a thisObject hook that returns the + * appropriate outer object. */ JSObject* thisObj = GetThisObject(cx, scope); if (!thisObj) diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index b6ef9630e22..cf370270d36 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -300,19 +300,18 @@ SetNameOperation(JSContext* cx, JSScript* script, jsbytecode* pc, HandleObject s } inline bool -DefLexicalOperation(JSContext* cx, HandlePropertyName name, unsigned attrs) +DefLexicalOperation(JSContext* cx, Handle lexicalScope, + HandleObject varObj, HandlePropertyName name, unsigned attrs) { - Rooted globalLexical(cx, &cx->global()->lexicalScope()); - // Due to the extensibility of the global lexical scope, we must check for // redeclaring a binding. mozilla::Maybe redeclKind; RootedId id(cx, NameToId(name)); RootedShape shape(cx); - if ((shape = globalLexical->lookup(cx, name))) { + if ((shape = lexicalScope->lookup(cx, name))) { redeclKind = mozilla::Some(shape->writable() ? frontend::Definition::LET : frontend::Definition::CONST); - } else if ((shape = cx->global()->lookup(cx, name))) { + } else if (varObj->isNative() && (shape = varObj->as().lookup(cx, name))) { if (!shape->configurable()) redeclKind = mozilla::Some(frontend::Definition::VAR); } else { @@ -332,7 +331,8 @@ DefLexicalOperation(JSContext* cx, HandlePropertyName name, unsigned attrs) } inline bool -DefLexicalOperation(JSContext* cx, JSScript* script, jsbytecode* pc) +DefLexicalOperation(JSContext* cx, ClonedBlockObject* lexicalScopeArg, + JSObject* varObjArg, JSScript* script, jsbytecode* pc) { MOZ_ASSERT(*pc == JSOP_DEFLET || *pc == JSOP_DEFCONST); RootedPropertyName name(cx, script->getName(pc)); @@ -341,17 +341,25 @@ DefLexicalOperation(JSContext* cx, JSScript* script, jsbytecode* pc) if (*pc == JSOP_DEFCONST) attrs |= JSPROP_READONLY; - return DefLexicalOperation(cx, name, attrs); + Rooted lexicalScope(cx, lexicalScopeArg); + RootedObject varObj(cx, varObjArg); + MOZ_ASSERT_IF(!script->hasNonSyntacticScope(), + lexicalScope == &cx->global()->lexicalScope() && varObj == cx->global()); + + return DefLexicalOperation(cx, lexicalScope, varObj, name, attrs); } inline void -InitGlobalLexicalOperation(JSContext* cx, JSScript* script, jsbytecode* pc, HandleValue value) +InitGlobalLexicalOperation(JSContext* cx, ClonedBlockObject* lexicalScopeArg, + JSScript* script, jsbytecode* pc, HandleValue value) { + MOZ_ASSERT_IF(!script->hasNonSyntacticScope(), + lexicalScopeArg == &cx->global()->lexicalScope()); MOZ_ASSERT(*pc == JSOP_INITGLEXICAL); - Rooted globalLexical(cx, &cx->global()->lexicalScope()); - RootedShape shape(cx, globalLexical->lookup(cx, script->getName(pc))); + Rooted lexicalScope(cx, lexicalScopeArg); + RootedShape shape(cx, lexicalScope->lookup(cx, script->getName(pc))); MOZ_ASSERT(shape); - globalLexical->setSlot(shape->slot(), value); + lexicalScope->setSlot(shape->slot(), value); } inline bool diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 4fd40e6fd91..24949a54be4 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -3429,8 +3429,13 @@ END_CASE(JSOP_INITALIASEDLEXICAL) CASE(JSOP_INITGLEXICAL) { + ClonedBlockObject* lexicalScope; + if (script->hasNonSyntacticScope()) + lexicalScope = ®S.fp()->extensibleLexicalScope(); + else + lexicalScope = &cx->global()->lexicalScope(); HandleValue value = REGS.stackHandleAt(-1); - InitGlobalLexicalOperation(cx, script, REGS.pc, value); + InitGlobalLexicalOperation(cx, lexicalScope, script, REGS.pc, value); } END_CASE(JSOP_INITGLEXICAL) @@ -3502,7 +3507,16 @@ END_CASE(JSOP_DEFVAR) CASE(JSOP_DEFCONST) CASE(JSOP_DEFLET) { - if (!DefLexicalOperation(cx, script, REGS.pc)) + ClonedBlockObject* lexicalScope; + JSObject* varObj; + if (script->hasNonSyntacticScope()) { + lexicalScope = ®S.fp()->extensibleLexicalScope(); + varObj = ®S.fp()->varObj(); + } else { + lexicalScope = &cx->global()->lexicalScope(); + varObj = cx->global(); + } + if (!DefLexicalOperation(cx, lexicalScope, varObj, script, REGS.pc)) goto error; } END_CASE(JSOP_DEFLET) diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index 3be957eb97c..1e9c53fa598 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -342,7 +342,8 @@ StatsCompartmentCallback(JSRuntime* rt, void* data, JSCompartment* compartment) &cStats.objectMetadataTable, &cStats.crossCompartmentWrappersTable, &cStats.regexpCompartment, - &cStats.savedStacksSet); + &cStats.savedStacksSet, + &cStats.nonSyntacticLexicalScopesTable); } static void diff --git a/js/src/vm/ScopeObject-inl.h b/js/src/vm/ScopeObject-inl.h index d54ef5933a9..84ab6bda721 100644 --- a/js/src/vm/ScopeObject-inl.h +++ b/js/src/vm/ScopeObject-inl.h @@ -16,6 +16,14 @@ namespace js { +inline ClonedBlockObject& +NearestEnclosingExtensibleLexicalScope(JSObject* scope) +{ + while (!IsExtensibleLexicalScope(scope)) + scope = scope->enclosingScope(); + return scope->as(); +} + inline void ScopeObject::setAliasedVar(JSContext* cx, ScopeCoordinate sc, PropertyName* name, const Value& v) { diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 26ad1c22a42..b7cd20faaab 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -782,8 +782,10 @@ const Class StaticNonSyntacticScopeObjects::class_ = { }; /* static */ NonSyntacticVariablesObject* -NonSyntacticVariablesObject::create(JSContext* cx, Handle global) +NonSyntacticVariablesObject::create(JSContext* cx, Handle globalLexical) { + MOZ_ASSERT(globalLexical->isGlobal()); + Rooted obj(cx, NewObjectWithNullTaggedProto(cx, TenuredObject, BaseShape::DELEGATE)); @@ -794,7 +796,7 @@ NonSyntacticVariablesObject::create(JSContext* cx, Handle global) if (!obj->setQualifiedVarObj(cx)) return nullptr; - obj->setEnclosingScope(global); + obj->setEnclosingScope(globalLexical); return obj; } @@ -869,6 +871,26 @@ ClonedBlockObject::createGlobal(JSContext* cx, Handle global) return lexical; } +/* static */ ClonedBlockObject* +ClonedBlockObject::createNonSyntactic(JSContext* cx, HandleObject enclosingStatic, + HandleObject enclosingScope) +{ + MOZ_ASSERT(enclosingStatic->is()); + MOZ_ASSERT(!IsSyntacticScope(enclosingScope)); + + Rooted staticLexical(cx, StaticBlockObject::create(cx)); + if (!staticLexical) + return nullptr; + + staticLexical->setLocalOffset(UINT32_MAX); + staticLexical->initEnclosingScope(enclosingStatic); + Rooted lexical(cx, ClonedBlockObject::create(cx, staticLexical, + enclosingScope)); + if (!lexical) + return nullptr; + return lexical; +} + /* static */ ClonedBlockObject* ClonedBlockObject::createHollowForDebug(JSContext* cx, Handle block) { @@ -987,9 +1009,11 @@ static JSObject* block_ThisObject(JSContext* cx, HandleObject obj) { // No other block objects should ever get passed to the 'this' object - // hook. - MOZ_ASSERT(obj->as().isGlobal()); - MOZ_ASSERT(&obj->as().enclosingScope() == cx->global()); + // hook except the global lexical scope and non-syntactic ones. + MOZ_ASSERT(obj->as().isGlobal() || + !obj->as().isSyntactic()); + MOZ_ASSERT_IF(obj->as().isGlobal(), + obj->enclosingScope() == cx->global()); RootedObject enclosing(cx, obj->enclosingScope()); return GetThisObject(cx, enclosing); } @@ -1666,9 +1690,12 @@ class DebugScopeProxy : public BaseProxyHandler if (!shape) return true; - // Currently consider all global lexical bindings to be aliased. - if (IsGlobalLexicalScope(block)) + // Currently consider all global and non-syntactic top-level lexical + // bindings to be aliased. + if (block->isExtensible()) { + MOZ_ASSERT(IsGlobalLexicalScope(block) || !IsSyntacticScope(block)); return true; + } unsigned i = block->staticBlock().shapeToIndex(*shape); if (block->staticBlock().isAliased(i)) @@ -2833,13 +2860,16 @@ js::GetDebugScopeForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc) // See declaration and documentation in jsfriendapi.h JS_FRIEND_API(JSObject*) -js::GetObjectEnvironmentObjectForFunction(JSFunction* fun) +js::GetNearestEnclosingWithScopeObjectForFunction(JSFunction* fun) { if (!fun->isInterpreted()) return &fun->global(); JSObject* env = fun->environment(); - if (!env || !env->is()) + while (env && !env->is()) + env = env->enclosingScope(); + + if (!env) return &fun->global(); return &env->as().object(); diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index 41661e2e6c8..fe74c60f207 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -215,7 +215,7 @@ ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc); * | * ScopeObject---+---+ Engine-internal scope * | | | | | - * | | | | StaticNonSyntacticScopeObjects See NB2 + * | | | | StaticNonSyntacticScopeObjects See "Non-syntactic scope objects" * | | | | * | | | StaticEvalObject Placeholder so eval scopes may be iterated through * | | | @@ -247,9 +247,6 @@ ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc); * are cloned at runtime. These objects should never escape into the wild and * support a restricted set of ScopeObject operations. * - * NB2: StaticNonSyntacticScopeObjects notify either of 0+ non-syntactic - * DynamicWithObjects on the dynamic scope chain or a NonSyntacticScopeObject. - * * See also "Debug scope objects" below. */ @@ -459,13 +456,113 @@ class StaticEvalObject : public ScopeObject inline bool isNonGlobal() const; }; -// Static scope objects that stand in for one or more "polluting global" -// scopes on the dynamic scope chain. -// -// There are two flavors of polluting global scopes on the dynamic scope -// chain: either 0+ non-syntactic DynamicWithObjects, or 1 -// NonSyntacticVariablesObject, created exclusively in -// js::ExecuteInGlobalAndReturnScope. +/* + * Non-syntactic scope objects + * + * A non-syntactic scope is one that was not created due to source code. On + * the static scope chain, a single StaticNonSyntacticScopeObjects maps to 0+ + * non-syntactic dynamic scope objects. This is contrasted with syntactic + * scopes, where each syntactic static scope corresponds to 0 or 1 dynamic + * scope objects. + * + * There are 3 kinds of dynamic non-syntactic scopes: + * + * 1. DynamicWithObject + * + * When the embedding compiles or executes a script, it has the option to + * pass in a vector of objects to be used as the initial scope chain. Each + * of those objects is wrapped by a DynamicWithObject. + * + * The innermost scope passed in by the embedding becomes a qualified + * variables object that captures 'var' bindings. That is, it wraps the + * holder object of 'var' bindings. + * + * Does not hold 'let' or 'const' bindings. + * + * 2. NonSyntacticVariablesObject + * + * When the embedding wants qualified 'var' bindings and unqualified + * bareword assignments to go on a different object than the global + * object. While any object can be made into a qualified variables object, + * only the GlobalObject and NonSyntacticVariablesObject are considered + * unqualified variables objects. + * + * Unlike DynamicWithObjects, this object is itself the holder of 'var' + * bindings. + * + * Does not hold 'let' or 'const' bindings. + * + * 3. ClonedBlockObject + * + * Each non-syntactic object used as a qualified variables object needs to + * enclose a non-syntactic ClonedBlockObject to hold 'let' and 'const' + * bindings. There is a bijection per compartment between the non-syntactic + * variables objects and their non-syntactic ClonedBlockObjects. + * + * Does not hold 'var' bindings. + * + * The embedding (Gecko) uses non-syntactic scopes for various things, some of + * which are detailed below. All scope chain listings below are, from top to + * bottom, outermost to innermost. + * + * A. Component loading + * + * Components may be loaded in "reuse loader global" mode, where to save on + * memory, all JSMs and JS-implemented XPCOM modules are loaded into a single + * global. Each individual JSMs are compiled as functions with their own + * FakeBackstagePass. They have the following dynamic scope chain: + * + * BackstagePass global + * | + * Global lexical scope + * | + * DynamicWithObject wrapping FakeBackstagePass + * | + * Non-syntactic lexical scope + * + * B. Subscript loading + * + * Subscripts may be loaded into a target object. They have the following + * dynamic scope chain: + * + * Loader global + * | + * Global lexical scope + * | + * DynamicWithObject wrapping target + * | + * ClonedBlockObject + * + * C. Frame scripts + * + * XUL frame scripts are always loaded with a NonSyntacticVariablesObject as a + * "polluting global". This is done exclusively in + * js::ExecuteInGlobalAndReturnScope. + * + * Loader global + * | + * Global lexical scope + * | + * NonSyntacticVariablesObject + * | + * ClonedBlockObject + * + * D. XBL + * + * XBL methods are compiled as functions with XUL elements on the scope chain. + * For a chain of elements e0,...,eN: + * + * ... + * | + * DynamicWithObject wrapping eN + * | + * ... + * | + * DynamicWithObject wrapping e0 + * | + * ClonedBlockObject + * + */ class StaticNonSyntacticScopeObjects : public ScopeObject { public: @@ -482,7 +579,7 @@ class StaticNonSyntacticScopeObjects : public ScopeObject // A non-syntactic dynamic scope object that captures non-lexical // bindings. That is, a scope object that captures both qualified var // assignments and unqualified bareword assignments. Its parent is always the -// real global. +// global lexical scope. // // This is used in ExecuteInGlobalAndReturnScope and sits in front of the // global scope to capture 'var' and bareword asignments. @@ -492,7 +589,8 @@ class NonSyntacticVariablesObject : public ScopeObject static const unsigned RESERVED_SLOTS = 1; static const Class class_; - static NonSyntacticVariablesObject* create(JSContext* cx, Handle global); + static NonSyntacticVariablesObject* create(JSContext* cx, + Handle globalLexical); }; class NestedScopeObject : public ScopeObject @@ -703,10 +801,14 @@ class StaticBlockObject : public BlockObject } // Is this the static global lexical scope? - bool isGlobal() { + bool isGlobal() const { return !enclosingStaticScope(); } + bool isSyntactic() const { + return !isExtensible() || isGlobal(); + } + /* Frontend-only functions ***********************************************/ /* Initialization functions for above fields. */ @@ -765,6 +867,9 @@ class ClonedBlockObject : public BlockObject static ClonedBlockObject* createGlobal(JSContext* cx, Handle global); + static ClonedBlockObject* createNonSyntactic(JSContext* cx, HandleObject enclosingStatic, + HandleObject enclosingScope); + static ClonedBlockObject* createHollowForDebug(JSContext* cx, Handle block); @@ -795,6 +900,10 @@ class ClonedBlockObject : public BlockObject return enclosingScope().as(); } + bool isSyntactic() const { + return !isExtensible() || isGlobal(); + } + /* Copy in all the unaliased formals and locals. */ void copyUnaliasedValues(AbstractFramePtr frame); @@ -1179,9 +1288,25 @@ namespace js { inline bool IsSyntacticScope(JSObject* scope) { - return scope->is() && - (!scope->is() || scope->as().isSyntactic()) && - !scope->is(); + if (!scope->is()) + return false; + + if (scope->is()) + return scope->as().isSyntactic(); + + if (scope->is()) + return scope->as().isSyntactic(); + + if (scope->is()) + return false; + + return true; +} + +inline bool +IsExtensibleLexicalScope(JSObject* scope) +{ + return scope->is() && scope->as().isExtensible(); } inline bool @@ -1234,13 +1359,12 @@ inline bool ScopeIter::hasNonSyntacticScopeObject() const { // The case we're worrying about here is a NonSyntactic static scope which - // has 0+ corresponding non-syntactic DynamicWithObject scopes or a - // NonSyntacticVariablesObject. + // has 0+ corresponding non-syntactic DynamicWithObject scopes, a + // NonSyntacticVariablesObject, or a non-syntactic ClonedBlockObject. if (ssi_.type() == StaticScopeIter::NonSyntactic) { MOZ_ASSERT_IF(scope_->is(), !scope_->as().isSyntactic()); - return scope_->is() || - scope_->is(); + return scope_->is() && !IsSyntacticScope(scope_); } return false; } diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index 1d43084d713..9dd61580816 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -62,7 +62,7 @@ InterpreterFrame::global() const } inline JSObject& -InterpreterFrame::varObj() +InterpreterFrame::varObj() const { JSObject* obj = scopeChain(); while (!obj->isQualifiedVarObj()) @@ -70,6 +70,12 @@ InterpreterFrame::varObj() return *obj; } +inline ClonedBlockObject& +InterpreterFrame::extensibleLexicalScope() const +{ + return NearestEnclosingExtensibleLexicalScope(scopeChain()); +} + inline JSCompartment* InterpreterFrame::compartment() const { diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 8c5c9895026..08a8809522f 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -144,7 +144,11 @@ AssertDynamicScopeMatchesStaticScope(JSContext* cx, JSScript* script, JSObject* RootedObject enclosingScope(cx, script->enclosingStaticScope()); for (StaticScopeIter i(enclosingScope); !i.done(); i++) { if (i.type() == StaticScopeIter::NonSyntactic) { - while (scope->is() || scope->is()) { + while (scope->is() || + scope->is() || + (scope->is() && + !scope->as().isSyntactic())) + { MOZ_ASSERT(!IsSyntacticScope(scope)); scope = &scope->as().enclosingScope(); } diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index 92f619f81f4..731c0bb0dfa 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -45,6 +45,7 @@ class ScriptFrameIter; class SPSProfiler; class InterpreterFrame; class StaticBlockObject; +class ClonedBlockObject; class ScopeCoordinate; @@ -616,7 +617,8 @@ class InterpreterFrame inline ScopeObject& aliasedVarScope(ScopeCoordinate sc) const; inline GlobalObject& global() const; inline CallObject& callObj() const; - inline JSObject& varObj(); + inline JSObject& varObj() const; + inline ClonedBlockObject& extensibleLexicalScope() const; inline void pushOnScopeChain(ScopeObject& scope); inline void popOffScopeChain(); diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 1abb36a0b89..0fc408bd574 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -2566,9 +2566,11 @@ UpdatePropertyType(ExclusiveContext* cx, HeapTypeSet* types, NativeObject* obj, * comment). * * Also don't add untracked values (initial uninitialized lexical - * magic values and optimized out values) as appearing in CallObjects. + * magic values and optimized out values) as appearing in CallObjects + * and the global lexical scope. */ - MOZ_ASSERT_IF(TypeSet::IsUntrackedValue(value), obj->is()); + MOZ_ASSERT_IF(TypeSet::IsUntrackedValue(value), + obj->is() || IsExtensibleLexicalScope(obj)); if ((indexed || !value.isUndefined() || !CanHaveEmptyPropertyTypesForOwnProperty(obj)) && !TypeSet::IsUntrackedValue(value)) { diff --git a/js/xpconnect/loader/mozJSComponentLoader.cpp b/js/xpconnect/loader/mozJSComponentLoader.cpp index 367b9336f0e..7ef691f168b 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.cpp +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp @@ -325,6 +325,25 @@ mozJSComponentLoader::ReallyInit() return NS_OK; } +// For terrible compatibility reasons, we need to consider both the global +// lexical scope and the global of modules when searching for exported +// symbols. +static JSObject* +ResolveModuleObjectProperty(JSContext* aCx, HandleObject aModObj, const char* name) +{ + if (JS_HasExtensibleLexicalScope(aModObj)) { + RootedObject lexical(aCx, JS_ExtensibleLexicalScope(aModObj)); + bool found; + if (!JS_HasOwnProperty(aCx, lexical, name, &found)) { + return nullptr; + } + if (found) { + return lexical; + } + } + return aModObj; +} + const mozilla::Module* mozJSComponentLoader::LoadModule(FileLocation& aFile) { @@ -372,9 +391,12 @@ mozJSComponentLoader::LoadModule(FileLocation& aFile) JSAutoCompartment ac(cx, entry->obj); RootedObject entryObj(cx, entry->obj); + RootedObject NSGetFactoryHolder(cx, ResolveModuleObjectProperty(cx, entryObj, "NSGetFactory")); RootedValue NSGetFactory_val(cx); - if (!JS_GetProperty(cx, entryObj, "NSGetFactory", &NSGetFactory_val) || - NSGetFactory_val.isUndefined()) { + if (!NSGetFactoryHolder || + !JS_GetProperty(cx, NSGetFactoryHolder, "NSGetFactory", &NSGetFactory_val) || + NSGetFactory_val.isUndefined()) + { return nullptr; } @@ -424,7 +446,7 @@ mozJSComponentLoader::FindTargetObject(JSContext* aCx, if (mReuseLoaderGlobal) { JSFunction* fun = js::GetOutermostEnclosingFunctionOfScriptedCaller(aCx); if (fun) { - JSObject* funParent = js::GetObjectEnvironmentObjectForFunction(fun); + JSObject* funParent = js::GetNearestEnclosingWithScopeObjectForFunction(fun); if (JS_GetClass(funParent) == &kFakeBackstagePassJSClass) targetObject = funParent; } @@ -964,6 +986,9 @@ mozJSComponentLoader::UnloadModules() RootedObject global(cx, mLoaderGlobal->GetJSObject()); if (global) { JSAutoCompartment ac(cx, global); + if (JS_HasExtensibleLexicalScope(global)) { + JS_SetAllNonReservedSlotsToUndefined(cx, JS_ExtensibleLexicalScope(global)); + } JS_SetAllNonReservedSlotsToUndefined(cx, global); } else { NS_WARNING("Going to leak!"); @@ -1074,6 +1099,22 @@ mozJSComponentLoader::IsModuleLoaded(const nsACString& aLocation, return NS_OK; } +static JSObject* +ResolveModuleObjectPropertyById(JSContext* aCx, HandleObject aModObj, HandleId id) +{ + if (JS_HasExtensibleLexicalScope(aModObj)) { + RootedObject lexical(aCx, JS_ExtensibleLexicalScope(aModObj)); + bool found; + if (!JS_HasOwnPropertyById(aCx, lexical, id, &found)) { + return nullptr; + } + if (found) { + return lexical; + } + } + return aModObj; +} + nsresult mozJSComponentLoader::ImportInto(const nsACString& aLocation, HandleObject targetObj, @@ -1178,8 +1219,10 @@ mozJSComponentLoader::ImportInto(const nsACString& aLocation, JSAutoCompartment ac(cx, mod->obj); RootedValue symbols(cx); - RootedObject modObj(cx, mod->obj); - if (!JS_GetProperty(cx, modObj, + RootedObject exportedSymbolsHolder(cx, ResolveModuleObjectProperty(cx, mod->obj, + "EXPORTED_SYMBOLS")); + if (!exportedSymbolsHolder || + !JS_GetProperty(cx, exportedSymbolsHolder, "EXPORTED_SYMBOLS", &symbols)) { return ReportOnCaller(cxhelper, ERROR_NOT_PRESENT, PromiseFlatCString(aLocation).get()); @@ -1210,6 +1253,7 @@ mozJSComponentLoader::ImportInto(const nsACString& aLocation, RootedValue value(cx); RootedId symbolId(cx); + RootedObject symbolHolder(cx); for (uint32_t i = 0; i < symbolCount; ++i) { if (!JS_GetElement(cx, symbolsObj, i, &value) || !value.isString() || @@ -1218,8 +1262,9 @@ mozJSComponentLoader::ImportInto(const nsACString& aLocation, PromiseFlatCString(aLocation).get(), i); } - RootedObject modObj(cx, mod->obj); - if (!JS_GetPropertyById(cx, modObj, symbolId, &value)) { + symbolHolder = ResolveModuleObjectPropertyById(cx, mod->obj, symbolId); + if (!symbolHolder || + !JS_GetPropertyById(cx, symbolHolder, symbolId, &value)) { JSAutoByteString bytes(cx, JSID_TO_STRING(symbolId)); if (!bytes) return NS_ERROR_FAILURE; diff --git a/js/xpconnect/loader/mozJSComponentLoader.h b/js/xpconnect/loader/mozJSComponentLoader.h index e55b0f77052..e81321eadf1 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.h +++ b/js/xpconnect/loader/mozJSComponentLoader.h @@ -112,6 +112,9 @@ class mozJSComponentLoader : public mozilla::ModuleLoader, mozilla::AutoJSContext cx; JSAutoCompartment ac(cx, obj); + if (JS_HasExtensibleLexicalScope(obj)) { + JS_SetAllNonReservedSlotsToUndefined(cx, JS_ExtensibleLexicalScope(obj)); + } JS_SetAllNonReservedSlotsToUndefined(cx, obj); obj = nullptr; thisObjectKey = nullptr; diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp index 31416aa2f0b..c518debefd4 100644 --- a/js/xpconnect/src/XPCShellImpl.cpp +++ b/js/xpconnect/src/XPCShellImpl.cpp @@ -1556,6 +1556,7 @@ XRE_XPCShellMain(int argc, char** argv, char** envp) JS_DropPrincipals(rt, gJSPrincipals); JS_SetAllNonReservedSlotsToUndefined(cx, glob); + JS_SetAllNonReservedSlotsToUndefined(cx, JS_GlobalLexicalScope(glob)); JS_GC(rt); } JS_GC(rt); diff --git a/testing/xpcshell/head.js b/testing/xpcshell/head.js index 3bde8b0853f..b2143155d18 100644 --- a/testing/xpcshell/head.js +++ b/testing/xpcshell/head.js @@ -333,7 +333,7 @@ function _register_protocol_handlers() { } function _register_modules_protocol_handler() { - if (!this._TESTING_MODULES_DIR) { + if (!_TESTING_MODULES_DIR) { throw new Error("Please define a path where the testing modules can be " + "found in a variable called '_TESTING_MODULES_DIR' before " + "head.js is included."); @@ -1227,7 +1227,7 @@ function do_load_child_test_harness() + "const _JSDEBUGGER_PORT=0; " + "const _XPCSHELL_PROCESS='child';"; - if (this._TESTING_MODULES_DIR) { + if (_TESTING_MODULES_DIR) { command += " const _TESTING_MODULES_DIR=" + uneval(_TESTING_MODULES_DIR) + ";"; }