console: format boxed primitives and RegExp from their internal slot
What
The native console.log / Bun.inspect formatter rendered boxed primitives (new Number/String/Boolean) and RegExp objects by running spec ToString on the wrapper object, which dispatches through the value's own [Symbol.toPrimitive] / toString / valueOf:
const n = new Number(5);
n[Symbol.toPrimitive] = h => "TP-Number";
console.log(n); // before: [Number: TP-Number] after: [Number: 5]
const t = new Number(7);
t.toString = () => { throw new Error("boom") };
console.log(t); // before: throws "boom" out of console.log
console.log({ a: 1, boxed: t, b: 2 }); // before: "{ a: 1, boxed: " then throws mid-line
Node reads the internal [[NumberData]] / [[StringData]] / [[BooleanData]] slot and never invokes the hooks. Bun's own ported node:util.inspect was already correct; only the native path had this.
Fix
- New
JSC__JSValue__getWrapperInternalValuereadsJSWrapperObject::internalValue()directly (noToPrimitive, no getters, never throws). - New
JSC__JSValue__getRegExpDisplayStringformats/source/flagsfromRegExp::toSourceString()(the internalRegExprecord, notRegExp.prototype.toString). print_double/print_boolean/print_stringinConsoleObject.rsnow obtain their text from the internal value instead ofto_zig_string/BunString::from_json the wrapper.
JSC__JSValue__unwrapBoxedPrimitive is left alone: it intentionally mirrors WebKit's JSONObject.cpp unwrapBoxedPrimitive for JSON.stringify semantics, which the TOML/YAML/JSON5 stringifiers depend on.
Side effect: new Number(-0) now prints [Number: -0] (matches Node); it previously printed [Number: 0] because spec ToString(-0) is "0".
Verification
USE_SYSTEM_BUN=1 bun test test/js/bun/util/inspect.test.js -t "boxed primitives" # 2 fail
bun bd test test/js/bun/util/inspect.test.js -t "boxed primitives" # 2 pass
bun bd test test/js/bun/util/inspect.test.js # 75 pass
bun bd test test/js/bun/console/ test/js/web/console/ # pass
[review] gate passed · iteration 1 · 4 files touched
fails on main (without fix)
ASAN without fix: 2 FAILED
$ 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 (66730150f)
test/js/bun/util/inspect.test.js:
(pass) prototype [293.97ms]
(pass) getters [5.30ms]
(pass) setters [3.48ms]
(pass) getter/setters [2.10ms]
(pass) Timeout [4.72ms]
(pass) when prototype defines the same property, don't print the same property twice [2.07ms]
(pass) Blob inspect [8.65ms]
(pass) utf16 property name [59.60ms]
(pass) latin1 [4.03ms]
(pass) Request object [2.54ms]
(pass) MessageEvent [1.84ms]
(pass) MessageEvent with no data set [2.14ms]
(pass) MessageEvent with deleted data [2.48ms]
(pass) TypedArray prints [92.01ms]
(pass) BigIntArray [30.25ms]
(pass) Float32Array 42.68000030517578 [3.82ms]
(pass) Float32Array 42.68 [3.85ms]
(pass) Float64Array 42.68000030517578 [1.35ms]
(pass) Float64Array 42.68 [1.60ms]
(pass) jsx with two elements [26.22ms]
(pass) jsx with anon component [2.58ms]
(pass) jsx with fragment [4.28ms]
(pass) inspect [72.42ms]
(pass) latin1 supplemental > latin1 (input) "äbc" [ "äbc" ] [1.64ms]
(pass) latin1 supplemental > latin1 (input) "cbä"
... (truncated)
release without fix: all passed
bun test v1.4.0-canary.1 (96f2fdd40)
test/js/bun/util/inspect.test.js:
(pass) prototype [3.31ms]
(pass) getters [0.11ms]
(pass) setters [0.04ms]
(pass) getter/setters [0.02ms]
(pass) Timeout [0.07ms]
(pass) when prototype defines the same property, don't print the same property twice [0.03ms]
(pass) Blob inspect [0.22ms]
(pass) utf16 property name [1.13ms]
(pass) latin1 [0.05ms]
(pass) Request object [0.04ms]
(pass) MessageEvent [0.03ms]
(pass) MessageEvent with no data set [0.01ms]
(pass) MessageEvent with deleted data [0.03ms]
(pass) TypedArray prints [0.83ms]
(pass) BigIntArray [0.31ms]
(pass) Float32Array 42.68000030517578 [0.07ms]
(pass) Float32Array 42.68 [0.07ms]
(pass) Float64Array 42.68000030517578 [0.01ms]
(pass) Float64Array 42.68
(pass) jsx with two elements [0.54ms]
(pass) jsx with anon component [0.03ms]
(pass) jsx with fragment [0.07ms]
(pass) inspect [0.66ms]
(pass) latin1 supplemental > latin1 (input) "äbc" [ "äbc" ] [0.03ms]
(pass) latin1 supplemental > latin1 (input) "cbä" [ "cbä" ]
(pass) latin1 supplemental > latin1 (input) "cäb" [ "cäb" ]
(pass) latin1 supplemental > latin1 (input) "äbc äbc" [ "äbc äbc" ]
(pass) latin1 supplemental >
... (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 (66730150f)
test/js/bun/util/inspect.test.js:
(pass) prototype [341.92ms]
(pass) getters [5.21ms]
(pass) setters [3.72ms]
(pass) getter/setters [3.31ms]
(pass) Timeout [7.29ms]
(pass) when prototype defines the same property, don't print the same property twice [2.72ms]
(pass) Blob inspect [9.26ms]
(pass) utf16 property name [77.17ms]
(pass) latin1 [4.06ms]
(pass) Request object [2.59ms]
(pass) MessageEvent [1.86ms]
(pass) MessageEvent with no data set [1.80ms]
(pass) MessageEvent with deleted data [2.50ms]
(pass) TypedArray prints [129.07ms]
(pass) BigIntArray [35.47ms]
(pass) Float32Array 42.68000030517578 [4.00ms]
(pass) Float32Array 42.68 [4.00ms]
(pass) Float64Array 42.68000030517578 [1.36ms]
(pass) Float64Array 42.68 [1.61ms]
(pass) jsx with two elements [34.10ms]
(pass) jsx with anon component [2.85ms]
(pass) jsx with fragment [6.68ms]
(pass) inspect [103.95ms]
(pass) latin1 supplemental > latin1 (input) "äbc" [ "äbc" ] [4.00ms]
(pass) latin1 supplemental > latin1 (input) "cbä
... (truncated)
release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 631ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/7] cxx obj/src/jsc/bindings/bindings.cpp.o
[2/7] gen cpp.rs (cppbind)
[3/7] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 239 extern-C blocks audited
[3/7] 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_jsc v0.0.0 (/workspace/bun/src/jsc)
[1m[92m Compiling[0m bun_js_parser_jsc v0.0.0 (/workspace/bun/src/js_parser_jsc)
[1m[92m Compiling[0m bun_ast_jsc v0.0.0 (/workspace/bun/src/ast_jsc)
[1m[92m Compiling[0m bun_bundler_jsc v0.0.0 (/workspace/bun/src/bundler_jsc)
[1m[92m Compiling[0m bun_css_jsc v0.0.0 (/workspace/bun/src/css_jsc)
[1m[92m Compiling[0m bun_patch_jsc v0.0.0 (/workspace/bun/src/patch_jsc)
[1m[92m Compiling[0m bun_semver_jsc v0.0.0 (/workspace/bun/src/semver_jsc)
[1m[92m Compiling[0m bun_sys_jsc v0.0.0 (/workspace/bun/src/sys_jsc)
[1m[92m Com
... (truncated)
diff hotspot
src/jsc/ConsoleObject.rs | 56 +++++++++++++++-----
src/jsc/JSValue.rs | 12 +++++
src/jsc/bindings/bindings.cpp | 20 ++++++++
test/js/bun/util/inspect.test.js | 107 +++++++++++++++++++++++++++++++++++++++
4 files changed, 183 insertions(+), 12 deletions(-)
gate history · 3 passed · 0 rejected · iteration 1
evidence per changed file
file reads edits tests
src/jsc/ConsoleObject.rs 6 9 0
src/jsc/JSValue.rs 5 5 0
src/jsc/bindings/bindings.cpp 5 3 0
test/js/bun/util/inspect.test.js 3 5 0