Commit 5e268155 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by V8 LUCI CQ

[refactor] Introduce VisitStack choke point.

This decouples the stack trace visitation logic from the creation
of actual stack frame objects, in preparation to introduce a
second kind of stack frame object (`v8::internal::StackFrameInfo`
as part of http://crrev.com/c/3302794) in addition to the existing
`v8::internal::CallSiteInfo`.

Doc: https://bit.ly/v8-stack-frame
Bug: chromium:1258599, chromium:1278647, chromium:1278650
Change-Id: I398933653e29cc2fe5c222526d9dd686ef8239b4
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3334781
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Auto-Submit: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78374}
parent bc5fc1db
......@@ -44,6 +44,7 @@
#include "src/diagnostics/basic-block-profiler.h"
#include "src/diagnostics/compilation-statistics.h"
#include "src/execution/frames-inl.h"
#include "src/execution/frames.h"
#include "src/execution/isolate-inl.h"
#include "src/execution/local-isolate.h"
#include "src/execution/messages.h"
......@@ -683,12 +684,16 @@ StackTraceFailureMessage::StackTraceFailureMessage(
}
}
class StackTraceBuilder {
bool NoExtension(const v8::FunctionCallbackInfo<v8::Value>&) { return false; }
namespace {
class CallSiteBuilder {
public:
enum FrameFilterMode { ALL, CURRENT_SECURITY_CONTEXT };
StackTraceBuilder(Isolate* isolate, FrameSkipMode mode, int limit,
Handle<Object> caller, FrameFilterMode filter_mode)
CallSiteBuilder(Isolate* isolate, FrameSkipMode mode, int limit,
Handle<Object> caller, FrameFilterMode filter_mode)
: isolate_(isolate),
mode_(mode),
limit_(limit),
......@@ -703,6 +708,18 @@ class StackTraceBuilder {
elements_ = isolate->factory()->NewFixedArray(std::min(64, limit));
}
bool Visit(FrameSummary const& summary) {
if (Full()) return false;
#if V8_ENABLE_WEBASSEMBLY
if (summary.IsWasm()) {
AppendWasmFrame(summary.AsWasm());
return true;
}
#endif // V8_ENABLE_WEBASSEMBLY
AppendJavaScriptFrame(summary.AsJavaScript());
return true;
}
void AppendAsyncFrame(Handle<JSGeneratorObject> generator_object) {
Handle<JSFunction> function(generator_object->function(), isolate_);
if (!IsVisibleInStackTrace(function)) return;
......@@ -905,10 +922,6 @@ bool GetStackTraceLimit(Isolate* isolate, int* result) {
return true;
}
bool NoExtension(const v8::FunctionCallbackInfo<v8::Value>&) { return false; }
namespace {
bool IsBuiltinFunction(Isolate* isolate, HeapObject object, Builtin builtin) {
if (!object.IsJSFunction()) return false;
JSFunction const function = JSFunction::cast(object);
......@@ -916,7 +929,7 @@ bool IsBuiltinFunction(Isolate* isolate, HeapObject object, Builtin builtin) {
}
void CaptureAsyncStackTrace(Isolate* isolate, Handle<JSPromise> promise,
StackTraceBuilder* builder) {
CallSiteBuilder* builder) {
while (!builder->Full()) {
// Check that the {promise} is not settled.
if (promise->status() != Promise::kPending) return;
......@@ -1026,38 +1039,70 @@ void CaptureAsyncStackTrace(Isolate* isolate, Handle<JSPromise> promise,
}
}
struct CaptureStackTraceOptions {
int limit;
// 'filter_mode' and 'skip_mode' are somewhat orthogonal. 'filter_mode'
// specifies whether to capture all frames, or just frames in the same
// security context. While 'skip_mode' allows skipping the first frame.
FrameSkipMode skip_mode;
StackTraceBuilder::FrameFilterMode filter_mode;
bool capture_only_frames_subject_to_debugging;
bool async_stack_trace;
};
void CaptureAsyncStackTrace(Isolate* isolate, CallSiteBuilder* builder) {
Handle<Object> current_microtask = isolate->factory()->current_microtask();
if (current_microtask->IsPromiseReactionJobTask()) {
Handle<PromiseReactionJobTask> promise_reaction_job_task =
Handle<PromiseReactionJobTask>::cast(current_microtask);
// Check if the {reaction} has one of the known async function or
// async generator continuations as its fulfill handler.
if (IsBuiltinFunction(isolate, promise_reaction_job_task->handler(),
Builtin::kAsyncFunctionAwaitResolveClosure) ||
IsBuiltinFunction(isolate, promise_reaction_job_task->handler(),
Builtin::kAsyncGeneratorAwaitResolveClosure) ||
IsBuiltinFunction(isolate, promise_reaction_job_task->handler(),
Builtin::kAsyncGeneratorYieldResolveClosure) ||
IsBuiltinFunction(isolate, promise_reaction_job_task->handler(),
Builtin::kAsyncFunctionAwaitRejectClosure) ||
IsBuiltinFunction(isolate, promise_reaction_job_task->handler(),
Builtin::kAsyncGeneratorAwaitRejectClosure)) {
// Now peek into the handlers' AwaitContext to get to
// the JSGeneratorObject for the async function.
Handle<Context> context(
JSFunction::cast(promise_reaction_job_task->handler()).context(),
isolate);
Handle<JSGeneratorObject> generator_object(
JSGeneratorObject::cast(context->extension()), isolate);
if (generator_object->is_executing()) {
if (generator_object->IsJSAsyncFunctionObject()) {
Handle<JSAsyncFunctionObject> async_function_object =
Handle<JSAsyncFunctionObject>::cast(generator_object);
Handle<JSPromise> promise(async_function_object->promise(), isolate);
CaptureAsyncStackTrace(isolate, promise, builder);
} else {
Handle<JSAsyncGeneratorObject> async_generator_object =
Handle<JSAsyncGeneratorObject>::cast(generator_object);
Handle<Object> queue(async_generator_object->queue(), isolate);
if (!queue->IsUndefined(isolate)) {
Handle<AsyncGeneratorRequest> async_generator_request =
Handle<AsyncGeneratorRequest>::cast(queue);
Handle<JSPromise> promise(
JSPromise::cast(async_generator_request->promise()), isolate);
CaptureAsyncStackTrace(isolate, promise, builder);
}
}
}
} else {
// The {promise_reaction_job_task} doesn't belong to an await (or
// yield inside an async generator), but we might still be able to
// find an async frame if we follow along the chain of promises on
// the {promise_reaction_job_task}.
Handle<HeapObject> promise_or_capability(
promise_reaction_job_task->promise_or_capability(), isolate);
if (promise_or_capability->IsJSPromise()) {
Handle<JSPromise> promise =
Handle<JSPromise>::cast(promise_or_capability);
CaptureAsyncStackTrace(isolate, promise, builder);
}
}
}
}
Handle<FixedArray> CaptureStackTrace(Isolate* isolate, Handle<Object> caller,
CaptureStackTraceOptions options) {
template <typename Visitor, typename Options>
void VisitStack(Isolate* isolate, Visitor* visitor, Options const& options) {
DisallowJavascriptExecution no_js(isolate);
TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("v8.stack_trace"),
"CaptureStackTrace", "maxFrameCount", options.limit);
#if V8_ENABLE_WEBASSEMBLY
wasm::WasmCodeRefScope code_ref_scope;
#endif // V8_ENABLE_WEBASSEMBLY
StackTraceBuilder builder(isolate, options.skip_mode, options.limit, caller,
options.filter_mode);
// Build the regular stack trace, and remember the last relevant
// frame ID and inlined index (for the async stack trace handling
// below, which starts from this last frame).
for (StackFrameIterator it(isolate); !it.done() && !builder.Full();
it.Advance()) {
StackFrame* const frame = it.frame();
for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
StackFrame* frame = it.frame();
switch (frame->type()) {
case StackFrame::BUILTIN_EXIT:
case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION:
......@@ -1072,30 +1117,15 @@ Handle<FixedArray> CaptureStackTrace(Isolate* isolate, Handle<Object> caller,
{
// A standard frame may include many summarized frames (due to
// inlining).
std::vector<FrameSummary> frames;
CommonFrame::cast(frame)->Summarize(&frames);
for (size_t i = frames.size(); i-- != 0 && !builder.Full();) {
auto& summary = frames[i];
std::vector<FrameSummary> summaries;
CommonFrame::cast(frame)->Summarize(&summaries);
for (auto rit = summaries.rbegin(); rit != summaries.rend(); ++rit) {
FrameSummary& summary = *rit;
if (options.capture_only_frames_subject_to_debugging &&
!summary.is_subject_to_debugging()) {
continue;
}
if (summary.IsJavaScript()) {
//=========================================================
// Handle a JavaScript frame.
//=========================================================
auto const& java_script = summary.AsJavaScript();
builder.AppendJavaScriptFrame(java_script);
#if V8_ENABLE_WEBASSEMBLY
} else if (summary.IsWasm()) {
//=========================================================
// Handle a Wasm frame.
//=========================================================
auto const& wasm = summary.AsWasm();
builder.AppendWasmFrame(wasm);
#endif // V8_ENABLE_WEBASSEMBLY
}
if (!visitor->Visit(summary)) return;
}
break;
}
......@@ -1104,68 +1134,40 @@ Handle<FixedArray> CaptureStackTrace(Isolate* isolate, Handle<Object> caller,
break;
}
}
}
struct CaptureStackTraceOptions {
int limit;
// 'filter_mode' and 'skip_mode' are somewhat orthogonal. 'filter_mode'
// specifies whether to capture all frames, or just frames in the same
// security context. While 'skip_mode' allows skipping the first frame.
FrameSkipMode skip_mode;
CallSiteBuilder::FrameFilterMode filter_mode;
bool capture_only_frames_subject_to_debugging;
bool async_stack_trace;
};
Handle<FixedArray> CaptureStackTrace(Isolate* isolate, Handle<Object> caller,
CaptureStackTraceOptions options) {
DisallowJavascriptExecution no_js(isolate);
TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("v8.stack_trace"),
"CaptureStackTrace", "maxFrameCount", options.limit);
#if V8_ENABLE_WEBASSEMBLY
wasm::WasmCodeRefScope code_ref_scope;
#endif // V8_ENABLE_WEBASSEMBLY
CallSiteBuilder builder(isolate, options.skip_mode, options.limit, caller,
options.filter_mode);
VisitStack(isolate, &builder, options);
// If --async-stack-traces are enabled and the "current microtask" is a
// PromiseReactionJobTask, we try to enrich the stack trace with async
// frames.
if (options.async_stack_trace) {
Handle<Object> current_microtask = isolate->factory()->current_microtask();
if (current_microtask->IsPromiseReactionJobTask()) {
Handle<PromiseReactionJobTask> promise_reaction_job_task =
Handle<PromiseReactionJobTask>::cast(current_microtask);
// Check if the {reaction} has one of the known async function or
// async generator continuations as its fulfill handler.
if (IsBuiltinFunction(isolate, promise_reaction_job_task->handler(),
Builtin::kAsyncFunctionAwaitResolveClosure) ||
IsBuiltinFunction(isolate, promise_reaction_job_task->handler(),
Builtin::kAsyncGeneratorAwaitResolveClosure) ||
IsBuiltinFunction(isolate, promise_reaction_job_task->handler(),
Builtin::kAsyncGeneratorYieldResolveClosure) ||
IsBuiltinFunction(isolate, promise_reaction_job_task->handler(),
Builtin::kAsyncFunctionAwaitRejectClosure) ||
IsBuiltinFunction(isolate, promise_reaction_job_task->handler(),
Builtin::kAsyncGeneratorAwaitRejectClosure)) {
// Now peek into the handlers' AwaitContext to get to
// the JSGeneratorObject for the async function.
Handle<Context> context(
JSFunction::cast(promise_reaction_job_task->handler()).context(),
isolate);
Handle<JSGeneratorObject> generator_object(
JSGeneratorObject::cast(context->extension()), isolate);
if (generator_object->is_executing()) {
if (generator_object->IsJSAsyncFunctionObject()) {
Handle<JSAsyncFunctionObject> async_function_object =
Handle<JSAsyncFunctionObject>::cast(generator_object);
Handle<JSPromise> promise(async_function_object->promise(),
isolate);
CaptureAsyncStackTrace(isolate, promise, &builder);
} else {
Handle<JSAsyncGeneratorObject> async_generator_object =
Handle<JSAsyncGeneratorObject>::cast(generator_object);
Handle<Object> queue(async_generator_object->queue(), isolate);
if (!queue->IsUndefined(isolate)) {
Handle<AsyncGeneratorRequest> async_generator_request =
Handle<AsyncGeneratorRequest>::cast(queue);
Handle<JSPromise> promise(
JSPromise::cast(async_generator_request->promise()), isolate);
CaptureAsyncStackTrace(isolate, promise, &builder);
}
}
}
} else {
// The {promise_reaction_job_task} doesn't belong to an await (or
// yield inside an async generator), but we might still be able to
// find an async frame if we follow along the chain of promises on
// the {promise_reaction_job_task}.
Handle<HeapObject> promise_or_capability(
promise_reaction_job_task->promise_or_capability(), isolate);
if (promise_or_capability->IsJSPromise()) {
Handle<JSPromise> promise =
Handle<JSPromise>::cast(promise_or_capability);
CaptureAsyncStackTrace(isolate, promise, &builder);
}
}
}
CaptureAsyncStackTrace(isolate, &builder);
}
Handle<FixedArray> stack_trace = builder.Build();
......@@ -1189,7 +1191,7 @@ Handle<Object> Isolate::CaptureSimpleStackTrace(Handle<JSReceiver> error_object,
options.limit = limit;
options.skip_mode = mode;
options.async_stack_trace = FLAG_async_stack_traces;
options.filter_mode = StackTraceBuilder::CURRENT_SECURITY_CONTEXT;
options.filter_mode = CallSiteBuilder::CURRENT_SECURITY_CONTEXT;
options.capture_only_frames_subject_to_debugging = false;
return CaptureStackTrace(this, caller, options);
......@@ -1289,8 +1291,8 @@ Handle<FixedArray> Isolate::CaptureDetailedStackTrace(
options.async_stack_trace = false;
options.filter_mode =
(stack_trace_options & StackTrace::kExposeFramesAcrossSecurityOrigins)
? StackTraceBuilder::ALL
: StackTraceBuilder::CURRENT_SECURITY_CONTEXT;
? CallSiteBuilder::ALL
: CallSiteBuilder::CURRENT_SECURITY_CONTEXT;
options.capture_only_frames_subject_to_debugging = true;
return CaptureStackTrace(this, factory()->undefined_value(), options);
......@@ -2203,7 +2205,7 @@ void Isolate::PrintCurrentStackTrace(std::ostream& out) {
options.limit = 0;
options.skip_mode = SKIP_NONE;
options.async_stack_trace = FLAG_async_stack_traces;
options.filter_mode = StackTraceBuilder::CURRENT_SECURITY_CONTEXT;
options.filter_mode = CallSiteBuilder::CURRENT_SECURITY_CONTEXT;
options.capture_only_frames_subject_to_debugging = false;
Handle<FixedArray> frames =
......
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