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
28String.prototype.startsWith = function (str) {
29  if (str.length > this.length)
30    return false;
31  return this.substr(0, str.length) == str;
32}
33
34function log10(num) {
35  return Math.log(num)/Math.log(10);
36}
37
38function ToInspectableObject(obj) {
39  if (!obj && typeof obj === 'object') {
40    return void 0;
41  } else {
42    return Object(obj);
43  }
44}
45
46function GetCompletions(global, last, full) {
47  var full_tokens = full.split();
48  full = full_tokens.pop();
49  var parts = full.split('.');
50  parts.pop();
51  var current = global;
52  for (var i = 0; i < parts.length; i++) {
53    var part = parts[i];
54    var next = current[part];
55    if (!next)
56      return [];
57    current = next;
58  }
59  var result = [];
60  current = ToInspectableObject(current);
61  while (typeof current !== 'undefined') {
62    var mirror = new $debug.ObjectMirror(current);
63    var properties = mirror.properties();
64    for (var i = 0; i < properties.length; i++) {
65      var name = properties[i].name();
66      if (typeof name === 'string' && name.startsWith(last))
67        result.push(name);
68    }
69    current = ToInspectableObject(current.__proto__);
70  }
71  return result;
72}
73
74
75// Global object holding debugger related constants and state.
76const Debug = {};
77
78
79// Debug events which can occour in the V8 JavaScript engine. These originate
80// from the API include file v8-debug.h.
81Debug.DebugEvent = { Break: 1,
82                     Exception: 2,
83                     NewFunction: 3,
84                     BeforeCompile: 4,
85                     AfterCompile: 5 };
86
87
88// The different types of scripts matching enum ScriptType in objects.h.
89Debug.ScriptType = { Native: 0,
90                     Extension: 1,
91                     Normal: 2 };
92
93
94// The different types of script compilations matching enum
95// Script::CompilationType in objects.h.
96Debug.ScriptCompilationType = { Host: 0,
97                                Eval: 1,
98                                JSON: 2 };
99
100
101// The different types of scopes matching constants runtime.cc.
102Debug.ScopeType = { Global: 0,
103                    Local: 1,
104                    With: 2,
105                    Closure: 3,
106                    Catch: 4 };
107
108
109// Current debug state.
110const kNoFrame = -1;
111Debug.State = {
112  currentFrame: kNoFrame,
113  currentSourceLine: -1
114}
115var trace_compile = false;  // Tracing all compile events?
116
117
118// Process a debugger JSON message into a display text and a running status.
119// This function returns an object with properties "text" and "running" holding
120// this information.
121function DebugMessageDetails(message) {
122  // Convert the JSON string to an object.
123  var response = new ProtocolPackage(message);
124
125  if (response.type() == 'event') {
126    return DebugEventDetails(response);
127  } else {
128    return DebugResponseDetails(response);
129  }
130}
131
132function DebugEventDetails(response) {
133  details = {text:'', running:false}
134
135  // Get the running state.
136  details.running = response.running();
137
138  var body = response.body();
139  var result = '';
140  switch (response.event()) {
141    case 'break':
142      if (body.breakpoints) {
143        result += 'breakpoint';
144        if (body.breakpoints.length > 1) {
145          result += 's';
146        }
147        result += ' #';
148        for (var i = 0; i < body.breakpoints.length; i++) {
149          if (i > 0) {
150            result += ', #';
151          }
152          result += body.breakpoints[i];
153        }
154      } else {
155        result += 'break';
156      }
157      result += ' in ';
158      result += body.invocationText;
159      result += ', ';
160      result += SourceInfo(body);
161      result += '\n';
162      result += SourceUnderline(body.sourceLineText, body.sourceColumn);
163      Debug.State.currentSourceLine = body.sourceLine;
164      Debug.State.currentFrame = 0;
165      details.text = result;
166      break;
167
168    case 'exception':
169      if (body.uncaught) {
170        result += 'Uncaught: ';
171      } else {
172        result += 'Exception: ';
173      }
174      result += '"';
175      result += body.exception.text;
176      result += '"';
177      if (body.sourceLine >= 0) {
178        result += ', ';
179        result += SourceInfo(body);
180        result += '\n';
181        result += SourceUnderline(body.sourceLineText, body.sourceColumn);
182        Debug.State.currentSourceLine = body.sourceLine;
183        Debug.State.currentFrame = 0;
184      } else {
185        result += ' (empty stack)';
186        Debug.State.currentSourceLine = -1;
187        Debug.State.currentFrame = kNoFrame;
188      }
189      details.text = result;
190      break;
191
192    case 'afterCompile':
193      if (trace_compile) {
194        result = 'Source ' + body.script.name + ' compiled:\n'
195        var source = body.script.source;
196        if (!(source[source.length - 1] == '\n')) {
197          result += source;
198        } else {
199          result += source.substring(0, source.length - 1);
200        }
201      }
202      details.text = result;
203      break;
204
205    default:
206      details.text = 'Unknown debug event ' + response.event();
207  }
208
209  return details;
210};
211
212
213function SourceInfo(body) {
214  var result = '';
215
216  if (body.script) {
217    if (body.script.name) {
218      result += body.script.name;
219    } else {
220      result += '[unnamed]';
221    }
222  }
223  result += ' line ';
224  result += body.sourceLine + 1;
225  result += ' column ';
226  result += body.sourceColumn + 1;
227
228  return result;
229}
230
231
232function SourceUnderline(source_text, position) {
233  if (!source_text) {
234    return;
235  }
236
237  // Create an underline with a caret pointing to the source position. If the
238  // source contains a tab character the underline will have a tab character in
239  // the same place otherwise the underline will have a space character.
240  var underline = '';
241  for (var i = 0; i < position; i++) {
242    if (source_text[i] == '\t') {
243      underline += '\t';
244    } else {
245      underline += ' ';
246    }
247  }
248  underline += '^';
249
250  // Return the source line text with the underline beneath.
251  return source_text + '\n' + underline;
252};
253
254
255// Converts a text command to a JSON request.
256function DebugCommandToJSONRequest(cmd_line) {
257  return new DebugRequest(cmd_line).JSONRequest();
258};
259
260
261function DebugRequest(cmd_line) {
262  // If the very first character is a { assume that a JSON request have been
263  // entered as a command. Converting that to a JSON request is trivial.
264  if (cmd_line && cmd_line.length > 0 && cmd_line.charAt(0) == '{') {
265    this.request_ = cmd_line;
266    return;
267  }
268
269  // Trim string for leading and trailing whitespace.
270  cmd_line = cmd_line.replace(/^\s+|\s+$/g, '');
271
272  // Find the command.
273  var pos = cmd_line.indexOf(' ');
274  var cmd;
275  var args;
276  if (pos == -1) {
277    cmd = cmd_line;
278    args = '';
279  } else {
280    cmd = cmd_line.slice(0, pos);
281    args = cmd_line.slice(pos).replace(/^\s+|\s+$/g, '');
282  }
283
284  // Switch on command.
285  switch (cmd) {
286    case 'continue':
287    case 'c':
288      this.request_ = this.continueCommandToJSONRequest_(args);
289      break;
290
291    case 'step':
292    case 's':
293      this.request_ = this.stepCommandToJSONRequest_(args);
294      break;
295
296    case 'backtrace':
297    case 'bt':
298      this.request_ = this.backtraceCommandToJSONRequest_(args);
299      break;
300
301    case 'frame':
302    case 'f':
303      this.request_ = this.frameCommandToJSONRequest_(args);
304      break;
305
306    case 'scopes':
307      this.request_ = this.scopesCommandToJSONRequest_(args);
308      break;
309
310    case 'scope':
311      this.request_ = this.scopeCommandToJSONRequest_(args);
312      break;
313
314    case 'print':
315    case 'p':
316      this.request_ = this.printCommandToJSONRequest_(args);
317      break;
318
319    case 'dir':
320      this.request_ = this.dirCommandToJSONRequest_(args);
321      break;
322
323    case 'references':
324      this.request_ = this.referencesCommandToJSONRequest_(args);
325      break;
326
327    case 'instances':
328      this.request_ = this.instancesCommandToJSONRequest_(args);
329      break;
330
331    case 'source':
332      this.request_ = this.sourceCommandToJSONRequest_(args);
333      break;
334
335    case 'scripts':
336      this.request_ = this.scriptsCommandToJSONRequest_(args);
337      break;
338
339    case 'break':
340    case 'b':
341      this.request_ = this.breakCommandToJSONRequest_(args);
342      break;
343
344    case 'clear':
345      this.request_ = this.clearCommandToJSONRequest_(args);
346      break;
347
348    case 'threads':
349      this.request_ = this.threadsCommandToJSONRequest_(args);
350      break;
351
352    case 'trace':
353      // Return undefined to indicate command handled internally (no JSON).
354      this.request_ = void 0;
355      this.traceCommand_(args);
356      break;
357
358    case 'help':
359    case '?':
360      this.helpCommand_(args);
361      // Return undefined to indicate command handled internally (no JSON).
362      this.request_ = void 0;
363      break;
364
365    default:
366      throw new Error('Unknown command "' + cmd + '"');
367  }
368
369  last_cmd = cmd;
370}
371
372DebugRequest.prototype.JSONRequest = function() {
373  return this.request_;
374}
375
376
377function RequestPacket(command) {
378  this.seq = 0;
379  this.type = 'request';
380  this.command = command;
381}
382
383
384RequestPacket.prototype.toJSONProtocol = function() {
385  // Encode the protocol header.
386  var json = '{';
387  json += '"seq":' + this.seq;
388  json += ',"type":"' + this.type + '"';
389  if (this.command) {
390    json += ',"command":' + StringToJSON_(this.command);
391  }
392  if (this.arguments) {
393    json += ',"arguments":';
394    // Encode the arguments part.
395    if (this.arguments.toJSONProtocol) {
396      json += this.arguments.toJSONProtocol()
397    } else {
398      json += SimpleObjectToJSON_(this.arguments);
399    }
400  }
401  json += '}';
402  return json;
403}
404
405
406DebugRequest.prototype.createRequest = function(command) {
407  return new RequestPacket(command);
408};
409
410
411// Create a JSON request for the evaluation command.
412DebugRequest.prototype.makeEvaluateJSONRequest_ = function(expression) {
413  // Global varaible used to store whether a handle was requested.
414  lookup_handle = null;
415  // Check if the expression is a handle id in the form #<handle>#.
416  var handle_match = expression.match(/^#([0-9]*)#$/);
417  if (handle_match) {
418    // Remember the handle requested in a global variable.
419    lookup_handle = parseInt(handle_match[1]);
420    // Build a lookup request.
421    var request = this.createRequest('lookup');
422    request.arguments = {};
423    request.arguments.handles = [ lookup_handle ];
424    return request.toJSONProtocol();
425  } else {
426    // Build an evaluate request.
427    var request = this.createRequest('evaluate');
428    request.arguments = {};
429    request.arguments.expression = expression;
430    // Request a global evaluation if there is no current frame.
431    if (Debug.State.currentFrame == kNoFrame) {
432      request.arguments.global = true;
433    }
434    return request.toJSONProtocol();
435  }
436};
437
438
439// Create a JSON request for the references/instances command.
440DebugRequest.prototype.makeReferencesJSONRequest_ = function(handle, type) {
441  // Build a references request.
442  var handle_match = handle.match(/^#([0-9]*)#$/);
443  if (handle_match) {
444    var request = this.createRequest('references');
445    request.arguments = {};
446    request.arguments.type = type;
447    request.arguments.handle = parseInt(handle_match[1]);
448    return request.toJSONProtocol();
449  } else {
450    throw new Error('Invalid object id.');
451  }
452};
453
454
455// Create a JSON request for the continue command.
456DebugRequest.prototype.continueCommandToJSONRequest_ = function(args) {
457  var request = this.createRequest('continue');
458  return request.toJSONProtocol();
459};
460
461
462// Create a JSON request for the step command.
463DebugRequest.prototype.stepCommandToJSONRequest_ = function(args) {
464  // Requesting a step is through the continue command with additional
465  // arguments.
466  var request = this.createRequest('continue');
467  request.arguments = {};
468
469  // Process arguments if any.
470  if (args && args.length > 0) {
471    args = args.split(/\s*[ ]+\s*/g);
472
473    if (args.length > 2) {
474      throw new Error('Invalid step arguments.');
475    }
476
477    if (args.length > 0) {
478      // Get step count argument if any.
479      if (args.length == 2) {
480        var stepcount = parseInt(args[1]);
481        if (isNaN(stepcount) || stepcount <= 0) {
482          throw new Error('Invalid step count argument "' + args[0] + '".');
483        }
484        request.arguments.stepcount = stepcount;
485      }
486
487      // Get the step action.
488      switch (args[0]) {
489        case 'in':
490        case 'i':
491          request.arguments.stepaction = 'in';
492          break;
493
494        case 'min':
495        case 'm':
496          request.arguments.stepaction = 'min';
497          break;
498
499        case 'next':
500        case 'n':
501          request.arguments.stepaction = 'next';
502          break;
503
504        case 'out':
505        case 'o':
506          request.arguments.stepaction = 'out';
507          break;
508
509        default:
510          throw new Error('Invalid step argument "' + args[0] + '".');
511      }
512    }
513  } else {
514    // Default is step next.
515    request.arguments.stepaction = 'next';
516  }
517
518  return request.toJSONProtocol();
519};
520
521
522// Create a JSON request for the backtrace command.
523DebugRequest.prototype.backtraceCommandToJSONRequest_ = function(args) {
524  // Build a backtrace request from the text command.
525  var request = this.createRequest('backtrace');
526
527  // Default is to show top 10 frames.
528  request.arguments = {};
529  request.arguments.fromFrame = 0;
530  request.arguments.toFrame = 10;
531
532  args = args.split(/\s*[ ]+\s*/g);
533  if (args.length == 1 && args[0].length > 0) {
534    var frameCount = parseInt(args[0]);
535    if (frameCount > 0) {
536      // Show top frames.
537      request.arguments.fromFrame = 0;
538      request.arguments.toFrame = frameCount;
539    } else {
540      // Show bottom frames.
541      request.arguments.fromFrame = 0;
542      request.arguments.toFrame = -frameCount;
543      request.arguments.bottom = true;
544    }
545  } else if (args.length == 2) {
546    var fromFrame = parseInt(args[0]);
547    var toFrame = parseInt(args[1]);
548    if (isNaN(fromFrame) || fromFrame < 0) {
549      throw new Error('Invalid start frame argument "' + args[0] + '".');
550    }
551    if (isNaN(toFrame) || toFrame < 0) {
552      throw new Error('Invalid end frame argument "' + args[1] + '".');
553    }
554    if (fromFrame > toFrame) {
555      throw new Error('Invalid arguments start frame cannot be larger ' +
556                      'than end frame.');
557    }
558    // Show frame range.
559    request.arguments.fromFrame = fromFrame;
560    request.arguments.toFrame = toFrame + 1;
561  } else if (args.length > 2) {
562    throw new Error('Invalid backtrace arguments.');
563  }
564
565  return request.toJSONProtocol();
566};
567
568
569// Create a JSON request for the frame command.
570DebugRequest.prototype.frameCommandToJSONRequest_ = function(args) {
571  // Build a frame request from the text command.
572  var request = this.createRequest('frame');
573  args = args.split(/\s*[ ]+\s*/g);
574  if (args.length > 0 && args[0].length > 0) {
575    request.arguments = {};
576    request.arguments.number = args[0];
577  }
578  return request.toJSONProtocol();
579};
580
581
582// Create a JSON request for the scopes command.
583DebugRequest.prototype.scopesCommandToJSONRequest_ = function(args) {
584  // Build a scopes request from the text command.
585  var request = this.createRequest('scopes');
586  return request.toJSONProtocol();
587};
588
589
590// Create a JSON request for the scope command.
591DebugRequest.prototype.scopeCommandToJSONRequest_ = function(args) {
592  // Build a scope request from the text command.
593  var request = this.createRequest('scope');
594  args = args.split(/\s*[ ]+\s*/g);
595  if (args.length > 0 && args[0].length > 0) {
596    request.arguments = {};
597    request.arguments.number = args[0];
598  }
599  return request.toJSONProtocol();
600};
601
602
603// Create a JSON request for the print command.
604DebugRequest.prototype.printCommandToJSONRequest_ = function(args) {
605  // Build an evaluate request from the text command.
606  if (args.length == 0) {
607    throw new Error('Missing expression.');
608  }
609  return this.makeEvaluateJSONRequest_(args);
610};
611
612
613// Create a JSON request for the dir command.
614DebugRequest.prototype.dirCommandToJSONRequest_ = function(args) {
615  // Build an evaluate request from the text command.
616  if (args.length == 0) {
617    throw new Error('Missing expression.');
618  }
619  return this.makeEvaluateJSONRequest_(args);
620};
621
622
623// Create a JSON request for the references command.
624DebugRequest.prototype.referencesCommandToJSONRequest_ = function(args) {
625  // Build an evaluate request from the text command.
626  if (args.length == 0) {
627    throw new Error('Missing object id.');
628  }
629
630  return this.makeReferencesJSONRequest_(args, 'referencedBy');
631};
632
633
634// Create a JSON request for the instances command.
635DebugRequest.prototype.instancesCommandToJSONRequest_ = function(args) {
636  // Build an evaluate request from the text command.
637  if (args.length == 0) {
638    throw new Error('Missing object id.');
639  }
640
641  // Build a references request.
642  return this.makeReferencesJSONRequest_(args, 'constructedBy');
643};
644
645
646// Create a JSON request for the source command.
647DebugRequest.prototype.sourceCommandToJSONRequest_ = function(args) {
648  // Build a evaluate request from the text command.
649  var request = this.createRequest('source');
650
651  // Default is ten lines starting five lines before the current location.
652  var from = Debug.State.currentSourceLine - 5;
653  var lines = 10;
654
655  // Parse the arguments.
656  args = args.split(/\s*[ ]+\s*/g);
657  if (args.length > 1 && args[0].length > 0 && args[1].length > 0) {
658    from = parseInt(args[0]) - 1;
659    lines = parseInt(args[1]);
660  } else if (args.length > 0 && args[0].length > 0) {
661    from = parseInt(args[0]) - 1;
662  }
663
664  if (from < 0) from = 0;
665  if (lines < 0) lines = 10;
666
667  // Request source arround current source location.
668  request.arguments = {};
669  request.arguments.fromLine = from;
670  request.arguments.toLine = from + lines;
671
672  return request.toJSONProtocol();
673};
674
675
676// Create a JSON request for the scripts command.
677DebugRequest.prototype.scriptsCommandToJSONRequest_ = function(args) {
678  // Build a evaluate request from the text command.
679  var request = this.createRequest('scripts');
680
681  // Process arguments if any.
682  if (args && args.length > 0) {
683    args = args.split(/\s*[ ]+\s*/g);
684
685    if (args.length > 1) {
686      throw new Error('Invalid scripts arguments.');
687    }
688
689    request.arguments = {};
690    switch (args[0]) {
691      case 'natives':
692        request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Native);
693        break;
694
695      case 'extensions':
696        request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Extension);
697        break;
698
699      case 'all':
700        request.arguments.types =
701            ScriptTypeFlag(Debug.ScriptType.Normal) |
702            ScriptTypeFlag(Debug.ScriptType.Native) |
703            ScriptTypeFlag(Debug.ScriptType.Extension);
704        break;
705
706      default:
707        throw new Error('Invalid argument "' + args[0] + '".');
708    }
709  }
710
711  return request.toJSONProtocol();
712};
713
714
715// Create a JSON request for the break command.
716DebugRequest.prototype.breakCommandToJSONRequest_ = function(args) {
717  // Build a evaluate request from the text command.
718  var request = this.createRequest('setbreakpoint');
719
720  // Process arguments if any.
721  if (args && args.length > 0) {
722    var target = args;
723    var type = 'function';
724    var line;
725    var column;
726    var condition;
727    var pos;
728
729    // Check for breakpoint condition.
730    pos = args.indexOf(' ');
731    if (pos > 0) {
732      target = args.substring(0, pos);
733      condition = args.substring(pos + 1, args.length);
734    }
735
736    // Check for script breakpoint (name:line[:column]). If no ':' in break
737    // specification it is considered a function break point.
738    pos = target.indexOf(':');
739    if (pos > 0) {
740      type = 'script';
741      var tmp = target.substring(pos + 1, target.length);
742      target = target.substring(0, pos);
743
744      // Check for both line and column.
745      pos = tmp.indexOf(':');
746      if (pos > 0) {
747        column = parseInt(tmp.substring(pos + 1, tmp.length)) - 1;
748        line = parseInt(tmp.substring(0, pos)) - 1;
749      } else {
750        line = parseInt(tmp) - 1;
751      }
752    } else if (target[0] == '#' && target[target.length - 1] == '#') {
753      type = 'handle';
754      target = target.substring(1, target.length - 1);
755    } else {
756      type = 'function';
757    }
758
759    request.arguments = {};
760    request.arguments.type = type;
761    request.arguments.target = target;
762    request.arguments.line = line;
763    request.arguments.column = column;
764    request.arguments.condition = condition;
765  } else {
766    throw new Error('Invalid break arguments.');
767  }
768
769  return request.toJSONProtocol();
770};
771
772
773// Create a JSON request for the clear command.
774DebugRequest.prototype.clearCommandToJSONRequest_ = function(args) {
775  // Build a evaluate request from the text command.
776  var request = this.createRequest('clearbreakpoint');
777
778  // Process arguments if any.
779  if (args && args.length > 0) {
780    request.arguments = {};
781    request.arguments.breakpoint = parseInt(args);
782  } else {
783    throw new Error('Invalid break arguments.');
784  }
785
786  return request.toJSONProtocol();
787};
788
789
790// Create a JSON request for the threads command.
791DebugRequest.prototype.threadsCommandToJSONRequest_ = function(args) {
792  // Build a threads request from the text command.
793  var request = this.createRequest('threads');
794  return request.toJSONProtocol();
795};
796
797
798// Handle the trace command.
799DebugRequest.prototype.traceCommand_ = function(args) {
800  // Process arguments.
801  if (args && args.length > 0) {
802    if (args == 'compile') {
803      trace_compile = !trace_compile;
804      print('Tracing of compiled scripts ' + (trace_compile ? 'on' : 'off'));
805    } else {
806      throw new Error('Invalid trace arguments.');
807    }
808  } else {
809    throw new Error('Invalid trace arguments.');
810  }
811}
812
813// Handle the help command.
814DebugRequest.prototype.helpCommand_ = function(args) {
815  // Help os quite simple.
816  if (args && args.length > 0) {
817    print('warning: arguments to \'help\' are ignored');
818  }
819
820  print('break location [condition]');
821  print('  break on named function: location is a function name');
822  print('  break on function: location is #<id>#');
823  print('  break on script position: location is name:line[:column]');
824  print('clear <breakpoint #>');
825  print('backtrace [n] | [-n] | [from to]');
826  print('frame <frame #>');
827  print('scopes');
828  print('scope <scope #>');
829  print('step [in | next | out| min [step count]]');
830  print('print <expression>');
831  print('dir <expression>');
832  print('source [from line [num lines]]');
833  print('scripts');
834  print('continue');
835  print('trace compile');
836  print('help');
837}
838
839
840function formatHandleReference_(value) {
841  if (value.handle() >= 0) {
842    return '#' + value.handle() + '#';
843  } else {
844    return '#Transient#';
845  }
846}
847
848
849function formatObject_(value, include_properties) {
850  var result = '';
851  result += formatHandleReference_(value);
852  result += ', type: object'
853  result += ', constructor ';
854  var ctor = value.constructorFunctionValue();
855  result += formatHandleReference_(ctor);
856  result += ', __proto__ ';
857  var proto = value.protoObjectValue();
858  result += formatHandleReference_(proto);
859  result += ', ';
860  result += value.propertyCount();
861  result +=  ' properties.';
862  if (include_properties) {
863    result +=  '\n';
864    for (var i = 0; i < value.propertyCount(); i++) {
865      result += '  ';
866      result += value.propertyName(i);
867      result += ': ';
868      var property_value = value.propertyValue(i);
869      if (property_value instanceof ProtocolReference) {
870        result += '<no type>';
871      } else {
872        if (property_value && property_value.type()) {
873          result += property_value.type();
874        } else {
875          result += '<no type>';
876        }
877      }
878      result += ' ';
879      result += formatHandleReference_(property_value);
880      result += '\n';
881    }
882  }
883  return result;
884}
885
886
887function formatScope_(scope) {
888  var result = '';
889  var index = scope.index;
890  result += '#' + (index <= 9 ? '0' : '') + index;
891  result += ' ';
892  switch (scope.type) {
893    case Debug.ScopeType.Global:
894      result += 'Global, ';
895      result += '#' + scope.object.ref + '#';
896      break;
897    case Debug.ScopeType.Local:
898      result += 'Local';
899      break;
900    case Debug.ScopeType.With:
901      result += 'With, ';
902      result += '#' + scope.object.ref + '#';
903      break;
904    case Debug.ScopeType.Catch:
905      result += 'Catch, ';
906      result += '#' + scope.object.ref + '#';
907      break;
908    case Debug.ScopeType.Closure:
909      result += 'Closure';
910      break;
911    default:
912      result += 'UNKNOWN';
913  }
914  return result;
915}
916
917
918// Convert a JSON response to text for display in a text based debugger.
919function DebugResponseDetails(response) {
920  details = {text:'', running:false}
921
922  try {
923    if (!response.success()) {
924      details.text = response.message();
925      return details;
926    }
927
928    // Get the running state.
929    details.running = response.running();
930
931    var body = response.body();
932    var result = '';
933    switch (response.command()) {
934      case 'setbreakpoint':
935        result = 'set breakpoint #';
936        result += body.breakpoint;
937        details.text = result;
938        break;
939
940      case 'clearbreakpoint':
941        result = 'cleared breakpoint #';
942        result += body.breakpoint;
943        details.text = result;
944        break;
945
946      case 'backtrace':
947        if (body.totalFrames == 0) {
948          result = '(empty stack)';
949        } else {
950          var result = 'Frames #' + body.fromFrame + ' to #' +
951              (body.toFrame - 1) + ' of ' + body.totalFrames + '\n';
952          for (i = 0; i < body.frames.length; i++) {
953            if (i != 0) result += '\n';
954            result += body.frames[i].text;
955          }
956        }
957        details.text = result;
958        break;
959
960      case 'frame':
961        details.text = SourceUnderline(body.sourceLineText,
962                                       body.column);
963        Debug.State.currentSourceLine = body.line;
964        Debug.State.currentFrame = body.index;
965        break;
966
967      case 'scopes':
968        if (body.totalScopes == 0) {
969          result = '(no scopes)';
970        } else {
971          result = 'Scopes #' + body.fromScope + ' to #' +
972                   (body.toScope - 1) + ' of ' + body.totalScopes + '\n';
973          for (i = 0; i < body.scopes.length; i++) {
974            if (i != 0) {
975              result += '\n';
976            }
977            result += formatScope_(body.scopes[i]);
978          }
979        }
980        details.text = result;
981        break;
982
983      case 'scope':
984        result += formatScope_(body);
985        result += '\n';
986        var scope_object_value = response.lookup(body.object.ref);
987        result += formatObject_(scope_object_value, true);
988        details.text = result;
989        break;
990
991      case 'evaluate':
992      case 'lookup':
993        if (last_cmd == 'p' || last_cmd == 'print') {
994          result = body.text;
995        } else {
996          var value;
997          if (lookup_handle) {
998            value = response.bodyValue(lookup_handle);
999          } else {
1000            value = response.bodyValue();
1001          }
1002          if (value.isObject()) {
1003            result += formatObject_(value, true);
1004          } else {
1005            result += 'type: ';
1006            result += value.type();
1007            if (!value.isUndefined() && !value.isNull()) {
1008              result += ', ';
1009              if (value.isString()) {
1010                result += '"';
1011              }
1012              result += value.value();
1013              if (value.isString()) {
1014                result += '"';
1015              }
1016            }
1017            result += '\n';
1018          }
1019        }
1020        details.text = result;
1021        break;
1022
1023      case 'references':
1024        var count = body.length;
1025        result += 'found ' + count + ' objects';
1026        result += '\n';
1027        for (var i = 0; i < count; i++) {
1028          var value = response.bodyValue(i);
1029          result += formatObject_(value, false);
1030          result += '\n';
1031        }
1032        details.text = result;
1033        break;
1034
1035      case 'source':
1036        // Get the source from the response.
1037        var source = body.source;
1038        var from_line = body.fromLine + 1;
1039        var lines = source.split('\n');
1040        var maxdigits = 1 + Math.floor(log10(from_line + lines.length));
1041        if (maxdigits < 3) {
1042          maxdigits = 3;
1043        }
1044        var result = '';
1045        for (var num = 0; num < lines.length; num++) {
1046          // Check if there's an extra newline at the end.
1047          if (num == (lines.length - 1) && lines[num].length == 0) {
1048            break;
1049          }
1050
1051          var current_line = from_line + num;
1052          spacer = maxdigits - (1 + Math.floor(log10(current_line)));
1053          if (current_line == Debug.State.currentSourceLine + 1) {
1054            for (var i = 0; i < maxdigits; i++) {
1055              result += '>';
1056            }
1057            result += '  ';
1058          } else {
1059            for (var i = 0; i < spacer; i++) {
1060              result += ' ';
1061            }
1062            result += current_line + ': ';
1063          }
1064          result += lines[num];
1065          result += '\n';
1066        }
1067        details.text = result;
1068        break;
1069
1070      case 'scripts':
1071        var result = '';
1072        for (i = 0; i < body.length; i++) {
1073          if (i != 0) result += '\n';
1074          if (body[i].id) {
1075            result += body[i].id;
1076          } else {
1077            result += '[no id]';
1078          }
1079          result += ', ';
1080          if (body[i].name) {
1081            result += body[i].name;
1082          } else {
1083            if (body[i].compilationType == Debug.ScriptCompilationType.Eval) {
1084              result += 'eval from ';
1085              var script_value = response.lookup(body[i].evalFromScript.ref);
1086              result += ' ' + script_value.field('name');
1087              result += ':' + (body[i].evalFromLocation.line + 1);
1088              result += ':' + body[i].evalFromLocation.column;
1089            } else if (body[i].compilationType ==
1090                       Debug.ScriptCompilationType.JSON) {
1091              result += 'JSON ';
1092            } else {  // body[i].compilation == Debug.ScriptCompilationType.Host
1093              result += '[unnamed] ';
1094            }
1095          }
1096          result += ' (lines: ';
1097          result += body[i].lineCount;
1098          result += ', length: ';
1099          result += body[i].sourceLength;
1100          if (body[i].type == Debug.ScriptType.Native) {
1101            result += ', native';
1102          } else if (body[i].type == Debug.ScriptType.Extension) {
1103            result += ', extension';
1104          }
1105          result += '), [';
1106          var sourceStart = body[i].sourceStart;
1107          if (sourceStart.length > 40) {
1108            sourceStart = sourceStart.substring(0, 37) + '...';
1109          }
1110          result += sourceStart;
1111          result += ']';
1112        }
1113        details.text = result;
1114        break;
1115
1116      case 'threads':
1117        var result = 'Active V8 threads: ' + body.totalThreads + '\n';
1118        body.threads.sort(function(a, b) { return a.id - b.id; });
1119        for (i = 0; i < body.threads.length; i++) {
1120          result += body.threads[i].current ? '*' : ' ';
1121          result += ' ';
1122          result += body.threads[i].id;
1123          result += '\n';
1124        }
1125        details.text = result;
1126        break;
1127
1128      case 'continue':
1129        details.text = "(running)";
1130        break;
1131
1132      default:
1133        details.text =
1134            'Response for unknown command \'' + response.command + '\'' +
1135            ' (' + json_response + ')';
1136    }
1137  } catch (e) {
1138    details.text = 'Error: "' + e + '" formatting response';
1139  }
1140
1141  return details;
1142};
1143
1144
1145/**
1146 * Protocol packages send from the debugger.
1147 * @param {string} json - raw protocol packet as JSON string.
1148 * @constructor
1149 */
1150function ProtocolPackage(json) {
1151  this.packet_ = JSON.parse(json);
1152  this.refs_ = [];
1153  if (this.packet_.refs) {
1154    for (var i = 0; i < this.packet_.refs.length; i++) {
1155      this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i];
1156    }
1157  }
1158}
1159
1160
1161/**
1162 * Get the packet type.
1163 * @return {String} the packet type
1164 */
1165ProtocolPackage.prototype.type = function() {
1166  return this.packet_.type;
1167}
1168
1169
1170/**
1171 * Get the packet event.
1172 * @return {Object} the packet event
1173 */
1174ProtocolPackage.prototype.event = function() {
1175  return this.packet_.event;
1176}
1177
1178
1179/**
1180 * Get the packet request sequence.
1181 * @return {number} the packet request sequence
1182 */
1183ProtocolPackage.prototype.requestSeq = function() {
1184  return this.packet_.request_seq;
1185}
1186
1187
1188/**
1189 * Get the packet request sequence.
1190 * @return {number} the packet request sequence
1191 */
1192ProtocolPackage.prototype.running = function() {
1193  return this.packet_.running ? true : false;
1194}
1195
1196
1197ProtocolPackage.prototype.success = function() {
1198  return this.packet_.success ? true : false;
1199}
1200
1201
1202ProtocolPackage.prototype.message = function() {
1203  return this.packet_.message;
1204}
1205
1206
1207ProtocolPackage.prototype.command = function() {
1208  return this.packet_.command;
1209}
1210
1211
1212ProtocolPackage.prototype.body = function() {
1213  return this.packet_.body;
1214}
1215
1216
1217ProtocolPackage.prototype.bodyValue = function(index) {
1218  if (index != null) {
1219    return new ProtocolValue(this.packet_.body[index], this);
1220  } else {
1221    return new ProtocolValue(this.packet_.body, this);
1222  }
1223}
1224
1225
1226ProtocolPackage.prototype.body = function() {
1227  return this.packet_.body;
1228}
1229
1230
1231ProtocolPackage.prototype.lookup = function(handle) {
1232  var value = this.refs_[handle];
1233  if (value) {
1234    return new ProtocolValue(value, this);
1235  } else {
1236    return new ProtocolReference(handle);
1237  }
1238}
1239
1240
1241function ProtocolValue(value, packet) {
1242  this.value_ = value;
1243  this.packet_ = packet;
1244}
1245
1246
1247/**
1248 * Get the value type.
1249 * @return {String} the value type
1250 */
1251ProtocolValue.prototype.type = function() {
1252  return this.value_.type;
1253}
1254
1255
1256/**
1257 * Get a metadata field from a protocol value.
1258 * @return {Object} the metadata field value
1259 */
1260ProtocolValue.prototype.field = function(name) {
1261  return this.value_[name];
1262}
1263
1264
1265/**
1266 * Check is the value is a primitive value.
1267 * @return {boolean} true if the value is primitive
1268 */
1269ProtocolValue.prototype.isPrimitive = function() {
1270  return this.isUndefined() || this.isNull() || this.isBoolean() ||
1271         this.isNumber() || this.isString();
1272}
1273
1274
1275/**
1276 * Get the object handle.
1277 * @return {number} the value handle
1278 */
1279ProtocolValue.prototype.handle = function() {
1280  return this.value_.handle;
1281}
1282
1283
1284/**
1285 * Check is the value is undefined.
1286 * @return {boolean} true if the value is undefined
1287 */
1288ProtocolValue.prototype.isUndefined = function() {
1289  return this.value_.type == 'undefined';
1290}
1291
1292
1293/**
1294 * Check is the value is null.
1295 * @return {boolean} true if the value is null
1296 */
1297ProtocolValue.prototype.isNull = function() {
1298  return this.value_.type == 'null';
1299}
1300
1301
1302/**
1303 * Check is the value is a boolean.
1304 * @return {boolean} true if the value is a boolean
1305 */
1306ProtocolValue.prototype.isBoolean = function() {
1307  return this.value_.type == 'boolean';
1308}
1309
1310
1311/**
1312 * Check is the value is a number.
1313 * @return {boolean} true if the value is a number
1314 */
1315ProtocolValue.prototype.isNumber = function() {
1316  return this.value_.type == 'number';
1317}
1318
1319
1320/**
1321 * Check is the value is a string.
1322 * @return {boolean} true if the value is a string
1323 */
1324ProtocolValue.prototype.isString = function() {
1325  return this.value_.type == 'string';
1326}
1327
1328
1329/**
1330 * Check is the value is an object.
1331 * @return {boolean} true if the value is an object
1332 */
1333ProtocolValue.prototype.isObject = function() {
1334  return this.value_.type == 'object' || this.value_.type == 'function' ||
1335         this.value_.type == 'error' || this.value_.type == 'regexp';
1336}
1337
1338
1339/**
1340 * Get the constructor function
1341 * @return {ProtocolValue} constructor function
1342 */
1343ProtocolValue.prototype.constructorFunctionValue = function() {
1344  var ctor = this.value_.constructorFunction;
1345  return this.packet_.lookup(ctor.ref);
1346}
1347
1348
1349/**
1350 * Get the __proto__ value
1351 * @return {ProtocolValue} __proto__ value
1352 */
1353ProtocolValue.prototype.protoObjectValue = function() {
1354  var proto = this.value_.protoObject;
1355  return this.packet_.lookup(proto.ref);
1356}
1357
1358
1359/**
1360 * Get the number og properties.
1361 * @return {number} the number of properties
1362 */
1363ProtocolValue.prototype.propertyCount = function() {
1364  return this.value_.properties ? this.value_.properties.length : 0;
1365}
1366
1367
1368/**
1369 * Get the specified property name.
1370 * @return {string} property name
1371 */
1372ProtocolValue.prototype.propertyName = function(index) {
1373  var property = this.value_.properties[index];
1374  return property.name;
1375}
1376
1377
1378/**
1379 * Return index for the property name.
1380 * @param name The property name to look for
1381 * @return {number} index for the property name
1382 */
1383ProtocolValue.prototype.propertyIndex = function(name) {
1384  for (var i = 0; i < this.propertyCount(); i++) {
1385    if (this.value_.properties[i].name == name) {
1386      return i;
1387    }
1388  }
1389  return null;
1390}
1391
1392
1393/**
1394 * Get the specified property value.
1395 * @return {ProtocolValue} property value
1396 */
1397ProtocolValue.prototype.propertyValue = function(index) {
1398  var property = this.value_.properties[index];
1399  return this.packet_.lookup(property.ref);
1400}
1401
1402
1403/**
1404 * Check is the value is a string.
1405 * @return {boolean} true if the value is a string
1406 */
1407ProtocolValue.prototype.value = function() {
1408  return this.value_.value;
1409}
1410
1411
1412function ProtocolReference(handle) {
1413  this.handle_ = handle;
1414}
1415
1416
1417ProtocolReference.prototype.handle = function() {
1418  return this.handle_;
1419}
1420
1421
1422function MakeJSONPair_(name, value) {
1423  return '"' + name + '":' + value;
1424}
1425
1426
1427function ArrayToJSONObject_(content) {
1428  return '{' + content.join(',') + '}';
1429}
1430
1431
1432function ArrayToJSONArray_(content) {
1433  return '[' + content.join(',') + ']';
1434}
1435
1436
1437function BooleanToJSON_(value) {
1438  return String(value);
1439}
1440
1441
1442function NumberToJSON_(value) {
1443  return String(value);
1444}
1445
1446
1447// Mapping of some control characters to avoid the \uXXXX syntax for most
1448// commonly used control cahracters.
1449const ctrlCharMap_ = {
1450  '\b': '\\b',
1451  '\t': '\\t',
1452  '\n': '\\n',
1453  '\f': '\\f',
1454  '\r': '\\r',
1455  '"' : '\\"',
1456  '\\': '\\\\'
1457};
1458
1459
1460// Regular expression testing for ", \ and control characters (0x00 - 0x1F).
1461const ctrlCharTest_ = new RegExp('["\\\\\x00-\x1F]');
1462
1463
1464// Regular expression matching ", \ and control characters (0x00 - 0x1F)
1465// globally.
1466const ctrlCharMatch_ = new RegExp('["\\\\\x00-\x1F]', 'g');
1467
1468
1469/**
1470 * Convert a String to its JSON representation (see http://www.json.org/). To
1471 * avoid depending on the String object this method calls the functions in
1472 * string.js directly and not through the value.
1473 * @param {String} value The String value to format as JSON
1474 * @return {string} JSON formatted String value
1475 */
1476function StringToJSON_(value) {
1477  // Check for" , \ and control characters (0x00 - 0x1F). No need to call
1478  // RegExpTest as ctrlchar is constructed using RegExp.
1479  if (ctrlCharTest_.test(value)) {
1480    // Replace ", \ and control characters (0x00 - 0x1F).
1481    return '"' +
1482      value.replace(ctrlCharMatch_, function (char) {
1483        // Use charmap if possible.
1484        var mapped = ctrlCharMap_[char];
1485        if (mapped) return mapped;
1486        mapped = char.charCodeAt();
1487        // Convert control character to unicode escape sequence.
1488        return '\\u00' +
1489          '0' + // TODO %NumberToRadixString(Math.floor(mapped / 16), 16) +
1490          '0' // TODO %NumberToRadixString(mapped % 16, 16);
1491      })
1492    + '"';
1493  }
1494
1495  // Simple string with no special characters.
1496  return '"' + value + '"';
1497}
1498
1499
1500/**
1501 * Convert a Date to ISO 8601 format. To avoid depending on the Date object
1502 * this method calls the functions in date.js directly and not through the
1503 * value.
1504 * @param {Date} value The Date value to format as JSON
1505 * @return {string} JSON formatted Date value
1506 */
1507function DateToISO8601_(value) {
1508  function f(n) {
1509    return n < 10 ? '0' + n : n;
1510  }
1511  function g(n) {
1512    return n < 10 ? '00' + n : n < 100 ? '0' + n : n;
1513  }
1514  return builtins.GetUTCFullYearFrom(value)         + '-' +
1515          f(builtins.GetUTCMonthFrom(value) + 1)    + '-' +
1516          f(builtins.GetUTCDateFrom(value))         + 'T' +
1517          f(builtins.GetUTCHoursFrom(value))        + ':' +
1518          f(builtins.GetUTCMinutesFrom(value))      + ':' +
1519          f(builtins.GetUTCSecondsFrom(value))      + '.' +
1520          g(builtins.GetUTCMillisecondsFrom(value)) + 'Z';
1521}
1522
1523
1524/**
1525 * Convert a Date to ISO 8601 format. To avoid depending on the Date object
1526 * this method calls the functions in date.js directly and not through the
1527 * value.
1528 * @param {Date} value The Date value to format as JSON
1529 * @return {string} JSON formatted Date value
1530 */
1531function DateToJSON_(value) {
1532  return '"' + DateToISO8601_(value) + '"';
1533}
1534
1535
1536/**
1537 * Convert an Object to its JSON representation (see http://www.json.org/).
1538 * This implementation simply runs through all string property names and adds
1539 * each property to the JSON representation for some predefined types. For type
1540 * "object" the function calls itself recursively unless the object has the
1541 * function property "toJSONProtocol" in which case that is used. This is not
1542 * a general implementation but sufficient for the debugger. Note that circular
1543 * structures will cause infinite recursion.
1544 * @param {Object} object The object to format as JSON
1545 * @return {string} JSON formatted object value
1546 */
1547function SimpleObjectToJSON_(object) {
1548  var content = [];
1549  for (var key in object) {
1550    // Only consider string keys.
1551    if (typeof key == 'string') {
1552      var property_value = object[key];
1553
1554      // Format the value based on its type.
1555      var property_value_json;
1556      switch (typeof property_value) {
1557        case 'object':
1558          if (typeof property_value.toJSONProtocol == 'function') {
1559            property_value_json = property_value.toJSONProtocol(true)
1560          } else if (property_value.constructor.name == 'Array'){
1561            property_value_json = SimpleArrayToJSON_(property_value);
1562          } else {
1563            property_value_json = SimpleObjectToJSON_(property_value);
1564          }
1565          break;
1566
1567        case 'boolean':
1568          property_value_json = BooleanToJSON_(property_value);
1569          break;
1570
1571        case 'number':
1572          property_value_json = NumberToJSON_(property_value);
1573          break;
1574
1575        case 'string':
1576          property_value_json = StringToJSON_(property_value);
1577          break;
1578
1579        default:
1580          property_value_json = null;
1581      }
1582
1583      // Add the property if relevant.
1584      if (property_value_json) {
1585        content.push(StringToJSON_(key) + ':' + property_value_json);
1586      }
1587    }
1588  }
1589
1590  // Make JSON object representation.
1591  return '{' + content.join(',') + '}';
1592}
1593
1594
1595/**
1596 * Convert an array to its JSON representation. This is a VERY simple
1597 * implementation just to support what is needed for the debugger.
1598 * @param {Array} arrya The array to format as JSON
1599 * @return {string} JSON formatted array value
1600 */
1601function SimpleArrayToJSON_(array) {
1602  // Make JSON array representation.
1603  var json = '[';
1604  for (var i = 0; i < array.length; i++) {
1605    if (i != 0) {
1606      json += ',';
1607    }
1608    var elem = array[i];
1609    if (elem.toJSONProtocol) {
1610      json += elem.toJSONProtocol(true)
1611    } else if (typeof(elem) === 'object')  {
1612      json += SimpleObjectToJSON_(elem);
1613    } else if (typeof(elem) === 'boolean')  {
1614      json += BooleanToJSON_(elem);
1615    } else if (typeof(elem) === 'number')  {
1616      json += NumberToJSON_(elem);
1617    } else if (typeof(elem) === 'string')  {
1618      json += StringToJSON_(elem);
1619    } else {
1620      json += elem;
1621    }
1622  }
1623  json += ']';
1624  return json;
1625}
1626