mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
13 KiB
433 lines
13 KiB
8 years ago
|
// Copyright 2016 the V8 project authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
// found in the LICENSE file.
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
// If true, prints all messages sent and received by inspector.
|
||
|
const printProtocolMessages = false;
|
||
|
|
||
|
// The active wrapper instance.
|
||
|
let activeWrapper = undefined;
|
||
|
|
||
|
// Receiver function called by inspector, delegating to active wrapper.
|
||
|
function receive(message) {
|
||
|
activeWrapper.receiveMessage(message);
|
||
|
}
|
||
|
|
||
|
class DebugWrapper {
|
||
|
constructor() {
|
||
|
// Message dictionary storing {id, message} pairs.
|
||
|
this.receivedMessages = new Map();
|
||
|
|
||
|
// Each message dispatched by the Debug wrapper is assigned a unique number
|
||
|
// using nextMessageId.
|
||
|
this.nextMessageId = 0;
|
||
|
|
||
|
// The listener method called on certain events.
|
||
|
this.listener = undefined;
|
||
|
|
||
|
// TODO(jgruber): Determine which of these are still required and possible.
|
||
|
// Debug events which can occur in the V8 JavaScript engine.
|
||
|
this.DebugEvent = { Break: 1,
|
||
|
Exception: 2,
|
||
|
NewFunction: 3,
|
||
|
BeforeCompile: 4,
|
||
|
AfterCompile: 5,
|
||
|
CompileError: 6,
|
||
|
AsyncTaskEvent: 7
|
||
|
};
|
||
|
|
||
|
// The different types of steps.
|
||
|
this.StepAction = { StepOut: 0,
|
||
|
StepNext: 1,
|
||
|
StepIn: 2,
|
||
|
StepFrame: 3,
|
||
|
};
|
||
|
|
||
|
// A copy of the scope types from runtime-debug.cc.
|
||
|
// NOTE: these constants should be backward-compatible, so
|
||
|
// add new ones to the end of this list.
|
||
|
this.ScopeType = { Global: 0,
|
||
|
Local: 1,
|
||
|
With: 2,
|
||
|
Closure: 3,
|
||
|
Catch: 4,
|
||
|
Block: 5,
|
||
|
Script: 6,
|
||
|
Eval: 7,
|
||
|
Module: 8
|
||
|
};
|
||
|
|
||
|
// Types of exceptions that can be broken upon.
|
||
|
this.ExceptionBreak = { Caught : 0,
|
||
|
Uncaught: 1 };
|
||
|
|
||
|
// Store the current script id so we can skip corresponding break events.
|
||
|
this.thisScriptId = %FunctionGetScriptId(receive);
|
||
|
|
||
|
// Register as the active wrapper.
|
||
|
assertTrue(activeWrapper === undefined);
|
||
|
activeWrapper = this;
|
||
|
}
|
||
|
|
||
|
enable() { this.sendMessageForMethodChecked("Debugger.enable"); }
|
||
|
disable() { this.sendMessageForMethodChecked("Debugger.disable"); }
|
||
|
|
||
|
setListener(listener) { this.listener = listener; }
|
||
|
|
||
|
stepOver() { this.sendMessageForMethodChecked("Debugger.stepOver"); }
|
||
|
stepInto() { this.sendMessageForMethodChecked("Debugger.stepInto"); }
|
||
|
stepOut() { this.sendMessageForMethodChecked("Debugger.stepOut"); }
|
||
|
|
||
|
setBreakOnException() {
|
||
|
this.sendMessageForMethodChecked(
|
||
|
"Debugger.setPauseOnExceptions", { state : "all" });
|
||
|
}
|
||
|
|
||
|
clearBreakOnException() {
|
||
|
const newState = this.isBreakOnUncaughtException() ? "uncaught" : "none";
|
||
|
this.sendMessageForMethodChecked(
|
||
|
"Debugger.setPauseOnExceptions", { state : newState });
|
||
|
}
|
||
|
|
||
|
isBreakOnException() {
|
||
|
return !!%IsBreakOnException(this.ExceptionBreak.Caught);
|
||
|
};
|
||
|
|
||
|
setBreakOnUncaughtException() {
|
||
|
const newState = this.isBreakOnException() ? "all" : "uncaught";
|
||
|
this.sendMessageForMethodChecked(
|
||
|
"Debugger.setPauseOnExceptions", { state : newState });
|
||
|
}
|
||
|
|
||
|
clearBreakOnUncaughtException() {
|
||
|
const newState = this.isBreakOnException() ? "all" : "none";
|
||
|
this.sendMessageForMethodChecked(
|
||
|
"Debugger.setPauseOnExceptions", { state : newState });
|
||
|
}
|
||
|
|
||
|
isBreakOnUncaughtException() {
|
||
|
return !!%IsBreakOnException(this.ExceptionBreak.Uncaught);
|
||
|
};
|
||
|
|
||
|
clearStepping() { %ClearStepping(); };
|
||
|
|
||
|
// Returns the resulting breakpoint id.
|
||
|
setBreakPoint(func, opt_line, opt_column, opt_condition) {
|
||
|
assertTrue(%IsFunction(func));
|
||
|
assertFalse(%FunctionIsAPIFunction(func));
|
||
|
|
||
|
// TODO(jgruber): We handle only script breakpoints for now.
|
||
|
|
||
|
const scriptid = %FunctionGetScriptId(func);
|
||
|
assertTrue(scriptid != -1);
|
||
|
|
||
|
const offset = %FunctionGetScriptSourcePosition(func);
|
||
|
const loc =
|
||
|
%ScriptLocationFromLine2(scriptid, opt_line, opt_column, offset);
|
||
|
|
||
|
const params = { location :
|
||
|
{ scriptId : scriptid.toString(),
|
||
|
lineNumber : loc.line,
|
||
|
columnNumber : loc.column,
|
||
|
}};
|
||
|
if (!!opt_condition) {
|
||
|
params.condition = opt_condition;
|
||
|
}
|
||
|
|
||
|
const {msgid, msg} = this.createMessage(
|
||
|
"Debugger.setBreakpoint", params);
|
||
|
this.sendMessage(msg);
|
||
|
|
||
|
const reply = this.takeReplyChecked(msgid);
|
||
|
assertTrue(reply.result !== undefined);
|
||
|
const breakid = reply.result.breakpointId;
|
||
|
assertTrue(breakid !== undefined);
|
||
|
|
||
|
return breakid;
|
||
|
}
|
||
|
|
||
|
clearBreakPoint(breakid) {
|
||
|
const {msgid, msg} = this.createMessage(
|
||
|
"Debugger.removeBreakpoint", { breakpointId : breakid });
|
||
|
this.sendMessage(msg);
|
||
|
this.takeReplyChecked(msgid);
|
||
|
}
|
||
|
|
||
|
// Returns the serialized result of the given expression. For example:
|
||
|
// {"type":"number", "value":33, "description":"33"}.
|
||
|
evaluate(frameid, expression) {
|
||
|
const {msgid, msg} = this.createMessage(
|
||
|
"Debugger.evaluateOnCallFrame",
|
||
|
{ callFrameId : frameid,
|
||
|
expression : expression
|
||
|
});
|
||
|
this.sendMessage(msg);
|
||
|
|
||
|
const reply = this.takeReplyChecked(msgid);
|
||
|
return reply.result.result;
|
||
|
}
|
||
|
|
||
|
// --- Internal methods. -----------------------------------------------------
|
||
|
|
||
|
getNextMessageId() {
|
||
|
return this.nextMessageId++;
|
||
|
}
|
||
|
|
||
|
createMessage(method, params) {
|
||
|
const id = this.getNextMessageId();
|
||
|
const msg = JSON.stringify({
|
||
|
id: id,
|
||
|
method: method,
|
||
|
params: params,
|
||
|
});
|
||
|
return { msgid : id, msg: msg };
|
||
|
}
|
||
|
|
||
|
receiveMessage(message) {
|
||
|
if (printProtocolMessages) print(message);
|
||
|
|
||
|
const parsedMessage = JSON.parse(message);
|
||
|
if (parsedMessage.id !== undefined) {
|
||
|
this.receivedMessages.set(parsedMessage.id, parsedMessage);
|
||
|
}
|
||
|
|
||
|
this.dispatchMessage(parsedMessage);
|
||
|
}
|
||
|
|
||
|
sendMessage(message) {
|
||
|
if (printProtocolMessages) print(message);
|
||
|
send(message);
|
||
|
}
|
||
|
|
||
|
sendMessageForMethodChecked(method, params) {
|
||
|
const {msgid, msg} = this.createMessage(method, params);
|
||
|
this.sendMessage(msg);
|
||
|
this.takeReplyChecked(msgid);
|
||
|
}
|
||
|
|
||
|
takeReplyChecked(msgid) {
|
||
|
const reply = this.receivedMessages.get(msgid);
|
||
|
assertTrue(reply !== undefined);
|
||
|
this.receivedMessages.delete(msgid);
|
||
|
return reply;
|
||
|
}
|
||
|
|
||
|
execStatePrepareStep(action) {
|
||
|
switch(action) {
|
||
|
case this.StepAction.StepOut: this.stepOut(); break;
|
||
|
case this.StepAction.StepNext: this.stepOver(); break;
|
||
|
case this.StepAction.StepIn: this.stepInto(); break;
|
||
|
default: %AbortJS("Unsupported StepAction"); break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
execStateScopeType(type) {
|
||
|
switch (type) {
|
||
|
case "global": return this.ScopeType.Global;
|
||
|
case "local": return this.ScopeType.Local;
|
||
|
case "with": return this.ScopeType.With;
|
||
|
case "closure": return this.ScopeType.Closure;
|
||
|
case "catch": return this.ScopeType.Catch;
|
||
|
case "block": return this.ScopeType.Block;
|
||
|
case "script": return this.ScopeType.Script;
|
||
|
default: %AbortJS("Unexpected scope type");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Returns an array of property descriptors of the scope object.
|
||
|
// This is in contrast to the original API, which simply passed object
|
||
|
// mirrors.
|
||
|
execStateScopeObject(obj) {
|
||
|
const serialized_scope = this.getProperties(obj.objectId);
|
||
|
const scope = {}
|
||
|
const scope_tuples = serialized_scope.forEach((elem) => {
|
||
|
const key = elem.name;
|
||
|
|
||
|
let value;
|
||
|
if (elem.value) {
|
||
|
// Some properties (e.g. with getters/setters) don't have a value.
|
||
|
switch (elem.value.type) {
|
||
|
case "undefined": value = undefined; break;
|
||
|
default: value = elem.value.value; break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
scope[key] = value;
|
||
|
})
|
||
|
|
||
|
return { value : () => scope };
|
||
|
}
|
||
|
|
||
|
execStateScope(scope) {
|
||
|
return { scopeType : () => this.execStateScopeType(scope.type),
|
||
|
scopeObject : () => this.execStateScopeObject(scope.object)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
getProperties(objectId) {
|
||
|
const {msgid, msg} = this.createMessage(
|
||
|
"Runtime.getProperties", { objectId : objectId });
|
||
|
this.sendMessage(msg);
|
||
|
const reply = this.takeReplyChecked(msgid);
|
||
|
return reply.result.result;
|
||
|
}
|
||
|
|
||
|
getLocalScopeDetails(frame) {
|
||
|
const scopes = frame.scopeChain;
|
||
|
for (let i = 0; i < scopes.length; i++) {
|
||
|
const scope = scopes[i]
|
||
|
if (scope.type == "local") {
|
||
|
return this.getProperties(scope.object.objectId);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return undefined;
|
||
|
}
|
||
|
|
||
|
execStateFrameLocalCount(frame) {
|
||
|
const scope_details = this.getLocalScopeDetails(frame);
|
||
|
return scope_details ? scope_details.length : 0;
|
||
|
}
|
||
|
|
||
|
execStateFrameLocalName(frame, index) {
|
||
|
const scope_details = this.getLocalScopeDetails(frame);
|
||
|
if (index < 0 || index >= scope_details.length) return undefined;
|
||
|
return scope_details[index].name;
|
||
|
}
|
||
|
|
||
|
execStateFrameLocalValue(frame, index) {
|
||
|
const scope_details = this.getLocalScopeDetails(frame);
|
||
|
if (index < 0 || index >= scope_details.length) return undefined;
|
||
|
|
||
|
const local = scope_details[index];
|
||
|
|
||
|
let localValue;
|
||
|
switch (local.value.type) {
|
||
|
case "undefined": localValue = undefined; break;
|
||
|
default: localValue = local.value.value; break;
|
||
|
}
|
||
|
|
||
|
return { value : () => localValue };
|
||
|
}
|
||
|
|
||
|
execStateFrameEvaluate(frame, expr) {
|
||
|
const frameid = frame.callFrameId;
|
||
|
const {msgid, msg} = this.createMessage(
|
||
|
"Debugger.evaluateOnCallFrame",
|
||
|
{ callFrameId : frameid,
|
||
|
expression : expr
|
||
|
});
|
||
|
this.sendMessage(msg);
|
||
|
const reply = this.takeReplyChecked(msgid);
|
||
|
|
||
|
const result = reply.result.result;
|
||
|
if (result.subtype == "error") {
|
||
|
throw new Error(result.description);
|
||
|
}
|
||
|
|
||
|
return { value : () => result.value };
|
||
|
}
|
||
|
|
||
|
execStateFrame(frame) {
|
||
|
const scriptid = parseInt(frame.location.scriptId);
|
||
|
const line = frame.location.lineNumber;
|
||
|
const column = frame.location.columnNumber;
|
||
|
const loc = %ScriptLocationFromLine2(scriptid, line, column, 0);
|
||
|
const func = { name : () => frame.functionName };
|
||
|
return { sourceLineText : () => loc.sourceText,
|
||
|
evaluate : (expr) => this.execStateFrameEvaluate(frame, expr),
|
||
|
functionName : () => frame.functionName,
|
||
|
func : () => func,
|
||
|
localCount : () => this.execStateFrameLocalCount(frame),
|
||
|
localName : (ix) => this.execStateFrameLocalName(frame, ix),
|
||
|
localValue: (ix) => this.execStateFrameLocalValue(frame, ix),
|
||
|
scopeCount : () => frame.scopeChain.length,
|
||
|
scope : (index) => this.execStateScope(frame.scopeChain[index]),
|
||
|
allScopes : () => frame.scopeChain.map(
|
||
|
this.execStateScope.bind(this))
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// --- Message handlers. -----------------------------------------------------
|
||
|
|
||
|
dispatchMessage(message) {
|
||
|
const method = message.method;
|
||
|
if (method == "Debugger.paused") {
|
||
|
this.handleDebuggerPaused(message);
|
||
|
} else if (method == "Debugger.scriptParsed") {
|
||
|
this.handleDebuggerScriptParsed(message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
handleDebuggerPaused(message) {
|
||
|
const params = message.params;
|
||
|
|
||
|
var debugEvent;
|
||
|
switch (params.reason) {
|
||
|
case "exception":
|
||
|
case "promiseRejection":
|
||
|
debugEvent = this.DebugEvent.Exception;
|
||
|
break;
|
||
|
default:
|
||
|
// TODO(jgruber): More granularity.
|
||
|
debugEvent = this.DebugEvent.Break;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Skip break events in this file.
|
||
|
if (params.callFrames[0].location.scriptId == this.thisScriptId) return;
|
||
|
|
||
|
// TODO(jgruber): Arguments as needed.
|
||
|
let execState = { frames : params.callFrames,
|
||
|
prepareStep : this.execStatePrepareStep.bind(this),
|
||
|
frame : (index) => this.execStateFrame(
|
||
|
index ? params.callFrames[index]
|
||
|
: params.callFrames[0]),
|
||
|
frameCount : () => params.callFrames.length
|
||
|
};
|
||
|
|
||
|
let eventData = this.execStateFrame(params.callFrames[0]);
|
||
|
if (debugEvent == this.DebugEvent.Exception) {
|
||
|
eventData.uncaught = () => params.data.uncaught;
|
||
|
}
|
||
|
|
||
|
this.invokeListener(debugEvent, execState, eventData);
|
||
|
}
|
||
|
|
||
|
handleDebuggerScriptParsed(message) {
|
||
|
const params = message.params;
|
||
|
let eventData = { scriptId : params.scriptId,
|
||
|
eventType : this.DebugEvent.AfterCompile
|
||
|
}
|
||
|
|
||
|
// TODO(jgruber): Arguments as needed. Still completely missing exec_state,
|
||
|
// and eventData used to contain the script mirror instead of its id.
|
||
|
this.invokeListener(this.DebugEvent.AfterCompile, undefined, eventData,
|
||
|
undefined);
|
||
|
}
|
||
|
|
||
|
invokeListener(event, exec_state, event_data, data) {
|
||
|
if (this.listener) {
|
||
|
this.listener(event, exec_state, event_data, data);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Simulate the debug object generated by --expose-debug-as debug.
|
||
|
var debug = { instance : undefined };
|
||
|
|
||
|
Object.defineProperty(debug, 'Debug', { get: function() {
|
||
|
if (!debug.instance) {
|
||
|
debug.instance = new DebugWrapper();
|
||
|
debug.instance.enable();
|
||
|
}
|
||
|
return debug.instance;
|
||
|
}});
|
||
|
|
||
|
Object.defineProperty(debug, 'ScopeType', { get: function() {
|
||
|
const instance = debug.Debug;
|
||
|
return instance.ScopeType;
|
||
|
}});
|