mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
## 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>
110 lines
3.0 KiB
TypeScript
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");
|
|
});
|