Skip to content

console: guard throwing Event/AggregateError property reads in the native formatters - #35816

Open
robobun wants to merge 10 commits into
mainfrom
farm/dc03da31/event-type-getter-guard
Open

console: guard throwing Event/AggregateError property reads in the native formatters#35816
robobun wants to merge 10 commits into
mainfrom
farm/dc03da31/event-type-getter-guard

Conversation

@robobun

@robobun robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Repro

// throws out of console.log / Bun.inspect on current main; Node prints all of these
class E extends Event { get type() { throw new Error("boom") } }
console.log(new E("t"));

class M extends MessageEvent { get data() { throw new Error("boom") } }
console.log(new M("message", { data: "p" }));

class R extends ErrorEvent { get error() { throw 0 } get message() { throw 0 } }
console.log(new R("error", { message: "m", error: new Error("i") }));

const a = new AggregateError([new Error("x")], "m");
Object.defineProperty(a, "errors", { get() { throw new Error("boom") } });
console.log(a);          // TypeError escapes

const b = new AggregateError([new Error("x")], "m");
delete b.errors;
console.log(b);          // segfault at 0x5

Cause

  • print_event in src/jsc/ConsoleObject.rs reads .type / .message / .data / .error via value.get(...)? / value.fast_get(...)? before deciding how to render. The ? propagates a thrown getter straight out of console.log / Bun.inspect. The near-identical Tag::Event arm in src/runtime/test_runner/pretty_format.rs (used by toMatchSnapshot / DiffFormatter) has the same four unguarded reads.
  • print_errorlike_object in src/jsc/VirtualMachine.rs reads AggregateError.errors via getDirect and unconditionally hands the result to for_each. getDirect on a deleted prop returns empty (crash in forEachInIterable), on a defineProperty accessor returns the GetterSetter cell (debug assert in synthesizePrototype, TypeError in release), and on a non-iterable object for_each throws; the let _ = discarded the JsResult but left the exception pending on the VM.

Fix

  • Match the Err(_) arm on the four Event reads in both formatters, call global_this.clear_exception(), and fall back to undefined / None. This mirrors the existing print_to_json pattern in ConsoleObject.rs. When .type throws, the formatter falls through to the generic object printer which renders type: [Getter].
  • Gate the AggregateError.errors iteration on is_object() (excludes empty / GetterSetter / primitives) and clear the exception if for_each still throws.

Verification

  • USE_SYSTEM_BUN=1 bun test test/js/bun/util/inspect.test.js -t "throwing getter|hostile" fails (2 tests)
  • bun bd test test/js/bun/util/inspect.test.js passes (76 tests)
  • BUN_JSC_validateExceptionChecks=1 clean

[review] gate passed · iteration 0 · 4 files touched

fails on main (without fix)
ASAN without fix: BUILD FAILED (no junit output)
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/util/inspect.test.js
bun test v1.4.0 (e015b8a26)

test/js/bun/util/inspect.test.js:
(pass) prototype [382.53ms]
(pass) getters [8.21ms]
(pass) setters [6.76ms]
(pass) getter/setters [4.09ms]
(pass) Timeout [8.31ms]
(pass) when prototype defines the same property, don't print the same property twice [6.21ms]
(pass) Blob inspect [47.48ms]
(pass) utf16 property name [78.03ms]
(pass) latin1 [5.09ms]
(pass) Request object [3.50ms]
(pass) MessageEvent [2.51ms]
(pass) MessageEvent with no data set [2.55ms]
(pass) MessageEvent with deleted data [3.09ms]
206 | });
207 | 
208 | it("Event subclass with a throwing getter does not make Bun.inspect throw", () => {
209 |   class ThrowType extends Event {
210 |     get type() {
211 |       throw new Error("type-getter-boom");
                                              ^
error: type-getter-boom
      at type (/workspace/bun/test/js/bun/util/inspect.test.js:211:41)
      at <anonymous> (/workspace/bun/test/js/bun/util/inspect.test.js:214:23)
(fail) Event subclass with a throwing getter do
... (truncated)

release without fix: 1 FAILED
bun test v1.4.0-canary.1 (1e88697f9)

test/js/bun/util/inspect.test.js:
(pass) prototype [18.61ms]
(pass) getters [1.21ms]
(pass) setters [0.11ms]
(pass) getter/setters [0.04ms]
(pass) Timeout [1.20ms]
(pass) when prototype defines the same property, don't print the same property twice [0.10ms]
(pass) Blob inspect [3.85ms]
(pass) utf16 property name [2.30ms]
(pass) latin1 [0.10ms]
(pass) Request object [2.52ms]
(pass) MessageEvent [0.12ms]
(pass) MessageEvent with no data set [0.04ms]
(pass) MessageEvent with deleted data [0.10ms]
(pass) Event subclass with a throwing getter does not make Bun.inspect throw [1.21ms]
256 |     get() {
257 |       throw new Error("errors-getter-boom");
258 |     },
259 |     configurable: true,
260 |   });
261 |   expect(() => Bun.inspect(a)).not.toThrow();
                                         ^
error: expect(received).not.toThrow()

Error name: "TypeError"
Error message: "Type error"

      at <anonymous> (/workspace/bun/test/js/bun/util/inspect.test.js:261:36)
(fail) AggregateError with a hostile 'errors' property does not make Bun.inspect throw [1.02ms]
(pass) Event subclass with a throwing getter does not make toMatchSnapshot f
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/util/inspect.test.js
bun test v1.4.0 (e015b8a26)

test/js/bun/util/inspect.test.js:
(pass) prototype [398.77ms]
(pass) getters [10.17ms]
(pass) setters [5.16ms]
(pass) getter/setters [3.11ms]
(pass) Timeout [6.50ms]
(pass) when prototype defines the same property, don't print the same property twice [3.36ms]
(pass) Blob inspect [13.62ms]
(pass) utf16 property name [85.56ms]
(pass) latin1 [4.69ms]
(pass) Request object [3.94ms]
(pass) MessageEvent [3.21ms]
(pass) MessageEvent with no data set [2.26ms]
(pass) MessageEvent with deleted data [3.38ms]
(pass) Event subclass with a throwing getter does not make Bun.inspect throw [33.30ms]
(pass) AggregateError with a hostile 'errors' property does not make Bun.inspect throw [13.01ms]
(pass) Event subclass with a throwing getter does not make toMatchSnapshot fail [597.08ms]
(pass) TypedArray prints [128.13ms]
(pass) BigIntArray [48.92ms]
(pass) Float32Array 42.68000030517578 [7.25ms]
(pass) Float32Array 42.68 [7.44ms]
(pass) Float64Array 42.68000030517578 [2.54ms]
(pass) Float64Arr
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 1054ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/6] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 240 extern-C blocks audited
[1/6] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m   Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp)
�[1m�[92m   Compiling�[0m bun_output 
... (truncated)
diff hotspot
src/jsc/ConsoleObject.rs                 |  44 +++++++++----
 src/jsc/VirtualMachine.rs                |  12 ++--
 src/runtime/test_runner/pretty_format.rs |  44 ++++++++++---
 test/js/bun/util/inspect.test.js         | 103 ++++++++++++++++++++++++++++++-
 4 files changed, 177 insertions(+), 26 deletions(-)

gate history · 2 passed · 0 rejected · iteration 0

evidence per changed file
file                                      reads  edits  tests
src/jsc/ConsoleObject.rs                      5      4      0
src/jsc/VirtualMachine.rs                     2      2      0
src/runtime/test_runner/pretty_format.rs      2      4      0
test/js/bun/util/inspect.test.js              3      5      0

The native Event formatter in ConsoleObject reads .type through the
user-visible accessor before deciding whether to render a MessageEvent /
ErrorEvent specially or fall through to the generic object printer. If an
Event subclass overrides type with a getter that throws, the exception
propagates out of console.log and Bun.inspect.

The generic object printer (forEachProperty in bindings.cpp) already swallows
getter exceptions for every other Event property, so only the four reads done
ahead of that path were unguarded: type, and the message/data/error reads on
the MessageEvent/ErrorEvent arm.

Catch and clear those four, matching the existing print_to_json pattern.
@robobun

robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Reproduced on current main: Bun.inspect(new (class extends Event { get type(){throw 0} })('t')) throws; toMatchSnapshot on the same value fails with "Failed to pretty format value"; and Bun.inspect on an AggregateError whose errors own slot was replaced with an accessor or deleted throws/asserts.

The guard is applied to the Event arm in both ConsoleObject.rs and test_runner/pretty_format.rs, plus the AggregateError errors iteration in VirtualMachine.rs. All three new tests in test/js/bun/util/inspect.test.js pass with the fix and fail without it. BUN_JSC_validateExceptionChecks=1 is clean.

CI status: the diff itself is green on every lane that ran end to end (windows-aarch64, freebsd-aarch64, linux-aarch64-android, alpine-x64). Builds #81656 and #81769 both failed on infra: a build-bun job finished the Rust compile in under 2 minutes, then timed out after an hour waiting for its paired build-cpp job, which stayed in scheduled and never got an agent (windows-x64 in 81656, linux-aarch64 in 81769). No test lane touched by this diff has failed. Ready for a maintainer to merge or re-run once the build-cpp queue catches up.

@robobun

robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 4:50 PM PT - Jul 25th, 2026

@robobun, your commit 939f384 is building: #81769

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 11 seconds

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 07ee3126-2b54-4780-9e5b-59ed77e6e3a3

📥 Commits

Reviewing files that changed from the base of the PR and between 006520c and 939f384.

📒 Files selected for processing (4)
  • src/jsc/ConsoleObject.rs
  • src/jsc/VirtualMachine.rs
  • src/runtime/test_runner/pretty_format.rs
  • test/js/bun/util/inspect.test.js

Walkthrough

Formatter::print_event now suppresses exceptions from failing event property getters and uses safe fallback values. Tests cover Bun.inspect on events and objects containing events with throwing type, data, error, and message getters.

Changes

Event inspection

Layer / File(s) Summary
Safe event field access and regression coverage
src/jsc/ConsoleObject.rs, test/js/bun/util/inspect.test.js
Formatter::print_event clears VM exceptions from failed event property access and applies undefined or omitted-field fallbacks; tests verify inspection does not throw and includes expected event fields.

Possibly related PRs

  • oven-sh/bun#35288: Both changes modify event formatting in src/jsc/ConsoleObject.rs.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main change: guarding throwing Event and AggregateError property reads in native formatters.
Description check ✅ Passed The description includes the fix rationale and verification results, which cover the required template content despite different headings.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/bun/util/inspect.test.js`:
- Around line 214-235: Strengthen the Bun.inspect assertions for ThrowType,
ThrowData, and ThrowError: require ThrowType output to contain `type: [Getter]`,
require ThrowData output to contain `data: undefined`, and assert ThrowError
output omits the throwing `message` and `error` fields while retaining `type:
"error"`. Use the existing inspect results in these test cases rather than only
checking that inspection does not throw.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 58fa2418-3016-4f65-9f43-2569b5949856

📥 Commits

Reviewing files that changed from the base of the PR and between 916492f and 006520c.

📒 Files selected for processing (2)
  • src/jsc/ConsoleObject.rs
  • test/js/bun/util/inspect.test.js

Comment thread test/js/bun/util/inspect.test.js Outdated
Comment thread src/jsc/ConsoleObject.rs
Comment thread test/js/bun/util/inspect.test.js Outdated
robobun added 2 commits July 25, 2026 22:01
The snapshot/diff pretty-format in src/runtime/test_runner/pretty_format.rs
has a near-identical Tag::Event arm with the same four unguarded reads. Apply
the same clear-on-Err treatment so toMatchSnapshot on an Event subclass with
a throwing type/data/error/message getter no longer fails with
'Failed to pretty format value'.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — both prior concerns addressed (pretty_format.rs twin site guarded, test assertions strengthened to check the actual fallback rendering).

Extended reasoning...

Overview

Guards four Event property reads (.type, .message, .data, .error) in the native Bun.inspect/console.log formatter (src/jsc/ConsoleObject.rs::print_event) and its copy in JestPrettyFormat (src/runtime/test_runner/pretty_format.rs) so a subclass with a throwing getter no longer propagates out of inspection. Each read is converted from ? to a match that clears the pending exception on Err(_) and substitutes undefined/None. Two tests added to test/js/bun/util/inspect.test.js: one exercising Bun.inspect directly with strengthened rendering assertions, and one spawning a subprocess to exercise toMatchSnapshot on the same three cases.

Security risks

None. This is inspect/console output formatting; the change swallows a getter exception during best-effort stringification, matching the existing forEachProperty behavior in bindings.cpp and the print_to_json precedent at ConsoleObject.rs:4287.

Level of scrutiny

Low-to-medium. The transformation is mechanical — the same 6-line match { Ok(v) => ..., Err(_) => { clear_exception(); fallback } } applied eight times across two files, mirroring an existing pattern in the same file. The fallback values preserve prior Ok(None) semantics (UNDEFINED for .type/.data, None for .message/.error), so the only behavioral delta is on the throwing path. I verified the .type-throws case falls through to the generic print_as(Tag::Object, ...) arm via EventType::unknown, which is the pre-existing Ok(None) path.

Other factors

My previous review flagged (1) the unguarded twin in pretty_format.rs and (2) near-vacuous .toContain("type") assertions. Both were addressed in fb1009b and 1e88697: the four JestPrettyFormat reads are now guarded identically, tests assert type: [Getter], data: undefined, and absence of message:/error: fields, and a spawned-subprocess test covers toMatchSnapshot. The author explicitly scoped out the Tag::Error .message read and JSX .type reads as separate tag arms, which is a reasonable stated exclusion. The subprocess test uses --update-snapshots on the whole file (no -t filter), drains both pipes concurrently, and asserts 3 pass + exit 0. No bugs from the bug-hunting pass.

robobun added 2 commits July 25, 2026 22:35
Extends the Event-getter guards to the remaining unguarded reads:

- print_errorlike_object: getDirect on AggregateError.errors can return
  empty (deleted), a GetterSetter cell (defineProperty accessor), or a
  non-iterable object; for_each on any of those crashed or threw out of
  console.log/Bun.inspect. Gate on is_object() and clear the iteration
  exception.
- test_runner/pretty_format.rs: apply the same type/message/data/error
  guards as ConsoleObject so toMatchSnapshot on a hostile Event subclass
  renders instead of failing with 'Failed to pretty format value'.

Tests cover subclass getters, own-instance accessors, nested objects,
and the three AggregateError.errors shapes.
Comment thread src/jsc/VirtualMachine.rs Outdated
@robobun robobun changed the title console: guard throwing Event property getters in Bun.inspect console: guard throwing Event/AggregateError property reads in the native formatters Jul 25, 2026
Comment thread src/jsc/VirtualMachine.rs Outdated
Comment thread src/jsc/VirtualMachine.rs Outdated
@robobun

robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Same fix was also prepared on claude/farm/7c2f8502/console-accessor-guards (commit 8317ac1) with the four reads routed through a get_swallowing_throw / fast_get_swallowing_throw helper pair on Formatter instead of inline match blocks, in case that shape is preferred. That branch has been rescoped to the Map/Set entry-count cap only (#35826), so this PR owns the accessor guards.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — all three review threads from earlier runs are addressed (pretty_format.rs guarded, test assertions strengthened to exact .toBe/[Getter], VirtualMachine.rs comment trimmed to one line).

What was reviewed:

  • The four Err(_) => clear_exception() guards in each of ConsoleObject.rs and pretty_format.rs match the existing pattern at ConsoleObject.rs:4287; fallback values (UNDEFINED/None) preserve the pre-PR rendering for the non-throwing case.
  • is_object() on the getDirect result: verified JSType::GetterSetter = 5 is below ObjectType in JSType.rs, so the guard rejects empty, GetterSetter, and primitives as claimed.
  • Tests cover subclass getters, own-instance defineProperty accessors, nested values, all three AggregateError shapes (accessor/non-iterable/deleted), and the toMatchSnapshot path via a spawned subprocess; evidence in the PR body shows they fail on main and pass with the fix.
Extended reasoning...

Overview

Guards eight Event property reads (.type/.message/.data/.error) across the two near-identical formatter arms in src/jsc/ConsoleObject.rs::print_event and src/runtime/test_runner/pretty_format.rs (Tag::Event), converting ? propagation into match { Err(_) => { clear_exception(); fallback } }. Also gates the AggregateError.errors iteration in VirtualMachine.rs::print_errorlike_object on is_object() and clears any exception for_each throws. Three new tests in test/js/bun/util/inspect.test.js.

Prior feedback resolution

I raised three items in earlier passes; all are addressed and the threads resolved:

  • 🔴 pretty_format.rs twin site unguarded → fixed in 1e88697, with a spawned-subprocess toMatchSnapshot test.
  • 🟡 vacuous .toContain("type") → replaced with type: [Getter], exact .toBe(...) on the full MessageEvent/ErrorEvent output, and .not.toContain(<thrown message>).
  • 🟡 comment-cop / narration in the VirtualMachine.rs comment → trimmed to a single line stating only the non-obvious getDirect return shapes.

Security risks

None. This is defensive exception handling inside inspection/formatting code; no input parsing, auth, or resource acquisition is touched. The change strictly narrows the set of inputs on which the formatter throws or crashes.

Level of scrutiny

Medium — native Rust touching JSC exception state. I verified the two non-obvious claims: (1) clear_exception() after a user-getter throw is the established idiom in this file (pre-existing at ConsoleObject.rs:4287, the toJSON call site), so REVIEW.md's "never clearException()" — which targets hiding real bugs behind a blanket clear — doesn't apply to a formatter deliberately swallowing hostile-getter throws; and (2) JSType::GetterSetter is 5 in src/jsc/JSType.rs while is_object() tests >= ObjectType, so the single is_object() guard correctly excludes empty, GetterSetter, and primitive returns from getDirect.

Other factors

The transformation is mechanical and localized: each x? becomes an explicit match whose Ok arms preserve the exact pre-PR value flow, so non-throwing inputs render identically. The PR body's evidence block shows the new tests failing on both debug-ASAN and release main and passing with the fix, and BUN_JSC_validateExceptionChecks=1 is reported clean. Test coverage is thorough — subclass vs. own-accessor, nested containers, all three errors shapes, and the snapshot-formatter path. Bug-hunting system found nothing.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — both earlier rounds of feedback (the twin pretty_format.rs Event arm and the test-assertion / comment-length nits) have been addressed, and this run found nothing further.

What was reviewed:

  • The four Event property reads now match on Err(_)clear_exception() + fallback in both ConsoleObject.rs and pretty_format.rs; pattern matches the existing print_to_json guard at ConsoleObject.rs:4287.
  • VirtualMachine.rs errors iteration is gated on is_object() (rejects empty/GetterSetter) and clears the exception if for_each throws — verified get_errors_property is nothrow getDirect.
  • Tests now assert exact rendered output (toBe on the MessageEvent/ErrorEvent cases, type: [Getter] on the fallback), cover accessor/non-iterable/deleted errors, and exercise toMatchSnapshot in a subprocess without a name filter.
Extended reasoning...

Overview

Guards five hostile-input paths in the native inspect/format code so a throwing user getter (or a tampered AggregateError.errors slot) no longer escapes console.log / Bun.inspect / toMatchSnapshot. Four value.get(...)? / fast_get(...)? reads in the Tag::Event arm of both src/jsc/ConsoleObject.rs and src/runtime/test_runner/pretty_format.rs become explicit match blocks that call global_this.clear_exception() on Err and fall back to undefined/None. The AggregateError.errors iteration in src/jsc/VirtualMachine.rs::print_errorlike_object is gated on is_object() and clears the exception if for_each throws. Three new tests in test/js/bun/util/inspect.test.js cover Bun.inspect, nested-object inspect, the AggregateError variants (accessor / non-iterable / deleted), and a spawned toMatchSnapshot run.

Security risks

None. This is display-layer formatting; the change swallows exceptions from user getters during best-effort inspection, which is the established contract for console.log (Node prints these values without throwing, and the existing generic-object printer already renders throwing getters as [Getter]). No auth, crypto, permission, or trust-boundary code is touched. The clear_exception() calls are scoped to the specific failed read and mirror the pre-existing pattern at ConsoleObject.rs:4287.

Level of scrutiny

Medium — native Rust/JSC code with exception-state handling, but the change is a narrow, mechanical defensive pattern applied identically at six sites. The PR was already reviewed twice by me: the first pass caught the missing sibling in pretty_format.rs (fixed in 1e88697) and the vacuous .toContain("type") assertion (fixed in fb1009b); the second pass flagged the over-long VirtualMachine.rs comment (trimmed to one line in e015b8a). All inline threads on the timeline are resolved. The PR evidence block shows the new tests fail on system Bun / release-without-fix and pass on the debug+ASAN build, and BUN_JSC_validateExceptionChecks=1 is clean.

Other factors

  • The Err(_) => clear_exception() shape is copied from an existing use in the same file (print_to_json), so it is not novel.
  • The subprocess snapshot test uses --update-snapshots without a -t filter, satisfying REVIEW.md's snapshot-update rule.
  • The author explicitly scoped out the Tag::Error .message read and JSX .type reads as separate tag arms (different bug class), which is a reasonable exclusion stated on the PR.
  • No CODEOWNERS on these paths; comment-cop is quiet after the trim.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Heads up: the VirtualMachine.rs hunk in this PR (the is_object() / clear_exception() guard around the AggregateError for_each) touches a block that #36602 removes entirely, so this will conflict once that lands. The Event/MessageEvent/ErrorEvent parts in ConsoleObject.rs and pretty_format.rs are independent; after #36602 this PR can drop the VirtualMachine.rs hunk and its AggregateError test cases, which are covered in test/js/bun/util/inspect-error.test.js there.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants