Files
bun.sh/test/regression/issue/22199.test.ts
robobun dd9d1530da Fix crash when plugin onResolve returns undefined (#22670)
## Summary
Fixes #22199

When a plugin's `onResolve` handler returns `undefined` or `null`, Bun
should continue to the next plugin or use default resolution. However,
the code was crashing with a segmentation fault.

## The Bug
The crash occurred when:
1. A plugin's `onResolve` handler returned `undefined` (especially from
an async function as a fulfilled promise)
2. The code extracted the promise result but didn't check if it was
undefined before expecting it to be an object
3. This caused an improper exception to be thrown, leading to a crash

## The Fix
1. **Main fix**: Added a check for `undefined/null` after extracting the
result from a fulfilled promise, allowing the code to continue to the
next plugin
2. **Promise rejection fix**: Changed rejected promise handling to
return the promise itself instead of throwing an exception (which was
causing hangs)
3. **Exception handling**: Standardized exception throwing throughout
the file to use the proper `throwException` pattern

## Test Plan
Added comprehensive regression tests in
`test/regression/issue/22199.test.ts` that verify:
-  Async function returning `undefined` doesn't crash
-  Async function returning `null` doesn't crash  
-  Sync function returning `undefined` doesn't crash
-  Async function throwing an error properly shows the error

All tests:
- **Fail (crash) with release Bun**: Segmentation fault
- **Pass with this fix**: All test cases pass

## Verification
```bash
# Crashes without the fix
bun test test/regression/issue/22199.test.ts  

# Passes with the fix
bun bd test test/regression/issue/22199.test.ts
```

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-15 23:37:10 -07:00

110 lines
3.0 KiB
TypeScript

import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
test("plugin onResolve returning undefined should not crash", () => {
using dir = tempDir("plugin-undefined", {
"plugin.js": `
Bun.plugin({
name: "test-plugin",
setup(build) {
build.onResolve({ filter: /.*\\.(ts|tsx|js|jsx)$/ }, async (args) => {
// Returning undefined should continue to next plugin or default resolution
return undefined;
});
},
});
`,
"index.js": `console.log("Hello from index.js");`,
});
const result = Bun.spawnSync({
cmd: [bunExe(), "--preload", "./plugin.js", "./index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "inherit",
});
expect(result.exitCode).toBe(0);
expect(result.stdout.toString().trim()).toBe("Hello from index.js");
});
test("plugin onResolve returning null should not crash", () => {
using dir = tempDir("plugin-null", {
"plugin.js": `
Bun.plugin({
name: "test-plugin",
setup(build) {
build.onResolve({ filter: /.*\\.(ts|tsx|js|jsx)$/ }, async (args) => {
// Returning null should continue to next plugin or default resolution
return null;
});
},
});
`,
"index.js": `console.log("Hello from index.js");`,
});
const result = Bun.spawnSync({
cmd: [bunExe(), "--preload", "./plugin.js", "./index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "inherit",
});
expect(result.exitCode).toBe(0);
expect(result.stdout.toString().trim()).toBe("Hello from index.js");
});
test("plugin onResolve with sync function returning undefined should not crash", () => {
using dir = tempDir("plugin-sync-undefined", {
"plugin.js": `
Bun.plugin({
name: "test-plugin",
setup(build) {
build.onResolve({ filter: /.*\\.(ts|tsx|js|jsx)$/ }, (args) => {
// Sync function returning undefined
return undefined;
});
},
});
`,
"index.js": `console.log("Hello from index.js");`,
});
const result = Bun.spawnSync({
cmd: [bunExe(), "--preload", "./plugin.js", "./index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "inherit",
});
expect(result.exitCode).toBe(0);
expect(result.stdout.toString().trim()).toBe("Hello from index.js");
});
test("plugin onResolve with rejected promise should throw error", () => {
using dir = tempDir("plugin-reject", {
"plugin.js": `
Bun.plugin({
name: "test-plugin",
setup(build) {
build.onResolve({ filter: /.*\\.(ts|tsx|js|jsx)$/ }, async (args) => {
throw new Error("Custom plugin error");
});
},
});
`,
"index.js": `console.log("Hello from index.js");`,
});
const result = Bun.spawnSync({
cmd: [bunExe(), "--preload", "./plugin.js", "./index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});
expect(result.exitCode).toBe(1);
expect(result.stderr.toString()).toContain("Custom plugin error");
});