debugger: breakpoints in scripts not loaded yet
authorMiroslav Bajtoš <miro.bajtos@gmail.com>
Fri, 26 Apr 2013 06:05:33 +0000 (08:05 +0200)
committerBen Noordhuis <info@bnoordhuis.nl>
Sat, 27 Apr 2013 10:51:22 +0000 (12:51 +0200)
When developer calls setBreakpoint with an unknown script name,
we convert the script name into regular expression matching all
paths ending with given name (name can be a relative path too).

To create such breakpoint in V8, we use type `scriptRegEx`
instead of `scriptId` for `setbreakpoint` request.

To restore such breakpoint, we save the original script name
send by the user. We use this original name to set (restore)
breakpoint in the new child process.

doc/api/debugger.markdown
lib/_debugger.js
test/fixtures/break-in-module/main.js [new file with mode: 0644]
test/fixtures/break-in-module/mod.js [new file with mode: 0644]
test/simple/helper-debugger-repl.js
test/simple/test-debugger-repl-break-in-module.js [new file with mode: 0644]

index 2c68c1d..f4f9890 100644 (file)
@@ -109,6 +109,30 @@ functions body
 script.js
 * `clearBreakpoint`, `cb(...)` - Clear breakpoint
 
+It is also possible to set a breakpoint in a file (module) that
+isn't loaded yet:
+
+    % ./node debug test/fixtures/break-in-module/main.js
+    < debugger listening on port 5858
+    connecting to port 5858... ok
+    break in test/fixtures/break-in-module/main.js:1
+      1 var mod = require('./mod.js');
+      2 mod.hello();
+      3 mod.hello();
+    debug> setBreakpoint('mod.js', 23)
+    Warning: script 'mod.js' was not loaded yet.
+      1 var mod = require('./mod.js');
+      2 mod.hello();
+      3 mod.hello();
+    debug> c
+    break in test/fixtures/break-in-module/mod.js:23
+     21
+     22 exports.hello = function() {
+     23   return 'hello from module';
+     24 };
+     25
+    debug>
+
 ### Info
 
 * `backtrace`, `bt` - Print backtrace of current execution frame
index 106e55c..170f5e7 100644 (file)
@@ -1385,16 +1385,28 @@ Interface.prototype.setBreakpoint = function(script, line,
       scriptId = script;
     }
 
-    if (!scriptId) return this.error('Script : ' + script + ' not found');
     if (ambiguous) return this.error('Script name is ambiguous');
     if (line <= 0) return this.error('Line should be a positive value');
 
-    var req = {
-      type: 'scriptId',
-      target: scriptId,
-      line: line - 1,
-      condition: condition
-    };
+    var req;
+    if (scriptId) {
+      req = {
+        type: 'scriptId',
+        target: scriptId,
+        line: line - 1,
+        condition: condition
+      };
+    } else {
+      this.print('Warning: script \'' + script + '\' was not loaded yet.');
+      var normalizedPath = script.replace('([/.?*])', '\\$1');
+      var scriptPathRegex = '^(.*[\\/\\\\])?' + normalizedPath + '$';
+      req = {
+        type: 'scriptRegExp',
+        target: scriptPathRegex,
+        line: line - 1,
+        condition: condition
+      };
+    }
   }
 
   self.pause();
@@ -1411,20 +1423,18 @@ Interface.prototype.setBreakpoint = function(script, line,
       // Try load scriptId and line from response
       if (!scriptId) {
         scriptId = res.script_id;
-        line = res.line;
-      }
-
-      // If we finally have one - remember this breakpoint
-      if (scriptId) {
-        self.client.breakpoints.push({
-          id: res.breakpoint,
-          scriptId: scriptId,
-          script: (self.client.scripts[scriptId] || {}).name,
-          line: line,
-          condition: condition
-        });
+        line = res.line + 1;
       }
 
+      // Remember this breakpoint even if scriptId is not resolved yet
+      self.client.breakpoints.push({
+        id: res.breakpoint,
+        scriptId: scriptId,
+        script: (self.client.scripts[scriptId] || {}).name,
+        line: line,
+        condition: condition,
+        scriptReq: script
+      });
     }
     self.resume();
   });
@@ -1439,7 +1449,9 @@ Interface.prototype.clearBreakpoint = function(script, line) {
       index;
 
   this.client.breakpoints.some(function(bp, i) {
-    if (bp.scriptId === script || bp.script.indexOf(script) !== -1) {
+    if (bp.scriptId === script ||
+        bp.scriptReq === script ||
+        (bp.script && bp.script.indexOf(script) !== -1)) {
       if (index !== undefined) {
         ambiguous = true;
       }
@@ -1657,7 +1669,8 @@ Interface.prototype.trySpawn = function(cb) {
 
     // Restore breakpoints
     breakpoints.forEach(function(bp) {
-      self.setBreakpoint(bp.scriptId, bp.line, bp.condition, true);
+      self.print('Restoring breakpoint ' + bp.scriptReq + ':' + bp.line);
+      self.setBreakpoint(bp.scriptReq, bp.line, bp.condition, true);
     });
 
     client.on('close', function() {
diff --git a/test/fixtures/break-in-module/main.js b/test/fixtures/break-in-module/main.js
new file mode 100644 (file)
index 0000000..cb7074e
--- /dev/null
@@ -0,0 +1,4 @@
+var mod = require('./mod.js');
+mod.hello();
+mod.hello();
+debugger;
diff --git a/test/fixtures/break-in-module/mod.js b/test/fixtures/break-in-module/mod.js
new file mode 100644 (file)
index 0000000..57bf58b
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+exports.hello = function() {
+  return 'hello from module';
+};
index 2ef3ea3..9a39bbb 100644 (file)
@@ -126,16 +126,23 @@ function addTest(input, output) {
   expected.push({input: input, lines: output, callback: next});
 }
 
-var initialLines = [
+var handshakeLines = [
   /listening on port \d+/,
-  /connecting.* ok/,
+  /connecting.* ok/
+];
+
+var initialBreakLines = [
   /break in .*:1/,
   /1/, /2/, /3/
 ];
 
+var initialLines = handshakeLines.concat(initialBreakLines);
+
 // Process initial lines
 addTest(null, initialLines);
 
 exports.startDebugger = startDebugger;
 exports.addTest = addTest;
 exports.initialLines = initialLines;
+exports.handshakeLines = handshakeLines;
+exports.initialBreakLines = initialBreakLines;
diff --git a/test/simple/test-debugger-repl-break-in-module.js b/test/simple/test-debugger-repl-break-in-module.js
new file mode 100644 (file)
index 0000000..ddf17f8
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var repl = require('./helper-debugger-repl.js');
+
+repl.startDebugger('break-in-module/main.js');
+
+// -- SET BREAKPOINT --
+
+// Set breakpoint by file name + line number where the file is not loaded yet
+repl.addTest('sb("mod.js", 23)', [
+  /Warning: script 'mod\.js' was not loaded yet\./,
+  /1/, /2/, /3/, /4/, /5/, /6/
+]);
+
+// continue - the breakpoint should be triggered
+repl.addTest('c', [
+    /break in .*[\\\/]mod\.js:23/,
+    /21/, /22/, /23/, /24/, /25/
+]);
+
+// -- RESTORE BREAKPOINT ON RESTART --
+
+// Restart the application - breakpoint should be restored
+repl.addTest('restart', [].concat(
+  [
+    /terminated/
+  ],
+  repl.handshakeLines,
+  [
+    /Restoring breakpoint mod.js:23/,
+    /Warning: script 'mod\.js' was not loaded yet\./
+  ],
+  repl.initialBreakLines));
+
+// continue - the breakpoint should be triggered
+repl.addTest('c', [
+  /break in .*[\\\/]mod\.js:23/,
+  /21/, /22/, /23/, /24/, /25/
+]);
+
+// -- CLEAR BREAKPOINT SET IN MODULE TO BE LOADED --
+
+repl.addTest('cb("mod.js", 23)', [
+  /18/, /./, /./, /./, /./, /./, /./, /./, /26/
+]);
+
+repl.addTest('c', [
+  /break in .*[\\\/]main\.js:4/,
+  /2/, /3/, /4/, /5/, /6/
+]);
+
+// -- (END) --
+repl.addTest('quit', []);