v8/test/debugger/test-api.js

802 lines
24 KiB
JavaScript
Raw Normal View History

// 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;
// Debug events which can occur in the V8 JavaScript engine.
this.DebugEvent = { Break: 1,
Exception: 2,
AfterCompile: 3,
CompileError: 4,
OOM: 5,
};
// The different types of steps.
this.StepAction = { StepOut: 0,
StepOver: 1,
StepInto: 2,
};
// 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);
// Stores all set breakpoints.
this.breakpoints = new Set();
// 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));
const scriptid = %FunctionGetScriptId(func);
assertTrue(scriptid != -1);
const offset = %FunctionGetScriptSourcePosition(func);
const loc =
%ScriptLocationFromLine2(scriptid, opt_line, opt_column, offset);
return this.setBreakPointAtLocation(scriptid, loc, opt_condition);
}
clearBreakPoint(breakpoint) {
assertTrue(this.breakpoints.has(breakpoint));
const breakid = breakpoint.id;
const {msgid, msg} = this.createMessage(
"Debugger.removeBreakpoint", { breakpointId : breakid });
this.sendMessage(msg);
this.takeReplyChecked(msgid);
this.breakpoints.delete(breakid);
}
clearAllBreakPoints() {
for (let breakpoint of this.breakpoints) {
this.clearBreakPoint(breakpoint);
}
this.breakpoints.clear();
}
showBreakPoints(f) {
2016-11-16 08:34:01 +00:00
if (!%IsFunction(f)) throw new Error("Not passed a Function");
const source = %FunctionGetSourceCode(f);
const offset = %FunctionGetScriptSourcePosition(f);
const locations = %GetBreakLocations(f);
2016-11-16 08:34:01 +00:00
if (!locations) return source;
locations.sort(function(x, y) { return x - y; });
let result = "";
let prev_pos = 0;
let pos;
for (var i = 0; i < locations.length; i++) {
pos = locations[i] - offset;
result += source.slice(prev_pos, pos);
result += "[B" + i + "]";
prev_pos = pos;
}
pos = source.length;
result += source.substring(prev_pos, pos);
return result;
}
debuggerFlags() {
return { breakPointsActive :
{ setValue : (enabled) => this.setBreakPointsActive(enabled) }
};
}
// Returns the script source. The return value is the script source for the
// script in which the function is defined.
scriptSource(func) {
return %FunctionGetScriptSource(func);
};
2016-11-16 08:34:01 +00:00
setBreakPointsActive(enabled) {
const {msgid, msg} = this.createMessage(
2016-11-16 08:34:01 +00:00
"Debugger.setBreakpointsActive", { active : enabled });
this.sendMessage(msg);
2016-11-16 08:34:01 +00:00
this.takeReplyChecked(msgid);
}
generatorScopeCount(gen) {
return %GetGeneratorScopeCount(gen);
}
generatorScope(gen, index) {
// These indexes correspond definitions in debug-scopes.h.
const kScopeDetailsTypeIndex = 0;
const kScopeDetailsObjectIndex = 1;
const details = %GetGeneratorScopeDetails(gen, index);
function scopeObjectProperties() {
const obj = details[kScopeDetailsObjectIndex];
return Object.keys(obj).map((k, v) => v);
}
function setScopeVariableValue(name, value) {
const res = %SetGeneratorScopeVariableValue(gen, index, name, value);
if (!res) throw new Error("Failed to set variable '" + name + "' value");
}
const scopeObject =
{ value : () => details[kScopeDetailsObjectIndex],
property : (prop) => details[kScopeDetailsObjectIndex][prop],
properties : scopeObjectProperties,
propertyNames : () => Object.keys(details[kScopeDetailsObjectIndex])
.map((key, _) => key),
};
return { scopeType : () => details[kScopeDetailsTypeIndex],
scopeIndex : () => index,
scopeObject : () => scopeObject,
setVariableValue : setScopeVariableValue,
}
}
generatorScopes(gen) {
const count = %GetGeneratorScopeCount(gen);
const scopes = [];
for (let i = 0; i < count; i++) {
scopes.push(this.generatorScope(gen, i));
}
return scopes;
}
// --- 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) {
const parsedMessage = JSON.parse(message);
if (printProtocolMessages) {
print(JSON.stringify(parsedMessage, undefined, 1));
}
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;
}
setBreakPointAtLocation(scriptid, loc, opt_condition) {
const params = { location :
{ scriptId : scriptid.toString(),
lineNumber : loc.line,
columnNumber : loc.column,
},
condition : opt_condition,
};
const {msgid, msg} = this.createMessage("Debugger.setBreakpoint", params);
this.sendMessage(msg);
const reply = this.takeReplyChecked(msgid);
const result = reply.result;
assertTrue(result !== undefined);
const breakid = result.breakpointId;
assertTrue(breakid !== undefined);
const breakpoint = { id : result.breakpointId }
this.breakpoints.add(breakpoint);
return breakpoint;
}
execStatePrepareStep(action) {
switch(action) {
case this.StepAction.StepOut: this.stepOut(); break;
case this.StepAction.StepOver: this.stepOver(); break;
case this.StepAction.StepInto: 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;
case "eval": return this.ScopeType.Eval;
case "module": return this.ScopeType.Module;
default: %AbortJS("Unexpected scope type");
}
}
2016-11-16 08:34:01 +00:00
execStateScopeObjectProperty(serialized_scope, prop) {
let found = null;
for (let i = 0; i < serialized_scope.length; i++) {
const elem = serialized_scope[i];
if (elem.name == prop) {
found = elem;
break;
}
}
if (found == null) return { isUndefined : () => true };
2016-11-16 08:34:01 +00:00
const val = { value : () => found.value.value };
// Not undefined in the sense that we did find a property, even though
// the value can be 'undefined'.
2016-11-16 08:34:01 +00:00
return { value : () => val,
isUndefined : () => false,
2016-11-16 08:34:01 +00:00
};
}
// 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);
2016-11-16 08:34:01 +00:00
const scope = this.propertiesToObject(serialized_scope);
return { value : () => scope,
property : (prop) =>
this.execStateScopeObjectProperty(serialized_scope, prop),
properties : () => serialized_scope.map(elem => elem.value),
propertyNames : () => serialized_scope.map(elem => elem.name)
};
}
2016-11-16 08:34:01 +00:00
setVariableValue(frame, scope_index, name, value) {
const frameid = frame.callFrameId;
const {msgid, msg} = this.createMessage(
"Debugger.setVariableValue",
{ callFrameId : frameid,
scopeNumber : scope_index,
variableName : name,
newValue : { value : value }
});
this.sendMessage(msg);
const reply = this.takeReplyChecked(msgid);
if (reply.error) {
throw new Error("Failed to set variable '" + name + "' value");
}
2016-11-16 08:34:01 +00:00
}
execStateScope(frame, scope_index) {
const scope = frame.scopeChain[scope_index];
return { scopeType : () => this.execStateScopeType(scope.type),
scopeIndex : () => scope_index,
frameIndex : () => frame.callFrameId,
2016-11-16 08:34:01 +00:00
scopeObject : () => this.execStateScopeObject(scope.object),
setVariableValue :
(name, value) => this.setVariableValue(frame, scope_index,
name, value),
2016-11-16 08:34:01 +00:00
};
}
// Takes a list of properties as produced by getProperties and turns them
// into an object.
propertiesToObject(props) {
const obj = {}
props.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;
}
}
2016-11-16 08:34:01 +00:00
obj[key] = value;
})
2016-11-16 08:34:01 +00:00
return obj;
}
getProperties(objectId) {
const {msgid, msg} = this.createMessage(
"Runtime.getProperties", { objectId : objectId, ownProperties: true });
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 };
}
reconstructValue(objectId) {
const {msgid, msg} = this.createMessage(
"Runtime.getProperties", { objectId : objectId, ownProperties: true });
this.sendMessage(msg);
const reply = this.takeReplyChecked(msgid);
Reland "[inspector] Report [[Prototype]] as internal property." This is a reland of 2b94e5677f6c75667b10a44bf648e8367110b627 Original change's description: > [inspector] Report [[Prototype]] as internal property. > > Previously the inspector was trying to add a special `__proto__` > property to every JSObject, which looked and behaved like a real > data property on the object. But this is confusing to developers > since `__proto__` is not a real data property, but usually an > accessor property on the `Object.prototype`. > > Additionally all other internal properties are reported using the > [[Name]] notation, with the [[Prototype]] having been the strange > outlier. > > Drive-by-cleanup: Use an ArrayList to collect the name/value pairs > inside Runtime::GetInternalProperties(), which makes this function > more readable and easier to add things. > > Bug: chromuium:1162229 > Fixed: chromium:1197019 > Screenshot: https://imgur.com/a/b7TZ32s.png > Change-Id: Ic4c1e35e2e65f90619fcc12bf3a72806cadb0794 > Doc: http://doc/1Xetnc9s6r0yy4LnPbqeCwsnsOtBlvJsV4OCdXMZ1wCM > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2814565 > Auto-Submit: Benedikt Meurer <bmeurer@chromium.org> > Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> > Reviewed-by: Yang Guo <yangguo@chromium.org> > Cr-Commit-Position: refs/heads/master@{#73881} Bug: chromuium:1162229, chromium:1197019 Screenshot: https://imgur.com/a/b7TZ32s.png Doc: http://doc/1Xetnc9s6r0yy4LnPbqeCwsnsOtBlvJsV4OCdXMZ1wCM Change-Id: Ie1e2276b385b18a5f865fdae583d1ce0101157c0 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2820970 Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Yang Guo <yangguo@chromium.org> Auto-Submit: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Cr-Commit-Position: refs/heads/master@{#73899}
2021-04-09 08:03:45 +00:00
for (const internalProperty of reply.result.internalProperties) {
if (internalProperty.name === '[[PrimitiveValue]]') {
return Object(internalProperty.value.value);
}
}
throw new Error('Remote object is not a value wrapper');
}
2016-11-16 08:34:01 +00:00
reconstructRemoteObject(obj) {
let value = obj.value;
let isUndefined = false;
switch (obj.type) {
case "object": {
switch (obj.subtype) {
case "error": {
const desc = obj.description;
switch (obj.className) {
case "EvalError": throw new EvalError(desc);
case "RangeError": throw new RangeError(desc);
case "ReferenceError": throw new ReferenceError(desc);
case "SyntaxError": throw new SyntaxError(desc);
case "TypeError": throw new TypeError(desc);
case "URIError": throw new URIError(desc);
default: throw new Error(desc);
}
break;
}
case "array": {
const array = [];
const props = this.propertiesToObject(
this.getProperties(obj.objectId));
for (let i = 0; i < props.length; i++) {
array[i] = props[i];
}
value = array;
break;
}
case "null": {
value = null;
break;
}
default: {
switch (obj.className) {
case "global":
value = Function('return this')();
break;
case "Number":
case "String":
case "Boolean":
value = this.reconstructValue(obj.objectId);
break;
default:
value = this.propertiesToObject(
this.getProperties(obj.objectId));
break;
}
break;
}
2016-11-16 08:34:01 +00:00
}
break;
}
case "undefined": {
value = undefined;
isUndefined = true;
break;
}
case "number": {
Revert "[inspector] RemoteObject.description should be empty for primitive type" This reverts commit 003159e777ea2af3a8a9653955897a44d8e34e72. Reason for revert: breaks roll into Chromium: https://ci.chromium.org/p/chromium/builders/luci.chromium.try/linux_chromium_headless_rel/3140 Original change's description: > [inspector] RemoteObject.description should be empty for primitive type > > We currently report description field for numbers. On client side user > can calculate description as remoteObject.unserializableValue || > (remoteObject.value + ''). Let's report description only for objects to > simplify value -> remoteObject logic a bit. > > R=​dgozman@chromium.org > TBR=jgruber@chromium.org > > Bug: chromium:595206 > Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel > Change-Id: I91356a44aa3024e20c8f966869abf4a41b88e4bc > Reviewed-on: https://chromium-review.googlesource.com/737485 > Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> > Reviewed-by: Pavel Feldman <pfeldman@chromium.org> > Cr-Commit-Position: refs/heads/master@{#53453} TBR=dgozman@chromium.org,pfeldman@chromium.org,kozyatinskiy@chromium.org Change-Id: Ifc184e1ac158d9ea7034922a7250444448fac49f No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: chromium:595206 Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel Reviewed-on: https://chromium-review.googlesource.com/1081207 Reviewed-by: Sergiy Byelozyorov <sergiyb@chromium.org> Commit-Queue: Sergiy Byelozyorov <sergiyb@chromium.org> Cr-Commit-Position: refs/heads/master@{#53461}
2018-05-31 18:51:49 +00:00
if (obj.description === "NaN") {
value = NaN;
}
break;
}
case "bigint": {
assertEquals("n", obj.unserializableValue.charAt(
obj.unserializableValue.length - 1));
value = eval(obj.unserializableValue);
break;
}
case "string":
case "boolean": {
break;
}
case "function": {
value = obj.description;
}
default: {
break;
2016-11-16 08:34:01 +00:00
}
}
return { value : () => value,
isUndefined : () => isUndefined,
type : () => obj.type,
className : () => obj.className
2016-11-16 08:34:01 +00:00
};
}
evaluateOnCallFrame(frame, expr, throw_on_side_effect = false) {
const frameid = frame.callFrameId;
const {msgid, msg} = this.createMessage(
"Debugger.evaluateOnCallFrame",
{ callFrameId : frameid,
expression : expr,
throwOnSideEffect : throw_on_side_effect,
});
this.sendMessage(msg);
const reply = this.takeReplyChecked(msgid);
const result = reply.result.result;
2016-11-16 08:34:01 +00:00
return this.reconstructRemoteObject(result);
}
frameReceiver(frame) {
return this.reconstructRemoteObject(frame.this);
}
frameReturnValue(frame) {
return this.reconstructRemoteObject(frame.returnValue);
}
execStateFrameRestart(frame) {
const frameid = frame.callFrameId;
const {msgid, msg} = this.createMessage(
"Debugger.restartFrame", { callFrameId : frameid });
this.sendMessage(msg);
this.takeReplyChecked(msgid);
}
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 };
const index = +frame.callFrameId.split(".")[2];
2016-11-16 08:34:01 +00:00
function allScopes() {
const scopes = [];
for (let i = 0; i < frame.scopeChain.length; i++) {
scopes.push(this.execStateScope(frame, i));
}
return scopes;
}
2016-11-16 08:34:01 +00:00
return { sourceColumn : () => column,
sourceLine : () => line + 1,
2016-11-16 08:34:01 +00:00
sourceLineText : () => loc.sourceText,
sourcePosition : () => loc.position,
evaluate : (expr, throw_on_side_effect) =>
this.evaluateOnCallFrame(frame, expr, throw_on_side_effect),
functionName : () => frame.functionName,
func : () => func,
index : () => index,
localCount : () => this.execStateFrameLocalCount(frame),
localName : (ix) => this.execStateFrameLocalName(frame, ix),
localValue: (ix) => this.execStateFrameLocalValue(frame, ix),
receiver : () => this.frameReceiver(frame),
restart : () => this.execStateFrameRestart(frame),
returnValue : () => this.frameReturnValue(frame),
scopeCount : () => frame.scopeChain.length,
2016-11-16 08:34:01 +00:00
scope : (index) => this.execStateScope(frame, index),
allScopes : allScopes.bind(this)
};
}
Reland "[debug] introduced runtime side effect check" This is a reland of 7a2c3713839f0915bd01738bf4633d8e5693651f Original change's description: > [debug] introduced runtime side effect check > > This CL demonstrates minimum valuable addition to existing debug evaluate > without side effects mechanism. > With this CL user can evaluate expressions like: > [a,b] // create any kind of temporary array literals > [a,b].reduce((x,y) => x + y, 0); // use reduce method > [1,2,3].fill(2); // change temporary arrays > > The core idea: any change of the object created during evaluation without > side effects is side effect free. As soon as we try to store this temporary > object to object existed before evaluation we will terminate execution. > > Implementation: > - track all objects allocated during evaluation and mark them as temporary, > - patch all bytecodes which change objects. > > A little more details (including performance analysis): [1]. > > [1] https://docs.google.com/document/d/10qqAtZADspPnpYa6SEdYRxrddfKIZJIzbLtGpsZQkRo/edit# > > Bug: v8:7588 > Change-Id: I69f7b96e1ebd7ad0022219e8213211c7be72a111 > Reviewed-on: https://chromium-review.googlesource.com/972615 > Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> > Reviewed-by: Yang Guo <yangguo@chromium.org> > Reviewed-by: Ulan Degenbaev <ulan@chromium.org> > Cr-Commit-Position: refs/heads/master@{#52370} Bug: v8:7588 Change-Id: Ibc92bf19155f2ddaedae39b0c576b994e84afcf8 Reviewed-on: https://chromium-review.googlesource.com/996760 Reviewed-by: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> Cr-Commit-Position: refs/heads/master@{#52373}
2018-04-05 02:01:34 +00:00
evaluateGlobal(expr, throw_on_side_effect) {
const {msgid, msg} = this.createMessage(
Reland "[debug] introduced runtime side effect check" This is a reland of 7a2c3713839f0915bd01738bf4633d8e5693651f Original change's description: > [debug] introduced runtime side effect check > > This CL demonstrates minimum valuable addition to existing debug evaluate > without side effects mechanism. > With this CL user can evaluate expressions like: > [a,b] // create any kind of temporary array literals > [a,b].reduce((x,y) => x + y, 0); // use reduce method > [1,2,3].fill(2); // change temporary arrays > > The core idea: any change of the object created during evaluation without > side effects is side effect free. As soon as we try to store this temporary > object to object existed before evaluation we will terminate execution. > > Implementation: > - track all objects allocated during evaluation and mark them as temporary, > - patch all bytecodes which change objects. > > A little more details (including performance analysis): [1]. > > [1] https://docs.google.com/document/d/10qqAtZADspPnpYa6SEdYRxrddfKIZJIzbLtGpsZQkRo/edit# > > Bug: v8:7588 > Change-Id: I69f7b96e1ebd7ad0022219e8213211c7be72a111 > Reviewed-on: https://chromium-review.googlesource.com/972615 > Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> > Reviewed-by: Yang Guo <yangguo@chromium.org> > Reviewed-by: Ulan Degenbaev <ulan@chromium.org> > Cr-Commit-Position: refs/heads/master@{#52370} Bug: v8:7588 Change-Id: Ibc92bf19155f2ddaedae39b0c576b994e84afcf8 Reviewed-on: https://chromium-review.googlesource.com/996760 Reviewed-by: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> Cr-Commit-Position: refs/heads/master@{#52373}
2018-04-05 02:01:34 +00:00
"Runtime.evaluate", { expression : expr, throwOnSideEffect: throw_on_side_effect });
this.sendMessage(msg);
const reply = this.takeReplyChecked(msgid);
const result = reply.result.result;
return this.reconstructRemoteObject(result);
}
evaluateGlobalREPL(expr) {
Reland "Implement top-level await for REPL mode" This is a reland of 5bddc0e1429dd5885462c26a5082097766776d37 The original CL was speculatively reverted as it was suspected to cause failures on the non-determinism bot. This was ultimately confirmed to not be the case, so this CL is safe to reland as-is. Original change's description: > Implement top-level await for REPL mode > > Design doc: bit.ly/v8-repl-mode > > This CL allows the usage of 'await' without wrapping code in an async > function when using REPL mode in global evaluate. REPL mode evaluate > is changed to *always* return a Promise. The resolve value of the > promise is the completion value of the REPL script. > > The implementation is based on two existing mechanisms: > - Similar to async functions, the content of a REPL script is > enclosed in a synthetic 'try' block. Any thrown error > is used to reject the Promise of the REPL script. > > - The content of the synthetic 'try' block is also re-written the > same way a normal script is. This is, artificial assignments to > a ".result" variable are inserted to simulate a completion > value. The difference for REPL scripts is, that ".result" is > used to resolve the Promise of the REPL script. > > - ".result" is not returned directly but wrapped in an object > literal: "{ .repl_result: .result}". This is done to prevent > resolved promises from being chained and resolved prematurely: > > > Promse.resolve(42); > > should evaluate to a promise, not 42. > > Bug: chromium:1021921 > Change-Id: I00a5aafd9126ca7c97d09cd8787a3aec2821a67f > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1900464 > Reviewed-by: Yang Guo <yangguo@chromium.org> > Reviewed-by: Leszek Swirski <leszeks@chromium.org> > Reviewed-by: Toon Verwaest <verwaest@chromium.org> > Commit-Queue: Simon Zünd <szuend@chromium.org> > Cr-Commit-Position: refs/heads/master@{#65273} TBR: yangguo@chromium.org,verwaest@chromium.org Bug: chromium:1021921 Change-Id: I95c5dc17593161009a533188f91b4cd67234c32f Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1954388 Reviewed-by: Simon Zünd <szuend@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Commit-Queue: Simon Zünd <szuend@chromium.org> Cr-Commit-Position: refs/heads/master@{#65360}
2019-11-25 09:33:49 +00:00
return %RuntimeEvaluateREPL(expr).then(value => {
return value[".repl_result"];
});
}
2016-11-16 08:34:01 +00:00
eventDataException(params) {
switch (params.data.type) {
case "string": {
return params.data.value;
}
case "object": {
const props = this.getProperties(params.data.objectId);
return this.propertiesToObject(props);
}
default: {
return undefined;
}
}
}
eventDataScriptSource(id) {
const {msgid, msg} = this.createMessage(
"Debugger.getScriptSource", { scriptId : String(id) });
2016-11-16 08:34:01 +00:00
this.sendMessage(msg);
const reply = this.takeReplyChecked(msgid);
return reply.result.scriptSource;
}
eventDataScriptSetSource(id, src) {
const {msgid, msg} = this.createMessage(
"Debugger.setScriptSource", { scriptId : id, scriptSource : src });
this.sendMessage(msg);
this.takeReplyChecked(msgid);
}
eventDataScript(params) {
const id = parseInt(params.scriptId);
2016-11-16 08:34:01 +00:00
const name = params.url ? params.url : undefined;
return { id : () => id,
name : () => name,
source : () => this.eventDataScriptSource(params.scriptId),
2016-11-16 08:34:01 +00:00
setSource : (src) => this.eventDataScriptSetSource(id, src)
};
}
// --- Message handlers. -----------------------------------------------------
dispatchMessage(message) {
const method = message.method;
if (method == "Debugger.paused") {
this.handleDebuggerPaused(message);
} else if (method == "Debugger.scriptParsed") {
this.handleDebuggerScriptParsed(message);
2016-11-16 08:34:01 +00:00
} else if (method == "Debugger.scriptFailedToParse") {
this.handleDebuggerScriptFailedToParse(message);
}
}
handleDebuggerPaused(message) {
const params = message.params;
var debugEvent;
switch (params.reason) {
case "exception":
case "promiseRejection":
debugEvent = this.DebugEvent.Exception;
break;
case "OOM":
debugEvent = this.DebugEvent.OOM;
break;
case "other":
debugEvent = this.DebugEvent.Break;
break;
case "ambiguous":
case "XHR":
case "DOM":
case "EventListener":
case "assert":
case "debugCommand":
case "CSPViolation":
assertUnreachable();
default:
assertUnreachable();
}
if (!params.callFrames[0]) return;
// 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),
evaluateGlobal :
Reland "[debug] introduced runtime side effect check" This is a reland of 7a2c3713839f0915bd01738bf4633d8e5693651f Original change's description: > [debug] introduced runtime side effect check > > This CL demonstrates minimum valuable addition to existing debug evaluate > without side effects mechanism. > With this CL user can evaluate expressions like: > [a,b] // create any kind of temporary array literals > [a,b].reduce((x,y) => x + y, 0); // use reduce method > [1,2,3].fill(2); // change temporary arrays > > The core idea: any change of the object created during evaluation without > side effects is side effect free. As soon as we try to store this temporary > object to object existed before evaluation we will terminate execution. > > Implementation: > - track all objects allocated during evaluation and mark them as temporary, > - patch all bytecodes which change objects. > > A little more details (including performance analysis): [1]. > > [1] https://docs.google.com/document/d/10qqAtZADspPnpYa6SEdYRxrddfKIZJIzbLtGpsZQkRo/edit# > > Bug: v8:7588 > Change-Id: I69f7b96e1ebd7ad0022219e8213211c7be72a111 > Reviewed-on: https://chromium-review.googlesource.com/972615 > Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> > Reviewed-by: Yang Guo <yangguo@chromium.org> > Reviewed-by: Ulan Degenbaev <ulan@chromium.org> > Cr-Commit-Position: refs/heads/master@{#52370} Bug: v8:7588 Change-Id: Ibc92bf19155f2ddaedae39b0c576b994e84afcf8 Reviewed-on: https://chromium-review.googlesource.com/996760 Reviewed-by: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> Cr-Commit-Position: refs/heads/master@{#52373}
2018-04-05 02:01:34 +00:00
(expr) => this.evaluateGlobal(expr),
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;
2016-11-16 08:34:01 +00:00
eventData.exception = () => this.eventDataException(params);
}
this.invokeListener(debugEvent, execState, eventData);
}
handleDebuggerScriptParsed(message) {
const params = message.params;
let eventData = { scriptId : params.scriptId,
2016-11-16 08:34:01 +00:00
script : () => this.eventDataScript(params),
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);
}
2016-11-16 08:34:01 +00:00
handleDebuggerScriptFailedToParse(message) {
const params = message.params;
let eventData = { scriptId : params.scriptId,
script : () => this.eventDataScript(params),
eventType : this.DebugEvent.CompileError
}
// 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.CompileError, 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;
}});