diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index a1cfeb510359..328e399b00ff 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/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/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig index a9e27eb97353..88b41ae9e99b 100644 --- a/src/bun.js/test/pretty_format.zig +++ b/src/bun.js/test/pretty_format.zig @@ -326,7 +326,7 @@ pub const JestPrettyFormat = struct { } 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; } const Result = struct { @@ -1534,13 +1534,12 @@ pub const JestPrettyFormat = 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 an object. - 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/test/pretty-format-overflow.test.ts b/test/js/bun/test/pretty-format-overflow.test.ts index 4acdf03b5110..677dfa04b025 100644 --- a/test/js/bun/test/pretty-format-overflow.test.ts +++ b/test/js/bun/test/pretty-format-overflow.test.ts @@ -50,4 +50,46 @@ test("deep nesting", () => { // Verify it actually formatted and showed the diff (not just crashed) expect(stderr).toContain("expect(received).toEqual(expected)"); }, 30000); + + test("JSX element with circular or non-object props", async () => { + const dir = tempDirWithFiles("pretty-format-jsx", { + "jsx.test.ts": ` +import { test, expect } from "bun:test"; + +test("circular props", () => { + const el: any = { $$typeof: Symbol.for("react.element"), type: "div", props: {} }; + el.props = el; + expect(el).toEqual({}); +}); + +test("circular children", () => { + const el: any = { $$typeof: Symbol.for("react.element"), type: "div", props: {} }; + el.props.children = el; + expect(el).toEqual({}); +}); + +test("non-object props", () => { + const el: any = { $$typeof: Symbol.for("react.element"), type: "div", props: 42 }; + expect(el).toEqual({}); +}); +`, + }); + + const proc = Bun.spawn({ + cmd: [bunExe(), "test", "jsx.test.ts"], + env: bunEnv, + cwd: dir, + stderr: "pipe", + stdout: "pipe", + }); + + const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]); + + expect(exitCode).toBe(1); + expect(stderr).not.toContain("panic"); + expect(stderr).not.toContain("SIGSEGV"); + expect(stderr).toContain("[Circular]"); + expect(stderr).toContain("expect(received).toEqual(expected)"); + expect(stderr).toContain("3 fail"); + }, 30000); }); diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index 32a70af30183..77abefa59826 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -772,3 +772,28 @@ it("CustomEvent", () => { }" `); }); + +describe("JSX element", () => { + it("handles circular props without crashing", () => { + const el = { $$typeof: Symbol.for("react.element"), type: "div", props: {} }; + el.props = el; + expect(Bun.inspect(el)).toContain("[Circular]"); + }); + + it("handles circular children without crashing", () => { + const el = { $$typeof: Symbol.for("react.element"), type: "div", props: {} }; + el.props.children = el; + expect(Bun.inspect(el)).toContain("[Circular]"); + }); + + it("handles circular array children without crashing", () => { + const el = { $$typeof: Symbol.for("react.element"), type: "div", props: { children: [] } }; + el.props.children.push(el); + expect(Bun.inspect(el)).toContain("[Circular]"); + }); + + it("handles non-object props without crashing", () => { + const el = { $$typeof: Symbol.for("react.element"), type: "div", props: 42 }; + expect(Bun.inspect(el)).toBe("
"); + }); +});