Bug 626743 - Set debug mode for all compartments in main thread (r=dmandelin, a=blocker)
authorSteve Fink <sfink@mozilla.com>
Thu, 20 Jan 2011 22:10:54 -0800
changeset 62057 641a33d0932fad734983c7e7158dac07af51098c
parent 62056 a1cba95edcf98a66b32aa9f511c34622e2b51959
child 62058 17e52535ac5b93b3b6dd1cc4f0ab020b306c004d
push idunknown
push userunknown
push dateunknown
reviewersdmandelin, blocker
bugs626743
milestone2.0b11pre
Bug 626743 - Set debug mode for all compartments in main thread (r=dmandelin, a=blocker)
js/jsd/idl/jsdIDebuggerService.idl
js/jsd/jsd_xpc.cpp
js/src/jsdbgapi.cpp
js/src/jsdbgapi.h
js/src/shell/js.cpp
js/src/xpconnect/src/nsXPConnect.cpp
--- a/js/jsd/idl/jsdIDebuggerService.idl
+++ b/js/jsd/idl/jsdIDebuggerService.idl
@@ -48,16 +48,17 @@
 [ptr] native JSDObject(JSDObject);
 [ptr] native JSDProperty(JSDProperty);
 [ptr] native JSDScript(JSDScript);
 [ptr] native JSDStackFrameInfo(JSDStackFrameInfo);
 [ptr] native JSDThreadState(JSDThreadState);
 [ptr] native JSDValue(JSDValue);
 [ptr] native JSRuntime(JSRuntime);
 [ptr] native JSContext(JSContext);
+[ptr] native JSCompartment(JSCompartment);
 
 /* interfaces we declare in this file */
 interface jsdIDebuggerService;
 interface jsdIFilter;
 interface jsdINestCallback;
 interface jsdIFilterEnumerator;
 interface jsdIContextEnumerator;
 interface jsdIScriptEnumerator;
@@ -73,17 +74,17 @@ interface jsdIValue;
 interface jsdIObject;
 interface jsdIProperty;
 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. */
     [noscript] readonly attribute JSDContext        JSDContext;
 
     /**
      * Called when an error or warning occurs.
      */
@@ -236,19 +237,24 @@ interface jsdIDebuggerService : nsISuppo
     
     /**
      * Called by nsIXPConnect after it's had a chance to recompile for
      * debug mode.
      */
     [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
      * derived objects, and clear all of your breakpoints.  In theory you
      * should be able to turn the debugger back on at some later time without
      * any problems.
      */
     void off ();
--- a/js/jsd/jsd_xpc.cpp
+++ b/js/jsd/jsd_xpc.cpp
@@ -2506,41 +2506,59 @@ jsdService::AsyncOn (jsdIActivationCallb
     if (NS_FAILED(rv)) return rv;
 
     mActivationCallback = activationCallback;
     
     return xpc->SetDebugModeWhenPossible(PR_TRUE);
 }
 
 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<jsword>(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)
 {
     if (mOn)
         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 */
         gLastGCProc = JS_SetGCCallbackRT (rt, jsds_GCCallbackProc);
 
     mCx = JSD_DebuggerOnForUser (rt, NULL, NULL);
     if (!mCx)
         return NS_ERROR_FAILURE;
@@ -2610,36 +2628,17 @@ jsdService::Off (void)
             jsds_NotifyPendingDeadScripts (cx);
     }
 
     /*
     if (gLastGCProc != jsds_GCCallbackProc)
         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");
 #endif
 
     nsresult rv;
     nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv);
     if (NS_FAILED(rv))
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -37,20 +37,22 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /*
  * JS debugging API.
  */
 #include <string.h>
+#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"
 #include "jsdbgapi.h"
 #include "jsemit.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsinterp.h"
@@ -93,104 +95,97 @@ typedef struct JSTrap {
 #define DBG_LOCK_EVAL(rt,expr)  (DBG_LOCK(rt), (expr), DBG_UNLOCK(rt))
 
 JS_PUBLIC_API(JSBool)
 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)
 {
     rt->debugMode = debug;
 }
 
+JS_FRIEND_API(JSBool)
+JS_SetDebugModeForCompartment(JSContext *cx, JSCompartment *comp, JSBool debug)
+{
+    JSRuntime *rt = cx->runtime;
+
+    // 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<jsword>(js_CurrentThreadId());
+    typedef HashSet<JSScript *, DefaultHasher<JSScript*>, 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
-static void
-PurgeCallICs(JSContext *cx, JSScript *start)
-{
-    for (JSScript *script = start;
-         &script->links != &cx->compartment->scripts;
+    for (JSScript *script = (JSScript *)comp->scripts.next;
+         &script->links != &comp->scripts;
          script = (JSScript *)script->links.next)
     {
-        // Debug mode does not use call ICs.
-        if (script->debugMode)
+        if (!script->debugMode == !debug)
+            continue;
+        if (liveScripts.has(script))
             continue;
 
-        JS_ASSERT(!IsScriptLive(cx, script));
+        /*
+         * 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;
+        }
 
-        if (script->jitNormal)
-            script->jitNormal->nukeScriptDependentICs();
-        if (script->jitCtor)
-            script->jitCtor->nukeScriptDependentICs();
+        mjit::ReleaseScriptCode(cx, script);
+        script->debugMode = !!debug;
     }
-}
 #endif
 
-JS_FRIEND_API(JSBool)
-js_SetDebugMode(JSContext *cx, JSBool debug)
-{
-    if (!cx->compartment)
-        return JS_TRUE;
-
-    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;
-            }
-        }
-    }
-#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)
 {
 #ifdef JS_METHODJIT
     if (!script->singleStepMode == !singleStep)
         return JS_TRUE;
 #endif
 
--- a/js/src/jsdbgapi.h
+++ b/js/src/jsdbgapi.h
@@ -65,22 +65,28 @@ JS_SetRuntimeDebugMode(JSRuntime *rt, JS
  * before debug mode was enabled. For this reason, it is also not safe to
  * enable debug mode while frames are live.
  */
 
 /* Get current state of debugging mode. */
 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. */
 extern JS_FRIEND_API(JSBool)
 js_SetSingleStepMode(JSContext *cx, JSScript *script, JSBool singleStep);
 
 /* Turn on single step mode. */
 extern JS_PUBLIC_API(JSBool)
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -904,17 +904,17 @@ ProcessArgs(JSContext *cx, JSObject *obj
             if (++i == argc)
                 return usage();
 
             /* Set maximum stack size. */
             gMaxStackSize = atoi(argv[i]);
             break;
 
         case 'd':
-            js_SetDebugMode(cx, JS_TRUE);
+            JS_SetDebugMode(cx, JS_TRUE);
             break;
 
         case 'z':
             obj = split_setup(cx, JS_FALSE);
             if (!obj)
                 return gExitCode;
             break;
 #ifdef MOZ_TRACEVIS
@@ -1747,17 +1747,17 @@ SetDebug(JSContext *cx, uintN argc, jsva
 {
     jsval *argv = JS_ARGV(cx, vp);
     if (argc == 0 || !JSVAL_IS_BOOLEAN(argv[0])) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
                              JSSMSG_NOT_ENOUGH_ARGS, "setDebug");
         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;
 }
 
 static JSBool
 GetTrapArgs(JSContext *cx, uintN argc, jsval *argv, JSScript **scriptp,
             int32 *ip)
 {
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -2468,44 +2468,78 @@ nsXPConnect::Peek(JSContext * *_retval)
         return NS_ERROR_FAILURE;
     }
 
     return data->GetJSContextStack()->Peek(_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;
-        }
-
-        JS_SetRuntimeDebugMode(rt, gDesiredDebugMode);
-
-        nsresult rv;
-        const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1";
-        nsCOMPtr<jsdIDebuggerService> jsds = do_GetService(jsdServiceCtrID, &rv);
-        if (NS_SUCCEEDED(rv)) {
-            if (gDesiredDebugMode == PR_FALSE) {
-                rv = jsds->RecompileForDebugMode(rt, PR_FALSE);
-            } else {
-                rv = jsds->ActivateDebugger(rt);
+    JSContext *cx = NULL;
+
+    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;
+    }
+
+    JS_SetRuntimeDebugMode(rt, gDesiredDebugMode);
+
+    nsresult rv;
+    const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1";
+    nsCOMPtr<jsdIDebuggerService> 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 (); */
 NS_IMETHODIMP
 nsXPConnect::Pop(JSContext * *_retval)
 {
     XPCPerThreadData* data = XPCPerThreadData::GetData(nsnull);