Bug 1037675 - Improve Debugger 'findScripts': Add 'source' param and allow 'displayURL' with 'line'. r=jorendorff
authorJames Long <jlong>
Thu, 28 Aug 2014 08:55:00 -0400
changeset 202233 ebe3f3603f563ff7eef7ee9e97b2fb633b5c2788
parent 202232 b83ffe1314204bcfdf849145fa2be143c8a1a661
child 202234 c16359660b11f4a61d701056ac109d81f7e07a8a
push idunknown
push userunknown
push dateunknown
reviewersjorendorff
bugs1037675
milestone34.0a1
Bug 1037675 - Improve Debugger 'findScripts': Add 'source' param and allow 'displayURL' with 'line'. r=jorendorff
js/src/jit-test/tests/debug/Debugger-findScripts-18.js
js/src/jit-test/tests/debug/Debugger-findScripts-20.js
js/src/js.msg
js/src/vm/Debugger.cpp
--- a/js/src/jit-test/tests/debug/Debugger-findScripts-18.js
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-18.js
@@ -13,16 +13,23 @@ var fw = gw.makeDebuggeeValue(g.f);
 var ggw = gw.makeDebuggeeValue(g.g);
 var hw = gw.makeDebuggeeValue(g.h);
 
 var fScripts = dbg.findScripts({ displayURL: "f.js" });
 assertEq(fScripts.indexOf(fw.script) != -1, true);
 assertEq(fScripts.indexOf(ggw.script), -1);
 assertEq(fScripts.indexOf(hw.script), -1);
 
+
+fScripts = dbg.findScripts({ displayURL: "f.js",
+                             line: 1 });
+assertEq(fScripts.indexOf(fw.script) != -1, true);
+assertEq(fScripts.indexOf(ggw.script), -1);
+assertEq(fScripts.indexOf(hw.script), -1);
+
 var gScripts = dbg.findScripts({ displayURL: "g.js" });
 assertEq(gScripts.indexOf(ggw.script) != -1, true);
 assertEq(gScripts.indexOf(fw.script), -1);
 assertEq(gScripts.indexOf(hw.script), -1);
 
 var allScripts = dbg.findScripts();
 assertEq(allScripts.indexOf(fw.script) != -1, true);
 assertEq(allScripts.indexOf(ggw.script) != -1, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-20.js
@@ -0,0 +1,20 @@
+var g = newGlobal();
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval('function f(){}');
+
+var o = gw.makeDebuggeeValue(g.f);
+
+var allScripts = dbg.findScripts();
+var scripts = dbg.findScripts({
+  source: o.script.source
+});
+assertEq(scripts.length < allScripts.length, true);
+assertEq(scripts.indexOf(o.script) !== -1, true);
+
+scripts = dbg.findScripts({
+  source: o.script.source,
+  line: 1
+});
+assertEq(scripts.indexOf(o.script) !== -1, true);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -366,18 +366,18 @@ MSG_DEF(JSMSG_DEBUG_OBJECT_PROTO,      0
 MSG_DEF(JSMSG_DEBUG_OBJECT_WRONG_OWNER,0, JSEXN_TYPEERR, "Debugger.Object belongs to a different Debugger")
 MSG_DEF(JSMSG_DEBUG_OPTIMIZED_OUT,     0, JSEXN_ERR, "variable has been optimized out")
 MSG_DEF(JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED, 0, JSEXN_TYPEERR, "resumption values are disallowed in this hook")
 MSG_DEF(JSMSG_DEBUG_VARIABLE_NOT_FOUND,0, JSEXN_TYPEERR, "variable not found in environment")
 MSG_DEF(JSMSG_DEBUG_WRAPPER_IN_WAY,    3, JSEXN_TYPEERR, "{0} is {1}{2}a global object, but a direct reference is required")
 MSG_DEF(JSMSG_NOT_CALLABLE_OR_UNDEFINED, 0, JSEXN_TYPEERR, "value is not a function or undefined")
 MSG_DEF(JSMSG_NOT_TRACKING_ALLOCATIONS, 1, JSEXN_ERR, "Cannot call {0} without setting trackingAllocationSites to true")
 MSG_DEF(JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET, 0, JSEXN_ERR, "Cannot track object allocation, because other tools are already doing so")
-MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 0, JSEXN_TYPEERR, "findScripts query object has 'innermost' property without both 'url' and 'line' properties")
-MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL,  0, JSEXN_TYPEERR, "findScripts query object has 'line' property, but no 'url' property")
+MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 0, JSEXN_TYPEERR, "findScripts query object with 'innermost' property must have 'line' and either 'displayURL', 'url', or 'source'")
+MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL, 0, JSEXN_TYPEERR, "findScripts query object has 'line' property, but no 'displayURL', 'url', or 'source' property")
 
 // Intl
 MSG_DEF(JSMSG_DATE_NOT_FINITE,         0, JSEXN_RANGEERR, "date value is not finite in DateTimeFormat.format()")
 MSG_DEF(JSMSG_INTERNAL_INTL_ERROR,     0, JSEXN_ERR, "internal error while computing Intl data")
 MSG_DEF(JSMSG_INTL_OBJECT_NOT_INITED,  3, JSEXN_TYPEERR, "Intl.{0}.prototype.{1} called on value that's not an object initialized as a {2}")
 MSG_DEF(JSMSG_INTL_OBJECT_REINITED,    0, JSEXN_TYPEERR, "can't initialize object twice as an object of an Intl constructor")
 MSG_DEF(JSMSG_INVALID_CURRENCY_CODE,   1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}")
 MSG_DEF(JSMSG_INVALID_DIGITS_VALUE,    1, JSEXN_RANGEERR, "invalid digits value: {0}")
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2509,26 +2509,29 @@ Debugger::removeDebuggeeGlobalUnderGC(Fr
      * The debuggee needs to be removed from the compartment last, as this can
      * trigger GCs if the compartment's debug mode is being changed, and the
      * global cannot be rooted on the stack without a cx.
      */
     if (global->getDebuggers()->empty())
         global->compartment()->removeDebuggeeUnderGC(fop, global, invalidate, compartmentEnum);
 }
 
+static inline ScriptSourceObject *GetSourceReferent(JSObject *obj);
+
 /*
  * A class for parsing 'findScripts' query arguments and searching for
  * scripts that match the criteria they represent.
  */
-class Debugger::ScriptQuery {
+class MOZ_STACK_CLASS Debugger::ScriptQuery
+{
   public:
     /* Construct a ScriptQuery to use matching scripts for |dbg|. */
     ScriptQuery(JSContext *cx, Debugger *dbg):
         cx(cx), debugger(dbg), compartments(cx->runtime()), url(cx), displayURLString(cx),
-        innermostForCompartment(cx->runtime())
+        source(cx), innermostForCompartment(cx->runtime())
     {}
 
     /*
      * Initialize this ScriptQuery. Raise an error and return false if we
      * haven't enough memory.
      */
     bool init() {
         if (!compartments.init() ||
@@ -2574,24 +2577,57 @@ class Debugger::ScriptQuery {
         if (!JSObject::getProperty(cx, query, query, cx->names().url, &url))
             return false;
         if (!url.isUndefined() && !url.isString()) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
                                  "query object's 'url' property", "neither undefined nor a string");
             return false;
         }
 
+        /* Check for a 'source' property */
+        RootedValue debuggerSource(cx);
+        if (!JSObject::getProperty(cx, query, query, cx->names().source, &debuggerSource))
+            return false;
+        if (!debuggerSource.isUndefined()) {
+            if (!debuggerSource.isObject() ||
+                debuggerSource.toObject().getClass() != &DebuggerSource_class) {
+                JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
+                                     "query object's 'source' property",
+                                     "not undefined nor a Debugger.Source object");
+                return false;
+            }
+
+            source = GetSourceReferent(&debuggerSource.toObject());
+        }
+
+        /* Check for a 'displayURL' property. */
+        RootedValue displayURL(cx);
+        if (!JSObject::getProperty(cx, query, query, cx->names().displayURL, &displayURL))
+            return false;
+        if (!displayURL.isUndefined() && !displayURL.isString()) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
+                                 "query object's 'displayURL' property",
+                                 "neither undefined nor a string");
+            return false;
+        }
+
+        if (displayURL.isString()) {
+            displayURLString = displayURL.toString()->ensureLinear(cx);
+            if (!displayURLString)
+                return false;
+        }
+
         /* Check for a 'line' property. */
         RootedValue lineProperty(cx);
         if (!JSObject::getProperty(cx, query, query, cx->names().line, &lineProperty))
             return false;
         if (lineProperty.isUndefined()) {
             hasLine = false;
         } else if (lineProperty.isNumber()) {
-            if (url.isUndefined()) {
+            if (displayURL.isUndefined() && url.isUndefined() && !source) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                                      JSMSG_QUERY_LINE_WITHOUT_URL);
                 return false;
             }
             double doubleLine = lineProperty.toNumber();
             if (doubleLine <= 0 || (unsigned int) doubleLine != doubleLine) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_LINE);
                 return false;
@@ -2608,40 +2644,23 @@ class Debugger::ScriptQuery {
         /* Check for an 'innermost' property. */
         PropertyName *innermostName = cx->names().innermost;
         RootedValue innermostProperty(cx);
         if (!JSObject::getProperty(cx, query, query, innermostName, &innermostProperty))
             return false;
         innermost = ToBoolean(innermostProperty);
         if (innermost) {
             /* Technically, we need only check hasLine, but this is clearer. */
-            if (url.isUndefined() || !hasLine) {
+            if ((displayURL.isUndefined() && url.isUndefined() && !source) || !hasLine) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                                      JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL);
                 return false;
             }
         }
 
-        /* Check for a 'displayURL' property. */
-        RootedValue displayURL(cx);
-        if (!JSObject::getProperty(cx, query, query, cx->names().displayURL, &displayURL))
-            return false;
-        if (!displayURL.isUndefined() && !displayURL.isString()) {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
-                                 "query object's 'displayURL' property",
-                                 "neither undefined nor a string");
-            return false;
-        }
-
-        if (displayURL.isString()) {
-            displayURLString = displayURL.toString()->ensureLinear(cx);
-            if (!displayURLString)
-                return false;
-        }
-
         return true;
     }
 
     /* Set up this ScriptQuery appropriately for a missing query argument. */
     bool omittedQuery() {
         url.setUndefined();
         hasLine = false;
         innermost = false;
@@ -2714,16 +2733,22 @@ class Debugger::ScriptQuery {
 
     /* url as a C string. */
     JSAutoByteString urlCString;
 
     /* If this is a string, matching scripts' sources have displayURLs equal to
      * it. */
     RootedLinearString displayURLString;
 
+    /*
+     * If this is a source object, matching scripts will have sources
+     * equal to this instance.
+     */
+    RootedScriptSource source;
+
     /* True if the query contained a 'line' property. */
     bool hasLine;
 
     /* The line matching scripts must cover. */
     unsigned int line;
 
     /* True if the query has an 'innermost' property whose value is true. */
     bool innermost;
@@ -2836,16 +2861,18 @@ class Debugger::ScriptQuery {
         if (displayURLString) {
             if (!script->scriptSource() || !script->scriptSource()->hasDisplayURL())
                 return;
 
             const jschar *s = script->scriptSource()->displayURL();
             if (CompareChars(s, js_strlen(s), displayURLString) != 0)
                 return;
         }
+        if (source && source != script->sourceObject())
+            return;
 
         if (innermost) {
             /*
              * For 'innermost' queries, we don't place scripts in |vector| right
              * away; we may later find another script that is nested inside this
              * one. Instead, we record the innermost script we've found so far
              * for each compartment in innermostForCompartment, and only
              * populate |vector| at the bottom of findScripts, when we've