diff --git a/src/bun.js/BuildMessage.zig b/src/bun.js/BuildMessage.zig index ced3a1df92a..1777e3f9908 100644 --- a/src/bun.js/BuildMessage.zig +++ b/src/bun.js/BuildMessage.zig @@ -183,7 +183,8 @@ pub const BuildMessage = struct { } pub fn finalize(this: *BuildMessage) void { - this.msg.deinit(bun.default_allocator); + this.msg.deinit(this.allocator); + this.allocator.destroy(this); } }; diff --git a/src/bun.js/ResolveMessage.zig b/src/bun.js/ResolveMessage.zig index 047accc373f..5ea9f55fb38 100644 --- a/src/bun.js/ResolveMessage.zig +++ b/src/bun.js/ResolveMessage.zig @@ -225,10 +225,11 @@ pub const ResolveMessage = struct { } pub fn finalize(this: *ResolveMessage) callconv(.c) void { - this.msg.deinit(bun.default_allocator); + this.msg.deinit(this.allocator); if (this.referrer) |referrer| { this.allocator.free(referrer.text); } + this.allocator.destroy(this); } }; diff --git a/src/ini.zig b/src/ini.zig index 9eaacd4ce92..c2530c4e9fb 100644 --- a/src/ini.zig +++ b/src/ini.zig @@ -634,7 +634,7 @@ pub const IniTestingAPIs = struct { var configs = std.array_list.Managed(ConfigIterator.Item).init(allocator); defer configs.deinit(); loadNpmrc(allocator, install, env, ".npmrc", &log, source, &configs) catch { - return log.toJS(globalThis, allocator, "error"); + return log.toJS(globalThis, bun.default_allocator, "error"); }; const default_registry_url, const default_registry_token, const default_registry_username, const default_registry_password, const default_registry_email = brk: { diff --git a/test/js/bun/resolve/build-error.test.ts b/test/js/bun/resolve/build-error.test.ts index 7f2d32cc307..a623aa505e9 100644 --- a/test/js/bun/resolve/build-error.test.ts +++ b/test/js/bun/resolve/build-error.test.ts @@ -1,3 +1,6 @@ +import { tempDir } from "harness"; +import { join } from "node:path"; + test("BuildError is modifiable", async () => { try { await import("../util/inspect-error-fixture-bad.js"); @@ -15,3 +18,24 @@ test("BuildError is modifiable", async () => { expect(error!.message).toBe("new message"); expect(error!.message).not.toBe(message); }); + +test("BuildMessage finalize frees with the same allocator it was created with", async () => { + // BuildMessage.create() clones the message with the passed allocator + // but finalize() was freeing it with bun.default_allocator and never + // destroying the struct itself. + using dir = tempDir("build-message-finalize", { "bad.js": "function bad( {" }); + const entry = join(String(dir), "bad.js"); + for (let i = 0; i < 20; i++) { + const r = await Bun.build({ entrypoints: [entry], throw: false }); + expect(r.success).toBe(false); + expect(r.logs.length).toBeGreaterThan(0); + for (const e of r.logs) { + void e.message; + void e.level; + void e.position; + void e.notes; + void String(e); + } + Bun.gc(true); + } +}); diff --git a/test/js/bun/resolve/resolve-error.test.ts b/test/js/bun/resolve/resolve-error.test.ts index 78b6c2c39fc..56a509866f1 100644 --- a/test/js/bun/resolve/resolve-error.test.ts +++ b/test/js/bun/resolve/resolve-error.test.ts @@ -76,6 +76,39 @@ describe("ResolveMessage", () => { expect(err.referrer).toStartWith("/tmp/caf"); expect(err.referrer).toEndWith("/file.js"); }); + + it("finalize frees with the same allocator it was created with", () => { + // ResolveMessage.create() clones the message with the VM's arena + // allocator but finalize() was freeing it with bun.default_allocator + // and never destroying the struct itself. Under ASAN with mimalloc's + // per-heap tracking this surfaced as a flaky use-after-poison in the + // resolver after many failed require()s + GCs in a long-running + // process (Fuzzilli REPRL). Use relative specifiers so auto-install + // does not kick in. + for (let i = 0; i < 50; i++) { + let errs: any[] = []; + for (let j = 0; j < 10; j++) { + try { + Bun.resolveSync("./does-not-exist-" + j, import.meta.dir); + } catch (e) { + errs.push(e); + } + } + for (const e of errs) { + void e.message; + void e.code; + void e.specifier; + void e.referrer; + void e.level; + void e.importKind; + void e.position; + void String(e); + } + errs = []; + Bun.gc(true); + } + expect().pass(); + }); }); // These tests reproduce panics where the module resolver wrote past fixed-size