Bug 626743 - Set debug mode for all compartments in main thread (r=dmandelin, a=blocker)

--HG--
extra : rebase_source : ee656687d36620cad69d415baa9a71748154b563
This commit is contained in:
Steve Fink 2011-01-20 22:10:54 -08:00
parent 92b06361aa
commit 4a60b81db6
6 changed files with 177 additions and 137 deletions

View File

@ -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

View File

@ -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<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)
{
@ -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");

View File

@ -42,10 +42,12 @@
* 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"
@ -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<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
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)
{

View File

@ -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. */

View File

@ -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;
}

View File

@ -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<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);
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 (); */