Home | History | Annotate | Download | only in src
      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 
     30 String.prototype.startsWith = function (str) {
     31   if (str.length > this.length) {
     32     return false;
     33   }
     34   return this.substr(0, str.length) == str;
     35 };
     36 
     37 function log10(num) {
     38   return Math.log(num)/Math.log(10);
     39 }
     40 
     41 function ToInspectableObject(obj) {
     42   if (!obj && typeof obj === 'object') {
     43     return void 0;
     44   } else {
     45     return Object(obj);
     46   }
     47 }
     48 
     49 function 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(current.__proto__);
     75   }
     76   return result;
     77 }
     78 
     79 
     80 // Global object holding debugger related constants and state.
     81 var 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.
     86 Debug.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.
     94 Debug.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.
    101 Debug.ScriptCompilationType = { Host: 0,
    102                                 Eval: 1,
    103                                 JSON: 2 };
    104 
    105 
    106 // The different types of scopes matching constants runtime.cc.
    107 Debug.ScopeType = { Global: 0,
    108                     Local: 1,
    109                     With: 2,
    110                     Closure: 3,
    111                     Catch: 4,
    112                     Block: 5 };
    113 
    114 
    115 // Current debug state.
    116 var kNoFrame = -1;
    117 Debug.State = {
    118   currentFrame: kNoFrame,
    119   displaySourceStartLine: -1,
    120   displaySourceEndLine: -1,
    121   currentSourceLine: -1
    122 };
    123 var trace_compile = false;  // Tracing all compile events?
    124 var trace_debug_json = false; // Tracing all debug json packets?
    125 var last_cmd = '';
    126 //var lol_is_enabled;  // Set to true in d8.cc if LIVE_OBJECT_LIST is defined.
    127 var lol_next_dump_index = 0;
    128 var kDefaultLolLinesToPrintAtATime = 10;
    129 var kMaxLolLinesToPrintAtATime = 1000;
    130 var repeat_cmd_line = '';
    131 var is_running = true;
    132 // Global variable used to store whether a handle was requested.
    133 var lookup_handle = null;
    134 
    135 // Copied from debug-delay.js.  This is needed below:
    136 function ScriptTypeFlag(type) {
    137   return (1 << type);
    138 }
    139 
    140 
    141 // Process a debugger JSON message into a display text and a running status.
    142 // This function returns an object with properties "text" and "running" holding
    143 // this information.
    144 function DebugMessageDetails(message) {
    145   if (trace_debug_json) {
    146     print("received: '" + message + "'");
    147   }
    148   // Convert the JSON string to an object.
    149   var response = new ProtocolPackage(message);
    150   is_running = response.running();
    151 
    152   if (response.type() == 'event') {
    153     return DebugEventDetails(response);
    154   } else {
    155     return DebugResponseDetails(response);
    156   }
    157 }
    158 
    159 function DebugEventDetails(response) {
    160   var details = {text:'', running:false};
    161 
    162   // Get the running state.
    163   details.running = response.running();
    164 
    165   var body = response.body();
    166   var result = '';
    167   switch (response.event()) {
    168     case 'break':
    169       if (body.breakpoints) {
    170         result += 'breakpoint';
    171         if (body.breakpoints.length > 1) {
    172           result += 's';
    173         }
    174         result += ' #';
    175         for (var i = 0; i < body.breakpoints.length; i++) {
    176           if (i > 0) {
    177             result += ', #';
    178           }
    179           result += body.breakpoints[i];
    180         }
    181       } else {
    182         result += 'break';
    183       }
    184       result += ' in ';
    185       result += body.invocationText;
    186       result += ', ';
    187       result += SourceInfo(body);
    188       result += '\n';
    189       result += SourceUnderline(body.sourceLineText, body.sourceColumn);
    190       Debug.State.currentSourceLine = body.sourceLine;
    191       Debug.State.displaySourceStartLine = -1;
    192       Debug.State.displaySourceEndLine = -1;
    193       Debug.State.currentFrame = 0;
    194       details.text = result;
    195       break;
    196 
    197     case 'exception':
    198       if (body.uncaught) {
    199         result += 'Uncaught: ';
    200       } else {
    201         result += 'Exception: ';
    202       }
    203       result += '"';
    204       result += body.exception.text;
    205       result += '"';
    206       if (body.sourceLine >= 0) {
    207         result += ', ';
    208         result += SourceInfo(body);
    209         result += '\n';
    210         result += SourceUnderline(body.sourceLineText, body.sourceColumn);
    211         Debug.State.currentSourceLine = body.sourceLine;
    212         Debug.State.displaySourceStartLine = -1;
    213         Debug.State.displaySourceEndLine = -1;
    214         Debug.State.currentFrame = 0;
    215       } else {
    216         result += ' (empty stack)';
    217         Debug.State.currentSourceLine = -1;
    218         Debug.State.displaySourceStartLine = -1;
    219         Debug.State.displaySourceEndLine = -1;
    220         Debug.State.currentFrame = kNoFrame;
    221       }
    222       details.text = result;
    223       break;
    224 
    225     case 'afterCompile':
    226       if (trace_compile) {
    227         result = 'Source ' + body.script.name + ' compiled:\n';
    228         var source = body.script.source;
    229         if (!(source[source.length - 1] == '\n')) {
    230           result += source;
    231         } else {
    232           result += source.substring(0, source.length - 1);
    233         }
    234       }
    235       details.text = result;
    236       break;
    237 
    238     case 'scriptCollected':
    239       details.text = result;
    240       break;
    241 
    242     default:
    243       details.text = 'Unknown debug event ' + response.event();
    244   }
    245 
    246   return details;
    247 }
    248 
    249 
    250 function SourceInfo(body) {
    251   var result = '';
    252 
    253   if (body.script) {
    254     if (body.script.name) {
    255       result += body.script.name;
    256     } else {
    257       result += '[unnamed]';
    258     }
    259   }
    260   result += ' line ';
    261   result += body.sourceLine + 1;
    262   result += ' column ';
    263   result += body.sourceColumn + 1;
    264 
    265   return result;
    266 }
    267 
    268 
    269 function SourceUnderline(source_text, position) {
    270   if (!source_text) {
    271     return;
    272   }
    273 
    274   // Create an underline with a caret pointing to the source position. If the
    275   // source contains a tab character the underline will have a tab character in
    276   // the same place otherwise the underline will have a space character.
    277   var underline = '';
    278   for (var i = 0; i < position; i++) {
    279     if (source_text[i] == '\t') {
    280       underline += '\t';
    281     } else {
    282       underline += ' ';
    283     }
    284   }
    285   underline += '^';
    286 
    287   // Return the source line text with the underline beneath.
    288   return source_text + '\n' + underline;
    289 }
    290 
    291 
    292 // Converts a text command to a JSON request.
    293 function DebugCommandToJSONRequest(cmd_line) {
    294   var result = new DebugRequest(cmd_line).JSONRequest();
    295   if (trace_debug_json && result) {
    296     print("sending: '" + result + "'");
    297   }
    298   return result;
    299 }
    300 
    301 
    302 function DebugRequest(cmd_line) {
    303   // If the very first character is a { assume that a JSON request have been
    304   // entered as a command. Converting that to a JSON request is trivial.
    305   if (cmd_line && cmd_line.length > 0 && cmd_line.charAt(0) == '{') {
    306     this.request_ = cmd_line;
    307     return;
    308   }
    309 
    310   // Check for a simple carriage return to repeat the last command:
    311   var is_repeating = false;
    312   if (cmd_line == '\n') {
    313     if (is_running) {
    314       cmd_line = 'break'; // Not in debugger mode, break with a frame request.
    315     } else {
    316       cmd_line = repeat_cmd_line; // use command to repeat.
    317       is_repeating = true;
    318     }
    319   }
    320   if (!is_running) { // Only save the command if in debugger mode.
    321     repeat_cmd_line = cmd_line;   // save last command.
    322   }
    323 
    324   // Trim string for leading and trailing whitespace.
    325   cmd_line = cmd_line.replace(/^\s+|\s+$/g, '');
    326 
    327   // Find the command.
    328   var pos = cmd_line.indexOf(' ');
    329   var cmd;
    330   var args;
    331   if (pos == -1) {
    332     cmd = cmd_line;
    333     args = '';
    334   } else {
    335     cmd = cmd_line.slice(0, pos);
    336     args = cmd_line.slice(pos).replace(/^\s+|\s+$/g, '');
    337   }
    338 
    339   if ((cmd === undefined) || !cmd) {
    340     this.request_ = void 0;
    341     return;
    342   }
    343 
    344   last_cmd = cmd;
    345 
    346   // Switch on command.
    347   switch (cmd) {
    348     case 'continue':
    349     case 'c':
    350       this.request_ = this.continueCommandToJSONRequest_(args);
    351       break;
    352 
    353     case 'step':
    354     case 's':
    355       this.request_ = this.stepCommandToJSONRequest_(args, 'in');
    356       break;
    357 
    358     case 'stepi':
    359     case 'si':
    360       this.request_ = this.stepCommandToJSONRequest_(args, 'min');
    361       break;
    362 
    363     case 'next':
    364     case 'n':
    365       this.request_ = this.stepCommandToJSONRequest_(args, 'next');
    366       break;
    367 
    368     case 'finish':
    369     case 'fin':
    370       this.request_ = this.stepCommandToJSONRequest_(args, 'out');
    371       break;
    372 
    373     case 'backtrace':
    374     case 'bt':
    375       this.request_ = this.backtraceCommandToJSONRequest_(args);
    376       break;
    377 
    378     case 'frame':
    379     case 'f':
    380       this.request_ = this.frameCommandToJSONRequest_(args);
    381       break;
    382 
    383     case 'scopes':
    384       this.request_ = this.scopesCommandToJSONRequest_(args);
    385       break;
    386 
    387     case 'scope':
    388       this.request_ = this.scopeCommandToJSONRequest_(args);
    389       break;
    390 
    391     case 'disconnect':
    392     case 'exit':
    393     case 'quit':
    394       this.request_ = this.disconnectCommandToJSONRequest_(args);
    395       break;
    396 
    397     case 'up':
    398       this.request_ =
    399           this.frameCommandToJSONRequest_('' +
    400                                           (Debug.State.currentFrame + 1));
    401       break;
    402 
    403     case 'down':
    404     case 'do':
    405       this.request_ =
    406           this.frameCommandToJSONRequest_('' +
    407                                           (Debug.State.currentFrame - 1));
    408       break;
    409 
    410     case 'set':
    411     case 'print':
    412     case 'p':
    413       this.request_ = this.printCommandToJSONRequest_(args);
    414       break;
    415 
    416     case 'dir':
    417       this.request_ = this.dirCommandToJSONRequest_(args);
    418       break;
    419 
    420     case 'references':
    421       this.request_ = this.referencesCommandToJSONRequest_(args);
    422       break;
    423 
    424     case 'instances':
    425       this.request_ = this.instancesCommandToJSONRequest_(args);
    426       break;
    427 
    428     case 'list':
    429     case 'l':
    430       this.request_ = this.listCommandToJSONRequest_(args);
    431       break;
    432     case 'source':
    433       this.request_ = this.sourceCommandToJSONRequest_(args);
    434       break;
    435 
    436     case 'scripts':
    437     case 'script':
    438     case 'scr':
    439       this.request_ = this.scriptsCommandToJSONRequest_(args);
    440       break;
    441 
    442     case 'break':
    443     case 'b':
    444       this.request_ = this.breakCommandToJSONRequest_(args);
    445       break;
    446 
    447     case 'breakpoints':
    448     case 'bb':
    449       this.request_ = this.breakpointsCommandToJSONRequest_(args);
    450       break;
    451 
    452     case 'clear':
    453     case 'delete':
    454     case 'd':
    455       this.request_ = this.clearCommandToJSONRequest_(args);
    456       break;
    457 
    458     case 'threads':
    459       this.request_ = this.threadsCommandToJSONRequest_(args);
    460       break;
    461 
    462     case 'cond':
    463       this.request_ = this.changeBreakpointCommandToJSONRequest_(args, 'cond');
    464       break;
    465 
    466     case 'enable':
    467     case 'en':
    468       this.request_ =
    469           this.changeBreakpointCommandToJSONRequest_(args, 'enable');
    470       break;
    471 
    472     case 'disable':
    473     case 'dis':
    474       this.request_ =
    475           this.changeBreakpointCommandToJSONRequest_(args, 'disable');
    476       break;
    477 
    478     case 'ignore':
    479       this.request_ =
    480           this.changeBreakpointCommandToJSONRequest_(args, 'ignore');
    481       break;
    482 
    483     case 'info':
    484     case 'inf':
    485       this.request_ = this.infoCommandToJSONRequest_(args);
    486       break;
    487 
    488     case 'flags':
    489       this.request_ = this.v8FlagsToJSONRequest_(args);
    490       break;
    491 
    492     case 'gc':
    493       this.request_ = this.gcToJSONRequest_(args);
    494       break;
    495 
    496     case 'trace':
    497     case 'tr':
    498       // Return undefined to indicate command handled internally (no JSON).
    499       this.request_ = void 0;
    500       this.traceCommand_(args);
    501       break;
    502 
    503     case 'help':
    504     case '?':
    505       this.helpCommand_(args);
    506       // Return undefined to indicate command handled internally (no JSON).
    507       this.request_ = void 0;
    508       break;
    509 
    510     case 'liveobjectlist':
    511     case 'lol':
    512       if (lol_is_enabled) {
    513         this.request_ = this.lolToJSONRequest_(args, is_repeating);
    514         break;
    515       }
    516 
    517     default:
    518       throw new Error('Unknown command "' + cmd + '"');
    519   }
    520 }
    521 
    522 DebugRequest.prototype.JSONRequest = function() {
    523   return this.request_;
    524 };
    525 
    526 
    527 function RequestPacket(command) {
    528   this.seq = 0;
    529   this.type = 'request';
    530   this.command = command;
    531 }
    532 
    533 
    534 RequestPacket.prototype.toJSONProtocol = function() {
    535   // Encode the protocol header.
    536   var json = '{';
    537   json += '"seq":' + this.seq;
    538   json += ',"type":"' + this.type + '"';
    539   if (this.command) {
    540     json += ',"command":' + StringToJSON_(this.command);
    541   }
    542   if (this.arguments) {
    543     json += ',"arguments":';
    544     // Encode the arguments part.
    545     if (this.arguments.toJSONProtocol) {
    546       json += this.arguments.toJSONProtocol();
    547     } else {
    548       json += SimpleObjectToJSON_(this.arguments);
    549     }
    550   }
    551   json += '}';
    552   return json;
    553 };
    554 
    555 
    556 DebugRequest.prototype.createRequest = function(command) {
    557   return new RequestPacket(command);
    558 };
    559 
    560 
    561 // Note: we use detected command repetition as a signal for continuation here.
    562 DebugRequest.prototype.createLOLRequest = function(command,
    563                                                    start_index,
    564                                                    lines_to_dump,
    565                                                    is_continuation) {
    566   if (is_continuation) {
    567     start_index = lol_next_dump_index;
    568   }
    569 
    570   if (lines_to_dump) {
    571     lines_to_dump = parseInt(lines_to_dump);
    572   } else {
    573     lines_to_dump = kDefaultLolLinesToPrintAtATime;
    574   }
    575   if (lines_to_dump > kMaxLolLinesToPrintAtATime) {
    576     lines_to_dump = kMaxLolLinesToPrintAtATime;
    577   }
    578 
    579   // Save the next start_index to dump from:
    580   lol_next_dump_index = start_index + lines_to_dump;
    581 
    582   var request = this.createRequest(command);
    583   request.arguments = {};
    584   request.arguments.start = start_index;
    585   request.arguments.count = lines_to_dump;
    586 
    587   return request;
    588 };
    589 
    590 
    591 // Create a JSON request for the evaluation command.
    592 DebugRequest.prototype.makeEvaluateJSONRequest_ = function(expression) {
    593   lookup_handle = null;
    594 
    595   if (lol_is_enabled) {
    596     // Check if the expression is a obj id in the form @<obj id>.
    597     var obj_id_match = expression.match(/^@([0-9]+)$/);
    598     if (obj_id_match) {
    599       var obj_id = parseInt(obj_id_match[1]);
    600       // Build a dump request.
    601       var request = this.createRequest('getobj');
    602       request.arguments = {};
    603       request.arguments.obj_id = obj_id;
    604       return request.toJSONProtocol();
    605     }
    606   }
    607 
    608   // Check if the expression is a handle id in the form #<handle>#.
    609   var handle_match = expression.match(/^#([0-9]*)#$/);
    610   if (handle_match) {
    611     // Remember the handle requested in a global variable.
    612     lookup_handle = parseInt(handle_match[1]);
    613     // Build a lookup request.
    614     var request = this.createRequest('lookup');
    615     request.arguments = {};
    616     request.arguments.handles = [ lookup_handle ];
    617     return request.toJSONProtocol();
    618   } else {
    619     // Build an evaluate request.
    620     var request = this.createRequest('evaluate');
    621     request.arguments = {};
    622     request.arguments.expression = expression;
    623     // Request a global evaluation if there is no current frame.
    624     if (Debug.State.currentFrame == kNoFrame) {
    625       request.arguments.global = true;
    626     }
    627     return request.toJSONProtocol();
    628   }
    629 };
    630 
    631 
    632 // Create a JSON request for the references/instances command.
    633 DebugRequest.prototype.makeReferencesJSONRequest_ = function(handle, type) {
    634   // Build a references request.
    635   var handle_match = handle.match(/^#([0-9]*)#$/);
    636   if (handle_match) {
    637     var request = this.createRequest('references');
    638     request.arguments = {};
    639     request.arguments.type = type;
    640     request.arguments.handle = parseInt(handle_match[1]);
    641     return request.toJSONProtocol();
    642   } else {
    643     throw new Error('Invalid object id.');
    644   }
    645 };
    646 
    647 
    648 // Create a JSON request for the continue command.
    649 DebugRequest.prototype.continueCommandToJSONRequest_ = function(args) {
    650   var request = this.createRequest('continue');
    651   return request.toJSONProtocol();
    652 };
    653 
    654 
    655 // Create a JSON request for the step command.
    656 DebugRequest.prototype.stepCommandToJSONRequest_ = function(args, type) {
    657   // Requesting a step is through the continue command with additional
    658   // arguments.
    659   var request = this.createRequest('continue');
    660   request.arguments = {};
    661 
    662   // Process arguments if any.
    663 
    664   // Only process args if the command is 'step' which is indicated by type being
    665   // set to 'in'.  For all other commands, ignore the args.
    666   if (args && args.length > 0) {
    667     args = args.split(/\s+/g);
    668 
    669     if (args.length > 2) {
    670       throw new Error('Invalid step arguments.');
    671     }
    672 
    673     if (args.length > 0) {
    674       // Check if we have a gdb stype step command.  If so, the 1st arg would
    675       // be the step count.  If it's not a number, then assume that we're
    676       // parsing for the legacy v8 step command.
    677       var stepcount = Number(args[0]);
    678       if (stepcount == Number.NaN) {
    679         // No step count at arg 1.  Process as legacy d8 step command:
    680         if (args.length == 2) {
    681           var stepcount = parseInt(args[1]);
    682           if (isNaN(stepcount) || stepcount <= 0) {
    683             throw new Error('Invalid step count argument "' + args[0] + '".');
    684           }
    685           request.arguments.stepcount = stepcount;
    686         }
    687 
    688         // Get the step action.
    689         switch (args[0]) {
    690           case 'in':
    691           case 'i':
    692             request.arguments.stepaction = 'in';
    693             break;
    694 
    695           case 'min':
    696           case 'm':
    697             request.arguments.stepaction = 'min';
    698             break;
    699 
    700           case 'next':
    701           case 'n':
    702             request.arguments.stepaction = 'next';
    703             break;
    704 
    705           case 'out':
    706           case 'o':
    707             request.arguments.stepaction = 'out';
    708             break;
    709 
    710           default:
    711             throw new Error('Invalid step argument "' + args[0] + '".');
    712         }
    713 
    714       } else {
    715         // gdb style step commands:
    716         request.arguments.stepaction = type;
    717         request.arguments.stepcount = stepcount;
    718       }
    719     }
    720   } else {
    721     // Default is step of the specified type.
    722     request.arguments.stepaction = type;
    723   }
    724 
    725   return request.toJSONProtocol();
    726 };
    727 
    728 
    729 // Create a JSON request for the backtrace command.
    730 DebugRequest.prototype.backtraceCommandToJSONRequest_ = function(args) {
    731   // Build a backtrace request from the text command.
    732   var request = this.createRequest('backtrace');
    733 
    734   // Default is to show top 10 frames.
    735   request.arguments = {};
    736   request.arguments.fromFrame = 0;
    737   request.arguments.toFrame = 10;
    738 
    739   args = args.split(/\s*[ ]+\s*/g);
    740   if (args.length == 1 && args[0].length > 0) {
    741     var frameCount = parseInt(args[0]);
    742     if (frameCount > 0) {
    743       // Show top frames.
    744       request.arguments.fromFrame = 0;
    745       request.arguments.toFrame = frameCount;
    746     } else {
    747       // Show bottom frames.
    748       request.arguments.fromFrame = 0;
    749       request.arguments.toFrame = -frameCount;
    750       request.arguments.bottom = true;
    751     }
    752   } else if (args.length == 2) {
    753     var fromFrame = parseInt(args[0]);
    754     var toFrame = parseInt(args[1]);
    755     if (isNaN(fromFrame) || fromFrame < 0) {
    756       throw new Error('Invalid start frame argument "' + args[0] + '".');
    757     }
    758     if (isNaN(toFrame) || toFrame < 0) {
    759       throw new Error('Invalid end frame argument "' + args[1] + '".');
    760     }
    761     if (fromFrame > toFrame) {
    762       throw new Error('Invalid arguments start frame cannot be larger ' +
    763                       'than end frame.');
    764     }
    765     // Show frame range.
    766     request.arguments.fromFrame = fromFrame;
    767     request.arguments.toFrame = toFrame + 1;
    768   } else if (args.length > 2) {
    769     throw new Error('Invalid backtrace arguments.');
    770   }
    771 
    772   return request.toJSONProtocol();
    773 };
    774 
    775 
    776 // Create a JSON request for the frame command.
    777 DebugRequest.prototype.frameCommandToJSONRequest_ = function(args) {
    778   // Build a frame request from the text command.
    779   var request = this.createRequest('frame');
    780   args = args.split(/\s*[ ]+\s*/g);
    781   if (args.length > 0 && args[0].length > 0) {
    782     request.arguments = {};
    783     request.arguments.number = args[0];
    784   }
    785   return request.toJSONProtocol();
    786 };
    787 
    788 
    789 // Create a JSON request for the scopes command.
    790 DebugRequest.prototype.scopesCommandToJSONRequest_ = function(args) {
    791   // Build a scopes request from the text command.
    792   var request = this.createRequest('scopes');
    793   return request.toJSONProtocol();
    794 };
    795 
    796 
    797 // Create a JSON request for the scope command.
    798 DebugRequest.prototype.scopeCommandToJSONRequest_ = function(args) {
    799   // Build a scope request from the text command.
    800   var request = this.createRequest('scope');
    801   args = args.split(/\s*[ ]+\s*/g);
    802   if (args.length > 0 && args[0].length > 0) {
    803     request.arguments = {};
    804     request.arguments.number = args[0];
    805   }
    806   return request.toJSONProtocol();
    807 };
    808 
    809 
    810 // Create a JSON request for the print command.
    811 DebugRequest.prototype.printCommandToJSONRequest_ = function(args) {
    812   // Build an evaluate request from the text command.
    813   if (args.length == 0) {
    814     throw new Error('Missing expression.');
    815   }
    816   return this.makeEvaluateJSONRequest_(args);
    817 };
    818 
    819 
    820 // Create a JSON request for the dir command.
    821 DebugRequest.prototype.dirCommandToJSONRequest_ = function(args) {
    822   // Build an evaluate request from the text command.
    823   if (args.length == 0) {
    824     throw new Error('Missing expression.');
    825   }
    826   return this.makeEvaluateJSONRequest_(args);
    827 };
    828 
    829 
    830 // Create a JSON request for the references command.
    831 DebugRequest.prototype.referencesCommandToJSONRequest_ = function(args) {
    832   // Build an evaluate request from the text command.
    833   if (args.length == 0) {
    834     throw new Error('Missing object id.');
    835   }
    836 
    837   return this.makeReferencesJSONRequest_(args, 'referencedBy');
    838 };
    839 
    840 
    841 // Create a JSON request for the instances command.
    842 DebugRequest.prototype.instancesCommandToJSONRequest_ = function(args) {
    843   // Build an evaluate request from the text command.
    844   if (args.length == 0) {
    845     throw new Error('Missing object id.');
    846   }
    847 
    848   // Build a references request.
    849   return this.makeReferencesJSONRequest_(args, 'constructedBy');
    850 };
    851 
    852 
    853 // Create a JSON request for the list command.
    854 DebugRequest.prototype.listCommandToJSONRequest_ = function(args) {
    855 
    856   // Default is ten lines starting five lines before the current location.
    857   if (Debug.State.displaySourceEndLine == -1) {
    858     // If we list forwards, we will start listing after the last source end
    859     // line.  Set it to start from 5 lines before the current location.
    860     Debug.State.displaySourceEndLine = Debug.State.currentSourceLine - 5;
    861     // If we list backwards, we will start listing backwards from the last
    862     // source start line.  Set it to start from 1 lines before the current
    863     // location.
    864     Debug.State.displaySourceStartLine = Debug.State.currentSourceLine + 1;
    865   }
    866 
    867   var from = Debug.State.displaySourceEndLine + 1;
    868   var lines = 10;
    869 
    870   // Parse the arguments.
    871   args = args.split(/\s*,\s*/g);
    872   if (args == '') {
    873   } else if ((args.length == 1) && (args[0] == '-')) {
    874     from = Debug.State.displaySourceStartLine - lines;
    875   } else if (args.length == 2) {
    876     from = parseInt(args[0]);
    877     lines = parseInt(args[1]) - from + 1; // inclusive of the ending line.
    878   } else {
    879     throw new Error('Invalid list arguments.');
    880   }
    881   Debug.State.displaySourceStartLine = from;
    882   Debug.State.displaySourceEndLine = from + lines - 1;
    883   var sourceArgs = '' + from + ' ' + lines;
    884   return this.sourceCommandToJSONRequest_(sourceArgs);
    885 };
    886 
    887 
    888 // Create a JSON request for the source command.
    889 DebugRequest.prototype.sourceCommandToJSONRequest_ = function(args) {
    890   // Build a evaluate request from the text command.
    891   var request = this.createRequest('source');
    892 
    893   // Default is ten lines starting five lines before the current location.
    894   var from = Debug.State.currentSourceLine - 5;
    895   var lines = 10;
    896 
    897   // Parse the arguments.
    898   args = args.split(/\s*[ ]+\s*/g);
    899   if (args.length > 1 && args[0].length > 0 && args[1].length > 0) {
    900     from = parseInt(args[0]) - 1;
    901     lines = parseInt(args[1]);
    902   } else if (args.length > 0 && args[0].length > 0) {
    903     from = parseInt(args[0]) - 1;
    904   }
    905 
    906   if (from < 0) from = 0;
    907   if (lines < 0) lines = 10;
    908 
    909   // Request source arround current source location.
    910   request.arguments = {};
    911   request.arguments.fromLine = from;
    912   request.arguments.toLine = from + lines;
    913 
    914   return request.toJSONProtocol();
    915 };
    916 
    917 
    918 // Create a JSON request for the scripts command.
    919 DebugRequest.prototype.scriptsCommandToJSONRequest_ = function(args) {
    920   // Build a evaluate request from the text command.
    921   var request = this.createRequest('scripts');
    922 
    923   // Process arguments if any.
    924   if (args && args.length > 0) {
    925     args = args.split(/\s*[ ]+\s*/g);
    926 
    927     if (args.length > 1) {
    928       throw new Error('Invalid scripts arguments.');
    929     }
    930 
    931     request.arguments = {};
    932     switch (args[0]) {
    933       case 'natives':
    934         request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Native);
    935         break;
    936 
    937       case 'extensions':
    938         request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Extension);
    939         break;
    940 
    941       case 'all':
    942         request.arguments.types =
    943             ScriptTypeFlag(Debug.ScriptType.Normal) |
    944             ScriptTypeFlag(Debug.ScriptType.Native) |
    945             ScriptTypeFlag(Debug.ScriptType.Extension);
    946         break;
    947 
    948       default:
    949         // If the arg is not one of the know one aboves, then it must be a
    950         // filter used for filtering the results:
    951         request.arguments.filter = args[0];
    952         break;
    953     }
    954   }
    955 
    956   return request.toJSONProtocol();
    957 };
    958 
    959 
    960 // Create a JSON request for the break command.
    961 DebugRequest.prototype.breakCommandToJSONRequest_ = function(args) {
    962   // Build a evaluate request from the text command.
    963   // Process arguments if any.
    964   if (args && args.length > 0) {
    965     var target = args;
    966     var type = 'function';
    967     var line;
    968     var column;
    969     var condition;
    970     var pos;
    971 
    972     var request = this.createRequest('setbreakpoint');
    973 
    974     // Break the args into target spec and condition if appropriate.
    975 
    976     // Check for breakpoint condition.
    977     pos = args.indexOf(' ');
    978     if (pos > 0) {
    979       target = args.substring(0, pos);
    980       condition = args.substring(pos + 1, args.length);
    981     }
    982 
    983     // Check for script breakpoint (name:line[:column]). If no ':' in break
    984     // specification it is considered a function break point.
    985     pos = target.indexOf(':');
    986     if (pos > 0) {
    987       var tmp = target.substring(pos + 1, target.length);
    988       target = target.substring(0, pos);
    989       if (target[0] == '/' && target[target.length - 1] == '/') {
    990         type = 'scriptRegExp';
    991         target = target.substring(1, target.length - 1);
    992       } else {
    993         type = 'script';
    994       }
    995 
    996       // Check for both line and column.
    997       pos = tmp.indexOf(':');
    998       if (pos > 0) {
    999         column = parseInt(tmp.substring(pos + 1, tmp.length)) - 1;
   1000         line = parseInt(tmp.substring(0, pos)) - 1;
   1001       } else {
   1002         line = parseInt(tmp) - 1;
   1003       }
   1004     } else if (target[0] == '#' && target[target.length - 1] == '#') {
   1005       type = 'handle';
   1006       target = target.substring(1, target.length - 1);
   1007     } else {
   1008       type = 'function';
   1009     }
   1010 
   1011     request.arguments = {};
   1012     request.arguments.type = type;
   1013     request.arguments.target = target;
   1014     request.arguments.line = line;
   1015     request.arguments.column = column;
   1016     request.arguments.condition = condition;
   1017   } else {
   1018     var request = this.createRequest('suspend');
   1019   }
   1020 
   1021   return request.toJSONProtocol();
   1022 };
   1023 
   1024 
   1025 DebugRequest.prototype.breakpointsCommandToJSONRequest_ = function(args) {
   1026   if (args && args.length > 0) {
   1027     throw new Error('Unexpected arguments.');
   1028   }
   1029   var request = this.createRequest('listbreakpoints');
   1030   return request.toJSONProtocol();
   1031 };
   1032 
   1033 
   1034 // Create a JSON request for the clear command.
   1035 DebugRequest.prototype.clearCommandToJSONRequest_ = function(args) {
   1036   // Build a evaluate request from the text command.
   1037   var request = this.createRequest('clearbreakpoint');
   1038 
   1039   // Process arguments if any.
   1040   if (args && args.length > 0) {
   1041     request.arguments = {};
   1042     request.arguments.breakpoint = parseInt(args);
   1043   } else {
   1044     throw new Error('Invalid break arguments.');
   1045   }
   1046 
   1047   return request.toJSONProtocol();
   1048 };
   1049 
   1050 
   1051 // Create a JSON request for the change breakpoint command.
   1052 DebugRequest.prototype.changeBreakpointCommandToJSONRequest_ =
   1053     function(args, command) {
   1054 
   1055   var request;
   1056 
   1057   // Check for exception breaks first:
   1058   //   en[able] exc[eptions] [all|unc[aught]]
   1059   //   en[able] [all|unc[aught]] exc[eptions]
   1060   //   dis[able] exc[eptions] [all|unc[aught]]
   1061   //   dis[able] [all|unc[aught]] exc[eptions]
   1062   if ((command == 'enable' || command == 'disable') &&
   1063       args && args.length > 1) {
   1064     var nextPos = args.indexOf(' ');
   1065     var arg1 = (nextPos > 0) ? args.substring(0, nextPos) : args;
   1066     var excType = null;
   1067 
   1068     // Check for:
   1069     //   en[able] exc[eptions] [all|unc[aught]]
   1070     //   dis[able] exc[eptions] [all|unc[aught]]
   1071     if (arg1 == 'exc' || arg1 == 'exception' || arg1 == 'exceptions') {
   1072 
   1073       var arg2 = (nextPos > 0) ?
   1074           args.substring(nextPos + 1, args.length) : 'all';
   1075       if (!arg2) {
   1076         arg2 = 'all'; // if unspecified, set for all.
   1077       } if (arg2 == 'unc') { // check for short cut.
   1078         arg2 = 'uncaught';
   1079       }
   1080       excType = arg2;
   1081 
   1082     // Check for:
   1083     //   en[able] [all|unc[aught]] exc[eptions]
   1084     //   dis[able] [all|unc[aught]] exc[eptions]
   1085     } else if (arg1 == 'all' || arg1 == 'unc' || arg1 == 'uncaught') {
   1086 
   1087       var arg2 = (nextPos > 0) ?
   1088           args.substring(nextPos + 1, args.length) : null;
   1089       if (arg2 == 'exc' || arg1 == 'exception' || arg1 == 'exceptions') {
   1090         excType = arg1;
   1091         if (excType == 'unc') {
   1092           excType = 'uncaught';
   1093         }
   1094       }
   1095     }
   1096 
   1097     // If we matched one of the command formats, then excType will be non-null:
   1098     if (excType) {
   1099       // Build a evaluate request from the text command.
   1100       request = this.createRequest('setexceptionbreak');
   1101 
   1102       request.arguments = {};
   1103       request.arguments.type = excType;
   1104       request.arguments.enabled = (command == 'enable');
   1105 
   1106       return request.toJSONProtocol();
   1107     }
   1108   }
   1109 
   1110   // Build a evaluate request from the text command.
   1111   request = this.createRequest('changebreakpoint');
   1112 
   1113   // Process arguments if any.
   1114   if (args && args.length > 0) {
   1115     request.arguments = {};
   1116     var pos = args.indexOf(' ');
   1117     var breakpointArg = args;
   1118     var otherArgs;
   1119     if (pos > 0) {
   1120       breakpointArg = args.substring(0, pos);
   1121       otherArgs = args.substring(pos + 1, args.length);
   1122     }
   1123 
   1124     request.arguments.breakpoint = parseInt(breakpointArg);
   1125 
   1126     switch(command) {
   1127       case 'cond':
   1128         request.arguments.condition = otherArgs ? otherArgs : null;
   1129         break;
   1130       case 'enable':
   1131         request.arguments.enabled = true;
   1132         break;
   1133       case 'disable':
   1134         request.arguments.enabled = false;
   1135         break;
   1136       case 'ignore':
   1137         request.arguments.ignoreCount = parseInt(otherArgs);
   1138         break;
   1139       default:
   1140         throw new Error('Invalid arguments.');
   1141     }
   1142   } else {
   1143     throw new Error('Invalid arguments.');
   1144   }
   1145 
   1146   return request.toJSONProtocol();
   1147 };
   1148 
   1149 
   1150 // Create a JSON request for the disconnect command.
   1151 DebugRequest.prototype.disconnectCommandToJSONRequest_ = function(args) {
   1152   var request;
   1153   request = this.createRequest('disconnect');
   1154   return request.toJSONProtocol();
   1155 };
   1156 
   1157 
   1158 // Create a JSON request for the info command.
   1159 DebugRequest.prototype.infoCommandToJSONRequest_ = function(args) {
   1160   var request;
   1161   if (args && (args == 'break' || args == 'br')) {
   1162     // Build a evaluate request from the text command.
   1163     request = this.createRequest('listbreakpoints');
   1164     last_cmd = 'info break';
   1165   } else if (args && (args == 'locals' || args == 'lo')) {
   1166     // Build a evaluate request from the text command.
   1167     request = this.createRequest('frame');
   1168     last_cmd = 'info locals';
   1169   } else if (args && (args == 'args' || args == 'ar')) {
   1170     // Build a evaluate request from the text command.
   1171     request = this.createRequest('frame');
   1172     last_cmd = 'info args';
   1173   } else if (lol_is_enabled &&
   1174              args && (args == 'liveobjectlist' || args == 'lol')) {
   1175     // Build a evaluate request from the text command.
   1176     return this.liveObjectListToJSONRequest_(null);
   1177   } else {
   1178     throw new Error('Invalid info arguments.');
   1179   }
   1180 
   1181   return request.toJSONProtocol();
   1182 };
   1183 
   1184 
   1185 DebugRequest.prototype.v8FlagsToJSONRequest_ = function(args) {
   1186   var request;
   1187   request = this.createRequest('v8flags');
   1188   request.arguments = {};
   1189   request.arguments.flags = args;
   1190   return request.toJSONProtocol();
   1191 };
   1192 
   1193 
   1194 DebugRequest.prototype.gcToJSONRequest_ = function(args) {
   1195   var request;
   1196   if (!args) {
   1197     args = 'all';
   1198   }
   1199   var args = args.split(/\s+/g);
   1200   var cmd = args[0];
   1201 
   1202   switch(cmd) {
   1203     case 'all':
   1204     case 'quick':
   1205     case 'full':
   1206     case 'young':
   1207     case 'old':
   1208     case 'compact':
   1209     case 'sweep':
   1210     case 'scavenge': {
   1211       if (cmd == 'young') { cmd = 'quick'; }
   1212       else if (cmd == 'old') { cmd = 'full'; }
   1213 
   1214       request = this.createRequest('gc');
   1215       request.arguments = {};
   1216       request.arguments.type = cmd;
   1217       break;
   1218     }
   1219       // Else fall thru to the default case below to report the error.
   1220     default:
   1221       throw new Error('Missing arguments after ' + cmd + '.');
   1222   }
   1223   return request.toJSONProtocol();
   1224 };
   1225 
   1226 
   1227 // Args: [v[erbose]] [<N>] [i[ndex] <i>] [t[ype] <type>] [sp[ace] <space>]
   1228 DebugRequest.prototype.lolMakeListRequest =
   1229     function(cmd, args, first_arg_index, is_repeating) {
   1230 
   1231   var request;
   1232   var start_index = 0;
   1233   var dump_limit = void 0;
   1234   var type_filter = void 0;
   1235   var space_filter = void 0;
   1236   var prop_filter = void 0;
   1237   var is_verbose = false;
   1238   var i;
   1239 
   1240   for (i = first_arg_index; i < args.length; i++) {
   1241     var arg = args[i];
   1242     // Check for [v[erbose]]:
   1243     if (arg === 'verbose' || arg === 'v') {
   1244       // Nothing to do.  This is already implied by args.length > 3.
   1245       is_verbose = true;
   1246 
   1247     // Check for [<N>]:
   1248     } else if (arg.match(/^[0-9]+$/)) {
   1249       dump_limit = arg;
   1250       is_verbose = true;
   1251 
   1252     // Check for i[ndex] <i>:
   1253     } else if (arg === 'index' || arg === 'i') {
   1254       i++;
   1255       if (args.length < i) {
   1256         throw new Error('Missing index after ' + arg + '.');
   1257       }
   1258       start_index = parseInt(args[i]);
   1259       // The user input start index starts at 1:
   1260       if (start_index <= 0) {
   1261         throw new Error('Invalid index ' + args[i] + '.');
   1262       }
   1263       start_index -= 1;
   1264       is_verbose = true;
   1265 
   1266     // Check for t[ype] <type>:
   1267     } else if (arg === 'type' || arg === 't') {
   1268       i++;
   1269       if (args.length < i) {
   1270         throw new Error('Missing type after ' + arg + '.');
   1271       }
   1272       type_filter = args[i];
   1273 
   1274     // Check for space <heap space name>:
   1275     } else if (arg === 'space' || arg === 'sp') {
   1276       i++;
   1277       if (args.length < i) {
   1278         throw new Error('Missing space name after ' + arg + '.');
   1279       }
   1280       space_filter = args[i];
   1281 
   1282     // Check for property <prop name>:
   1283     } else if (arg === 'property' || arg === 'prop') {
   1284       i++;
   1285       if (args.length < i) {
   1286         throw new Error('Missing property name after ' + arg + '.');
   1287       }
   1288       prop_filter = args[i];
   1289 
   1290     } else {
   1291       throw new Error('Unknown args at ' + arg + '.');
   1292     }
   1293   }
   1294 
   1295   // Build the verbose request:
   1296   if (is_verbose) {
   1297     request = this.createLOLRequest('lol-'+cmd,
   1298                                     start_index,
   1299                                     dump_limit,
   1300                                     is_repeating);
   1301     request.arguments.verbose = true;
   1302   } else {
   1303     request = this.createRequest('lol-'+cmd);
   1304     request.arguments = {};
   1305   }
   1306 
   1307   request.arguments.filter = {};
   1308   if (type_filter) {
   1309     request.arguments.filter.type = type_filter;
   1310   }
   1311   if (space_filter) {
   1312     request.arguments.filter.space = space_filter;
   1313   }
   1314   if (prop_filter) {
   1315     request.arguments.filter.prop = prop_filter;
   1316   }
   1317 
   1318   return request;
   1319 };
   1320 
   1321 
   1322 function extractObjId(args) {
   1323   var id = args;
   1324   id = id.match(/^@([0-9]+)$/);
   1325   if (id) {
   1326     id = id[1];
   1327   } else {
   1328     throw new Error('Invalid obj id ' + args + '.');
   1329   }
   1330   return parseInt(id);
   1331 }
   1332 
   1333 
   1334 DebugRequest.prototype.lolToJSONRequest_ = function(args, is_repeating) {
   1335   var request;
   1336   // Use default command if one is not specified:
   1337   if (!args) {
   1338     args = 'info';
   1339   }
   1340 
   1341   var orig_args = args;
   1342   var first_arg_index;
   1343 
   1344   var arg, i;
   1345   var args = args.split(/\s+/g);
   1346   var cmd = args[0];
   1347   var id;
   1348 
   1349   // Command: <id> [v[erbose]] ...
   1350   if (cmd.match(/^[0-9]+$/)) {
   1351     // Convert to the padded list command:
   1352     // Command: l[ist] <dummy> <id> [v[erbose]] ...
   1353 
   1354     // Insert the implicit 'list' in front and process as normal:
   1355     cmd = 'list';
   1356     args.unshift(cmd);
   1357   }
   1358 
   1359   switch(cmd) {
   1360     // Command: c[apture]
   1361     case 'capture':
   1362     case 'c':
   1363       request = this.createRequest('lol-capture');
   1364       break;
   1365 
   1366     // Command: clear|d[elete] <id>|all
   1367     case 'clear':
   1368     case 'delete':
   1369     case 'del': {
   1370       if (args.length < 2) {
   1371         throw new Error('Missing argument after ' + cmd + '.');
   1372       } else if (args.length > 2) {
   1373         throw new Error('Too many arguments after ' + cmd + '.');
   1374       }
   1375       id = args[1];
   1376       if (id.match(/^[0-9]+$/)) {
   1377         // Delete a specific lol record:
   1378         request = this.createRequest('lol-delete');
   1379         request.arguments = {};
   1380         request.arguments.id = parseInt(id);
   1381       } else if (id === 'all') {
   1382         // Delete all:
   1383         request = this.createRequest('lol-reset');
   1384       } else {
   1385         throw new Error('Invalid argument after ' + cmd + '.');
   1386       }
   1387       break;
   1388     }
   1389 
   1390     // Command: diff <id1> <id2> [<dump options>]
   1391     case 'diff':
   1392       first_arg_index = 3;
   1393 
   1394     // Command: list <dummy> <id> [<dump options>]
   1395     case 'list':
   1396 
   1397     // Command: ret[ainers] <obj id> [<dump options>]
   1398     case 'retainers':
   1399     case 'ret':
   1400     case 'retaining-paths':
   1401     case 'rp': {
   1402       if (cmd === 'ret') cmd = 'retainers';
   1403       else if (cmd === 'rp') cmd = 'retaining-paths';
   1404 
   1405       if (!first_arg_index) first_arg_index = 2;
   1406 
   1407       if (args.length < first_arg_index) {
   1408         throw new Error('Too few arguments after ' + cmd + '.');
   1409       }
   1410 
   1411       var request_cmd = (cmd === 'list') ? 'diff':cmd;
   1412       request = this.lolMakeListRequest(request_cmd,
   1413                                         args,
   1414                                         first_arg_index,
   1415                                         is_repeating);
   1416 
   1417       if (cmd === 'diff') {
   1418         request.arguments.id1 = parseInt(args[1]);
   1419         request.arguments.id2 = parseInt(args[2]);
   1420       } else if (cmd == 'list') {
   1421         request.arguments.id1 = 0;
   1422         request.arguments.id2 = parseInt(args[1]);
   1423       } else {
   1424         request.arguments.id = extractObjId(args[1]);
   1425       }
   1426       break;
   1427     }
   1428 
   1429     // Command: getid
   1430     case 'getid': {
   1431       request = this.createRequest('lol-getid');
   1432       request.arguments = {};
   1433       request.arguments.address = args[1];
   1434       break;
   1435     }
   1436 
   1437     // Command: inf[o] [<N>]
   1438     case 'info':
   1439     case 'inf': {
   1440       if (args.length > 2) {
   1441         throw new Error('Too many arguments after ' + cmd + '.');
   1442       }
   1443       // Built the info request:
   1444       request = this.createLOLRequest('lol-info', 0, args[1], is_repeating);
   1445       break;
   1446     }
   1447 
   1448     // Command: path <obj id 1> <obj id 2>
   1449     case 'path': {
   1450       request = this.createRequest('lol-path');
   1451       request.arguments = {};
   1452       if (args.length > 2) {
   1453         request.arguments.id1 = extractObjId(args[1]);
   1454         request.arguments.id2 = extractObjId(args[2]);
   1455       } else {
   1456         request.arguments.id1 = 0;
   1457         request.arguments.id2 = extractObjId(args[1]);
   1458       }
   1459       break;
   1460     }
   1461 
   1462     // Command: print
   1463     case 'print': {
   1464       request = this.createRequest('lol-print');
   1465       request.arguments = {};
   1466       request.arguments.id = extractObjId(args[1]);
   1467       break;
   1468     }
   1469 
   1470     // Command: reset
   1471     case 'reset': {
   1472       request = this.createRequest('lol-reset');
   1473       break;
   1474     }
   1475 
   1476     default:
   1477       throw new Error('Invalid arguments.');
   1478   }
   1479   return request.toJSONProtocol();
   1480 };
   1481 
   1482 
   1483 // Create a JSON request for the threads command.
   1484 DebugRequest.prototype.threadsCommandToJSONRequest_ = function(args) {
   1485   // Build a threads request from the text command.
   1486   var request = this.createRequest('threads');
   1487   return request.toJSONProtocol();
   1488 };
   1489 
   1490 
   1491 // Handle the trace command.
   1492 DebugRequest.prototype.traceCommand_ = function(args) {
   1493   // Process arguments.
   1494   if (args && args.length > 0) {
   1495     if (args == 'compile') {
   1496       trace_compile = !trace_compile;
   1497       print('Tracing of compiled scripts ' + (trace_compile ? 'on' : 'off'));
   1498     } else if (args === 'debug json' || args === 'json' || args === 'packets') {
   1499       trace_debug_json = !trace_debug_json;
   1500       print('Tracing of debug json packets ' +
   1501             (trace_debug_json ? 'on' : 'off'));
   1502     } else {
   1503       throw new Error('Invalid trace arguments.');
   1504     }
   1505   } else {
   1506     throw new Error('Invalid trace arguments.');
   1507   }
   1508 };
   1509 
   1510 // Handle the help command.
   1511 DebugRequest.prototype.helpCommand_ = function(args) {
   1512   // Help os quite simple.
   1513   if (args && args.length > 0) {
   1514     print('warning: arguments to \'help\' are ignored');
   1515   }
   1516 
   1517   print('Note: <> denotes symbollic values to be replaced with real values.');
   1518   print('Note: [] denotes optional parts of commands, or optional options / arguments.');
   1519   print('      e.g. d[elete] - you get the same command if you type d or delete.');
   1520   print('');
   1521   print('[break] - break as soon as possible');
   1522   print('b[reak] location [condition]');
   1523   print('        - break on named function: location is a function name');
   1524   print('        - break on function: location is #<id>#');
   1525   print('        - break on script position: location is name:line[:column]');
   1526   print('');
   1527   print('clear <breakpoint #>       - deletes the specified user defined breakpoint');
   1528   print('d[elete]  <breakpoint #>   - deletes the specified user defined breakpoint');
   1529   print('dis[able] <breakpoint #>   - disables the specified user defined breakpoint');
   1530   print('dis[able] exc[eptions] [[all] | unc[aught]]');
   1531   print('                           - disables breaking on exceptions');
   1532   print('en[able]  <breakpoint #>   - enables the specified user defined breakpoint');
   1533   print('en[able]  exc[eptions] [[all] | unc[aught]]');
   1534   print('                           - enables breaking on exceptions');
   1535   print('');
   1536   print('b[ack]t[race] [n] | [-n] | [from to]');
   1537   print('                           - prints the stack back trace');
   1538   print('f[rame]                    - prints info about the current frame context');
   1539   print('f[rame] <frame #>          - set context to specified frame #');
   1540   print('scopes');
   1541   print('scope <scope #>');
   1542   print('');
   1543   print('up                         - set context to caller of current frame');
   1544   print('do[wn]                     - set context to callee of current frame');
   1545   print('inf[o] br[eak]             - prints info about breakpoints in use');
   1546   print('inf[o] ar[gs]              - prints info about arguments of the current function');
   1547   print('inf[o] lo[cals]            - prints info about locals in the current function');
   1548   print('inf[o] liveobjectlist|lol  - same as \'lol info\'');
   1549   print('');
   1550   print('step [in | next | out| min [step count]]');
   1551   print('c[ontinue]                 - continue executing after a breakpoint');
   1552   print('s[tep]   [<N>]             - step into the next N callees (default N is 1)');
   1553   print('s[tep]i  [<N>]             - step into the next N callees (default N is 1)');
   1554   print('n[ext]   [<N>]             - step over the next N callees (default N is 1)');
   1555   print('fin[ish] [<N>]             - step out of N frames (default N is 1)');
   1556   print('');
   1557   print('p[rint] <expression>       - prints the result of the specified expression');
   1558   print('dir <expression>           - prints the object structure of the result');
   1559   print('set <var> = <expression>   - executes the specified statement');
   1560   print('');
   1561   print('l[ist]                     - list the source code around for the current pc');
   1562   print('l[ist] [- | <start>,<end>] - list the specified range of source code');
   1563   print('source [from line [num lines]]');
   1564   print('scr[ipts] [native|extensions|all]');
   1565   print('scr[ipts] [<filter text>]  - list scripts with the specified text in its description');
   1566   print('');
   1567   print('gc                         - runs the garbage collector');
   1568   print('');
   1569 
   1570   if (lol_is_enabled) {
   1571     print('liveobjectlist|lol <command> - live object list tracking.');
   1572     print('  where <command> can be:');
   1573     print('  c[apture]               - captures a LOL list.');
   1574     print('  clear|del[ete] <id>|all - clears LOL of id <id>.');
   1575     print('                            If \'all\' is unspecified instead, will clear all.');
   1576     print('  diff <id1> <id2> [<dump options>]');
   1577     print('                          - prints the diff between LOLs id1 and id2.');
   1578     print('                          - also see <dump options> below.');
   1579     print('  getid <address>         - gets the obj id for the specified address if available.');
   1580     print('                            The address must be in hex form prefixed with 0x.');
   1581     print('  inf[o] [<N>]            - lists summary info of all LOL lists.');
   1582     print('                            If N is specified, will print N items at a time.');
   1583     print('  [l[ist]] <id> [<dump options>]');
   1584     print('                          - prints the listing of objects in LOL id.');
   1585     print('                          - also see <dump options> below.');
   1586     print('  reset                   - clears all LOL lists.');
   1587     print('  ret[ainers] <id> [<dump options>]');
   1588     print('                          - prints the list of retainers of obj id.');
   1589     print('                          - also see <dump options> below.');
   1590     print('  path <id1> <id2>        - prints the retaining path from obj id1 to id2.');
   1591     print('                            If only one id is specified, will print the path from');
   1592     print('                            roots to the specified object if available.');
   1593     print('  print <id>              - prints the obj for the specified obj id if available.');
   1594     print('');
   1595     print('  <dump options> includes:');
   1596     print('     [v[erbose]]            - do verbose dump.');
   1597     print('     [<N>]                  - dump N items at a time.  Implies verbose dump.');
   1598     print('                             If unspecified, N will default to '+
   1599           kDefaultLolLinesToPrintAtATime+'.  Max N is '+
   1600           kMaxLolLinesToPrintAtATime+'.');
   1601     print('     [i[ndex] <i>]          - start dump from index i.  Implies verbose dump.');
   1602     print('     [t[ype] <type>]        - filter by type.');
   1603     print('     [sp[ace] <space name>] - filter by heap space where <space name> is one of');
   1604     print('                              { cell, code, lo, map, new, old-data, old-pointer }.');
   1605     print('');
   1606     print('     If the verbose option, or an option that implies a verbose dump');
   1607     print('     is specified, then a verbose dump will requested.  Else, a summary dump');
   1608     print('     will be requested.');
   1609     print('');
   1610   }
   1611 
   1612   print('trace compile');
   1613   // hidden command: trace debug json - toggles tracing of debug json packets
   1614   print('');
   1615   print('disconnect|exit|quit       - disconnects and quits the debugger');
   1616   print('help                       - prints this help information');
   1617 };
   1618 
   1619 
   1620 function formatHandleReference_(value) {
   1621   if (value.handle() >= 0) {
   1622     return '#' + value.handle() + '#';
   1623   } else {
   1624     return '#Transient#';
   1625   }
   1626 }
   1627 
   1628 
   1629 function formatObject_(value, include_properties) {
   1630   var result = '';
   1631   result += formatHandleReference_(value);
   1632   result += ', type: object';
   1633   result += ', constructor ';
   1634   var ctor = value.constructorFunctionValue();
   1635   result += formatHandleReference_(ctor);
   1636   result += ', __proto__ ';
   1637   var proto = value.protoObjectValue();
   1638   result += formatHandleReference_(proto);
   1639   result += ', ';
   1640   result += value.propertyCount();
   1641   result +=  ' properties.';
   1642   if (include_properties) {
   1643     result +=  '\n';
   1644     for (var i = 0; i < value.propertyCount(); i++) {
   1645       result += '  ';
   1646       result += value.propertyName(i);
   1647       result += ': ';
   1648       var property_value = value.propertyValue(i);
   1649       if (property_value instanceof ProtocolReference) {
   1650         result += '<no type>';
   1651       } else {
   1652         if (property_value && property_value.type()) {
   1653           result += property_value.type();
   1654         } else {
   1655           result += '<no type>';
   1656         }
   1657       }
   1658       result += ' ';
   1659       result += formatHandleReference_(property_value);
   1660       result += '\n';
   1661     }
   1662   }
   1663   return result;
   1664 }
   1665 
   1666 
   1667 function formatScope_(scope) {
   1668   var result = '';
   1669   var index = scope.index;
   1670   result += '#' + (index <= 9 ? '0' : '') + index;
   1671   result += ' ';
   1672   switch (scope.type) {
   1673     case Debug.ScopeType.Global:
   1674       result += 'Global, ';
   1675       result += '#' + scope.object.ref + '#';
   1676       break;
   1677     case Debug.ScopeType.Local:
   1678       result += 'Local';
   1679       break;
   1680     case Debug.ScopeType.With:
   1681       result += 'With, ';
   1682       result += '#' + scope.object.ref + '#';
   1683       break;
   1684     case Debug.ScopeType.Catch:
   1685       result += 'Catch, ';
   1686       result += '#' + scope.object.ref + '#';
   1687       break;
   1688     case Debug.ScopeType.Closure:
   1689       result += 'Closure';
   1690       break;
   1691     default:
   1692       result += 'UNKNOWN';
   1693   }
   1694   return result;
   1695 }
   1696 
   1697 
   1698 function refObjectToString_(protocolPackage, handle) {
   1699   var value = protocolPackage.lookup(handle);
   1700   var result = '';
   1701   if (value.isString()) {
   1702     result = '"' + value.value() + '"';
   1703   } else if (value.isPrimitive()) {
   1704     result = value.valueString();
   1705   } else if (value.isObject()) {
   1706     result += formatObject_(value, true);
   1707   }
   1708   return result;
   1709 }
   1710 
   1711 
   1712 function decodeLolCaptureResponse(body) {
   1713   var result;
   1714   result = 'Captured live object list '+ body.id +
   1715            ': count '+ body.count + ' size ' + body.size;
   1716   return result;
   1717 }
   1718 
   1719 
   1720 function decodeLolDeleteResponse(body) {
   1721   var result;
   1722   result = 'Deleted live object list '+ body.id;
   1723   return result;
   1724 }
   1725 
   1726 
   1727 function digitsIn(value) {
   1728   var digits = 0;
   1729   if (value === 0) value = 1;
   1730   while (value >= 1) {
   1731     digits++;
   1732     value /= 10;
   1733   }
   1734   return digits;
   1735 }
   1736 
   1737 
   1738 function padding(value, max_digits) {
   1739   var padding_digits = max_digits - digitsIn(value);
   1740   var padding = '';
   1741   while (padding_digits > 0) {
   1742     padding += ' ';
   1743     padding_digits--;
   1744   }
   1745   return padding;
   1746 }
   1747 
   1748 
   1749 function decodeLolInfoResponse(body) {
   1750   var result;
   1751   var lists = body.lists;
   1752   var length = lists.length;
   1753   var first_index = body.first_index + 1;
   1754   var has_more = ((first_index + length) <= body.count);
   1755   result = 'captured live object lists';
   1756   if (has_more || (first_index != 1)) {
   1757     result += ' ['+ length +' of '+ body.count +
   1758               ': starting from '+ first_index +']';
   1759   }
   1760   result += ':\n';
   1761   var max_digits = digitsIn(body.count);
   1762   var last_count = 0;
   1763   var last_size = 0;
   1764   for (var i = 0; i < length; i++) {
   1765     var entry = lists[i];
   1766     var count = entry.count;
   1767     var size = entry.size;
   1768     var index = first_index + i;
   1769     result += '  [' + padding(index, max_digits) + index + '] id '+ entry.id +
   1770               ': count '+ count;
   1771     if (last_count > 0) {
   1772       result += '(+' + (count - last_count) + ')';
   1773     }
   1774     result += ' size '+ size;
   1775     if (last_size > 0) {
   1776       result += '(+' + (size - last_size) + ')';
   1777     }
   1778     result += '\n';
   1779     last_count = count;
   1780     last_size = size;
   1781   }
   1782   result += '  total: '+length+' lists\n';
   1783   if (has_more) {
   1784     result += '  -- press <enter> for more --\n';
   1785   } else {
   1786     repeat_cmd_line = '';
   1787   }
   1788   if (length === 0) result += '  none\n';
   1789 
   1790   return result;
   1791 }
   1792 
   1793 
   1794 function decodeLolListResponse(body, title) {
   1795 
   1796   var result;
   1797   var total_count = body.count;
   1798   var total_size = body.size;
   1799   var length;
   1800   var max_digits;
   1801   var i;
   1802   var entry;
   1803   var index;
   1804 
   1805   var max_count_digits = digitsIn(total_count);
   1806   var max_size_digits;
   1807 
   1808   var summary = body.summary;
   1809   if (summary) {
   1810 
   1811     var roots_count = 0;
   1812     var found_root = body.found_root || 0;
   1813     var found_weak_root = body.found_weak_root || 0;
   1814 
   1815     // Print the summary result:
   1816     result = 'summary of objects:\n';
   1817     length = summary.length;
   1818     if (found_root !== 0) {
   1819       roots_count++;
   1820     }
   1821     if (found_weak_root !== 0) {
   1822       roots_count++;
   1823     }
   1824     max_digits = digitsIn(length + roots_count);
   1825     max_size_digits = digitsIn(total_size);
   1826 
   1827     index = 1;
   1828     if (found_root !== 0) {
   1829       result += '  [' + padding(index, max_digits) + index + '] ' +
   1830                 ' count '+ 1 + padding(0, max_count_digits) +
   1831                 '      '+ padding(0, max_size_digits+1) +
   1832                 ' : <root>\n';
   1833       index++;
   1834     }
   1835     if (found_weak_root !== 0) {
   1836       result += '  [' + padding(index, max_digits) + index + '] ' +
   1837                 ' count '+ 1 + padding(0, max_count_digits) +
   1838                 '      '+ padding(0, max_size_digits+1) +
   1839                 ' : <weak root>\n';
   1840       index++;
   1841     }
   1842 
   1843     for (i = 0; i < length; i++) {
   1844       entry = summary[i];
   1845       var count = entry.count;
   1846       var size = entry.size;
   1847       result += '  [' + padding(index, max_digits) + index + '] ' +
   1848                 ' count '+ count + padding(count, max_count_digits) +
   1849                 ' size '+ size + padding(size, max_size_digits) +
   1850                 ' : <' + entry.desc + '>\n';
   1851       index++;
   1852     }
   1853     result += '\n  total count: '+(total_count+roots_count)+'\n';
   1854     if (body.size) {
   1855       result += '  total size:  '+body.size+'\n';
   1856     }
   1857 
   1858   } else {
   1859     // Print the full dump result:
   1860     var first_index = body.first_index + 1;
   1861     var elements = body.elements;
   1862     length = elements.length;
   1863     var has_more = ((first_index + length) <= total_count);
   1864     result = title;
   1865     if (has_more || (first_index != 1)) {
   1866       result += ' ['+ length +' of '+ total_count +
   1867                 ': starting from '+ first_index +']';
   1868     }
   1869     result += ':\n';
   1870     if (length === 0) result += '  none\n';
   1871     max_digits = digitsIn(length);
   1872 
   1873     var max_id = 0;
   1874     var max_size = 0;
   1875     for (i = 0; i < length; i++) {
   1876       entry = elements[i];
   1877       if (entry.id > max_id) max_id = entry.id;
   1878       if (entry.size > max_size) max_size = entry.size;
   1879     }
   1880     var max_id_digits = digitsIn(max_id);
   1881     max_size_digits = digitsIn(max_size);
   1882 
   1883     for (i = 0; i < length; i++) {
   1884       entry = elements[i];
   1885       index = first_index + i;
   1886       result += '  ['+ padding(index, max_digits) + index +']';
   1887       if (entry.id !== 0) {
   1888         result += ' @' + entry.id + padding(entry.id, max_id_digits) +
   1889                   ': size ' + entry.size + ', ' +
   1890                   padding(entry.size, max_size_digits) +  entry.desc + '\n';
   1891       } else {
   1892         // Must be a root or weak root:
   1893         result += ' ' + entry.desc + '\n';
   1894       }
   1895     }
   1896     if (has_more) {
   1897       result += '  -- press <enter> for more --\n';
   1898     } else {
   1899       repeat_cmd_line = '';
   1900     }
   1901     if (length === 0) result += '  none\n';
   1902   }
   1903 
   1904   return result;
   1905 }
   1906 
   1907 
   1908 function decodeLolDiffResponse(body) {
   1909   var title = 'objects';
   1910   return decodeLolListResponse(body, title);
   1911 }
   1912 
   1913 
   1914 function decodeLolRetainersResponse(body) {
   1915   var title = 'retainers for @' + body.id;
   1916   return decodeLolListResponse(body, title);
   1917 }
   1918 
   1919 
   1920 function decodeLolPathResponse(body) {
   1921   return body.path;
   1922 }
   1923 
   1924 
   1925 function decodeLolResetResponse(body) {
   1926   return 'Reset all live object lists.';
   1927 }
   1928 
   1929 
   1930 function decodeLolGetIdResponse(body) {
   1931   if (body.id == 0) {
   1932     return 'Address is invalid, or object has been moved or collected';
   1933   }
   1934   return 'obj id is @' + body.id;
   1935 }
   1936 
   1937 
   1938 function decodeLolPrintResponse(body) {
   1939   return body.dump;
   1940 }
   1941 
   1942 
   1943 // Rounds number 'num' to 'length' decimal places.
   1944 function roundNumber(num, length) {
   1945   var factor = Math.pow(10, length);
   1946   return Math.round(num * factor) / factor;
   1947 }
   1948 
   1949 
   1950 // Convert a JSON response to text for display in a text based debugger.
   1951 function DebugResponseDetails(response) {
   1952   var details = { text: '', running: false };
   1953 
   1954   try {
   1955     if (!response.success()) {
   1956       details.text = response.message();
   1957       return details;
   1958     }
   1959 
   1960     // Get the running state.
   1961     details.running = response.running();
   1962 
   1963     var body = response.body();
   1964     var result = '';
   1965     switch (response.command()) {
   1966       case 'suspend':
   1967         details.text = 'stopped';
   1968         break;
   1969 
   1970       case 'setbreakpoint':
   1971         result = 'set breakpoint #';
   1972         result += body.breakpoint;
   1973         details.text = result;
   1974         break;
   1975 
   1976       case 'clearbreakpoint':
   1977         result = 'cleared breakpoint #';
   1978         result += body.breakpoint;
   1979         details.text = result;
   1980         break;
   1981 
   1982       case 'changebreakpoint':
   1983         result = 'successfully changed breakpoint';
   1984         details.text = result;
   1985         break;
   1986 
   1987       case 'listbreakpoints':
   1988         result = 'breakpoints: (' + body.breakpoints.length + ')';
   1989         for (var i = 0; i < body.breakpoints.length; i++) {
   1990           var breakpoint = body.breakpoints[i];
   1991           result += '\n id=' + breakpoint.number;
   1992           result += ' type=' + breakpoint.type;
   1993           if (breakpoint.script_id) {
   1994               result += ' script_id=' + breakpoint.script_id;
   1995           }
   1996           if (breakpoint.script_name) {
   1997               result += ' script_name=' + breakpoint.script_name;
   1998           }
   1999           if (breakpoint.script_regexp) {
   2000               result += ' script_regexp=' + breakpoint.script_regexp;
   2001           }
   2002           result += ' line=' + (breakpoint.line + 1);
   2003           if (breakpoint.column != null) {
   2004             result += ' column=' + (breakpoint.column + 1);
   2005           }
   2006           if (breakpoint.groupId) {
   2007             result += ' groupId=' + breakpoint.groupId;
   2008           }
   2009           if (breakpoint.ignoreCount) {
   2010               result += ' ignoreCount=' + breakpoint.ignoreCount;
   2011           }
   2012           if (breakpoint.active === false) {
   2013             result += ' inactive';
   2014           }
   2015           if (breakpoint.condition) {
   2016             result += ' condition=' + breakpoint.condition;
   2017           }
   2018           result += ' hit_count=' + breakpoint.hit_count;
   2019         }
   2020         if (body.breakpoints.length === 0) {
   2021           result = "No user defined breakpoints\n";
   2022         } else {
   2023           result += '\n';
   2024         }
   2025         if (body.breakOnExceptions) {
   2026           result += '* breaking on ALL exceptions is enabled\n';
   2027         } else if (body.breakOnUncaughtExceptions) {
   2028           result += '* breaking on UNCAUGHT exceptions is enabled\n';
   2029         } else {
   2030           result += '* all exception breakpoints are disabled\n';
   2031         }
   2032         details.text = result;
   2033         break;
   2034 
   2035       case 'setexceptionbreak':
   2036         result = 'Break on ' + body.type + ' exceptions: ';
   2037         result += body.enabled ? 'enabled' : 'disabled';
   2038         details.text = result;
   2039         break;
   2040 
   2041       case 'backtrace':
   2042         if (body.totalFrames == 0) {
   2043           result = '(empty stack)';
   2044         } else {
   2045           var result = 'Frames #' + body.fromFrame + ' to #' +
   2046               (body.toFrame - 1) + ' of ' + body.totalFrames + '\n';
   2047           for (i = 0; i < body.frames.length; i++) {
   2048             if (i != 0) result += '\n';
   2049             result += body.frames[i].text;
   2050           }
   2051         }
   2052         details.text = result;
   2053         break;
   2054 
   2055       case 'frame':
   2056         if (last_cmd === 'info locals') {
   2057           var locals = body.locals;
   2058           if (locals.length === 0) {
   2059             result = 'No locals';
   2060           } else {
   2061             for (var i = 0; i < locals.length; i++) {
   2062               var local = locals[i];
   2063               result += local.name + ' = ';
   2064               result += refObjectToString_(response, local.value.ref);
   2065               result += '\n';
   2066             }
   2067           }
   2068         } else if (last_cmd === 'info args') {
   2069           var args = body.arguments;
   2070           if (args.length === 0) {
   2071             result = 'No arguments';
   2072           } else {
   2073             for (var i = 0; i < args.length; i++) {
   2074               var arg = args[i];
   2075               result += arg.name + ' = ';
   2076               result += refObjectToString_(response, arg.value.ref);
   2077               result += '\n';
   2078             }
   2079           }
   2080         } else {
   2081           result = SourceUnderline(body.sourceLineText,
   2082                                    body.column);
   2083           Debug.State.currentSourceLine = body.line;
   2084           Debug.State.currentFrame = body.index;
   2085           Debug.State.displaySourceStartLine = -1;
   2086           Debug.State.displaySourceEndLine = -1;
   2087         }
   2088         details.text = result;
   2089         break;
   2090 
   2091       case 'scopes':
   2092         if (body.totalScopes == 0) {
   2093           result = '(no scopes)';
   2094         } else {
   2095           result = 'Scopes #' + body.fromScope + ' to #' +
   2096                    (body.toScope - 1) + ' of ' + body.totalScopes + '\n';
   2097           for (i = 0; i < body.scopes.length; i++) {
   2098             if (i != 0) {
   2099               result += '\n';
   2100             }
   2101             result += formatScope_(body.scopes[i]);
   2102           }
   2103         }
   2104         details.text = result;
   2105         break;
   2106 
   2107       case 'scope':
   2108         result += formatScope_(body);
   2109         result += '\n';
   2110         var scope_object_value = response.lookup(body.object.ref);
   2111         result += formatObject_(scope_object_value, true);
   2112         details.text = result;
   2113         break;
   2114 
   2115       case 'evaluate':
   2116       case 'lookup':
   2117       case 'getobj':
   2118         if (last_cmd == 'p' || last_cmd == 'print') {
   2119           result = body.text;
   2120         } else {
   2121           var value;
   2122           if (lookup_handle) {
   2123             value = response.bodyValue(lookup_handle);
   2124           } else {
   2125             value = response.bodyValue();
   2126           }
   2127           if (value.isObject()) {
   2128             result += formatObject_(value, true);
   2129           } else {
   2130             result += 'type: ';
   2131             result += value.type();
   2132             if (!value.isUndefined() && !value.isNull()) {
   2133               result += ', ';
   2134               if (value.isString()) {
   2135                 result += '"';
   2136               }
   2137               result += value.value();
   2138               if (value.isString()) {
   2139                 result += '"';
   2140               }
   2141             }
   2142             result += '\n';
   2143           }
   2144         }
   2145         details.text = result;
   2146         break;
   2147 
   2148       case 'references':
   2149         var count = body.length;
   2150         result += 'found ' + count + ' objects';
   2151         result += '\n';
   2152         for (var i = 0; i < count; i++) {
   2153           var value = response.bodyValue(i);
   2154           result += formatObject_(value, false);
   2155           result += '\n';
   2156         }
   2157         details.text = result;
   2158         break;
   2159 
   2160       case 'source':
   2161         // Get the source from the response.
   2162         var source = body.source;
   2163         var from_line = body.fromLine + 1;
   2164         var lines = source.split('\n');
   2165         var maxdigits = 1 + Math.floor(log10(from_line + lines.length));
   2166         if (maxdigits < 3) {
   2167           maxdigits = 3;
   2168         }
   2169         var result = '';
   2170         for (var num = 0; num < lines.length; num++) {
   2171           // Check if there's an extra newline at the end.
   2172           if (num == (lines.length - 1) && lines[num].length == 0) {
   2173             break;
   2174           }
   2175 
   2176           var current_line = from_line + num;
   2177           spacer = maxdigits - (1 + Math.floor(log10(current_line)));
   2178           if (current_line == Debug.State.currentSourceLine + 1) {
   2179             for (var i = 0; i < maxdigits; i++) {
   2180               result += '>';
   2181             }
   2182             result += '  ';
   2183           } else {
   2184             for (var i = 0; i < spacer; i++) {
   2185               result += ' ';
   2186             }
   2187             result += current_line + ': ';
   2188           }
   2189           result += lines[num];
   2190           result += '\n';
   2191         }
   2192         details.text = result;
   2193         break;
   2194 
   2195       case 'scripts':
   2196         var result = '';
   2197         for (i = 0; i < body.length; i++) {
   2198           if (i != 0) result += '\n';
   2199           if (body[i].id) {
   2200             result += body[i].id;
   2201           } else {
   2202             result += '[no id]';
   2203           }
   2204           result += ', ';
   2205           if (body[i].name) {
   2206             result += body[i].name;
   2207           } else {
   2208             if (body[i].compilationType == Debug.ScriptCompilationType.Eval
   2209                 && body[i].evalFromScript
   2210                 ) {
   2211               result += 'eval from ';
   2212               var script_value = response.lookup(body[i].evalFromScript.ref);
   2213               result += ' ' + script_value.field('name');
   2214               result += ':' + (body[i].evalFromLocation.line + 1);
   2215               result += ':' + body[i].evalFromLocation.column;
   2216             } else if (body[i].compilationType ==
   2217                        Debug.ScriptCompilationType.JSON) {
   2218               result += 'JSON ';
   2219             } else {  // body[i].compilation == Debug.ScriptCompilationType.Host
   2220               result += '[unnamed] ';
   2221             }
   2222           }
   2223           result += ' (lines: ';
   2224           result += body[i].lineCount;
   2225           result += ', length: ';
   2226           result += body[i].sourceLength;
   2227           if (body[i].type == Debug.ScriptType.Native) {
   2228             result += ', native';
   2229           } else if (body[i].type == Debug.ScriptType.Extension) {
   2230             result += ', extension';
   2231           }
   2232           result += '), [';
   2233           var sourceStart = body[i].sourceStart;
   2234           if (sourceStart.length > 40) {
   2235             sourceStart = sourceStart.substring(0, 37) + '...';
   2236           }
   2237           result += sourceStart;
   2238           result += ']';
   2239         }
   2240         if (body.length == 0) {
   2241           result = "no matching scripts found";
   2242         }
   2243         details.text = result;
   2244         break;
   2245 
   2246       case 'threads':
   2247         var result = 'Active V8 threads: ' + body.totalThreads + '\n';
   2248         body.threads.sort(function(a, b) { return a.id - b.id; });
   2249         for (i = 0; i < body.threads.length; i++) {
   2250           result += body.threads[i].current ? '*' : ' ';
   2251           result += ' ';
   2252           result += body.threads[i].id;
   2253           result += '\n';
   2254         }
   2255         details.text = result;
   2256         break;
   2257 
   2258       case 'continue':
   2259         details.text = "(running)";
   2260         break;
   2261 
   2262       case 'v8flags':
   2263         details.text = "flags set";
   2264         break;
   2265 
   2266       case 'gc':
   2267         details.text = "GC " + body.before + " => " + body.after;
   2268         if (body.after > (1024*1024)) {
   2269           details.text +=
   2270               " (" + roundNumber(body.before/(1024*1024), 1) + "M => " +
   2271                      roundNumber(body.after/(1024*1024), 1) + "M)";
   2272         } else if (body.after > 1024) {
   2273           details.text +=
   2274               " (" + roundNumber(body.before/1024, 1) + "K => " +
   2275                      roundNumber(body.after/1024, 1) + "K)";
   2276         }
   2277         break;
   2278 
   2279       case 'lol-capture':
   2280         details.text = decodeLolCaptureResponse(body);
   2281         break;
   2282       case 'lol-delete':
   2283         details.text = decodeLolDeleteResponse(body);
   2284         break;
   2285       case 'lol-diff':
   2286         details.text = decodeLolDiffResponse(body);
   2287         break;
   2288       case 'lol-getid':
   2289         details.text = decodeLolGetIdResponse(body);
   2290         break;
   2291       case 'lol-info':
   2292         details.text = decodeLolInfoResponse(body);
   2293         break;
   2294       case 'lol-print':
   2295         details.text = decodeLolPrintResponse(body);
   2296         break;
   2297       case 'lol-reset':
   2298         details.text = decodeLolResetResponse(body);
   2299         break;
   2300       case 'lol-retainers':
   2301         details.text = decodeLolRetainersResponse(body);
   2302         break;
   2303       case 'lol-path':
   2304         details.text = decodeLolPathResponse(body);
   2305         break;
   2306 
   2307       default:
   2308         details.text =
   2309             'Response for unknown command \'' + response.command() + '\'' +
   2310             ' (' + response.raw_json() + ')';
   2311     }
   2312   } catch (e) {
   2313     details.text = 'Error: "' + e + '" formatting response';
   2314   }
   2315 
   2316   return details;
   2317 }
   2318 
   2319 
   2320 /**
   2321  * Protocol packages send from the debugger.
   2322  * @param {string} json - raw protocol packet as JSON string.
   2323  * @constructor
   2324  */
   2325 function ProtocolPackage(json) {
   2326   this.raw_json_ = json;
   2327   this.packet_ = JSON.parse(json);
   2328   this.refs_ = [];
   2329   if (this.packet_.refs) {
   2330     for (var i = 0; i < this.packet_.refs.length; i++) {
   2331       this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i];
   2332     }
   2333   }
   2334 }
   2335 
   2336 
   2337 /**
   2338  * Get the packet type.
   2339  * @return {String} the packet type
   2340  */
   2341 ProtocolPackage.prototype.type = function() {
   2342   return this.packet_.type;
   2343 };
   2344 
   2345 
   2346 /**
   2347  * Get the packet event.
   2348  * @return {Object} the packet event
   2349  */
   2350 ProtocolPackage.prototype.event = function() {
   2351   return this.packet_.event;
   2352 };
   2353 
   2354 
   2355 /**
   2356  * Get the packet request sequence.
   2357  * @return {number} the packet request sequence
   2358  */
   2359 ProtocolPackage.prototype.requestSeq = function() {
   2360   return this.packet_.request_seq;
   2361 };
   2362 
   2363 
   2364 /**
   2365  * Get the packet request sequence.
   2366  * @return {number} the packet request sequence
   2367  */
   2368 ProtocolPackage.prototype.running = function() {
   2369   return this.packet_.running ? true : false;
   2370 };
   2371 
   2372 
   2373 ProtocolPackage.prototype.success = function() {
   2374   return this.packet_.success ? true : false;
   2375 };
   2376 
   2377 
   2378 ProtocolPackage.prototype.message = function() {
   2379   return this.packet_.message;
   2380 };
   2381 
   2382 
   2383 ProtocolPackage.prototype.command = function() {
   2384   return this.packet_.command;
   2385 };
   2386 
   2387 
   2388 ProtocolPackage.prototype.body = function() {
   2389   return this.packet_.body;
   2390 };
   2391 
   2392 
   2393 ProtocolPackage.prototype.bodyValue = function(index) {
   2394   if (index != null) {
   2395     return new ProtocolValue(this.packet_.body[index], this);
   2396   } else {
   2397     return new ProtocolValue(this.packet_.body, this);
   2398   }
   2399 };
   2400 
   2401 
   2402 ProtocolPackage.prototype.body = function() {
   2403   return this.packet_.body;
   2404 };
   2405 
   2406 
   2407 ProtocolPackage.prototype.lookup = function(handle) {
   2408   var value = this.refs_[handle];
   2409   if (value) {
   2410     return new ProtocolValue(value, this);
   2411   } else {
   2412     return new ProtocolReference(handle);
   2413   }
   2414 };
   2415 
   2416 
   2417 ProtocolPackage.prototype.raw_json = function() {
   2418   return this.raw_json_;
   2419 };
   2420 
   2421 
   2422 function ProtocolValue(value, packet) {
   2423   this.value_ = value;
   2424   this.packet_ = packet;
   2425 }
   2426 
   2427 
   2428 /**
   2429  * Get the value type.
   2430  * @return {String} the value type
   2431  */
   2432 ProtocolValue.prototype.type = function() {
   2433   return this.value_.type;
   2434 };
   2435 
   2436 
   2437 /**
   2438  * Get a metadata field from a protocol value.
   2439  * @return {Object} the metadata field value
   2440  */
   2441 ProtocolValue.prototype.field = function(name) {
   2442   return this.value_[name];
   2443 };
   2444 
   2445 
   2446 /**
   2447  * Check is the value is a primitive value.
   2448  * @return {boolean} true if the value is primitive
   2449  */
   2450 ProtocolValue.prototype.isPrimitive = function() {
   2451   return this.isUndefined() || this.isNull() || this.isBoolean() ||
   2452          this.isNumber() || this.isString();
   2453 };
   2454 
   2455 
   2456 /**
   2457  * Get the object handle.
   2458  * @return {number} the value handle
   2459  */
   2460 ProtocolValue.prototype.handle = function() {
   2461   return this.value_.handle;
   2462 };
   2463 
   2464 
   2465 /**
   2466  * Check is the value is undefined.
   2467  * @return {boolean} true if the value is undefined
   2468  */
   2469 ProtocolValue.prototype.isUndefined = function() {
   2470   return this.value_.type == 'undefined';
   2471 };
   2472 
   2473 
   2474 /**
   2475  * Check is the value is null.
   2476  * @return {boolean} true if the value is null
   2477  */
   2478 ProtocolValue.prototype.isNull = function() {
   2479   return this.value_.type == 'null';
   2480 };
   2481 
   2482 
   2483 /**
   2484  * Check is the value is a boolean.
   2485  * @return {boolean} true if the value is a boolean
   2486  */
   2487 ProtocolValue.prototype.isBoolean = function() {
   2488   return this.value_.type == 'boolean';
   2489 };
   2490 
   2491 
   2492 /**
   2493  * Check is the value is a number.
   2494  * @return {boolean} true if the value is a number
   2495  */
   2496 ProtocolValue.prototype.isNumber = function() {
   2497   return this.value_.type == 'number';
   2498 };
   2499 
   2500 
   2501 /**
   2502  * Check is the value is a string.
   2503  * @return {boolean} true if the value is a string
   2504  */
   2505 ProtocolValue.prototype.isString = function() {
   2506   return this.value_.type == 'string';
   2507 };
   2508 
   2509 
   2510 /**
   2511  * Check is the value is an object.
   2512  * @return {boolean} true if the value is an object
   2513  */
   2514 ProtocolValue.prototype.isObject = function() {
   2515   return this.value_.type == 'object' || this.value_.type == 'function' ||
   2516          this.value_.type == 'error' || this.value_.type == 'regexp';
   2517 };
   2518 
   2519 
   2520 /**
   2521  * Get the constructor function
   2522  * @return {ProtocolValue} constructor function
   2523  */
   2524 ProtocolValue.prototype.constructorFunctionValue = function() {
   2525   var ctor = this.value_.constructorFunction;
   2526   return this.packet_.lookup(ctor.ref);
   2527 };
   2528 
   2529 
   2530 /**
   2531  * Get the __proto__ value
   2532  * @return {ProtocolValue} __proto__ value
   2533  */
   2534 ProtocolValue.prototype.protoObjectValue = function() {
   2535   var proto = this.value_.protoObject;
   2536   return this.packet_.lookup(proto.ref);
   2537 };
   2538 
   2539 
   2540 /**
   2541  * Get the number og properties.
   2542  * @return {number} the number of properties
   2543  */
   2544 ProtocolValue.prototype.propertyCount = function() {
   2545   return this.value_.properties ? this.value_.properties.length : 0;
   2546 };
   2547 
   2548 
   2549 /**
   2550  * Get the specified property name.
   2551  * @return {string} property name
   2552  */
   2553 ProtocolValue.prototype.propertyName = function(index) {
   2554   var property = this.value_.properties[index];
   2555   return property.name;
   2556 };
   2557 
   2558 
   2559 /**
   2560  * Return index for the property name.
   2561  * @param name The property name to look for
   2562  * @return {number} index for the property name
   2563  */
   2564 ProtocolValue.prototype.propertyIndex = function(name) {
   2565   for (var i = 0; i < this.propertyCount(); i++) {
   2566     if (this.value_.properties[i].name == name) {
   2567       return i;
   2568     }
   2569   }
   2570   return null;
   2571 };
   2572 
   2573 
   2574 /**
   2575  * Get the specified property value.
   2576  * @return {ProtocolValue} property value
   2577  */
   2578 ProtocolValue.prototype.propertyValue = function(index) {
   2579   var property = this.value_.properties[index];
   2580   return this.packet_.lookup(property.ref);
   2581 };
   2582 
   2583 
   2584 /**
   2585  * Check is the value is a string.
   2586  * @return {boolean} true if the value is a string
   2587  */
   2588 ProtocolValue.prototype.value = function() {
   2589   return this.value_.value;
   2590 };
   2591 
   2592 
   2593 ProtocolValue.prototype.valueString = function() {
   2594   return this.value_.text;
   2595 };
   2596 
   2597 
   2598 function ProtocolReference(handle) {
   2599   this.handle_ = handle;
   2600 }
   2601 
   2602 
   2603 ProtocolReference.prototype.handle = function() {
   2604   return this.handle_;
   2605 };
   2606 
   2607 
   2608 function MakeJSONPair_(name, value) {
   2609   return '"' + name + '":' + value;
   2610 }
   2611 
   2612 
   2613 function ArrayToJSONObject_(content) {
   2614   return '{' + content.join(',') + '}';
   2615 }
   2616 
   2617 
   2618 function ArrayToJSONArray_(content) {
   2619   return '[' + content.join(',') + ']';
   2620 }
   2621 
   2622 
   2623 function BooleanToJSON_(value) {
   2624   return String(value);
   2625 }
   2626 
   2627 
   2628 function NumberToJSON_(value) {
   2629   return String(value);
   2630 }
   2631 
   2632 
   2633 // Mapping of some control characters to avoid the \uXXXX syntax for most
   2634 // commonly used control cahracters.
   2635 var ctrlCharMap_ = {
   2636   '\b': '\\b',
   2637   '\t': '\\t',
   2638   '\n': '\\n',
   2639   '\f': '\\f',
   2640   '\r': '\\r',
   2641   '"' : '\\"',
   2642   '\\': '\\\\'
   2643 };
   2644 
   2645 
   2646 // Regular expression testing for ", \ and control characters (0x00 - 0x1F).
   2647 var ctrlCharTest_ = new RegExp('["\\\\\x00-\x1F]');
   2648 
   2649 
   2650 // Regular expression matching ", \ and control characters (0x00 - 0x1F)
   2651 // globally.
   2652 var ctrlCharMatch_ = new RegExp('["\\\\\x00-\x1F]', 'g');
   2653 
   2654 
   2655 /**
   2656  * Convert a String to its JSON representation (see http://www.json.org/). To
   2657  * avoid depending on the String object this method calls the functions in
   2658  * string.js directly and not through the value.
   2659  * @param {String} value The String value to format as JSON
   2660  * @return {string} JSON formatted String value
   2661  */
   2662 function StringToJSON_(value) {
   2663   // Check for" , \ and control characters (0x00 - 0x1F). No need to call
   2664   // RegExpTest as ctrlchar is constructed using RegExp.
   2665   if (ctrlCharTest_.test(value)) {
   2666     // Replace ", \ and control characters (0x00 - 0x1F).
   2667     return '"' +
   2668       value.replace(ctrlCharMatch_, function (char) {
   2669         // Use charmap if possible.
   2670         var mapped = ctrlCharMap_[char];
   2671         if (mapped) return mapped;
   2672         mapped = char.charCodeAt();
   2673         // Convert control character to unicode escape sequence.
   2674         return '\\u00' +
   2675           '0' + // TODO %NumberToRadixString(Math.floor(mapped / 16), 16) +
   2676           '0'; // TODO %NumberToRadixString(mapped % 16, 16)
   2677       })
   2678     + '"';
   2679   }
   2680 
   2681   // Simple string with no special characters.
   2682   return '"' + value + '"';
   2683 }
   2684 
   2685 
   2686 /**
   2687  * Convert a Date to ISO 8601 format. To avoid depending on the Date object
   2688  * this method calls the functions in date.js directly and not through the
   2689  * value.
   2690  * @param {Date} value The Date value to format as JSON
   2691  * @return {string} JSON formatted Date value
   2692  */
   2693 function DateToISO8601_(value) {
   2694   var f = function(n) {
   2695     return n < 10 ? '0' + n : n;
   2696   };
   2697   var g = function(n) {
   2698     return n < 10 ? '00' + n : n < 100 ? '0' + n : n;
   2699   };
   2700   return builtins.GetUTCFullYearFrom(value)         + '-' +
   2701           f(builtins.GetUTCMonthFrom(value) + 1)    + '-' +
   2702           f(builtins.GetUTCDateFrom(value))         + 'T' +
   2703           f(builtins.GetUTCHoursFrom(value))        + ':' +
   2704           f(builtins.GetUTCMinutesFrom(value))      + ':' +
   2705           f(builtins.GetUTCSecondsFrom(value))      + '.' +
   2706           g(builtins.GetUTCMillisecondsFrom(value)) + 'Z';
   2707 }
   2708 
   2709 
   2710 /**
   2711  * Convert a Date to ISO 8601 format. To avoid depending on the Date object
   2712  * this method calls the functions in date.js directly and not through the
   2713  * value.
   2714  * @param {Date} value The Date value to format as JSON
   2715  * @return {string} JSON formatted Date value
   2716  */
   2717 function DateToJSON_(value) {
   2718   return '"' + DateToISO8601_(value) + '"';
   2719 }
   2720 
   2721 
   2722 /**
   2723  * Convert an Object to its JSON representation (see http://www.json.org/).
   2724  * This implementation simply runs through all string property names and adds
   2725  * each property to the JSON representation for some predefined types. For type
   2726  * "object" the function calls itself recursively unless the object has the
   2727  * function property "toJSONProtocol" in which case that is used. This is not
   2728  * a general implementation but sufficient for the debugger. Note that circular
   2729  * structures will cause infinite recursion.
   2730  * @param {Object} object The object to format as JSON
   2731  * @return {string} JSON formatted object value
   2732  */
   2733 function SimpleObjectToJSON_(object) {
   2734   var content = [];
   2735   for (var key in object) {
   2736     // Only consider string keys.
   2737     if (typeof key == 'string') {
   2738       var property_value = object[key];
   2739 
   2740       // Format the value based on its type.
   2741       var property_value_json;
   2742       switch (typeof property_value) {
   2743         case 'object':
   2744           if (property_value === null) {
   2745             property_value_json = 'null';
   2746           } else if (typeof property_value.toJSONProtocol == 'function') {
   2747             property_value_json = property_value.toJSONProtocol(true);
   2748           } else if (property_value.constructor.name == 'Array'){
   2749             property_value_json = SimpleArrayToJSON_(property_value);
   2750           } else {
   2751             property_value_json = SimpleObjectToJSON_(property_value);
   2752           }
   2753           break;
   2754 
   2755         case 'boolean':
   2756           property_value_json = BooleanToJSON_(property_value);
   2757           break;
   2758 
   2759         case 'number':
   2760           property_value_json = NumberToJSON_(property_value);
   2761           break;
   2762 
   2763         case 'string':
   2764           property_value_json = StringToJSON_(property_value);
   2765           break;
   2766 
   2767         default:
   2768           property_value_json = null;
   2769       }
   2770 
   2771       // Add the property if relevant.
   2772       if (property_value_json) {
   2773         content.push(StringToJSON_(key) + ':' + property_value_json);
   2774       }
   2775     }
   2776   }
   2777 
   2778   // Make JSON object representation.
   2779   return '{' + content.join(',') + '}';
   2780 }
   2781 
   2782 
   2783 /**
   2784  * Convert an array to its JSON representation. This is a VERY simple
   2785  * implementation just to support what is needed for the debugger.
   2786  * @param {Array} arrya The array to format as JSON
   2787  * @return {string} JSON formatted array value
   2788  */
   2789 function SimpleArrayToJSON_(array) {
   2790   // Make JSON array representation.
   2791   var json = '[';
   2792   for (var i = 0; i < array.length; i++) {
   2793     if (i != 0) {
   2794       json += ',';
   2795     }
   2796     var elem = array[i];
   2797     if (elem.toJSONProtocol) {
   2798       json += elem.toJSONProtocol(true);
   2799     } else if (typeof(elem) === 'object')  {
   2800       json += SimpleObjectToJSON_(elem);
   2801     } else if (typeof(elem) === 'boolean')  {
   2802       json += BooleanToJSON_(elem);
   2803     } else if (typeof(elem) === 'number')  {
   2804       json += NumberToJSON_(elem);
   2805     } else if (typeof(elem) === 'string')  {
   2806       json += StringToJSON_(elem);
   2807     } else {
   2808       json += elem;
   2809     }
   2810   }
   2811   json += ']';
   2812   return json;
   2813 }
   2814