Compare commits

...

3 Commits

Author SHA1 Message Date
Claude
17f1a1b167 Remove negative stderr assertion per review guidelines
The exit code check is the reliable crash indicator; negative string
assertions against stderr won't catch regressions in CI.

https://claude.ai/code/session_01UK81UJuPkCGjry9QgUrLRk
2026-02-21 05:05:05 +00:00
autofix-ci[bot]
9774591c2f [autofix.ci] apply automated fixes 2026-02-20 22:39:40 +00:00
Claude
d47fff51f2 fix(bindings): prevent null cell crash in PropertyCallback handlers on exception
PropertyCallback handlers like constructBunShell return `{}` (empty
JSValue, encoded as 0x0) when an exception occurs. JSC's
reifyStaticProperty passes this value to putDirect, which calls
isGetterSetter() on it. The empty JSValue passes the isCell() check
(since 0x0 has no tag bits set) but yields a null cell pointer,
causing a null pointer dereference.

Return jsUndefined() instead of {} from PropertyCallback handlers that
can throw exceptions (constructBunShell, defaultBunSQLObject,
constructBunSQLObject). jsUndefined() has tag bits set so isCell()
returns false, avoiding the null dereference. The pending exception
still propagates normally.
2026-02-20 22:37:44 +00:00
2 changed files with 44 additions and 6 deletions

View File

@@ -317,7 +317,7 @@ static JSValue defaultBunSQLObject(VM& vm, JSObject* bunObject)
#if BUN_DEBUG
if (scope.exception()) globalObject->reportUncaughtExceptionAtEventLoop(globalObject, scope.exception());
#endif
RETURN_IF_EXCEPTION(scope, {});
RETURN_IF_EXCEPTION(scope, jsUndefined());
RELEASE_AND_RETURN(scope, sqlValue.getObject()->get(globalObject, vm.propertyNames->defaultKeyword));
}
@@ -329,7 +329,7 @@ static JSValue constructBunSQLObject(VM& vm, JSObject* bunObject)
#if BUN_DEBUG
if (scope.exception()) globalObject->reportUncaughtExceptionAtEventLoop(globalObject, scope.exception());
#endif
RETURN_IF_EXCEPTION(scope, {});
RETURN_IF_EXCEPTION(scope, jsUndefined());
auto clientData = WebCore::clientData(vm);
RELEASE_AND_RETURN(scope, sqlValue.getObject()->get(globalObject, clientData->builtinNames().SQLPublicName()));
}
@@ -364,20 +364,20 @@ static JSValue constructBunShell(VM& vm, JSObject* bunObject)
args.append(createShellInterpreterFunction);
args.append(createParsedShellScript);
JSC::JSValue shell = JSC::call(globalObject, createShellFn, args, "BunShell"_s);
RETURN_IF_EXCEPTION(scope, {});
RETURN_IF_EXCEPTION(scope, jsUndefined());
if (!shell.isObject()) [[unlikely]] {
throwTypeError(globalObject, scope, "Internal error: BunShell constructor did not return an object"_s);
return {};
return jsUndefined();
}
auto* bunShell = shell.getObject();
auto ShellError = bunShell->get(globalObject, JSC::Identifier::fromString(vm, "ShellError"_s));
RETURN_IF_EXCEPTION(scope, {});
RETURN_IF_EXCEPTION(scope, jsUndefined());
if (!ShellError.isObject()) [[unlikely]] {
throwTypeError(globalObject, scope, "Internal error: BunShell.ShellError is not an object"_s);
return {};
return jsUndefined();
}
bunShell->putDirectNativeFunction(vm, globalObject, Identifier::fromString(vm, "braces"_s), 1, Generated::BunObject::jsBraces, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | 0);

View File

@@ -0,0 +1,38 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
test("accessing Bun.$ after stack overflow from recursive constructor does not crash", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
delete globalThis.Loader;
Bun.generateHeapSnapshot = console.profile = console.profileEnd = process.abort = () => {};
const v2 = { maxByteLength: 875 };
const v4 = new ArrayBuffer(875, v2);
try { v4.resize(875); } catch (e) {}
new BigUint64Array(v4);
function F8(a10, a11, a12, a13) {
if (!new.target) { throw 'must be called with new'; }
const v14 = this?.constructor;
try { new v14(a12, v4, v2, v2); } catch (e) {}
Bun.$;
}
new F8(F8, v4, v2, BigUint64Array);
try {
} catch(e19) {
}
const v20 = {};
Bun.gc(true);
`,
],
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(0);
});