| // 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. |
| |
| // How crappy is it that I have to implement completely basic stuff |
| // like this myself? Answer: very. |
| 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 }; |
| |
| |
| // Current debug state. |
| const kNoFrame = -1; |
| Debug.State = { |
| currentFrame: kNoFrame, |
| currentSourceLine: -1 |
| } |
| var trace_compile = false; // Tracing all compile events? |
| |
| |
| // 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) { |
| // Convert the JSON string to an object. |
| var response = new ProtocolPackage(message); |
| |
| 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.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.currentFrame = 0; |
| } else { |
| result += ' (empty stack)'; |
| Debug.State.currentSourceLine = -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; |
| |
| 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) { |
| return new DebugRequest(cmd_line).JSONRequest(); |
| }; |
| |
| |
| 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; |
| } |
| |
| // 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, ''); |
| } |
| |
| // Switch on command. |
| switch (cmd) { |
| case 'continue': |
| case 'c': |
| this.request_ = this.continueCommandToJSONRequest_(args); |
| break; |
| |
| case 'step': |
| case 's': |
| this.request_ = this.stepCommandToJSONRequest_(args); |
| break; |
| |
| case 'backtrace': |
| case 'bt': |
| this.request_ = this.backtraceCommandToJSONRequest_(args); |
| break; |
| |
| case 'frame': |
| case 'f': |
| this.request_ = this.frameCommandToJSONRequest_(args); |
| break; |
| |
| 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 'source': |
| this.request_ = this.sourceCommandToJSONRequest_(args); |
| break; |
| |
| case 'scripts': |
| this.request_ = this.scriptsCommandToJSONRequest_(args); |
| break; |
| |
| case 'break': |
| case 'b': |
| this.request_ = this.breakCommandToJSONRequest_(args); |
| break; |
| |
| case 'clear': |
| this.request_ = this.clearCommandToJSONRequest_(args); |
| break; |
| |
| case 'threads': |
| this.request_ = this.threadsCommandToJSONRequest_(args); |
| break; |
| |
| case 'trace': |
| // 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; |
| |
| default: |
| throw new Error('Unknown command "' + cmd + '"'); |
| } |
| |
| last_cmd = 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); |
| }; |
| |
| |
| // Create a JSON request for the evaluation command. |
| DebugRequest.prototype.makeEvaluateJSONRequest_ = function(expression) { |
| // Check if the expression is a handle id in the form #<handle>#. |
| var handle_match = expression.match(/^#([0-9]*)#$/); |
| if (handle_match) { |
| // Build a lookup request. |
| var request = this.createRequest('lookup'); |
| request.arguments = {}; |
| request.arguments.handle = parseInt(handle_match[1]); |
| 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) { |
| // Requesting a step is through the continue command with additional |
| // arguments. |
| var request = this.createRequest('continue'); |
| request.arguments = {}; |
| |
| // Process arguments if any. |
| if (args && args.length > 0) { |
| args = args.split(/\s*[ ]+\s*/g); |
| |
| if (args.length > 2) { |
| throw new Error('Invalid step arguments.'); |
| } |
| |
| if (args.length > 0) { |
| // Get step count argument if any. |
| 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 { |
| // Default is step next. |
| request.arguments.stepaction = 'next'; |
| } |
| |
| 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 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 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: |
| throw new Error('Invalid argument "' + args[0] + '".'); |
| } |
| } |
| |
| return request.toJSONProtocol(); |
| }; |
| |
| |
| // Create a JSON request for the break command. |
| DebugRequest.prototype.breakCommandToJSONRequest_ = function(args) { |
| // Build a evaluate request from the text command. |
| var request = this.createRequest('setbreakpoint'); |
| |
| // Process arguments if any. |
| if (args && args.length > 0) { |
| var target = args; |
| var type = 'function'; |
| var line; |
| var column; |
| var condition; |
| var pos; |
| |
| // 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 { |
| throw new Error('Invalid break arguments.'); |
| } |
| |
| 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 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 { |
| 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('break 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('clear <breakpoint #>'); |
| print('backtrace [n] | [-n] | [from to]'); |
| print('frame <frame #>'); |
| print('step [in | next | out| min [step count]]'); |
| print('print <expression>'); |
| print('source [from line [num lines]]'); |
| print('scripts'); |
| print('continue'); |
| print('trace compile'); |
| print('help'); |
| } |
| |
| |
| function formatHandleReference_(value) { |
| return '#' + value.handle() + '#'; |
| } |
| |
| |
| 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 && property_value.type()) { |
| result += property_value.type(); |
| } else { |
| result += '<no type>'; |
| } |
| result += ' '; |
| result += formatHandleReference_(property_value); |
| result += '\n'; |
| } |
| } |
| return result; |
| } |
| |
| |
| // 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 'setbreakpoint': |
| result = 'set breakpoint #'; |
| result += body.breakpoint; |
| details.text = result; |
| break; |
| |
| case 'clearbreakpoint': |
| result = 'cleared breakpoint #'; |
| result += body.breakpoint; |
| 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': |
| details.text = SourceUnderline(body.sourceLineText, |
| body.column); |
| Debug.State.currentSourceLine = body.line; |
| Debug.State.currentFrame = body.index; |
| break; |
| |
| case 'evaluate': |
| case 'lookup': |
| if (last_cmd == 'p' || last_cmd == 'print') { |
| result = body.text; |
| } else { |
| var 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 { |
| 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 += ']'; |
| } |
| 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; |
| |
| default: |
| details.text = |
| 'Response for unknown command \'' + response.command + '\'' + |
| ' (' + json_response + ')'; |
| } |
| } 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.packet_ = eval('(' + 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) { |
| 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); |
| } |
| } |
| |
| |
| 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; |
| } |
| |
| |
| /** |
| * 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; |
| } |
| |
| |
| 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 (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; |
| } |