doc: improvements to debugger.markdown copy
[platform/upstream/nodejs.git] / lib / _debugger.js
1 'use strict';
2
3 const util = require('util');
4 const path = require('path');
5 const net = require('net');
6 const vm = require('vm');
7 const Module = require('module');
8 const repl = Module.requireRepl();
9 const inherits = util.inherits;
10 const assert = require('assert');
11 const spawn = require('child_process').spawn;
12 const Buffer = require('buffer').Buffer;
13
14 exports.start = function(argv, stdin, stdout) {
15   argv || (argv = process.argv.slice(2));
16
17   if (argv.length < 1) {
18     console.error('Usage: node debug script.js');
19     console.error('       node debug <host>:<port>');
20     console.error('       node debug -p <pid>');
21     process.exit(1);
22   }
23
24   // Setup input/output streams
25   stdin = stdin || process.stdin;
26   stdout = stdout || process.stdout;
27
28   var args = ['--debug-brk'].concat(argv),
29       interface_ = new Interface(stdin, stdout, args);
30
31   stdin.resume();
32
33   process.on('uncaughtException', function(e) {
34     console.error("There was an internal error in Node's debugger. " +
35         'Please report this bug.');
36     console.error(e.message);
37     console.error(e.stack);
38     if (interface_.child) interface_.child.kill();
39     process.exit(1);
40   });
41 };
42
43 exports.port = 5858;
44
45
46 //
47 // Parser/Serializer for V8 debugger protocol
48 // https://github.com/v8/v8/wiki/Debugging-Protocol
49 //
50 // Usage:
51 //    p = new Protocol();
52 //
53 //    p.onResponse = function(res) {
54 //      // do stuff with response from V8
55 //    };
56 //
57 //    socket.setEncoding('utf8');
58 //    socket.on('data', function(s) {
59 //      // Pass strings into the protocol
60 //      p.execute(s);
61 //    });
62 //
63 //
64 function Protocol() {
65   this._newRes();
66 }
67 exports.Protocol = Protocol;
68
69
70 Protocol.prototype._newRes = function(raw) {
71   this.res = { raw: raw || '', headers: {} };
72   this.state = 'headers';
73   this.reqSeq = 1;
74   this.execute('');
75 };
76
77
78 Protocol.prototype.execute = function(d) {
79   var res = this.res;
80   res.raw += d;
81
82   switch (this.state) {
83     case 'headers':
84       var endHeaderIndex = res.raw.indexOf('\r\n\r\n');
85
86       if (endHeaderIndex < 0) break;
87
88       var rawHeader = res.raw.slice(0, endHeaderIndex);
89       var endHeaderByteIndex = Buffer.byteLength(rawHeader, 'utf8');
90       var lines = rawHeader.split('\r\n');
91       for (var i = 0; i < lines.length; i++) {
92         var kv = lines[i].split(/: +/);
93         res.headers[kv[0]] = kv[1];
94       }
95
96       this.contentLength = +res.headers['Content-Length'];
97       this.bodyStartByteIndex = endHeaderByteIndex + 4;
98
99       this.state = 'body';
100
101       var len = Buffer.byteLength(res.raw, 'utf8');
102       if (len - this.bodyStartByteIndex < this.contentLength) {
103         break;
104       }
105       // falls through
106     case 'body':
107       var resRawByteLength = Buffer.byteLength(res.raw, 'utf8');
108
109       if (resRawByteLength - this.bodyStartByteIndex >= this.contentLength) {
110         var buf = new Buffer(resRawByteLength);
111         buf.write(res.raw, 0, resRawByteLength, 'utf8');
112         res.body =
113             buf.slice(this.bodyStartByteIndex,
114                       this.bodyStartByteIndex +
115                       this.contentLength).toString('utf8');
116         // JSON parse body?
117         res.body = res.body.length ? JSON.parse(res.body) : {};
118
119         // Done!
120         this.onResponse(res);
121
122         this._newRes(buf.slice(this.bodyStartByteIndex +
123                                this.contentLength).toString('utf8'));
124       }
125       break;
126
127     default:
128       throw new Error('Unknown state');
129   }
130 };
131
132
133 Protocol.prototype.serialize = function(req) {
134   req.type = 'request';
135   req.seq = this.reqSeq++;
136   var json = JSON.stringify(req);
137   return 'Content-Length: ' + Buffer.byteLength(json, 'utf8') +
138          '\r\n\r\n' + json;
139 };
140
141
142 const NO_FRAME = -1;
143
144 function Client() {
145   net.Stream.call(this);
146   var protocol = this.protocol = new Protocol(this);
147   this._reqCallbacks = [];
148   var socket = this;
149
150   this.currentFrame = NO_FRAME;
151   this.currentSourceLine = -1;
152   this.handles = {};
153   this.scripts = {};
154   this.breakpoints = [];
155
156   // Note that 'Protocol' requires strings instead of Buffers.
157   socket.setEncoding('utf8');
158   socket.on('data', function(d) {
159     protocol.execute(d);
160   });
161
162   protocol.onResponse = this._onResponse.bind(this);
163 }
164 inherits(Client, net.Stream);
165 exports.Client = Client;
166
167
168 Client.prototype._addHandle = function(desc) {
169   if (desc === null || typeof desc !== 'object' ||
170       typeof desc.handle !== 'number') {
171     return;
172   }
173
174   this.handles[desc.handle] = desc;
175
176   if (desc.type === 'script') {
177     this._addScript(desc);
178   }
179 };
180
181
182 const natives = process.binding('natives');
183
184
185 Client.prototype._addScript = function(desc) {
186   this.scripts[desc.id] = desc;
187   if (desc.name) {
188     desc.isNative = (desc.name.replace('.js', '') in natives) ||
189                     desc.name == 'node.js';
190   }
191 };
192
193
194 Client.prototype._removeScript = function(desc) {
195   this.scripts[desc.id] = undefined;
196 };
197
198
199 Client.prototype._onResponse = function(res) {
200   var cb,
201       index = -1;
202
203   this._reqCallbacks.some(function(fn, i) {
204     if (fn.request_seq == res.body.request_seq) {
205       cb = fn;
206       index = i;
207       return true;
208     }
209   });
210
211   var self = this;
212   var handled = false;
213
214   if (res.headers.Type == 'connect') {
215     // Request a list of scripts for our own storage.
216     self.reqScripts();
217     self.emit('ready');
218     handled = true;
219
220   } else if (res.body && res.body.event == 'break') {
221     this.emit('break', res.body);
222     handled = true;
223
224   } else if (res.body && res.body.event == 'exception') {
225     this.emit('exception', res.body);
226     handled = true;
227
228   } else if (res.body && res.body.event == 'afterCompile') {
229     this._addHandle(res.body.body.script);
230     handled = true;
231
232   } else if (res.body && res.body.event == 'scriptCollected') {
233     // ???
234     this._removeScript(res.body.body.script);
235     handled = true;
236
237   } else if (res.body && res.body.event === 'compileError') {
238     // This event is not used anywhere right now, perhaps somewhere in the
239     // future?
240     handled = true;
241   }
242
243   if (cb) {
244     this._reqCallbacks.splice(index, 1);
245     handled = true;
246
247     var err = res.success === false && (res.message || true) ||
248               res.body.success === false && (res.body.message || true);
249     cb(err, res.body && res.body.body || res.body, res);
250   }
251
252   if (!handled) this.emit('unhandledResponse', res.body);
253 };
254
255
256 Client.prototype.req = function(req, cb) {
257   this.write(this.protocol.serialize(req));
258   cb.request_seq = req.seq;
259   this._reqCallbacks.push(cb);
260 };
261
262
263 Client.prototype.reqVersion = function(cb) {
264   cb = cb || function() {};
265   this.req({ command: 'version' }, function(err, body, res) {
266     if (err) return cb(err);
267     cb(null, res.body.body.V8Version, res.body.running);
268   });
269 };
270
271
272 Client.prototype.reqLookup = function(refs, cb) {
273   var self = this;
274
275   // TODO: We have a cache of handle's we've already seen in this.handles
276   // This can be used if we're careful.
277   var req = {
278     command: 'lookup',
279     arguments: {
280       handles: refs
281     }
282   };
283
284   cb = cb || function() {};
285   this.req(req, function(err, res) {
286     if (err) return cb(err);
287     for (var ref in res) {
288       if (res[ref] !== null && typeof res[ref] === 'object') {
289         self._addHandle(res[ref]);
290       }
291     }
292
293     cb(null, res);
294   });
295 };
296
297 Client.prototype.reqScopes = function(cb) {
298   var self = this,
299       req = {
300         command: 'scopes',
301         arguments: {}
302       };
303
304   cb = cb || function() {};
305   this.req(req, function(err, res) {
306     if (err) return cb(err);
307     var refs = res.scopes.map(function(scope) {
308       return scope.object.ref;
309     });
310
311     self.reqLookup(refs, function(err, res) {
312       if (err) return cb(err);
313
314       var globals = Object.keys(res).map(function(key) {
315         return res[key].properties.map(function(prop) {
316           return prop.name;
317         });
318       });
319
320       cb(null, globals.reverse());
321     });
322   });
323 };
324
325 // This is like reqEval, except it will look up the expression in each of the
326 // scopes associated with the current frame.
327 Client.prototype.reqEval = function(expression, cb) {
328   var self = this;
329
330   if (this.currentFrame == NO_FRAME) {
331     // Only need to eval in global scope.
332     this.reqFrameEval(expression, NO_FRAME, cb);
333     return;
334   }
335
336   cb = cb || function() {};
337   // Otherwise we need to get the current frame to see which scopes it has.
338   this.reqBacktrace(function(err, bt) {
339     if (err || !bt.frames) {
340       // ??
341       return cb(null, {});
342     }
343
344     var frame = bt.frames[self.currentFrame];
345
346     var evalFrames = frame.scopes.map(function(s) {
347       if (!s) return;
348       var x = bt.frames[s.index];
349       if (!x) return;
350       return x.index;
351     });
352
353     self._reqFramesEval(expression, evalFrames, cb);
354   });
355 };
356
357
358 // Finds the first scope in the array in which the expression evals.
359 Client.prototype._reqFramesEval = function(expression, evalFrames, cb) {
360   if (evalFrames.length == 0) {
361     // Just eval in global scope.
362     this.reqFrameEval(expression, NO_FRAME, cb);
363     return;
364   }
365
366   var self = this;
367   var i = evalFrames.shift();
368
369   cb = cb || function() {};
370   this.reqFrameEval(expression, i, function(err, res) {
371     if (!err) return cb(null, res);
372     self._reqFramesEval(expression, evalFrames, cb);
373   });
374 };
375
376
377 Client.prototype.reqFrameEval = function(expression, frame, cb) {
378   var self = this;
379   var req = {
380     command: 'evaluate',
381     arguments: { expression: expression }
382   };
383
384   if (frame == NO_FRAME) {
385     req.arguments.global = true;
386   } else {
387     req.arguments.frame = frame;
388   }
389
390   cb = cb || function() {};
391   this.req(req, function(err, res) {
392     if (!err) self._addHandle(res);
393     cb(err, res);
394   });
395 };
396
397
398 // reqBacktrace(cb)
399 // TODO: from, to, bottom
400 Client.prototype.reqBacktrace = function(cb) {
401   this.req({ command: 'backtrace', arguments: { inlineRefs: true } }, cb);
402 };
403
404
405 // reqSetExceptionBreak(type, cb)
406 // TODO: from, to, bottom
407 Client.prototype.reqSetExceptionBreak = function(type, cb) {
408   this.req({
409     command: 'setexceptionbreak',
410     arguments: { type: type, enabled: true }
411   }, cb);
412 };
413
414
415 // Returns an array of objects like this:
416 //
417 //   { handle: 11,
418 //     type: 'script',
419 //     name: 'node.js',
420 //     id: 14,
421 //     lineOffset: 0,
422 //     columnOffset: 0,
423 //     lineCount: 562,
424 //     sourceStart: '(function(process) {\n\n  ',
425 //     sourceLength: 15939,
426 //     scriptType: 2,
427 //     compilationType: 0,
428 //     context: { ref: 10 },
429 //     text: 'node.js (lines: 562)' }
430 //
431 Client.prototype.reqScripts = function(cb) {
432   var self = this;
433   cb = cb || function() {};
434
435   this.req({ command: 'scripts' }, function(err, res) {
436     if (err) return cb(err);
437
438     for (var i = 0; i < res.length; i++) {
439       self._addHandle(res[i]);
440     }
441     cb(null);
442   });
443 };
444
445
446 Client.prototype.reqContinue = function(cb) {
447   this.currentFrame = NO_FRAME;
448   this.req({ command: 'continue' }, cb);
449 };
450
451 Client.prototype.listbreakpoints = function(cb) {
452   this.req({ command: 'listbreakpoints' }, cb);
453 };
454
455 Client.prototype.setBreakpoint = function(req, cb) {
456   req = {
457     command: 'setbreakpoint',
458     arguments: req
459   };
460
461   this.req(req, cb);
462 };
463
464 Client.prototype.clearBreakpoint = function(req, cb) {
465   var req = {
466     command: 'clearbreakpoint',
467     arguments: req
468   };
469
470   this.req(req, cb);
471 };
472
473 Client.prototype.reqSource = function(from, to, cb) {
474   var req = {
475     command: 'source',
476     fromLine: from,
477     toLine: to
478   };
479
480   this.req(req, cb);
481 };
482
483
484 // client.next(1, cb);
485 Client.prototype.step = function(action, count, cb) {
486   var req = {
487     command: 'continue',
488     arguments: { stepaction: action, stepcount: count }
489   };
490
491   this.currentFrame = NO_FRAME;
492   this.req(req, cb);
493 };
494
495
496 Client.prototype.mirrorObject = function(handle, depth, cb) {
497   var self = this;
498
499   var val;
500
501   if (handle.type === 'object') {
502     // The handle looks something like this:
503     // { handle: 8,
504     //   type: 'object',
505     //   className: 'Object',
506     //   constructorFunction: { ref: 9 },
507     //   protoObject: { ref: 4 },
508     //   prototypeObject: { ref: 2 },
509     //   properties: [ { name: 'hello', propertyType: 1, ref: 10 } ],
510     //   text: '#<an Object>' }
511
512     // For now ignore the className and constructor and prototype.
513     // TJ's method of object inspection would probably be good for this:
514     // https://groups.google.com/forum/?pli=1#!topic/nodejs-dev/4gkWBOimiOg
515
516     var propertyRefs = handle.properties.map(function(p) {
517       return p.ref;
518     });
519
520     cb = cb || function() {};
521     this.reqLookup(propertyRefs, function(err, res) {
522       if (err) {
523         console.error('problem with reqLookup');
524         cb(null, handle);
525         return;
526       }
527
528       var mirror,
529           waiting = 1;
530
531       if (handle.className == 'Array') {
532         mirror = [];
533       } else if (handle.className == 'Date') {
534         mirror = new Date(handle.value);
535       } else {
536         mirror = {};
537       }
538
539
540       var keyValues = [];
541       handle.properties.forEach(function(prop, i) {
542         var value = res[prop.ref];
543         var mirrorValue;
544         if (value) {
545           mirrorValue = value.value ? value.value : value.text;
546         } else {
547           mirrorValue = '[?]';
548         }
549
550         if (Array.isArray(mirror) && typeof prop.name !== 'number') {
551           // Skip the 'length' property.
552           return;
553         }
554
555         keyValues[i] = {
556           name: prop.name,
557           value: mirrorValue
558         };
559         if (value && value.handle && depth > 0) {
560           waiting++;
561           self.mirrorObject(value, depth - 1, function(err, result) {
562             if (!err) keyValues[i].value = result;
563             waitForOthers();
564           });
565         }
566       });
567
568       waitForOthers();
569       function waitForOthers() {
570         if (--waiting === 0 && cb) {
571           keyValues.forEach(function(kv) {
572             mirror[kv.name] = kv.value;
573           });
574           cb(null, mirror);
575         }
576       };
577     });
578     return;
579   } else if (handle.type === 'function') {
580     val = function() {};
581   } else if (handle.type === 'null') {
582     val = null;
583   } else if (handle.value !== undefined) {
584     val = handle.value;
585   } else if (handle.type === 'undefined') {
586     val = undefined;
587   } else {
588     val = handle;
589   }
590   process.nextTick(cb, null, val);
591 };
592
593
594 Client.prototype.fullTrace = function(cb) {
595   var self = this;
596
597   cb = cb || function() {};
598   this.reqBacktrace(function(err, trace) {
599     if (err) return cb(err);
600     if (trace.totalFrames <= 0) return cb(Error('No frames'));
601
602     var refs = [];
603
604     for (var i = 0; i < trace.frames.length; i++) {
605       var frame = trace.frames[i];
606       // looks like this:
607       // { type: 'frame',
608       //   index: 0,
609       //   receiver: { ref: 1 },
610       //   func: { ref: 0 },
611       //   script: { ref: 7 },
612       //   constructCall: false,
613       //   atReturn: false,
614       //   debuggerFrame: false,
615       //   arguments: [],
616       //   locals: [],
617       //   position: 160,
618       //   line: 7,
619       //   column: 2,
620       //   sourceLineText: '  debugger;',
621       //   scopes: [ { type: 1, index: 0 }, { type: 0, index: 1 } ],
622       //   text: '#00 blah() /home/ryan/projects/node/test-debug.js l...' }
623       refs.push(frame.script.ref);
624       refs.push(frame.func.ref);
625       refs.push(frame.receiver.ref);
626     }
627
628     self.reqLookup(refs, function(err, res) {
629       if (err) return cb(err);
630
631       for (var i = 0; i < trace.frames.length; i++) {
632         var frame = trace.frames[i];
633         frame.script = res[frame.script.ref];
634         frame.func = res[frame.func.ref];
635         frame.receiver = res[frame.receiver.ref];
636       }
637
638       cb(null, trace);
639     });
640   });
641 };
642
643
644 const commands = [
645   [
646     'run (r)',
647     'cont (c)',
648     'next (n)',
649     'step (s)',
650     'out (o)',
651     'backtrace (bt)',
652     'setBreakpoint (sb)',
653     'clearBreakpoint (cb)'
654   ],
655   [
656     'watch',
657     'unwatch',
658     'watchers',
659     'repl',
660     'restart',
661     'kill',
662     'list',
663     'scripts',
664     'breakOnException',
665     'breakpoints',
666     'version'
667   ]
668 ];
669
670
671 var helpMessage = 'Commands: ' + commands.map(function(group) {
672   return group.join(', ');
673 }).join(',\n');
674
675
676 function SourceUnderline(sourceText, position, repl) {
677   if (!sourceText) return '';
678
679   var head = sourceText.slice(0, position),
680       tail = sourceText.slice(position);
681
682   // Colourize char if stdout supports colours
683   if (repl.useColors) {
684     tail = tail.replace(/(.+?)([^\w]|$)/, '\u001b[32m$1\u001b[39m$2');
685   }
686
687   // Return source line with coloured char at `position`
688   return [
689     head,
690     tail
691   ].join('');
692 }
693
694
695 function SourceInfo(body) {
696   var result = body.exception ? 'exception in ' : 'break in ';
697
698   if (body.script) {
699     if (body.script.name) {
700       var name = body.script.name,
701           dir = path.resolve() + '/';
702
703       // Change path to relative, if possible
704       if (name.indexOf(dir) === 0) {
705         name = name.slice(dir.length);
706       }
707
708       result += name;
709     } else {
710       result += '[unnamed]';
711     }
712   }
713   result += ':';
714   result += body.sourceLine + 1;
715
716   if (body.exception) result += '\n' + body.exception.text;
717
718   return result;
719 }
720
721 // This class is the repl-enabled debugger interface which is invoked on
722 // "node debug"
723 function Interface(stdin, stdout, args) {
724   var self = this;
725
726   this.stdin = stdin;
727   this.stdout = stdout;
728   this.args = args;
729
730   // Two eval modes are available: controlEval and debugEval
731   // But controlEval is used by default
732   var opts = {
733     prompt: 'debug> ',
734     input: this.stdin,
735     output: this.stdout,
736     eval: this.controlEval.bind(this),
737     useGlobal: false,
738     ignoreUndefined: true
739   };
740   if (parseInt(process.env['NODE_NO_READLINE'], 10)) {
741     opts.terminal = false;
742   } else if (parseInt(process.env['NODE_FORCE_READLINE'], 10)) {
743     opts.terminal = true;
744
745     // Emulate Ctrl+C if we're emulating terminal
746     if (!this.stdout.isTTY) {
747       process.on('SIGINT', function() {
748         self.repl.rli.emit('SIGINT');
749       });
750     }
751   }
752   if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) {
753     opts.useColors = false;
754   }
755
756   this.repl = repl.start(opts);
757
758   // Do not print useless warning
759   repl._builtinLibs.splice(repl._builtinLibs.indexOf('repl'), 1);
760
761   // Kill child process when main process dies
762   this.repl.on('exit', function() {
763     process.exit(0);
764   });
765
766   // Handle all possible exits
767   process.on('exit', this.killChild.bind(this));
768   process.once('SIGTERM', process.exit.bind(process, 0));
769   process.once('SIGHUP', process.exit.bind(process, 0));
770
771   var proto = Interface.prototype;
772   const ignored = ['pause', 'resume', 'exitRepl', 'handleBreak',
773                    'requireConnection', 'killChild', 'trySpawn',
774                    'controlEval', 'debugEval', 'print', 'childPrint',
775                    'clearline'];
776   const shortcut = {
777     'run': 'r',
778     'cont': 'c',
779     'next': 'n',
780     'step': 's',
781     'out': 'o',
782     'backtrace': 'bt',
783     'setBreakpoint': 'sb',
784     'clearBreakpoint': 'cb',
785     'pause_': 'pause'
786   };
787
788   function defineProperty(key, protoKey) {
789     // Check arity
790     var fn = proto[protoKey].bind(self);
791
792     if (proto[protoKey].length === 0) {
793       Object.defineProperty(self.repl.context, key, {
794         get: fn,
795         enumerable: true,
796         configurable: false
797       });
798     } else {
799       self.repl.context[key] = fn;
800     }
801   };
802
803   // Copy all prototype methods in repl context
804   // Setup them as getters if possible
805   for (var i in proto) {
806     if (Object.prototype.hasOwnProperty.call(proto, i) &&
807         ignored.indexOf(i) === -1) {
808       defineProperty(i, i);
809       if (shortcut[i]) defineProperty(shortcut[i], i);
810     }
811   }
812
813   this.killed = false;
814   this.waiting = null;
815   this.paused = 0;
816   this.context = this.repl.context;
817   this.history = {
818     debug: [],
819     control: []
820   };
821   this.breakpoints = [];
822   this._watchers = [];
823
824   // Run script automatically
825   this.pause();
826
827   // XXX Need to figure out why we need this delay
828   setTimeout(function() {
829
830     self.run(function() {
831       self.resume();
832     });
833   }, 10);
834 }
835
836
837 // Stream control
838
839
840 Interface.prototype.pause = function() {
841   if (this.killed || this.paused++ > 0) return this;
842   this.repl.rli.pause();
843   this.stdin.pause();
844   return this;
845 };
846
847 Interface.prototype.resume = function(silent) {
848   if (this.killed || this.paused === 0 || --this.paused !== 0) return this;
849   this.repl.rli.resume();
850   if (silent !== true) {
851     this.repl.displayPrompt();
852   }
853   this.stdin.resume();
854
855   if (this.waiting) {
856     this.waiting();
857     this.waiting = null;
858   }
859   return this;
860 };
861
862
863 // Clear current line
864 Interface.prototype.clearline = function() {
865   if (this.stdout.isTTY) {
866     this.stdout.cursorTo(0);
867     this.stdout.clearLine(1);
868   } else {
869     this.stdout.write('\b');
870   }
871 };
872
873 // Print text to output stream
874 Interface.prototype.print = function(text, oneline) {
875   if (this.killed) return;
876   this.clearline();
877
878   this.stdout.write(typeof text === 'string' ? text : util.inspect(text));
879
880   if (oneline !== true) {
881     this.stdout.write('\n');
882   }
883 };
884
885 // Format and print text from child process
886 Interface.prototype.childPrint = function(text) {
887   this.print(text.toString().split(/\r\n|\r|\n/g).filter(function(chunk) {
888     return chunk;
889   }).map(function(chunk) {
890     return '< ' + chunk;
891   }).join('\n'));
892   this.repl.displayPrompt(true);
893 };
894
895 // Errors formatting
896 Interface.prototype.error = function(text) {
897   this.print(text);
898   this.resume();
899 };
900
901
902 // Debugger's `break` event handler
903 Interface.prototype.handleBreak = function(r) {
904   var self = this;
905
906   this.pause();
907
908   // Save execution context's data
909   this.client.currentSourceLine = r.sourceLine;
910   this.client.currentSourceLineText = r.sourceLineText;
911   this.client.currentSourceColumn = r.sourceColumn;
912   this.client.currentFrame = 0;
913   this.client.currentScript = r.script && r.script.name;
914
915   // Print break data
916   this.print(SourceInfo(r));
917
918   // Show watchers' values
919   this.watchers(true, function(err) {
920     if (err) return self.error(err);
921
922     // And list source
923     self.list(2);
924
925     self.resume(true);
926   });
927 };
928
929
930 // Internal method for checking connection state
931 Interface.prototype.requireConnection = function() {
932   if (!this.client) {
933     this.error('App isn\'t running... Try `run` instead');
934     return false;
935   }
936   return true;
937 };
938
939
940 // Evals
941
942 // Used for debugger's commands evaluation and execution
943 Interface.prototype.controlEval = function(code, context, filename, callback) {
944   try {
945     // Repeat last command if empty line are going to be evaluated
946     if (this.repl.rli.history && this.repl.rli.history.length > 0) {
947       if (code === '\n') {
948         code = this.repl.rli.history[0] + '\n';
949       }
950     }
951
952     var result = vm.runInContext(code, context, filename);
953
954     // Repl should not ask for next command
955     // if current one was asynchronous.
956     if (this.paused === 0) return callback(null, result);
957
958     // Add a callback for asynchronous command
959     // (it will be automatically invoked by .resume() method
960     this.waiting = function() {
961       callback(null, result);
962     };
963   } catch (e) {
964     callback(e);
965   }
966 };
967
968 // Used for debugger's remote evaluation (`repl`) commands
969 Interface.prototype.debugEval = function(code, context, filename, callback) {
970   if (!this.requireConnection()) return;
971
972   var self = this,
973       client = this.client;
974
975   // Repl asked for scope variables
976   if (code === '.scope') {
977     client.reqScopes(callback);
978     return;
979   }
980
981   var frame = client.currentFrame === NO_FRAME ? frame : undefined;
982
983   self.pause();
984
985   // Request remote evaluation globally or in current frame
986   client.reqFrameEval(code, frame, function(err, res) {
987     if (err) {
988       callback(err);
989       self.resume(true);
990       return;
991     }
992
993     // Request object by handles (and it's sub-properties)
994     client.mirrorObject(res, 3, function(err, mirror) {
995       callback(null, mirror);
996       self.resume(true);
997     });
998   });
999 };
1000
1001
1002 // Utils
1003
1004 // Adds spaces and prefix to number
1005 // maxN is a maximum number we should have space for
1006 function leftPad(n, prefix, maxN) {
1007   var s = n.toString(),
1008       nchars = Math.max(2, String(maxN).length) + 1,
1009       nspaces = nchars - s.length - 1;
1010
1011   for (var i = 0; i < nspaces; i++) {
1012     prefix += ' ';
1013   }
1014
1015   return prefix + s;
1016 }
1017
1018
1019 // Commands
1020
1021
1022 // Print help message
1023 Interface.prototype.help = function() {
1024   this.print(helpMessage);
1025 };
1026
1027
1028 // Run script
1029 Interface.prototype.run = function() {
1030   var callback = arguments[0];
1031
1032   if (this.child) {
1033     this.error('App is already running... Try `restart` instead');
1034     callback && callback(true);
1035   } else {
1036     this.trySpawn(callback);
1037   }
1038 };
1039
1040
1041 // Restart script
1042 Interface.prototype.restart = function() {
1043   if (!this.requireConnection()) return;
1044
1045   var self = this;
1046
1047   self.pause();
1048   self.killChild();
1049
1050   // XXX need to wait a little bit for the restart to work?
1051   setTimeout(function() {
1052     self.trySpawn();
1053     self.resume();
1054   }, 1000);
1055 };
1056
1057
1058 // Print version
1059 Interface.prototype.version = function() {
1060   if (!this.requireConnection()) return;
1061
1062   var self = this;
1063
1064   this.pause();
1065   this.client.reqVersion(function(err, v) {
1066     if (err) {
1067       self.error(err);
1068     } else {
1069       self.print(v);
1070     }
1071     self.resume();
1072   });
1073 };
1074
1075 // List source code
1076 Interface.prototype.list = function(delta) {
1077   if (!this.requireConnection()) return;
1078
1079   delta || (delta = 5);
1080
1081   var self = this,
1082       client = this.client,
1083       from = client.currentSourceLine - delta + 1,
1084       to = client.currentSourceLine + delta + 1;
1085
1086   self.pause();
1087   client.reqSource(from, to, function(err, res) {
1088     if (err || !res) {
1089       self.error('You can\'t list source code right now');
1090       self.resume();
1091       return;
1092     }
1093
1094     var lines = res.source.split('\n');
1095     for (var i = 0; i < lines.length; i++) {
1096       var lineno = res.fromLine + i + 1;
1097       if (lineno < from || lineno > to) continue;
1098
1099       var current = lineno == 1 + client.currentSourceLine,
1100           breakpoint = client.breakpoints.some(function(bp) {
1101             return (bp.scriptReq === client.currentScript ||
1102                     bp.script === client.currentScript) &&
1103                     bp.line == lineno;
1104           });
1105
1106       if (lineno == 1) {
1107         // The first line needs to have the module wrapper filtered out of
1108         // it.
1109         var wrapper = Module.wrapper[0];
1110         lines[i] = lines[i].slice(wrapper.length);
1111
1112         client.currentSourceColumn -= wrapper.length;
1113       }
1114
1115       // Highlight executing statement
1116       var line;
1117       if (current) {
1118         line = SourceUnderline(lines[i],
1119                                client.currentSourceColumn,
1120                                self.repl);
1121       } else {
1122         line = lines[i];
1123       }
1124
1125       var prefixChar = ' ';
1126       if (current) {
1127         prefixChar = '>';
1128       } else if (breakpoint) {
1129         prefixChar = '*';
1130       }
1131
1132       self.print(leftPad(lineno, prefixChar, to) + ' ' + line);
1133     }
1134     self.resume();
1135   });
1136 };
1137
1138 // Print backtrace
1139 Interface.prototype.backtrace = function() {
1140   if (!this.requireConnection()) return;
1141
1142   var self = this,
1143       client = this.client;
1144
1145   self.pause();
1146   client.fullTrace(function(err, bt) {
1147     if (err) {
1148       self.error('Can\'t request backtrace now');
1149       self.resume();
1150       return;
1151     }
1152
1153     if (bt.totalFrames == 0) {
1154       self.print('(empty stack)');
1155     } else {
1156       var trace = [],
1157           firstFrameNative = bt.frames[0].script.isNative;
1158
1159       for (var i = 0; i < bt.frames.length; i++) {
1160         var frame = bt.frames[i];
1161         if (!firstFrameNative && frame.script.isNative) break;
1162
1163         var text = '#' + i + ' ';
1164         if (frame.func.inferredName && frame.func.inferredName.length > 0) {
1165           text += frame.func.inferredName + ' ';
1166         }
1167         text += path.basename(frame.script.name) + ':';
1168         text += (frame.line + 1) + ':' + (frame.column + 1);
1169
1170         trace.push(text);
1171       }
1172
1173       self.print(trace.join('\n'));
1174     }
1175
1176     self.resume();
1177   });
1178 };
1179
1180
1181 // First argument tells if it should display internal node scripts or not
1182 // (available only for internal debugger's functions)
1183 Interface.prototype.scripts = function() {
1184   if (!this.requireConnection()) return;
1185
1186   var client = this.client,
1187       displayNatives = arguments[0] || false,
1188       scripts = [];
1189
1190   this.pause();
1191   for (var id in client.scripts) {
1192     var script = client.scripts[id];
1193     if (script !== null && typeof script === 'object' && script.name) {
1194       if (displayNatives ||
1195           script.name == client.currentScript ||
1196           !script.isNative) {
1197         scripts.push(
1198             (script.name == client.currentScript ? '* ' : '  ') +
1199             id + ': ' +
1200             path.basename(script.name)
1201         );
1202       }
1203     }
1204   }
1205   this.print(scripts.join('\n'));
1206   this.resume();
1207 };
1208
1209
1210 // Continue execution of script
1211 Interface.prototype.cont = function() {
1212   if (!this.requireConnection()) return;
1213   this.pause();
1214
1215   var self = this;
1216   this.client.reqContinue(function(err) {
1217     if (err) self.error(err);
1218     self.resume();
1219   });
1220 };
1221
1222
1223 // Step commands generator
1224 Interface.stepGenerator = function(type, count) {
1225   return function() {
1226     if (!this.requireConnection()) return;
1227
1228     var self = this;
1229
1230     self.pause();
1231     self.client.step(type, count, function(err, res) {
1232       if (err) self.error(err);
1233       self.resume();
1234     });
1235   };
1236 };
1237
1238
1239 // Jump to next command
1240 Interface.prototype.next = Interface.stepGenerator('next', 1);
1241
1242
1243 // Step in
1244 Interface.prototype.step = Interface.stepGenerator('in', 1);
1245
1246
1247 // Step out
1248 Interface.prototype.out = Interface.stepGenerator('out', 1);
1249
1250
1251 // Watch
1252 Interface.prototype.watch = function(expr) {
1253   this._watchers.push(expr);
1254 };
1255
1256 // Unwatch
1257 Interface.prototype.unwatch = function(expr) {
1258   var index = this._watchers.indexOf(expr);
1259
1260   // Unwatch by expression
1261   // or
1262   // Unwatch by watcher number
1263   this._watchers.splice(index !== -1 ? index : +expr, 1);
1264 };
1265
1266 // List watchers
1267 Interface.prototype.watchers = function() {
1268   var self = this;
1269   var verbose = arguments[0] || false;
1270   var callback = arguments[1] || function() {};
1271   var waiting = this._watchers.length;
1272   var values = [];
1273
1274   this.pause();
1275
1276   if (!waiting) {
1277     this.resume();
1278
1279     return callback();
1280   }
1281
1282   this._watchers.forEach(function(watcher, i) {
1283     self.debugEval(watcher, null, null, function(err, value) {
1284       values[i] = err ? '<error>' : value;
1285       wait();
1286     });
1287   });
1288
1289   function wait() {
1290     if (--waiting === 0) {
1291       if (verbose) self.print('Watchers:');
1292
1293       self._watchers.forEach(function(watcher, i) {
1294         self.print(leftPad(i, ' ', self._watchers.length - 1) +
1295                    ': ' + watcher + ' = ' +
1296                    JSON.stringify(values[i]));
1297       });
1298
1299       if (verbose) self.print('');
1300
1301       self.resume();
1302
1303       callback(null);
1304     }
1305   }
1306 };
1307
1308 // Break on exception
1309 Interface.prototype.breakOnException = function breakOnException() {
1310   if (!this.requireConnection()) return;
1311
1312   var self = this;
1313
1314   // Break on exceptions
1315   this.pause();
1316   this.client.reqSetExceptionBreak('all', function(err, res) {
1317     self.resume();
1318   });
1319 };
1320
1321 // Add breakpoint
1322 Interface.prototype.setBreakpoint = function(script, line,
1323                                              condition, silent) {
1324   if (!this.requireConnection()) return;
1325
1326   var self = this,
1327       scriptId,
1328       ambiguous;
1329
1330   // setBreakpoint() should insert breakpoint on current line
1331   if (script === undefined) {
1332     script = this.client.currentScript;
1333     line = this.client.currentSourceLine + 1;
1334   }
1335
1336   // setBreakpoint(line-number) should insert breakpoint in current script
1337   if (line === undefined && typeof script === 'number') {
1338     line = script;
1339     script = this.client.currentScript;
1340   }
1341
1342   if (script === undefined) {
1343     this.print('Cannot determine the current script, ' +
1344         'make sure the debugged process is paused.');
1345     return;
1346   }
1347
1348   if (/\(\)$/.test(script)) {
1349     // setBreakpoint('functionname()');
1350     var req = {
1351       type: 'function',
1352       target: script.replace(/\(\)$/, ''),
1353       condition: condition
1354     };
1355   } else {
1356     // setBreakpoint('scriptname')
1357     if (script != +script && !this.client.scripts[script]) {
1358       var scripts = this.client.scripts;
1359       for (var id in scripts) {
1360         if (scripts[id] &&
1361             scripts[id].name &&
1362             scripts[id].name.indexOf(script) !== -1) {
1363           if (scriptId) {
1364             ambiguous = true;
1365           }
1366           scriptId = id;
1367         }
1368       }
1369     } else {
1370       scriptId = script;
1371     }
1372
1373     if (ambiguous) return this.error('Script name is ambiguous');
1374     if (line <= 0) return this.error('Line should be a positive value');
1375
1376     var req;
1377     if (scriptId) {
1378       req = {
1379         type: 'scriptId',
1380         target: scriptId,
1381         line: line - 1,
1382         condition: condition
1383       };
1384     } else {
1385       this.print('Warning: script \'' + script + '\' was not loaded yet.');
1386       var escapedPath = script.replace(/([/\\.?*()^${}|[\]])/g, '\\$1');
1387       var scriptPathRegex = '^(.*[\\/\\\\])?' + escapedPath + '$';
1388       req = {
1389         type: 'scriptRegExp',
1390         target: scriptPathRegex,
1391         line: line - 1,
1392         condition: condition
1393       };
1394     }
1395   }
1396
1397   self.pause();
1398   self.client.setBreakpoint(req, function(err, res) {
1399     if (err) {
1400       if (!silent) {
1401         self.error(err);
1402       }
1403     } else {
1404       if (!silent) {
1405         self.list(5);
1406       }
1407
1408       // Try load scriptId and line from response
1409       if (!scriptId) {
1410         scriptId = res.script_id;
1411         line = res.line + 1;
1412       }
1413
1414       // Remember this breakpoint even if scriptId is not resolved yet
1415       self.client.breakpoints.push({
1416         id: res.breakpoint,
1417         scriptId: scriptId,
1418         script: (self.client.scripts[scriptId] || {}).name,
1419         line: line,
1420         condition: condition,
1421         scriptReq: script
1422       });
1423     }
1424     self.resume();
1425   });
1426 };
1427
1428 // Clear breakpoint
1429 Interface.prototype.clearBreakpoint = function(script, line) {
1430   if (!this.requireConnection()) return;
1431
1432   var ambiguous,
1433       breakpoint,
1434       scriptId,
1435       index;
1436
1437   this.client.breakpoints.some(function(bp, i) {
1438     if (bp.scriptId === script ||
1439         bp.scriptReq === script ||
1440         (bp.script && bp.script.indexOf(script) !== -1)) {
1441       if (index !== undefined) {
1442         ambiguous = true;
1443       }
1444       scriptId = script;
1445       if (bp.line === line) {
1446         index = i;
1447         breakpoint = bp.id;
1448         return true;
1449       }
1450     }
1451   });
1452
1453   if (!scriptId && !this.client.scripts[script]) {
1454     var scripts = this.client.scripts;
1455     for (var id in scripts) {
1456       if (scripts[id] &&
1457           scripts[id].name &&
1458           scripts[id].name.indexOf(script) !== -1) {
1459         if (scriptId) {
1460           ambiguous = true;
1461         }
1462         scriptId = id;
1463       }
1464     }
1465   }
1466
1467   if (ambiguous) return this.error('Script name is ambiguous');
1468
1469   if (scriptId === undefined) {
1470     return this.error('Script ' + script + ' not found');
1471   }
1472
1473   if (breakpoint === undefined) {
1474     return this.error('Breakpoint not found on line ' + line);
1475   }
1476
1477   var self = this,
1478       req = {
1479         breakpoint: breakpoint
1480       };
1481
1482   self.pause();
1483   self.client.clearBreakpoint(req, function(err, res) {
1484     if (err) {
1485       self.error(err);
1486     } else {
1487       self.client.breakpoints.splice(index, 1);
1488       self.list(5);
1489     }
1490     self.resume();
1491   });
1492 };
1493
1494
1495 // Show breakpoints
1496 Interface.prototype.breakpoints = function() {
1497   if (!this.requireConnection()) return;
1498
1499   this.pause();
1500   var self = this;
1501   this.client.listbreakpoints(function(err, res) {
1502     if (err) {
1503       self.error(err);
1504     } else {
1505       self.print(res);
1506       self.resume();
1507     }
1508   });
1509 };
1510
1511
1512 // Pause child process
1513 Interface.prototype.pause_ = function() {
1514   if (!this.requireConnection()) return;
1515
1516   var self = this,
1517       cmd = 'process._debugPause();';
1518
1519   this.pause();
1520   this.client.reqFrameEval(cmd, NO_FRAME, function(err, res) {
1521     if (err) {
1522       self.error(err);
1523     } else {
1524       self.resume();
1525     }
1526   });
1527 };
1528
1529
1530 // Kill child process
1531 Interface.prototype.kill = function() {
1532   if (!this.child) return;
1533   this.killChild();
1534 };
1535
1536
1537 // Activate debug repl
1538 Interface.prototype.repl = function() {
1539   if (!this.requireConnection()) return;
1540
1541   var self = this;
1542
1543   self.print('Press Ctrl + C to leave debug repl');
1544
1545   // Don't display any default messages
1546   var listeners = this.repl.rli.listeners('SIGINT').slice(0);
1547   this.repl.rli.removeAllListeners('SIGINT');
1548
1549   function exitDebugRepl() {
1550     // Restore all listeners
1551     process.nextTick(function() {
1552       listeners.forEach(function(listener) {
1553         self.repl.rli.on('SIGINT', listener);
1554       });
1555     });
1556
1557     // Exit debug repl
1558     self.exitRepl();
1559
1560     self.repl.rli.removeListener('SIGINT', exitDebugRepl);
1561     self.repl.removeListener('exit', exitDebugRepl);
1562   }
1563
1564   // Exit debug repl on SIGINT
1565   this.repl.rli.on('SIGINT', exitDebugRepl);
1566
1567   // Exit debug repl on repl exit
1568   this.repl.on('exit', exitDebugRepl);
1569
1570   // Set new
1571   this.repl.eval = this.debugEval.bind(this);
1572   this.repl.context = {};
1573
1574   // Swap history
1575   this.history.control = this.repl.rli.history;
1576   this.repl.rli.history = this.history.debug;
1577
1578   this.repl.rli.setPrompt('> ');
1579   this.repl.displayPrompt();
1580 };
1581
1582
1583 // Exit debug repl
1584 Interface.prototype.exitRepl = function() {
1585   // Restore eval
1586   this.repl.eval = this.controlEval.bind(this);
1587
1588   // Swap history
1589   this.history.debug = this.repl.rli.history;
1590   this.repl.rli.history = this.history.control;
1591
1592   this.repl.context = this.context;
1593   this.repl.rli.setPrompt('debug> ');
1594   this.repl.displayPrompt();
1595 };
1596
1597
1598 // Quit
1599 Interface.prototype.quit = function() {
1600   this.killChild();
1601   process.exit(0);
1602 };
1603
1604
1605 // Kills child process
1606 Interface.prototype.killChild = function() {
1607   if (this.child) {
1608     this.child.kill();
1609     this.child = null;
1610   }
1611
1612   if (this.client) {
1613     // Save breakpoints
1614     this.breakpoints = this.client.breakpoints;
1615
1616     this.client.destroy();
1617     this.client = null;
1618   }
1619 };
1620
1621
1622 // Spawns child process (and restores breakpoints)
1623 Interface.prototype.trySpawn = function(cb) {
1624   var self = this,
1625       breakpoints = this.breakpoints || [],
1626       port = exports.port,
1627       host = '127.0.0.1',
1628       childArgs = this.args;
1629
1630   this.killChild();
1631   assert(!this.child);
1632
1633   var isRemote = false;
1634   if (this.args.length === 2) {
1635     var match = this.args[1].match(/^([^:]+):(\d+)$/);
1636
1637     if (match) {
1638       // Connecting to remote debugger
1639       // `node debug localhost:5858`
1640       host = match[1];
1641       port = parseInt(match[2], 10);
1642       isRemote = true;
1643     }
1644   } else if (this.args.length === 3) {
1645     // `node debug -p pid`
1646     if (this.args[1] === '-p' && /^\d+$/.test(this.args[2])) {
1647       const pid = parseInt(this.args[2], 10);
1648       try {
1649         process._debugProcess(pid);
1650       } catch (e) {
1651         if (e.code === 'ESRCH') {
1652           console.error(`Target process: ${pid} doesn't exist.`);
1653           process.exit(1);
1654         }
1655         throw e;
1656       }
1657       isRemote = true;
1658     } else {
1659       var match = this.args[1].match(/^--port=(\d+)$/);
1660       if (match) {
1661         // Start debugger on custom port
1662         // `node debug --port=5858 app.js`
1663         port = parseInt(match[1], 10);
1664         childArgs = ['--debug-brk=' + port].concat(this.args.slice(2));
1665       }
1666     }
1667   }
1668
1669   if (!isRemote) {
1670     // pipe stream into debugger
1671     this.child = spawn(process.execPath, childArgs);
1672
1673     this.child.stdout.on('data', this.childPrint.bind(this));
1674     this.child.stderr.on('data', this.childPrint.bind(this));
1675   }
1676
1677   this.pause();
1678
1679   var client = self.client = new Client(),
1680       connectionAttempts = 0;
1681
1682   client.once('ready', function() {
1683     self.stdout.write(' ok\n');
1684
1685     // Restore breakpoints
1686     breakpoints.forEach(function(bp) {
1687       self.print('Restoring breakpoint ' + bp.scriptReq + ':' + bp.line);
1688       self.setBreakpoint(bp.scriptReq, bp.line, bp.condition, true);
1689     });
1690
1691     client.on('close', function() {
1692       self.pause();
1693       self.print('program terminated');
1694       self.resume();
1695       self.client = null;
1696       self.killChild();
1697     });
1698
1699     if (cb) cb();
1700     self.resume();
1701   });
1702
1703   client.on('unhandledResponse', function(res) {
1704     self.pause();
1705     self.print('\nunhandled res:' + JSON.stringify(res));
1706     self.resume();
1707   });
1708
1709   client.on('break', function(res) {
1710     self.handleBreak(res.body);
1711   });
1712
1713   client.on('exception', function(res) {
1714     self.handleBreak(res.body);
1715   });
1716
1717   client.on('error', connectError);
1718   function connectError() {
1719     // If it's failed to connect 10 times then print failed message
1720     if (connectionAttempts >= 10) {
1721       console.error(' failed, please retry');
1722       process.exit(1);
1723     }
1724     setTimeout(attemptConnect, 500);
1725   }
1726
1727   function attemptConnect() {
1728     ++connectionAttempts;
1729     self.stdout.write('.');
1730     client.connect(port, host);
1731   }
1732
1733   self.print('connecting to ' + host + ':' + port + ' ..', true);
1734   if (isRemote) {
1735     attemptConnect();
1736   } else {
1737     this.child.stderr.once('data', function() {
1738       setImmediate(attemptConnect);
1739     });
1740   }
1741 };