Bug 638178 - Add jsdIScript.getExecutableLines for fast identification of breakable lines (r=timeless)
☠☠ backed out by dba38247d691 ☠ ☠
authorSteve Fink <sfink@mozilla.com>
Thu, 03 Mar 2011 10:11:54 -0800
changeset 70872 8d39d7a5b85ceb6b6dbae9c8297fc26cbc92381f
parent 70871 80231df12d57f15797eaf0614c8b0146b193a7b2
child 70873 6358f05bdd6d38a5ae57fd65c989aa0bd339a04e
push idunknown
push userunknown
push dateunknown
reviewerstimeless
bugs638178
milestone6.0a1
Bug 638178 - Add jsdIScript.getExecutableLines for fast identification of breakable lines (r=timeless)
js/jsd/idl/jsdIDebuggerService.idl
js/jsd/jsd.h
js/jsd/jsd_scpt.c
js/jsd/jsd_xpc.cpp
js/jsd/jsdebug.c
js/jsd/jsdebug.h
js/src/jsdbgapi.cpp
js/src/jsdbgapi.h
--- a/js/jsd/idl/jsdIDebuggerService.idl
+++ b/js/jsd/idl/jsdIDebuggerService.idl
@@ -537,17 +537,17 @@ interface jsdIContextEnumerator : nsISup
      * currently in use.
      */
     void enumerateContext(in jsdIContext executionContext);
 };
 
 /**
  * Set jsdIDebuggerService::scriptHook to an instance of one of these.
  */
-[scriptable, uuid(bb722893-0f63-45c5-b547-7a0947c7b6b6)]
+[scriptable, uuid(d030d1a2-a58a-4f19-b9e3-96da4e2cdd09)]
 interface jsdIScriptHook : nsISupports
 {
     /**
      * Called when scripts are created.
      */
     void onScriptCreated(in jsdIScript script);
     /**
      * Called when the JavaScript engine destroys a script. The jsdIScript
@@ -806,17 +806,17 @@ interface jsdIContext : jsdIEphemeral
 
 /**
  * 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
  * frame you found attached through it, are invalidated via the jsdIEphemeral
  * interface. Once a jsdIStackFrame has been invalidated all method and
  * property accesses will throw a NS_ERROR_NOT_AVAILABLE exception.
  */
-[scriptable, uuid(0633ca73-105e-4e8e-bcc5-13405d61754a)]
+[scriptable, uuid(7c95422c-7579-4a6f-8ef7-e5b391552ee5)]
 interface jsdIStackFrame : jsdIEphemeral
 {
     /** Internal use only. */
     [noscript] readonly attribute JSDContext        JSDContext;
     /** Internal use only. */
     [noscript] readonly attribute JSDThreadState    JSDThreadState;
     /** Internal use only. */
     [noscript] readonly attribute JSDStackFrameInfo JSDStackFrameInfo;
@@ -879,17 +879,17 @@ interface jsdIStackFrame : jsdIEphemeral
                  in unsigned long line, out jsdIValue result);
     
 };
 
 /**
  * Script object. In JavaScript engine terms, there's a single script for each
  * function, and one for the top level script.
  */
-[scriptable, uuid(e7935220-7def-4c8e-832f-fbc948a97490)]
+[scriptable, uuid(721724e0-7716-4bf4-b48f-92b78d056141)]
 interface jsdIScript : jsdIEphemeral
 {
     /** Internal use only. */
     [noscript] readonly attribute JSDContext JSDContext;
     /** Internal use only. */
     [noscript] readonly attribute JSDScript  JSDScript;
     
     /**
@@ -1025,16 +1025,27 @@ interface jsdIScript : jsdIEphemeral
      */
     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, in unsigned long pcmap);
+
+    /**
+     * Return a list of all executable lines in a script.
+     * |pcmap| specifies which pc to source line map to use.
+     * |startLine| and |maxLines| may be used to retrieve a chunk at a time.
+     */
+    void getExecutableLines(in unsigned long pcmap,
+                            in unsigned long startLine, in unsigned long maxLines,
+                            out unsigned long count,
+                            [array, size_is(count), retval] out unsigned long executableLines);
+ 
     /**
      * Set a breakpoint at a PC in this script.
      */
     void setBreakpoint(in unsigned long pc);
     /**
      * Clear a breakpoint at a PC in this script.
      */
     void clearBreakpoint(in unsigned long pc);
@@ -1049,17 +1060,17 @@ interface jsdIScript : jsdIEphemeral
 };
 
 /**
  * Value objects. Represents typeless JavaScript values (jsval in SpiderMonkey
  * terminology.)  These are valid until the debugger is turned off. Holding a
  * jsdIValue adds a root for the underlying JavaScript value, so don't keep it
  * if you don't need to.
  */
-[scriptable, uuid(fd1311f7-096c-44a3-847b-9d478c8176c3)]
+[scriptable, uuid(861c4d37-e115-4a52-9f76-273cb6b21c3b)]
 interface jsdIValue : jsdIEphemeral
 {
     /** Internal use only. */
     [noscript] readonly attribute JSDContext JSDContext;
     /** Internal use only. */
     [noscript] readonly attribute JSDValue   JSDValue;
 
     /**
--- a/js/jsd/jsd.h
+++ b/js/jsd/jsd.h
@@ -476,16 +476,21 @@ extern JSBool
 jsd_GetScriptHook(JSDContext* jsdc, JSD_ScriptHookProc* hook, void** callerdata);
 
 extern jsuword
 jsd_GetClosestPC(JSDContext* jsdc, JSDScript* jsdscript, uintN line);
 
 extern uintN
 jsd_GetClosestLine(JSDContext* jsdc, JSDScript* jsdscript, jsuword pc);
 
+extern JSBool
+jsd_GetLinePCs(JSDContext* jsdc, JSDScript* jsdscript,
+               uintN startLine, uintN maxLines,
+               uintN* count, uintN** lines, jsuword** pcs);
+
 extern void
 jsd_NewScriptHookProc(
                 JSContext   *cx,
                 const char  *filename,      /* URL this script loads from */
                 uintN       lineno,         /* line where this script starts */
                 JSScript    *script,
                 JSFunction  *fun,
                 void*       callerdata);
--- a/js/jsd/jsd_scpt.c
+++ b/js/jsd/jsd_scpt.c
@@ -576,16 +576,54 @@ jsd_GetClosestLine(JSDContext* jsdc, JSD
         line = newline;
     }
 #endif
 
     return line;    
 }
 
 JSBool
+jsd_GetLinePCs(JSDContext* jsdc, JSDScript* jsdscript,
+               uintN startLine, uintN maxLines,
+               uintN* count, uintN** retLines, jsuword** retPCs)
+{
+    JSCrossCompartmentCall *call;
+    uintN first = jsdscript->lineBase;
+    uintN last = first + jsd_GetScriptLineExtent(jsdc, jsdscript) - 1;
+    JSBool ok;
+    uintN *lines;
+    jsbytecode **pcs;
+    uintN i;
+
+    if (last < startLine)
+        return JS_TRUE;
+
+    call = JS_EnterCrossCompartmentCallScript(jsdc->dumbContext, jsdscript->script);
+    if (!call)
+        return JS_FALSE;
+
+    ok = JS_GetLinePCs(jsdc->dumbContext, jsdscript->script,
+                       startLine, maxLines,
+                       count, retLines, &pcs);
+
+    if (ok) {
+        if (retPCs) {
+            for (i = 0; i < *count; ++i) {
+                (*retPCs)[i] = (*pcs)[i];
+            }
+        }
+
+        JS_free(jsdc->dumbContext, pcs);
+    }
+
+    JS_LeaveCrossCompartmentCall(call);
+    return ok;
+}
+
+JSBool
 jsd_SetScriptHook(JSDContext* jsdc, JSD_ScriptHookProc hook, void* callerdata)
 {
     JSD_LOCK();
     jsdc->scriptHook = hook;
     jsdc->scriptHookData = callerdata;
     JSD_UNLOCK();
     return JS_TRUE;
 }
--- a/js/jsd/jsd_xpc.cpp
+++ b/js/jsd/jsd_xpc.cpp
@@ -1537,16 +1537,68 @@ jsdScript::EnableSingleStepInterrupts(PR
     /* Must have set interrupt hook before enabling */
     if (enable && !jsdService::GetService()->CheckInterruptHook())
         return NS_ERROR_NOT_INITIALIZED;
 
     return (JSD_EnableSingleStepInterrupts(mCx, mScript, enable) ? NS_OK : NS_ERROR_FAILURE);
 }
 
 NS_IMETHODIMP
+jsdScript::GetExecutableLines(PRUint32 aPcmap, PRUint32 aStartLine, PRUint32 aMaxLines,
+                              PRUint32* aCount, PRUint32** aExecutableLines)
+{
+    ASSERT_VALID_EPHEMERAL;
+    if (aPcmap == PCMAP_SOURCETEXT) {
+        jsuword start = JSD_GetClosestPC(mCx, mScript, 0);
+        uintN lastLine = JSD_GetScriptBaseLineNumber(mCx, mScript)
+                       + JSD_GetScriptLineExtent(mCx, mScript) - 1;
+        jsuword end = JSD_GetClosestPC(mCx, mScript, lastLine + 1);
+
+        *aExecutableLines = static_cast<PRUint32*>(NS_Alloc((end - start + 1) * sizeof(PRUint32)));
+        if (!JSD_GetLinePCs(mCx, mScript, aStartLine, aMaxLines, aCount, aExecutableLines, NULL))
+            return NS_ERROR_OUT_OF_MEMORY;
+        
+        return NS_OK;
+    }
+
+    if (aPcmap == PCMAP_PRETTYPRINT) {
+        if (!mPPLineMap) {
+            if (!CreatePPLineMap())
+                return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        nsTArray<PRUint32> lines;
+        PRUint32 i;
+
+        for (i = 0; i < mPCMapSize; ++i) {
+            if (mPPLineMap[i].line >= aStartLine)
+                break;
+        }
+
+        for (; i < mPCMapSize && lines.Length() < aMaxLines; ++i) {
+            lines.AppendElement(mPPLineMap[i].line);
+        }
+
+        if (aCount)
+            *aCount = lines.Length();
+
+        *aExecutableLines = static_cast<PRUint32*>(NS_Alloc(lines.Length() * sizeof(PRUint32)));
+        if (!*aExecutableLines)
+            return NS_ERROR_OUT_OF_MEMORY;
+
+        for (i = 0; i < lines.Length(); ++i)
+            (*aExecutableLines)[i] = lines[i];
+
+        return NS_OK;
+    }
+
+    return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
 jsdScript::IsLineExecutable(PRUint32 aLine, PRUint32 aPcmap, PRBool *_rval)
 {
     ASSERT_VALID_EPHEMERAL;
     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())
--- a/js/jsd/jsdebug.c
+++ b/js/jsd/jsdebug.c
@@ -351,16 +351,26 @@ JSD_GetClosestPC(JSDContext* jsdc, JSDSc
 JSD_PUBLIC_API(uintN)
 JSD_GetClosestLine(JSDContext* jsdc, JSDScript* jsdscript, jsuword pc)
 {
     JSD_ASSERT_VALID_CONTEXT(jsdc);
     JSD_ASSERT_VALID_SCRIPT(jsdscript);
     return jsd_GetClosestLine(jsdc, jsdscript, pc);
 }
 
+JSD_PUBLIC_API(JSBool)
+JSD_GetLinePCs(JSDContext* jsdc, JSDScript* jsdscript,
+               uintN startLine, uintN maxLines,
+               uintN* count, uintN** lines, jsuword** pcs)
+{
+    JSD_ASSERT_VALID_CONTEXT(jsdc);
+    JSD_ASSERT_VALID_SCRIPT(jsdscript);
+    return jsd_GetLinePCs(jsdc, jsdscript, startLine, maxLines, count, lines, pcs);
+}
+
 JSD_PUBLIC_API(void)
 JSD_ScriptCreated(JSDContext* jsdc,
                   JSContext   *cx,
                   const char  *filename,    /* URL this script loads from */
                   uintN       lineno,       /* line where this script starts */
                   JSScript    *script,
                   JSFunction  *fun)
 {
--- a/js/jsd/jsdebug.h
+++ b/js/jsd/jsdebug.h
@@ -491,16 +491,27 @@ JSD_GetClosestPC(JSDContext* jsdc, JSDSc
 /*
 * Get the source line number for a given 'Program Counter' location.
 * Returns 0 if no source line information is appropriate (or available) for
 * the given pc.
 */
 extern JSD_PUBLIC_API(uintN)
 JSD_GetClosestLine(JSDContext* jsdc, JSDScript* jsdscript, jsuword pc);
 
+/*
+ * Get a list of lines and the corresponding earliest PC for each (see
+ * JSD_GetClosestPC). Lines with no PCs associated will not be returned. NULL
+ * may be passed for either lines or pcs to avoid filling anything in for that
+ * argument.
+ */
+extern JSD_PUBLIC_API(JSBool)
+JSD_GetLinePCs(JSDContext* jsdc, JSDScript* jsdscript,
+               uintN startLine, uintN maxLines,
+               uintN* count, uintN** lines, jsuword** pcs);
+
 /* these are only used in cases where scripts are created outside of JS*/
 
 /*
 * Direct call to notify JSD that a script has been created.
 * Embeddings that use the normal jsapi script functions need not call this.
 * Any embedding that follows the (discouraged!) practice of contructing script
 * structures manually should call this function to inform JSD. (older ssjs
 * systems do this).
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -1294,16 +1294,69 @@ JS_LineNumberToPC(JSContext *cx, JSScrip
 }
 
 JS_PUBLIC_API(jsbytecode *)
 JS_EndPC(JSContext *cx, JSScript *script)
 {
     return script->code + script->length;
 }
 
+JS_PUBLIC_API(JSBool)
+JS_GetLinePCs(JSContext *cx, JSScript *script,
+              uintN startLine, uintN maxLines,
+              uintN* count, uintN** retLines, jsbytecode*** retPCs)
+{
+    uintN* lines;
+    jsbytecode** pcs;
+    size_t len = (script->length > maxLines ? maxLines : script->length);
+    lines = (uintN*) cx->malloc_(len * sizeof(uintN));
+    if (!lines)
+        return JS_FALSE;
+
+    pcs = (jsbytecode**) cx->malloc_(len * sizeof(jsbytecode*));
+    if (!pcs) {
+        cx->free_(lines);
+        return JS_FALSE;
+    }
+
+    uintN lineno = script->lineno;
+    uintN offset = 0;
+    uintN i = 0;
+    for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
+        offset += SN_DELTA(sn);
+        JSSrcNoteType type = (JSSrcNoteType) SN_TYPE(sn);
+        if (type == SRC_SETLINE || type == SRC_NEWLINE) {
+            if (type == SRC_SETLINE)
+                lineno = (uintN) js_GetSrcNoteOffset(sn, 0);
+            else
+                lineno++;
+
+            if (lineno >= startLine) {
+                lines[i] = lineno;
+                pcs[i] = script->code + offset;
+                if (++i >= maxLines)
+                    break;
+            }
+        }
+    }
+
+    *count = i;
+    if (retLines)
+        *retLines = lines;
+    else
+        cx->free_(lines);
+
+    if (retPCs)
+        *retPCs = pcs;
+    else
+        cx->free_(pcs);
+
+    return JS_TRUE;
+}
+
 JS_PUBLIC_API(uintN)
 JS_GetFunctionArgumentCount(JSContext *cx, JSFunction *fun)
 {
     return fun->nargs;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_FunctionHasLocalNames(JSContext *cx, JSFunction *fun)
--- a/js/src/jsdbgapi.h
+++ b/js/src/jsdbgapi.h
@@ -212,16 +212,21 @@ extern JS_PUBLIC_API(uintN)
 JS_PCToLineNumber(JSContext *cx, JSScript *script, jsbytecode *pc);
 
 extern JS_PUBLIC_API(jsbytecode *)
 JS_LineNumberToPC(JSContext *cx, JSScript *script, uintN lineno);
 
 extern JS_PUBLIC_API(jsbytecode *)
 JS_EndPC(JSContext *cx, JSScript *script);
 
+extern JS_PUBLIC_API(JSBool)
+JS_GetLinePCs(JSContext *cx, JSScript *script,
+              uintN startLine, uintN maxLines,
+              uintN* count, uintN** lines, jsbytecode*** pcs);
+
 extern JS_PUBLIC_API(uintN)
 JS_GetFunctionArgumentCount(JSContext *cx, JSFunction *fun);
 
 extern JS_PUBLIC_API(JSBool)
 JS_FunctionHasLocalNames(JSContext *cx, JSFunction *fun);
 
 /*
  * N.B. The mark is in the context temp pool and thus the caller must take care