mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
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:
parent
92b06361aa
commit
4a60b81db6
@ -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
|
||||
|
@ -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 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");
|
||||
|
@ -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 JS_SetDebugModeForCompartment(cx, cx->compartment, debug);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#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)
|
||||
JS_FRIEND_API(JSBool)
|
||||
JS_SetDebugModeForCompartment(JSContext *cx, JSCompartment *comp, JSBool debug)
|
||||
{
|
||||
for (JSScript *script = start;
|
||||
&script->links != &cx->compartment->scripts;
|
||||
script = (JSScript *)script->links.next)
|
||||
{
|
||||
// Debug mode does not use call ICs.
|
||||
if (script->debugMode)
|
||||
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;
|
||||
|
||||
JS_ASSERT(!IsScriptLive(cx, script));
|
||||
|
||||
if (script->jitNormal)
|
||||
script->jitNormal->nukeScriptDependentICs();
|
||||
if (script->jitCtor)
|
||||
script->jitCtor->nukeScriptDependentICs();
|
||||
for (AllFramesIter i(icx); !i.done(); ++i) {
|
||||
JSScript *script = i.fp()->maybeScript();
|
||||
if (script)
|
||||
liveScripts.put(script);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
JS_FRIEND_API(JSBool)
|
||||
js_SetDebugMode(JSContext *cx, JSBool debug)
|
||||
{
|
||||
if (!cx->compartment)
|
||||
return JS_TRUE;
|
||||
comp->debugMode = debug;
|
||||
|
||||
JSAutoEnterCompartment ac;
|
||||
|
||||
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)) {
|
||||
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;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
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)
|
||||
{
|
||||
|
@ -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. */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -2473,9 +2473,14 @@ 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.
|
||||
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;
|
||||
}
|
||||
@ -2485,23 +2490,52 @@ nsXPConnect::CheckForDebugMode(JSRuntime *rt) {
|
||||
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);
|
||||
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)) {
|
||||
JS_EndRequest(cx);
|
||||
JS_DestroyContext(cx);
|
||||
|
||||
if (gDesiredDebugMode) {
|
||||
rv = jsds->ActivateDebugger(rt);
|
||||
}
|
||||
|
||||
gDebugMode = gDesiredDebugMode;
|
||||
} else {
|
||||
return;
|
||||
|
||||
fail:
|
||||
if (jsds)
|
||||
jsds->DeactivateDebugger();
|
||||
|
||||
// if the attempt failed, cancel the debugMode request
|
||||
gDesiredDebugMode = gDebugMode;
|
||||
JS_SetRuntimeDebugMode(rt, gDebugMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* JSContext Pop (); */
|
||||
NS_IMETHODIMP
|
||||
|
Loading…
Reference in New Issue
Block a user