console: don't list prototype members as own properties

Closed
RO
10d ago
+88-69

Fixes #4223.
Fixes #1713.
Fixes #12365.

Repro

class ImageSettings {
  a = 123;
  toJSON() { return { a: '456' }; }
}
console.log(new ImageSettings());
beforeafternode
ImageSettings { a: 123, toJSON: [Function: toJSON] }ImageSettings { a: 123 }ImageSettings { a: 123 }

Cause

JSC__JSValue__forEachPropertyImpl walks up to five prototype levels after the object's own slots and reports every entry it finds. For a plain class instance that pulls in every prototype method and class-level constant, so console.log / Bun.inspect list shared behavior (toJSON, addEventListener, NONE, CAPTURING_PHASE, ...) as if it were instance state.

Fix

Keep the prototype walk but filter what it yields:

  • Fast path: the fast enumerator only runs on structures with no accessors, so every prototype slot it sees is a plain data property. Skip those when the current structure is not the original object.
  • Slow path: when iterating a prototype, keep only slot.isCustom() entries. Native custom accessors (URL.href, Event.type, AbortSignal.aborted) still surface per-instance state; user-defined getters and data/function slots are shared class members and are dropped, matching Node's default formatter.

Own properties are untouched, so { fn: () => {} } still prints fn: [Function: fn].

Effect on host objects (no custom-inspect hook needed):

URL { href, origin, protocol, ..., searchParams }         // no more toJSON/toString
AbortSignal { aborted, reason, onabort }                  // no throwIfAborted/addEventListener
Event { isTrusted, type, target, ..., returnValue }       // no composedPath/.../NONE/CAPTURING_PHASE

Verification

USE_SYSTEM_BUN=1 bun test test/js/bun/util/inspect.test.js -t "class instances show only own"   # fails
bun bd test test/js/bun/util/inspect.test.js                                                    # 74 pass
bun bd test test/js/web/console/console-log.test.ts test/js/web/url/url.test.ts                 # 22 pass
bun bd test test/js/node/util/custom-inspect.test.js test/js/node/util/bun-inspect.test.ts test/js/bun/console/   # 141 pass, 1 skip

Updated snapshots in inspect.test.js (CloseEvent/CustomEvent), url.test.ts, and console-log.expected.txt drop only [Function: ...] entries and the Event phase constants.


no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/bundler/bundler_edgecase.test.ts