Bug 602003: add jsd API to query valid script begin and end PCs, r=sayrer,jjb
authorDavid Mandelin <dmandelin@mozilla.com>
Wed, 06 Oct 2010 11:23:14 -0700
changeset 55561 1998fa24043490e63ce38e724e5f850e08904c7a
parent 55560 ab87d9da6e2f406cccf85bc58baa9e956eecb88a
child 55562 7b037f68bccfd6fea0c60b12b75eecd021005633
child 56026 2bdf648e701581ddc3c867ae8ba6039b2c3483ae
push id16269
push userjst@mozilla.com
push dateThu, 14 Oct 2010 01:40:35 +0000
treeherdermozilla-central@29c228a4d7eb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssayrer, jjb
bugs602003
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 602003: add jsd API to query valid script begin and end PCs, r=sayrer,jjb
js/jsd/idl/jsdIDebuggerService.idl
js/jsd/jsd.h
js/jsd/jsd_scpt.c
js/jsd/jsd_xpc.cpp
js/jsd/jsd_xpc.h
js/jsd/jsdebug.c
js/jsd/jsdebug.h
js/jsd/test/Makefile.in
js/jsd/test/test_bug602003.html
js/src/jscntxt.cpp
js/src/jsdbgapi.cpp
js/src/jsdbgapi.h
--- a/js/jsd/idl/jsdIDebuggerService.idl
+++ b/js/jsd/idl/jsdIDebuggerService.idl
@@ -997,16 +997,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);
     /**
+     * Get the first valid PC in the script. This will be either
+     * (a) the first bytecode in the script, or (b) the next bytecode
+     * in the script, iff the first bytecode is a JSOP_BEGIN.
+     */
+    unsigned long getFirstValidPC ();
+    /**
+     * Return the last valid PC in the script (i.e., the PC just after 
+     * the last bytecode).
+     */
+    unsigned long getEndValidPC ();
+    /**
      * 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);
     /**
--- a/js/jsd/jsd.h
+++ b/js/jsd/jsd.h
@@ -473,16 +473,22 @@ extern JSBool
 jsd_SetScriptHook(JSDContext* jsdc, JSD_ScriptHookProc hook, void* callerdata);
 
 extern JSBool
 jsd_GetScriptHook(JSDContext* jsdc, JSD_ScriptHookProc* hook, void** callerdata);
 
 extern jsuword
 jsd_GetClosestPC(JSDContext* jsdc, JSDScript* jsdscript, uintN line);
 
+extern jsuword
+jsd_GetFirstValidPC(JSDContext* jsdc, JSDScript* jsdscript);
+
+extern jsuword
+jsd_GetEndPC(JSDContext* jsdc, JSDScript* jsdscript);
+
 extern uintN
 jsd_GetClosestLine(JSDContext* jsdc, JSDScript* jsdscript, jsuword pc);
 
 extern void
 jsd_NewScriptHookProc(
                 JSContext   *cx,
                 const char  *filename,      /* URL this script loads from */
                 uintN       lineno,         /* line where this script starts */
--- a/js/jsd/jsd_scpt.c
+++ b/js/jsd/jsd_scpt.c
@@ -495,30 +495,32 @@ jsd_GetScriptLineExtent(JSDContext* jsdc
     if( NOT_SET_YET == (int)jsdscript->lineExtent )
         jsdscript->lineExtent = JS_GetScriptLineExtent(jsdc->dumbContext, jsdscript->script);
     return jsdscript->lineExtent;
 }
 
 jsuword
 jsd_GetClosestPC(JSDContext* jsdc, JSDScript* jsdscript, uintN line)
 {
-#ifdef LIVEWIRE
-    if( jsdscript && jsdscript->lwscript )
-    {
-        uintN newline;
-        jsdlw_RawToProcessedLineNumber(jsdc, jsdscript, line, &newline);
-        if( line != newline )
-            line = newline;
-    }
-#endif
-
     return (jsuword) JS_LineNumberToPC(jsdc->dumbContext, 
                                        jsdscript->script, line );
 }
 
+jsuword
+jsd_GetFirstValidPC(JSDContext* jsdc, JSDScript* jsdscript)
+{
+    return (jsuword) JS_FirstValidPC(jsdc->dumbContext, jsdscript->script );
+}
+
+jsuword
+jsd_GetEndPC(JSDContext* jsdc, JSDScript* jsdscript)
+{
+    return (jsuword) JS_EndPC(jsdc->dumbContext, jsdscript->script );
+}
+
 uintN
 jsd_GetClosestLine(JSDContext* jsdc, JSDScript* jsdscript, jsuword pc)
 {
     uintN first = jsdscript->lineBase;
     uintN last = first + jsd_GetScriptLineExtent(jsdc, jsdscript) - 1;
     uintN line = pc
         ? JS_PCToLineNumber(jsdc->dumbContext, 
                             jsdscript->script,
--- a/js/jsd/jsd_xpc.cpp
+++ b/js/jsd/jsd_xpc.cpp
@@ -953,30 +953,34 @@ jsdScript::jsdScript (JSDContext *aCx, J
                                                              mTag(0),
                                                              mCx(aCx),
                                                              mScript(aScript),
                                                              mFileName(0), 
                                                              mFunctionName(0),
                                                              mBaseLineNumber(0),
                                                              mLineExtent(0),
                                                              mPPLineMap(0),
-                                                             mFirstPC(0)
+                                                             mFirstValidPC(0),
+                                                             mFirstPC(0),
+                                                             mEndPC(0)
 {
     DEBUG_CREATE ("jsdScript", gScriptCount);
 
     if (mScript) {
         /* copy the script's information now, so we have it later, when it
          * gets destroyed. */
         JSD_LockScriptSubsystem(mCx);
         mFileName = new nsCString(JSD_GetScriptFilename(mCx, mScript));
         mFunctionName =
             new nsCString(JSD_GetScriptFunctionName(mCx, mScript));
         mBaseLineNumber = JSD_GetScriptBaseLineNumber(mCx, mScript);
         mLineExtent = JSD_GetScriptLineExtent(mCx, mScript);
         mFirstPC = JSD_GetClosestPC(mCx, mScript, 0);
+        mFirstValidPC = JSD_GetFirstValidPC(mCx, mScript);
+        mEndPC = JSD_GetEndPC(mCx, mScript);
         JSD_UnlockScriptSubsystem(mCx);
         
         mValid = PR_TRUE;
     }
 }
 
 jsdScript::~jsdScript () 
 {
@@ -1471,16 +1475,32 @@ jsdScript::IsLineExecutable(PRUint32 aLi
     } else {
         return NS_ERROR_INVALID_ARG;
     }
     
     return NS_OK;
 }
 
 NS_IMETHODIMP
+jsdScript::GetFirstValidPC(PRUint32 *_rval)
+{
+    ASSERT_VALID_EPHEMERAL;
+    *_rval = PRUint32(mFirstValidPC - mFirstPC);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+jsdScript::GetEndValidPC(PRUint32 *_rval)
+{
+    ASSERT_VALID_EPHEMERAL;
+    *_rval = PRUint32(mEndPC - mFirstPC);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 jsdScript::SetBreakpoint(PRUint32 aPC)
 {
     ASSERT_VALID_EPHEMERAL;
     jsuword pc = mFirstPC + aPC;
     JSD_SetExecutionHook (mCx, mScript, pc, jsds_ExecutionHookProc, NULL);
     return NS_OK;
 }
 
--- a/js/jsd/jsd_xpc.h
+++ b/js/jsd/jsd_xpc.h
@@ -177,17 +177,19 @@ class jsdScript : public jsdIScript
     PRUint32    mTag;
     JSDContext *mCx;
     JSDScript  *mScript;
     nsCString  *mFileName;
     nsCString  *mFunctionName;
     PRUint32    mBaseLineNumber, mLineExtent;
     PCMapEntry *mPPLineMap;
     PRUint32    mPCMapSize;
-    jsuword     mFirstPC;
+    jsuword     mFirstPC;        /* address of first PC in script */
+    jsuword     mFirstValidPC;   /* address of first valid bkpt PC */
+    jsuword     mEndPC;          /* address of end of script code */
 };
 
 PRUint32 jsdScript::LastTag = 0;
 
 class jsdContext : public jsdIContext
 {
   public:
     NS_DECL_ISUPPORTS
--- a/js/jsd/jsdebug.c
+++ b/js/jsd/jsdebug.c
@@ -343,16 +343,32 @@ JSD_GetScriptHook(JSDContext* jsdc, JSD_
 JSD_PUBLIC_API(jsuword)
 JSD_GetClosestPC(JSDContext* jsdc, JSDScript* jsdscript, uintN line)
 {
     JSD_ASSERT_VALID_CONTEXT(jsdc);
     JSD_ASSERT_VALID_SCRIPT(jsdscript);
     return jsd_GetClosestPC(jsdc, jsdscript, line);
 }
 
+JSD_PUBLIC_API(jsuword)
+JSD_GetFirstValidPC(JSDContext* jsdc, JSDScript* jsdscript)
+{
+    JSD_ASSERT_VALID_CONTEXT(jsdc);
+    JSD_ASSERT_VALID_SCRIPT(jsdscript);
+    return jsd_GetFirstValidPC(jsdc, jsdscript);
+}
+
+JSD_PUBLIC_API(jsuword)
+JSD_GetEndPC(JSDContext* jsdc, JSDScript* jsdscript)
+{
+    JSD_ASSERT_VALID_CONTEXT(jsdc);
+    JSD_ASSERT_VALID_SCRIPT(jsdscript);
+    return jsd_GetEndPC(jsdc, jsdscript);
+}
+
 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);
 }
 
--- a/js/jsd/jsdebug.h
+++ b/js/jsd/jsdebug.h
@@ -482,16 +482,29 @@ JSD_GetScriptHook(JSDContext* jsdc, JSD_
 * If no code is on the given line, then the returned pc represents the first
 * code within the script (if any) after the given line.
 * This function can be used to set breakpoints -- see JSD_SetExecutionHook
 */
 extern JSD_PUBLIC_API(jsuword)
 JSD_GetClosestPC(JSDContext* jsdc, JSDScript* jsdscript, uintN line);
 
 /*
+* Get the first 'Program Counter' value where a breakpoint can be set.
+*/
+extern JSD_PUBLIC_API(jsuword)
+JSD_GetFirstValidPC(JSDContext* jsdc, JSDScript* jsdscript);
+
+/*
+* Get the 'Program Counter' value just after the last byte of the script.
+* 0 is returned for invalid scripts.
+*/
+extern JSD_PUBLIC_API(jsuword)
+JSD_GetEndPC(JSDContext* jsdc, JSDScript* jsdscript);
+
+/*
 * 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);
 
 /* these are only used in cases where scripts are created outside of JS*/
--- a/js/jsd/test/Makefile.in
+++ b/js/jsd/test/Makefile.in
@@ -43,12 +43,13 @@ relativesrcdir  = js/jsd/test
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = jsdebug
 
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = 	test_bug507448.html \
+                test_bug602003.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/js/jsd/test/test_bug602003.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=507448
+-->
+<head>
+  <title>Test for Bug 602003</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=507448">Mozilla Bug 507448</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602003 **/
+
+// This is somewhat unfortunate: jsd only deals with scripts that have a
+// nonzero line number, so we can't just createElement a script here.
+// So break the test up into three <script>s, of which the middle one has our test functions.
+
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+var jsdIDebuggerService = Components.interfaces.jsdIDebuggerService;
+var jsd = Components.classes['@mozilla.org/js/jsd/debugger-service;1']
+                    .getService(jsdIDebuggerService);
+var jsdOn = jsd.isOn;
+if (!jsdOn) {
+  jsd.on();
+  ok(jsd.isOn, "JSD should be running.");
+}
+</script>
+<script>
+  function g(a,b) { return a + b }
+</script>
+<script>
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var script = jsd.wrapValue(g).script;
+
+  // Test the script start/end PC APIs.
+  var start = script.getFirstValidPC();
+  var end = script.getEndValidPC();
+
+  // Start PC should be 1 for a function because it starts with JSOP_BEGIN.
+  is(start, 1, "Start PC should be 1");
+
+  // End PC should be something greater than 1, and not huge. Changes
+  // in the bytecode will change this, so we'll just be approximate.
+  ok(1 < end && end < 100, "End PC doesn't seem sane.");
+
+  if (!jsdOn) {
+    jsd.off();
+    ok(!jsd.isOn, "JSD shouldn't be running anymore.");
+  }
+</script>
+</pre>
+</body>
+</html>
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -470,17 +470,19 @@ AllFramesIter::AllFramesIter(JSContext *
 {
 }
 
 AllFramesIter&
 AllFramesIter::operator++()
 {
     JS_ASSERT(!done());
     if (curfp == curcs->getInitialFrame()) {
-        curcs = curcs->getPreviousInMemory();
+        do {
+            curcs = curcs->getPreviousInMemory();
+        } while (curcs && !curcs->inContext());
         curfp = curcs ? curcs->getCurrentFrame() : NULL;
     } else {
         curfp = curfp->prev();
     }
     return *this;
 }
 
 bool
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -230,19 +230,21 @@ JS_SetTrap(JSContext *cx, JSScript *scri
         return JS_FALSE;
 
     if (script == JSScript::emptyScript()) {
         JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage,
                                      NULL, JSMSG_READ_ONLY, "empty script");
         return JS_FALSE;
     }
 
-    // Do not trap BEGIN, it's a special prologue opcode.
-    if (JSOp(*pc) == JSOP_BEGIN)
-        pc += JSOP_BEGIN_LENGTH;
+    if (JSOp(*pc) == JSOP_BEGIN) {
+        JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage,
+                                     NULL, JSMSG_READ_ONLY, "trap invalid on BEGIN opcode");
+        return JS_FALSE;
+    }
 
     JS_ASSERT((JSOp) *pc != JSOP_TRAP);
     junk = NULL;
     rt = cx->runtime;
     DBG_LOCK(rt);
     trap = FindTrap(rt, script, pc);
     if (trap) {
         JS_ASSERT(trap->script == script && trap->pc == pc);
@@ -1014,16 +1016,29 @@ JS_PCToLineNumber(JSContext *cx, JSScrip
 }
 
 JS_PUBLIC_API(jsbytecode *)
 JS_LineNumberToPC(JSContext *cx, JSScript *script, uintN lineno)
 {
     return js_LineNumberToPC(script, lineno);
 }
 
+JS_PUBLIC_API(jsbytecode *)
+JS_FirstValidPC(JSContext *cx, JSScript *script)
+{
+    jsbytecode *pc = script->code;
+    return *pc == JSOP_BEGIN ? pc + JSOP_BEGIN_LENGTH : pc;
+}
+
+JS_PUBLIC_API(jsbytecode *)
+JS_EndPC(JSContext *cx, JSScript *script)
+{
+    return script->code + script->length;
+}
+
 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
@@ -154,16 +154,22 @@ js_WrapWatchedSetter(JSContext *cx, jsid
 /************************************************************************/
 
 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_FirstValidPC(JSContext *cx, JSScript *script);
+
+extern JS_PUBLIC_API(jsbytecode *)
+JS_EndPC(JSContext *cx, JSScript *script);
+
 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