diff --git a/src/runtime/api/JSTranspiler.zig b/src/runtime/api/JSTranspiler.zig index 7d231902efe..e5532382df5 100644 --- a/src/runtime/api/JSTranspiler.zig +++ b/src/runtime/api/JSTranspiler.zig @@ -503,6 +503,9 @@ pub const TransformTask = struct { this.transpiler.setAllocator(allocator); this.transpiler.setLog(&this.log); this.log.msgs.allocator = bun.default_allocator; + defer for (this.log.msgs.items) |*msg| { + msg.* = bun.handleOom(msg.clone(bun.default_allocator)); + }; const jsx = if (this.tsconfig != null) this.tsconfig.?.mergeJSX(this.transpiler.options.jsx) @@ -587,6 +590,11 @@ pub const TransformTask = struct { } pub fn deinit(this: *TransformTask) void { + for (this.log.msgs.items) |*msg| { + freeClonedLocation(&msg.data); + for (msg.notes) |*note| freeClonedLocation(note); + msg.deinit(bun.default_allocator); + } this.log.deinit(); this.input_code.deinitAndUnprotect(); this.output_code.deref(); @@ -595,6 +603,12 @@ pub const TransformTask = struct { this.js_instance.deref(); bun.destroy(this); } + + fn freeClonedLocation(data: *logger.Data) void { + const loc = &(data.location orelse return); + bun.default_allocator.free(loc.file); + if (loc.line_text) |lt| bun.default_allocator.free(lt); + } }; fn exportReplacementValue(value: JSValue, globalThis: *JSGlobalObject, allocator: std.mem.Allocator) bun.JSError!?JSAst.Expr { diff --git a/test/js/bun/transpiler/transpiler-transform-error-uaf.test.ts b/test/js/bun/transpiler/transpiler-transform-error-uaf.test.ts new file mode 100644 index 00000000000..5cab86b0f4a --- /dev/null +++ b/test/js/bun/transpiler/transpiler-transform-error-uaf.test.ts @@ -0,0 +1,29 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe } from "harness"; + +test("async transform() rejecting with parse errors does not read freed memory", async () => { + const script = ` + const transpiler = new Bun.Transpiler(); + const bad = Buffer.alloc(1000, "const a = 1;\\n").toString() + "const x = ;"; + const results = await Promise.all( + Array.from({ length: 20 }, () => transpiler.transform(bad).then(() => null, e => e)), + ); + for (const e of results) { + if (!(e instanceof Error)) throw new Error("expected Error, got " + e); + const text = e.errors ? e.errors.map(String).join("\\n") : String(e); + if (!/already been declared|Unexpected/.test(text)) throw new Error("bad message: " + text); + } + console.log("ok"); + `; + + const { exitCode, stdout, stderr, signalCode } = Bun.spawnSync({ + cmd: [bunExe(), "-e", script], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const out = stdout.toString(); + const err = stderr.toString(); + expect({ exitCode, signalCode, out, err }).toEqual({ exitCode: 0, signalCode: undefined, out: "ok\n", err: "" }); +});