From 4a60b81db664b40a0c1ae718ea164457bec15f23 Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Thu, 20 Jan 2011 22:10:54 -0800 Subject: [PATCH] Bug 626743 - Set debug mode for all compartments in main thread (r=dmandelin, a=blocker) --HG-- extra : rebase_source : ee656687d36620cad69d415baa9a71748154b563 --- js/jsd/idl/jsdIDebuggerService.idl | 10 +- js/jsd/jsd_xpc.cpp | 69 +++++++------- js/src/jsdbgapi.cpp | 135 +++++++++++++-------------- js/src/jsdbgapi.h | 16 +++- js/src/shell/js.cpp | 4 +- js/src/xpconnect/src/nsXPConnect.cpp | 80 +++++++++++----- 6 files changed, 177 insertions(+), 137 deletions(-) diff --git a/js/jsd/idl/jsdIDebuggerService.idl b/js/jsd/idl/jsdIDebuggerService.idl index 4dec26923b88..42d77355f827 100644 --- a/js/jsd/idl/jsdIDebuggerService.idl +++ b/js/jsd/idl/jsdIDebuggerService.idl @@ -53,6 +53,7 @@ [ptr] native JSDValue(JSDValue); [ptr] native JSRuntime(JSRuntime); [ptr] native JSContext(JSContext); +[ptr] native JSCompartment(JSCompartment); /* interfaces we declare in this file */ interface jsdIDebuggerService; @@ -78,7 +79,7 @@ interface jsdIActivationCallback; * Debugger service. It's not a good idea to have more than one active client of * the debugger service. */ -[scriptable, uuid(1ad86ef3-5eca-4ed7-81c5-a757d1957dff)] +[scriptable, uuid(aa232c7f-855f-4488-a92c-6f89adc668cc)] interface jsdIDebuggerService : nsISupports { /** Internal use only. */ @@ -240,10 +241,15 @@ interface jsdIDebuggerService : nsISupports */ [noscript] void activateDebugger(in JSRuntime rt); + /** + * Called by nsIXPConnect to deactivate debugger on setup failure. + */ + [noscript] void deactivateDebugger(); + /** * Recompile all active scripts in the runtime for debugMode. */ - [noscript] void recompileForDebugMode(in JSRuntime rt, in PRBool mode); + [noscript] void recompileForDebugMode(in JSContext cx, in JSCompartment comp, in PRBool mode); /** * Turn the debugger off. This will invalidate all of your jsdIEphemeral diff --git a/js/jsd/jsd_xpc.cpp b/js/jsd/jsd_xpc.cpp index e36928688957..4d60f8537a1c 100644 --- a/js/jsd/jsd_xpc.cpp +++ b/js/jsd/jsd_xpc.cpp @@ -2511,23 +2511,42 @@ jsdService::AsyncOn (jsdIActivationCallback *activationCallback) } NS_IMETHODIMP -jsdService::RecompileForDebugMode (JSRuntime *rt, JSBool mode) { +jsdService::RecompileForDebugMode (JSContext *cx, JSCompartment *comp, JSBool mode) { NS_ASSERTION(NS_IsMainThread(), "wrong thread"); - - JSContext *cx; - JSContext *iter = NULL; - - jsword currentThreadId = reinterpret_cast(js_CurrentThreadId()); - - while ((cx = JS_ContextIterator (rt, &iter))) { - if (JS_GetContextThread(cx) == currentThreadId) { - JS_SetDebugMode(cx, mode); - } - } - - return NS_OK; + return JS_SetDebugModeForCompartment(cx, comp, mode) ? NS_OK : NS_ERROR_FAILURE; } +NS_IMETHODIMP +jsdService::DeactivateDebugger () +{ + if (!mCx) + return NS_OK; + + jsdContext::InvalidateAll(); + jsdScript::InvalidateAll(); + jsdValue::InvalidateAll(); + jsdProperty::InvalidateAll(); + ClearAllBreakpoints(); + + JSD_SetErrorReporter (mCx, NULL, NULL); + JSD_SetScriptHook (mCx, NULL, NULL); + JSD_ClearThrowHook (mCx); + JSD_ClearInterruptHook (mCx); + JSD_ClearDebuggerHook (mCx); + JSD_ClearDebugBreakHook (mCx); + JSD_ClearTopLevelHook (mCx); + JSD_ClearFunctionHook (mCx); + + JSD_DebuggerOff (mCx); + + mCx = nsnull; + mRuntime = nsnull; + mOn = PR_FALSE; + + return NS_OK; +} + + NS_IMETHODIMP jsdService::ActivateDebugger (JSRuntime *rt) { @@ -2535,7 +2554,6 @@ jsdService::ActivateDebugger (JSRuntime *rt) return (rt == mRuntime) ? NS_OK : NS_ERROR_ALREADY_INITIALIZED; mRuntime = rt; - RecompileForDebugMode(rt, JS_TRUE); if (gLastGCProc == jsds_GCCallbackProc) /* condition indicates that the callback proc has not been set yet */ @@ -2615,26 +2633,7 @@ jsdService::Off (void) JS_SetGCCallbackRT (mRuntime, gLastGCProc); */ - jsdContext::InvalidateAll(); - jsdScript::InvalidateAll(); - jsdValue::InvalidateAll(); - jsdProperty::InvalidateAll(); - ClearAllBreakpoints(); - - JSD_SetErrorReporter (mCx, NULL, NULL); - JSD_SetScriptHook (mCx, NULL, NULL); - JSD_ClearThrowHook (mCx); - JSD_ClearInterruptHook (mCx); - JSD_ClearDebuggerHook (mCx); - JSD_ClearDebugBreakHook (mCx); - JSD_ClearTopLevelHook (mCx); - JSD_ClearFunctionHook (mCx); - - JSD_DebuggerOff (mCx); - - mCx = nsnull; - mRuntime = nsnull; - mOn = PR_FALSE; + DeactivateDebugger(); #ifdef DEBUG printf ("+++ JavaScript debugging hooks removed.\n"); diff --git a/js/src/jsdbgapi.cpp b/js/src/jsdbgapi.cpp index 501a4840da78..a658c20589f6 100644 --- a/js/src/jsdbgapi.cpp +++ b/js/src/jsdbgapi.cpp @@ -42,10 +42,12 @@ * JS debugging API. */ #include +#include "jsprvtd.h" #include "jstypes.h" #include "jsstdint.h" #include "jsutil.h" #include "jsclist.h" +#include "jshashtable.h" #include "jsapi.h" #include "jscntxt.h" #include "jsversion.h" @@ -98,17 +100,11 @@ JS_GetDebugMode(JSContext *cx) return cx->compartment->debugMode; } -#ifdef JS_METHODJIT -static bool -IsScriptLive(JSContext *cx, JSScript *script) +JS_PUBLIC_API(JSBool) +JS_SetDebugMode(JSContext *cx, JSBool debug) { - for (AllFramesIter i(cx); !i.done(); ++i) { - if (i.fp()->maybeScript() == script) - return true; - } - return false; + return JS_SetDebugModeForCompartment(cx, cx->compartment, debug); } -#endif JS_PUBLIC_API(void) JS_SetRuntimeDebugMode(JSRuntime *rt, JSBool debug) @@ -116,76 +112,75 @@ JS_SetRuntimeDebugMode(JSRuntime *rt, JSBool debug) rt->debugMode = debug; } -#ifdef JS_METHODJIT -static void -PurgeCallICs(JSContext *cx, JSScript *start) -{ - for (JSScript *script = start; - &script->links != &cx->compartment->scripts; - script = (JSScript *)script->links.next) - { - // Debug mode does not use call ICs. - if (script->debugMode) - continue; - - JS_ASSERT(!IsScriptLive(cx, script)); - - if (script->jitNormal) - script->jitNormal->nukeScriptDependentICs(); - if (script->jitCtor) - script->jitCtor->nukeScriptDependentICs(); - } -} -#endif - JS_FRIEND_API(JSBool) -js_SetDebugMode(JSContext *cx, JSBool debug) +JS_SetDebugModeForCompartment(JSContext *cx, JSCompartment *comp, JSBool debug) { - if (!cx->compartment) - return JS_TRUE; + JSRuntime *rt = cx->runtime; - cx->compartment->debugMode = debug; -#ifdef JS_METHODJIT - for (JSScript *script = (JSScript *)cx->compartment->scripts.next; - &script->links != &cx->compartment->scripts; - script = (JSScript *)script->links.next) { - if (script->debugMode != !!debug && - script->hasJITCode() && - !IsScriptLive(cx, script)) { - /* - * In the event that this fails, debug mode is left partially on, - * leading to a small performance overhead but no loss of - * correctness. We set the debug flag to false so that the caller - * will not later attempt to use debugging features. - */ - js::mjit::Recompiler recompiler(cx, script); - if (!recompiler.recompile()) { - /* - * If recompilation failed, we could be in a state where - * remaining compiled scripts hold call IC references that - * have been destroyed by recompilation. Clear those ICs now. - */ - PurgeCallICs(cx, script); - cx->compartment->debugMode = JS_FALSE; - return JS_FALSE; - } + // We can only recompile scripts that are not currently live (executing in + // some context). This function is only called from the main thread, and + // will only consider contexts in that same thread and scripts inside + // compartments associated with that same thread. (Scripts in other threads + // are allowed to migrate from thread to thread, but scripts do not migrate + // between the main thread and other threads.) + // + // Discard all of this thread's inactive JITScripts and set their + // debugMode. The remaining scripts will be left as-is. + + // Find all live scripts + + JSContext *iter = NULL; + jsword currentThreadId = reinterpret_cast(js_CurrentThreadId()); + typedef HashSet, ContextAllocPolicy> ScriptMap; + ScriptMap liveScripts(cx); + if (!liveScripts.init()) + return JS_FALSE; + + JSContext *icx; + while ((icx = JS_ContextIterator(rt, &iter))) { + if (JS_GetContextThread(icx) != currentThreadId) + continue; + + for (AllFramesIter i(icx); !i.done(); ++i) { + JSScript *script = i.fp()->maybeScript(); + if (script) + liveScripts.put(script); } } + + comp->debugMode = debug; + + JSAutoEnterCompartment ac; + +#ifdef JS_METHODJIT + for (JSScript *script = (JSScript *)comp->scripts.next; + &script->links != &comp->scripts; + script = (JSScript *)script->links.next) + { + if (!script->debugMode == !debug) + continue; + if (liveScripts.has(script)) + continue; + + /* + * If compartment entry fails, debug mode is left partially on, leading + * to a small performance overhead but no loss of correctness. We set + * the debug flags to false so that the caller will not later attempt + * to use debugging features. + */ + if (!ac.entered() && !ac.enter(cx, script)) { + comp->debugMode = JS_FALSE; + return JS_FALSE; + } + + mjit::ReleaseScriptCode(cx, script); + script->debugMode = !!debug; + } #endif + return JS_TRUE; } -JS_PUBLIC_API(JSBool) -JS_SetDebugMode(JSContext *cx, JSBool debug) -{ -#ifdef DEBUG - for (AllFramesIter i(cx); !i.done(); ++i) - JS_ASSERT(!JS_IsScriptFrame(cx, i.fp())); -#endif - - return js_SetDebugMode(cx, debug); -} - JS_FRIEND_API(JSBool) js_SetSingleStepMode(JSContext *cx, JSScript *script, JSBool singleStep) { diff --git a/js/src/jsdbgapi.h b/js/src/jsdbgapi.h index 6db6e4a5a3a0..fc476fac3f01 100644 --- a/js/src/jsdbgapi.h +++ b/js/src/jsdbgapi.h @@ -70,12 +70,18 @@ JS_SetRuntimeDebugMode(JSRuntime *rt, JSBool debug); extern JS_PUBLIC_API(JSBool) JS_GetDebugMode(JSContext *cx); -/* Turn on debugging mode, ignoring the presence of live frames. */ -extern JS_FRIEND_API(JSBool) -js_SetDebugMode(JSContext *cx, JSBool debug); +/* + * Turn on/off debugging mode for a single compartment. This must be + * called from the main thread and the compartment must be associated + * with the main thread. + */ +JS_FRIEND_API(JSBool) +JS_SetDebugModeForCompartment(JSContext *cx, JSCompartment *comp, JSBool debug); -/* Turn on debugging mode. */ -extern JS_PUBLIC_API(JSBool) +/* + * Turn on/off debugging mode for a context's compartment. + */ +JS_FRIEND_API(JSBool) JS_SetDebugMode(JSContext *cx, JSBool debug); /* Turn on single step mode. Requires debug mode. */ diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index a7ee83b76ebe..ab47e22288d8 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -909,7 +909,7 @@ ProcessArgs(JSContext *cx, JSObject *obj, char **argv, int argc) break; case 'd': - js_SetDebugMode(cx, JS_TRUE); + JS_SetDebugMode(cx, JS_TRUE); break; case 'z': @@ -1752,7 +1752,7 @@ SetDebug(JSContext *cx, uintN argc, jsval *vp) return JS_FALSE; } - js_SetDebugMode(cx, JSVAL_TO_BOOLEAN(argv[0])); + JS_SetDebugMode(cx, JSVAL_TO_BOOLEAN(argv[0])); JS_SET_RVAL(cx, vp, JSVAL_VOID); return JS_TRUE; } diff --git a/js/src/xpconnect/src/nsXPConnect.cpp b/js/src/xpconnect/src/nsXPConnect.cpp index f4deb7ee26b5..d676cdec90cc 100644 --- a/js/src/xpconnect/src/nsXPConnect.cpp +++ b/js/src/xpconnect/src/nsXPConnect.cpp @@ -2473,34 +2473,68 @@ nsXPConnect::Peek(JSContext * *_retval) void nsXPConnect::CheckForDebugMode(JSRuntime *rt) { - if (gDebugMode != gDesiredDebugMode) { - // This can happen if a Worker is running, but we don't have the ability - // to debug workers right now, so just return. - if (!NS_IsMainThread()) { - return; - } + JSContext *cx = NULL; - JS_SetRuntimeDebugMode(rt, gDesiredDebugMode); + if (gDebugMode == gDesiredDebugMode) { + return; + } + + // This can happen if a Worker is running, but we don't have the ability to + // debug workers right now, so just return. + if (!NS_IsMainThread()) { + return; + } - nsresult rv; - const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1"; - nsCOMPtr jsds = do_GetService(jsdServiceCtrID, &rv); - if (NS_SUCCEEDED(rv)) { - if (gDesiredDebugMode == PR_FALSE) { - rv = jsds->RecompileForDebugMode(rt, PR_FALSE); - } else { - rv = jsds->ActivateDebugger(rt); + JS_SetRuntimeDebugMode(rt, gDesiredDebugMode); + + nsresult rv; + const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1"; + nsCOMPtr jsds = do_GetService(jsdServiceCtrID, &rv); + if (!NS_SUCCEEDED(rv)) { + goto fail; + } + + if (!(cx = JS_NewContext(rt, 256))) { + goto fail; + } + JS_BeginRequest(cx); + + { + js::WrapperVector &vector = rt->compartments; + for (JSCompartment **p = vector.begin(); p != vector.end(); ++p) { + JSCompartment *comp = *p; + if (!comp->principals) { + /* Ignore special compartments (atoms, JSD compartments) */ + continue; + } + + /* ParticipatesInCycleCollection means "on the main thread" */ + if (xpc::CompartmentParticipatesInCycleCollection(cx, comp)) { + rv = jsds->RecompileForDebugMode(cx, comp, gDesiredDebugMode); + if (!NS_SUCCEEDED(rv)) { + goto fail; + } } } - - if (NS_SUCCEEDED(rv)) { - gDebugMode = gDesiredDebugMode; - } else { - // if the attempt failed, cancel the debugMode request - gDesiredDebugMode = gDebugMode; - JS_SetRuntimeDebugMode(rt, gDebugMode); - } } + + JS_EndRequest(cx); + JS_DestroyContext(cx); + + if (gDesiredDebugMode) { + rv = jsds->ActivateDebugger(rt); + } + + gDebugMode = gDesiredDebugMode; + return; + +fail: + if (jsds) + jsds->DeactivateDebugger(); + + // if the attempt failed, cancel the debugMode request + gDesiredDebugMode = gDebugMode; + JS_SetRuntimeDebugMode(rt, gDebugMode); } /* JSContext Pop (); */