diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index b5526b524b..6debf68af0 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -78,7 +78,7 @@ pub const StandaloneModuleGraph = struct { pub fn blob(this: *File, globalObject: *bun.JSC.JSGlobalObject) *bun.JSC.WebCore.Blob { if (this.blob_ == null) { - var store = bun.JSC.WebCore.Blob.Store.init(@constCast(this.contents), bun.default_allocator) catch bun.outOfMemory(); + var store = bun.JSC.WebCore.Blob.Store.init(@constCast(this.contents), bun.default_allocator); // make it never free store.ref(); diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig index 04d86bf989..d217fbcba9 100644 --- a/src/bun.js/api/html_rewriter.zig +++ b/src/bun.js/api/html_rewriter.zig @@ -49,7 +49,7 @@ pub const HTMLRewriter = struct { pub usingnamespace JSC.Codegen.JSHTMLRewriter; pub fn constructor(_: *JSGlobalObject, _: *JSC.CallFrame) callconv(.C) ?*HTMLRewriter { - const rewriter = bun.default_allocator.create(HTMLRewriter) catch unreachable; + const rewriter = bun.default_allocator.create(HTMLRewriter) catch bun.outOfMemory(); rewriter.* = HTMLRewriter{ .builder = LOLHTML.HTMLRewriter.Builder.init(), .context = LOLHTMLContext.new(.{}), @@ -65,12 +65,12 @@ pub const HTMLRewriter = struct { callFrame: *JSC.CallFrame, listener: JSValue, ) JSValue { - const selector_slice = std.fmt.allocPrint(bun.default_allocator, "{}", .{selector_name}) catch unreachable; + const selector_slice = std.fmt.allocPrint(bun.default_allocator, "{}", .{selector_name}) catch bun.outOfMemory(); var selector = LOLHTML.HTMLSelector.parse(selector_slice) catch return throwLOLHTMLError(global); const handler_ = ElementHandler.init(global, listener) catch return .zero; - const handler = getAllocator(global).create(ElementHandler) catch unreachable; + const handler = getAllocator(global).create(ElementHandler) catch bun.outOfMemory(); handler.* = handler_; this.builder.addElementContentHandlers( @@ -101,8 +101,8 @@ pub const HTMLRewriter = struct { return throwLOLHTMLError(global); }; - this.context.selectors.append(bun.default_allocator, selector) catch unreachable; - this.context.element_handlers.append(bun.default_allocator, handler) catch unreachable; + this.context.selectors.append(bun.default_allocator, selector) catch bun.outOfMemory(); + this.context.element_handlers.append(bun.default_allocator, handler) catch bun.outOfMemory(); return callFrame.this(); } @@ -114,7 +114,7 @@ pub const HTMLRewriter = struct { ) JSValue { const handler_ = DocumentHandler.init(global, listener) catch return .zero; - const handler = getAllocator(global).create(DocumentHandler) catch unreachable; + const handler = getAllocator(global).create(DocumentHandler) catch bun.outOfMemory(); handler.* = handler_; // If this fails, subsequent calls to write or end should throw @@ -148,7 +148,7 @@ pub const HTMLRewriter = struct { null, ); - this.context.document_handlers.append(bun.default_allocator, handler) catch unreachable; + this.context.document_handlers.append(bun.default_allocator, handler) catch bun.outOfMemory(); return callFrame.this(); } @@ -341,7 +341,7 @@ pub const HTMLRewriter = struct { return bun.sys.Error{ .errno = 1, // TODO: make this a union - .path = bun.default_allocator.dupe(u8, LOLHTML.HTMLString.lastError().slice()) catch unreachable, + .path = bun.default_allocator.dupe(u8, LOLHTML.HTMLString.lastError().slice()) catch bun.outOfMemory(), }; }; if (comptime deinit_) bytes.listManaged(bun.default_allocator).deinit(); @@ -536,7 +536,7 @@ pub const HTMLRewriter = struct { bytes: []const u8, is_async: bool, ) ?JSValue { - sink.bytes.growBy(bytes.len) catch unreachable; + sink.bytes.growBy(bytes.len) catch bun.outOfMemory(); const global = sink.global; var response = sink.response; @@ -589,11 +589,12 @@ pub const HTMLRewriter = struct { prev_value.resolve( &this.response.body.value, this.global, + null, ); } pub fn write(this: *BufferOutputSink, bytes: []const u8) void { - this.bytes.append(bytes) catch unreachable; + this.bytes.append(bytes) catch bun.outOfMemory(); } pub fn deinit(this: *BufferOutputSink) void { @@ -875,7 +876,7 @@ fn HandlerCallback( return struct { pub fn callback(this: *HandlerType, value: *LOLHTMLType) bool { JSC.markBinding(@src()); - var zig_element = bun.default_allocator.create(ZigType) catch unreachable; + var zig_element = bun.default_allocator.create(ZigType) catch bun.outOfMemory(); @field(zig_element, field_name) = value; defer @field(zig_element, field_name) = null; @@ -1495,7 +1496,7 @@ pub const Element = struct { return ZigString.init("Expected a function").withEncoding().toValueGC(globalObject); } - const end_tag_handler = bun.default_allocator.create(EndTag.Handler) catch unreachable; + const end_tag_handler = bun.default_allocator.create(EndTag.Handler) catch bun.outOfMemory(); end_tag_handler.* = .{ .global = globalObject, .callback = function }; this.element.?.onEndTag(EndTag.Handler.onEndTagHandler, end_tag_handler) catch { @@ -1760,7 +1761,7 @@ pub const Element = struct { return JSValue.jsUndefined(); const iter = this.element.?.attributes() orelse return throwLOLHTMLError(globalObject); - var attr_iter = bun.default_allocator.create(AttributeIterator) catch unreachable; + var attr_iter = bun.default_allocator.create(AttributeIterator) catch bun.outOfMemory(); attr_iter.* = .{ .iterator = iter }; var js_attr_iter = attr_iter.toJS(globalObject); js_attr_iter.protect(); diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index e7085e0ea4..1ab7f76494 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -3515,7 +3515,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp loop.enter(); defer loop.exit(); - old.resolve(&body.value, this.server.globalThis); + old.resolve(&body.value, this.server.globalThis, null); } return; } @@ -3567,7 +3567,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp var old = body.value; old.Locked.onReceiveValue = null; var new_body = .{ .Null = {} }; - old.resolve(&new_body, this.server.globalThis); + old.resolve(&new_body, this.server.globalThis, null); body.value = new_body; } } else { @@ -5788,6 +5788,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp if (listener.socket().localAddressText(&buf, &is_ipv6)) |slice| { var ip = bun.String.createUTF8(slice); + defer ip.deref(); return JSSocketAddress__create( this.globalThis, ip.toJS(this.globalThis), @@ -5834,6 +5835,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp defer default_allocator.free(buf); var value = bun.String.createUTF8(buf); + defer value.deref(); return value.toJSDOMURL(globalThis); } diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index 3d153d0c05..0c2dba92d6 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -5458,7 +5458,9 @@ pub const ExpectMatcherUtils = struct { try buffered_writer.flush(); - return bun.String.createUTF8(mutable_string.toOwnedSlice()).toJS(globalThis); + const str = bun.String.createUTF8(mutable_string.toOwnedSlice()); + defer str.deref(); + return str.toJS(globalThis); } inline fn printValueCatched(globalThis: *JSC.JSGlobalObject, value: JSValue, comptime color_or_null: ?[]const u8) JSValue { diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index e6aa8cecd5..1d99226fa1 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -109,6 +109,10 @@ pub const Blob = struct { globalThis: *JSGlobalObject = undefined, last_modified: f64 = 0.0, + /// Blob name will lazy initialize when getName is called, but + /// we must be able to set the name, and we need to keep the value alive + /// https://github.com/oven-sh/bun/issues/10178 + name: bun.String = bun.String.dead, /// Max int of double precision /// 9 petabytes is probably enough for awhile @@ -127,7 +131,7 @@ pub const Blob = struct { var content_type_slice: ZigString.Slice = this.getContentType() orelse return null; defer content_type_slice.deinit(); const encoding = bun.FormData.Encoding.get(content_type_slice.slice()) orelse return null; - return bun.FormData.AsyncFormData.init(this.allocator orelse bun.default_allocator, encoding) catch unreachable; + return bun.FormData.AsyncFormData.init(this.allocator orelse bun.default_allocator, encoding) catch bun.outOfMemory(); } pub fn hasContentTypeFromUser(this: *const Blob) bool { @@ -171,8 +175,8 @@ pub const Blob = struct { *Handler, handler, Handler.run, - ) catch unreachable; - var read_file_task = ReadFile.ReadFileTask.createOnJSThread(bun.default_allocator, global, file_read) catch unreachable; + ) catch bun.outOfMemory(); + var read_file_task = ReadFile.ReadFileTask.createOnJSThread(bun.default_allocator, global, file_read) catch bun.outOfMemory(); // Create the Promise only after the store has been ref()'d. // The garbage collector runs on memory allocations @@ -209,8 +213,8 @@ pub const Blob = struct { NewInternalReadFileHandler(Handler, Function).run, this.offset, this.size, - ) catch unreachable; - var read_file_task = ReadFile.ReadFileTask.createOnJSThread(bun.default_allocator, global, file_read) catch unreachable; + ) catch bun.outOfMemory(); + var read_file_task = ReadFile.ReadFileTask.createOnJSThread(bun.default_allocator, global, file_read) catch bun.outOfMemory(); read_file_task.schedule(); } @@ -524,7 +528,7 @@ pub const Blob = struct { buf: []u8 = "", globalThis: *JSC.JSGlobalObject, pub fn convert(this: *URLSearchParamsConverter, str: ZigString) void { - var out = str.toSlice(this.allocator).cloneIfNeeded(this.allocator) catch unreachable; + var out = str.toSlice(this.allocator).cloneIfNeeded(this.allocator) catch bun.outOfMemory(); this.buf = @constCast(out.slice()); } }; @@ -539,7 +543,7 @@ pub const Blob = struct { .globalThis = globalThis, }; search_params.toString(URLSearchParamsConverter, &converter, URLSearchParamsConverter.convert); - var store = Blob.Store.init(converter.buf, allocator) catch unreachable; + var store = Blob.Store.init(converter.buf, allocator); store.mime_type = MimeType.all.@"application/x-www-form-urlencoded"; var blob = Blob.initWithStore(store, globalThis); @@ -581,9 +585,9 @@ pub const Blob = struct { context.joiner.pushStatic(boundary); context.joiner.pushStatic("--\r\n"); - const store = Blob.Store.init(context.joiner.done(allocator) catch unreachable, allocator) catch unreachable; + const store = Blob.Store.init(context.joiner.done(allocator) catch bun.outOfMemory(), allocator); var blob = Blob.initWithStore(store, globalThis); - blob.content_type = std.fmt.allocPrint(allocator, "multipart/form-data; boundary=\"{s}\"", .{boundary}) catch unreachable; + blob.content_type = std.fmt.allocPrint(allocator, "multipart/form-data; boundary=\"{s}\"", .{boundary}) catch bun.outOfMemory(); blob.content_type_allocated = true; blob.content_type_was_set = true; @@ -854,7 +858,7 @@ pub const Blob = struct { WriteFilePromise.run, mkdirp_if_not_exists, ) catch unreachable; - var task = WriteFile.WriteFileTask.createOnJSThread(bun.default_allocator, ctx.ptr(), file_copier) catch unreachable; + var task = WriteFile.WriteFileTask.createOnJSThread(bun.default_allocator, ctx.ptr(), file_copier) catch bun.outOfMemory(); // Defer promise creation until we're just about to schedule the task var promise = JSC.JSPromise.create(ctx.ptr()); const promise_value = promise.asValue(ctx); @@ -1440,7 +1444,7 @@ pub const Blob = struct { blob.content_type = mime.value; break :inner; } - const content_type_buf = allocator.alloc(u8, slice.len) catch unreachable; + const content_type_buf = allocator.alloc(u8, slice.len) catch bun.outOfMemory(); blob.content_type = strings.copyLowercase(slice, content_type_buf); blob.content_type_allocated = true; } @@ -1546,7 +1550,7 @@ pub const Blob = struct { blob.content_type = entry.value; break :inner; } - const content_type_buf = allocator.alloc(u8, slice.len) catch unreachable; + const content_type_buf = allocator.alloc(u8, slice.len) catch bun.outOfMemory(); blob.content_type = strings.copyLowercase(slice, content_type_buf); blob.content_type_allocated = true; } @@ -1621,7 +1625,7 @@ pub const Blob = struct { } }; - return Blob.initWithStore(Blob.Store.initFile(path, null, allocator) catch unreachable, globalThis); + return Blob.initWithStore(Blob.Store.initFile(path, null, allocator) catch bun.outOfMemory(), globalThis); } pub const Store = struct { @@ -1686,7 +1690,7 @@ pub const Blob = struct { return store; } - pub fn init(bytes: []u8, allocator: std.mem.Allocator) !*Store { + pub fn init(bytes: []u8, allocator: std.mem.Allocator) *Store { const store = Blob.Store.new(.{ .data = .{ .bytes = ByteStore.init(bytes, allocator), @@ -2254,7 +2258,7 @@ pub const Blob = struct { }); store.ref(); source_store.ref(); - return try CopyFilePromiseTask.createOnJSThread(allocator, globalThis, read_file); + return CopyFilePromiseTask.createOnJSThread(allocator, globalThis, read_file) catch bun.outOfMemory(); } const linux = std.os.linux; @@ -3107,7 +3111,7 @@ pub const Blob = struct { store.data.file.pathlike.path.slice(), ).clone( globalThis.allocator(), - ) catch unreachable, + ) catch bun.outOfMemory(), }; } }; @@ -3222,7 +3226,7 @@ pub const Blob = struct { } content_type_was_allocated = slice.len > 0; - const content_type_buf = allocator.alloc(u8, slice.len) catch unreachable; + const content_type_buf = allocator.alloc(u8, slice.len) catch bun.outOfMemory(); content_type = strings.copyLowercase(slice, content_type_buf); } } @@ -3291,16 +3295,43 @@ pub const Blob = struct { // TODO: Move this to a separate `File` object or BunFile pub fn getName( this: *Blob, + _: JSC.JSValue, globalThis: *JSC.JSGlobalObject, ) callconv(.C) JSValue { + if (this.name.tag != .Dead) return this.name.toJS(globalThis); + if (this.getFileName()) |path| { var str = bun.String.createUTF8(path); + this.name = str; return str.toJS(globalThis); } return JSValue.undefined; } + pub fn setName( + this: *Blob, + jsThis: JSC.JSValue, + globalThis: *JSC.JSGlobalObject, + value: JSValue, + ) callconv(.C) bool { + // by default we don't have a name so lets allow it to be set undefined + if (value.isEmptyOrUndefinedOrNull()) { + this.name.deref(); + this.name = bun.String.dead; + Blob.nameSetCached(jsThis, globalThis, value); + return true; + } + if (value.isString()) { + this.name.deref(); + this.name = bun.String.tryFromJS(value, globalThis) orelse return false; + this.name.ref(); + Blob.nameSetCached(jsThis, globalThis, value); + return true; + } + return false; + } + pub fn getFileName( this: *const Blob, ) ?[]const u8 { @@ -3501,7 +3532,7 @@ pub const Blob = struct { blob.content_type = mime.value; break :inner; } - const content_type_buf = allocator.alloc(u8, slice.len) catch unreachable; + const content_type_buf = allocator.alloc(u8, slice.len) catch bun.outOfMemory(); blob.content_type = strings.copyLowercase(slice, content_type_buf); blob.content_type_allocated = true; } @@ -3532,7 +3563,7 @@ pub const Blob = struct { // avoid allocating a Blob.Store if the buffer is actually empty var store: ?*Blob.Store = null; if (bytes.len > 0) { - store = Blob.Store.init(bytes, allocator) catch unreachable; + store = Blob.Store.init(bytes, allocator); store.?.is_all_ascii = is_all_ascii; } return Blob{ @@ -3549,7 +3580,7 @@ pub const Blob = struct { return Blob{ .size = @as(SizeType, @truncate(bytes.len)), .store = if (bytes.len > 0) - Blob.Store.init(bytes, allocator) catch unreachable + Blob.Store.init(bytes, allocator) else null, .allocator = null, @@ -3567,7 +3598,7 @@ pub const Blob = struct { return Blob{ .size = @as(SizeType, @truncate(bytes.len)), .store = if (bytes.len > 0) - Blob.Store.init(bytes, allocator) catch unreachable + Blob.Store.init(bytes, allocator) else null, .allocator = null, @@ -3699,6 +3730,8 @@ pub const Blob = struct { pub fn deinit(this: *Blob) void { this.detach(); + this.name.deref(); + this.name = bun.String.dead; // TODO: remove this field, make it a boolean. if (this.allocator) |alloc| { diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig index 87f5cfdf09..632bce1609 100644 --- a/src/bun.js/webcore/body.zig +++ b/src/bun.js/webcore/body.zig @@ -617,7 +617,12 @@ pub const Body = struct { }; } - pub fn resolve(to_resolve: *Value, new: *Value, global: *JSGlobalObject) void { + pub fn resolve( + to_resolve: *Value, + new: *Value, + global: *JSGlobalObject, + headers: ?*FetchHeaders, + ) void { log("resolve", .{}); if (to_resolve.* == .Locked) { var locked = &to_resolve.Locked; @@ -683,9 +688,29 @@ pub const Body = struct { async_form_data.toJS(global, blob.slice(), promise); }, else => { - var ptr = Blob.new(new.use()); - ptr.allocator = bun.default_allocator; - promise.resolve(global, ptr.toJS(global)); + var blob = Blob.new(new.use()); + blob.allocator = bun.default_allocator; + if (headers) |fetch_headers| { + if (fetch_headers.fastGet(.ContentType)) |content_type| { + var content_slice = content_type.toSlice(bun.default_allocator); + defer content_slice.deinit(); + var allocated = false; + const mimeType = MimeType.init(content_slice.slice(), bun.default_allocator, &allocated); + blob.content_type = mimeType.value; + blob.content_type_allocated = allocated; + blob.content_type_was_set = true; + if (blob.store != null) { + blob.store.?.mime_type = mimeType; + } + } + } + if (!blob.content_type_was_set and blob.store != null) { + blob.content_type = MimeType.text.value; + blob.content_type_allocated = false; + blob.content_type_was_set = true; + blob.store.?.mime_type = MimeType.text; + } + promise.resolve(global, blob.toJS(global)); }, } JSC.C.JSValueUnprotect(global, promise_.asObjectRef()); @@ -1162,26 +1187,36 @@ pub fn BodyMixin(comptime Type: type) type { } if (value.* == .Locked) { - if (value.Locked.promise != null) { - return handleBodyAlreadyUsed(globalObject); + if (value.Locked.promise == null or value.Locked.promise.?.isEmptyOrUndefinedOrNull()) { + return value.Locked.setPromise(globalObject, .{ .getBlob = {} }); } - - return value.Locked.setPromise(globalObject, .{ .getBlob = {} }); + return handleBodyAlreadyUsed(globalObject); } var blob = Blob.new(value.use()); blob.allocator = getAllocator(globalObject); - - if (blob.content_type.len == 0 and blob.store != null) { + if (blob.content_type.len == 0) { if (this.getFetchHeaders()) |fetch_headers| { if (fetch_headers.fastGet(.ContentType)) |content_type| { - blob.store.?.mime_type = MimeType.init(content_type.slice(), null, null); + var content_slice = content_type.toSlice(blob.allocator.?); + defer content_slice.deinit(); + var allocated = false; + const mimeType = MimeType.init(content_slice.slice(), blob.allocator.?, &allocated); + blob.content_type = mimeType.value; + blob.content_type_allocated = allocated; + blob.content_type_was_set = true; + if (blob.store != null) { + blob.store.?.mime_type = mimeType; + } } - } else { + } + if (!blob.content_type_was_set and blob.store != null) { + blob.content_type = MimeType.text.value; + blob.content_type_allocated = false; + blob.content_type_was_set = true; blob.store.?.mime_type = MimeType.text; } } - return JSC.JSPromise.resolvedPromiseValue(globalObject, blob.toJS(globalObject)); } }; diff --git a/src/bun.js/webcore/response.classes.ts b/src/bun.js/webcore/response.classes.ts index 96f92121a4..157f0abc38 100644 --- a/src/bun.js/webcore/response.classes.ts +++ b/src/bun.js/webcore/response.classes.ts @@ -152,8 +152,10 @@ export default [ // TODO: Move this to a separate `File` object or BunFile // This is *not* spec-compliant. name: { - getter: "getName", + this: true, cache: true, + getter: "getName", + setter: "setName", }, // TODO: Move this to a separate `File` object or BunFile diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index e4666692b6..931cf31ec1 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -87,7 +87,7 @@ pub const Response = struct { var content_type_slice: ZigString.Slice = this.getContentType() orelse return null; defer content_type_slice.deinit(); const encoding = bun.FormData.Encoding.get(content_type_slice.slice()) orelse return null; - return bun.FormData.AsyncFormData.init(bun.default_allocator, encoding) catch unreachable; + return bun.FormData.AsyncFormData.init(bun.default_allocator, encoding) catch bun.outOfMemory(); } pub fn estimatedSize(this: *Response) callconv(.C) usize { @@ -146,38 +146,38 @@ pub const Response = struct { try formatter.writeIndent(Writer, writer); try writer.writeAll(comptime Output.prettyFmt("ok: ", enable_ansi_colors)); formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.isOK()), .BooleanObject, enable_ansi_colors); - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; + formatter.printComma(Writer, writer, enable_ansi_colors) catch bun.outOfMemory(); try writer.writeAll("\n"); try formatter.writeIndent(Writer, writer); try writer.writeAll(comptime Output.prettyFmt("url: \"", enable_ansi_colors)); try writer.print(comptime Output.prettyFmt("{}", enable_ansi_colors), .{this.url}); try writer.writeAll("\""); - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; + formatter.printComma(Writer, writer, enable_ansi_colors) catch bun.outOfMemory(); try writer.writeAll("\n"); try formatter.writeIndent(Writer, writer); try writer.writeAll(comptime Output.prettyFmt("status: ", enable_ansi_colors)); formatter.printAs(.Double, Writer, writer, JSC.JSValue.jsNumber(this.init.status_code), .NumberObject, enable_ansi_colors); - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; + formatter.printComma(Writer, writer, enable_ansi_colors) catch bun.outOfMemory(); try writer.writeAll("\n"); try formatter.writeIndent(Writer, writer); try writer.writeAll(comptime Output.prettyFmt("statusText: ", enable_ansi_colors)); try writer.print(comptime Output.prettyFmt("\"{}\"", enable_ansi_colors), .{this.init.status_text}); - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; + formatter.printComma(Writer, writer, enable_ansi_colors) catch bun.outOfMemory(); try writer.writeAll("\n"); try formatter.writeIndent(Writer, writer); try writer.writeAll(comptime Output.prettyFmt("headers: ", enable_ansi_colors)); formatter.printAs(.Private, Writer, writer, this.getHeaders(formatter.globalThis), .DOMWrapper, enable_ansi_colors); - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; + formatter.printComma(Writer, writer, enable_ansi_colors) catch bun.outOfMemory(); try writer.writeAll("\n"); try formatter.writeIndent(Writer, writer); try writer.writeAll(comptime Output.prettyFmt("redirected: ", enable_ansi_colors)); formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.redirected), .BooleanObject, enable_ansi_colors); - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; + formatter.printComma(Writer, writer, enable_ansi_colors) catch bun.outOfMemory(); try writer.writeAll("\n"); formatter.resetLine(); @@ -1048,7 +1048,7 @@ pub const Fetch = struct { }; if (old == .Locked) { - old.resolve(&response.body.value, this.global_this); + old.resolve(&response.body.value, this.global_this, response.getFetchHeaders()); } } } @@ -1187,7 +1187,7 @@ pub const Fetch = struct { prom.reject(globalObject, res); } }; - var holder = bun.default_allocator.create(Holder) catch unreachable; + var holder = bun.default_allocator.create(Holder) catch bun.outOfMemory(); holder.* = .{ .held = JSC.Strong.create(result, globalThis), // we need the promise to be alive until the task is done @@ -1215,6 +1215,7 @@ pub const Fetch = struct { const globalObject = this.global_this; const js_cert = X509.toJS(x509, globalObject); var hostname: bun.String = bun.String.createUTF8(certificate_info.hostname); + defer hostname.deref(); const js_hostname = hostname.toJS(globalObject); js_hostname.ensureStillAlive(); js_cert.ensureStillAlive(); @@ -1923,7 +1924,7 @@ pub const Fetch = struct { if (first_arg.as(Request)) |request| { const can_use_fast_getters = first_arg.asDirect(Request) == request; const slow_getters: ?JSC.JSValue = if (can_use_fast_getters) null else first_arg; - request.ensureURL() catch unreachable; + request.ensureURL() catch bun.outOfMemory(); var url_str = request.url; var need_to_deinit_url_str = false; @@ -2020,30 +2021,30 @@ pub const Fetch = struct { if (hostname) |host| { allocator.free(host); } - hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; + hostname = _hostname.toOwnedSliceZ(allocator) catch bun.outOfMemory(); } - headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable; + headers = Headers.from(headers__, allocator, .{ .body = &body }) catch bun.outOfMemory(); // TODO: make this one pass } else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| { if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| { if (hostname) |host| { allocator.free(host); } - hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; + hostname = _hostname.toOwnedSliceZ(allocator) catch bun.outOfMemory(); } - headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable; + headers = Headers.from(headers__, allocator, .{ .body = &body }) catch bun.outOfMemory(); headers__.deref(); } else if (request.getFetchHeaders()) |head| { if (head.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| { if (hostname) |host| { allocator.free(host); } - hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; + hostname = _hostname.toOwnedSliceZ(allocator) catch bun.outOfMemory(); } - headers = Headers.from(head, allocator, .{ .body = &body }) catch unreachable; + headers = Headers.from(head, allocator, .{ .body = &body }) catch bun.outOfMemory(); } } else if (request.getFetchHeaders()) |head| { - headers = Headers.from(head, allocator, .{ .body = &body }) catch unreachable; + headers = Headers.from(head, allocator, .{ .body = &body }) catch bun.outOfMemory(); } if (options.get(ctx, "timeout")) |timeout_value| { @@ -2216,9 +2217,9 @@ pub const Fetch = struct { if (hostname) |host| { allocator.free(host); } - hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; + hostname = _hostname.toOwnedSliceZ(allocator) catch bun.outOfMemory(); } - headers = Headers.from(head, allocator, .{ .body = &body }) catch unreachable; + headers = Headers.from(head, allocator, .{ .body = &body }) catch bun.outOfMemory(); } // Creating headers can throw. @@ -2322,9 +2323,9 @@ pub const Fetch = struct { if (hostname) |host| { allocator.free(host); } - hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; + hostname = _hostname.toOwnedSliceZ(allocator) catch bun.outOfMemory(); } - headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable; + headers = Headers.from(headers__, allocator, .{ .body = &body }) catch bun.outOfMemory(); // TODO: make this one pass } else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| { defer headers__.deref(); @@ -2332,9 +2333,9 @@ pub const Fetch = struct { if (hostname) |host| { allocator.free(host); } - hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; + hostname = _hostname.toOwnedSliceZ(allocator) catch bun.outOfMemory(); } - headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable; + headers = Headers.from(headers__, allocator, .{ .body = &body }) catch bun.outOfMemory(); } else { // Converting the headers failed; return null and // let the set exception get thrown @@ -2598,7 +2599,7 @@ pub const Fetch = struct { null, allocator, .{ .body = &body }, - ) catch unreachable; + ) catch bun.outOfMemory(); } var http_body = FetchTasklet.HTTPRequestBody{ @@ -2793,9 +2794,9 @@ pub const Headers = struct { } break :brk false; }; - headers.entries.ensureTotalCapacity(allocator, header_count) catch unreachable; + headers.entries.ensureTotalCapacity(allocator, header_count) catch bun.outOfMemory(); headers.entries.len = header_count; - headers.buf.ensureTotalCapacityPrecise(allocator, buf_len) catch unreachable; + headers.buf.ensureTotalCapacityPrecise(allocator, buf_len) catch bun.outOfMemory(); headers.buf.items.len = buf_len; var sliced = headers.entries.slice(); var names = sliced.items(.name); diff --git a/src/http/mime_type.zig b/src/http/mime_type.zig index fe2ec086eb..a7cf32c5a9 100644 --- a/src/http/mime_type.zig +++ b/src/http/mime_type.zig @@ -143,7 +143,7 @@ pub fn init(str_: string, allocator: ?std.mem.Allocator, allocated: ?*bool) Mime if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch bun.outOfMemory() else str_, .category = .application, }; }, @@ -151,7 +151,7 @@ pub fn init(str_: string, allocator: ?std.mem.Allocator, allocated: ?*bool) Mime if (strings.eqlComptimeIgnoreLen(category_, "font")) { if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch bun.outOfMemory() else str_, .category = .font, }; } @@ -175,7 +175,7 @@ pub fn init(str_: string, allocator: ?std.mem.Allocator, allocated: ?*bool) Mime if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch bun.outOfMemory() else str_, .category = .text, }; } @@ -184,7 +184,7 @@ pub fn init(str_: string, allocator: ?std.mem.Allocator, allocated: ?*bool) Mime if (strings.eqlComptimeIgnoreLen(category_, "image")) { if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch bun.outOfMemory() else str_, .category = .image, }; } @@ -192,7 +192,7 @@ pub fn init(str_: string, allocator: ?std.mem.Allocator, allocated: ?*bool) Mime if (strings.eqlComptimeIgnoreLen(category_, "audio")) { if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch bun.outOfMemory() else str_, .category = .audio, }; } @@ -200,7 +200,7 @@ pub fn init(str_: string, allocator: ?std.mem.Allocator, allocated: ?*bool) Mime if (strings.eqlComptimeIgnoreLen(category_, "video")) { if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch bun.outOfMemory() else str_, .category = .video, }; } @@ -211,7 +211,7 @@ pub fn init(str_: string, allocator: ?std.mem.Allocator, allocated: ?*bool) Mime if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch bun.outOfMemory() else str_, .category = .other, }; } diff --git a/src/io/PipeReader.zig b/src/io/PipeReader.zig index 40f047f99e..b7b8ebfbdc 100644 --- a/src/io/PipeReader.zig +++ b/src/io/PipeReader.zig @@ -399,6 +399,7 @@ pub fn WindowsPipeReader( } var this: *This = bun.cast(*This, fs.data); fs.deinit(); + if (this.flags.is_done) return; switch (nread_int) { // 0 actually means EOF too diff --git a/test/js/web/fetch/blob.test.ts b/test/js/web/fetch/blob.test.ts index 58f398d78b..4ef2c53428 100644 --- a/test/js/web/fetch/blob.test.ts +++ b/test/js/web/fetch/blob.test.ts @@ -1,5 +1,6 @@ import { test, expect } from "bun:test"; - +import type { BinaryLike } from "node:crypto"; +import type { BlobOptions } from "node:buffer"; test("blob: imports have sourcemapped stacktraces", async () => { const blob = new Blob( [ @@ -128,3 +129,85 @@ test("blob: can be imported", async () => { await import(url); }).toThrow(); }); + +test("blob: can reliable get type from fetch #10072", async () => { + using server = Bun.serve({ + fetch() { + return new Response( + new ReadableStream({ + start(controller) { + controller.enqueue(Buffer.from("Hello")); + }, + async pull(controller) { + await Bun.sleep(100); + controller.enqueue(Buffer.from("World")); + await Bun.sleep(100); + controller.close(); + }, + }), + { + headers: { + "Content-Type": "plain/text", + }, + }, + ); + }, + }); + + const blob = await fetch(server.url).then(res => res.blob()); + expect(blob.type).toBe("plain/text"); +}); + +test("blob: can set name property #10178", () => { + const blob = new Blob([Buffer.from("Hello, World")]); + // @ts-expect-error + expect(blob.name).toBeUndefined(); + // @ts-expect-error + blob.name = "logo.svg"; + // @ts-expect-error + expect(blob.name).toBe("logo.svg"); + // @ts-expect-error + blob.name = 10; + // @ts-expect-error + expect(blob.name).toBe("logo.svg"); + Object.defineProperty(blob, "name", { + value: 42, + writable: false, + }); + // @ts-expect-error + expect(blob.name).toBe(42); + + class MyBlob extends Blob { + constructor(sources: Array, options?: BlobOptions) { + super(sources, options); + // @ts-expect-error + this.name = "logo.svg"; + } + } + + const myBlob = new MyBlob([Buffer.from("Hello, World")]); + // @ts-expect-error + expect(myBlob.name).toBe("logo.svg"); + // @ts-expect-error + myBlob.name = 10; + // @ts-expect-error + expect(myBlob.name).toBe("logo.svg"); + Object.defineProperty(myBlob, "name", { + value: 42, + writable: false, + }); + // @ts-expect-error + expect(myBlob.name).toBe(42); + + class MyOtherBlob extends Blob { + name: string | number; + constructor(sources: Array, options?: BlobOptions) { + super(sources, options); + this.name = "logo.svg"; + } + } + const myOtherBlob = new MyOtherBlob([Buffer.from("Hello, World")]); + expect(myOtherBlob.name).toBe("logo.svg"); + myOtherBlob.name = 10; + expect(myOtherBlob.name).toBe(10); +}); diff --git a/test/js/web/fetch/fetch-leak-test-fixture-4.js b/test/js/web/fetch/fetch-leak-test-fixture-4.js index bb24178be6..687424fa74 100644 --- a/test/js/web/fetch/fetch-leak-test-fixture-4.js +++ b/test/js/web/fetch/fetch-leak-test-fixture-4.js @@ -21,8 +21,8 @@ try { { Bun.gc(true); const stats = getHeapStats(); - expect(stats.Response || 0).toBeLessThanOrEqual(batch + 1); - expect(stats.Promise || 0).toBeLessThanOrEqual(batch + 2); + expect(stats.Response || 0).toBeLessThanOrEqual(batch + 5); + expect(stats.Promise || 0).toBeLessThanOrEqual(batch + 5); } } process.exit(0); diff --git a/test/js/web/streams/streams.test.js b/test/js/web/streams/streams.test.js index 740399230c..f057673fee 100644 --- a/test/js/web/streams/streams.test.js +++ b/test/js/web/streams/streams.test.js @@ -8,7 +8,7 @@ import { } from "bun"; import { expect, it, beforeEach, afterEach, describe, test } from "bun:test"; import { mkfifo } from "mkfifo"; -import { realpathSync, unlinkSync, writeFileSync } from "node:fs"; +import { realpathSync, unlinkSync, writeFileSync, createReadStream } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "os"; @@ -1042,3 +1042,14 @@ it("Bun.file().stream() read text from large file", async () => { unlinkSync(tmpfile); } }); + +it("fs.createReadStream(filename) should be able to break inside async loop", async () => { + for (let i = 0; i < 10; i++) { + const fileStream = createReadStream(join(import.meta.dir, "..", "fetch", "fixture.png")); + for await (const chunk of fileStream) { + expect(chunk).toBeDefined(); + break; + } + expect(true).toBe(true); + } +}); diff --git a/test/js/web/websocket/websocket.test.js b/test/js/web/websocket/websocket.test.js index 24442639fa..84815de355 100644 --- a/test/js/web/websocket/websocket.test.js +++ b/test/js/web/websocket/websocket.test.js @@ -1,7 +1,7 @@ import { describe, expect, it } from "bun:test"; import { readFileSync } from "fs"; +import { bunEnv, bunExe, gc, tls } from "harness"; import { join } from "path"; -import { tls, bunEnv, bunExe, gc } from "harness"; import process from "process"; const TEST_WEBSOCKET_HOST = process.env.TEST_WEBSOCKET_HOST || "wss://ws.postman-echo.com/raw";