mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
fix: panic when overriding Set/Map size property with non-numeric value (#23787)
## Summary
Fixes a panic that occurred when `console.log()` tried to format a Set
or Map instance with a non-numeric `size` property.
## Issue
When a Set or Map subclass overrides the `size` property with a
non-numeric value (like a constructor function, string, or other
object), calling `console.log()` on the instance would trigger a panic:
```javascript
class C1 extends Set {
constructor() {
super();
Object.defineProperty(this, "size", {
writable: true,
enumerable: true,
value: Set
});
console.log(this); // panic!
}
}
new C1();
```
## Root Cause
In `src/bun.js/ConsoleObject.zig`, the Map and Set formatting code
called `toInt32()` directly on the `size` property value. This function
asserts that the value is not a Cell (objects/functions), causing a
panic when `size` was overridden with non-numeric values.
## Solution
Changed both Map and Set formatting to use `coerce(i32, globalThis)`
instead of `toInt32()`. This properly handles non-numeric values using
JavaScript's standard type coercion rules and propagates any coercion
errors appropriately.
## Test Plan
Added regression tests to `test/js/bun/util/inspect.test.js` that verify
Set and Map instances with overridden non-numeric `size` properties can
be inspected without panicking.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2717,7 +2717,7 @@ pub const Formatter = struct {
|
||||
},
|
||||
.Map => {
|
||||
const length_value = try value.get(this.globalThis, "size") orelse jsc.JSValue.jsNumberFromInt32(0);
|
||||
const length = length_value.toInt32();
|
||||
const length = try length_value.coerce(i32, this.globalThis);
|
||||
|
||||
const prev_quote_strings = this.quote_strings;
|
||||
this.quote_strings = true;
|
||||
@@ -2824,7 +2824,7 @@ pub const Formatter = struct {
|
||||
},
|
||||
.Set => {
|
||||
const length_value = try value.get(this.globalThis, "size") orelse jsc.JSValue.jsNumberFromInt32(0);
|
||||
const length = length_value.toInt32();
|
||||
const length = try length_value.coerce(i32, this.globalThis);
|
||||
|
||||
const prev_quote_strings = this.quote_strings;
|
||||
this.quote_strings = true;
|
||||
|
||||
@@ -347,6 +347,23 @@ it("inspect", () => {
|
||||
expect(Bun.inspect(new Map())).toBe("Map {}");
|
||||
expect(Bun.inspect(new Map([["foo", "bar"]]))).toBe('Map(1) {\n "foo": "bar",\n}');
|
||||
expect(Bun.inspect(new Set(["bar"]))).toBe('Set(1) {\n "bar",\n}');
|
||||
|
||||
// Regression test: Set/Map with overridden size property should not panic
|
||||
const setWithOverriddenSize = new Set();
|
||||
Object.defineProperty(setWithOverriddenSize, "size", {
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
value: Set,
|
||||
});
|
||||
expect(Bun.inspect(setWithOverriddenSize)).toBe("Set {}");
|
||||
|
||||
const mapWithOverriddenSize = new Map();
|
||||
Object.defineProperty(mapWithOverriddenSize, "size", {
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
value: "not a number",
|
||||
});
|
||||
expect(Bun.inspect(mapWithOverriddenSize)).toBe("Map {}");
|
||||
expect(Bun.inspect(<div>foo</div>)).toBe("<div>foo</div>");
|
||||
expect(Bun.inspect(<div hello>foo</div>)).toBe("<div hello=true>foo</div>");
|
||||
expect(Bun.inspect(<div hello={1}>foo</div>)).toBe("<div hello=1>foo</div>");
|
||||
|
||||
Reference in New Issue
Block a user