| // Copyright 2008 the V8 project authors. All rights reserved. |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following |
| // disclaimer in the documentation and/or other materials provided |
| // with the distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived |
| // from this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| String.prototype.startsWith = function (str) { |
| if (str.length > this.length) |
| return false; |
| return this.substr(0, str.length) == str; |
| } |
| |
| function log10(num) { |
| return Math.log(num)/Math.log(10); |
| } |
| |
| function ToInspectableObject(obj) { |
| if (!obj && typeof obj === 'object') { |
| return void 0; |
| } else { |
| return Object(obj); |
| } |
| } |
| |
| function GetCompletions(global, last, full) { |
| var full_tokens = full.split(); |
| full = full_tokens.pop(); |
| var parts = full.split('.'); |
| parts.pop(); |
| var current = global; |
| for (var i = 0; i < parts.length; i++) { |
| var part = parts[i]; |
| var next = current[part]; |
| if (!next) |
| return []; |
| current = next; |
| } |
| var result = []; |
| current = ToInspectableObject(current); |
| while (typeof current !== 'undefined') { |
| var mirror = new $debug.ObjectMirror(current); |
| var properties = mirror.properties(); |
| for (var i = 0; i < properties.length; i++) { |
| var name = properties[i].name(); |
| if (typeof name === 'string' && name.startsWith(last)) |
| result.push(name); |
| } |
| current = ToInspectableObject(current.__proto__); |
| } |
| return result; |
| } |
| |
| |
| // Global object holding debugger related constants and state. |
| const Debug = {}; |
| |
| |
| // Debug events which can occour in the V8 JavaScript engine. These originate |
| // from the API include file v8-debug.h. |
| Debug.DebugEvent = { Break: 1, |
| Exception: 2, |
| NewFunction: 3, |
| BeforeCompile: 4, |
| AfterCompile: 5 }; |
| |
| |
| // The different types of scripts matching enum ScriptType in objects.h. |
| Debug.ScriptType = { Native: 0, |
| Extension: 1, |
| Normal: 2 }; |
| |
| |
| // The different types of script compilations matching enum |
| // Script::CompilationType in objects.h. |
| Debug.ScriptCompilationType = { Host: 0, |
| Eval: 1, |
| JSON: 2 }; |
| |
| |
| // The different types of scopes matching constants runtime.cc. |
| Debug.ScopeType = { Global: 0, |
| Local: 1, |
| With: 2, |
| Closure: 3, |
| Catch: 4 }; |
| |
| |
| // Current debug state. |
| const kNoFrame = -1; |
| Debug.State = { |
| currentFrame: kNoFrame, |
| displaySourceStartLine: -1, |
| displaySourceEndLine: -1, |
| currentSourceLine: -1 |
| } |
| var trace_compile = false; // Tracing all compile events? |
| var trace_debug_json = false; // Tracing all debug json packets? |
| var last_cmd_line = ''; |
| //var lol_is_enabled; // Set to true in d8.cc if LIVE_OBJECT_LIST is defined. |
| var lol_next_dump_index = 0; |
| const kDefaultLolLinesToPrintAtATime = 10; |
| const kMaxLolLinesToPrintAtATime = 1000; |
| var repeat_cmd_line = ''; |
| var is_running = true; |
| |
| // Copied from debug-delay.js. This is needed below: |
| function ScriptTypeFlag(type) { |
| return (1 << type); |
| } |
| |
| |
| // Process a debugger JSON message into a display text and a running status. |
| // This function returns an object with properties "text" and "running" holding |
| // this information. |
| function DebugMessageDetails(message) { |
| if (trace_debug_json) { |
| print("received: '" + message + "'"); |
| } |
| // Convert the JSON string to an object. |
| var response = new ProtocolPackage(message); |
| is_running = response.running(); |
| |
| if (response.type() == 'event') { |
| return DebugEventDetails(response); |
| } else { |
| return DebugResponseDetails(response); |
| } |
| } |
| |
| function DebugEventDetails(response) { |
| details = {text:'', running:false} |
| |
| // Get the running state. |
| details.running = response.running(); |
| |
| var body = response.body(); |
| var result = ''; |
| switch (response.event()) { |
| case 'break': |
| if (body.breakpoints) { |
| result += 'breakpoint'; |
| if (body.breakpoints.length > 1) { |
| result += 's'; |
| } |
| result += ' #'; |
| for (var i = 0; i < body.breakpoints.length; i++) { |
| if (i > 0) { |
| result += ', #'; |
| } |
| result += body.breakpoints[i]; |
| } |
| } else { |
| result += 'break'; |
| } |
| result += ' in '; |
| result += body.invocationText; |
| result += ', '; |
| result += SourceInfo(body); |
| result += '\n'; |
| result += SourceUnderline(body.sourceLineText, body.sourceColumn); |
| Debug.State.currentSourceLine = body.sourceLine; |
| Debug.State.displaySourceStartLine = -1; |
| Debug.State.displaySourceEndLine = -1; |
| Debug.State.currentFrame = 0; |
| details.text = result; |
| break; |
| |
| case 'exception': |
| if (body.uncaught) { |
| result += 'Uncaught: '; |
| } else { |
| result += 'Exception: '; |
| } |
| result += '"'; |
| result += body.exception.text; |
| result += '"'; |
| if (body.sourceLine >= 0) { |
| result += ', '; |
| result += SourceInfo(body); |
| result += '\n'; |
| result += SourceUnderline(body.sourceLineText, body.sourceColumn); |
| Debug.State.currentSourceLine = body.sourceLine; |
| Debug.State.displaySourceStartLine = -1; |
| Debug.State.displaySourceEndLine = -1; |
| Debug.State.currentFrame = 0; |
| } else { |
| result += ' (empty stack)'; |
| Debug.State.currentSourceLine = -1; |
| Debug.State.displaySourceStartLine = -1; |
| Debug.State.displaySourceEndLine = -1; |
| Debug.State.currentFrame = kNoFrame; |
| } |
| details.text = result; |
| break; |
| |
| case 'afterCompile': |
| if (trace_compile) { |
| result = 'Source ' + body.script.name + ' compiled:\n' |
| var source = body.script.source; |
| if (!(source[source.length - 1] == '\n')) { |
| result += source; |
| } else { |
| result += source.substring(0, source.length - 1); |
| } |
| } |
| details.text = result; |
| break; |
| |
| case 'scriptCollected': |
| details.text = result; |
| break; |
| |
| default: |
| details.text = 'Unknown debug event ' + response.event(); |
| } |
| |
| return details; |
| }; |
| |
| |
| function SourceInfo(body) { |
| var result = ''; |
| |
| if (body.script) { |
| if (body.script.name) { |
| result += body.script.name; |
| } else { |
| result += '[unnamed]'; |
| } |
| } |
| result += ' line '; |
| result += body.sourceLine + 1; |
| result += ' column '; |
| result += body.sourceColumn + 1; |
| |
| return result; |
| } |
| |
| |
| function SourceUnderline(source_text, position) { |
| if (!source_text) { |
| return; |
| } |
| |
| // Create an underline with a caret pointing to the source position. If the |
| // source contains a tab character the underline will have a tab character in |
| // the same place otherwise the underline will have a space character. |
| var underline = ''; |
| for (var i = 0; i < position; i++) { |
| if (source_text[i] == '\t') { |
| underline += '\t'; |
| } else { |
| underline += ' '; |
| } |
| } |
| underline += '^'; |
| |
| // Return the source line text with the underline beneath. |
| return source_text + '\n' + underline; |
| }; |
| |
| |
| // Converts a text command to a JSON request. |
| function DebugCommandToJSONRequest(cmd_line) { |
| var result = new DebugRequest(cmd_line).JSONRequest(); |
| if (trace_debug_json && result) { |
| print("sending: '" + result + "'"); |
| } |
| return result; |
| }; |
| |
| |
| function DebugRequest(cmd_line) { |
| // If the very first character is a { assume that a JSON request have been |
| // entered as a command. Converting that to a JSON request is trivial. |
| if (cmd_line && cmd_line.length > 0 && cmd_line.charAt(0) == '{') { |
| this.request_ = cmd_line; |
| return; |
| } |
| |
| // Check for a simple carriage return to repeat the last command: |
| var is_repeating = false; |
| if (cmd_line == '\n') { |
| if (is_running) { |
| cmd_line = 'break'; // Not in debugger mode, break with a frame request. |
| } else { |
| cmd_line = repeat_cmd_line; // use command to repeat. |
| is_repeating = true; |
| } |
| } |
| if (!is_running) { // Only save the command if in debugger mode. |
| repeat_cmd_line = cmd_line; // save last command. |
| } |
| |
| // Trim string for leading and trailing whitespace. |
| cmd_line = cmd_line.replace(/^\s+|\s+$/g, ''); |
| |
| // Find the command. |
| var pos = cmd_line.indexOf(' '); |
| var cmd; |
| var args; |
| if (pos == -1) { |
| cmd = cmd_line; |
| args = ''; |
| } else { |
| cmd = cmd_line.slice(0, pos); |
| args = cmd_line.slice(pos).replace(/^\s+|\s+$/g, ''); |
| } |
| |
| if ((cmd === undefined) || !cmd) { |
| this.request_ = void 0; |
| return; |
| } |
| |
| last_cmd = cmd; |
| |
| // Switch on command. |
| switch (cmd) { |
| case 'continue': |
| case 'c': |
| this.request_ = this.continueCommandToJSONRequest_(args); |
| break; |
| |
| case 'step': |
| case 's': |
| this.request_ = this.stepCommandToJSONRequest_(args, 'in'); |
| break; |
| |
| case 'stepi': |
| case 'si': |
| this.request_ = this.stepCommandToJSONRequest_(args, 'min'); |
| break; |
| |
| case 'next': |
| case 'n': |
| this.request_ = this.stepCommandToJSONRequest_(args, 'next'); |
| break; |
| |
| case 'finish': |
| case 'fin': |
| this.request_ = this.stepCommandToJSONRequest_(args, 'out'); |
| break; |
| |
| case 'backtrace': |
| case 'bt': |
| this.request_ = this.backtraceCommandToJSONRequest_(args); |
| break; |
| |
| case 'frame': |
| case 'f': |
| this.request_ = this.frameCommandToJSONRequest_(args); |
| break; |
| |
| case 'scopes': |
| this.request_ = this.scopesCommandToJSONRequest_(args); |
| break; |
| |
| case 'scope': |
| this.request_ = this.scopeCommandToJSONRequest_(args); |
| break; |
| |
| case 'disconnect': |
| case 'exit': |
| case 'quit': |
| this.request_ = this.disconnectCommandToJSONRequest_(args); |
| break; |
| |
| case 'up': |
| this.request_ = |
| this.frameCommandToJSONRequest_('' + |
| (Debug.State.currentFrame + 1)); |
| break; |
| |
| case 'down': |
| case 'do': |
| this.request_ = |
| this.frameCommandToJSONRequest_('' + |
| (Debug.State.currentFrame - 1)); |
| break; |
| |
| case 'set': |
| case 'print': |
| case 'p': |
| this.request_ = this.printCommandToJSONRequest_(args); |
| break; |
| |
| case 'dir': |
| this.request_ = this.dirCommandToJSONRequest_(args); |
| break; |
| |
| case 'references': |
| this.request_ = this.referencesCommandToJSONRequest_(args); |
| break; |
| |
| case 'instances': |
| this.request_ = this.instancesCommandToJSONRequest_(args); |
| break; |
| |
| case 'list': |
| case 'l': |
| this.request_ = this.listCommandToJSONRequest_(args); |
| break; |
| case 'source': |
| this.request_ = this.sourceCommandToJSONRequest_(args); |
| break; |
| |
| case 'scripts': |
| case 'script': |
| case 'scr': |
| this.request_ = this.scriptsCommandToJSONRequest_(args); |
| break; |
| |
| case 'break': |
| case 'b': |
| this.request_ = this.breakCommandToJSONRequest_(args); |
| break; |
| |
| case 'breakpoints': |
| case 'bb': |
| this.request_ = this.breakpointsCommandToJSONRequest_(args); |
| break; |
| |
| case 'clear': |
| case 'delete': |
| case 'd': |
| this.request_ = this.clearCommandToJSONRequest_(args); |
| break; |
| |
| case 'threads': |
| this.request_ = this.threadsCommandToJSONRequest_(args); |
| break; |
| |
| case 'cond': |
| this.request_ = this.changeBreakpointCommandToJSONRequest_(args, 'cond'); |
| break; |
| |
| case 'enable': |
| case 'en': |
| this.request_ = |
| this.changeBreakpointCommandToJSONRequest_(args, 'enable'); |
| break; |
| |
| case 'disable': |
| case 'dis': |
| this.request_ = |
| this.changeBreakpointCommandToJSONRequest_(args, 'disable'); |
| break; |
| |
| case 'ignore': |
| this.request_ = |
| this.changeBreakpointCommandToJSONRequest_(args, 'ignore'); |
| break; |
| |
| case 'info': |
| case 'inf': |
| this.request_ = this.infoCommandToJSONRequest_(args); |
| break; |
| |
| case 'flags': |
| this.request_ = this.v8FlagsToJSONRequest_(args); |
| break; |
| |
| case 'gc': |
| this.request_ = this.gcToJSONRequest_(args); |
| break; |
| |
| case 'trace': |
| case 'tr': |
| // Return undefined to indicate command handled internally (no JSON). |
| this.request_ = void 0; |
| this.traceCommand_(args); |
| break; |
| |
| case 'help': |
| case '?': |
| this.helpCommand_(args); |
| // Return undefined to indicate command handled internally (no JSON). |
| this.request_ = void 0; |
| break; |
| |
| case 'liveobjectlist': |
| case 'lol': |
| if (lol_is_enabled) { |
| this.request_ = this.lolToJSONRequest_(args, is_repeating); |
| break; |
| } |
| |
| default: |
| throw new Error('Unknown command "' + cmd + '"'); |
| } |
| } |
| |
| DebugRequest.prototype.JSONRequest = function() { |
| return this.request_; |
| } |
| |
| |
| function RequestPacket(command) { |
| this.seq = 0; |
| this.type = 'request'; |
| this.command = command; |
| } |
| |
| |
| RequestPacket.prototype.toJSONProtocol = function() { |
| // Encode the protocol header. |
| var json = '{'; |
| json += '"seq":' + this.seq; |
| json += ',"type":"' + this.type + '"'; |
| if (this.command) { |
| json += ',"command":' + StringToJSON_(this.command); |
| } |
| if (this.arguments) { |
| json += ',"arguments":'; |
| // Encode the arguments part. |
| if (this.arguments.toJSONProtocol) { |
| json += this.arguments.toJSONProtocol() |
| } else { |
| json += SimpleObjectToJSON_(this.arguments); |
| } |
| } |
| json += '}'; |
| return json; |
| } |
| |
| |
| DebugRequest.prototype.createRequest = function(command) { |
| return new RequestPacket(command); |
| }; |
| |
| |
| // Note: we use detected command repetition as a signal for continuation here. |
| DebugRequest.prototype.createLOLRequest = function(command, |
| start_index, |
| lines_to_dump, |
| is_continuation) { |
| if (is_continuation) { |
| start_index = lol_next_dump_index; |
| } |
| |
| if (lines_to_dump) { |
| lines_to_dump = parseInt(lines_to_dump); |
| } else { |
| lines_to_dump = kDefaultLolLinesToPrintAtATime; |
| } |
| if (lines_to_dump > kMaxLolLinesToPrintAtATime) { |
| lines_to_dump = kMaxLolLinesToPrintAtATime; |
| } |
| |
| // Save the next start_index to dump from: |
| lol_next_dump_index = start_index + lines_to_dump; |
| |
| var request = this.createRequest(command); |
| request.arguments = {}; |
| request.arguments.start = start_index; |
| request.arguments.count = lines_to_dump; |
| |
| return request; |
| }; |
| |
| |
| // Create a JSON request for the evaluation command. |
| DebugRequest.prototype.makeEvaluateJSONRequest_ = function(expression) { |
| // Global varaible used to store whether a handle was requested. |
| lookup_handle = null; |
| |
| if (lol_is_enabled) { |
| // Check if the expression is a obj id in the form @<obj id>. |
| var obj_id_match = expression.match(/^@([0-9]+)$/); |
| if (obj_id_match) { |
| var obj_id = parseInt(obj_id_match[1]); |
| // Build a dump request. |
| var request = this.createRequest('getobj'); |
| request.arguments = {}; |
| request.arguments.obj_id = obj_id; |
| return request.toJSONProtocol(); |
| } |
| } |
| |
| // Check if the expression is a handle id in the form #<handle>#. |
| var handle_match = expression.match(/^#([0-9]*)#$/); |
| if (handle_match) { |
| // Remember the handle requested in a global variable. |
| lookup_handle = parseInt(handle_match[1]); |
| // Build a lookup request. |
| var request = this.createRequest('lookup'); |
| request.arguments = {}; |
| request.arguments.handles = [ lookup_handle ]; |
| return request.toJSONProtocol(); |
| } else { |
| // Build an evaluate request. |
| var request = this.createRequest('evaluate'); |
| request.arguments = {}; |
| request.arguments.expression = expression; |
| // Request a global evaluation if there is no current frame. |
| if (Debug.State.currentFrame == kNoFrame) { |
| request.arguments.global = true; |
| } |
| return request.toJSONProtocol(); |
| } |
| }; |
| |
| |
| // Create a JSON request for the references/instances command. |
| DebugRequest.prototype.makeReferencesJSONRequest_ = function(handle, type) { |
| // Build a references request. |
| var handle_match = handle.match(/^#([0-9]*)#$/); |
| if (handle_match) { |
| var request = this.createRequest('references'); |
| request.arguments = {}; |
| request.arguments.type = type; |
| request.arguments.handle = parseInt(handle_match[1]); |
| return request.toJSONProtocol(); |
| } else { |
| throw new Error('Invalid object id.'); |
| } |
| }; |
| |
| |
| // Create a JSON request for the continue command. |
| DebugRequest.prototype.continueCommandToJSONRequest_ = function(args) { |
| var request = this.createRequest('continue'); |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the step command. |
| DebugRequest.prototype.stepCommandToJSONRequest_ = function(args, type) { |
| // Requesting a step is through the continue command with additional |
| // arguments. |
| var request = this.createRequest('continue'); |
| request.arguments = {}; |
| |
| // Process arguments if any. |
| |
| // Only process args if the command is 'step' which is indicated by type being |
| // set to 'in'. For all other commands, ignore the args. |
| if (args && args.length > 0) { |
| args = args.split(/\s+/g); |
| |
| if (args.length > 2) { |
| throw new Error('Invalid step arguments.'); |
| } |
| |
| if (args.length > 0) { |
| // Check if we have a gdb stype step command. If so, the 1st arg would |
| // be the step count. If it's not a number, then assume that we're |
| // parsing for the legacy v8 step command. |
| var stepcount = Number(args[0]); |
| if (stepcount == Number.NaN) { |
| // No step count at arg 1. Process as legacy d8 step command: |
| if (args.length == 2) { |
| var stepcount = parseInt(args[1]); |
| if (isNaN(stepcount) || stepcount <= 0) { |
| throw new Error('Invalid step count argument "' + args[0] + '".'); |
| } |
| request.arguments.stepcount = stepcount; |
| } |
| |
| // Get the step action. |
| switch (args[0]) { |
| case 'in': |
| case 'i': |
| request.arguments.stepaction = 'in'; |
| break; |
| |
| case 'min': |
| case 'm': |
| request.arguments.stepaction = 'min'; |
| break; |
| |
| case 'next': |
| case 'n': |
| request.arguments.stepaction = 'next'; |
| break; |
| |
| case 'out': |
| case 'o': |
| request.arguments.stepaction = 'out'; |
| break; |
| |
| default: |
| throw new Error('Invalid step argument "' + args[0] + '".'); |
| } |
| |
| } else { |
| // gdb style step commands: |
| request.arguments.stepaction = type; |
| request.arguments.stepcount = stepcount; |
| } |
| } |
| } else { |
| // Default is step of the specified type. |
| request.arguments.stepaction = type; |
| } |
| |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the backtrace command. |
| DebugRequest.prototype.backtraceCommandToJSONRequest_ = function(args) { |
| // Build a backtrace request from the text command. |
| var request = this.createRequest('backtrace'); |
| |
| // Default is to show top 10 frames. |
| request.arguments = {}; |
| request.arguments.fromFrame = 0; |
| request.arguments.toFrame = 10; |
| |
| args = args.split(/\s*[ ]+\s*/g); |
| if (args.length == 1 && args[0].length > 0) { |
| var frameCount = parseInt(args[0]); |
| if (frameCount > 0) { |
| // Show top frames. |
| request.arguments.fromFrame = 0; |
| request.arguments.toFrame = frameCount; |
| } else { |
| // Show bottom frames. |
| request.arguments.fromFrame = 0; |
| request.arguments.toFrame = -frameCount; |
| request.arguments.bottom = true; |
| } |
| } else if (args.length == 2) { |
| var fromFrame = parseInt(args[0]); |
| var toFrame = parseInt(args[1]); |
| if (isNaN(fromFrame) || fromFrame < 0) { |
| throw new Error('Invalid start frame argument "' + args[0] + '".'); |
| } |
| if (isNaN(toFrame) || toFrame < 0) { |
| throw new Error('Invalid end frame argument "' + args[1] + '".'); |
| } |
| if (fromFrame > toFrame) { |
| throw new Error('Invalid arguments start frame cannot be larger ' + |
| 'than end frame.'); |
| } |
| // Show frame range. |
| request.arguments.fromFrame = fromFrame; |
| request.arguments.toFrame = toFrame + 1; |
| } else if (args.length > 2) { |
| throw new Error('Invalid backtrace arguments.'); |
| } |
| |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the frame command. |
| DebugRequest.prototype.frameCommandToJSONRequest_ = function(args) { |
| // Build a frame request from the text command. |
| var request = this.createRequest('frame'); |
| args = args.split(/\s*[ ]+\s*/g); |
| if (args.length > 0 && args[0].length > 0) { |
| request.arguments = {}; |
| request.arguments.number = args[0]; |
| } |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the scopes command. |
| DebugRequest.prototype.scopesCommandToJSONRequest_ = function(args) { |
| // Build a scopes request from the text command. |
| var request = this.createRequest('scopes'); |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the scope command. |
| DebugRequest.prototype.scopeCommandToJSONRequest_ = function(args) { |
| // Build a scope request from the text command. |
| var request = this.createRequest('scope'); |
| args = args.split(/\s*[ ]+\s*/g); |
| if (args.length > 0 && args[0].length > 0) { |
| request.arguments = {}; |
| request.arguments.number = args[0]; |
| } |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the print command. |
| DebugRequest.prototype.printCommandToJSONRequest_ = function(args) { |
| // Build an evaluate request from the text command. |
| if (args.length == 0) { |
| throw new Error('Missing expression.'); |
| } |
| return this.makeEvaluateJSONRequest_(args); |
| }; |
| |
| |
| // Create a JSON request for the dir command. |
| DebugRequest.prototype.dirCommandToJSONRequest_ = function(args) { |
| // Build an evaluate request from the text command. |
| if (args.length == 0) { |
| throw new Error('Missing expression.'); |
| } |
| return this.makeEvaluateJSONRequest_(args); |
| }; |
| |
| |
| // Create a JSON request for the references command. |
| DebugRequest.prototype.referencesCommandToJSONRequest_ = function(args) { |
| // Build an evaluate request from the text command. |
| if (args.length == 0) { |
| throw new Error('Missing object id.'); |
| } |
| |
| return this.makeReferencesJSONRequest_(args, 'referencedBy'); |
| }; |
| |
| |
| // Create a JSON request for the instances command. |
| DebugRequest.prototype.instancesCommandToJSONRequest_ = function(args) { |
| // Build an evaluate request from the text command. |
| if (args.length == 0) { |
| throw new Error('Missing object id.'); |
| } |
| |
| // Build a references request. |
| return this.makeReferencesJSONRequest_(args, 'constructedBy'); |
| }; |
| |
| |
| // Create a JSON request for the list command. |
| DebugRequest.prototype.listCommandToJSONRequest_ = function(args) { |
| |
| // Default is ten lines starting five lines before the current location. |
| if (Debug.State.displaySourceEndLine == -1) { |
| // If we list forwards, we will start listing after the last source end |
| // line. Set it to start from 5 lines before the current location. |
| Debug.State.displaySourceEndLine = Debug.State.currentSourceLine - 5; |
| // If we list backwards, we will start listing backwards from the last |
| // source start line. Set it to start from 1 lines before the current |
| // location. |
| Debug.State.displaySourceStartLine = Debug.State.currentSourceLine + 1; |
| } |
| |
| var from = Debug.State.displaySourceEndLine + 1; |
| var lines = 10; |
| |
| // Parse the arguments. |
| args = args.split(/\s*,\s*/g); |
| if (args == '') { |
| } else if ((args.length == 1) && (args[0] == '-')) { |
| from = Debug.State.displaySourceStartLine - lines; |
| } else if (args.length == 2) { |
| from = parseInt(args[0]); |
| lines = parseInt(args[1]) - from + 1; // inclusive of the ending line. |
| } else { |
| throw new Error('Invalid list arguments.'); |
| } |
| Debug.State.displaySourceStartLine = from; |
| Debug.State.displaySourceEndLine = from + lines - 1; |
| var sourceArgs = '' + from + ' ' + lines; |
| return this.sourceCommandToJSONRequest_(sourceArgs); |
| }; |
| |
| |
| // Create a JSON request for the source command. |
| DebugRequest.prototype.sourceCommandToJSONRequest_ = function(args) { |
| // Build a evaluate request from the text command. |
| var request = this.createRequest('source'); |
| |
| // Default is ten lines starting five lines before the current location. |
| var from = Debug.State.currentSourceLine - 5; |
| var lines = 10; |
| |
| // Parse the arguments. |
| args = args.split(/\s*[ ]+\s*/g); |
| if (args.length > 1 && args[0].length > 0 && args[1].length > 0) { |
| from = parseInt(args[0]) - 1; |
| lines = parseInt(args[1]); |
| } else if (args.length > 0 && args[0].length > 0) { |
| from = parseInt(args[0]) - 1; |
| } |
| |
| if (from < 0) from = 0; |
| if (lines < 0) lines = 10; |
| |
| // Request source arround current source location. |
| request.arguments = {}; |
| request.arguments.fromLine = from; |
| request.arguments.toLine = from + lines; |
| |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the scripts command. |
| DebugRequest.prototype.scriptsCommandToJSONRequest_ = function(args) { |
| // Build a evaluate request from the text command. |
| var request = this.createRequest('scripts'); |
| |
| // Process arguments if any. |
| if (args && args.length > 0) { |
| args = args.split(/\s*[ ]+\s*/g); |
| |
| if (args.length > 1) { |
| throw new Error('Invalid scripts arguments.'); |
| } |
| |
| request.arguments = {}; |
| switch (args[0]) { |
| case 'natives': |
| request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Native); |
| break; |
| |
| case 'extensions': |
| request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Extension); |
| break; |
| |
| case 'all': |
| request.arguments.types = |
| ScriptTypeFlag(Debug.ScriptType.Normal) | |
| ScriptTypeFlag(Debug.ScriptType.Native) | |
| ScriptTypeFlag(Debug.ScriptType.Extension); |
| break; |
| |
| default: |
| // If the arg is not one of the know one aboves, then it must be a |
| // filter used for filtering the results: |
| request.arguments.filter = args[0]; |
| break; |
| } |
| } |
| |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the break command. |
| DebugRequest.prototype.breakCommandToJSONRequest_ = function(args) { |
| // Build a evaluate request from the text command. |
| // Process arguments if any. |
| if (args && args.length > 0) { |
| var target = args; |
| var type = 'function'; |
| var line; |
| var column; |
| var condition; |
| var pos; |
| |
| var request = this.createRequest('setbreakpoint'); |
| |
| // Break the args into target spec and condition if appropriate. |
| |
| // Check for breakpoint condition. |
| pos = args.indexOf(' '); |
| if (pos > 0) { |
| target = args.substring(0, pos); |
| condition = args.substring(pos + 1, args.length); |
| } |
| |
| // Check for script breakpoint (name:line[:column]). If no ':' in break |
| // specification it is considered a function break point. |
| pos = target.indexOf(':'); |
| if (pos > 0) { |
| type = 'script'; |
| var tmp = target.substring(pos + 1, target.length); |
| target = target.substring(0, pos); |
| |
| // Check for both line and column. |
| pos = tmp.indexOf(':'); |
| if (pos > 0) { |
| column = parseInt(tmp.substring(pos + 1, tmp.length)) - 1; |
| line = parseInt(tmp.substring(0, pos)) - 1; |
| } else { |
| line = parseInt(tmp) - 1; |
| } |
| } else if (target[0] == '#' && target[target.length - 1] == '#') { |
| type = 'handle'; |
| target = target.substring(1, target.length - 1); |
| } else { |
| type = 'function'; |
| } |
| |
| request.arguments = {}; |
| request.arguments.type = type; |
| request.arguments.target = target; |
| request.arguments.line = line; |
| request.arguments.column = column; |
| request.arguments.condition = condition; |
| } else { |
| var request = this.createRequest('suspend'); |
| } |
| |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| DebugRequest.prototype.breakpointsCommandToJSONRequest_ = function(args) { |
| if (args && args.length > 0) { |
| throw new Error('Unexpected arguments.'); |
| } |
| var request = this.createRequest('listbreakpoints'); |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the clear command. |
| DebugRequest.prototype.clearCommandToJSONRequest_ = function(args) { |
| // Build a evaluate request from the text command. |
| var request = this.createRequest('clearbreakpoint'); |
| |
| // Process arguments if any. |
| if (args && args.length > 0) { |
| request.arguments = {}; |
| request.arguments.breakpoint = parseInt(args); |
| } else { |
| throw new Error('Invalid break arguments.'); |
| } |
| |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the change breakpoint command. |
| DebugRequest.prototype.changeBreakpointCommandToJSONRequest_ = |
| function(args, command) { |
| |
| var request; |
| |
| // Check for exception breaks first: |
| // en[able] exc[eptions] [all|unc[aught]] |
| // en[able] [all|unc[aught]] exc[eptions] |
| // dis[able] exc[eptions] [all|unc[aught]] |
| // dis[able] [all|unc[aught]] exc[eptions] |
| if ((command == 'enable' || command == 'disable') && |
| args && args.length > 1) { |
| var nextPos = args.indexOf(' '); |
| var arg1 = (nextPos > 0) ? args.substring(0, nextPos) : args; |
| var excType = null; |
| |
| // Check for: |
| // en[able] exc[eptions] [all|unc[aught]] |
| // dis[able] exc[eptions] [all|unc[aught]] |
| if (arg1 == 'exc' || arg1 == 'exception' || arg1 == 'exceptions') { |
| |
| var arg2 = (nextPos > 0) ? |
| args.substring(nextPos + 1, args.length) : 'all'; |
| if (!arg2) { |
| arg2 = 'all'; // if unspecified, set for all. |
| } if (arg2 == 'unc') { // check for short cut. |
| arg2 = 'uncaught'; |
| } |
| excType = arg2; |
| |
| // Check for: |
| // en[able] [all|unc[aught]] exc[eptions] |
| // dis[able] [all|unc[aught]] exc[eptions] |
| } else if (arg1 == 'all' || arg1 == 'unc' || arg1 == 'uncaught') { |
| |
| var arg2 = (nextPos > 0) ? |
| args.substring(nextPos + 1, args.length) : null; |
| if (arg2 == 'exc' || arg1 == 'exception' || arg1 == 'exceptions') { |
| excType = arg1; |
| if (excType == 'unc') { |
| excType = 'uncaught'; |
| } |
| } |
| } |
| |
| // If we matched one of the command formats, then excType will be non-null: |
| if (excType) { |
| // Build a evaluate request from the text command. |
| request = this.createRequest('setexceptionbreak'); |
| |
| request.arguments = {}; |
| request.arguments.type = excType; |
| request.arguments.enabled = (command == 'enable'); |
| |
| return request.toJSONProtocol(); |
| } |
| } |
| |
| // Build a evaluate request from the text command. |
| request = this.createRequest('changebreakpoint'); |
| |
| // Process arguments if any. |
| if (args && args.length > 0) { |
| request.arguments = {}; |
| var pos = args.indexOf(' '); |
| var breakpointArg = args; |
| var otherArgs; |
| if (pos > 0) { |
| breakpointArg = args.substring(0, pos); |
| otherArgs = args.substring(pos + 1, args.length); |
| } |
| |
| request.arguments.breakpoint = parseInt(breakpointArg); |
| |
| switch(command) { |
| case 'cond': |
| request.arguments.condition = otherArgs ? otherArgs : null; |
| break; |
| case 'enable': |
| request.arguments.enabled = true; |
| break; |
| case 'disable': |
| request.arguments.enabled = false; |
| break; |
| case 'ignore': |
| request.arguments.ignoreCount = parseInt(otherArgs); |
| break; |
| default: |
| throw new Error('Invalid arguments.'); |
| } |
| } else { |
| throw new Error('Invalid arguments.'); |
| } |
| |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the disconnect command. |
| DebugRequest.prototype.disconnectCommandToJSONRequest_ = function(args) { |
| var request; |
| request = this.createRequest('disconnect'); |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the info command. |
| DebugRequest.prototype.infoCommandToJSONRequest_ = function(args) { |
| var request; |
| if (args && (args == 'break' || args == 'br')) { |
| // Build a evaluate request from the text command. |
| request = this.createRequest('listbreakpoints'); |
| last_cmd = 'info break'; |
| } else if (args && (args == 'locals' || args == 'lo')) { |
| // Build a evaluate request from the text command. |
| request = this.createRequest('frame'); |
| last_cmd = 'info locals'; |
| } else if (args && (args == 'args' || args == 'ar')) { |
| // Build a evaluate request from the text command. |
| request = this.createRequest('frame'); |
| last_cmd = 'info args'; |
| } else if (lol_is_enabled && |
| args && (args == 'liveobjectlist' || args == 'lol')) { |
| // Build a evaluate request from the text command. |
| return this.liveObjectListToJSONRequest_(null); |
| } else { |
| throw new Error('Invalid info arguments.'); |
| } |
| |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| DebugRequest.prototype.v8FlagsToJSONRequest_ = function(args) { |
| var request; |
| request = this.createRequest('v8flags'); |
| request.arguments = {}; |
| request.arguments.flags = args; |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| DebugRequest.prototype.gcToJSONRequest_ = function(args) { |
| var request; |
| if (!args) { |
| args = 'all'; |
| } |
| var args = args.split(/\s+/g); |
| var cmd = args[0]; |
| |
| switch(cmd) { |
| case 'all': |
| case 'quick': |
| case 'full': |
| case 'young': |
| case 'old': |
| case 'compact': |
| case 'sweep': |
| case 'scavenge': { |
| if (cmd == 'young') { cmd = 'quick'; } |
| else if (cmd == 'old') { cmd = 'full'; } |
| |
| request = this.createRequest('gc'); |
| request.arguments = {}; |
| request.arguments.type = cmd; |
| break; |
| } |
| // Else fall thru to the default case below to report the error. |
| default: |
| throw new Error('Missing arguments after ' + cmd + '.'); |
| } |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Args: [v[erbose]] [<N>] [i[ndex] <i>] [t[ype] <type>] [sp[ace] <space>] |
| DebugRequest.prototype.lolMakeListRequest = |
| function(cmd, args, first_arg_index, is_repeating) { |
| |
| var request; |
| var start_index = 0; |
| var dump_limit = void 0; |
| var type_filter = void 0; |
| var space_filter = void 0; |
| var prop_filter = void 0; |
| var is_verbose = false; |
| var i; |
| |
| for (i = first_arg_index; i < args.length; i++) { |
| var arg = args[i]; |
| // Check for [v[erbose]]: |
| if (arg === 'verbose' || arg === 'v') { |
| // Nothing to do. This is already implied by args.length > 3. |
| is_verbose = true; |
| |
| // Check for [<N>]: |
| } else if (arg.match(/^[0-9]+$/)) { |
| dump_limit = arg; |
| is_verbose = true; |
| |
| // Check for i[ndex] <i>: |
| } else if (arg === 'index' || arg === 'i') { |
| i++; |
| if (args.length < i) { |
| throw new Error('Missing index after ' + arg + '.'); |
| } |
| start_index = parseInt(args[i]); |
| // The user input start index starts at 1: |
| if (start_index <= 0) { |
| throw new Error('Invalid index ' + args[i] + '.'); |
| } |
| start_index -= 1; |
| is_verbose = true; |
| |
| // Check for t[ype] <type>: |
| } else if (arg === 'type' || arg === 't') { |
| i++; |
| if (args.length < i) { |
| throw new Error('Missing type after ' + arg + '.'); |
| } |
| type_filter = args[i]; |
| |
| // Check for space <heap space name>: |
| } else if (arg === 'space' || arg === 'sp') { |
| i++; |
| if (args.length < i) { |
| throw new Error('Missing space name after ' + arg + '.'); |
| } |
| space_filter = args[i]; |
| |
| // Check for property <prop name>: |
| } else if (arg === 'property' || arg === 'prop') { |
| i++; |
| if (args.length < i) { |
| throw new Error('Missing property name after ' + arg + '.'); |
| } |
| prop_filter = args[i]; |
| |
| } else { |
| throw new Error('Unknown args at ' + arg + '.'); |
| } |
| } |
| |
| // Build the verbose request: |
| if (is_verbose) { |
| request = this.createLOLRequest('lol-'+cmd, |
| start_index, |
| dump_limit, |
| is_repeating); |
| request.arguments.verbose = true; |
| } else { |
| request = this.createRequest('lol-'+cmd); |
| request.arguments = {}; |
| } |
| |
| request.arguments.filter = {}; |
| if (type_filter) { |
| request.arguments.filter.type = type_filter; |
| } |
| if (space_filter) { |
| request.arguments.filter.space = space_filter; |
| } |
| if (prop_filter) { |
| request.arguments.filter.prop = prop_filter; |
| } |
| |
| return request; |
| } |
| |
| |
| function extractObjId(args) { |
| var id = args; |
| id = id.match(/^@([0-9]+)$/); |
| if (id) { |
| id = id[1]; |
| } else { |
| throw new Error('Invalid obj id ' + args + '.'); |
| } |
| return parseInt(id); |
| } |
| |
| |
| DebugRequest.prototype.lolToJSONRequest_ = function(args, is_repeating) { |
| var request; |
| // Use default command if one is not specified: |
| if (!args) { |
| args = 'info'; |
| } |
| |
| var orig_args = args; |
| var first_arg_index; |
| |
| var arg, i; |
| var args = args.split(/\s+/g); |
| var cmd = args[0]; |
| var id; |
| |
| // Command: <id> [v[erbose]] ... |
| if (cmd.match(/^[0-9]+$/)) { |
| // Convert to the padded list command: |
| // Command: l[ist] <dummy> <id> [v[erbose]] ... |
| |
| // Insert the implicit 'list' in front and process as normal: |
| cmd = 'list'; |
| args.unshift(cmd); |
| } |
| |
| switch(cmd) { |
| // Command: c[apture] |
| case 'capture': |
| case 'c': |
| request = this.createRequest('lol-capture'); |
| break; |
| |
| // Command: clear|d[elete] <id>|all |
| case 'clear': |
| case 'delete': |
| case 'del': { |
| if (args.length < 2) { |
| throw new Error('Missing argument after ' + cmd + '.'); |
| } else if (args.length > 2) { |
| throw new Error('Too many arguments after ' + cmd + '.'); |
| } |
| id = args[1]; |
| if (id.match(/^[0-9]+$/)) { |
| // Delete a specific lol record: |
| request = this.createRequest('lol-delete'); |
| request.arguments = {}; |
| request.arguments.id = parseInt(id); |
| } else if (id === 'all') { |
| // Delete all: |
| request = this.createRequest('lol-reset'); |
| } else { |
| throw new Error('Invalid argument after ' + cmd + '.'); |
| } |
| break; |
| } |
| |
| // Command: diff <id1> <id2> [<dump options>] |
| case 'diff': |
| first_arg_index = 3; |
| |
| // Command: list <dummy> <id> [<dump options>] |
| case 'list': |
| |
| // Command: ret[ainers] <obj id> [<dump options>] |
| case 'retainers': |
| case 'ret': |
| case 'retaining-paths': |
| case 'rp': { |
| if (cmd === 'ret') cmd = 'retainers'; |
| else if (cmd === 'rp') cmd = 'retaining-paths'; |
| |
| if (!first_arg_index) first_arg_index = 2; |
| |
| if (args.length < first_arg_index) { |
| throw new Error('Too few arguments after ' + cmd + '.'); |
| } |
| |
| var request_cmd = (cmd === 'list') ? 'diff':cmd; |
| request = this.lolMakeListRequest(request_cmd, |
| args, |
| first_arg_index, |
| is_repeating); |
| |
| if (cmd === 'diff') { |
| request.arguments.id1 = parseInt(args[1]); |
| request.arguments.id2 = parseInt(args[2]); |
| } else if (cmd == 'list') { |
| request.arguments.id1 = 0; |
| request.arguments.id2 = parseInt(args[1]); |
| } else { |
| request.arguments.id = extractObjId(args[1]); |
| } |
| break; |
| } |
| |
| // Command: getid |
| case 'getid': { |
| request = this.createRequest('lol-getid'); |
| request.arguments = {}; |
| request.arguments.address = args[1]; |
| break; |
| } |
| |
| // Command: inf[o] [<N>] |
| case 'info': |
| case 'inf': { |
| if (args.length > 2) { |
| throw new Error('Too many arguments after ' + cmd + '.'); |
| } |
| // Built the info request: |
| request = this.createLOLRequest('lol-info', 0, args[1], is_repeating); |
| break; |
| } |
| |
| // Command: path <obj id 1> <obj id 2> |
| case 'path': { |
| request = this.createRequest('lol-path'); |
| request.arguments = {}; |
| if (args.length > 2) { |
| request.arguments.id1 = extractObjId(args[1]); |
| request.arguments.id2 = extractObjId(args[2]); |
| } else { |
| request.arguments.id1 = 0; |
| request.arguments.id2 = extractObjId(args[1]); |
| } |
| break; |
| } |
| |
| // Command: print |
| case 'print': { |
| request = this.createRequest('lol-print'); |
| request.arguments = {}; |
| request.arguments.id = extractObjId(args[1]); |
| break; |
| } |
| |
| // Command: reset |
| case 'reset': { |
| request = this.createRequest('lol-reset'); |
| break; |
| } |
| |
| default: |
| throw new Error('Invalid arguments.'); |
| } |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the threads command. |
| DebugRequest.prototype.threadsCommandToJSONRequest_ = function(args) { |
| // Build a threads request from the text command. |
| var request = this.createRequest('threads'); |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Handle the trace command. |
| DebugRequest.prototype.traceCommand_ = function(args) { |
| // Process arguments. |
| if (args && args.length > 0) { |
| if (args == 'compile') { |
| trace_compile = !trace_compile; |
| print('Tracing of compiled scripts ' + (trace_compile ? 'on' : 'off')); |
| } else if (args === 'debug json' || args === 'json' || args === 'packets') { |
| trace_debug_json = !trace_debug_json; |
| print('Tracing of debug json packets ' + |
| (trace_debug_json ? 'on' : 'off')); |
| } else { |
| throw new Error('Invalid trace arguments.'); |
| } |
| } else { |
| throw new Error('Invalid trace arguments.'); |
| } |
| } |
| |
| // Handle the help command. |
| DebugRequest.prototype.helpCommand_ = function(args) { |
| // Help os quite simple. |
| if (args && args.length > 0) { |
| print('warning: arguments to \'help\' are ignored'); |
| } |
| |
| print('Note: <> denotes symbollic values to be replaced with real values.'); |
| print('Note: [] denotes optional parts of commands, or optional options / arguments.'); |
| print(' e.g. d[elete] - you get the same command if you type d or delete.'); |
| print(''); |
| print('[break] - break as soon as possible'); |
| print('b[reak] location [condition]'); |
| print(' - break on named function: location is a function name'); |
| print(' - break on function: location is #<id>#'); |
| print(' - break on script position: location is name:line[:column]'); |
| print(''); |
| print('clear <breakpoint #> - deletes the specified user defined breakpoint'); |
| print('d[elete] <breakpoint #> - deletes the specified user defined breakpoint'); |
| print('dis[able] <breakpoint #> - disables the specified user defined breakpoint'); |
| print('dis[able] exc[eptions] [[all] | unc[aught]]'); |
| print(' - disables breaking on exceptions'); |
| print('en[able] <breakpoint #> - enables the specified user defined breakpoint'); |
| print('en[able] exc[eptions] [[all] | unc[aught]]'); |
| print(' - enables breaking on exceptions'); |
| print(''); |
| print('b[ack]t[race] [n] | [-n] | [from to]'); |
| print(' - prints the stack back trace'); |
| print('f[rame] - prints info about the current frame context'); |
| print('f[rame] <frame #> - set context to specified frame #'); |
| print('scopes'); |
| print('scope <scope #>'); |
| print(''); |
| print('up - set context to caller of current frame'); |
| print('do[wn] - set context to callee of current frame'); |
| print('inf[o] br[eak] - prints info about breakpoints in use'); |
| print('inf[o] ar[gs] - prints info about arguments of the current function'); |
| print('inf[o] lo[cals] - prints info about locals in the current function'); |
| print('inf[o] liveobjectlist|lol - same as \'lol info\''); |
| print(''); |
| print('step [in | next | out| min [step count]]'); |
| print('c[ontinue] - continue executing after a breakpoint'); |
| print('s[tep] [<N>] - step into the next N callees (default N is 1)'); |
| print('s[tep]i [<N>] - step into the next N callees (default N is 1)'); |
| print('n[ext] [<N>] - step over the next N callees (default N is 1)'); |
| print('fin[ish] [<N>] - step out of N frames (default N is 1)'); |
| print(''); |
| print('p[rint] <expression> - prints the result of the specified expression'); |
| print('dir <expression> - prints the object structure of the result'); |
| print('set <var> = <expression> - executes the specified statement'); |
| print(''); |
| print('l[ist] - list the source code around for the current pc'); |
| print('l[ist] [- | <start>,<end>] - list the specified range of source code'); |
| print('source [from line [num lines]]'); |
| print('scr[ipts] [native|extensions|all]'); |
| print('scr[ipts] [<filter text>] - list scripts with the specified text in its description'); |
| print(''); |
| print('gc - runs the garbage collector'); |
| print(''); |
| |
| if (lol_is_enabled) { |
| print('liveobjectlist|lol <command> - live object list tracking.'); |
| print(' where <command> can be:'); |
| print(' c[apture] - captures a LOL list.'); |
| print(' clear|del[ete] <id>|all - clears LOL of id <id>.'); |
| print(' If \'all\' is unspecified instead, will clear all.'); |
| print(' diff <id1> <id2> [<dump options>]'); |
| print(' - prints the diff between LOLs id1 and id2.'); |
| print(' - also see <dump options> below.'); |
| print(' getid <address> - gets the obj id for the specified address if available.'); |
| print(' The address must be in hex form prefixed with 0x.'); |
| print(' inf[o] [<N>] - lists summary info of all LOL lists.'); |
| print(' If N is specified, will print N items at a time.'); |
| print(' [l[ist]] <id> [<dump options>]'); |
| print(' - prints the listing of objects in LOL id.'); |
| print(' - also see <dump options> below.'); |
| print(' reset - clears all LOL lists.'); |
| print(' ret[ainers] <id> [<dump options>]'); |
| print(' - prints the list of retainers of obj id.'); |
| print(' - also see <dump options> below.'); |
| print(' path <id1> <id2> - prints the retaining path from obj id1 to id2.'); |
| print(' If only one id is specified, will print the path from'); |
| print(' roots to the specified object if available.'); |
| print(' print <id> - prints the obj for the specified obj id if available.'); |
| print(''); |
| print(' <dump options> includes:'); |
| print(' [v[erbose]] - do verbose dump.'); |
| print(' [<N>] - dump N items at a time. Implies verbose dump.'); |
| print(' If unspecified, N will default to '+ |
| kDefaultLolLinesToPrintAtATime+'. Max N is '+ |
| kMaxLolLinesToPrintAtATime+'.'); |
| print(' [i[ndex] <i>] - start dump from index i. Implies verbose dump.'); |
| print(' [t[ype] <type>] - filter by type.'); |
| print(' [sp[ace] <space name>] - filter by heap space where <space name> is one of'); |
| print(' { cell, code, lo, map, new, old-data, old-pointer }.'); |
| print(''); |
| print(' If the verbose option, or an option that implies a verbose dump'); |
| print(' is specified, then a verbose dump will requested. Else, a summary dump'); |
| print(' will be requested.'); |
| print(''); |
| } |
| |
| print('trace compile'); |
| // hidden command: trace debug json - toggles tracing of debug json packets |
| print(''); |
| print('disconnect|exit|quit - disconnects and quits the debugger'); |
| print('help - prints this help information'); |
| } |
| |
| |
| function formatHandleReference_(value) { |
| if (value.handle() >= 0) { |
| return '#' + value.handle() + '#'; |
| } else { |
| return '#Transient#'; |
| } |
| } |
| |
| |
| function formatObject_(value, include_properties) { |
| var result = ''; |
| result += formatHandleReference_(value); |
| result += ', type: object' |
| result += ', constructor '; |
| var ctor = value.constructorFunctionValue(); |
| result += formatHandleReference_(ctor); |
| result += ', __proto__ '; |
| var proto = value.protoObjectValue(); |
| result += formatHandleReference_(proto); |
| result += ', '; |
| result += value.propertyCount(); |
| result += ' properties.'; |
| if (include_properties) { |
| result += '\n'; |
| for (var i = 0; i < value.propertyCount(); i++) { |
| result += ' '; |
| result += value.propertyName(i); |
| result += ': '; |
| var property_value = value.propertyValue(i); |
| if (property_value instanceof ProtocolReference) { |
| result += '<no type>'; |
| } else { |
| if (property_value && property_value.type()) { |
| result += property_value.type(); |
| } else { |
| result += '<no type>'; |
| } |
| } |
| result += ' '; |
| result += formatHandleReference_(property_value); |
| result += '\n'; |
| } |
| } |
| return result; |
| } |
| |
| |
| function formatScope_(scope) { |
| var result = ''; |
| var index = scope.index; |
| result += '#' + (index <= 9 ? '0' : '') + index; |
| result += ' '; |
| switch (scope.type) { |
| case Debug.ScopeType.Global: |
| result += 'Global, '; |
| result += '#' + scope.object.ref + '#'; |
| break; |
| case Debug.ScopeType.Local: |
| result += 'Local'; |
| break; |
| case Debug.ScopeType.With: |
| result += 'With, '; |
| result += '#' + scope.object.ref + '#'; |
| break; |
| case Debug.ScopeType.Catch: |
| result += 'Catch, '; |
| result += '#' + scope.object.ref + '#'; |
| break; |
| case Debug.ScopeType.Closure: |
| result += 'Closure'; |
| break; |
| default: |
| result += 'UNKNOWN'; |
| } |
| return result; |
| } |
| |
| |
| function refObjectToString_(protocolPackage, handle) { |
| var value = protocolPackage.lookup(handle); |
| var result = ''; |
| if (value.isString()) { |
| result = '"' + value.value() + '"'; |
| } else if (value.isPrimitive()) { |
| result = value.valueString(); |
| } else if (value.isObject()) { |
| result += formatObject_(value, true); |
| } |
| return result; |
| } |
| |
| |
| function decodeLolCaptureResponse(body) { |
| var result; |
| result = 'Captured live object list '+ body.id + |
| ': count '+ body.count + ' size ' + body.size; |
| return result; |
| } |
| |
| |
| function decodeLolDeleteResponse(body) { |
| var result; |
| result = 'Deleted live object list '+ body.id; |
| return result; |
| } |
| |
| |
| function digitsIn(value) { |
| var digits = 0; |
| if (value === 0) value = 1; |
| while (value >= 1) { |
| digits++; |
| value /= 10; |
| } |
| return digits; |
| } |
| |
| |
| function padding(value, max_digits) { |
| var padding_digits = max_digits - digitsIn(value); |
| var padding = ''; |
| while (padding_digits > 0) { |
| padding += ' '; |
| padding_digits--; |
| } |
| return padding; |
| } |
| |
| |
| function decodeLolInfoResponse(body) { |
| var result; |
| var lists = body.lists; |
| var length = lists.length; |
| var first_index = body.first_index + 1; |
| var has_more = ((first_index + length) <= body.count); |
| result = 'captured live object lists'; |
| if (has_more || (first_index != 1)) { |
| result += ' ['+ length +' of '+ body.count + |
| ': starting from '+ first_index +']'; |
| } |
| result += ':\n'; |
| var max_digits = digitsIn(body.count); |
| var last_count = 0; |
| var last_size = 0; |
| for (var i = 0; i < length; i++) { |
| var entry = lists[i]; |
| var count = entry.count; |
| var size = entry.size; |
| var index = first_index + i; |
| result += ' [' + padding(index, max_digits) + index + '] id '+ entry.id + |
| ': count '+ count; |
| if (last_count > 0) { |
| result += '(+' + (count - last_count) + ')'; |
| } |
| result += ' size '+ size; |
| if (last_size > 0) { |
| result += '(+' + (size - last_size) + ')'; |
| } |
| result += '\n'; |
| last_count = count; |
| last_size = size; |
| } |
| result += ' total: '+length+' lists\n'; |
| if (has_more) { |
| result += ' -- press <enter> for more --\n'; |
| } else { |
| repeat_cmd_line = ''; |
| } |
| if (length === 0) result += ' none\n'; |
| |
| return result; |
| } |
| |
| |
| function decodeLolListResponse(body, title) { |
| |
| var result; |
| var total_count = body.count; |
| var total_size = body.size; |
| var length; |
| var max_digits; |
| var i; |
| var entry; |
| var index; |
| |
| var max_count_digits = digitsIn(total_count); |
| var max_size_digits; |
| |
| var summary = body.summary; |
| if (summary) { |
| |
| var roots_count = 0; |
| var found_root = body.found_root || 0; |
| var found_weak_root = body.found_weak_root || 0; |
| |
| // Print the summary result: |
| result = 'summary of objects:\n'; |
| length = summary.length; |
| if (found_root !== 0) { |
| roots_count++; |
| } |
| if (found_weak_root !== 0) { |
| roots_count++; |
| } |
| max_digits = digitsIn(length + roots_count); |
| max_size_digits = digitsIn(total_size); |
| |
| index = 1; |
| if (found_root !== 0) { |
| result += ' [' + padding(index, max_digits) + index + '] ' + |
| ' count '+ 1 + padding(0, max_count_digits) + |
| ' '+ padding(0, max_size_digits+1) + |
| ' : <root>\n'; |
| index++; |
| } |
| if (found_weak_root !== 0) { |
| result += ' [' + padding(index, max_digits) + index + '] ' + |
| ' count '+ 1 + padding(0, max_count_digits) + |
| ' '+ padding(0, max_size_digits+1) + |
| ' : <weak root>\n'; |
| index++; |
| } |
| |
| for (i = 0; i < length; i++) { |
| entry = summary[i]; |
| var count = entry.count; |
| var size = entry.size; |
| result += ' [' + padding(index, max_digits) + index + '] ' + |
| ' count '+ count + padding(count, max_count_digits) + |
| ' size '+ size + padding(size, max_size_digits) + |
| ' : <' + entry.desc + '>\n'; |
| index++; |
| } |
| result += '\n total count: '+(total_count+roots_count)+'\n'; |
| if (body.size) { |
| result += ' total size: '+body.size+'\n'; |
| } |
| |
| } else { |
| // Print the full dump result: |
| var first_index = body.first_index + 1; |
| var elements = body.elements; |
| length = elements.length; |
| var has_more = ((first_index + length) <= total_count); |
| result = title; |
| if (has_more || (first_index != 1)) { |
| result += ' ['+ length +' of '+ total_count + |
| ': starting from '+ first_index +']'; |
| } |
| result += ':\n'; |
| if (length === 0) result += ' none\n'; |
| max_digits = digitsIn(length); |
| |
| var max_id = 0; |
| var max_size = 0; |
| for (i = 0; i < length; i++) { |
| entry = elements[i]; |
| if (entry.id > max_id) max_id = entry.id; |
| if (entry.size > max_size) max_size = entry.size; |
| } |
| var max_id_digits = digitsIn(max_id); |
| max_size_digits = digitsIn(max_size); |
| |
| for (i = 0; i < length; i++) { |
| entry = elements[i]; |
| index = first_index + i; |
| result += ' ['+ padding(index, max_digits) + index +']'; |
| if (entry.id !== 0) { |
| result += ' @' + entry.id + padding(entry.id, max_id_digits) + |
| ': size ' + entry.size + ', ' + |
| padding(entry.size, max_size_digits) + entry.desc + '\n'; |
| } else { |
| // Must be a root or weak root: |
| result += ' ' + entry.desc + '\n'; |
| } |
| } |
| if (has_more) { |
| result += ' -- press <enter> for more --\n'; |
| } else { |
| repeat_cmd_line = ''; |
| } |
| if (length === 0) result += ' none\n'; |
| } |
| |
| return result; |
| } |
| |
| |
| function decodeLolDiffResponse(body) { |
| var title = 'objects'; |
| return decodeLolListResponse(body, title); |
| } |
| |
| |
| function decodeLolRetainersResponse(body) { |
| var title = 'retainers for @' + body.id; |
| return decodeLolListResponse(body, title); |
| } |
| |
| |
| function decodeLolPathResponse(body) { |
| return body.path; |
| } |
| |
| |
| function decodeLolResetResponse(body) { |
| return 'Reset all live object lists.'; |
| } |
| |
| |
| function decodeLolGetIdResponse(body) { |
| if (body.id == 0) { |
| return 'Address is invalid, or object has been moved or collected'; |
| } |
| return 'obj id is @' + body.id; |
| } |
| |
| |
| function decodeLolPrintResponse(body) { |
| return body.dump; |
| } |
| |
| |
| // Rounds number 'num' to 'length' decimal places. |
| function roundNumber(num, length) { |
| var factor = Math.pow(10, length); |
| return Math.round(num * factor) / factor; |
| } |
| |
| |
| // Convert a JSON response to text for display in a text based debugger. |
| function DebugResponseDetails(response) { |
| details = {text:'', running:false} |
| |
| try { |
| if (!response.success()) { |
| details.text = response.message(); |
| return details; |
| } |
| |
| // Get the running state. |
| details.running = response.running(); |
| |
| var body = response.body(); |
| var result = ''; |
| switch (response.command()) { |
| case 'suspend': |
| details.text = 'stopped'; |
| break; |
| |
| case 'setbreakpoint': |
| result = 'set breakpoint #'; |
| result += body.breakpoint; |
| details.text = result; |
| break; |
| |
| case 'clearbreakpoint': |
| result = 'cleared breakpoint #'; |
| result += body.breakpoint; |
| details.text = result; |
| break; |
| |
| case 'changebreakpoint': |
| result = 'successfully changed breakpoint'; |
| details.text = result; |
| break; |
| |
| case 'listbreakpoints': |
| result = 'breakpoints: (' + body.breakpoints.length + ')'; |
| for (var i = 0; i < body.breakpoints.length; i++) { |
| var breakpoint = body.breakpoints[i]; |
| result += '\n id=' + breakpoint.number; |
| result += ' type=' + breakpoint.type; |
| if (breakpoint.script_id) { |
| result += ' script_id=' + breakpoint.script_id; |
| } |
| if (breakpoint.script_name) { |
| result += ' script_name=' + breakpoint.script_name; |
| } |
| result += ' line=' + (breakpoint.line + 1); |
| if (breakpoint.column != null) { |
| result += ' column=' + (breakpoint.column + 1); |
| } |
| if (breakpoint.groupId) { |
| result += ' groupId=' + breakpoint.groupId; |
| } |
| if (breakpoint.ignoreCount) { |
| result += ' ignoreCount=' + breakpoint.ignoreCount; |
| } |
| if (breakpoint.active === false) { |
| result += ' inactive'; |
| } |
| if (breakpoint.condition) { |
| result += ' condition=' + breakpoint.condition; |
| } |
| result += ' hit_count=' + breakpoint.hit_count; |
| } |
| if (body.breakpoints.length === 0) { |
| result = "No user defined breakpoints\n"; |
| } else { |
| result += '\n'; |
| } |
| if (body.breakOnExceptions) { |
| result += '* breaking on ALL exceptions is enabled\n'; |
| } else if (body.breakOnUncaughtExceptions) { |
| result += '* breaking on UNCAUGHT exceptions is enabled\n'; |
| } else { |
| result += '* all exception breakpoints are disabled\n'; |
| } |
| details.text = result; |
| break; |
| |
| case 'setexceptionbreak': |
| result = 'Break on ' + body.type + ' exceptions: '; |
| result += body.enabled ? 'enabled' : 'disabled'; |
| details.text = result; |
| break; |
| |
| case 'backtrace': |
| if (body.totalFrames == 0) { |
| result = '(empty stack)'; |
| } else { |
| var result = 'Frames #' + body.fromFrame + ' to #' + |
| (body.toFrame - 1) + ' of ' + body.totalFrames + '\n'; |
| for (i = 0; i < body.frames.length; i++) { |
| if (i != 0) result += '\n'; |
| result += body.frames[i].text; |
| } |
| } |
| details.text = result; |
| break; |
| |
| case 'frame': |
| if (last_cmd === 'info locals') { |
| var locals = body.locals; |
| if (locals.length === 0) { |
| result = 'No locals'; |
| } else { |
| for (var i = 0; i < locals.length; i++) { |
| var local = locals[i]; |
| result += local.name + ' = '; |
| result += refObjectToString_(response, local.value.ref); |
| result += '\n'; |
| } |
| } |
| } else if (last_cmd === 'info args') { |
| var args = body.arguments; |
| if (args.length === 0) { |
| result = 'No arguments'; |
| } else { |
| for (var i = 0; i < args.length; i++) { |
| var arg = args[i]; |
| result += arg.name + ' = '; |
| result += refObjectToString_(response, arg.value.ref); |
| result += '\n'; |
| } |
| } |
| } else { |
| result = SourceUnderline(body.sourceLineText, |
| body.column); |
| Debug.State.currentSourceLine = body.line; |
| Debug.State.currentFrame = body.index; |
| Debug.State.displaySourceStartLine = -1; |
| Debug.State.displaySourceEndLine = -1; |
| } |
| details.text = result; |
| break; |
| |
| case 'scopes': |
| if (body.totalScopes == 0) { |
| result = '(no scopes)'; |
| } else { |
| result = 'Scopes #' + body.fromScope + ' to #' + |
| (body.toScope - 1) + ' of ' + body.totalScopes + '\n'; |
| for (i = 0; i < body.scopes.length; i++) { |
| if (i != 0) { |
| result += '\n'; |
| } |
| result += formatScope_(body.scopes[i]); |
| } |
| } |
| details.text = result; |
| break; |
| |
| case 'scope': |
| result += formatScope_(body); |
| result += '\n'; |
| var scope_object_value = response.lookup(body.object.ref); |
| result += formatObject_(scope_object_value, true); |
| details.text = result; |
| break; |
| |
| case 'evaluate': |
| case 'lookup': |
| case 'getobj': |
| if (last_cmd == 'p' || last_cmd == 'print') { |
| result = body.text; |
| } else { |
| var value; |
| if (lookup_handle) { |
| value = response.bodyValue(lookup_handle); |
| } else { |
| value = response.bodyValue(); |
| } |
| if (value.isObject()) { |
| result += formatObject_(value, true); |
| } else { |
| result += 'type: '; |
| result += value.type(); |
| if (!value.isUndefined() && !value.isNull()) { |
| result += ', '; |
| if (value.isString()) { |
| result += '"'; |
| } |
| result += value.value(); |
| if (value.isString()) { |
| result += '"'; |
| } |
| } |
| result += '\n'; |
| } |
| } |
| details.text = result; |
| break; |
| |
| case 'references': |
| var count = body.length; |
| result += 'found ' + count + ' objects'; |
| result += '\n'; |
| for (var i = 0; i < count; i++) { |
| var value = response.bodyValue(i); |
| result += formatObject_(value, false); |
| result += '\n'; |
| } |
| details.text = result; |
| break; |
| |
| case 'source': |
| // Get the source from the response. |
| var source = body.source; |
| var from_line = body.fromLine + 1; |
| var lines = source.split('\n'); |
| var maxdigits = 1 + Math.floor(log10(from_line + lines.length)); |
| if (maxdigits < 3) { |
| maxdigits = 3; |
| } |
| var result = ''; |
| for (var num = 0; num < lines.length; num++) { |
| // Check if there's an extra newline at the end. |
| if (num == (lines.length - 1) && lines[num].length == 0) { |
| break; |
| } |
| |
| var current_line = from_line + num; |
| spacer = maxdigits - (1 + Math.floor(log10(current_line))); |
| if (current_line == Debug.State.currentSourceLine + 1) { |
| for (var i = 0; i < maxdigits; i++) { |
| result += '>'; |
| } |
| result += ' '; |
| } else { |
| for (var i = 0; i < spacer; i++) { |
| result += ' '; |
| } |
| result += current_line + ': '; |
| } |
| result += lines[num]; |
| result += '\n'; |
| } |
| details.text = result; |
| break; |
| |
| case 'scripts': |
| var result = ''; |
| for (i = 0; i < body.length; i++) { |
| if (i != 0) result += '\n'; |
| if (body[i].id) { |
| result += body[i].id; |
| } else { |
| result += '[no id]'; |
| } |
| result += ', '; |
| if (body[i].name) { |
| result += body[i].name; |
| } else { |
| if (body[i].compilationType == Debug.ScriptCompilationType.Eval |
| && body[i].evalFromScript |
| ) { |
| result += 'eval from '; |
| var script_value = response.lookup(body[i].evalFromScript.ref); |
| result += ' ' + script_value.field('name'); |
| result += ':' + (body[i].evalFromLocation.line + 1); |
| result += ':' + body[i].evalFromLocation.column; |
| } else if (body[i].compilationType == |
| Debug.ScriptCompilationType.JSON) { |
| result += 'JSON '; |
| } else { // body[i].compilation == Debug.ScriptCompilationType.Host |
| result += '[unnamed] '; |
| } |
| } |
| result += ' (lines: '; |
| result += body[i].lineCount; |
| result += ', length: '; |
| result += body[i].sourceLength; |
| if (body[i].type == Debug.ScriptType.Native) { |
| result += ', native'; |
| } else if (body[i].type == Debug.ScriptType.Extension) { |
| result += ', extension'; |
| } |
| result += '), ['; |
| var sourceStart = body[i].sourceStart; |
| if (sourceStart.length > 40) { |
| sourceStart = sourceStart.substring(0, 37) + '...'; |
| } |
| result += sourceStart; |
| result += ']'; |
| } |
| if (body.length == 0) { |
| result = "no matching scripts found"; |
| } |
| details.text = result; |
| break; |
| |
| case 'threads': |
| var result = 'Active V8 threads: ' + body.totalThreads + '\n'; |
| body.threads.sort(function(a, b) { return a.id - b.id; }); |
| for (i = 0; i < body.threads.length; i++) { |
| result += body.threads[i].current ? '*' : ' '; |
| result += ' '; |
| result += body.threads[i].id; |
| result += '\n'; |
| } |
| details.text = result; |
| break; |
| |
| case 'continue': |
| details.text = "(running)"; |
| break; |
| |
| case 'v8flags': |
| details.text = "flags set"; |
| break; |
| |
| case 'gc': |
| details.text = "GC " + body.before + " => " + body.after; |
| if (body.after > (1024*1024)) { |
| details.text += |
| " (" + roundNumber(body.before/(1024*1024), 1) + "M => " + |
| roundNumber(body.after/(1024*1024), 1) + "M)"; |
| } else if (body.after > 1024) { |
| details.text += |
| " (" + roundNumber(body.before/1024, 1) + "K => " + |
| roundNumber(body.after/1024, 1) + "K)"; |
| } |
| break; |
| |
| case 'lol-capture': |
| details.text = decodeLolCaptureResponse(body); |
| break; |
| case 'lol-delete': |
| details.text = decodeLolDeleteResponse(body); |
| break; |
| case 'lol-diff': |
| details.text = decodeLolDiffResponse(body); |
| break; |
| case 'lol-getid': |
| details.text = decodeLolGetIdResponse(body); |
| break; |
| case 'lol-info': |
| details.text = decodeLolInfoResponse(body); |
| break; |
| case 'lol-print': |
| details.text = decodeLolPrintResponse(body); |
| break; |
| case 'lol-reset': |
| details.text = decodeLolResetResponse(body); |
| break; |
| case 'lol-retainers': |
| details.text = decodeLolRetainersResponse(body); |
| break; |
| case 'lol-path': |
| details.text = decodeLolPathResponse(body); |
| break; |
| |
| default: |
| details.text = |
| 'Response for unknown command \'' + response.command() + '\'' + |
| ' (' + response.raw_json() + ')'; |
| } |
| } catch (e) { |
| details.text = 'Error: "' + e + '" formatting response'; |
| } |
| |
| return details; |
| }; |
| |
| |
| /** |
| * Protocol packages send from the debugger. |
| * @param {string} json - raw protocol packet as JSON string. |
| * @constructor |
| */ |
| function ProtocolPackage(json) { |
| this.raw_json_ = json; |
| this.packet_ = JSON.parse(json); |
| this.refs_ = []; |
| if (this.packet_.refs) { |
| for (var i = 0; i < this.packet_.refs.length; i++) { |
| this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i]; |
| } |
| } |
| } |
| |
| |
| /** |
| * Get the packet type. |
| * @return {String} the packet type |
| */ |
| ProtocolPackage.prototype.type = function() { |
| return this.packet_.type; |
| } |
| |
| |
| /** |
| * Get the packet event. |
| * @return {Object} the packet event |
| */ |
| ProtocolPackage.prototype.event = function() { |
| return this.packet_.event; |
| } |
| |
| |
| /** |
| * Get the packet request sequence. |
| * @return {number} the packet request sequence |
| */ |
| ProtocolPackage.prototype.requestSeq = function() { |
| return this.packet_.request_seq; |
| } |
| |
| |
| /** |
| * Get the packet request sequence. |
| * @return {number} the packet request sequence |
| */ |
| ProtocolPackage.prototype.running = function() { |
| return this.packet_.running ? true : false; |
| } |
| |
| |
| ProtocolPackage.prototype.success = function() { |
| return this.packet_.success ? true : false; |
| } |
| |
| |
| ProtocolPackage.prototype.message = function() { |
| return this.packet_.message; |
| } |
| |
| |
| ProtocolPackage.prototype.command = function() { |
| return this.packet_.command; |
| } |
| |
| |
| ProtocolPackage.prototype.body = function() { |
| return this.packet_.body; |
| } |
| |
| |
| ProtocolPackage.prototype.bodyValue = function(index) { |
| if (index != null) { |
| return new ProtocolValue(this.packet_.body[index], this); |
| } else { |
| return new ProtocolValue(this.packet_.body, this); |
| } |
| } |
| |
| |
| ProtocolPackage.prototype.body = function() { |
| return this.packet_.body; |
| } |
| |
| |
| ProtocolPackage.prototype.lookup = function(handle) { |
| var value = this.refs_[handle]; |
| if (value) { |
| return new ProtocolValue(value, this); |
| } else { |
| return new ProtocolReference(handle); |
| } |
| } |
| |
| |
| ProtocolPackage.prototype.raw_json = function() { |
| return this.raw_json_; |
| } |
| |
| |
| function ProtocolValue(value, packet) { |
| this.value_ = value; |
| this.packet_ = packet; |
| } |
| |
| |
| /** |
| * Get the value type. |
| * @return {String} the value type |
| */ |
| ProtocolValue.prototype.type = function() { |
| return this.value_.type; |
| } |
| |
| |
| /** |
| * Get a metadata field from a protocol value. |
| * @return {Object} the metadata field value |
| */ |
| ProtocolValue.prototype.field = function(name) { |
| return this.value_[name]; |
| } |
| |
| |
| /** |
| * Check is the value is a primitive value. |
| * @return {boolean} true if the value is primitive |
| */ |
| ProtocolValue.prototype.isPrimitive = function() { |
| return this.isUndefined() || this.isNull() || this.isBoolean() || |
| this.isNumber() || this.isString(); |
| } |
| |
| |
| /** |
| * Get the object handle. |
| * @return {number} the value handle |
| */ |
| ProtocolValue.prototype.handle = function() { |
| return this.value_.handle; |
| } |
| |
| |
| /** |
| * Check is the value is undefined. |
| * @return {boolean} true if the value is undefined |
| */ |
| ProtocolValue.prototype.isUndefined = function() { |
| return this.value_.type == 'undefined'; |
| } |
| |
| |
| /** |
| * Check is the value is null. |
| * @return {boolean} true if the value is null |
| */ |
| ProtocolValue.prototype.isNull = function() { |
| return this.value_.type == 'null'; |
| } |
| |
| |
| /** |
| * Check is the value is a boolean. |
| * @return {boolean} true if the value is a boolean |
| */ |
| ProtocolValue.prototype.isBoolean = function() { |
| return this.value_.type == 'boolean'; |
| } |
| |
| |
| /** |
| * Check is the value is a number. |
| * @return {boolean} true if the value is a number |
| */ |
| ProtocolValue.prototype.isNumber = function() { |
| return this.value_.type == 'number'; |
| } |
| |
| |
| /** |
| * Check is the value is a string. |
| * @return {boolean} true if the value is a string |
| */ |
| ProtocolValue.prototype.isString = function() { |
| return this.value_.type == 'string'; |
| } |
| |
| |
| /** |
| * Check is the value is an object. |
| * @return {boolean} true if the value is an object |
| */ |
| ProtocolValue.prototype.isObject = function() { |
| return this.value_.type == 'object' || this.value_.type == 'function' || |
| this.value_.type == 'error' || this.value_.type == 'regexp'; |
| } |
| |
| |
| /** |
| * Get the constructor function |
| * @return {ProtocolValue} constructor function |
| */ |
| ProtocolValue.prototype.constructorFunctionValue = function() { |
| var ctor = this.value_.constructorFunction; |
| return this.packet_.lookup(ctor.ref); |
| } |
| |
| |
| /** |
| * Get the __proto__ value |
| * @return {ProtocolValue} __proto__ value |
| */ |
| ProtocolValue.prototype.protoObjectValue = function() { |
| var proto = this.value_.protoObject; |
| return this.packet_.lookup(proto.ref); |
| } |
| |
| |
| /** |
| * Get the number og properties. |
| * @return {number} the number of properties |
| */ |
| ProtocolValue.prototype.propertyCount = function() { |
| return this.value_.properties ? this.value_.properties.length : 0; |
| } |
| |
| |
| /** |
| * Get the specified property name. |
| * @return {string} property name |
| */ |
| ProtocolValue.prototype.propertyName = function(index) { |
| var property = this.value_.properties[index]; |
| return property.name; |
| } |
| |
| |
| /** |
| * Return index for the property name. |
| * @param name The property name to look for |
| * @return {number} index for the property name |
| */ |
| ProtocolValue.prototype.propertyIndex = function(name) { |
| for (var i = 0; i < this.propertyCount(); i++) { |
| if (this.value_.properties[i].name == name) { |
| return i; |
| } |
| } |
| return null; |
| } |
| |
| |
| /** |
| * Get the specified property value. |
| * @return {ProtocolValue} property value |
| */ |
| ProtocolValue.prototype.propertyValue = function(index) { |
| var property = this.value_.properties[index]; |
| return this.packet_.lookup(property.ref); |
| } |
| |
| |
| /** |
| * Check is the value is a string. |
| * @return {boolean} true if the value is a string |
| */ |
| ProtocolValue.prototype.value = function() { |
| return this.value_.value; |
| } |
| |
| |
| ProtocolValue.prototype.valueString = function() { |
| return this.value_.text; |
| } |
| |
| |
| function ProtocolReference(handle) { |
| this.handle_ = handle; |
| } |
| |
| |
| ProtocolReference.prototype.handle = function() { |
| return this.handle_; |
| } |
| |
| |
| function MakeJSONPair_(name, value) { |
| return '"' + name + '":' + value; |
| } |
| |
| |
| function ArrayToJSONObject_(content) { |
| return '{' + content.join(',') + '}'; |
| } |
| |
| |
| function ArrayToJSONArray_(content) { |
| return '[' + content.join(',') + ']'; |
| } |
| |
| |
| function BooleanToJSON_(value) { |
| return String(value); |
| } |
| |
| |
| function NumberToJSON_(value) { |
| return String(value); |
| } |
| |
| |
| // Mapping of some control characters to avoid the \uXXXX syntax for most |
| // commonly used control cahracters. |
| const ctrlCharMap_ = { |
| '\b': '\\b', |
| '\t': '\\t', |
| '\n': '\\n', |
| '\f': '\\f', |
| '\r': '\\r', |
| '"' : '\\"', |
| '\\': '\\\\' |
| }; |
| |
| |
| // Regular expression testing for ", \ and control characters (0x00 - 0x1F). |
| const ctrlCharTest_ = new RegExp('["\\\\\x00-\x1F]'); |
| |
| |
| // Regular expression matching ", \ and control characters (0x00 - 0x1F) |
| // globally. |
| const ctrlCharMatch_ = new RegExp('["\\\\\x00-\x1F]', 'g'); |
| |
| |
| /** |
| * Convert a String to its JSON representation (see http://www.json.org/). To |
| * avoid depending on the String object this method calls the functions in |
| * string.js directly and not through the value. |
| * @param {String} value The String value to format as JSON |
| * @return {string} JSON formatted String value |
| */ |
| function StringToJSON_(value) { |
| // Check for" , \ and control characters (0x00 - 0x1F). No need to call |
| // RegExpTest as ctrlchar is constructed using RegExp. |
| if (ctrlCharTest_.test(value)) { |
| // Replace ", \ and control characters (0x00 - 0x1F). |
| return '"' + |
| value.replace(ctrlCharMatch_, function (char) { |
| // Use charmap if possible. |
| var mapped = ctrlCharMap_[char]; |
| if (mapped) return mapped; |
| mapped = char.charCodeAt(); |
| // Convert control character to unicode escape sequence. |
| return '\\u00' + |
| '0' + // TODO %NumberToRadixString(Math.floor(mapped / 16), 16) + |
| '0' // TODO %NumberToRadixString(mapped % 16, 16); |
| }) |
| + '"'; |
| } |
| |
| // Simple string with no special characters. |
| return '"' + value + '"'; |
| } |
| |
| |
| /** |
| * Convert a Date to ISO 8601 format. To avoid depending on the Date object |
| * this method calls the functions in date.js directly and not through the |
| * value. |
| * @param {Date} value The Date value to format as JSON |
| * @return {string} JSON formatted Date value |
| */ |
| function DateToISO8601_(value) { |
| function f(n) { |
| return n < 10 ? '0' + n : n; |
| } |
| function g(n) { |
| return n < 10 ? '00' + n : n < 100 ? '0' + n : n; |
| } |
| return builtins.GetUTCFullYearFrom(value) + '-' + |
| f(builtins.GetUTCMonthFrom(value) + 1) + '-' + |
| f(builtins.GetUTCDateFrom(value)) + 'T' + |
| f(builtins.GetUTCHoursFrom(value)) + ':' + |
| f(builtins.GetUTCMinutesFrom(value)) + ':' + |
| f(builtins.GetUTCSecondsFrom(value)) + '.' + |
| g(builtins.GetUTCMillisecondsFrom(value)) + 'Z'; |
| } |
| |
| |
| /** |
| * Convert a Date to ISO 8601 format. To avoid depending on the Date object |
| * this method calls the functions in date.js directly and not through the |
| * value. |
| * @param {Date} value The Date value to format as JSON |
| * @return {string} JSON formatted Date value |
| */ |
| function DateToJSON_(value) { |
| return '"' + DateToISO8601_(value) + '"'; |
| } |
| |
| |
| /** |
| * Convert an Object to its JSON representation (see http://www.json.org/). |
| * This implementation simply runs through all string property names and adds |
| * each property to the JSON representation for some predefined types. For type |
| * "object" the function calls itself recursively unless the object has the |
| * function property "toJSONProtocol" in which case that is used. This is not |
| * a general implementation but sufficient for the debugger. Note that circular |
| * structures will cause infinite recursion. |
| * @param {Object} object The object to format as JSON |
| * @return {string} JSON formatted object value |
| */ |
| function SimpleObjectToJSON_(object) { |
| var content = []; |
| for (var key in object) { |
| // Only consider string keys. |
| if (typeof key == 'string') { |
| var property_value = object[key]; |
| |
| // Format the value based on its type. |
| var property_value_json; |
| switch (typeof property_value) { |
| case 'object': |
| if (property_value === null) { |
| property_value_json = 'null'; |
| } else if (typeof property_value.toJSONProtocol == 'function') { |
| property_value_json = property_value.toJSONProtocol(true) |
| } else if (property_value.constructor.name == 'Array'){ |
| property_value_json = SimpleArrayToJSON_(property_value); |
| } else { |
| property_value_json = SimpleObjectToJSON_(property_value); |
| } |
| break; |
| |
| case 'boolean': |
| property_value_json = BooleanToJSON_(property_value); |
| break; |
| |
| case 'number': |
| property_value_json = NumberToJSON_(property_value); |
| break; |
| |
| case 'string': |
| property_value_json = StringToJSON_(property_value); |
| break; |
| |
| default: |
| property_value_json = null; |
| } |
| |
| // Add the property if relevant. |
| if (property_value_json) { |
| content.push(StringToJSON_(key) + ':' + property_value_json); |
| } |
| } |
| } |
| |
| // Make JSON object representation. |
| return '{' + content.join(',') + '}'; |
| } |
| |
| |
| /** |
| * Convert an array to its JSON representation. This is a VERY simple |
| * implementation just to support what is needed for the debugger. |
| * @param {Array} arrya The array to format as JSON |
| * @return {string} JSON formatted array value |
| */ |
| function SimpleArrayToJSON_(array) { |
| // Make JSON array representation. |
| var json = '['; |
| for (var i = 0; i < array.length; i++) { |
| if (i != 0) { |
| json += ','; |
| } |
| var elem = array[i]; |
| if (elem.toJSONProtocol) { |
| json += elem.toJSONProtocol(true) |
| } else if (typeof(elem) === 'object') { |
| json += SimpleObjectToJSON_(elem); |
| } else if (typeof(elem) === 'boolean') { |
| json += BooleanToJSON_(elem); |
| } else if (typeof(elem) === 'number') { |
| json += NumberToJSON_(elem); |
| } else if (typeof(elem) === 'string') { |
| json += StringToJSON_(elem); |
| } else { |
| json += elem; |
| } |
| } |
| json += ']'; |
| return json; |
| } |