mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Enable async stack traces (#22517)
### What does this PR do? Enables async stack traces ### How did you verify your code works? Added tests --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
|
||||
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
|
||||
|
||||
if(NOT WEBKIT_VERSION)
|
||||
set(WEBKIT_VERSION 0ddf6f47af0a9782a354f61e06d7f83d097d9f84)
|
||||
set(WEBKIT_VERSION 2d2e8dd5b020cc165e2bc1d284461b4504d624e5)
|
||||
endif()
|
||||
|
||||
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)
|
||||
|
||||
@@ -78,6 +78,7 @@ pub fn runWithBody(ctx: *ErrorReportRequest, body: []const u8, r: AnyResponse) !
|
||||
.line_start_byte = 0,
|
||||
},
|
||||
.code_type = .None,
|
||||
.is_async = false,
|
||||
.remapped = false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ void CallSite::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSCStac
|
||||
if (!stackFrame.codeBlock()) {
|
||||
m_flags |= static_cast<unsigned int>(Flags::IsNative);
|
||||
}
|
||||
if (stackFrame.isAsync()) {
|
||||
m_flags |= static_cast<unsigned int>(Flags::IsAsync);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
IsNative = 8,
|
||||
IsWasm = 16,
|
||||
IsFunction = 32,
|
||||
IsAsync = 64,
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -79,6 +80,7 @@ public:
|
||||
bool isConstructor() const { return m_flags & static_cast<unsigned int>(Flags::IsConstructor); }
|
||||
bool isStrict() const { return m_flags & static_cast<unsigned int>(Flags::IsStrict); }
|
||||
bool isNative() const { return m_flags & static_cast<unsigned int>(Flags::IsNative); }
|
||||
bool isAsync() const { return m_flags & static_cast<unsigned int>(Flags::IsAsync); }
|
||||
|
||||
void setLineNumber(OrdinalNumber lineNumber) { m_lineNumber = lineNumber; }
|
||||
void setColumnNumber(OrdinalNumber columnNumber) { m_columnNumber = columnNumber; }
|
||||
|
||||
@@ -220,12 +220,12 @@ JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncIsConstructor, (JSGlobalObject * globa
|
||||
return JSC::JSValue::encode(JSC::jsBoolean(isConstructor));
|
||||
}
|
||||
|
||||
// TODO:
|
||||
JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncIsAsync, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
ENTER_PROTO_FUNC();
|
||||
|
||||
return JSC::JSValue::encode(JSC::jsBoolean(false));
|
||||
bool isAsync = callSite->isAsync();
|
||||
return JSC::JSValue::encode(JSC::jsBoolean(isAsync));
|
||||
}
|
||||
|
||||
// TODO:
|
||||
|
||||
@@ -292,6 +292,7 @@ JSCStackFrame::JSCStackFrame(JSC::VM& vm, JSC::StackVisitor& visitor)
|
||||
, m_sourceURL()
|
||||
, m_functionName()
|
||||
, m_isWasmFrame(false)
|
||||
, m_isAsync(false)
|
||||
, m_sourcePositionsState(SourcePositionsState::NotCalculated)
|
||||
{
|
||||
m_callee = visitor->callee().asCell();
|
||||
@@ -340,6 +341,7 @@ JSCStackFrame::JSCStackFrame(JSC::VM& vm, const JSC::StackFrame& frame)
|
||||
, m_sourceURL()
|
||||
, m_functionName()
|
||||
, m_isWasmFrame(false)
|
||||
, m_isAsync(frame.isAsyncFrame())
|
||||
, m_sourcePositionsState(SourcePositionsState::NotCalculated)
|
||||
{
|
||||
m_callee = frame.callee();
|
||||
|
||||
@@ -65,6 +65,7 @@ private:
|
||||
bool m_isWasmFrame = false;
|
||||
|
||||
bool m_isFunctionOrEval = false;
|
||||
bool m_isAsync = false;
|
||||
|
||||
enum class SourcePositionsState {
|
||||
NotCalculated,
|
||||
@@ -89,6 +90,7 @@ public:
|
||||
JSC::JSString* typeName();
|
||||
|
||||
bool isFunctionOrEval() const { return m_isFunctionOrEval; }
|
||||
bool isAsync() const { return m_isAsync; }
|
||||
|
||||
bool hasBytecodeIndex() const { return (m_bytecodeIndex.offset() != UINT_MAX) && !m_isWasmFrame; }
|
||||
JSC::BytecodeIndex bytecodeIndex() const
|
||||
|
||||
@@ -300,6 +300,7 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
|
||||
JSC::Options::evalMode() = evalMode;
|
||||
JSC::Options::heapGrowthSteepnessFactor() = 1.0;
|
||||
JSC::Options::heapGrowthMaxIncrease() = 2.0;
|
||||
JSC::Options::useAsyncStackTrace() = true;
|
||||
JSC::dangerouslyOverrideJSCBytecodeCacheVersion(getWebKitBytecodeCacheVersion());
|
||||
|
||||
#ifdef BUN_DEBUG
|
||||
@@ -628,6 +629,9 @@ WTF::String Bun::formatStackTrace(
|
||||
sb.append(" at "_s);
|
||||
|
||||
if (!functionName.isEmpty()) {
|
||||
if (frame.isAsyncFrame()) {
|
||||
sb.append("async "_s);
|
||||
}
|
||||
sb.append(functionName);
|
||||
sb.append(" ("_s);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ pub const ZigStackFrame = extern struct {
|
||||
source_url: String,
|
||||
position: ZigStackFramePosition,
|
||||
code_type: ZigStackFrameCode,
|
||||
is_async: bool,
|
||||
|
||||
/// This informs formatters whether to display as a blob URL or not
|
||||
remapped: bool = false,
|
||||
@@ -119,6 +120,7 @@ pub const ZigStackFrame = extern struct {
|
||||
function_name: String,
|
||||
code_type: ZigStackFrameCode,
|
||||
enable_color: bool,
|
||||
is_async: bool,
|
||||
|
||||
pub fn format(this: NameFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
const name = this.function_name;
|
||||
@@ -141,14 +143,29 @@ pub const ZigStackFrame = extern struct {
|
||||
.Function => {
|
||||
if (!name.isEmpty()) {
|
||||
if (this.enable_color) {
|
||||
try std.fmt.format(writer, comptime Output.prettyFmt("<r><b><i>{}<r>", true), .{name});
|
||||
if (this.is_async) {
|
||||
try std.fmt.format(writer, comptime Output.prettyFmt("<r><b><i>async {}<r>", true), .{name});
|
||||
} else {
|
||||
try std.fmt.format(writer, comptime Output.prettyFmt("<r><b><i>{}<r>", true), .{name});
|
||||
}
|
||||
} else {
|
||||
try std.fmt.format(writer, "{}", .{name});
|
||||
if (this.is_async) {
|
||||
try std.fmt.format(writer, "async {}", .{name});
|
||||
} else {
|
||||
try std.fmt.format(writer, "{}", .{name});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.enable_color) {
|
||||
try std.fmt.format(writer, comptime Output.prettyFmt("<r><d>", true) ++ "<anonymous>" ++ Output.prettyFmt("<r>", true), .{});
|
||||
if (this.is_async) {
|
||||
try std.fmt.format(writer, comptime Output.prettyFmt("<r><d>", true) ++ "async <anonymous>" ++ Output.prettyFmt("<r>", true), .{});
|
||||
} else {
|
||||
try std.fmt.format(writer, comptime Output.prettyFmt("<r><d>", true) ++ "<anonymous>" ++ Output.prettyFmt("<r>", true), .{});
|
||||
}
|
||||
} else {
|
||||
if (this.is_async) {
|
||||
try writer.writeAll("async ");
|
||||
}
|
||||
try writer.writeAll("<anonymous>");
|
||||
}
|
||||
}
|
||||
@@ -178,10 +195,11 @@ pub const ZigStackFrame = extern struct {
|
||||
.code_type = .None,
|
||||
.source_url = .empty,
|
||||
.position = .invalid,
|
||||
.is_async = false,
|
||||
};
|
||||
|
||||
pub fn nameFormatter(this: *const ZigStackFrame, comptime enable_color: bool) NameFormatter {
|
||||
return NameFormatter{ .function_name = this.function_name, .code_type = this.code_type, .enable_color = enable_color };
|
||||
return NameFormatter{ .function_name = this.function_name, .code_type = this.code_type, .enable_color = enable_color, .is_async = this.is_async };
|
||||
}
|
||||
|
||||
pub fn sourceURLFormatter(this: *const ZigStackFrame, root_path: string, origin: ?*const ZigURL, exclude_line_column: bool, comptime enable_color: bool) SourceURLFormatter {
|
||||
|
||||
@@ -4427,6 +4427,8 @@ static void populateStackFrameMetadata(JSC::VM& vm, JSC::JSGlobalObject* globalO
|
||||
if (!functionName.isEmpty()) {
|
||||
frame->function_name = Bun::toStringRef(functionName);
|
||||
}
|
||||
|
||||
frame->is_async = stackFrame->isAsyncFrame();
|
||||
}
|
||||
|
||||
static void populateStackFramePosition(const JSC::StackFrame* stackFrame, BunString* source_lines,
|
||||
@@ -4552,6 +4554,7 @@ public:
|
||||
|
||||
bool isConstructor = false;
|
||||
bool isGlobalCode = false;
|
||||
bool isAsync = false;
|
||||
};
|
||||
|
||||
WTF::StringView stack;
|
||||
@@ -4677,20 +4680,25 @@ public:
|
||||
|
||||
StringView functionName = line.substring(0, openingParentheses - 1);
|
||||
|
||||
if (functionName == "<anonymous>"_s) {
|
||||
functionName = StringView();
|
||||
}
|
||||
|
||||
if (functionName == "global code"_s) {
|
||||
functionName = StringView();
|
||||
frame.isGlobalCode = true;
|
||||
}
|
||||
|
||||
if (functionName.startsWith("async "_s)) {
|
||||
frame.isAsync = true;
|
||||
functionName = functionName.substring(6);
|
||||
}
|
||||
|
||||
if (functionName.startsWith("new "_s)) {
|
||||
frame.isConstructor = true;
|
||||
functionName = functionName.substring(4);
|
||||
}
|
||||
|
||||
if (functionName == "<anonymous>"_s) {
|
||||
functionName = StringView();
|
||||
}
|
||||
|
||||
frame.functionName = functionName;
|
||||
|
||||
return true;
|
||||
@@ -4889,6 +4897,7 @@ static void fromErrorInstance(ZigException* except, JSC::JSGlobalObject* global,
|
||||
current.position.column_zero_based = frame.columnNumber.zeroBasedInt();
|
||||
|
||||
current.remapped = true;
|
||||
current.is_async = frame.isAsync;
|
||||
|
||||
if (frame.isConstructor) {
|
||||
current.code_type = ZigStackFrameCodeConstructor;
|
||||
|
||||
@@ -181,6 +181,7 @@ typedef struct ZigStackFrame {
|
||||
BunString source_url;
|
||||
ZigStackFramePosition position;
|
||||
ZigStackFrameCode code_type;
|
||||
bool is_async;
|
||||
bool remapped;
|
||||
} ZigStackFrame;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { $ } from "bun";
|
||||
import { expect, test } from "bun:test";
|
||||
import "harness";
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
import { bunEnv, bunExe, normalizeBunSnapshot } from "harness";
|
||||
import { join } from "node:path";
|
||||
|
||||
test("name property is used for function calls in Error.stack", () => {
|
||||
@@ -121,3 +121,32 @@ test("throwing inside an error suppresses the error and continues printing prope
|
||||
`);
|
||||
expect(exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test("Async functions frame should be included in stack trace", async () => {
|
||||
async function foo() {
|
||||
return await bar();
|
||||
}
|
||||
async function bar() {
|
||||
return await baz();
|
||||
}
|
||||
async function baz() {
|
||||
await 1;
|
||||
return await qux();
|
||||
}
|
||||
async function qux() {
|
||||
return new Error("error from qux");
|
||||
}
|
||||
|
||||
const error = await foo();
|
||||
|
||||
console.log(error.stack);
|
||||
|
||||
expect(normalizeBunSnapshot(error.stack!)).toMatchInlineSnapshot(`
|
||||
"Error: error from qux
|
||||
at qux (file:NN:NN)
|
||||
at baz (file:NN:NN)
|
||||
at async bar (file:NN:NN)
|
||||
at async foo (file:NN:NN)
|
||||
at async <anonymous> (file:NN:NN)"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -724,3 +724,33 @@ test("CallFrame.p.getScriptNameOrSourceURL inside eval", () => {
|
||||
|
||||
expect(prepare).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("CallFrame.p.isAsync", async () => {
|
||||
let prevPrepareStackTrace = Error.prepareStackTrace;
|
||||
const prepare = mock((e, s) => {
|
||||
expect(s[0].isAsync()).toBeFalse();
|
||||
expect(s[1].isAsync()).toBeTrue();
|
||||
expect(s[2].isAsync()).toBeTrue();
|
||||
expect(s[3].isAsync()).toBeTrue();
|
||||
});
|
||||
Error.prepareStackTrace = prepare;
|
||||
async function foo() {
|
||||
await bar();
|
||||
}
|
||||
async function bar() {
|
||||
await baz();
|
||||
}
|
||||
async function baz() {
|
||||
await 1;
|
||||
throw new Error("error from baz");
|
||||
}
|
||||
|
||||
try {
|
||||
await foo();
|
||||
} catch (e) {
|
||||
e.stack;
|
||||
}
|
||||
Error.prepareStackTrace = prevPrepareStackTrace;
|
||||
|
||||
expect(prepare).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user