Skip to content

Pass the error instance to the stack string callback#302

Open
robobun wants to merge 1 commit into
mainfrom
error-info-with-instance
Open

Pass the error instance to the stack string callback#302
robobun wants to merge 1 commit into
mainfrom
error-info-with-instance

Conversation

@robobun

@robobun robobun commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

ErrorInstance::finalizeUnconditionally computes and caches the stack string through VM::onComputeErrorInfo when any captured stack frame is no longer marked. That callback does not receive the ErrorInstance, so Bun cannot read the error's name/message there, and the cached string begins with a bare Error instead of Error: the message. A later .stack access serves the cached string as-is.

User-visible effect in Bun (oven-sh/bun#34398): an async-thrown error whose .stack is first read after a GC cycle loses its message from the stack string.

Change

Add an optional VM::onComputeErrorInfoWithInstance callback with the same shape as onComputeErrorInfo plus the JSObject* instance (mirroring onComputeErrorInfoJSValue). ErrorInstance::computeErrorInfo prefers it when set. The existing callback keeps its signature and priority, so embedders that do not set the new callback see no behavior change.

The Bun-side change that registers the new callback follows in oven-sh/bun (it reads name/message via the VMInquiry-based sanitizedNameString/sanitizedMessageString, which do not run user code, so it is safe from the finalizer).

ErrorInstance::finalizeUnconditionally computes and caches the stack
string through VM::onComputeErrorInfo, which does not receive the
ErrorInstance, so the embedder cannot include the error's name and
message in the cached string. A later .stack access serves that cached
string and the "Name: message" head line is lost.

Add an optional VM::onComputeErrorInfoWithInstance callback that also
receives the instance and is preferred over onComputeErrorInfo when
set. The existing callback keeps its signature and behavior, so current
embedders are unaffected until they opt in.

Needed for oven-sh/bun#34398.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — straightforward additive callback that mirrors the existing onComputeErrorInfoJSValue pattern.

Checked that the new fnWithInstance branch in computeErrorInfo is byte-for-byte the same as the existing fn branch plus this, so behavior is unchanged when the callback is unset.
Verified all additions are inside USE(BUN_JSC_ADDITIONS) guards and the VM.h accessor/setter/member follow the same shape as m_onComputeErrorInfoJSValue.
Considered whether passing this from the finalizeUnconditionally path is unsafe — the instance is live there (finalizer runs on live objects to snapshot before frame cells die), and the existing JSValue callback already receives this from the materialize path.

Extended reasoning...

Overview

Adds a new optional VM::onComputeErrorInfoWithInstance callback (typedef, member, const/non-const accessors, setter) in VM.h and prefers it over onComputeErrorInfo in ErrorInstance::computeErrorInfo. The new callback has the same signature as the existing one plus a JSObject* for the error instance, exactly mirroring the pre-existing ErrorInfoFunctionJSValue shape. ~20 lines net, all inside #if USE(BUN_JSC_ADDITIONS).

Security risks

None. No parsing, no external input, no auth/crypto. The callback is embedder-registered (Bun), same trust level as the existing onComputeErrorInfo / onComputeErrorInfoJSValue hooks. The only new data flowing to the callback is this, which the JSValue variant already exposes.

Level of scrutiny

Low-to-moderate. It touches ErrorInstance::computeErrorInfo, which can run from the GC finalizeUnconditionally path — normally a sensitive area — but the change is purely additive: a new if (fnWithInstance) branch ahead of the existing if (fn) branch, with an identical body except for passing this. When the new callback is not set (the default), control flow is unchanged. The safety of what the callback does with the instance is deferred to the Bun-side PR, which is the right layering.

Other factors

  • Follows an established pattern precisely: the typedef, accessor pair, setter, and member declaration are copy-paste of the neighboring onComputeErrorInfo / onComputeErrorInfoJSValue lines with the type swapped.
  • No behavior change for any caller until setOnComputeErrorInfoWithInstance is invoked, which nothing in this repo does.
  • The bug hunting system found no issues.
  • finalizeUnconditionally runs on live ErrorInstances (to snapshot the stack string before captured frame cells become unmarked), so this is a valid pointer there; this is not the destroy path.

@github-actions

Copy link
Copy Markdown

Preview Builds

Commit Release Date
4abb9e38 autobuild-preview-pr-302-4abb9e38 2026-07-16 21:07:25 UTC

@Jarred-Sumner Jarred-Sumner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot access the object after it's been finalized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants