From 1c0ed31f94a8ce57ed55d94cfda516c5bd9d5a68 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 14 Jul 2026 00:44:19 +0000 Subject: [PATCH 1/2] error: defer termination and never return empty from computeErrorInfoWrapperToJSValue ErrorInstance::getOwnPropertySlot does not check for an exception after materializeErrorInfoIfNeeded calls vm.onComputeErrorInfoJSValue, so a TerminationException raised inside the hook trips JSObject::getOwnPropertyDescriptor's EXCEPTION_ASSERT(!scope.exception() || !result). Defer termination across the hook so the request is handled at the next trap checkpoint after the slot is filled. Separately, when the hook throws before the default stack string is computed (e.g. a throwing .message getter), it returned an empty JSValue which materializeErrorInfoIfNeeded putDirect()s into the error's stack slot; the next read of e.stack then dereferences JSValue() and segfaults. Fall back to jsUndefined() so the stored value is always valid. Refs #34095 --- src/jsc/bindings/FormatStackTraceForJS.cpp | 10 +++++++++ test/js/node/v8/capture-stack-trace.test.js | 24 +++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/jsc/bindings/FormatStackTraceForJS.cpp b/src/jsc/bindings/FormatStackTraceForJS.cpp index 9cbaf3b2909d..8301c3c58f3d 100644 --- a/src/jsc/bindings/FormatStackTraceForJS.cpp +++ b/src/jsc/bindings/FormatStackTraceForJS.cpp @@ -6,6 +6,7 @@ #include "JavaScriptCore/ArgList.h" #include "JavaScriptCore/CallData.h" +#include "JavaScriptCore/DeferTermination.h" #include "JavaScriptCore/TopExceptionScope.h" #include "JavaScriptCore/Error.h" #include "JavaScriptCore/ErrorInstance.h" @@ -639,6 +640,11 @@ void computeLineColumnWithSourcemap(JSC::VM& vm, JSC::SourceProvider* _Nonnull s JSC::JSValue computeErrorInfoWrapperToJSValue(JSC::VM& vm, Vector& stackTrace, unsigned int& line_in, unsigned int& column_in, String& sourceURL, JSObject* errorInstance, void* bunErrorData) { + // ErrorInstance::getOwnPropertySlot doesn't check for exceptions after materializeErrorInfoIfNeeded, + // so a TerminationException raised in here trips getOwnPropertyDescriptor's EXCEPTION_ASSERT. + // https://github.com/oven-sh/bun/issues/34095 + JSC::DeferTerminationForAWhile deferTermination(vm); + OrdinalNumber line = OrdinalNumber::fromOneBasedInt(line_in); OrdinalNumber column = OrdinalNumber::fromOneBasedInt(column_in); @@ -647,6 +653,10 @@ JSC::JSValue computeErrorInfoWrapperToJSValue(JSC::VM& vm, Vector& s line_in = line.oneBasedInt(); column_in = column.oneBasedInt(); + // materializeErrorInfoIfNeeded putDirect()s this unconditionally; an empty JSValue + // in property storage crashes the next read. + if (!result) [[unlikely]] + return jsUndefined(); return result; } diff --git a/test/js/node/v8/capture-stack-trace.test.js b/test/js/node/v8/capture-stack-trace.test.js index 20d405594b28..a834a4b0a0ad 100644 --- a/test/js/node/v8/capture-stack-trace.test.js +++ b/test/js/node/v8/capture-stack-trace.test.js @@ -1003,3 +1003,27 @@ test("printing an error whose message getter calls Error.captureStackTrace on it expect({ lastLine: stdout.trimEnd().split("\n").pop(), exitCode }).toEqual({ lastLine: "after", exitCode: 0 }); }); + +// https://github.com/oven-sh/bun/issues/34095 +test("lazy error-info materialization does not store an empty stack value when the compute hook throws", async () => { + const src = ` + Error.prepareStackTrace = (e, s) => "custom-stack"; + const e = new Error("x"); + Object.defineProperty(e, "message", { get() { throw new TypeError("msg-boom"); } }); + let first = "no-throw"; + try { void e.stack; } catch (err) { first = err.message; } + console.log(JSON.stringify({ first, secondType: typeof e.stack })); + `; + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", src], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect({ stdout: stdout.trim(), signalCode: proc.signalCode }).toEqual({ + stdout: JSON.stringify({ first: "msg-boom", secondType: "undefined" }), + signalCode: null, + }); + expect(exitCode).toBe(0); +}); From 3dd57f69d146d752f56377d868f7b2d7d4addba8 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 14 Jul 2026 01:11:30 +0000 Subject: [PATCH 2/2] Drop DeferTerminationForAWhile The deferral wrapped the profiledCall into user Error.prepareStackTrace, so a worker with an infinite loop there could no longer be interrupted by worker.terminate(). The termination case for ErrorInstance materialization needs a WebKit-side RETURN_IF_EXCEPTION in getOwnPropertySlot instead; only the empty-return guard remains here. --- src/jsc/bindings/FormatStackTraceForJS.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/jsc/bindings/FormatStackTraceForJS.cpp b/src/jsc/bindings/FormatStackTraceForJS.cpp index 8301c3c58f3d..cca4900cb3d3 100644 --- a/src/jsc/bindings/FormatStackTraceForJS.cpp +++ b/src/jsc/bindings/FormatStackTraceForJS.cpp @@ -6,7 +6,6 @@ #include "JavaScriptCore/ArgList.h" #include "JavaScriptCore/CallData.h" -#include "JavaScriptCore/DeferTermination.h" #include "JavaScriptCore/TopExceptionScope.h" #include "JavaScriptCore/Error.h" #include "JavaScriptCore/ErrorInstance.h" @@ -640,11 +639,6 @@ void computeLineColumnWithSourcemap(JSC::VM& vm, JSC::SourceProvider* _Nonnull s JSC::JSValue computeErrorInfoWrapperToJSValue(JSC::VM& vm, Vector& stackTrace, unsigned int& line_in, unsigned int& column_in, String& sourceURL, JSObject* errorInstance, void* bunErrorData) { - // ErrorInstance::getOwnPropertySlot doesn't check for exceptions after materializeErrorInfoIfNeeded, - // so a TerminationException raised in here trips getOwnPropertyDescriptor's EXCEPTION_ASSERT. - // https://github.com/oven-sh/bun/issues/34095 - JSC::DeferTerminationForAWhile deferTermination(vm); - OrdinalNumber line = OrdinalNumber::fromOneBasedInt(line_in); OrdinalNumber column = OrdinalNumber::fromOneBasedInt(column_in); @@ -654,7 +648,7 @@ JSC::JSValue computeErrorInfoWrapperToJSValue(JSC::VM& vm, Vector& s column_in = column.oneBasedInt(); // materializeErrorInfoIfNeeded putDirect()s this unconditionally; an empty JSValue - // in property storage crashes the next read. + // in property storage crashes the next read. https://github.com/oven-sh/bun/issues/34095 if (!result) [[unlikely]] return jsUndefined(); return result;