Skip to content

Fix stack overflow in Bun.inspect() on circular JSX elements#29709

Open
robobun wants to merge 1 commit into
mainfrom
farm/f27ac5e7/fix-jsx-inspect-circular
Open

Fix stack overflow in Bun.inspect() on circular JSX elements#29709
robobun wants to merge 1 commit into
mainfrom
farm/f27ac5e7/fix-jsx-inspect-circular

Conversation

@robobun

@robobun robobun commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

Fuzzilli found a stack overflow (fingerprint d8d53c93d7954f20) when inspecting a React element that references itself:

const el = { $$typeof: Symbol.for("react.element"), type: "div", props: null, key: null };
el.props = el;
Bun.inspect(el); // segfault

Tag::JSX was not included in can_have_circular_references(), so the visited-map and stack-depth guards were never applied when recursing into JSX props, key, or children. Any self-referential element overflowed the native stack.

Applied in both src/jsc/ConsoleObject.rs (Bun.inspect/console.log) and src/runtime/test_runner/pretty_format.rs (test diff formatter used by expect().toEqual()).

Circular references now print [Circular]:

<div $$typeof=Symbol(react.element) type="div" props=[Circular] key=null />

Regression tests run the crashing inputs in subprocesses so a regression would fail the suite rather than segfault the runner.

@robobun

robobun commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 9:59 PM PT - May 21st, 2026

@robobun, your commit 815032915ccc7db987554602e81971f68bf991b9 passed in Build #56793! 🎉


🧪   To try this PR locally:

bunx bun-pr 29709

That installs a local version of the PR into your bun-29709 executable, so you can run:

bun-29709 --bun

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds JSX to circular-reference detection and makes JSX props inspection safe when props is not an object, avoiding invalid access and skipping prop printing in those cases.

Changes

Cohort / File(s) Summary
Formatter: circular detection & JSX props
src/bun.js/test/pretty_format.zig, src/bun.js/ConsoleObject.zig
Treat JSX values as potentially circular by adding .JSX to canHaveCircularReferences. Change JSX element rendering to obtain props safely (use orelse break :props) and skip printing when props is not an object instead of asserting.
Tests: JSX inspect cases
test/js/bun/util/inspect.test.js
Add tests for Bun.inspect with JSX elements that have circular props, children, or key, verify [Circular] output, and add a test ensuring non-object props are ignored (renders <div />).
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main fix: addressing a stack overflow in Bun.inspect() when handling circular JSX elements.
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.
Description check ✅ Passed The pull request description is comprehensive and follows the template with clear sections for what the PR does and how it was verified.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. bun test with react DOM shows infinite error log on expect failed #10886 - React DOM elements contain circular references (parent/child relationships); the missing .JSX tag in canHaveCircularReferences() caused infinite recursion when inspecting them for test failure messages, producing the "infinite error log" described in this issue.

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #10886

🤖 Generated with Claude Code

Comment thread src/jsc/ConsoleObject.zig
@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Fix stack overflow when inspecting JSX elements with circular references #29170 - Fixes the same stack overflow in Bun.inspect() on circular JSX elements by adding .JSX to canHaveCircularReferences() and guarding non-object props; also applies the fix to pretty_format.zig

🤖 Generated with Claude Code

@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 the current code and only fix it if needed.

Inline comments:
In `@test/js/bun/util/inspect.test.js`:
- Around line 351-353: The test creates el2 with props: 42 and currently only
asserts that an error is thrown when comparing it to {}
(expect(el2).toEqual({})), which allows any thrown error (including ones whose
message incorrectly contains "[Circular]"). Update the assertion for the thrown
error to also assert the error message does NOT contain the string "[Circular]"
so the test fails if a circular-related message is produced; target the existing
el2 setup and the current expect(...).toThrow invocation when making this
change.
🪄 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: 34ba822f-7961-4803-84d6-bf95dc3fc265

📥 Commits

Reviewing files that changed from the base of the PR and between f04366c and f26c1c3.

📒 Files selected for processing (2)
  • src/bun.js/test/pretty_format.zig
  • test/js/bun/util/inspect.test.js

Comment thread test/js/bun/util/inspect.test.js Outdated
Comment on lines +351 to +353
const el2 = { $$typeof: Symbol.for("react.element"), type: "div", props: 42, key: null };
expect(() => expect(el2).toEqual({})).toThrow();
});

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.

⚠️ Potential issue | 🟡 Minor

Strengthen the non-object props diff assertion to avoid false positives.

This currently passes on any thrown error, even if the message incorrectly includes [Circular]. Assert the thrown message does not contain [Circular] to lock in the intended behavior.

💡 Suggested test tightening
   const el2 = { $$typeof: Symbol.for("react.element"), type: "div", props: 42, key: null };
-  expect(() => expect(el2).toEqual({})).toThrow();
+  try {
+    expect(el2).toEqual({});
+    throw new Error("expected assertion to throw");
+  } catch (err) {
+    expect(String(err)).not.toContain("[Circular]");
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/js/bun/util/inspect.test.js` around lines 351 - 353, The test creates
el2 with props: 42 and currently only asserts that an error is thrown when
comparing it to {} (expect(el2).toEqual({})), which allows any thrown error
(including ones whose message incorrectly contains "[Circular]"). Update the
assertion for the thrown error to also assert the error message does NOT contain
the string "[Circular]" so the test fails if a circular-related message is
produced; target the existing el2 setup and the current expect(...).toThrow
invocation when making this change.

@robobun

robobun commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator Author

The darwin-x64-build-cpp failure is GitHub returning HTTP 502 when fetching vendor tarballs (tinycc, zlib) — unrelated infra flake. This PR only touches Zig + test JS.

Supersedes #29170 (same fix, this one has more test coverage).

@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 — the pretty_format.zig mirror fix and the diff-formatter regression test address my earlier comment.

Extended reasoning...

Overview

This PR closes a fuzzer-found native stack overflow in the JSX inspection path. It touches two near-duplicate formatters — src/bun.js/ConsoleObject.zig (used by Bun.inspect/console.log) and src/bun.js/test/pretty_format.zig (used by the test diff renderer) — plus test/js/bun/util/inspect.test.js. In each formatter the change is two lines: add .JSX to canHaveCircularReferences() so the existing visited-map guard fires, and replace props.getObject().? with orelse break :props so a non-object props is treated as no-props instead of panicking. Five new tests cover circular props/children/key, non-object props, and the expect().toEqual() diff-formatter path.

Security risks

None. This is purely output-formatting / crash-avoidance code; it does not touch auth, filesystem, network, or parsing. Both edits are strictly defensive — they only add a cycle guard for one more tag and remove a force-unwrap.

Level of scrutiny

Low-to-medium. The change follows the exact pattern already used for .Array/.Object/.Map/.Set in the same function, so there's no novel logic. The labeled break :props exits after the defer this.quote_strings = prev_quote_strings is registered, so state is restored correctly. The only behavioural surface beyond crash-fix is that JSX values now occupy a slot in the visited map during recursion, which is the intended effect.

Other factors

My previous review flagged that pretty_format.zig was unpatched; the author applied the same fix in f26c1c3 and added a test that drives JestPrettyFormat via expect(el).toEqual({}). No CODEOWNERS cover these files. A bot flagged a possible duplicate (#29170), which maintainers may want to close, but that doesn't affect this PR's correctness.

@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 — the mirrored fix in pretty_format.zig addresses my earlier comment, and both code paths now have test coverage.

Extended reasoning...

Overview

This PR fixes a fuzzer-found native stack overflow when Bun.inspect() (and the test-runner diff formatter) encounters a self-referential JSX element. The change touches two near-identical formatters — src/jsc/ConsoleObject.zig and src/test_runner/pretty_format.zig — adding .JSX to canHaveCircularReferences() so the existing visited-map / [Circular] guard applies, and replacing a props.getObject().? force-unwrap with orelse break :props so a non-object props is skipped instead of asserting. Five new tests in inspect.test.js cover circular props/children/key, non-object props, and the expect().toEqual() diff path.

Security risks

None. This is purely output-formatting / crash-hardening code with no auth, crypto, filesystem, or network surface. The change strictly narrows behavior from a segfault to printing [Circular] or skipping invalid props.

Level of scrutiny

Low-to-medium. The fix is two mechanical edits per file: one enum case added to an existing switch, and one optional-unwrap converted to a labeled-block bailout. The defer this.quote_strings = prev_quote_strings placed before the break :props still fires on early exit, so state is correctly restored. The same pattern (visited-map guarded by canHaveCircularReferences()) already protects .Array/.Object/.Map/.Set, so adding .JSX follows the established design exactly.

Other factors

I previously flagged that the original revision only patched ConsoleObject.zig; the author applied the identical fix to pretty_format.zig in f26c1c3 and added a regression test exercising the diff-formatter path (expect(circularJsx).toEqual({})). That comment is resolved. CodeRabbit's remaining suggestion to tighten the props: 42 diff-formatter assertion is a minor test-quality nit — the test as written already regresses the crash (prior behavior was a segfault, not a thrown error). The CI failures listed by robobun are unrelated flakes (vendor-tarball 502 on darwin-x64, plus known-flaky tests in heap-snapshot/bake/webview/v8-date-parser that this PR does not touch). The bug-hunting system found no issues on the current revision.

@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 — the mirrored fix in pretty_format.zig addresses my earlier comment; the remaining .Event note is pre-existing and non-blocking.

Extended reasoning...

Overview

This PR fixes a fuzzer-found native stack overflow in Bun.inspect() for self-referential JSX elements. It makes two small changes, mirrored across src/jsc/ConsoleObject.zig and src/test_runner/pretty_format.zig: (1) add .JSX to canHaveCircularReferences() so the visited-map / [Circular] guard applies, and (2) replace the props.getObject().? force-unwrap with orelse break :props so a non-object props no longer panics. Five new tests in test/js/bun/util/inspect.test.js cover circular props, children, key, non-object props, and the test-diff-formatter path.

Security risks

None. This is purely a crash-hardening change in the value-formatter; it does not touch auth, network, filesystem, or parsing of untrusted input beyond making an existing code path more defensive.

Level of scrutiny

Low. Each Zig change is a one-line edit on a well-understood guard, applied identically in two near-duplicate formatters. The labelled-block break :props is the idiomatic Zig pattern already used elsewhere in these files. The change can only reduce crashes — it cannot regress correct output for well-formed JSX, since real React elements always have an object props and the visited-map check is a no-op on the first visit.

Other factors

My earlier review asked for the fix to be mirrored into pretty_format.zig; the author did so and added a regression test that exercises the diff-formatter path (expect(el).toEqual({})). The one new inline finding (.Event still omitted from pretty_format.zig's circular-reference list) is explicitly flagged as pre-existing, requires Object.defineProperty to trigger, and is a one-token follow-up the author can take or leave — it does not block this fuzzer fix. CodeRabbit's test-tightening suggestion is a minor nit. CI failure was an unrelated GitHub 502 on vendor tarball fetch.

Comment thread src/test_runner/pretty_format.zig Outdated

pub inline fn canHaveCircularReferences(tag: Tag) bool {
return tag == .Array or tag == .Object or tag == .Map or tag == .Set;
return tag == .Array or tag == .Object or tag == .Map or tag == .Set or tag == .JSX;

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.

🟣 While syncing this line with ConsoleObject.zig (which lists .Function, .Error, .Class, .Event in addition to .JSX), .Event is still omitted here. The .Event arm of JestPrettyFormat.printAs() recurses into this.format(...) on a MessageEvent's data / ErrorEvent's error without a visited-map guard, so e.g. Object.defineProperty(ev, 'data', {value: ev}); expect(ev).toEqual({}) still overflows the native stack — same crash class this PR fixes for .JSX. Pre-existing, but a one-token addition on the line already being edited.

Extended reasoning...

What the bug is

This PR brings canHaveCircularReferences() in src/test_runner/pretty_format.zig:329 partially into sync with src/jsc/ConsoleObject.zig:1139 by adding .JSX. However, ConsoleObject.zig's version also lists .Function, .Error, .Class, and .Event — and after this PR pretty_format.zig still omits all four. Of those, .Event is the one that matters: the .Event arm of JestPrettyFormat.printAs() recursively calls this.format(...) on the event's data (MessageEvent) and error (ErrorEvent) properties, and that recursion is unguarded because the visited-map insert/lookup at the top of printAs() is gated on comptime Format.canHaveCircularReferences().

The specific code path

In pretty_format.zig:

  • Tag.get() — a MessageEvent/ErrorEvent has js_type == .Event (JSEventType = 0b11101111 per JSMessageEvent.h:49 / JSErrorEvent.h:49), which is not .DOMWrapper (0b11101110), so the js_type == .DOMWrapper early-return doesn't fire. .Event is not isHidden(), so execution falls through to the final switch where .Event => .Event.
  • printAs(.Event, ...) — for event_type == .MessageEvent it does const data = value.fastGet(this.globalThis, .data), then const tag = Tag.get(data, ...), then this.format(tag, ..., data, ...). For .ErrorEvent it does the same with .@"error".
  • canHaveCircularReferences() at line 329 returns false for .Event, so the this.map.getOrPut(value) / [Circular] block at the top of printAs() is comptime-elided for the .Event instantiation.

Why nothing else prevents it

JestPrettyFormat has no depth limiter; the only cycle defence is the visited map, and it is only consulted when canHaveCircularReferences() returns true. ConsoleObject.zig already includes .Event in its list (line 1139), which is why Bun.inspect(ev) doesn't crash — but pretty_format.zig is a separate copy reached via diff_format.zig for expect().toEqual() / snapshot diffs, and it never got that addition.

Step-by-step proof

  1. const ev = new MessageEvent('message'); Object.defineProperty(ev, 'data', { value: ev }); — overriding data on a MessageEvent instance via defineProperty works (the existing MessageEvent with deleted data test in inspect.test.js already relies on this).
  2. expect(ev).toEqual({}) fails and diff_format.zig calls JestPrettyFormat.format(received, ...) to render ev.
  3. Tag.get(ev) returns { .tag = .Event, .cell = .Event }.
  4. printAs(.Event, ..., ev, ...) is entered. comptime .Event.canHaveCircularReferences() is false, so no visited-map insert.
  5. event_type resolves to .MessageEvent; fastGet(.data) returns ev itself.
  6. Tag.get(ev) again returns .Event; this.format(...) re-enters printAs(.Event, ..., ev, ...) → step 4. Native stack overflow → segfault in the test process.

The same applies to an ErrorEvent whose error property is overridden to point to itself.

Impact

Same crash class this PR fixes for .JSX: a self-referential value routed through the test diff formatter overflows the native stack and segfaults bun test. The trigger is admittedly contrived (requires Object.defineProperty to make a DOM event reference itself), so this is lower-priority than the JSX case the fuzzer actually found — but it's the identical mechanism on the very line this PR is editing to bring the two implementations into sync.

Fix

Add or tag == .Event to line 329:

return tag == .Array or tag == .Object or tag == .Map or tag == .Set or tag == .JSX or tag == .Event;

(.Function, .Error, and .Class are also missing relative to ConsoleObject.zig, but those arms in pretty_format.zig don't recurse, so omitting them is harmless — adding them would just fully sync the two lists.)

@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 — targeted crash fix with good test coverage; the .Event note is pre-existing and can be a follow-up.

Extended reasoning...

Overview

This PR fixes a fuzzer-found native stack overflow in Bun.inspect() when formatting self-referential JSX elements. The change is ~6 lines of logic across two near-duplicate formatters (src/jsc/ConsoleObject.zig and src/test_runner/pretty_format.zig): adding .JSX to canHaveCircularReferences() so the existing visited-map guard fires, and replacing a props.getObject().? force-unwrap with orelse break :props so non-object props is skipped instead of asserting. Five new test cases in inspect.test.js cover circular props/children/key, non-object props, and the test-diff-formatter path.

Security risks

None. This is output-formatting code with no auth, crypto, filesystem, or network surface. The change strictly narrows behavior (turning two crash paths into bounded [Circular] / no-op output) and introduces no new inputs or side effects.

Level of scrutiny

Low-to-moderate. The fix is mechanical — it extends an existing enum allowlist by one variant and replaces a panic-on-null with an early break, both well-established patterns already used for other tags in the same functions. The labelled-block break :props correctly preserves the defer this.quote_strings = prev_quote_strings restore. Both copies of the formatter received identical edits.

Other factors

My earlier review flagged that pretty_format.zig needed the same fix; the author applied it and the thread is resolved. The remaining open comments are (1) CodeRabbit's minor test-tightening suggestion and (2) my own note that .Event is still missing from pretty_format.zig's list — both are non-blocking nits about pre-existing behavior, not regressions introduced here. Test coverage directly exercises the previously-crashing inputs in both formatter code paths.

Comment thread test/js/bun/util/inspect.test.js Outdated

@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 — thanks for addressing the test nits; the remaining .Event note is pre-existing and can be a follow-up.

Extended reasoning...

Overview

This PR fixes a fuzzer-found native stack overflow in Bun.inspect() / the test diff formatter when given self-referential JSX-shaped objects. The change is two-fold across both src/jsc/ConsoleObject.zig and src/test_runner/pretty_format.zig: (1) add .JSX to canHaveCircularReferences() so the existing visited-map guard applies, and (2) replace the props.getObject().? force-unwrap with orelse break :props so non-object props is treated as no-props instead of asserting. Three subprocess regression tests are added to test/js/bun/util/inspect.test.js.

Security risks

None. This is a defensive fix in output-formatting code — it strictly reduces crash surface by adding cycle detection and removing an unsafe unwrap. No new inputs, no auth/crypto/permission logic.

Level of scrutiny

Low. The Zig changes are mechanical: one enum tag appended to an existing comptime allowlist (identical pattern already used for .Array/.Object/etc.), and one .? swapped for orelse break :props on a labeled block. Both edits are mirrored verbatim across the two duplicate formatters. The visited-map machinery being enabled is pre-existing and well-exercised by other tags.

Other factors

  • My earlier feedback (mirror the fix into pretty_format.zig; test-convention nits around assertion order, tempDir, and the negative panic check) has all been addressed in commits 39e75db and 8f62f57.
  • The one open note I left — .Event still missing from pretty_format.zig's canHaveCircularReferences() — is a pre-existing gap with a contrived trigger and is reasonable to defer to a follow-up; it does not block this fix.
  • The reported CI failures on 56394e7 are build-infra (scripts/build/ci.ts, vendor tarball fetches) unrelated to the Zig/test changes here, consistent with the two "Retrigger CI" commits.
  • Test coverage is solid: circular via props, children (single + array), and key; non-object props; and the expect().toEqual() diff path — all run in subprocesses so a regression segfault fails the test rather than killing the runner.

@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 — thanks for addressing the test-convention nits. The remaining .Event note on pretty_format.zig:329 is pre-existing and fine as a follow-up.

Extended reasoning...

Overview

Adds .JSX to canHaveCircularReferences() in both src/jsc/ConsoleObject.zig and src/test_runner/pretty_format.zig, and replaces the props.getObject().? force-unwrap with orelse break :props in both files. Three subprocess regression tests are added to test/js/bun/util/inspect.test.js covering circular props/children/key, non-object props, and the test-diff-formatter path.

Security risks

None. This is a defensive change in display/formatting code — it strictly removes a native stack overflow and an assertion failure on malformed JSX-shaped objects. No new inputs are trusted, no parsing/auth/crypto is touched.

Level of scrutiny

Low-to-medium. The fix is a one-token enum addition plus replacing a panic-on-null with a graceful early-out, mirrored across two near-identical formatters. The pattern matches how the other recursive tags (.Array, .Object, .Map, .Set) already participate in the visited-map guard, and ConsoleObject.zig already listed several other tags here. The labelled-block break :props correctly preserves the defer this.quote_strings = prev_quote_strings cleanup.

Other factors

  • All earlier review feedback has been addressed: the duplicate formatter in pretty_format.zig was patched (f26c1c3), and the test-convention nits (assertion order, tempDir, dropping the negative panic check) were fixed in 8f62f57.
  • The one open inline comment (.Event missing from pretty_format.zig:329) is my own and explicitly marked as a pre-existing issue of the same class — it does not block this fuzzer-found crash fix.
  • Tests run the crashing inputs in subprocesses so a regression would fail the suite rather than segfault the runner.
  • Reported CI failures on earlier commits were infra-level build-zig/build-cpp flakes unrelated to these Zig/JS-only changes; the author has retriggered.

@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 — minimal, well-tested crash fix; all prior review feedback addressed. The remaining .Event note is pre-existing and fine to defer.

Extended reasoning...

Overview

This PR fixes a fuzzer-found native stack overflow in Bun.inspect() (and the Jest diff formatter) when a JSX-shaped object references itself. The Zig changes are tiny and surgical: (1) add .JSX to canHaveCircularReferences() in both src/jsc/ConsoleObject.zig and src/test_runner/pretty_format.zig so the existing visited-map / [Circular] guard fires, and (2) replace props.getObject().? with orelse break :props so a non-object props no longer panics. Three new subprocess-based tests in test/js/bun/util/inspect.test.js cover circular props/children/key, non-object props, and the test-runner diff path.

Security risks

None. This is read-only formatting/inspection code; the change strictly narrows crash surface by adding cycle detection and removing a force-unwrap. No auth, crypto, filesystem, or network paths touched.

Level of scrutiny

Low–medium. The logic change is ~4 lines per file and follows the exact pattern already used for .Array/.Object/etc. The orelse break :props is strictly safer than the prior .? unwrap. Tests run in subprocesses so a regression segfault would still produce a clean failure rather than killing the suite.

Other factors

All earlier review feedback has been addressed: the fix was mirrored into pretty_format.zig (f26c1c3), and the test-convention nits — assertion order, tempDir over tmpdirSync, dropping the negative panic check — were applied in 8f62f57. My outstanding 🟣 note about .Event in pretty_format.zig is a pre-existing, contrived-trigger issue I explicitly flagged as optional; not a blocker for this PR. The single CI failure (test-http-should-emit-close-when-connection-is-aborted.ts timeout on Windows) is unrelated to these changes.

The JSX formatter did not participate in circular reference detection,
so a React element that referenced itself through props, key, or
children would recurse until the native stack overflowed.

Add Tag::JSX to can_have_circular_references() in both ConsoleObject.rs
and the test diff formatter (pretty_format.rs) so the visited-map guard
applies.
@robobun robobun force-pushed the farm/f27ac5e7/fix-jsx-inspect-circular branch from 195aec0 to 8150329 Compare May 22, 2026 03:44

@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 — thanks for addressing the test-convention nits. The pre-existing .Event gap in pretty_format.rs can be a follow-up.

Extended reasoning...

Overview

Two one-line additions: Tag::JSX is added to the can_have_circular_references() allowlist in both src/jsc/ConsoleObject.rs (Bun.inspect / console.log) and src/runtime/test_runner/pretty_format.rs (expect().toEqual() diff renderer). This enables the existing visited-map / [Circular] guard for JSX elements, fixing a fuzzer-found native stack overflow when a React element references itself. ~80 lines of subprocess-based regression tests cover circular props, children, key, and non-object props in both formatters.

Security risks

None. This is a defensive hardening change in output-formatting code — it gates more inputs through an existing cycle guard rather than adding new logic. No auth, crypto, parsing, or untrusted-input boundaries are touched beyond making inspection of pathological objects safer (turning a segfault into [Circular] text).

Level of scrutiny

Low-to-moderate. The Rust changes are mechanically trivial — adding one enum variant to two matches!() arms to opt into pre-existing safety machinery. There is no new control flow; the visited-map logic itself is unchanged. The test additions follow repo conventions (subprocess isolation so a regression fails the suite rather than segfaulting the runner, tempDir with using, stdout asserted before exitCode).

Other factors

All prior review feedback has been addressed: the fix was mirrored into pretty_format.rs after my first-round comment, and the latest revision applies all three test-convention nits from my second-round comment (assertion order, removed not.toContain("panic"), tempDir instead of tmpdirSync). The one remaining note — .Event still missing from pretty_format.rs's allowlist — is a pre-existing issue with a contrived trigger that I explicitly marked as optional; it should not block this fuzzer-fix PR. CodeRabbit's test-tightening suggestion is a minor nice-to-have that does not affect correctness. No bugs were found by the bug-hunting system on the current revision.

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.

1 participant