From 775103fc6a0f75df26b5da20df808feffebedc10 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 12 May 2026 09:25:38 +0000 Subject: [PATCH] Fix crash in Bun.inspect when JSX props is non-object or circular Bun.inspect of a JSX-like element (object with $$typeof = react.element) assumed the `props` value was always an object, unwrapping it with `.?`. When it was a primitive, this panicked with a null unwrap. Additionally, the JSX formatter was not included in the set of tags that track visited objects, so a circular reference through `props` or `props.children` would recurse until the stack overflowed. - Skip props handling when `props` is not an object - Add .JSX to canHaveCircularReferences so the visited map and stack check apply to JSX elements --- src/jsc/ConsoleObject.zig | 7 +++---- test/js/bun/util/inspect.test.js | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/jsc/ConsoleObject.zig b/src/jsc/ConsoleObject.zig index 2489ea14ea03..917dfe29949a 100644 --- a/src/jsc/ConsoleObject.zig +++ b/src/jsc/ConsoleObject.zig @@ -1136,7 +1136,7 @@ pub const Formatter = struct { pub fn canHaveCircularReferences(tag: Tag) bool { return switch (tag) { - .Function, .Array, .Object, .Map, .Set, .Error, .Class, .Event => true, + .Function, .Array, .Object, .Map, .Set, .Error, .Class, .Event, .JSX => true, else => false, }; } @@ -3140,13 +3140,12 @@ pub const Formatter = struct { } } - if (try value.get(this.globalThis, "props")) |props| { + if (try value.get(this.globalThis, "props")) |props| props: { const prev_quote_strings = this.quote_strings; defer this.quote_strings = prev_quote_strings; this.quote_strings = true; - // SAFETY: JSX props are always objects - const props_obj = props.getObject().?; + const props_obj = props.getObject() orelse break :props; var props_iter = try jsc.JSPropertyIterator(.{ .skip_empty_name = true, .include_value = true, diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index 32a70af30183..2e43673e1863 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -316,6 +316,39 @@ it("jsx with fragment", () => { expect(input).toBe(output); }); +it("jsx with non-object props does not crash", () => { + const el = { + $$typeof: Symbol.for("react.transitional.element"), + type: "div", + key: null, + ref: null, + props: 1, + }; + expect(Bun.inspect(el)).toBe("
"); +}); + +it("jsx with circular props does not crash", () => { + const el = { + $$typeof: Symbol.for("react.transitional.element"), + type: "div", + key: null, + ref: null, + }; + el.props = el; + expect(Bun.inspect(el)).toContain("[Circular]"); +}); + +it("jsx with circular children does not crash", () => { + const el = { + $$typeof: Symbol.for("react.transitional.element"), + type: "div", + key: null, + ref: null, + }; + el.props = { children: el }; + expect(Bun.inspect(el)).toContain("[Circular]"); +}); + it("inspect", () => { expect(Bun.inspect(new TypeError("what")).includes("TypeError: what")).toBe(true); expect(Bun.inspect("hi")).toBe('"hi"');