diff --git a/src/bun.js/VirtualMachine.zig b/src/bun.js/VirtualMachine.zig index 3512d374025..07286c1c0fe 100644 --- a/src/bun.js/VirtualMachine.zig +++ b/src/bun.js/VirtualMachine.zig @@ -1840,6 +1840,12 @@ pub fn resolveMaybeNeedsTrailingSlash( jsc_vm.log = old_log; jsc_vm.transpiler.linker.log = old_log; jsc_vm.transpiler.resolver.log = old_log; + // The package manager may have been initialized during this resolve + // with a pointer to the stack-local `log`. Restore it so it doesn't + // dangle after this frame returns. + if (jsc_vm.transpiler.resolver.package_manager) |pm| { + pm.log = old_log; + } } jsc_vm._resolve(&result, specifier_utf8.slice(), normalizeSource(source_utf8.slice()), is_esm, is_a_file_path) catch |err_| { var err = err_; diff --git a/test/js/bun/test/mock/mock-module-resolve-crash.test.ts b/test/js/bun/test/mock/mock-module-resolve-crash.test.ts new file mode 100644 index 00000000000..9ab1746597d --- /dev/null +++ b/test/js/bun/test/mock/mock-module-resolve-crash.test.ts @@ -0,0 +1,31 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe } from "harness"; + +// Regression test: mock.module with a non-string specifier (e.g. Intl.Segmenter) +// used to cause a stack-buffer-overflow because the PackageManager singleton could +// capture a dangling pointer to a stack-local Log during module resolution. +test("mock.module with non-string specifier does not crash", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + ` + const { mock } = require("bun:test"); + try { + mock.module(Intl.Segmenter, () => ({ default: 1 })); + } catch (e) { + // Expected to throw - we just need it not to crash + } + console.log("ok"); + `, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toContain("ok"); + expect(exitCode).toBe(0); +});