1// Copyright 2008 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6//     * Redistributions of source code must retain the above copyright
7//       notice, this list of conditions and the following disclaimer.
8//     * Redistributions in binary form must reproduce the above
9//       copyright notice, this list of conditions and the following
10//       disclaimer in the documentation and/or other materials provided
11//       with the distribution.
12//     * Neither the name of Google Inc. nor the names of its
13//       contributors may be used to endorse or promote products derived
14//       from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28"use strict";
29
30String.prototype.startsWith = function (str) {
31  if (str.length > this.length) {
32    return false;
33  }
34  return this.substr(0, str.length) == str;
35};
36
37function log10(num) {
38  return Math.log(num)/Math.log(10);
39}
40
41function ToInspectableObject(obj) {
42  if (!obj && typeof obj === 'object') {
43    return void 0;
44  } else {
45    return Object(obj);
46  }
47}
48
49function GetCompletions(global, last, full) {
50  var full_tokens = full.split();
51  full = full_tokens.pop();
52  var parts = full.split('.');
53  parts.pop();
54  var current = global;
55  for (var i = 0; i < parts.length; i++) {
56    var part = parts[i];
57    var next = current[part];
58    if (!next) {
59      return [];
60    }
61    current = next;
62  }
63  var result = [];
64  current = ToInspectableObject(current);
65  while (typeof current !== 'undefined') {
66    var mirror = new $debug.ObjectMirror(current);
67    var properties = mirror.properties();
68    for (var i = 0; i < properties.length; i++) {
69      var name = properties[i].name();
70      if (typeof name === 'string' && name.startsWith(last)) {
71        result.push(name);
72      }
73    }
74    current = ToInspectableObject(Object.getPrototypeOf(current));
75  }
76  return result;
77}
78
79
80// Global object holding debugger related constants and state.
81var Debug = {};
82
83
84// Debug events which can occour in the V8 JavaScript engine. These originate
85// from the API include file v8-debug.h.
86Debug.DebugEvent = { Break: 1,
87                     Exception: 2,
88                     NewFunction: 3,
89                     BeforeCompile: 4,
90                     AfterCompile: 5 };
91
92
93// The different types of scripts matching enum ScriptType in objects.h.
94Debug.ScriptType = { Native: 0,
95                     Extension: 1,
96                     Normal: 2 };
97
98
99// The different types of script compilations matching enum
100// Script::CompilationType in objects.h.
101Debug.ScriptCompilationType = { Host: 0,
102                                Eval: 1,
103                                JSON: 2 };
104
105
106// The different types of scopes matching constants runtime.cc.
107Debug.ScopeType = { Global: 0,
108                    Local: 1,
109                    With: 2,
110                    Closure: 3,
111                    Catch: 4,
112                    Block: 5 };
113
114
115// Current debug state.
116var kNoFrame = -1;
117Debug.State = {
118  currentFrame: kNoFrame,
119  displaySourceStartLine: -1,
120  displaySourceEndLine: -1,
121  currentSourceLine: -1
122};
123var trace_compile = false;  // Tracing all compile events?
124var trace_debug_json = false; // Tracing all debug json packets?
125var last_cmd = '';
126var repeat_cmd_line = '';
127var is_running = true;
128// Global variable used to store whether a handle was requested.
129var lookup_handle = null;
130
131// Copied from debug-delay.js.  This is needed below:
132function ScriptTypeFlag(type) {
133  return (1 << type);
134}
135
136
137// Process a debugger JSON message into a display text and a running status.
138// This function returns an object with properties "text" and "running" holding
139// this information.
140function DebugMessageDetails(message) {
141  if (trace_debug_json) {
142    print("received: '" + message + "'");
143  }
144  // Convert the JSON string to an object.
145  var response = new ProtocolPackage(message);
146  is_running = response.running();
147
148  if (response.type() == 'event') {
149    return DebugEventDetails(response);
150  } else {
151    return DebugResponseDetails(response);
152  }
153}
154
155function DebugEventDetails(response) {
156  var details = {text:'', running:false};
157
158  // Get the running state.
159  details.running = response.running();
160
161  var body = response.body();
162  var result = '';
163  switch (response.event()) {
164    case 'break':
165      if (body.breakpoints) {
166        result += 'breakpoint';
167        if (body.breakpoints.length > 1) {
168          result += 's';
169        }
170        result += ' #';
171        for (var i = 0; i < body.breakpoints.length; i++) {
172          if (i > 0) {
173            result += ', #';
174          }
175          result += body.breakpoints[i];
176        }
177      } else {
178        result += 'break';
179      }
180      result += ' in ';
181      result += body.invocationText;
182      result += ', ';
183      result += SourceInfo(body);
184      result += '\n';
185      result += SourceUnderline(body.sourceLineText, body.sourceColumn);
186      Debug.State.currentSourceLine = body.sourceLine;
187      Debug.State.displaySourceStartLine = -1;
188      Debug.State.displaySourceEndLine = -1;
189      Debug.State.currentFrame = 0;
190      details.text = result;
191      break;
192
193    case 'exception':
194      if (body.uncaught) {
195        result += 'Uncaught: ';
196      } else {
197        result += 'Exception: ';
198      }
199      result += '"';
200      result += body.exception.text;
201      result += '"';
202      if (body.sourceLine >= 0) {
203        result += ', ';
204        result += SourceInfo(body);
205        result += '\n';
206        result += SourceUnderline(body.sourceLineText, body.sourceColumn);
207        Debug.State.currentSourceLine = body.sourceLine;
208        Debug.State.displaySourceStartLine = -1;
209        Debug.State.displaySourceEndLine = -1;
210        Debug.State.currentFrame = 0;
211      } else {
212        result += ' (empty stack)';
213        Debug.State.currentSourceLine = -1;
214        Debug.State.displaySourceStartLine = -1;
215        Debug.State.displaySourceEndLine = -1;
216        Debug.State.currentFrame = kNoFrame;
217      }
218      details.text = result;
219      break;
220
221    case 'afterCompile':
222      if (trace_compile) {
223        result = 'Source ' + body.script.name + ' compiled:\n';
224        var source = body.script.source;
225        if (!(source[source.length - 1] == '\n')) {
226          result += source;
227        } else {
228          result += source.substring(0, source.length - 1);
229        }
230      }
231      details.text = result;
232      break;
233
234    case 'scriptCollected':
235      details.text = result;
236      break;
237
238    default:
239      details.text = 'Unknown debug event ' + response.event();
240  }
241
242  return details;
243}
244
245
246function SourceInfo(body) {
247  var result = '';
248
249  if (body.script) {
250    if (body.script.name) {
251      result += body.script.name;
252    } else {
253      result += '[unnamed]';
254    }
255  }
256  result += ' line ';
257  result += body.sourceLine + 1;
258  result += ' column ';
259  result += body.sourceColumn + 1;
260
261  return result;
262}
263
264
265function SourceUnderline(source_text, position) {
266  if (!source_text) {
267    return;
268  }
269
270  // Create an underline with a caret pointing to the source position. If the
271  // source contains a tab character the underline will have a tab character in
272  // the same place otherwise the underline will have a space character.
273  var underline = '';
274  for (var i = 0; i < position; i++) {
275    if (source_text[i] == '\t') {
276      underline += '\t';
277    } else {
278      underline += ' ';
279    }
280  }
281  underline += '^';
282
283  // Return the source line text with the underline beneath.
284  return source_text + '\n' + underline;
285}
286
287
288// Converts a text command to a JSON request.
289function DebugCommandToJSONRequest(cmd_line) {
290  var result = new DebugRequest(cmd_line).JSONRequest();
291  if (trace_debug_json && result) {
292    print("sending: '" + result + "'");
293  }
294  return result;
295}
296
297
298function DebugRequest(cmd_line) {
299  // If the very first character is a { assume that a JSON request have been
300  // entered as a command. Converting that to a JSON request is trivial.
301  if (cmd_line && cmd_line.length > 0 && cmd_line.charAt(0) == '{') {
302    this.request_ = cmd_line;
303    return;
304  }
305
306  // Check for a simple carriage return to repeat the last command:
307  var is_repeating = false;
308  if (cmd_line == '\n') {
309    if (is_running) {
310      cmd_line = 'break'; // Not in debugger mode, break with a frame request.
311    } else {
312      cmd_line = repeat_cmd_line; // use command to repeat.
313      is_repeating = true;
314    }
315  }
316  if (!is_running) { // Only save the command if in debugger mode.
317    repeat_cmd_line = cmd_line;   // save last command.
318  }
319
320  // Trim string for leading and trailing whitespace.
321  cmd_line = cmd_line.replace(/^\s+|\s+$/g, '');
322
323  // Find the command.
324  var pos = cmd_line.indexOf(' ');
325  var cmd;
326  var args;
327  if (pos == -1) {
328    cmd = cmd_line;
329    args = '';
330  } else {
331    cmd = cmd_line.slice(0, pos);
332    args = cmd_line.slice(pos).replace(/^\s+|\s+$/g, '');
333  }
334
335  if ((cmd === undefined) || !cmd) {
336    this.request_ = void 0;
337    return;
338  }
339
340  last_cmd = cmd;
341
342  // Switch on command.
343  switch (cmd) {
344    case 'continue':
345    case 'c':
346      this.request_ = this.continueCommandToJSONRequest_(args);
347      break;
348
349    case 'step':
350    case 's':
351      this.request_ = this.stepCommandToJSONRequest_(args, 'in');
352      break;
353
354    case 'stepi':
355    case 'si':
356      this.request_ = this.stepCommandToJSONRequest_(args, 'min');
357      break;
358
359    case 'next':
360    case 'n':
361      this.request_ = this.stepCommandToJSONRequest_(args, 'next');
362      break;
363
364    case 'finish':
365    case 'fin':
366      this.request_ = this.stepCommandToJSONRequest_(args, 'out');
367      break;
368
369    case 'backtrace':
370    case 'bt':
371      this.request_ = this.backtraceCommandToJSONRequest_(args);
372      break;
373
374    case 'frame':
375    case 'f':
376      this.request_ = this.frameCommandToJSONRequest_(args);
377      break;
378
379    case 'scopes':
380      this.request_ = this.scopesCommandToJSONRequest_(args);
381      break;
382
383    case 'scope':
384      this.request_ = this.scopeCommandToJSONRequest_(args);
385      break;
386
387    case 'disconnect':
388    case 'exit':
389    case 'quit':
390      this.request_ = this.disconnectCommandToJSONRequest_(args);
391      break;
392
393    case 'up':
394      this.request_ =
395          this.frameCommandToJSONRequest_('' +
396                                          (Debug.State.currentFrame + 1));
397      break;
398
399    case 'down':
400    case 'do':
401      this.request_ =
402          this.frameCommandToJSONRequest_('' +
403                                          (Debug.State.currentFrame - 1));
404      break;
405
406    case 'set':
407    case 'print':
408    case 'p':
409      this.request_ = this.printCommandToJSONRequest_(args);
410      break;
411
412    case 'dir':
413      this.request_ = this.dirCommandToJSONRequest_(args);
414      break;
415
416    case 'references':
417      this.request_ = this.referencesCommandToJSONRequest_(args);
418      break;
419
420    case 'instances':
421      this.request_ = this.instancesCommandToJSONRequest_(args);
422      break;
423
424    case 'list':
425    case 'l':
426      this.request_ = this.listCommandToJSONRequest_(args);
427      break;
428    case 'source':
429      this.request_ = this.sourceCommandToJSONRequest_(args);
430      break;
431
432    case 'scripts':
433    case 'script':
434    case 'scr':
435      this.request_ = this.scriptsCommandToJSONRequest_(args);
436      break;
437
438    case 'break':
439    case 'b':
440      this.request_ = this.breakCommandToJSONRequest_(args);
441      break;
442
443    case 'breakpoints':
444    case 'bb':
445      this.request_ = this.breakpointsCommandToJSONRequest_(args);
446      break;
447
448    case 'clear':
449    case 'delete':
450    case 'd':
451      this.request_ = this.clearCommandToJSONRequest_(args);
452      break;
453
454    case 'threads':
455      this.request_ = this.threadsCommandToJSONRequest_(args);
456      break;
457
458    case 'cond':
459      this.request_ = this.changeBreakpointCommandToJSONRequest_(args, 'cond');
460      break;
461
462    case 'enable':
463    case 'en':
464      this.request_ =
465          this.changeBreakpointCommandToJSONRequest_(args, 'enable');
466      break;
467
468    case 'disable':
469    case 'dis':
470      this.request_ =
471          this.changeBreakpointCommandToJSONRequest_(args, 'disable');
472      break;
473
474    case 'ignore':
475      this.request_ =
476          this.changeBreakpointCommandToJSONRequest_(args, 'ignore');
477      break;
478
479    case 'info':
480    case 'inf':
481      this.request_ = this.infoCommandToJSONRequest_(args);
482      break;
483
484    case 'flags':
485      this.request_ = this.v8FlagsToJSONRequest_(args);
486      break;
487
488    case 'gc':
489      this.request_ = this.gcToJSONRequest_(args);
490      break;
491
492    case 'trace':
493    case 'tr':
494      // Return undefined to indicate command handled internally (no JSON).
495      this.request_ = void 0;
496      this.traceCommand_(args);
497      break;
498
499    case 'help':
500    case '?':
501      this.helpCommand_(args);
502      // Return undefined to indicate command handled internally (no JSON).
503      this.request_ = void 0;
504      break;
505
506    default:
507      throw new Error('Unknown command "' + cmd + '"');
508  }
509}
510
511DebugRequest.prototype.JSONRequest = function() {
512  return this.request_;
513};
514
515
516function RequestPacket(command) {
517  this.seq = 0;
518  this.type = 'request';
519  this.command = command;
520}
521
522
523RequestPacket.prototype.toJSONProtocol = function() {
524  // Encode the protocol header.
525  var json = '{';
526  json += '"seq":' + this.seq;
527  json += ',"type":"' + this.type + '"';
528  if (this.command) {
529    json += ',"command":' + StringToJSON_(this.command);
530  }
531  if (this.arguments) {
532    json += ',"arguments":';
533    // Encode the arguments part.
534    if (this.arguments.toJSONProtocol) {
535      json += this.arguments.toJSONProtocol();
536    } else {
537      json += SimpleObjectToJSON_(this.arguments);
538    }
539  }
540  json += '}';
541  return json;
542};
543
544
545DebugRequest.prototype.createRequest = function(command) {
546  return new RequestPacket(command);
547};
548
549
550// Create a JSON request for the evaluation command.
551DebugRequest.prototype.makeEvaluateJSONRequest_ = function(expression) {
552  lookup_handle = null;
553
554  // Check if the expression is a handle id in the form #<handle>#.
555  var handle_match = expression.match(/^#([0-9]*)#$/);
556  if (handle_match) {
557    // Remember the handle requested in a global variable.
558    lookup_handle = parseInt(handle_match[1]);
559    // Build a lookup request.
560    var request = this.createRequest('lookup');
561    request.arguments = {};
562    request.arguments.handles = [ lookup_handle ];
563    return request.toJSONProtocol();
564  } else {
565    // Build an evaluate request.
566    var request = this.createRequest('evaluate');
567    request.arguments = {};
568    request.arguments.expression = expression;
569    // Request a global evaluation if there is no current frame.
570    if (Debug.State.currentFrame == kNoFrame) {
571      request.arguments.global = true;
572    }
573    return request.toJSONProtocol();
574  }
575};
576
577
578// Create a JSON request for the references/instances command.
579DebugRequest.prototype.makeReferencesJSONRequest_ = function(handle, type) {
580  // Build a references request.
581  var handle_match = handle.match(/^#([0-9]*)#$/);
582  if (handle_match) {
583    var request = this.createRequest('references');
584    request.arguments = {};
585    request.arguments.type = type;
586    request.arguments.handle = parseInt(handle_match[1]);
587    return request.toJSONProtocol();
588  } else {
589    throw new Error('Invalid object id.');
590  }
591};
592
593
594// Create a JSON request for the continue command.
595DebugRequest.prototype.continueCommandToJSONRequest_ = function(args) {
596  var request = this.createRequest('continue');
597  return request.toJSONProtocol();
598};
599
600
601// Create a JSON request for the step command.
602DebugRequest.prototype.stepCommandToJSONRequest_ = function(args, type) {
603  // Requesting a step is through the continue command with additional
604  // arguments.
605  var request = this.createRequest('continue');
606  request.arguments = {};
607
608  // Process arguments if any.
609
610  // Only process args if the command is 'step' which is indicated by type being
611  // set to 'in'.  For all other commands, ignore the args.
612  if (args && args.length > 0) {
613    args = args.split(/\s+/g);
614
615    if (args.length > 2) {
616      throw new Error('Invalid step arguments.');
617    }
618
619    if (args.length > 0) {
620      // Check if we have a gdb stype step command.  If so, the 1st arg would
621      // be the step count.  If it's not a number, then assume that we're
622      // parsing for the legacy v8 step command.
623      var stepcount = Number(args[0]);
624      if (stepcount == Number.NaN) {
625        // No step count at arg 1.  Process as legacy d8 step command:
626        if (args.length == 2) {
627          var stepcount = parseInt(args[1]);
628          if (isNaN(stepcount) || stepcount <= 0) {
629            throw new Error('Invalid step count argument "' + args[0] + '".');
630          }
631          request.arguments.stepcount = stepcount;
632        }
633
634        // Get the step action.
635        switch (args[0]) {
636          case 'in':
637          case 'i':
638            request.arguments.stepaction = 'in';
639            break;
640
641          case 'min':
642          case 'm':
643            request.arguments.stepaction = 'min';
644            break;
645
646          case 'next':
647          case 'n':
648            request.arguments.stepaction = 'next';
649            break;
650
651          case 'out':
652          case 'o':
653            request.arguments.stepaction = 'out';
654            break;
655
656          default:
657            throw new Error('Invalid step argument "' + args[0] + '".');
658        }
659
660      } else {
661        // gdb style step commands:
662        request.arguments.stepaction = type;
663        request.arguments.stepcount = stepcount;
664      }
665    }
666  } else {
667    // Default is step of the specified type.
668    request.arguments.stepaction = type;
669  }
670
671  return request.toJSONProtocol();
672};
673
674
675// Create a JSON request for the backtrace command.
676DebugRequest.prototype.backtraceCommandToJSONRequest_ = function(args) {
677  // Build a backtrace request from the text command.
678  var request = this.createRequest('backtrace');
679
680  // Default is to show top 10 frames.
681  request.arguments = {};
682  request.arguments.fromFrame = 0;
683  request.arguments.toFrame = 10;
684
685  args = args.split(/\s*[ ]+\s*/g);
686  if (args.length == 1 && args[0].length > 0) {
687    var frameCount = parseInt(args[0]);
688    if (frameCount > 0) {
689      // Show top frames.
690      request.arguments.fromFrame = 0;
691      request.arguments.toFrame = frameCount;
692    } else {
693      // Show bottom frames.
694      request.arguments.fromFrame = 0;
695      request.arguments.toFrame = -frameCount;
696      request.arguments.bottom = true;
697    }
698  } else if (args.length == 2) {
699    var fromFrame = parseInt(args[0]);
700    var toFrame = parseInt(args[1]);
701    if (isNaN(fromFrame) || fromFrame < 0) {
702      throw new Error('Invalid start frame argument "' + args[0] + '".');
703    }
704    if (isNaN(toFrame) || toFrame < 0) {
705      throw new Error('Invalid end frame argument "' + args[1] + '".');
706    }
707    if (fromFrame > toFrame) {
708      throw new Error('Invalid arguments start frame cannot be larger ' +
709                      'than end frame.');
710    }
711    // Show frame range.
712    request.arguments.fromFrame = fromFrame;
713    request.arguments.toFrame = toFrame + 1;
714  } else if (args.length > 2) {
715    throw new Error('Invalid backtrace arguments.');
716  }
717
718  return request.toJSONProtocol();
719};
720
721
722// Create a JSON request for the frame command.
723DebugRequest.prototype.frameCommandToJSONRequest_ = function(args) {
724  // Build a frame request from the text command.
725  var request = this.createRequest('frame');
726  args = args.split(/\s*[ ]+\s*/g);
727  if (args.length > 0 && args[0].length > 0) {
728    request.arguments = {};
729    request.arguments.number = args[0];
730  }
731  return request.toJSONProtocol();
732};
733
734
735// Create a JSON request for the scopes command.
736DebugRequest.prototype.scopesCommandToJSONRequest_ = function(args) {
737  // Build a scopes request from the text command.
738  var request = this.createRequest('scopes');
739  return request.toJSONProtocol();
740};
741
742
743// Create a JSON request for the scope command.
744DebugRequest.prototype.scopeCommandToJSONRequest_ = function(args) {
745  // Build a scope request from the text command.
746  var request = this.createRequest('scope');
747  args = args.split(/\s*[ ]+\s*/g);
748  if (args.length > 0 && args[0].length > 0) {
749    request.arguments = {};
750    request.arguments.number = args[0];
751  }
752  return request.toJSONProtocol();
753};
754
755
756// Create a JSON request for the print command.
757DebugRequest.prototype.printCommandToJSONRequest_ = function(args) {
758  // Build an evaluate request from the text command.
759  if (args.length == 0) {
760    throw new Error('Missing expression.');
761  }
762  return this.makeEvaluateJSONRequest_(args);
763};
764
765
766// Create a JSON request for the dir command.
767DebugRequest.prototype.dirCommandToJSONRequest_ = function(args) {
768  // Build an evaluate request from the text command.
769  if (args.length == 0) {
770    throw new Error('Missing expression.');
771  }
772  return this.makeEvaluateJSONRequest_(args);
773};
774
775
776// Create a JSON request for the references command.
777DebugRequest.prototype.referencesCommandToJSONRequest_ = function(args) {
778  // Build an evaluate request from the text command.
779  if (args.length == 0) {
780    throw new Error('Missing object id.');
781  }
782
783  return this.makeReferencesJSONRequest_(args, 'referencedBy');
784};
785
786
787// Create a JSON request for the instances command.
788DebugRequest.prototype.instancesCommandToJSONRequest_ = function(args) {
789  // Build an evaluate request from the text command.
790  if (args.length == 0) {
791    throw new Error('Missing object id.');
792  }
793
794  // Build a references request.
795  return this.makeReferencesJSONRequest_(args, 'constructedBy');
796};
797
798
799// Create a JSON request for the list command.
800DebugRequest.prototype.listCommandToJSONRequest_ = function(args) {
801
802  // Default is ten lines starting five lines before the current location.
803  if (Debug.State.displaySourceEndLine == -1) {
804    // If we list forwards, we will start listing after the last source end
805    // line.  Set it to start from 5 lines before the current location.
806    Debug.State.displaySourceEndLine = Debug.State.currentSourceLine - 5;
807    // If we list backwards, we will start listing backwards from the last
808    // source start line.  Set it to start from 1 lines before the current
809    // location.
810    Debug.State.displaySourceStartLine = Debug.State.currentSourceLine + 1;
811  }
812
813  var from = Debug.State.displaySourceEndLine + 1;
814  var lines = 10;
815
816  // Parse the arguments.
817  args = args.split(/\s*,\s*/g);
818  if (args == '') {
819  } else if ((args.length == 1) && (args[0] == '-')) {
820    from = Debug.State.displaySourceStartLine - lines;
821  } else if (args.length == 2) {
822    from = parseInt(args[0]);
823    lines = parseInt(args[1]) - from + 1; // inclusive of the ending line.
824  } else {
825    throw new Error('Invalid list arguments.');
826  }
827  Debug.State.displaySourceStartLine = from;
828  Debug.State.displaySourceEndLine = from + lines - 1;
829  var sourceArgs = '' + from + ' ' + lines;
830  return this.sourceCommandToJSONRequest_(sourceArgs);
831};
832
833
834// Create a JSON request for the source command.
835DebugRequest.prototype.sourceCommandToJSONRequest_ = function(args) {
836  // Build a evaluate request from the text command.
837  var request = this.createRequest('source');
838
839  // Default is ten lines starting five lines before the current location.
840  var from = Debug.State.currentSourceLine - 5;
841  var lines = 10;
842
843  // Parse the arguments.
844  args = args.split(/\s*[ ]+\s*/g);
845  if (args.length > 1 && args[0].length > 0 && args[1].length > 0) {
846    from = parseInt(args[0]) - 1;
847    lines = parseInt(args[1]);
848  } else if (args.length > 0 && args[0].length > 0) {
849    from = parseInt(args[0]) - 1;
850  }
851
852  if (from < 0) from = 0;
853  if (lines < 0) lines = 10;
854
855  // Request source arround current source location.
856  request.arguments = {};
857  request.arguments.fromLine = from;
858  request.arguments.toLine = from + lines;
859
860  return request.toJSONProtocol();
861};
862
863
864// Create a JSON request for the scripts command.
865DebugRequest.prototype.scriptsCommandToJSONRequest_ = function(args) {
866  // Build a evaluate request from the text command.
867  var request = this.createRequest('scripts');
868
869  // Process arguments if any.
870  if (args && args.length > 0) {
871    args = args.split(/\s*[ ]+\s*/g);
872
873    if (args.length > 1) {
874      throw new Error('Invalid scripts arguments.');
875    }
876
877    request.arguments = {};
878    switch (args[0]) {
879      case 'natives':
880        request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Native);
881        break;
882
883      case 'extensions':
884        request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Extension);
885        break;
886
887      case 'all':
888        request.arguments.types =
889            ScriptTypeFlag(Debug.ScriptType.Normal) |
890            ScriptTypeFlag(Debug.ScriptType.Native) |
891            ScriptTypeFlag(Debug.ScriptType.Extension);
892        break;
893
894      default:
895        // If the arg is not one of the know one aboves, then it must be a
896        // filter used for filtering the results:
897        request.arguments.filter = args[0];
898        break;
899    }
900  }
901
902  return request.toJSONProtocol();
903};
904
905
906// Create a JSON request for the break command.
907DebugRequest.prototype.breakCommandToJSONRequest_ = function(args) {
908  // Build a evaluate request from the text command.
909  // Process arguments if any.
910  if (args && args.length > 0) {
911    var target = args;
912    var type = 'function';
913    var line;
914    var column;
915    var condition;
916    var pos;
917
918    var request = this.createRequest('setbreakpoint');
919
920    // Break the args into target spec and condition if appropriate.
921
922    // Check for breakpoint condition.
923    pos = args.indexOf(' ');
924    if (pos > 0) {
925      target = args.substring(0, pos);
926      condition = args.substring(pos + 1, args.length);
927    }
928
929    // Check for script breakpoint (name:line[:column]). If no ':' in break
930    // specification it is considered a function break point.
931    pos = target.indexOf(':');
932    if (pos > 0) {
933      var tmp = target.substring(pos + 1, target.length);
934      target = target.substring(0, pos);
935      if (target[0] == '/' && target[target.length - 1] == '/') {
936        type = 'scriptRegExp';
937        target = target.substring(1, target.length - 1);
938      } else {
939        type = 'script';
940      }
941
942      // Check for both line and column.
943      pos = tmp.indexOf(':');
944      if (pos > 0) {
945        column = parseInt(tmp.substring(pos + 1, tmp.length)) - 1;
946        line = parseInt(tmp.substring(0, pos)) - 1;
947      } else {
948        line = parseInt(tmp) - 1;
949      }
950    } else if (target[0] == '#' && target[target.length - 1] == '#') {
951      type = 'handle';
952      target = target.substring(1, target.length - 1);
953    } else {
954      type = 'function';
955    }
956
957    request.arguments = {};
958    request.arguments.type = type;
959    request.arguments.target = target;
960    request.arguments.line = line;
961    request.arguments.column = column;
962    request.arguments.condition = condition;
963  } else {
964    var request = this.createRequest('suspend');
965  }
966
967  return request.toJSONProtocol();
968};
969
970
971DebugRequest.prototype.breakpointsCommandToJSONRequest_ = function(args) {
972  if (args && args.length > 0) {
973    throw new Error('Unexpected arguments.');
974  }
975  var request = this.createRequest('listbreakpoints');
976  return request.toJSONProtocol();
977};
978
979
980// Create a JSON request for the clear command.
981DebugRequest.prototype.clearCommandToJSONRequest_ = function(args) {
982  // Build a evaluate request from the text command.
983  var request = this.createRequest('clearbreakpoint');
984
985  // Process arguments if any.
986  if (args && args.length > 0) {
987    request.arguments = {};
988    request.arguments.breakpoint = parseInt(args);
989  } else {
990    throw new Error('Invalid break arguments.');
991  }
992
993  return request.toJSONProtocol();
994};
995
996
997// Create a JSON request for the change breakpoint command.
998DebugRequest.prototype.changeBreakpointCommandToJSONRequest_ =
999    function(args, command) {
1000
1001  var request;
1002
1003  // Check for exception breaks first:
1004  //   en[able] exc[eptions] [all|unc[aught]]
1005  //   en[able] [all|unc[aught]] exc[eptions]
1006  //   dis[able] exc[eptions] [all|unc[aught]]
1007  //   dis[able] [all|unc[aught]] exc[eptions]
1008  if ((command == 'enable' || command == 'disable') &&
1009      args && args.length > 1) {
1010    var nextPos = args.indexOf(' ');
1011    var arg1 = (nextPos > 0) ? args.substring(0, nextPos) : args;
1012    var excType = null;
1013
1014    // Check for:
1015    //   en[able] exc[eptions] [all|unc[aught]]
1016    //   dis[able] exc[eptions] [all|unc[aught]]
1017    if (arg1 == 'exc' || arg1 == 'exception' || arg1 == 'exceptions') {
1018
1019      var arg2 = (nextPos > 0) ?
1020          args.substring(nextPos + 1, args.length) : 'all';
1021      if (!arg2) {
1022        arg2 = 'all'; // if unspecified, set for all.
1023      } else if (arg2 == 'unc') { // check for short cut.
1024        arg2 = 'uncaught';
1025      }
1026      excType = arg2;
1027
1028    // Check for:
1029    //   en[able] [all|unc[aught]] exc[eptions]
1030    //   dis[able] [all|unc[aught]] exc[eptions]
1031    } else if (arg1 == 'all' || arg1 == 'unc' || arg1 == 'uncaught') {
1032
1033      var arg2 = (nextPos > 0) ?
1034          args.substring(nextPos + 1, args.length) : null;
1035      if (arg2 == 'exc' || arg1 == 'exception' || arg1 == 'exceptions') {
1036        excType = arg1;
1037        if (excType == 'unc') {
1038          excType = 'uncaught';
1039        }
1040      }
1041    }
1042
1043    // If we matched one of the command formats, then excType will be non-null:
1044    if (excType) {
1045      // Build a evaluate request from the text command.
1046      request = this.createRequest('setexceptionbreak');
1047
1048      request.arguments = {};
1049      request.arguments.type = excType;
1050      request.arguments.enabled = (command == 'enable');
1051
1052      return request.toJSONProtocol();
1053    }
1054  }
1055
1056  // Build a evaluate request from the text command.
1057  request = this.createRequest('changebreakpoint');
1058
1059  // Process arguments if any.
1060  if (args && args.length > 0) {
1061    request.arguments = {};
1062    var pos = args.indexOf(' ');
1063    var breakpointArg = args;
1064    var otherArgs;
1065    if (pos > 0) {
1066      breakpointArg = args.substring(0, pos);
1067      otherArgs = args.substring(pos + 1, args.length);
1068    }
1069
1070    request.arguments.breakpoint = parseInt(breakpointArg);
1071
1072    switch(command) {
1073      case 'cond':
1074        request.arguments.condition = otherArgs ? otherArgs : null;
1075        break;
1076      case 'enable':
1077        request.arguments.enabled = true;
1078        break;
1079      case 'disable':
1080        request.arguments.enabled = false;
1081        break;
1082      case 'ignore':
1083        request.arguments.ignoreCount = parseInt(otherArgs);
1084        break;
1085      default:
1086        throw new Error('Invalid arguments.');
1087    }
1088  } else {
1089    throw new Error('Invalid arguments.');
1090  }
1091
1092  return request.toJSONProtocol();
1093};
1094
1095
1096// Create a JSON request for the disconnect command.
1097DebugRequest.prototype.disconnectCommandToJSONRequest_ = function(args) {
1098  var request;
1099  request = this.createRequest('disconnect');
1100  return request.toJSONProtocol();
1101};
1102
1103
1104// Create a JSON request for the info command.
1105DebugRequest.prototype.infoCommandToJSONRequest_ = function(args) {
1106  var request;
1107  if (args && (args == 'break' || args == 'br')) {
1108    // Build a evaluate request from the text command.
1109    request = this.createRequest('listbreakpoints');
1110    last_cmd = 'info break';
1111  } else if (args && (args == 'locals' || args == 'lo')) {
1112    // Build a evaluate request from the text command.
1113    request = this.createRequest('frame');
1114    last_cmd = 'info locals';
1115  } else if (args && (args == 'args' || args == 'ar')) {
1116    // Build a evaluate request from the text command.
1117    request = this.createRequest('frame');
1118    last_cmd = 'info args';
1119  } else {
1120    throw new Error('Invalid info arguments.');
1121  }
1122
1123  return request.toJSONProtocol();
1124};
1125
1126
1127DebugRequest.prototype.v8FlagsToJSONRequest_ = function(args) {
1128  var request;
1129  request = this.createRequest('v8flags');
1130  request.arguments = {};
1131  request.arguments.flags = args;
1132  return request.toJSONProtocol();
1133};
1134
1135
1136DebugRequest.prototype.gcToJSONRequest_ = function(args) {
1137  var request;
1138  if (!args) {
1139    args = 'all';
1140  }
1141  var args = args.split(/\s+/g);
1142  var cmd = args[0];
1143
1144  switch(cmd) {
1145    case 'all':
1146    case 'quick':
1147    case 'full':
1148    case 'young':
1149    case 'old':
1150    case 'compact':
1151    case 'sweep':
1152    case 'scavenge': {
1153      if (cmd == 'young') { cmd = 'quick'; }
1154      else if (cmd == 'old') { cmd = 'full'; }
1155
1156      request = this.createRequest('gc');
1157      request.arguments = {};
1158      request.arguments.type = cmd;
1159      break;
1160    }
1161      // Else fall thru to the default case below to report the error.
1162    default:
1163      throw new Error('Missing arguments after ' + cmd + '.');
1164  }
1165  return request.toJSONProtocol();
1166};
1167
1168
1169// Create a JSON request for the threads command.
1170DebugRequest.prototype.threadsCommandToJSONRequest_ = function(args) {
1171  // Build a threads request from the text command.
1172  var request = this.createRequest('threads');
1173  return request.toJSONProtocol();
1174};
1175
1176
1177// Handle the trace command.
1178DebugRequest.prototype.traceCommand_ = function(args) {
1179  // Process arguments.
1180  if (args && args.length > 0) {
1181    if (args == 'compile') {
1182      trace_compile = !trace_compile;
1183      print('Tracing of compiled scripts ' + (trace_compile ? 'on' : 'off'));
1184    } else if (args === 'debug json' || args === 'json' || args === 'packets') {
1185      trace_debug_json = !trace_debug_json;
1186      print('Tracing of debug json packets ' +
1187            (trace_debug_json ? 'on' : 'off'));
1188    } else {
1189      throw new Error('Invalid trace arguments.');
1190    }
1191  } else {
1192    throw new Error('Invalid trace arguments.');
1193  }
1194};
1195
1196// Handle the help command.
1197DebugRequest.prototype.helpCommand_ = function(args) {
1198  // Help os quite simple.
1199  if (args && args.length > 0) {
1200    print('warning: arguments to \'help\' are ignored');
1201  }
1202
1203  print('Note: <> denotes symbollic values to be replaced with real values.');
1204  print('Note: [] denotes optional parts of commands, or optional options / arguments.');
1205  print('      e.g. d[elete] - you get the same command if you type d or delete.');
1206  print('');
1207  print('[break] - break as soon as possible');
1208  print('b[reak] location [condition]');
1209  print('        - break on named function: location is a function name');
1210  print('        - break on function: location is #<id>#');
1211  print('        - break on script position: location is name:line[:column]');
1212  print('');
1213  print('clear <breakpoint #>       - deletes the specified user defined breakpoint');
1214  print('d[elete]  <breakpoint #>   - deletes the specified user defined breakpoint');
1215  print('dis[able] <breakpoint #>   - disables the specified user defined breakpoint');
1216  print('dis[able] exc[eptions] [[all] | unc[aught]]');
1217  print('                           - disables breaking on exceptions');
1218  print('en[able]  <breakpoint #>   - enables the specified user defined breakpoint');
1219  print('en[able]  exc[eptions] [[all] | unc[aught]]');
1220  print('                           - enables breaking on exceptions');
1221  print('');
1222  print('b[ack]t[race] [n] | [-n] | [from to]');
1223  print('                           - prints the stack back trace');
1224  print('f[rame]                    - prints info about the current frame context');
1225  print('f[rame] <frame #>          - set context to specified frame #');
1226  print('scopes');
1227  print('scope <scope #>');
1228  print('');
1229  print('up                         - set context to caller of current frame');
1230  print('do[wn]                     - set context to callee of current frame');
1231  print('inf[o] br[eak]             - prints info about breakpoints in use');
1232  print('inf[o] ar[gs]              - prints info about arguments of the current function');
1233  print('inf[o] lo[cals]            - prints info about locals in the current function');
1234  print('');
1235  print('step [in | next | out| min [step count]]');
1236  print('c[ontinue]                 - continue executing after a breakpoint');
1237  print('s[tep]   [<N>]             - step into the next N callees (default N is 1)');
1238  print('s[tep]i  [<N>]             - step into the next N callees (default N is 1)');
1239  print('n[ext]   [<N>]             - step over the next N callees (default N is 1)');
1240  print('fin[ish] [<N>]             - step out of N frames (default N is 1)');
1241  print('');
1242  print('p[rint] <expression>       - prints the result of the specified expression');
1243  print('dir <expression>           - prints the object structure of the result');
1244  print('set <var> = <expression>   - executes the specified statement');
1245  print('');
1246  print('l[ist]                     - list the source code around for the current pc');
1247  print('l[ist] [- | <start>,<end>] - list the specified range of source code');
1248  print('source [from line [num lines]]');
1249  print('scr[ipts] [native|extensions|all]');
1250  print('scr[ipts] [<filter text>]  - list scripts with the specified text in its description');
1251  print('');
1252  print('gc                         - runs the garbage collector');
1253  print('');
1254  print('trace compile');
1255  // hidden command: trace debug json - toggles tracing of debug json packets
1256  print('');
1257  print('disconnect|exit|quit       - disconnects and quits the debugger');
1258  print('help                       - prints this help information');
1259};
1260
1261
1262function formatHandleReference_(value) {
1263  if (value.handle() >= 0) {
1264    return '#' + value.handle() + '#';
1265  } else {
1266    return '#Transient#';
1267  }
1268}
1269
1270
1271function formatObject_(value, include_properties) {
1272  var result = '';
1273  result += formatHandleReference_(value);
1274  result += ', type: object';
1275  result += ', constructor ';
1276  var ctor = value.constructorFunctionValue();
1277  result += formatHandleReference_(ctor);
1278  result += ', __proto__ ';
1279  var proto = value.protoObjectValue();
1280  result += formatHandleReference_(proto);
1281  result += ', ';
1282  result += value.propertyCount();
1283  result +=  ' properties.';
1284  if (include_properties) {
1285    result +=  '\n';
1286    for (var i = 0; i < value.propertyCount(); i++) {
1287      result += '  ';
1288      result += value.propertyName(i);
1289      result += ': ';
1290      var property_value = value.propertyValue(i);
1291      if (property_value instanceof ProtocolReference) {
1292        result += '<no type>';
1293      } else {
1294        if (property_value && property_value.type()) {
1295          result += property_value.type();
1296        } else {
1297          result += '<no type>';
1298        }
1299      }
1300      result += ' ';
1301      result += formatHandleReference_(property_value);
1302      result += '\n';
1303    }
1304  }
1305  return result;
1306}
1307
1308
1309function formatScope_(scope) {
1310  var result = '';
1311  var index = scope.index;
1312  result += '#' + (index <= 9 ? '0' : '') + index;
1313  result += ' ';
1314  switch (scope.type) {
1315    case Debug.ScopeType.Global:
1316      result += 'Global, ';
1317      result += '#' + scope.object.ref + '#';
1318      break;
1319    case Debug.ScopeType.Local:
1320      result += 'Local';
1321      break;
1322    case Debug.ScopeType.With:
1323      result += 'With, ';
1324      result += '#' + scope.object.ref + '#';
1325      break;
1326    case Debug.ScopeType.Catch:
1327      result += 'Catch, ';
1328      result += '#' + scope.object.ref + '#';
1329      break;
1330    case Debug.ScopeType.Closure:
1331      result += 'Closure';
1332      break;
1333    default:
1334      result += 'UNKNOWN';
1335  }
1336  return result;
1337}
1338
1339
1340function refObjectToString_(protocolPackage, handle) {
1341  var value = protocolPackage.lookup(handle);
1342  var result = '';
1343  if (value.isString()) {
1344    result = '"' + value.value() + '"';
1345  } else if (value.isPrimitive()) {
1346    result = value.valueString();
1347  } else if (value.isObject()) {
1348    result += formatObject_(value, true);
1349  }
1350  return result;
1351}
1352
1353
1354// Rounds number 'num' to 'length' decimal places.
1355function roundNumber(num, length) {
1356  var factor = Math.pow(10, length);
1357  return Math.round(num * factor) / factor;
1358}
1359
1360
1361// Convert a JSON response to text for display in a text based debugger.
1362function DebugResponseDetails(response) {
1363  var details = { text: '', running: false };
1364
1365  try {
1366    if (!response.success()) {
1367      details.text = response.message();
1368      return details;
1369    }
1370
1371    // Get the running state.
1372    details.running = response.running();
1373
1374    var body = response.body();
1375    var result = '';
1376    switch (response.command()) {
1377      case 'suspend':
1378        details.text = 'stopped';
1379        break;
1380
1381      case 'setbreakpoint':
1382        result = 'set breakpoint #';
1383        result += body.breakpoint;
1384        details.text = result;
1385        break;
1386
1387      case 'clearbreakpoint':
1388        result = 'cleared breakpoint #';
1389        result += body.breakpoint;
1390        details.text = result;
1391        break;
1392
1393      case 'changebreakpoint':
1394        result = 'successfully changed breakpoint';
1395        details.text = result;
1396        break;
1397
1398      case 'listbreakpoints':
1399        result = 'breakpoints: (' + body.breakpoints.length + ')';
1400        for (var i = 0; i < body.breakpoints.length; i++) {
1401          var breakpoint = body.breakpoints[i];
1402          result += '\n id=' + breakpoint.number;
1403          result += ' type=' + breakpoint.type;
1404          if (breakpoint.script_id) {
1405              result += ' script_id=' + breakpoint.script_id;
1406          }
1407          if (breakpoint.script_name) {
1408              result += ' script_name=' + breakpoint.script_name;
1409          }
1410          if (breakpoint.script_regexp) {
1411              result += ' script_regexp=' + breakpoint.script_regexp;
1412          }
1413          result += ' line=' + (breakpoint.line + 1);
1414          if (breakpoint.column != null) {
1415            result += ' column=' + (breakpoint.column + 1);
1416          }
1417          if (breakpoint.groupId) {
1418            result += ' groupId=' + breakpoint.groupId;
1419          }
1420          if (breakpoint.ignoreCount) {
1421              result += ' ignoreCount=' + breakpoint.ignoreCount;
1422          }
1423          if (breakpoint.active === false) {
1424            result += ' inactive';
1425          }
1426          if (breakpoint.condition) {
1427            result += ' condition=' + breakpoint.condition;
1428          }
1429          result += ' hit_count=' + breakpoint.hit_count;
1430        }
1431        if (body.breakpoints.length === 0) {
1432          result = "No user defined breakpoints\n";
1433        } else {
1434          result += '\n';
1435        }
1436        if (body.breakOnExceptions) {
1437          result += '* breaking on ALL exceptions is enabled\n';
1438        } else if (body.breakOnUncaughtExceptions) {
1439          result += '* breaking on UNCAUGHT exceptions is enabled\n';
1440        } else {
1441          result += '* all exception breakpoints are disabled\n';
1442        }
1443        details.text = result;
1444        break;
1445
1446      case 'setexceptionbreak':
1447        result = 'Break on ' + body.type + ' exceptions: ';
1448        result += body.enabled ? 'enabled' : 'disabled';
1449        details.text = result;
1450        break;
1451
1452      case 'backtrace':
1453        if (body.totalFrames == 0) {
1454          result = '(empty stack)';
1455        } else {
1456          var result = 'Frames #' + body.fromFrame + ' to #' +
1457              (body.toFrame - 1) + ' of ' + body.totalFrames + '\n';
1458          for (i = 0; i < body.frames.length; i++) {
1459            if (i != 0) result += '\n';
1460            result += body.frames[i].text;
1461          }
1462        }
1463        details.text = result;
1464        break;
1465
1466      case 'frame':
1467        if (last_cmd === 'info locals') {
1468          var locals = body.locals;
1469          if (locals.length === 0) {
1470            result = 'No locals';
1471          } else {
1472            for (var i = 0; i < locals.length; i++) {
1473              var local = locals[i];
1474              result += local.name + ' = ';
1475              result += refObjectToString_(response, local.value.ref);
1476              result += '\n';
1477            }
1478          }
1479        } else if (last_cmd === 'info args') {
1480          var args = body.arguments;
1481          if (args.length === 0) {
1482            result = 'No arguments';
1483          } else {
1484            for (var i = 0; i < args.length; i++) {
1485              var arg = args[i];
1486              result += arg.name + ' = ';
1487              result += refObjectToString_(response, arg.value.ref);
1488              result += '\n';
1489            }
1490          }
1491        } else {
1492          result = SourceUnderline(body.sourceLineText,
1493                                   body.column);
1494          Debug.State.currentSourceLine = body.line;
1495          Debug.State.currentFrame = body.index;
1496          Debug.State.displaySourceStartLine = -1;
1497          Debug.State.displaySourceEndLine = -1;
1498        }
1499        details.text = result;
1500        break;
1501
1502      case 'scopes':
1503        if (body.totalScopes == 0) {
1504          result = '(no scopes)';
1505        } else {
1506          result = 'Scopes #' + body.fromScope + ' to #' +
1507                   (body.toScope - 1) + ' of ' + body.totalScopes + '\n';
1508          for (i = 0; i < body.scopes.length; i++) {
1509            if (i != 0) {
1510              result += '\n';
1511            }
1512            result += formatScope_(body.scopes[i]);
1513          }
1514        }
1515        details.text = result;
1516        break;
1517
1518      case 'scope':
1519        result += formatScope_(body);
1520        result += '\n';
1521        var scope_object_value = response.lookup(body.object.ref);
1522        result += formatObject_(scope_object_value, true);
1523        details.text = result;
1524        break;
1525
1526      case 'evaluate':
1527      case 'lookup':
1528      case 'getobj':
1529        if (last_cmd == 'p' || last_cmd == 'print') {
1530          result = body.text;
1531        } else {
1532          var value;
1533          if (lookup_handle) {
1534            value = response.bodyValue(lookup_handle);
1535          } else {
1536            value = response.bodyValue();
1537          }
1538          if (value.isObject()) {
1539            result += formatObject_(value, true);
1540          } else {
1541            result += 'type: ';
1542            result += value.type();
1543            if (!value.isUndefined() && !value.isNull()) {
1544              result += ', ';
1545              if (value.isString()) {
1546                result += '"';
1547              }
1548              result += value.value();
1549              if (value.isString()) {
1550                result += '"';
1551              }
1552            }
1553            result += '\n';
1554          }
1555        }
1556        details.text = result;
1557        break;
1558
1559      case 'references':
1560        var count = body.length;
1561        result += 'found ' + count + ' objects';
1562        result += '\n';
1563        for (var i = 0; i < count; i++) {
1564          var value = response.bodyValue(i);
1565          result += formatObject_(value, false);
1566          result += '\n';
1567        }
1568        details.text = result;
1569        break;
1570
1571      case 'source':
1572        // Get the source from the response.
1573        var source = body.source;
1574        var from_line = body.fromLine + 1;
1575        var lines = source.split('\n');
1576        var maxdigits = 1 + Math.floor(log10(from_line + lines.length));
1577        if (maxdigits < 3) {
1578          maxdigits = 3;
1579        }
1580        var result = '';
1581        for (var num = 0; num < lines.length; num++) {
1582          // Check if there's an extra newline at the end.
1583          if (num == (lines.length - 1) && lines[num].length == 0) {
1584            break;
1585          }
1586
1587          var current_line = from_line + num;
1588          var spacer = maxdigits - (1 + Math.floor(log10(current_line)));
1589          if (current_line == Debug.State.currentSourceLine + 1) {
1590            for (var i = 0; i < maxdigits; i++) {
1591              result += '>';
1592            }
1593            result += '  ';
1594          } else {
1595            for (var i = 0; i < spacer; i++) {
1596              result += ' ';
1597            }
1598            result += current_line + ': ';
1599          }
1600          result += lines[num];
1601          result += '\n';
1602        }
1603        details.text = result;
1604        break;
1605
1606      case 'scripts':
1607        var result = '';
1608        for (i = 0; i < body.length; i++) {
1609          if (i != 0) result += '\n';
1610          if (body[i].id) {
1611            result += body[i].id;
1612          } else {
1613            result += '[no id]';
1614          }
1615          result += ', ';
1616          if (body[i].name) {
1617            result += body[i].name;
1618          } else {
1619            if (body[i].compilationType == Debug.ScriptCompilationType.Eval
1620                && body[i].evalFromScript
1621                ) {
1622              result += 'eval from ';
1623              var script_value = response.lookup(body[i].evalFromScript.ref);
1624              result += ' ' + script_value.field('name');
1625              result += ':' + (body[i].evalFromLocation.line + 1);
1626              result += ':' + body[i].evalFromLocation.column;
1627            } else if (body[i].compilationType ==
1628                       Debug.ScriptCompilationType.JSON) {
1629              result += 'JSON ';
1630            } else {  // body[i].compilation == Debug.ScriptCompilationType.Host
1631              result += '[unnamed] ';
1632            }
1633          }
1634          result += ' (lines: ';
1635          result += body[i].lineCount;
1636          result += ', length: ';
1637          result += body[i].sourceLength;
1638          if (body[i].type == Debug.ScriptType.Native) {
1639            result += ', native';
1640          } else if (body[i].type == Debug.ScriptType.Extension) {
1641            result += ', extension';
1642          }
1643          result += '), [';
1644          var sourceStart = body[i].sourceStart;
1645          if (sourceStart.length > 40) {
1646            sourceStart = sourceStart.substring(0, 37) + '...';
1647          }
1648          result += sourceStart;
1649          result += ']';
1650        }
1651        if (body.length == 0) {
1652          result = "no matching scripts found";
1653        }
1654        details.text = result;
1655        break;
1656
1657      case 'threads':
1658        var result = 'Active V8 threads: ' + body.totalThreads + '\n';
1659        body.threads.sort(function(a, b) { return a.id - b.id; });
1660        for (i = 0; i < body.threads.length; i++) {
1661          result += body.threads[i].current ? '*' : ' ';
1662          result += ' ';
1663          result += body.threads[i].id;
1664          result += '\n';
1665        }
1666        details.text = result;
1667        break;
1668
1669      case 'continue':
1670        details.text = "(running)";
1671        break;
1672
1673      case 'v8flags':
1674        details.text = "flags set";
1675        break;
1676
1677      case 'gc':
1678        details.text = "GC " + body.before + " => " + body.after;
1679        if (body.after > (1024*1024)) {
1680          details.text +=
1681              " (" + roundNumber(body.before/(1024*1024), 1) + "M => " +
1682                     roundNumber(body.after/(1024*1024), 1) + "M)";
1683        } else if (body.after > 1024) {
1684          details.text +=
1685              " (" + roundNumber(body.before/1024, 1) + "K => " +
1686                     roundNumber(body.after/1024, 1) + "K)";
1687        }
1688        break;
1689
1690      default:
1691        details.text =
1692            'Response for unknown command \'' + response.command() + '\'' +
1693            ' (' + response.raw_json() + ')';
1694    }
1695  } catch (e) {
1696    details.text = 'Error: "' + e + '" formatting response';
1697  }
1698
1699  return details;
1700}
1701
1702
1703/**
1704 * Protocol packages send from the debugger.
1705 * @param {string} json - raw protocol packet as JSON string.
1706 * @constructor
1707 */
1708function ProtocolPackage(json) {
1709  this.raw_json_ = json;
1710  this.packet_ = JSON.parse(json);
1711  this.refs_ = [];
1712  if (this.packet_.refs) {
1713    for (var i = 0; i < this.packet_.refs.length; i++) {
1714      this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i];
1715    }
1716  }
1717}
1718
1719
1720/**
1721 * Get the packet type.
1722 * @return {String} the packet type
1723 */
1724ProtocolPackage.prototype.type = function() {
1725  return this.packet_.type;
1726};
1727
1728
1729/**
1730 * Get the packet event.
1731 * @return {Object} the packet event
1732 */
1733ProtocolPackage.prototype.event = function() {
1734  return this.packet_.event;
1735};
1736
1737
1738/**
1739 * Get the packet request sequence.
1740 * @return {number} the packet request sequence
1741 */
1742ProtocolPackage.prototype.requestSeq = function() {
1743  return this.packet_.request_seq;
1744};
1745
1746
1747/**
1748 * Get the packet request sequence.
1749 * @return {number} the packet request sequence
1750 */
1751ProtocolPackage.prototype.running = function() {
1752  return this.packet_.running ? true : false;
1753};
1754
1755
1756ProtocolPackage.prototype.success = function() {
1757  return this.packet_.success ? true : false;
1758};
1759
1760
1761ProtocolPackage.prototype.message = function() {
1762  return this.packet_.message;
1763};
1764
1765
1766ProtocolPackage.prototype.command = function() {
1767  return this.packet_.command;
1768};
1769
1770
1771ProtocolPackage.prototype.body = function() {
1772  return this.packet_.body;
1773};
1774
1775
1776ProtocolPackage.prototype.bodyValue = function(index) {
1777  if (index != null) {
1778    return new ProtocolValue(this.packet_.body[index], this);
1779  } else {
1780    return new ProtocolValue(this.packet_.body, this);
1781  }
1782};
1783
1784
1785ProtocolPackage.prototype.body = function() {
1786  return this.packet_.body;
1787};
1788
1789
1790ProtocolPackage.prototype.lookup = function(handle) {
1791  var value = this.refs_[handle];
1792  if (value) {
1793    return new ProtocolValue(value, this);
1794  } else {
1795    return new ProtocolReference(handle);
1796  }
1797};
1798
1799
1800ProtocolPackage.prototype.raw_json = function() {
1801  return this.raw_json_;
1802};
1803
1804
1805function ProtocolValue(value, packet) {
1806  this.value_ = value;
1807  this.packet_ = packet;
1808}
1809
1810
1811/**
1812 * Get the value type.
1813 * @return {String} the value type
1814 */
1815ProtocolValue.prototype.type = function() {
1816  return this.value_.type;
1817};
1818
1819
1820/**
1821 * Get a metadata field from a protocol value.
1822 * @return {Object} the metadata field value
1823 */
1824ProtocolValue.prototype.field = function(name) {
1825  return this.value_[name];
1826};
1827
1828
1829/**
1830 * Check is the value is a primitive value.
1831 * @return {boolean} true if the value is primitive
1832 */
1833ProtocolValue.prototype.isPrimitive = function() {
1834  return this.isUndefined() || this.isNull() || this.isBoolean() ||
1835         this.isNumber() || this.isString();
1836};
1837
1838
1839/**
1840 * Get the object handle.
1841 * @return {number} the value handle
1842 */
1843ProtocolValue.prototype.handle = function() {
1844  return this.value_.handle;
1845};
1846
1847
1848/**
1849 * Check is the value is undefined.
1850 * @return {boolean} true if the value is undefined
1851 */
1852ProtocolValue.prototype.isUndefined = function() {
1853  return this.value_.type == 'undefined';
1854};
1855
1856
1857/**
1858 * Check is the value is null.
1859 * @return {boolean} true if the value is null
1860 */
1861ProtocolValue.prototype.isNull = function() {
1862  return this.value_.type == 'null';
1863};
1864
1865
1866/**
1867 * Check is the value is a boolean.
1868 * @return {boolean} true if the value is a boolean
1869 */
1870ProtocolValue.prototype.isBoolean = function() {
1871  return this.value_.type == 'boolean';
1872};
1873
1874
1875/**
1876 * Check is the value is a number.
1877 * @return {boolean} true if the value is a number
1878 */
1879ProtocolValue.prototype.isNumber = function() {
1880  return this.value_.type == 'number';
1881};
1882
1883
1884/**
1885 * Check is the value is a string.
1886 * @return {boolean} true if the value is a string
1887 */
1888ProtocolValue.prototype.isString = function() {
1889  return this.value_.type == 'string';
1890};
1891
1892
1893/**
1894 * Check is the value is an object.
1895 * @return {boolean} true if the value is an object
1896 */
1897ProtocolValue.prototype.isObject = function() {
1898  return this.value_.type == 'object' || this.value_.type == 'function' ||
1899         this.value_.type == 'error' || this.value_.type == 'regexp';
1900};
1901
1902
1903/**
1904 * Get the constructor function
1905 * @return {ProtocolValue} constructor function
1906 */
1907ProtocolValue.prototype.constructorFunctionValue = function() {
1908  var ctor = this.value_.constructorFunction;
1909  return this.packet_.lookup(ctor.ref);
1910};
1911
1912
1913/**
1914 * Get the __proto__ value
1915 * @return {ProtocolValue} __proto__ value
1916 */
1917ProtocolValue.prototype.protoObjectValue = function() {
1918  var proto = this.value_.protoObject;
1919  return this.packet_.lookup(proto.ref);
1920};
1921
1922
1923/**
1924 * Get the number og properties.
1925 * @return {number} the number of properties
1926 */
1927ProtocolValue.prototype.propertyCount = function() {
1928  return this.value_.properties ? this.value_.properties.length : 0;
1929};
1930
1931
1932/**
1933 * Get the specified property name.
1934 * @return {string} property name
1935 */
1936ProtocolValue.prototype.propertyName = function(index) {
1937  var property = this.value_.properties[index];
1938  return property.name;
1939};
1940
1941
1942/**
1943 * Return index for the property name.
1944 * @param name The property name to look for
1945 * @return {number} index for the property name
1946 */
1947ProtocolValue.prototype.propertyIndex = function(name) {
1948  for (var i = 0; i < this.propertyCount(); i++) {
1949    if (this.value_.properties[i].name == name) {
1950      return i;
1951    }
1952  }
1953  return null;
1954};
1955
1956
1957/**
1958 * Get the specified property value.
1959 * @return {ProtocolValue} property value
1960 */
1961ProtocolValue.prototype.propertyValue = function(index) {
1962  var property = this.value_.properties[index];
1963  return this.packet_.lookup(property.ref);
1964};
1965
1966
1967/**
1968 * Check is the value is a string.
1969 * @return {boolean} true if the value is a string
1970 */
1971ProtocolValue.prototype.value = function() {
1972  return this.value_.value;
1973};
1974
1975
1976ProtocolValue.prototype.valueString = function() {
1977  return this.value_.text;
1978};
1979
1980
1981function ProtocolReference(handle) {
1982  this.handle_ = handle;
1983}
1984
1985
1986ProtocolReference.prototype.handle = function() {
1987  return this.handle_;
1988};
1989
1990
1991function MakeJSONPair_(name, value) {
1992  return '"' + name + '":' + value;
1993}
1994
1995
1996function ArrayToJSONObject_(content) {
1997  return '{' + content.join(',') + '}';
1998}
1999
2000
2001function ArrayToJSONArray_(content) {
2002  return '[' + content.join(',') + ']';
2003}
2004
2005
2006function BooleanToJSON_(value) {
2007  return String(value);
2008}
2009
2010
2011function NumberToJSON_(value) {
2012  return String(value);
2013}
2014
2015
2016// Mapping of some control characters to avoid the \uXXXX syntax for most
2017// commonly used control cahracters.
2018var ctrlCharMap_ = {
2019  '\b': '\\b',
2020  '\t': '\\t',
2021  '\n': '\\n',
2022  '\f': '\\f',
2023  '\r': '\\r',
2024  '"' : '\\"',
2025  '\\': '\\\\'
2026};
2027
2028
2029// Regular expression testing for ", \ and control characters (0x00 - 0x1F).
2030var ctrlCharTest_ = new RegExp('["\\\\\x00-\x1F]');
2031
2032
2033// Regular expression matching ", \ and control characters (0x00 - 0x1F)
2034// globally.
2035var ctrlCharMatch_ = new RegExp('["\\\\\x00-\x1F]', 'g');
2036
2037
2038/**
2039 * Convert a String to its JSON representation (see http://www.json.org/). To
2040 * avoid depending on the String object this method calls the functions in
2041 * string.js directly and not through the value.
2042 * @param {String} value The String value to format as JSON
2043 * @return {string} JSON formatted String value
2044 */
2045function StringToJSON_(value) {
2046  // Check for" , \ and control characters (0x00 - 0x1F). No need to call
2047  // RegExpTest as ctrlchar is constructed using RegExp.
2048  if (ctrlCharTest_.test(value)) {
2049    // Replace ", \ and control characters (0x00 - 0x1F).
2050    return '"' +
2051      value.replace(ctrlCharMatch_, function (char) {
2052        // Use charmap if possible.
2053        var mapped = ctrlCharMap_[char];
2054        if (mapped) return mapped;
2055        mapped = char.charCodeAt();
2056        // Convert control character to unicode escape sequence.
2057        return '\\u00' +
2058          '0' + // TODO %NumberToRadixString(Math.floor(mapped / 16), 16) +
2059          '0'; // TODO %NumberToRadixString(mapped % 16, 16)
2060      })
2061    + '"';
2062  }
2063
2064  // Simple string with no special characters.
2065  return '"' + value + '"';
2066}
2067
2068
2069/**
2070 * Convert a Date to ISO 8601 format. To avoid depending on the Date object
2071 * this method calls the functions in date.js directly and not through the
2072 * value.
2073 * @param {Date} value The Date value to format as JSON
2074 * @return {string} JSON formatted Date value
2075 */
2076function DateToISO8601_(value) {
2077  var f = function(n) {
2078    return n < 10 ? '0' + n : n;
2079  };
2080  var g = function(n) {
2081    return n < 10 ? '00' + n : n < 100 ? '0' + n : n;
2082  };
2083  return builtins.GetUTCFullYearFrom(value)         + '-' +
2084          f(builtins.GetUTCMonthFrom(value) + 1)    + '-' +
2085          f(builtins.GetUTCDateFrom(value))         + 'T' +
2086          f(builtins.GetUTCHoursFrom(value))        + ':' +
2087          f(builtins.GetUTCMinutesFrom(value))      + ':' +
2088          f(builtins.GetUTCSecondsFrom(value))      + '.' +
2089          g(builtins.GetUTCMillisecondsFrom(value)) + 'Z';
2090}
2091
2092
2093/**
2094 * Convert a Date to ISO 8601 format. To avoid depending on the Date object
2095 * this method calls the functions in date.js directly and not through the
2096 * value.
2097 * @param {Date} value The Date value to format as JSON
2098 * @return {string} JSON formatted Date value
2099 */
2100function DateToJSON_(value) {
2101  return '"' + DateToISO8601_(value) + '"';
2102}
2103
2104
2105/**
2106 * Convert an Object to its JSON representation (see http://www.json.org/).
2107 * This implementation simply runs through all string property names and adds
2108 * each property to the JSON representation for some predefined types. For type
2109 * "object" the function calls itself recursively unless the object has the
2110 * function property "toJSONProtocol" in which case that is used. This is not
2111 * a general implementation but sufficient for the debugger. Note that circular
2112 * structures will cause infinite recursion.
2113 * @param {Object} object The object to format as JSON
2114 * @return {string} JSON formatted object value
2115 */
2116function SimpleObjectToJSON_(object) {
2117  var content = [];
2118  for (var key in object) {
2119    // Only consider string keys.
2120    if (typeof key == 'string') {
2121      var property_value = object[key];
2122
2123      // Format the value based on its type.
2124      var property_value_json;
2125      switch (typeof property_value) {
2126        case 'object':
2127          if (property_value === null) {
2128            property_value_json = 'null';
2129          } else if (typeof property_value.toJSONProtocol == 'function') {
2130            property_value_json = property_value.toJSONProtocol(true);
2131          } else if (property_value.constructor.name == 'Array'){
2132            property_value_json = SimpleArrayToJSON_(property_value);
2133          } else {
2134            property_value_json = SimpleObjectToJSON_(property_value);
2135          }
2136          break;
2137
2138        case 'boolean':
2139          property_value_json = BooleanToJSON_(property_value);
2140          break;
2141
2142        case 'number':
2143          property_value_json = NumberToJSON_(property_value);
2144          break;
2145
2146        case 'string':
2147          property_value_json = StringToJSON_(property_value);
2148          break;
2149
2150        default:
2151          property_value_json = null;
2152      }
2153
2154      // Add the property if relevant.
2155      if (property_value_json) {
2156        content.push(StringToJSON_(key) + ':' + property_value_json);
2157      }
2158    }
2159  }
2160
2161  // Make JSON object representation.
2162  return '{' + content.join(',') + '}';
2163}
2164
2165
2166/**
2167 * Convert an array to its JSON representation. This is a VERY simple
2168 * implementation just to support what is needed for the debugger.
2169 * @param {Array} arrya The array to format as JSON
2170 * @return {string} JSON formatted array value
2171 */
2172function SimpleArrayToJSON_(array) {
2173  // Make JSON array representation.
2174  var json = '[';
2175  for (var i = 0; i < array.length; i++) {
2176    if (i != 0) {
2177      json += ',';
2178    }
2179    var elem = array[i];
2180    if (elem.toJSONProtocol) {
2181      json += elem.toJSONProtocol(true);
2182    } else if (typeof(elem) === 'object')  {
2183      json += SimpleObjectToJSON_(elem);
2184    } else if (typeof(elem) === 'boolean')  {
2185      json += BooleanToJSON_(elem);
2186    } else if (typeof(elem) === 'number')  {
2187      json += NumberToJSON_(elem);
2188    } else if (typeof(elem) === 'string')  {
2189      json += StringToJSON_(elem);
2190    } else {
2191      json += elem;
2192    }
2193  }
2194  json += ']';
2195  return json;
2196}
2197
2198
2199// A more universal stringify that supports more types than JSON.
2200// Used by the d8 shell to output results.
2201var stringifyDepthLimit = 4;  // To avoid crashing on cyclic objects
2202
2203function Stringify(x, depth) {
2204  if (depth === undefined)
2205    depth = stringifyDepthLimit;
2206  else if (depth === 0)
2207    return "*";
2208  switch (typeof x) {
2209    case "undefined":
2210      return "undefined";
2211    case "boolean":
2212    case "number":
2213    case "function":
2214      return x.toString();
2215    case "string":
2216      return "\"" + x.toString() + "\"";
2217    case "symbol":
2218      return "Symbol(" + (x.name ? Stringify(x.name, depth) : "") + ")"
2219    case "object":
2220      if (x === null) return "null";
2221      if (x.constructor && x.constructor.name === "Array") {
2222        var elems = [];
2223        for (var i = 0; i < x.length; ++i) {
2224          elems.push(
2225            {}.hasOwnProperty.call(x, i) ? Stringify(x[i], depth - 1) : "");
2226        }
2227        return "[" + elems.join(", ") + "]";
2228      }
2229      try {
2230        var string = String(x);
2231        if (string && string !== "[object Object]") return string;
2232      } catch(e) {}
2233      var props = [];
2234      for (var name in x) {
2235        var desc = Object.getOwnPropertyDescriptor(x, name);
2236        if (desc === void 0) continue;
2237        if ("value" in desc) {
2238          props.push(name + ": " + Stringify(desc.value, depth - 1));
2239        }
2240        if ("get" in desc) {
2241          var getter = desc.get.toString();
2242          props.push("get " + name + getter.slice(getter.indexOf('(')));
2243        }
2244        if ("set" in desc) {
2245          var setter = desc.set.toString();
2246          props.push("set " + name + setter.slice(setter.indexOf('(')));
2247        }
2248      }
2249      return "{" + props.join(", ") + "}";
2250    default:
2251      return "[crazy non-standard shit]";
2252  }
2253}
2254