diff --git a/js/jsd/idl/jsdIDebuggerService.idl b/js/jsd/idl/jsdIDebuggerService.idl index ea385313b4b4..68fae738928d 100644 --- a/js/jsd/idl/jsdIDebuggerService.idl +++ b/js/jsd/idl/jsdIDebuggerService.idl @@ -75,6 +75,9 @@ interface jsdIProperty; [scriptable, uuid(01be7f9a-1dd2-11b2-9d55-aaf919b27c73)] interface jsdIDebuggerService : nsISupports { + /** Internal use only. */ + [noscript] readonly attribute JSDContext JSDContext; + /** * Called when a jsdIScript is created or destroyed. */ @@ -120,7 +123,7 @@ interface jsdIDebuggerService : nsISupports * autostart pref may have turned the service on. */ readonly attribute boolean isOn; - + /** * Turn on the debugger. This function should only be called from JavaScript * code. The debugger will be enabled on the runtime the call is made on, @@ -183,8 +186,7 @@ interface jsdIDebuggerService : nsISupports */ void appendFilter (in jsdIFilter filter); /** - * Unignore execution hooks which are triggered by a jsdIScript matching - * particular filter. + * Remove a filter. * * If |filter| is not present this method throws NS_ERROR_INVALID_ARG. * @@ -194,16 +196,15 @@ interface jsdIDebuggerService : nsISupports */ void removeFilter (in jsdIFilter filter); /** - * Swaps the positions of the filters passed in. + * Swap position of two filters. * * If |filter_a| is not present, this method throws NS_ERROR_INVALID_ARG. * If |filter_b| is not present, filter_a is replaced by filter_b. - * If |filter_a| == |filter_b|, then the glob and urlPattern properties of - * the filter are reread. + * If |filter_a| == |filter_b|, then filter is refreshed. */ void swapFilters (in jsdIFilter filter_a, in jsdIFilter filter_b); /** - * Enumerate all active filters. This routine refreshes each filter before + * Enumerate registered filters. This routine refreshes each filter before * passing them on to the enumeration function. Calling this with a null * |enumerator| is equivilant to jsdIService::refreshFilters. * @@ -213,13 +214,13 @@ interface jsdIDebuggerService : nsISupports void enumerateFilters (in jsdIFilterEnumerator enumerator); /** * Force the debugger to resync its internal filter cache with the - * actual values in the jsdIFilter objects. To refresh a single filter, - * see jsdIService::swapFilters. This method is equivilant to + * actual values in the jsdIFilter objects. To refresh a single filter + * use jsdIService::swapFilters. This method is equivilant to * jsdIService::enumerateFilters with a null enumerator. */ void refreshFilters (); /** - * Causes the debugger service to clear its list of filters. + * Clear the list of filters. */ void clearFilters(); @@ -468,21 +469,6 @@ interface jsdIEphemeral : nsISupports /* handle objects */ -/** - * XXX can't reflect the jsuword pc because it'll change sizes on 64 bit systems, - * but we could represent all pcs as offsets, and store them in ulongs. This - * would allow us to get rid of jsdIPC, and simplify things in some places. The - * only tradeoff would be that scripts with more than 2^32 instructions would - * have pc's we can't represent. If you're script is that large, you need more - * help than the debugger can provide. - */ -[scriptable, uuid(e7c8ea2c-1dd1-11b2-9242-f2768e04e92e)] -interface jsdIPC : nsISupports -{ - /** Internal use only. */ - [noscript] readonly attribute jsuword pc; -}; - /** * Stack frame objects. These are only valid inside the jsdIExecutionHook which * gave it to you. After you return from that handler the bottom frame, and any @@ -511,7 +497,7 @@ interface jsdIStackFrame : jsdIEphemeral /** * Current program counter in this stack frame. */ - readonly attribute jsdIPC pc; + readonly attribute unsigned long pc; /** * Current line number (using the script's pc to line map.) */ @@ -556,6 +542,12 @@ interface jsdIScript : jsdIEphemeral /** Internal use only. */ [noscript] readonly attribute JSDScript JSDScript; + /** + * Tag value guaranteed unique among jsdIScript objects. Useful as a + * hash key in script. + */ + readonly attribute unsigned long tag; + /** * Filename given for this script when it was compiled. * This data is copied from the underlying structure when the jsdIScript @@ -571,6 +563,10 @@ interface jsdIScript : jsdIEphemeral * invalidated. */ readonly attribute string functionName; + /** + * Source code for this script, without function declaration. + */ + readonly attribute AString functionSource; /** * Line number in source file containing the first line of this script. * This data is copied from the underlying structure when the jsdIScript @@ -586,27 +582,33 @@ interface jsdIScript : jsdIEphemeral */ readonly attribute unsigned long lineExtent; + const unsigned long PCMAP_SOURCETEXT = 1; /* map to actual source text */ + const unsigned long PCMAP_PRETTYPRINT = 2; /* map to pretty printed source */ + /** * Get the closest line number to a given PC. + * The |pcmap| argument specifies which pc to source line map to use. */ - unsigned long pcToLine (in jsdIPC pc); + unsigned long pcToLine (in unsigned long pc, in unsigned long pcmap); /** * Get the first PC associated with a line. + * The |pcmap| argument specifies which pc to source line map to use. */ - jsdIPC lineToPc (in unsigned long line); + unsigned long lineToPc (in unsigned long line, in unsigned long pcmap); /** * Determine is a particular line is executable, like checking that * lineToPc == pcToLine, except in one call. + * The |pcmap| argument specifies which pc to source line map to use. */ - boolean isLineExecutable (in unsigned long line); + boolean isLineExecutable (in unsigned long line, in unsigned long pcmap); /** * Set a breakpoint at a PC in this script. */ - void setBreakpoint (in jsdIPC pc); + void setBreakpoint (in unsigned long pc); /** * Clear a breakpoint at a PC in this script. */ - void clearBreakpoint (in jsdIPC pc); + void clearBreakpoint (in unsigned long pc); /** * Clear all breakpoints set in this script. */ @@ -646,7 +648,7 @@ interface jsdIValue : jsdIEphemeral const unsigned long TYPE_BOOLEAN = 0; /** Value is a primitive number that is too large to fit in an integer. */ const unsigned long TYPE_DOUBLE = 1; - /** Value is a number that fits into an integer. */ + /** Value is a primitive number that fits into an integer. */ const unsigned long TYPE_INT = 2; /** Value is a function. */ const unsigned long TYPE_FUNCTION = 3; diff --git a/js/jsd/jsd.h b/js/jsd/jsd.h index a257d63bfc8f..617f9f444faa 100644 --- a/js/jsd/jsd.h +++ b/js/jsd/jsd.h @@ -350,6 +350,12 @@ extern JSDScript* jsd_FindJSDScript(JSDContext* jsdc, JSScript *script); +extern JSScript * +jsd_GetJSScript (JSDContext *jsdc, JSDScript *script); + +extern JSFunction * +jsd_GetJSFunction (JSDContext *jsdc, JSDScript *script); + extern JSDScript* jsd_IterateScripts(JSDContext* jsdc, JSDScript **iterp); diff --git a/js/jsd/jsd_scpt.c b/js/jsd/jsd_scpt.c index 81659ea41e7a..fe565536a005 100644 --- a/js/jsd/jsd_scpt.c +++ b/js/jsd/jsd_scpt.c @@ -248,6 +248,18 @@ jsd_FindJSDScript( JSDContext* jsdc, return NULL; } +JSScript * +jsd_GetJSScript (JSDContext *jsdc, JSDScript *script) +{ + return script->script; +} + +JSFunction * +jsd_GetJSFunction (JSDContext *jsdc, JSDScript *script) +{ + return script->function; +} + JSDScript* jsd_IterateScripts(JSDContext* jsdc, JSDScript **iterp) { @@ -336,7 +348,7 @@ jsd_GetClosestPC(JSDContext* jsdc, JSDScript* jsdscript, uintN line) #endif return (jsuword) JS_LineNumberToPC(jsdc->dumbContext, - jsdscript->script, line ); + jsdscript->script, line ); } uintN diff --git a/js/jsd/jsd_xpc.cpp b/js/jsd/jsd_xpc.cpp index 297e3921d8a8..6842bce23155 100644 --- a/js/jsd/jsd_xpc.cpp +++ b/js/jsd/jsd_xpc.cpp @@ -34,7 +34,9 @@ */ #include "jsd_xpc.h" +#include "jsdbgapi.h" #include "jscntxt.h" +#include "jsfun.h" #include "nsIXPConnect.h" #include "nsIGenericFactory.h" @@ -55,6 +57,11 @@ #include "nsIAppShell.h" #include "nsIJSContextStack.h" +/* XXX + * defining CAUTIOUS_SCRIPTHOOK makes jsds disable GC while calling out to the + * script hook. This is a hack to avoid some js engine problems that I havn't + * properly tracked down. I'm lame. + */ #define CAUTIOUS_SCRIPTHOOK #ifdef DEBUG_verbose @@ -424,16 +431,17 @@ jsds_NotifyPendingDeadScripts (JSContext *cx) { nsCOMPtr hook = 0; gJsds->GetScriptHook (getter_AddRefs(hook)); - if (hook) - { - DeadScript *ds; + + DeadScript *ds; #ifdef CAUTIOUS_SCRIPTHOOK - JSRuntime *rt = JS_GetRuntime(cx); + JSRuntime *rt = JS_GetRuntime(cx); #endif - gJsds->Pause(nsnull); - do { - ds = gDeadScripts; - + gJsds->Pause(nsnull); + do { + ds = gDeadScripts; + + if (hook) + { /* tell the user this script has been destroyed */ #ifdef CAUTIOUS_SCRIPTHOOK JS_DISABLE_GC(rt); @@ -442,19 +450,19 @@ jsds_NotifyPendingDeadScripts (JSContext *cx) #ifdef CAUTIOUS_SCRIPTHOOK JS_ENABLE_GC(rt); #endif - /* get next deleted script */ - gDeadScripts = NS_REINTERPRET_CAST(DeadScript *, - PR_NEXT_LINK(&ds->links)); - /* take ourselves out of the circular list */ - PR_REMOVE_LINK(&ds->links); - /* addref came from the FromPtr call in jsds_ScriptHookProc */ - NS_RELEASE(ds->script); - /* free the struct! */ - PR_Free(ds); - } while (&gDeadScripts->links != &ds->links); - /* keep going until we catch up with our tail */ - gJsds->UnPause(nsnull); - } + } + /* get next deleted script */ + gDeadScripts = NS_REINTERPRET_CAST(DeadScript *, + PR_NEXT_LINK(&ds->links)); + /* take ourselves out of the circular list */ + PR_REMOVE_LINK(&ds->links); + /* addref came from the FromPtr call in jsds_ScriptHookProc */ + NS_RELEASE(ds->script); + /* free the struct! */ + PR_Free(ds); + } while (&gDeadScripts->links != &ds->links); + /* keep going until we catch up with our tail */ + gJsds->UnPause(nsnull); gDeadScripts = 0; } @@ -586,12 +594,15 @@ jsds_ScriptHookProc (JSDContext* jsdc, JSDScript* jsdscript, JSBool creating, JSRuntime *rt = JS_GetRuntime(cx); #endif + nsCOMPtr hook; + gJsds->GetScriptHook (getter_AddRefs(hook)); + if (creating) { - jsdIScriptHook *hook = 0; - - gJsds->GetScriptHook (&hook); - if (!hook) + /* a script is being created */ + if (!hook) { + /* nobody cares, just exit */ return; + } nsCOMPtr script = getter_AddRefs(jsdScript::FromPtr(jsdc, jsdscript)); @@ -605,41 +616,42 @@ jsds_ScriptHookProc (JSDContext* jsdc, JSDScript* jsdscript, JSBool creating, JS_ENABLE_GC(rt); #endif } else { + /* a script is being destroyed. even if there is no registered hook + * we'll still need to invalidate the jsdIScript record, in order + * to remove the reference held in the JSDScript private data. */ + nsCOMPtr jsdis = + NS_STATIC_CAST(jsdIScript *, JSD_GetScriptPrivate(jsdscript)); + if (!jsdis) + return; - jsdIScript *jsdis = jsdScript::FromPtr(jsdc, jsdscript); - /* the initial addref is owned by the DeadScript record */ jsdis->Invalidate(); - + if (!hook) + return; + if (gGCStatus == JSGC_END) { /* if GC *isn't* running, we can tell the user about the script * delete now. */ - nsCOMPtr hook = 0; - gJsds->GetScriptHook (getter_AddRefs(hook)); - if (hook) { #ifdef CAUTIOUS_SCRIPTHOOK - JS_DISABLE_GC(rt); + JS_DISABLE_GC(rt); #endif - gJsds->Pause(nsnull); - hook->OnScriptDestroyed (jsdis); - gJsds->UnPause(nsnull); + gJsds->Pause(nsnull); + hook->OnScriptDestroyed (jsdis); + gJsds->UnPause(nsnull); #ifdef CAUTIOUS_SCRIPTHOOK - JS_ENABLE_GC(rt); + JS_ENABLE_GC(rt); #endif - } } else { /* if a GC *is* running, we've got to wait until it's done before * we can execute any JS, so we queue the notification in a PRCList * until GC tells us it's done. See jsds_GCCallbackProc(). */ DeadScript *ds = PR_NEW(DeadScript); - if (!ds) { - NS_RELEASE(jsdis); + if (!ds) return; /* NS_ERROR_OUT_OF_MEMORY */ - } ds->jsdc = jsdc; ds->script = jsdis; - + NS_ADDREF(ds->script); if (gDeadScripts) /* if the queue exists, add to it */ PR_APPEND_LINK(&ds->links, &gDeadScripts->links); @@ -649,9 +661,7 @@ jsds_ScriptHookProc (JSDContext* jsdc, JSDScript* jsdscript, JSBool creating, gDeadScripts = ds; } } - } - - + } } /******************************************************************************* @@ -724,16 +734,6 @@ jsdObject::GetValue(jsdIValue **_rval) return NS_OK; } -/* PC */ -NS_IMPL_THREADSAFE_ISUPPORTS1(jsdPC, jsdIPC); - -NS_IMETHODIMP -jsdPC::GetPc(jsuword *_rval) -{ - *_rval = mPC; - return NS_OK; -} - /* Properties */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdProperty, jsdIProperty, jsdIEphemeral); @@ -837,12 +837,15 @@ jsdProperty::GetVarArgSlot(PRUint32 *_rval) NS_IMPL_THREADSAFE_ISUPPORTS2(jsdScript, jsdIScript, jsdIEphemeral); jsdScript::jsdScript (JSDContext *aCx, JSDScript *aScript) : mValid(PR_FALSE), + mTag(0), mCx(aCx), mScript(aScript), mFileName(0), mFunctionName(0), mBaseLineNumber(0), - mLineExtent(0) + mLineExtent(0), + mPPLineMap(0), + mFirstPC(0) { DEBUG_CREATE ("jsdScript", gScriptCount); NS_INIT_ISUPPORTS(); @@ -856,6 +859,7 @@ jsdScript::jsdScript (JSDContext *aCx, JSDScript *aScript) : mValid(PR_FALSE), new nsCString(JSD_GetScriptFunctionName(mCx, mScript)); mBaseLineNumber = JSD_GetScriptBaseLineNumber(mCx, mScript); mLineExtent = JSD_GetScriptLineExtent(mCx, mScript); + mFirstPC = JSD_GetClosestPC(mCx, mScript, 0); JSD_UnlockScriptSubsystem(mCx); mValid = PR_TRUE; @@ -869,12 +873,123 @@ jsdScript::~jsdScript () delete mFileName; if (mFunctionName) delete mFunctionName; - + + if (mPPLineMap) + PR_Free(mPPLineMap); + /* Invalidate() needs to be called to release an owning reference to * ourselves, so if we got here without being invalidated, something * has gone wrong with our ref count. */ NS_ASSERTION (!mValid, "Script destroyed without being invalidated."); +} + +/* + * This method populates a line <-> pc map for a pretty printed version of this + * script. It does this by decompiling, and then recompiling the script. The + * resulting script is scanned for the line map, and then left as GC fodder. + */ +PCMapEntry * +jsdScript::CreatePPLineMap() +{ + JSContext *cx = JSD_GetDefaultJSContext (mCx); + JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL); + JSFunction *fun = JSD_GetJSFunction (mCx, mScript); + JSScript *script; + PRUint32 baseLine; + PRBool scriptOwner = PR_FALSE; + if (fun) { + if (fun->nargs > 8) + return 0; + JSString *jsstr = JS_DecompileFunctionBody (cx, fun, 4); + if (!jsstr) + return 0; + + const char *argnames[] = {"arg1", "arg2", "arg3", "arg4", + "arg5", "arg6", "arg7", "arg8", + "arg9", "arg10", "arg11", "arg12" }; + fun = JS_CompileUCFunction (cx, obj, "ppfun", fun->nargs, argnames, + JS_GetStringChars(jsstr), + JS_GetStringLength(jsstr), + "jsd:ppfun", 3); + if (!fun || !(script = JS_GetFunctionScript(cx, fun))) + return 0; + baseLine = 3; + } else { + JSString *jsstr = JS_DecompileScript (cx, JSD_GetJSScript(mCx, mScript), + "ppscript", 4); + if (!jsstr) + return 0; + + script = JS_CompileUCScript (cx, obj, + JS_GetStringChars(jsstr), + JS_GetStringLength(jsstr), + "jsd:ppscript", 1); + if (!script) + return 0; + scriptOwner = PR_TRUE; + baseLine = 1; + } + + PRUint32 scriptExtent = JS_GetScriptLineExtent (cx, script); + PRUint32 firstPC = (PRUint32) JS_LineNumberToPC (cx, script, 0); + /* allocate worst case size of map (number of lines in script + 1 + * for our 0 record), we'll shrink it with a realloc later. */ + mPPLineMap = + NS_STATIC_CAST(PCMapEntry *, + PR_Malloc((scriptExtent + 1) * sizeof (PCMapEntry))); + if (mPPLineMap) { + mPCMapSize = 0; + for (PRUint32 line = baseLine; line < scriptExtent + baseLine; ++line) { + PRUint32 pc = (PRUint32) JS_LineNumberToPC (cx, script, + line); + if (line == JS_PCToLineNumber (cx, script, (jsbytecode *)pc)) { + pc -= firstPC; + mPPLineMap[mPCMapSize].line = line; + mPPLineMap[mPCMapSize].pc = pc; + ++mPCMapSize; + } + } + if (scriptExtent != mPCMapSize) { + mPPLineMap = + NS_STATIC_CAST(PCMapEntry *, + PR_Realloc(mPPLineMap, + mPCMapSize * sizeof(PCMapEntry))); + } + } + + if (scriptOwner) + JS_DestroyScript (cx, script); + + return mPPLineMap; +} + +PRUint32 +jsdScript::PPPcToLine (PRUint32 aPC) +{ + if (!mPPLineMap && !CreatePPLineMap()) + return 0; + PRUint32 i; + for (i = 1; i < mPCMapSize; ++i) { + if (mPPLineMap[i].pc > aPC) + return mPPLineMap[i - 1].line; + } + + return mPPLineMap[mPCMapSize - 1].line; +} + +PRUint32 +jsdScript::PPLineToPc (PRUint32 aLine) +{ + if (!mPPLineMap && !CreatePPLineMap()) + return 0; + PRUint32 i; + for (i = 1; i < mPCMapSize; ++i) { + if (mPPLineMap[i].line > aLine) + return mPPLineMap[i - 1].pc; + } + + return mPPLineMap[mPCMapSize - 1].pc; } NS_IMETHODIMP @@ -893,6 +1008,16 @@ jsdScript::GetJSDScript(JSDScript **_rval) return NS_OK; } +NS_IMETHODIMP +jsdScript::GetTag(PRUint32 *_rval) +{ + if (!mTag) + mTag = ++jsdScript::LastTag; + + *_rval = mTag; + return NS_OK; +} + NS_IMETHODIMP jsdScript::Invalidate() { @@ -904,10 +1029,28 @@ jsdScript::Invalidate() JSD_GetScriptPrivate(mScript)); NS_ASSERTION (script == this, "That's not my script!"); NS_RELEASE(script); - + JSD_SetScriptPrivate(mScript, NULL); return NS_OK; } +void +jsdScript::InvalidateAll () +{ + JSDContext *cx; + gJsds->GetJSDContext (&cx); + JSDScript *script; + JSDScript *iter = NULL; + + JSD_LockScriptSubsystem(cx); + while((script = JSD_IterateScripts(cx, &iter)) != NULL) { + jsdIScript *jsdis = NS_STATIC_CAST(jsdIScript *, + JSD_GetScriptPrivate(script)); + if (jsdis) + jsdis->Invalidate(); + } + JSD_UnlockScriptSubsystem(cx); +} + NS_IMETHODIMP jsdScript::GetIsValid(PRBool *_rval) { @@ -929,6 +1072,30 @@ jsdScript::GetFunctionName(char **_rval) return NS_OK; } +NS_IMETHODIMP +jsdScript::GetFunctionSource(nsAString & aFunctionSource) +{ + ASSERT_VALID_SCRIPT; + JSContext *cx = JSD_GetDefaultJSContext (mCx); + if (NS_WARN_IF_FALSE(cx, "No default context !?")) + return NS_ERROR_FAILURE; + JSFunction *fun = JSD_GetJSFunction (mCx, mScript); + JSString *jsstr; + if (fun) + { + jsstr = JS_DecompileFunction (cx, fun, 4); + } + else + { + JSScript *script = JSD_GetJSScript (mCx, mScript); + jsstr = JS_DecompileScript (cx, script, "ppscript", 4); + } + if (!jsstr) + return NS_ERROR_FAILURE; + aFunctionSource = JS_GetStringChars(jsstr); + return NS_OK; +} + NS_IMETHODIMP jsdScript::GetBaseLineNumber(PRUint32 *_rval) { @@ -944,55 +1111,75 @@ jsdScript::GetLineExtent(PRUint32 *_rval) } NS_IMETHODIMP -jsdScript::PcToLine(jsdIPC *aPC, PRUint32 *_rval) +jsdScript::PcToLine(PRUint32 aPC, PRUint32 aPcmap, PRUint32 *_rval) { ASSERT_VALID_SCRIPT; - jsuword pc; - aPC->GetPc(&pc); - *_rval = JSD_GetClosestLine (mCx, mScript, pc); - return NS_OK; -} - -NS_IMETHODIMP -jsdScript::LineToPc(PRUint32 aLine, jsdIPC **_rval) -{ - ASSERT_VALID_SCRIPT; - jsuword pc = JSD_GetClosestPC (mCx, mScript, aLine); - *_rval = jsdPC::FromPtr (pc); - return NS_OK; -} - -NS_IMETHODIMP -jsdScript::IsLineExecutable(PRUint32 aLine, PRBool *_rval) -{ - ASSERT_VALID_SCRIPT; - jsuword pc = JSD_GetClosestPC (mCx, mScript, aLine); - *_rval = (aLine == JSD_GetClosestLine (mCx, mScript, pc)); - return NS_OK; -} - -NS_IMETHODIMP -jsdScript::SetBreakpoint(jsdIPC *aPC) -{ - ASSERT_VALID_SCRIPT; - jsuword pc; - aPC->GetPc (&pc); + if (aPcmap == PCMAP_SOURCETEXT) { + *_rval = JSD_GetClosestLine (mCx, mScript, mFirstPC + aPC); + } else if (aPcmap == PCMAP_PRETTYPRINT) { + *_rval = PPPcToLine(aPC); + } else { + return NS_ERROR_INVALID_ARG; + } + return NS_OK; +} + +NS_IMETHODIMP +jsdScript::LineToPc(PRUint32 aLine, PRUint32 aPcmap, PRUint32 *_rval) +{ + ASSERT_VALID_SCRIPT; + if (aPcmap == PCMAP_SOURCETEXT) { + jsuword pc = JSD_GetClosestPC (mCx, mScript, aLine); + *_rval = pc - mFirstPC; + } else if (aPcmap == PCMAP_PRETTYPRINT) { + *_rval = PPLineToPc(aLine); + } else { + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + +NS_IMETHODIMP +jsdScript::IsLineExecutable(PRUint32 aLine, PRUint32 aPcmap, PRBool *_rval) +{ + ASSERT_VALID_SCRIPT; + if (aPcmap == PCMAP_SOURCETEXT) { + jsuword pc = JSD_GetClosestPC (mCx, mScript, aLine); + *_rval = (aLine == JSD_GetClosestLine (mCx, mScript, pc)); + } else if (aPcmap == PCMAP_PRETTYPRINT) { + if (!mPPLineMap && !CreatePPLineMap()) + return NS_ERROR_FAILURE; + *_rval = PR_FALSE; + for (PRUint32 i = 0; i < mPCMapSize; ++i) { + if (mPPLineMap[i].line >= aLine) { + *_rval = (mPPLineMap[i].line == aLine); + break; + } + } + } else { + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + +NS_IMETHODIMP +jsdScript::SetBreakpoint(PRUint32 aPC) +{ + ASSERT_VALID_SCRIPT; + jsuword pc = mFirstPC + aPC; JSD_SetExecutionHook (mCx, mScript, pc, jsds_ExecutionHookProc, NS_REINTERPRET_CAST(void *, PRIVATE_TO_JSVAL(NULL))); return NS_OK; } NS_IMETHODIMP -jsdScript::ClearBreakpoint(jsdIPC *aPC) +jsdScript::ClearBreakpoint(PRUint32 aPC) { ASSERT_VALID_SCRIPT; - if (!aPC) - return NS_ERROR_INVALID_ARG; - - jsuword pc; - aPC->GetPc (&pc); - + jsuword pc = mFirstPC + aPC; JSD_ClearExecutionHook (mCx, mScript, pc); return NS_OK; } @@ -1070,12 +1257,17 @@ jsdStackFrame::GetScript(jsdIScript **_rval) } NS_IMETHODIMP -jsdStackFrame::GetPc(jsdIPC **_rval) +jsdStackFrame::GetPc(PRUint32 *_rval) { ASSERT_VALID_FRAME; - jsuword pc; - pc = JSD_GetPCForStackFrame (mCx, mThreadState, mStackFrameInfo); - *_rval = jsdPC::FromPtr (pc); + JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, + mStackFrameInfo); + jsuword pcbase = JSD_GetClosestPC(mCx, script, 0); + if (!script) + return NS_ERROR_FAILURE; + + jsuword pc = JSD_GetPCForStackFrame (mCx, mThreadState, mStackFrameInfo); + *_rval = pc - pcbase; return NS_OK; } @@ -1424,6 +1616,13 @@ jsdValue::Refresh() ******************************************************************************/ NS_IMPL_THREADSAFE_ISUPPORTS1(jsdService, jsdIDebuggerService); +NS_IMETHODIMP +jsdService::GetJSDContext(JSDContext **_rval) +{ + *_rval = mCx; + return NS_OK; +} + NS_IMETHODIMP jsdService::GetInitAtStartup (PRBool *_rval) { @@ -1587,8 +1786,8 @@ jsdService::OnForRuntime (JSRuntime *rt) */ if (mThrowHook) JSD_SetThrowHook (mCx, jsds_ExecutionHookProc, NULL); - if (mScriptHook) - JSD_SetScriptHook (mCx, jsds_ScriptHookProc, NULL); + /* can't ignore script callbacks, as we need to |Release| the wrapper + * stored in private data when a script is deleted. */ if (mInterruptHook) JSD_SetInterruptHook (mCx, jsds_ExecutionHookProc, NULL); if (mDebuggerHook) @@ -1635,12 +1834,12 @@ jsdService::Off (void) JS_SetGCCallbackRT (mRuntime, gLastGCProc); */ + jsdScript::InvalidateAll(); jsdValue::InvalidateAll(); jsdProperty::InvalidateAll(); ClearAllBreakpoints(); JSD_ClearThrowHook (mCx); - JSD_SetScriptHook (mCx, NULL, NULL); JSD_ClearInterruptHook (mCx); JSD_ClearDebuggerHook (mCx); JSD_ClearDebugBreakHook (mCx); @@ -1720,7 +1919,9 @@ jsdService::EnumerateScripts (jsdIScriptEnumerator *enumerator) JSD_LockScriptSubsystem(mCx); while((script = JSD_IterateScripts(mCx, &iter)) != NULL) { - rv = enumerator->EnumerateScript (jsdScript::FromPtr(mCx, script)); + nsCOMPtr jsdis = + getter_AddRefs(jsdScript::FromPtr(mCx, script)); + rv = enumerator->EnumerateScript (jsdis); if (NS_FAILED(rv)) break; } @@ -2098,9 +2299,9 @@ jsdService::SetScriptHook (jsdIScriptHook *aHook) if (aHook) JSD_SetScriptHook (mCx, jsds_ScriptHookProc, NULL); - else - JSD_SetScriptHook (mCx, NULL, NULL); - + /* we can't unset it if !aHook, because we still need to see script + * deletes in order to Release the jsdIScripts held in JSDScript + * private data. */ return NS_OK; } diff --git a/js/jsd/jsd_xpc.h b/js/jsd/jsd_xpc.h index 176da1473cfc..5a2a0dcfde23 100644 --- a/js/jsd/jsd_xpc.h +++ b/js/jsd/jsd_xpc.h @@ -48,43 +48,18 @@ struct LiveEphemeral { /* link in a chain of live values list */ - PRCList links; + PRCList links; jsdIEphemeral *value; }; +struct PCMapEntry { + PRUint32 pc, line; +}; + /******************************************************************************* * reflected jsd data structures *******************************************************************************/ -class jsdPC : public jsdIPC -{ - public: - NS_DECL_ISUPPORTS - NS_DECL_JSDIPC - - /* you'll normally use use FromPtr() instead of directly constructing one */ - jsdPC (jsuword aPC) : mPC(aPC) - { - NS_INIT_ISUPPORTS(); - } - - static jsdIPC *FromPtr (jsuword aPC) - { - if (!aPC) - return nsnull; - - jsdIPC *rv = new jsdPC (aPC); - NS_IF_ADDREF(rv); - return rv; - } - - private: - jsdPC(); /* no implementation */ - jsdPC(const jsdPC&); /* no implementation */ - - jsuword mPC; -}; - class jsdObject : public jsdIObject { public: @@ -183,18 +158,31 @@ class jsdScript : public jsdIScript return rv; } + static void InvalidateAll(); + private: + static PRUint32 LastTag; + jsdScript(); /* no implementation */ jsdScript (const jsdScript&); /* no implementation */ + PCMapEntry* CreatePPLineMap(); + PRUint32 PPPcToLine(PRUint32 aPC); + PRUint32 PPLineToPc(PRUint32 aLine); PRBool mValid; + PRUint32 mTag; JSDContext *mCx; JSDScript *mScript; nsCString *mFileName; nsCString *mFunctionName; PRUint32 mBaseLineNumber, mLineExtent; + PCMapEntry *mPPLineMap; + PRUint32 mPCMapSize; + jsuword mFirstPC; }; +PRUint32 jsdScript::LastTag = 0; + class jsdStackFrame : public jsdIStackFrame { public: diff --git a/js/jsd/jsdebug.c b/js/jsd/jsdebug.c index 21a3a548706a..ae82765cb2fd 100644 --- a/js/jsd/jsdebug.c +++ b/js/jsd/jsdebug.c @@ -142,6 +142,18 @@ JSD_IterateScripts(JSDContext* jsdc, JSDScript **iterp) return jsd_IterateScripts(jsdc, iterp); } +JSD_PUBLIC_API(JSScript*) +JSD_GetJSScript(JSDContext* jsdc, JSDScript *script) +{ + return jsd_GetJSScript(jsdc, script); +} + +JSD_PUBLIC_API(JSFunction*) +JSD_GetJSFunction(JSDContext* jsdc, JSDScript *script) +{ + return jsd_GetJSFunction (jsdc, script); +} + JSD_PUBLIC_API(void *) JSD_SetScriptPrivate(JSDScript *jsdscript, void *data) { diff --git a/js/jsd/jsdebug.h b/js/jsd/jsdebug.h index 3195e5c4f069..93f6aba6e30c 100644 --- a/js/jsd/jsdebug.h +++ b/js/jsd/jsdebug.h @@ -234,6 +234,18 @@ JSD_UnlockScriptSubsystem(JSDContext* jsdc); extern JSD_PUBLIC_API(JSDScript*) JSD_IterateScripts(JSDContext* jsdc, JSDScript **iterp); +/* +* Get the JSScript for a JSDScript +*/ +extern JSD_PUBLIC_API(JSScript*) +JSD_GetJSScript(JSDContext* jsdc, JSDScript *script); + +/* +* Get the JSFunction for a JSDScript +*/ +extern JSD_PUBLIC_API(JSFunction*) +JSD_GetJSFunction(JSDContext* jsdc, JSDScript *script); + /* * Set the private data for this script, returns previous value */