From b72bbd4bad6d5689b7162bca2243aed737a802cc Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:20:28 +0000 Subject: [PATCH 1/2] bun:jsc: only return JSObjects from getProtectedObjects --- src/jsc/modules/BunJSCModule.h | 8 +++++++- test/js/bun/jsc/bun-jsc.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/jsc/modules/BunJSCModule.h b/src/jsc/modules/BunJSCModule.h index deb683bb80d7..f54ace34774b 100644 --- a/src/jsc/modules/BunJSCModule.h +++ b/src/jsc/modules/BunJSCModule.h @@ -590,8 +590,14 @@ JSC_DEFINE_HOST_FUNCTION(functionGetProtectedObjects, (JSGlobalObject * globalObject, CallFrame*)) { MarkedArgumentBuffer list; + // The protected cell set also contains internal cells (unlinked code blocks, + // private symbols, structures). Those are not valid JavaScript values, so + // handing them to JS as-is causes type confusion the first time one is touched. globalObject->vm().heap.forEachProtectedCell( - [&](JSCell* cell) { list.append(cell); }); + [&](JSCell* cell) { + if (cell->isObject()) + list.append(cell); + }); RELEASE_ASSERT(!list.hasOverflowed()); return JSC::JSValue::encode(constructArray( globalObject, static_cast(nullptr), list)); diff --git a/test/js/bun/jsc/bun-jsc.test.ts b/test/js/bun/jsc/bun-jsc.test.ts index 13657cb8909c..1eb3226f6837 100644 --- a/test/js/bun/jsc/bun-jsc.test.ts +++ b/test/js/bun/jsc/bun-jsc.test.ts @@ -361,6 +361,30 @@ it("deserialize rejects a RegExp record whose pattern does not parse", async () expect(exitCode).toBe(0); }); +it("getProtectedObjects returns only objects", async () => { + // The protected cell set also contains internal JSC cells (unlinked code + // blocks, private symbols, structures). Those are not JavaScript values, so + // generic operations on them (Object(), Object.prototype.toString.call, + // String(list)) crashed the process instead of behaving like objects. + const script = ` + import { getProtectedObjects } from "bun:jsc"; + const list = getProtectedObjects(); + for (const value of list) { + if (Object(value) !== value) throw new Error("getProtectedObjects returned a non-object: " + typeof value); + Object.prototype.toString.call(value); + } + console.log("ok", list.length > 0); + `; + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", script], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect({ stdout, stderr, exitCode }).toEqual({ stdout: "ok true\n", stderr: "", exitCode: 0 }); +}); + it("serialize rejects a CryptoKey created with extractable set to false", async () => { // bun:jsc serialize() (and node:v8 serialize(), which wraps it) hands the raw // structured-clone buffer to the caller, so a key imported with From d2774cdbd939a2dc8338ef77a43a3b444a52868d Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 11 Jul 2026 03:05:30 +0000 Subject: [PATCH 2/2] types: update getProtectedObjects JSDoc and return object[] --- packages/bun-types/jsc.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bun-types/jsc.d.ts b/packages/bun-types/jsc.d.ts index 92416afcc383..856c5ce92dcc 100644 --- a/packages/bun-types/jsc.d.ts +++ b/packages/bun-types/jsc.d.ts @@ -196,11 +196,11 @@ declare module "bun:jsc" { * Calling this function creates another reference to each object, which * further prevents it from being garbage collected. * - * This is mostly a debugging tool for Bun itself. - * - * Warning: not all of the returned objects are supposed to be observable from JavaScript. + * This is mostly a debugging tool for Bun itself. The list may include + * engine-internal objects (such as module records and the global object) + * that user code would not otherwise be able to reach. */ - function getProtectedObjects(): any[]; + function getProtectedObjects(): object[]; /** * Starts a remote debugging socket server on the given port.