From 664c080d026fc29271fd3bc30d8300ee548fe27a Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 31 Oct 2024 14:26:19 -0700 Subject: [PATCH] Fixes #14918 (#14921) --- src/bun.js/webcore/blob.zig | 6 ++++-- test/js/web/html/FormData.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index de458d86c9..f5848a6e07 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -1542,7 +1542,7 @@ pub const Blob = struct { } } - this.reported_estimated_size = size + (this.content_type.len * @intFromBool(this.content_type_allocated)); + this.reported_estimated_size = size + (this.content_type.len * @intFromBool(this.content_type_allocated)) + this.name.byteSlice().len; } pub fn estimatedSize(this: *Blob) usize { @@ -3784,7 +3784,7 @@ pub const Blob = struct { return true; } if (value.isString()) { - this.name.deref(); + const old_name = this.name; this.name = bun.String.tryFromJS(value, globalThis) orelse { // Handle allocation failure. @@ -3793,6 +3793,7 @@ pub const Blob = struct { }; // We don't need to increment the reference count since tryFromJS already did it. Blob.nameSetCached(jsThis, globalThis, value); + old_name.deref(); return true; } return false; @@ -4181,6 +4182,7 @@ pub const Blob = struct { } else if (duped.content_type_allocated and duped.allocator != null and include_content_type) { duped.content_type = bun.default_allocator.dupe(u8, this.content_type) catch bun.outOfMemory(); } + duped.name = duped.name.dupeRef(); duped.allocator = null; return duped; diff --git a/test/js/web/html/FormData.test.ts b/test/js/web/html/FormData.test.ts index 6adaee48b0..83f35cb1b4 100644 --- a/test/js/web/html/FormData.test.ts +++ b/test/js/web/html/FormData.test.ts @@ -620,4 +620,29 @@ describe("FormData", () => { expect(fileSlice.size).toBe(result.size); }); }); + + // The minimum repro for this was to not call the .name and .type getter on the Blob + // But the crux of the issue is that we called dupe() on the Blob, without also incrementing the reference count of the name string. + // https://github.com/oven-sh/bun/issues/14918 + it("should increment reference count of the name string on Blob", async () => { + const buffer = new File([Buffer.from(Buffer.alloc(48 * 1024, "abcdefh").toString("base64"), "base64")], "ok.jpg"); + function test() { + let file = new File([buffer], "ok.jpg"); + file.name; + file.type; + + let formData = new FormData(); + formData.append("foo", file); + formData.get("foo"); + formData.get("foo")!.name; + formData.get("foo")!.type; + return formData; + } + for (let i = 0; i < 100000; i++) { + test(); + if (i % 5000 === 0) { + Bun.gc(); + } + } + }); });