Commit 1f53cbf1 authored by Simon Zünd's avatar Simon Zünd Committed by V8 LUCI CQ

[inspector] Add Runtime#getExceptionDetails CDP method

CDP has a "ExceptionDetails" structure that is attached to various
CDP commands, e.g. "Runtime#exceptionThrown" or "Runtime#evaluate".
The stack trace in the "ExceptionDetails" structure is used in
various places in DevTools. The information in the "ExceptionDetails"
structure is extracted from a v8::Message object. Message objects
are normally created at the exception throw site and may augment
the error with manually inspecting the stack (both to capture a fresh
stack trace in some cases, as well as to calculate location info).

The problem is that in some cases we want to get an "ExceptionDetails"
structure after the fact, e.g. when logging a JS "Error" object in
a catch block. To help in this case, this CL introduces a new
CDP method "Runtime#getExceptionDetails" that behaves exactly as
advertised: It provides a populated "ExceptionDetails" structure
from a JS Error object.

R=bmeurer@chromium.org

Doc: https://bit.ly/runtime-get-exception-details
Bug: chromium:1278650
Change-Id: I084be10c1d852d3b7cac8d88e7f820e867be4722
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3337258
Commit-Queue: Simon Zünd <szuend@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78676}
parent 1511a19d
......@@ -1552,6 +1552,18 @@ domain Runtime
parameters
string name
# This method tries to lookup and populate exception details for a
# JavaScript Error object.
# Note that the stackTrace portion of the resulting exceptionDetails will
# only be populated if the Runtime domain was enabled at the time when the
# Error was thrown.
experimental command getExceptionDetails
parameters
# The error object for which to resolve the exception details.
RemoteObjectId errorObjectId
returns
optional ExceptionDetails exceptionDetails
# Notification is issued every time when binding is called.
experimental event bindingCalled
parameters
......
......@@ -809,6 +809,34 @@ Response V8RuntimeAgentImpl::removeBinding(const String16& name) {
return Response::Success();
}
Response V8RuntimeAgentImpl::getExceptionDetails(
const String16& errorObjectId,
Maybe<protocol::Runtime::ExceptionDetails>* out_exceptionDetails) {
InjectedScript::ObjectScope scope(m_session, errorObjectId);
Response response = scope.initialize();
if (!response.IsSuccess()) return response;
const v8::Local<v8::Value> error = scope.object();
if (!error->IsNativeError())
return Response::InvalidParams("errorObjectId is not a JS error object");
const v8::Local<v8::Message> message =
v8::debug::CreateMessageFromException(m_inspector->isolate(), error);
response = scope.injectedScript()->createExceptionDetails(
message, error, scope.objectGroupName(), out_exceptionDetails);
if (!response.IsSuccess()) return response;
CHECK(out_exceptionDetails->isJust());
// When an exception object is present, `createExceptionDetails` assumes
// the exception is uncaught and will overwrite the text field to "Uncaught".
// Lets use the normal message text instead.
out_exceptionDetails->fromJust()->setText(
toProtocolString(m_inspector->isolate(), message->Get()));
return Response::Success();
}
void V8RuntimeAgentImpl::bindingCalled(const String16& name,
const String16& payload,
int executionContextId) {
......
......@@ -128,6 +128,9 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
Maybe<String16> executionContextName) override;
Response removeBinding(const String16& name) override;
void addBindings(InspectedContext* context);
Response getExceptionDetails(const String16& errorObjectId,
Maybe<protocol::Runtime::ExceptionDetails>*
out_exceptionDetails) override;
void reset();
void reportExecutionContextCreated(InspectedContext*);
......
Tests that Runtime.getExceptionDetails works
Running test: itShouldReturnExceptionDetailsForJSErrorObjects
{
id : <messageId>
result : {
exceptionDetails : {
columnNumber : 9
exception : {
className : Error
description : Error: error 1 at foo (<anonymous>:3:10) at <anonymous>:5:1
objectId : <objectId>
subtype : error
type : object
}
exceptionId : <exceptionId>
lineNumber : 2
scriptId : <scriptId>
stackTrace : {
callFrames : [
[0] : {
columnNumber : 9
functionName : foo
lineNumber : 2
scriptId : <scriptId>
url :
}
[1] : {
columnNumber : 0
functionName :
lineNumber : 4
scriptId : <scriptId>
url :
}
]
}
text : Error: error 1
}
}
}
Running test: itShouldReturnIncompleteDetailsForJSErrorWithNoStack
{
id : <messageId>
result : {
exceptionDetails : {
columnNumber : -1
exception : {
className : Error
description : Error: error 1 at foo (<anonymous>:3:10) at <anonymous>:5:1
objectId : <objectId>
subtype : error
type : object
}
exceptionId : <exceptionId>
lineNumber : -1
scriptId : <scriptId>
text : Error: error 1
}
}
}
Running test: itShouldReportAnErrorForNonJSErrorObjects
{
error : {
code : -32602
message : errorObjectId is not a JS error object
}
id : <messageId>
}
// Copyright 2022 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.
const {session, contextGroup, Protocol} =
InspectorTest.start('Tests that Runtime.getExceptionDetails works');
const expression = `
function foo() {
return new Error("error 1");
}
foo();
`;
InspectorTest.runAsyncTestSuite([
async function itShouldReturnExceptionDetailsForJSErrorObjects() {
await Protocol.Runtime.enable(); // Enable detailed stacktrace capturing.
const {result} = await Protocol.Runtime.evaluate({expression});
InspectorTest.logMessage(await Protocol.Runtime.getExceptionDetails(
{errorObjectId: result.result.objectId}));
},
async function itShouldReturnIncompleteDetailsForJSErrorWithNoStack() {
await Protocol.Runtime.disable(); // Disable detailed stacktrace capturing.
const {result} = await Protocol.Runtime.evaluate({expression});
InspectorTest.logMessage(await Protocol.Runtime.getExceptionDetails(
{errorObjectId: result.result.objectId}));
},
async function itShouldReportAnErrorForNonJSErrorObjects() {
const {result} = await Protocol.Runtime.evaluate({expression: '() =>({})'});
InspectorTest.logMessage(await Protocol.Runtime.getExceptionDetails(
{errorObjectId: result.result.objectId}));
}
]);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment