Skip to content

Comments

fix(bindings): prevent null cell crash in PropertyCallback handlers on exception#27307

Open
robobun wants to merge 3 commits intomainfrom
toaster/fix-shell-property-callback-crash
Open

fix(bindings): prevent null cell crash in PropertyCallback handlers on exception#27307
robobun wants to merge 3 commits intomainfrom
toaster/fix-shell-property-callback-crash

Conversation

@robobun
Copy link
Collaborator

@robobun robobun commented Feb 20, 2026

Summary

  • Fix null pointer dereference when accessing Bun.$ (or other PropertyCallback-backed properties) during/after a stack overflow exception
  • PropertyCallback handlers (constructBunShell, defaultBunSQLObject, constructBunSQLObject) returned {} (empty JSValue, encoded as 0x0) on exception. JSC's reifyStaticProperty passes this value to putDirect(), which calls isGetterSetter() on it. The empty JSValue passes the isCell() check but yields a null cell pointer, causing the crash.
  • Changed these handlers to return jsUndefined() instead, which has tag bits set so isCell() returns false, avoiding the null dereference. The pending exception still propagates normally.

Crash reproduction

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);

Test plan

  • Added test/js/bun/shell/shell-property-callback-crash.test.ts that reproduces the crash scenario
  • Test passes with bun bd test and fails with USE_SYSTEM_BUN=1
  • Ran reproduction case 10 times to verify no flakiness

…n 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.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

No actionable comments were generated in the recent review. 🎉


Walkthrough

Change error-path returns in src/bun.js/bindings/BunObject.cpp to consistently return jsUndefined() on exceptions instead of empty JS values; add a new regression test that spawns a child process to verify shell property callback behavior produces no runtime errors and exits with code 0.

Changes

Cohort / File(s) Summary
Error handling in BunObject
src/bun.js/bindings/BunObject.cpp
Replaced instances of empty JSValue/{} returns and RETURN_IF_EXCEPTION(scope, {}) with jsUndefined() in exception handling paths for SQL object constructors, BunShell construction, and related initialization branches.
Regression test for shell callbacks
test/js/bun/shell/shell-property-callback-crash.test.ts
Added a test that spawns a separate process which manipulates globals and Bun internals, invokes shell property callbacks, and asserts the child process exits with code 0 and emits no stderr runtime errors.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: preventing null cell crashes in PropertyCallback handlers when exceptions occur.
Description check ✅ Passed The description includes both required sections (What does this PR do and How did you verify) with comprehensive details: root cause analysis, code changes, crash reproduction, and test validation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@robobun
Copy link
Collaborator Author

robobun commented Feb 20, 2026

Updated 9:05 PM PT - Feb 20th, 2026

@claude, your commit 17f1a1b1672b27c052f796de8500f03d41f1f0c7 passed in Build #37804! 🎉


🧪   To try this PR locally:

bunx bun-pr 27307

That installs a local version of the PR into your bun-27307 executable, so you can run:

bun-27307 --bun

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/js/bun/shell/shell-property-callback-crash.test.ts`:
- Line 37: Remove the negative assertion that checks stderr for "runtime error"
by deleting the expect(stderr).not.toContain("runtime error"); line in the test
(the assertion that references stderr and .not.toContain("runtime error")). Rely
solely on the existing exit code assertion (the exit code check/assertion in the
test) as the crash indicator, so no additional checks against stderr are
performed.


const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stderr).not.toContain("runtime error");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove the negative assertion for "runtime error" in stderr.

As per coding guidelines, tests should never check for the absence of 'panic', 'uncaught exception', or similar strings in test output because these tests will never fail in CI if the fix regresses. The exit code check on line 38 is the reliable indicator of a crash. If the null pointer dereference occurs, the process will exit with a non-zero code.

Proposed fix
   const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

-  expect(stderr).not.toContain("runtime error");
   expect(exitCode).toBe(0);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/js/bun/shell/shell-property-callback-crash.test.ts` at line 37, Remove
the negative assertion that checks stderr for "runtime error" by deleting the
expect(stderr).not.toContain("runtime error"); line in the test (the assertion
that references stderr and .not.toContain("runtime error")). Rely solely on the
existing exit code assertion (the exit code check/assertion in the test) as the
crash indicator, so no additional checks against stderr are performed.

@claude
Copy link
Contributor

claude bot commented Feb 20, 2026

Newest first

17f1a — Looks good!

Reviewed 2 files across src/bun.js/bindings/ and test/js/bun/shell/: Fixes a null pointer dereference crash when accessing PropertyCallback-backed properties (like Bun.$, Bun.sql, Bun.SQL) during a pending exception by returning jsUndefined() instead of empty JSValue.

Previous reviews

🟡 97745 — 1 issue(s) found

Issue Severity File
Test uses prohibited negative stderr assertion 🟡 test/js/bun/shell/shell-property-callback-crash.test.ts

Note: This issue was already flagged by CodeRabbit in an existing review thread. No duplicate inline comment posted.

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants