diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index c0fc473f71b5..d2974e5bff16 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -141,8 +141,7 @@ CanLazilyParse(ExclusiveContext *cx, const ReadOnlyCompileOptions &options) { return options.canLazilyParse && options.compileAndGo && - options.sourcePolicy == CompileOptions::SAVE_SOURCE && - !cx->compartment()->debugMode(); + options.sourcePolicy == CompileOptions::SAVE_SOURCE; } void diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index c99d9b04be9b..0e8e879a6328 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1757,7 +1757,9 @@ BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext *cx) RootedFunction function(cx, script->function()); CallNewScriptHook(cx->asJSContext(), script, function); - if (!parent) { + // Lazy scripts are never top level (despite always being invoked with a + // nullptr parent), and so the hook should never be fired. + if (emitterMode != LazyFunction && !parent) { GlobalObject *compileAndGoGlobal = nullptr; if (script->compileAndGo) compileAndGoGlobal = &script->global(); diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 86792e1c73fd..e06b2183a2a8 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -673,20 +673,20 @@ CreateLazyScriptsForCompartment(JSContext *cx) { AutoObjectVector lazyFunctions(cx); - // Find all root lazy functions in the compartment: those which have not been - // compiled and which have a source object, indicating that their parent has - // been compiled. - for (gc::CellIter i(cx->zone(), JSFunction::FinalizeKind); !i.done(); i.next()) { - JSObject *obj = i.get(); - if (obj->compartment() == cx->compartment() && obj->is()) { - JSFunction *fun = &obj->as(); - if (fun->isInterpretedLazy()) { - LazyScript *lazy = fun->lazyScriptOrNull(); - if (lazy && lazy->sourceObject() && !lazy->maybeScript()) { - if (!lazyFunctions.append(fun)) - return false; - } - } + // Find all live lazy scripts in the compartment, and via them all root + // lazy functions in the compartment: those which have not been compiled + // and which have a source object, indicating that their parent has been + // compiled. + for (gc::CellIter i(cx->zone(), gc::FINALIZE_LAZY_SCRIPT); !i.done(); i.next()) { + LazyScript *lazy = i.get(); + JSFunction *fun = lazy->function(); + if (fun->compartment() == cx->compartment() && + lazy->sourceObject() && !lazy->maybeScript()) + { + MOZ_ASSERT(fun->isInterpretedLazy()); + MOZ_ASSERT(lazy == fun->lazyScriptOrNull()); + if (!lazyFunctions.append(fun)) + return false; } } @@ -708,19 +708,16 @@ CreateLazyScriptsForCompartment(JSContext *cx) return false; } - // Repoint any clones of the original functions to their new script. - for (gc::CellIter i(cx->zone(), JSFunction::FinalizeKind); !i.done(); i.next()) { - JSObject *obj = i.get(); - if (obj->compartment() == cx->compartment() && obj->is()) { - JSFunction *fun = &obj->as(); - if (fun->isInterpretedLazy()) { - LazyScript *lazy = fun->lazyScriptOrNull(); - if (lazy && lazy->maybeScript()) - fun->existingScript(); - } - } - } + return true; +} +bool +JSCompartment::ensureDelazifyScriptsForDebugMode(JSContext *cx) +{ + MOZ_ASSERT(cx->compartment() == this); + if ((debugModeBits & DebugNeedDelazification) && !CreateLazyScriptsForCompartment(cx)) + return false; + debugModeBits &= ~DebugNeedDelazification; return true; } @@ -728,7 +725,7 @@ bool JSCompartment::setDebugModeFromC(JSContext *cx, bool b, AutoDebugModeInvalidation &invalidate) { bool enabledBefore = debugMode(); - bool enabledAfter = (debugModeBits & ~unsigned(DebugFromC)) || b; + bool enabledAfter = (debugModeBits & DebugModeFromMask & ~DebugFromC) || b; // Debug mode can be enabled only when no scripts from the target // compartment are on the stack. It would even be incorrect to discard just @@ -747,11 +744,9 @@ JSCompartment::setDebugModeFromC(JSContext *cx, bool b, AutoDebugModeInvalidatio JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_IDLE); return false; } - if (enabledAfter && !CreateLazyScriptsForCompartment(cx)) - return false; } - debugModeBits = (debugModeBits & ~unsigned(DebugFromC)) | (b ? DebugFromC : 0); + debugModeBits = (debugModeBits & ~DebugFromC) | (b ? DebugFromC : 0); JS_ASSERT(debugMode() == enabledAfter); if (enabledBefore != enabledAfter) { updateForDebugMode(cx->runtime()->defaultFreeOp(), invalidate); @@ -801,8 +796,6 @@ JSCompartment::addDebuggee(JSContext *cx, Rooted global(cx, globalArg); bool wasEnabled = debugMode(); - if (!wasEnabled && !CreateLazyScriptsForCompartment(cx)) - return false; if (!debuggees.put(global)) { js_ReportOutOfMemory(cx); return false; diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index a00817717968..ccd2e9871cc8 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -278,7 +278,13 @@ struct JSCompartment js::WeakMapBase *gcWeakMapList; private: - enum { DebugFromC = 1, DebugFromJS = 2 }; + enum { + DebugFromC = 1 << 0, + DebugFromJS = 1 << 1, + DebugNeedDelazification = 1 << 2 + }; + + static const unsigned DebugModeFromMask = DebugFromC | DebugFromJS; unsigned debugModeBits; // see debugMode() below @@ -356,13 +362,34 @@ struct JSCompartment * by Debugger objects. Therefore debugModeBits has the DebugFromC bit set * if the C API wants debug mode and the DebugFromJS bit set if debuggees * is non-empty. + * + * When toggling on, DebugNeedDelazification is set to signal that + * Debugger methods which depend on seeing all scripts (like findScripts) + * need to delazify the scripts in the compartment first. */ - bool debugMode() const { return !!debugModeBits; } + bool debugMode() const { + return !!(debugModeBits & DebugModeFromMask); + } /* True if any scripts from this compartment are on the JS stack. */ bool hasScriptsOnStack(); + /* + * Schedule the compartment to be delazified. Called from + * LazyScript::Create. + */ + void scheduleDelazificationForDebugMode() { + debugModeBits |= DebugNeedDelazification; + } + + /* + * If we scheduled delazification for turning on debug mode, delazify all + * scripts. + */ + bool ensureDelazifyScriptsForDebugMode(JSContext *cx); + private: + /* This is called only when debugMode() has just toggled. */ void updateForDebugMode(js::FreeOp *fop, js::AutoDebugModeInvalidation &invalidate); diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 0c9cefdd4ff2..9cad05fa329b 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -2994,6 +2994,8 @@ LazyScript::Create(ExclusiveContext *cx, HandleFunction fun, if (!res) return nullptr; + cx->compartment()->scheduleDelazificationForDebugMode(); + return new (res) LazyScript(fun, table, numFreeVariables, numInnerFunctions, version, begin, end, lineno, column); } diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 2d4b5dfe52da..86390af428df 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -97,6 +97,25 @@ ReportMoreArgsNeeded(JSContext *cx, const char *name, unsigned required) return false; } +static inline bool +EnsureFunctionHasScript(JSContext *cx, JSFunction *fun) +{ + if (fun->isInterpretedLazy()) { + AutoCompartment ac(cx, fun); + return !!fun->getOrCreateScript(cx); + } + return true; +} + +static inline JSScript * +GetOrCreateFunctionScript(JSContext *cx, JSFunction *fun) +{ + MOZ_ASSERT(fun->isInterpreted()); + if (!EnsureFunctionHasScript(cx, fun)) + return nullptr; + return fun->nonLazyScript(); +} + #define REQUIRE_ARGC(name, n) \ JS_BEGIN_MACRO \ if (argc < (n)) \ @@ -686,6 +705,9 @@ Debugger::wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp) if (vp.isObject()) { RootedObject obj(cx, &vp.toObject()); + if (obj->is() && !EnsureFunctionHasScript(cx, &obj->as())) + return false; + ObjectWeakMap::AddPtr p = objects.lookupForAdd(obj); if (p) { vp.setObject(*p->value); @@ -2403,10 +2425,14 @@ class Debugger::ScriptQuery { if (!prepareQuery()) return false; + JSCompartment *singletonComp = nullptr; + if (compartments.count() == 1) + singletonComp = compartments.all().front(); + /* Search each compartment for debuggee scripts. */ vector = v; oom = false; - IterateScripts(cx->runtime(), nullptr, this, considerScript); + IterateScripts(cx->runtime(), singletonComp, this, considerScript); if (oom) { js_ReportOutOfMemory(cx); return false; @@ -2476,10 +2502,21 @@ class Debugger::ScriptQuery { /* Indicates whether OOM has occurred while matching. */ bool oom; + bool addCompartment(JSCompartment *comp) { + { + // All scripts in the debuggee compartment must be visible, so + // delazify everything. + AutoCompartment ac(cx, comp); + if (!comp->ensureDelazifyScriptsForDebugMode(cx)) + return false; + } + return compartments.put(comp); + } + /* Arrange for this ScriptQuery to match only scripts that run in |global|. */ bool matchSingleGlobal(GlobalObject *global) { JS_ASSERT(compartments.count() == 0); - if (!compartments.put(global->compartment())) { + if (!addCompartment(global->compartment())) { js_ReportOutOfMemory(cx); return false; } @@ -2494,7 +2531,7 @@ class Debugger::ScriptQuery { JS_ASSERT(compartments.count() == 0); /* Build our compartment set from the debugger's set of debuggee globals. */ for (GlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) { - if (!compartments.put(r.front()->compartment())) { + if (!addCompartment(r.front()->compartment())) { js_ReportOutOfMemory(cx); return false; } @@ -2937,8 +2974,10 @@ DebuggerScript_getChildScripts(JSContext *cx, unsigned argc, Value *vp) for (uint32_t i = script->innerObjectsStart(); i < objects->length; i++) { obj = objects->vector[i]; if (obj->is()) { - fun = static_cast(obj.get()); - funScript = fun->nonLazyScript(); + fun = &obj->as(); + funScript = GetOrCreateFunctionScript(cx, fun); + if (!funScript) + return false; s = dbg->wrapScript(cx, funScript); if (!s || !js_NewbornArrayPush(cx, result, ObjectValue(*s))) return false; @@ -4659,14 +4698,9 @@ DebuggerObject_getParameterNames(JSContext *cx, unsigned argc, Value *vp) result->ensureDenseInitializedLength(cx, 0, fun->nargs); if (fun->isInterpreted()) { - RootedScript script(cx); - - { - AutoCompartment ac(cx, fun); - script = fun->getOrCreateScript(cx); - if (!script) - return false; - } + RootedScript script(cx, GetOrCreateFunctionScript(cx, fun)); + if (!script) + return false; JS_ASSERT(fun->nargs == script->bindings.numArgs()); @@ -4708,15 +4742,9 @@ DebuggerObject_getScript(JSContext *cx, unsigned argc, Value *vp) return true; } - RootedScript script(cx); - - { - AutoCompartment ac(cx, obj); - - script = fun->getOrCreateScript(cx); - if (!script) - return false; - } + RootedScript script(cx, GetOrCreateFunctionScript(cx, fun)); + if (!script) + return false; /* Only hand out debuggee scripts. */ if (!dbg->observesScript(script)) {