From 4d74855fd7467b8463ffc902a75cac69ea41d800 Mon Sep 17 00:00:00 2001 From: 190n Date: Tue, 16 Jul 2024 16:31:07 -0700 Subject: [PATCH 001/123] Prevent unref from hanging on uninitialized dgram socket (#12585) Co-authored-by: Jarred Sumner --- src/bun.js/api/bun/udp_socket.zig | 10 ++++++---- src/js/node/dgram.ts | 11 ++++++++++- test/js/bun/udp/dgram-unref-hang-fixture.ts | 6 ++++++ test/js/bun/udp/dgram.test.ts | 7 +++++++ 4 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 test/js/bun/udp/dgram-unref-hang-fixture.ts diff --git a/src/bun.js/api/bun/udp_socket.zig b/src/bun.js/api/bun/udp_socket.zig index ea43d4947d..92000d6324 100644 --- a/src/bun.js/api/bun/udp_socket.zig +++ b/src/bun.js/api/bun/udp_socket.zig @@ -27,7 +27,7 @@ fn onClose(socket: *uws.udp.Socket) callconv(.C) void { const this: *UDPSocket = bun.cast(*UDPSocket, socket.user().?); this.closed = true; - this.poll_ref.unref(this.globalThis.bunVM()); + this.poll_ref.disable(); _ = this.js_refcount.fetchSub(1, .monotonic); } @@ -38,6 +38,10 @@ fn onDrain(socket: *uws.udp.Socket) callconv(.C) void { const callback = this.config.on_drain; if (callback == .zero) return; + const vm = JSC.VirtualMachine.get(); + const event_loop = vm.eventLoop(); + event_loop.enter(); + defer event_loop.exit(); const result = callback.callWithThis(this.globalThis, this.thisValue, &[_]JSValue{this.thisValue}); if (result.toError()) |err| { _ = this.callErrorHandler(.zero, &[_]JSValue{err}); @@ -575,9 +579,7 @@ pub const UDPSocket = struct { } pub fn unref(this: *This, globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue { - if (!this.closed) { - this.poll_ref.unref(globalThis.bunVM()); - } + this.poll_ref.unref(globalThis.bunVM()); return .undefined; } diff --git a/src/js/node/dgram.ts b/src/js/node/dgram.ts index b3fb856096..8762c82b0a 100644 --- a/src/js/node/dgram.ts +++ b/src/js/node/dgram.ts @@ -247,6 +247,7 @@ function Socket(type, listener) { ipv6Only: options && options.ipv6Only, recvBufferSize, sendBufferSize, + unrefOnBind: false, }; if (options?.signal !== undefined) { @@ -399,6 +400,10 @@ Socket.prototype.bind = function (port_, address_ /* , callback */) { }, }).$then( socket => { + if (state.unrefOnBind) { + socket.unref(); + state.unrefOnBind = false; + } state.handle.socket = socket; state.receiving = true; state.bindState = BIND_STATE_BOUND; @@ -934,7 +939,11 @@ Socket.prototype.ref = function () { Socket.prototype.unref = function () { const socket = this[kStateSymbol].handle?.socket; - if (socket) socket.unref(); + if (socket) { + socket.unref(); + } else { + this[kStateSymbol].unrefOnBind = true; + } return this; }; diff --git a/test/js/bun/udp/dgram-unref-hang-fixture.ts b/test/js/bun/udp/dgram-unref-hang-fixture.ts new file mode 100644 index 0000000000..0dcb614d24 --- /dev/null +++ b/test/js/bun/udp/dgram-unref-hang-fixture.ts @@ -0,0 +1,6 @@ +import dgram from "node:dgram"; +const socket = dgram.createSocket({ type: "udp4" }); +socket.unref(); +socket.send("test", 1337, "127.0.0.1", (error, bytes) => { + console.log(error, bytes); +}); diff --git a/test/js/bun/udp/dgram.test.ts b/test/js/bun/udp/dgram.test.ts index c6d486c3c6..5a9301322a 100644 --- a/test/js/bun/udp/dgram.test.ts +++ b/test/js/bun/udp/dgram.test.ts @@ -3,6 +3,7 @@ import { describe, test, expect, it } from "bun:test"; import { nodeDataCases } from "./testdata"; import { disableAggressiveGCScope } from "harness"; +import path from "path"; describe("createSocket()", () => { test("connect", done => { @@ -188,3 +189,9 @@ describe("createSocket()", () => { }); } }); + +describe("unref()", () => { + test("call before bind() does not hang", async () => { + expect([path.join(import.meta.dir, "dgram-unref-hang-fixture.ts")]).toRun(); + }); +}); From f05f13780e205b2c927deeebd884f1ffa2f927b9 Mon Sep 17 00:00:00 2001 From: Ciro Spaciari Date: Tue, 16 Jul 2024 16:33:35 -0700 Subject: [PATCH 002/123] fix(CryptoHasher) check of .empty/null/undefined in update (#12607) Co-authored-by: cirospaciari --- src/bun.js/api/BunObject.zig | 4 ++++ test/js/bun/util/bun-cryptohasher.test.ts | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 78ca8c367f..ce7a0f7b73 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -2772,6 +2772,10 @@ pub const Crypto = struct { const thisValue = callframe.this(); const arguments = callframe.arguments(2); const input = arguments.ptr[0]; + if (input.isEmptyOrUndefinedOrNull()) { + globalThis.throwInvalidArguments("expected blob, string or buffer", .{}); + return JSC.JSValue.zero; + } const encoding = arguments.ptr[1]; const buffer = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValue(globalThis, globalThis.bunVM().allocator, input, encoding) orelse { globalThis.throwInvalidArguments("expected blob, string or buffer", .{}); diff --git a/test/js/bun/util/bun-cryptohasher.test.ts b/test/js/bun/util/bun-cryptohasher.test.ts index 554dfef6fe..28af94e035 100644 --- a/test/js/bun/util/bun-cryptohasher.test.ts +++ b/test/js/bun/util/bun-cryptohasher.test.ts @@ -6,7 +6,14 @@ test("Bun.file in CryptoHasher is not supported yet", () => { expect(() => new Bun.CryptoHasher("sha1").update(Bun.file(import.meta.path))).toThrow(); expect(() => new Bun.SHA1().update(Bun.file(import.meta.path))).toThrow(); }); - +test("CryptoHasher update should throw when no parameter/null/undefined is passed", () => { + // @ts-expect-error + expect(() => new Bun.CryptoHasher("sha1").update()).toThrow(); + // @ts-expect-error + expect(() => new Bun.CryptoHasher("sha1").update(undefined)).toThrow(); + // @ts-expect-error + expect(() => new Bun.CryptoHasher("sha1").update(null)).toThrow(); +}); describe("Hash is consistent", () => { const sourceInputs = [ Buffer.from([ From ff0dc623143ef5de969331207133d19b2bb9beb9 Mon Sep 17 00:00:00 2001 From: 190n Date: Tue, 16 Jul 2024 16:37:21 -0700 Subject: [PATCH 003/123] Accept undefined as explicit second argument for path.*.basename (#12609) --- src/bun.js/node/path.zig | 2 +- test/js/node/path/basename.test.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bun.js/node/path.zig b/src/bun.js/node/path.zig index c3767f3c23..9987cbf379 100644 --- a/src/bun.js/node/path.zig +++ b/src/bun.js/node/path.zig @@ -453,7 +453,7 @@ pub inline fn basenameJS_T(comptime T: type, globalObject: *JSC.JSGlobalObject, } pub fn basename(globalObject: *JSC.JSGlobalObject, isWindows: bool, args_ptr: [*]JSC.JSValue, args_len: u16) callconv(JSC.conv) JSC.JSValue { - const suffix_ptr: ?JSC.JSValue = if (args_len > 1) args_ptr[1] else null; + const suffix_ptr: ?JSC.JSValue = if (args_len > 1 and args_ptr[1] != .undefined) args_ptr[1] else null; if (suffix_ptr) |_suffix_ptr| { // Supress exeption in zig. It does globalThis.vm().throwError() in JS land. diff --git a/test/js/node/path/basename.test.js b/test/js/node/path/basename.test.js index 7d53a9909c..c94cd91132 100644 --- a/test/js/node/path/basename.test.js +++ b/test/js/node/path/basename.test.js @@ -45,6 +45,7 @@ describe("path.dirname", () => { assert.strictEqual(path.win32.basename("basename.ext\\"), "basename.ext"); assert.strictEqual(path.win32.basename("basename.ext\\\\"), "basename.ext"); assert.strictEqual(path.win32.basename("foo"), "foo"); + assert.strictEqual(path.win32.basename("foo", undefined), "foo"); assert.strictEqual(path.win32.basename("aaa\\bbb", "\\bbb"), "bbb"); assert.strictEqual(path.win32.basename("aaa\\bbb", "a\\bbb"), "bbb"); assert.strictEqual(path.win32.basename("aaa\\bbb", "bbb"), "bbb"); @@ -72,6 +73,7 @@ describe("path.dirname", () => { assert.strictEqual(path.posix.basename("basename.ext\\"), "basename.ext\\"); assert.strictEqual(path.posix.basename("basename.ext\\\\"), "basename.ext\\\\"); assert.strictEqual(path.posix.basename("foo"), "foo"); + assert.strictEqual(path.posix.basename("foo", undefined), "foo"); }); test("posix with control characters", () => { From 37036f2eb003e6fe4b7384ded7412a393e5cb6b2 Mon Sep 17 00:00:00 2001 From: Ciro Spaciari Date: Tue, 16 Jul 2024 16:39:37 -0700 Subject: [PATCH 004/123] fix(serve) fix abrupt close when downloading data (#12581) Co-authored-by: cirospaciari --- src/bun.js/api/server.zig | 304 +++++++++++++--------------- src/bun.js/webcore/body.zig | 2 +- test/js/bun/http/bun-server.test.ts | 82 +++++++- 3 files changed, 226 insertions(+), 162 deletions(-) diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index c6bc8f1df7..78af6dac27 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -1283,7 +1283,6 @@ fn NewFlags(comptime debug_mode: bool) type { has_marked_pending: bool = false, has_abort_handler: bool = false, has_sendfile_ctx: bool = false, - has_pending_read: bool = false, has_called_error_handler: bool = false, needs_content_length: bool = false, needs_content_range: bool = false, @@ -1422,11 +1421,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp /// We can only safely free once the request body promise is finalized /// and the response is rejected response_jsvalue: JSC.JSValue = JSC.JSValue.zero, - pending_promises_for_abort: u8 = 0, + ref_count: u8 = 1, response_ptr: ?*JSC.WebCore.Response = null, blob: JSC.WebCore.AnyBlob = JSC.WebCore.AnyBlob{ .Blob = .{} }, - promise: ?*JSC.JSValue = null, sendfile: SendfileContext = undefined, request_body: ?*JSC.WebCore.BodyValueRef = null, @@ -1476,15 +1474,15 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const result = arguments.ptr[0]; result.ensureStillAlive(); - ctx.pending_promises_for_abort -|= 1; + defer ctx.deref(); if (ctx.isAbortedOrEnded()) { - ctx.finalizeForAbort(); + ctx.deref(); return JSValue.jsUndefined(); } if (ctx.didUpgradeWebSocket()) { - ctx.finalize(); + ctx.deref(); return JSValue.jsUndefined(); } @@ -1538,10 +1536,75 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp ctx.render(response); } - pub fn finalizeForAbort(this: *RequestContext) void { - streamLog("finalizeForAbort", .{}); - this.pending_promises_for_abort -|= 1; - if (this.pending_promises_for_abort == 0) this.finalize(); + pub fn shouldRenderMissing(this: *RequestContext) bool { + // If we did not respond yet, we should render missing + // To allow this all the conditions above should be true: + // 1 - still has a response (not detached) + // 2 - not aborted + // 3 - not marked completed + // 4 - not marked pending + // 5 - is the only reference of the context + // 6 - is not waiting for request body + // 7 - did not call sendfile + return this.resp != null and !this.flags.aborted and !this.flags.has_marked_complete and !this.flags.has_marked_pending and this.ref_count == 1 and !this.flags.is_waiting_for_request_body and !this.flags.has_sendfile_ctx; + } + + pub fn isDeadRequest(this: *RequestContext) bool { + // check if has pending promise or extra reference (aka not the only reference) + if (this.ref_count > 1) return false; + // check if the body is Locked (streaming) + if (this.request_body) |body| { + if (body.value == .Locked) { + return false; + } + } + + return true; + } + + /// destroy RequestContext, should be only called by deref or if defer_deinit_until_callback_completes is ref is set to true + fn deinit(this: *RequestContext) void { + this.detachResponse(); + // TODO: has_marked_complete is doing something? + this.flags.has_marked_complete = true; + + if (this.defer_deinit_until_callback_completes) |defer_deinit| { + defer_deinit.* = true; + ctxLog("deferred deinit ({*})", .{this}); + return; + } + + ctxLog("deinit ({*})", .{this}); + if (comptime Environment.allow_assert) + assert(this.flags.has_finalized); + + this.request_body_buf.clearAndFree(this.allocator); + this.response_buf_owned.clearAndFree(this.allocator); + + if (this.request_body) |body| { + _ = body.unref(); + this.request_body = null; + } + + const server = this.server; + server.request_pool_allocator.put(this); + server.onRequestComplete(); + } + + pub fn deref(this: *RequestContext) void { + streamLog("deref", .{}); + bun.assert(this.ref_count > 0); + const ref_count = this.ref_count; + this.ref_count -= 1; + if (ref_count == 1) { + this.finalizeWithoutDeinit(); + this.deinit(); + } + } + + pub fn ref(this: *RequestContext) void { + streamLog("ref", .{}); + this.ref_count += 1; } pub fn onReject(_: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSValue { @@ -1551,7 +1614,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp var ctx = arguments.ptr[1].asPromisePtr(@This()); const err = arguments.ptr[0]; - ctx.pending_promises_for_abort -|= 1; + defer ctx.deref(); handleReject(ctx, if (!err.isEmptyOrUndefinedOrNull()) err else JSC.JSValue.jsUndefined()); return JSValue.jsUndefined(); @@ -1559,7 +1622,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp fn handleReject(ctx: *RequestContext, value: JSC.JSValue) void { if (ctx.isAbortedOrEnded()) { - ctx.finalizeForAbort(); + ctx.deref(); return; } @@ -1582,13 +1645,13 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // check again in case it get aborted after runErrorHandler if (ctx.isAbortedOrEnded()) { - ctx.finalizeForAbort(); + ctx.deref(); return; } // I don't think this case happens? if (ctx.didUpgradeWebSocket()) { - ctx.finalize(); + ctx.deref(); return; } @@ -1602,7 +1665,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (ctx.resp) |resp| { resp.runCorkedWithType(*RequestContext, renderMissingCorked, ctx); } - ctx.finalize(); + ctx.deref(); } pub fn renderMissingCorked(ctx: *RequestContext) void { @@ -1721,7 +1784,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return; } } - this.finalize(); + this.deref(); } /// Drain a partial response buffer @@ -1739,40 +1802,27 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn end(this: *RequestContext, data: []const u8, closeConnection: bool) void { if (this.resp) |resp| { - if (this.flags.is_waiting_for_request_body) { - this.flags.is_waiting_for_request_body = false; - resp.clearOnData(); - } - resp.end(data, closeConnection); this.detachResponse(); + resp.end(data, closeConnection); } } pub fn endStream(this: *RequestContext, closeConnection: bool) void { ctxLog("endStream", .{}); if (this.resp) |resp| { - if (this.flags.is_waiting_for_request_body) { - this.flags.is_waiting_for_request_body = false; - resp.clearOnData(); - } - + this.detachResponse(); // This will send a terminating 0\r\n\r\n chunk to the client // We only want to do that if they're still expecting a body // We cannot call this function if the Content-Length header was previously set if (resp.state().isResponsePending()) resp.endStream(closeConnection); - this.detachResponse(); } } pub fn endWithoutBody(this: *RequestContext, closeConnection: bool) void { if (this.resp) |resp| { - if (this.flags.is_waiting_for_request_body) { - this.flags.is_waiting_for_request_body = false; - resp.clearOnData(); - } - resp.endWithoutBody(closeConnection); this.detachResponse(); + resp.endWithoutBody(closeConnection); } } @@ -1781,11 +1831,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp assert(this.resp == resp); if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return false; } this.end("", this.shouldCloseConnection()); - this.finalize(); + this.deref(); return false; } @@ -1795,7 +1845,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp assert(this.resp == resp); if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return false; } @@ -1805,7 +1855,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (this.method == .HEAD) { this.end("", this.shouldCloseConnection()); - this.finalize(); + this.deref(); return false; } @@ -1816,7 +1866,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp ctxLog("onWritableCompleteResponseBuffer", .{}); assert(this.resp == resp); if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return false; } return this.sendWritableBytesForCompleteResponseBuffer(this.response_buf_owned.items, write_offset, resp); @@ -1834,27 +1884,13 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp ctxLog("create ({*})", .{this}); } - pub fn isDeadRequest(this: *RequestContext) bool { - if (this.pending_promises_for_abort > 0 or this.flags.has_pending_read) return false; - - if (this.promise != null) { - return false; - } - - if (this.request_body) |body| { - if (body.value == .Locked) { - return false; - } - } - - return true; - } - pub fn onAbort(this: *RequestContext, resp: *App.Response) void { assert(this.resp == resp); assert(!this.flags.aborted); // mark request as aborted this.flags.aborted = true; + // we should not use the response anymore + this.resp = null; var any_js_calls = false; var vm = this.server.vm; defer { @@ -1886,46 +1922,33 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // if we can, free the request now. if (this.isDeadRequest()) { this.finalizeWithoutDeinit(); - this.deinit(); + this.deref(); } else { - this.pending_promises_for_abort = 0; + this.ref(); + defer this.deref(); // if we cannot, we have to reject pending promises // first, we reject the request body promise if (this.request_body) |body| { // User called .blob(), .json(), text(), or .arrayBuffer() on the Request object // but we received nothing or the connection was aborted - if (body.value == .Locked) { - // the promise is pending - if (body.value.Locked.action != .none or body.value.Locked.promise != null) { - this.pending_promises_for_abort += 1; - } else if (body.value.Locked.readable.get()) |readable| { - readable.abort(this.server.globalThis); - body.value.Locked.readable.deinit(); - any_js_calls = true; - } body.value.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis); + any_js_calls = true; } } if (this.response_ptr) |response| { if (response.body.value == .Locked) { - if (response.body.value.Locked.readable.get()) |readable| { - defer response.body.value.Locked.readable.deinit(); + var strong_readable = response.body.value.Locked.readable; + response.body.value.Locked.readable = .{}; + defer strong_readable.deinit(); + if (strong_readable.get()) |readable| { readable.abort(this.server.globalThis); any_js_calls = true; } } } - - // then, we reject the response promise - if (this.promise) |promise| { - this.pending_promises_for_abort += 1; - this.promise = null; - promise.asAnyPromise().?.reject(this.server.globalThis, JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis)); - any_js_calls = true; - } } } @@ -1975,16 +1998,6 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } } - if (this.promise) |promise| { - ctxLog("finalizeWithoutDeinit: this.promise != null", .{}); - this.promise = null; - - if (promise.asAnyPromise()) |prom| { - prom.rejectAsHandled(this.server.globalThis, (JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis))); - } - JSC.C.JSValueUnprotect(this.server.globalThis, promise.asObjectRef()); - } - if (this.byte_stream) |stream| { ctxLog("finalizeWithoutDeinit: stream != null", .{}); @@ -2007,48 +2020,6 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } } } - pub fn finalize(this: *RequestContext) void { - ctxLog("finalize ({*})", .{this}); - this.finalizeWithoutDeinit(); - this.deinit(); - } - - pub fn deinit(this: *RequestContext) void { - ctxLog("deinit ({*})", .{this}); - - if (!this.isDeadRequest()) { - ctxLog("deinit ({*}) waiting request", .{this}); - return; - } - - if (!this.flags.has_marked_complete) this.server.onRequestComplete(); - this.flags.has_marked_complete = true; - - this.detachResponse(); - - if (this.defer_deinit_until_callback_completes) |defer_deinit| { - defer_deinit.* = true; - ctxLog("deferred deinit ({*})", .{this}); - return; - } - - ctxLog("deinit ({*})", .{this}); - if (comptime Environment.allow_assert) - assert(this.flags.has_finalized); - - if (comptime Environment.allow_assert) - assert(this.flags.has_marked_complete); - - this.request_body_buf.clearAndFree(this.allocator); - this.response_buf_owned.clearAndFree(this.allocator); - - if (this.request_body) |body| { - _ = body.unref(); - this.request_body = null; - } - - this.server.request_pool_allocator.put(this); - } fn writeHeaders( this: *RequestContext, @@ -2086,7 +2057,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (this.sendfile.auto_close) _ = bun.sys.close(this.sendfile.fd); this.sendfile = undefined; - this.finalize(); + this.deref(); } const separator: string = "\r\n"; const separator_iovec = [1]std.posix.iovec_const{.{ @@ -2163,7 +2134,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp ctxLog("onWritableBytes", .{}); assert(this.resp == resp); if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return false; } @@ -2181,7 +2152,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const bytes = bytes_[@min(bytes_.len, @as(usize, @truncate(write_offset)))..]; if (resp.tryEnd(bytes, bytes_.len, this.shouldCloseConnection())) { - this.finalize(); + this.deref(); return true; } else { this.flags.has_marked_pending = true; @@ -2197,7 +2168,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const bytes = bytes_[@min(bytes_.len, @as(usize, @truncate(write_offset)))..]; if (resp.tryEnd(bytes, bytes_.len, this.shouldCloseConnection())) { this.response_buf_owned.items.len = 0; - this.finalize(); + this.deref(); } else { this.flags.has_marked_pending = true; resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); @@ -2331,7 +2302,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn doSendfile(this: *RequestContext, blob: Blob) void { if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return; } @@ -2344,15 +2315,15 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } this.setAbortHandler(); - this.flags.has_pending_read = true; + this.ref(); this.blob.Blob.doReadFileInternal(*RequestContext, this, onReadFile, this.server.globalThis); } pub fn onReadFile(this: *RequestContext, result: Blob.ReadFile.ResultType) void { - this.flags.has_pending_read = false; + defer this.deref(); if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return; } @@ -2405,7 +2376,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp fn renderWithBlobFromBodyValue(this: *RequestContext) void { if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return; } @@ -2434,7 +2405,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (this.isAbortedOrEnded()) { stream.cancel(this.server.globalThis); this.readable_stream_ref.deinit(); - this.finalizeForAbort(); + this.deref(); return; } const resp = this.resp.?; @@ -2502,7 +2473,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp stream.done(this.server.globalThis); this.readable_stream_ref.deinit(); this.endStream(this.shouldCloseConnection()); - this.finalize(); + this.deref(); return; } @@ -2524,7 +2495,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // TODO: should this timeout? this.setAbortHandler(); - this.pending_promises_for_abort += 1; + this.ref(); this.response_ptr.?.body.value = .{ .Locked = .{ .readable = JSC.WebCore.ReadableStream.Strong.init(stream, globalThis), @@ -2576,7 +2547,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp defer this.readable_stream_ref.deinit(); response_stream.sink.markDone(); - this.finalizeForAbort(); + this.deref(); response_stream.sink.onFirstWrite = null; response_stream.sink.finalize(); @@ -2645,7 +2616,20 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } fn detachResponse(this: *RequestContext) void { - this.resp = null; + if (this.resp) |resp| { + this.resp = null; + + // onAbort should have set this to null + bun.assert(!this.flags.aborted); + if (this.flags.is_waiting_for_request_body) { + this.flags.is_waiting_for_request_body = false; + resp.clearOnData(); + } + if (this.flags.has_abort_handler) { + resp.clearAborted(); + this.flags.has_abort_handler = false; + } + } } fn isAbortedOrEnded(this: *const RequestContext) bool { @@ -2678,7 +2662,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp ctx.drainMicrotasks(); if (ctx.isAbortedOrEnded()) { - ctx.finalizeForAbort(); + ctx.deref(); return; } @@ -2687,7 +2671,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // just ignore the Response object. It doesn't do anything. // it's better to do that than to throw an error if (ctx.didUpgradeWebSocket()) { - ctx.finalize(); + ctx.deref(); return; } @@ -2740,7 +2724,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // just ignore the Response object. It doesn't do anything. // it's better to do that than to throw an error if (ctx.didUpgradeWebSocket()) { - ctx.finalize(); + ctx.deref(); return; } @@ -2785,7 +2769,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } if (wait_for_promise) { - ctx.pending_promises_for_abort += 1; + ctx.ref(); response_value.then(this.globalThis, ctx, RequestContext.onResolve, RequestContext.onReject); return; } @@ -2820,7 +2804,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp streamLog("onResolve({any})", .{wrote_anything}); //aborted so call finalizeForAbort if (req.isAbortedOrEnded()) { - req.finalizeForAbort(); + req.deref(); return; } const resp = req.resp.?; @@ -2835,14 +2819,14 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp req.endStream(req.shouldCloseConnection()); } - req.finalize(); + req.deref(); } pub fn onResolveStream(_: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSValue { streamLog("onResolveStream", .{}); var args = callframe.arguments(2); var req: *@This() = args.ptr[args.len - 1].asPromisePtr(@This()); - req.pending_promises_for_abort -|= 1; + defer req.deref(); req.handleResolveStream(); return JSValue.jsUndefined(); } @@ -2850,7 +2834,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp streamLog("onRejectStream", .{}); const args = callframe.arguments(2); var req = args.ptr[args.len - 1].asPromisePtr(@This()); - req.pending_promises_for_abort -|= 1; + defer req.deref(); const err = args.ptr[0]; req.handleRejectStream(globalThis, err); return JSValue.jsUndefined(); @@ -2881,7 +2865,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // aborted so call finalizeForAbort if (req.isAbortedOrEnded()) { - req.finalizeForAbort(); + req.deref(); return; } @@ -2899,7 +2883,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp req.server.vm.runErrorHandler(err, &exception_list); } } - req.finalize(); + req.deref(); } pub fn doRenderWithBody(this: *RequestContext, value: *JSC.WebCore.Body.Value) void { @@ -2914,7 +2898,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const err = value.Error; _ = value.use(); if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return; } this.runErrorHandler(err); @@ -2932,7 +2916,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp }, .Locked => |*lock| { if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return; } @@ -3047,7 +3031,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return; } const resp = this.resp.?; @@ -3060,7 +3044,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (resp.write(chunk)) { if (stream.isDone()) { this.endStream(this.shouldCloseConnection()); - this.finalize(); + this.deref(); } } else { // when it's the last one, we just want to know if it's done @@ -3095,7 +3079,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp ctxLog("doRender", .{}); if (this.isAbortedOrEnded()) { - this.finalizeForAbort(); + this.deref(); return; } var response = this.response_ptr.?; @@ -3123,7 +3107,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp }, } } - this.finalize(); + this.deref(); } pub fn runErrorHandler( @@ -3281,7 +3265,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // Promise is not fulfilled yet { ctx.flags.is_error_promise_pending = true; - ctx.pending_promises_for_abort += 1; + ctx.ref(); promise_js.then( ctx.server.globalThis, ctx, @@ -3435,7 +3419,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } } - this.finalize(); + this.deref(); } pub fn render(this: *RequestContext, response: *JSC.WebCore.Response) void { @@ -6347,7 +6331,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp return; } - if (!ctx.flags.has_marked_complete and !ctx.flags.has_marked_pending and ctx.pending_promises_for_abort == 0 and !ctx.flags.is_waiting_for_request_body and !ctx.flags.has_sendfile_ctx) { + if (ctx.shouldRenderMissing()) { ctx.renderMissing(); return; } @@ -6416,7 +6400,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp return; } - if (!ctx.flags.has_marked_complete and !ctx.flags.has_marked_pending and ctx.pending_promises_for_abort == 0 and !ctx.flags.is_waiting_for_request_body and !ctx.flags.has_sendfile_ctx) { + if (ctx.shouldRenderMissing()) { ctx.renderMissing(); return; } diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig index 6bb0d6b831..e4ff382112 100644 --- a/src/bun.js/webcore/body.zig +++ b/src/bun.js/webcore/body.zig @@ -861,7 +861,7 @@ pub const Body = struct { error_instance.ensureStillAlive(); if (this.* == .Locked) { var locked = this.Locked; - + // will be unprotected by body value deinit error_instance.protect(); this.* = .{ .Error = error_instance }; diff --git a/test/js/bun/http/bun-server.test.ts b/test/js/bun/http/bun-server.test.ts index 4d4cc571ea..30318d15d7 100644 --- a/test/js/bun/http/bun-server.test.ts +++ b/test/js/bun/http/bun-server.test.ts @@ -1,4 +1,4 @@ -import type { ServerWebSocket, Server } from "bun"; +import type { ServerWebSocket, Server, Socket } from "bun"; import { describe, expect, test } from "bun:test"; import { bunExe, bunEnv, rejectUnauthorizedScope } from "harness"; import path from "path"; @@ -543,3 +543,83 @@ test("should be able to async upgrade using custom protocol", async () => { expect(await promise).toBe(true); }); + +test("should be able to abrubtly close a upload request", async () => { + const { promise, resolve } = Promise.withResolvers(); + using server = Bun.serve({ + port: 0, + hostname: "localhost", + maxRequestBodySize: 1024 * 1024 * 1024 * 16, + async fetch(req) { + let total_size = 0; + req.signal.addEventListener("abort", resolve); + + for await (const chunk of req.body as ReadableStream) { + total_size += chunk.length; + if (total_size > 1024 * 1024 * 1024) { + return new Response("too big", { status: 413 }); + } + } + + return new Response("Received " + total_size); + }, + }); + // ~100KB + const chunk = Buffer.alloc(1024 * 100, "a"); + // ~1GB + const MAX_PAYLOAD = 1024 * 1024 * 1024; + const request = Buffer.from( + `POST / HTTP/1.1\r\nHost: ${server.hostname}:${server.port}\r\nContent-Length: ${MAX_PAYLOAD}\r\n\r\n`, + ); + + type SocketInfo = { state: number; pending: Buffer | null }; + function tryWritePending(socket: Socket) { + if (socket.data.pending === null) { + // first write + socket.data.pending = request; + } + const data = socket.data.pending as Buffer; + const written = socket.write(data); + if (written < data.byteLength) { + // partial write + socket.data.pending = data.slice(0, written); + return false; + } + + // full write got to next state + if (socket.data.state === 0) { + // request sent -> send chunk + socket.data.pending = chunk; + } else { + // chunk sent -> delay shutdown + setTimeout(() => socket.shutdown(), 100); + } + socket.data.state++; + socket.flush(); + return true; + } + + function trySend(socket: Socket) { + while (socket.data.state < 2) { + if (!tryWritePending(socket)) { + return; + } + } + return; + } + await Bun.connect({ + hostname: server.hostname, + port: server.port, + data: { + state: 0, + pending: null, + } as SocketInfo, + socket: { + open: trySend, + drain: trySend, + data(socket, data) {}, + }, + }); + await promise; + expect().pass(); +}); From d703354fcde131e7671cc1fb3e755ba6540b40bd Mon Sep 17 00:00:00 2001 From: HibanaSama <48864283+HibanaSama@users.noreply.github.com> Date: Wed, 17 Jul 2024 05:50:47 +0500 Subject: [PATCH 005/123] docs: close details block (#12533) --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f3439f503..86d148847d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ If you are using Windows, please refer to [this guide](/docs/project/building-wi {% details summary="For Ubuntu users" %} TL;DR: Ubuntu 22.04 is suggested. Bun currently requires `glibc >=2.32` in development which means if you're on Ubuntu 20.04 (glibc == 2.31), you may likely meet `error: undefined symbol: __libc_single_threaded `. You need to take extra configurations. Also, according to this [issue](https://github.com/llvm/llvm-project/issues/97314), LLVM 16 is no longer maintained on Ubuntu 24.04 (noble). And instead, you might want `brew` to install LLVM 16 for your Ubuntu 24.04. +{% /details %} ## Install Dependencies From cabc0fa0e664dc73168e3463b9f8e2c29e0537e8 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 16 Jul 2024 19:39:27 -0700 Subject: [PATCH 006/123] fix typescript namespace merging with functions and classes (#12610) --- src/js_parser.zig | 14 +++-- test/bundler/bundler_edgecase.test.ts | 76 +++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index 47b46c2e52..55788e0ae5 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -11072,7 +11072,6 @@ fn NewParser_( inline .s_namespace, .s_enum => |ns| { if (ns.is_export) { if (p.ref_to_ts_namespace_member.get(ns.name.ref.?)) |member_data| { - bun.assert(member_data == .namespace); try exported_members.put( p.allocator, p.symbols.items[ns.name.ref.?.inner_index].original_name, @@ -11081,11 +11080,11 @@ fn NewParser_( .loc = ns.name.loc, }, ); - // try p.ref_to_ts_namespace_member.put( - // p.allocator, - // id.ref, - // member_data, - // ); + try p.ref_to_ts_namespace_member.put( + p.allocator, + ns.name.ref.?, + member_data, + ); } } }, @@ -11108,8 +11107,7 @@ fn NewParser_( // them entirely from the output. That can cause the namespace itself // to be considered empty and thus be removed. var import_equal_count: usize = 0; - const _stmts: []Stmt = stmts.items; - for (_stmts) |stmt| { + for (stmts.items) |stmt| { switch (stmt.data) { .s_local => |local| { if (local.was_ts_import_equals and !local.is_export) { diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 373b382c15..96c585af75 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -1354,6 +1354,82 @@ describe("bundler", () => { api.expectFile("/out.js").toBe(`import{a as c}from"external";\n`); }, }); + itBundled("edgecase/TypeScriptNamespaceSiblingFunction", { + files: { + "/entry.ts": ` + namespace X { + export function Y() { + return 2; + } + export namespace Y { + export const Z = 1; + } + } + console.log(X, X.Y(), X.Y.Z); + `, + }, + run: { + stdout: "{\n Y: [Function: Y],\n} 2 1", + }, + }); + itBundled("edgecase/TypeScriptNamespaceSiblingClass", { + files: { + "/entry.ts": ` + namespace X { + export class Y { + constructor(v) { + this.value = v; + } + + toJSON() { + return this.value; + } + } + export namespace Y { + export const Z = 1; + } + } + console.log(X, new X.Y(2).toJSON(), X.Y.Z); + `, + }, + run: { + stdout: "{\n Y: [class Y],\n} 2 1", + }, + }); + itBundled("edgecase/TypeScriptNamespaceSiblingEnum", { + files: { + "/entry.ts": ` + namespace X { + export enum Y { + A, + B, + } + export namespace Y { + export const Z = 1; + } + } + console.log(JSON.stringify([X, X.Y.A, X.Y.Z])); + `, + }, + run: { + stdout: '[{"Y":{"0":"A","1":"B","A":0,"B":1,"Z":1}},0,1]', + }, + }); + itBundled("edgecase/TypeScriptNamespaceSiblingVariable", { + files: { + "/entry.ts": ` + namespace X { + export let Y = {}; + export namespace Y { + export const Z = 1; + } + } + `, + }, + bundleErrors: { + "/entry.ts": [`"Y" has already been declared`], + }, + }); // TODO(@paperdave): test every case of this. I had already tested it manually, but it may break later const requireTranspilationListESM = [ From 866b30162672f93b56f912bf04936632b93093a3 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 16 Jul 2024 23:21:34 -0700 Subject: [PATCH 007/123] bundler: make `import()` calls visit the options object (#12617) --- src/fmt.zig | 333 +++++++++++--------------- src/js_ast.zig | 52 ++-- src/js_parser.zig | 168 ++++++------- src/js_printer.zig | 154 ++++++------ test/bundler/bundler_edgecase.test.ts | 13 + 5 files changed, 332 insertions(+), 388 deletions(-) diff --git a/src/fmt.zig b/src/fmt.zig index 8bb95896be..938469c277 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -534,140 +534,78 @@ pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { pub fn colorCode(this: Keyword) ColorCode { return switch (this) { - Keyword.abstract => ColorCode.blue, - Keyword.as => ColorCode.blue, - Keyword.@"async" => ColorCode.magenta, - Keyword.@"await" => ColorCode.magenta, - Keyword.case => ColorCode.magenta, - Keyword.@"catch" => ColorCode.magenta, - Keyword.class => ColorCode.magenta, - Keyword.@"const" => ColorCode.magenta, - Keyword.@"continue" => ColorCode.magenta, - Keyword.debugger => ColorCode.magenta, - Keyword.default => ColorCode.magenta, - Keyword.delete => ColorCode.red, - Keyword.do => ColorCode.magenta, - Keyword.@"else" => ColorCode.magenta, - Keyword.@"break" => ColorCode.magenta, - Keyword.undefined => ColorCode.orange, - Keyword.@"enum" => ColorCode.blue, - Keyword.@"export" => ColorCode.magenta, - Keyword.extends => ColorCode.magenta, - Keyword.false => ColorCode.orange, - Keyword.finally => ColorCode.magenta, - Keyword.@"for" => ColorCode.magenta, - Keyword.function => ColorCode.magenta, - Keyword.@"if" => ColorCode.magenta, - Keyword.implements => ColorCode.blue, - Keyword.import => ColorCode.magenta, - Keyword.in => ColorCode.magenta, - Keyword.instanceof => ColorCode.magenta, - Keyword.interface => ColorCode.blue, - Keyword.let => ColorCode.magenta, - Keyword.new => ColorCode.magenta, - Keyword.null => ColorCode.orange, - Keyword.package => ColorCode.magenta, - Keyword.private => ColorCode.blue, - Keyword.protected => ColorCode.blue, - Keyword.public => ColorCode.blue, - Keyword.@"return" => ColorCode.magenta, - Keyword.static => ColorCode.magenta, - Keyword.super => ColorCode.magenta, - Keyword.@"switch" => ColorCode.magenta, - Keyword.this => ColorCode.orange, - Keyword.throw => ColorCode.magenta, - Keyword.true => ColorCode.orange, - Keyword.@"try" => ColorCode.magenta, - Keyword.type => ColorCode.blue, - Keyword.typeof => ColorCode.magenta, - Keyword.@"var" => ColorCode.magenta, - Keyword.void => ColorCode.magenta, - Keyword.@"while" => ColorCode.magenta, - Keyword.with => ColorCode.magenta, - Keyword.yield => ColorCode.magenta, - Keyword.string => ColorCode.blue, - Keyword.number => ColorCode.blue, - Keyword.boolean => ColorCode.blue, - Keyword.symbol => ColorCode.blue, - Keyword.any => ColorCode.blue, - Keyword.object => ColorCode.blue, - Keyword.unknown => ColorCode.blue, - Keyword.never => ColorCode.blue, - Keyword.namespace => ColorCode.blue, - Keyword.declare => ColorCode.blue, - Keyword.readonly => ColorCode.blue, + .abstract => .blue, + .as => .blue, + .@"async" => .magenta, + .@"await" => .magenta, + .case => .magenta, + .@"catch" => .magenta, + .class => .magenta, + .@"const" => .magenta, + .@"continue" => .magenta, + .debugger => .magenta, + .default => .magenta, + .delete => .red, + .do => .magenta, + .@"else" => .magenta, + .@"break" => .magenta, + .undefined => .orange, + .@"enum" => .blue, + .@"export" => .magenta, + .extends => .magenta, + .false => .orange, + .finally => .magenta, + .@"for" => .magenta, + .function => .magenta, + .@"if" => .magenta, + .implements => .blue, + .import => .magenta, + .in => .magenta, + .instanceof => .magenta, + .interface => .blue, + .let => .magenta, + .new => .magenta, + .null => .orange, + .package => .magenta, + .private => .blue, + .protected => .blue, + .public => .blue, + .@"return" => .magenta, + .static => .magenta, + .super => .magenta, + .@"switch" => .magenta, + .this => .orange, + .throw => .magenta, + .true => .orange, + .@"try" => .magenta, + .type => .blue, + .typeof => .magenta, + .@"var" => .magenta, + .void => .magenta, + .@"while" => .magenta, + .with => .magenta, + .yield => .magenta, + .string => .blue, + .number => .blue, + .boolean => .blue, + .symbol => .blue, + .any => .blue, + .object => .blue, + .unknown => .blue, + .never => .blue, + .namespace => .blue, + .declare => .blue, + .readonly => .blue, }; } }; - pub const Keywords = ComptimeStringMap(Keyword, .{ - .{ "abstract", Keyword.abstract }, - .{ "any", Keyword.any }, - .{ "as", Keyword.as }, - .{ "async", Keyword.@"async" }, - .{ "await", Keyword.@"await" }, - .{ "boolean", Keyword.boolean }, - .{ "break", Keyword.@"break" }, - .{ "case", Keyword.case }, - .{ "catch", Keyword.@"catch" }, - .{ "class", Keyword.class }, - .{ "const", Keyword.@"const" }, - .{ "continue", Keyword.@"continue" }, - .{ "debugger", Keyword.debugger }, - .{ "declare", Keyword.declare }, - .{ "default", Keyword.default }, - .{ "delete", Keyword.delete }, - .{ "do", Keyword.do }, - .{ "else", Keyword.@"else" }, - .{ "enum", Keyword.@"enum" }, - .{ "export", Keyword.@"export" }, - .{ "extends", Keyword.extends }, - .{ "false", Keyword.false }, - .{ "finally", Keyword.finally }, - .{ "for", Keyword.@"for" }, - .{ "function", Keyword.function }, - .{ "if", Keyword.@"if" }, - .{ "implements", Keyword.implements }, - .{ "import", Keyword.import }, - .{ "in", Keyword.in }, - .{ "instanceof", Keyword.instanceof }, - .{ "interface", Keyword.interface }, - .{ "let", Keyword.let }, - .{ "namespace", Keyword.namespace }, - .{ "never", Keyword.never }, - .{ "new", Keyword.new }, - .{ "null", Keyword.null }, - .{ "number", Keyword.number }, - .{ "object", Keyword.object }, - .{ "package", Keyword.package }, - .{ "private", Keyword.private }, - .{ "protected", Keyword.protected }, - .{ "public", Keyword.public }, - .{ "readonly", Keyword.readonly }, - .{ "return", Keyword.@"return" }, - .{ "static", Keyword.static }, - .{ "string", Keyword.string }, - .{ "super", Keyword.super }, - .{ "switch", Keyword.@"switch" }, - .{ "symbol", Keyword.symbol }, - .{ "this", Keyword.this }, - .{ "throw", Keyword.throw }, - .{ "true", Keyword.true }, - .{ "try", Keyword.@"try" }, - .{ "type", Keyword.type }, - .{ "typeof", Keyword.typeof }, - .{ "undefined", Keyword.undefined }, - .{ "unknown", Keyword.unknown }, - .{ "var", Keyword.@"var" }, - .{ "void", Keyword.void }, - .{ "while", Keyword.@"while" }, - .{ "with", Keyword.with }, - .{ "yield", Keyword.yield }, - }); + pub const Keywords = bun.ComptimeEnumMap(Keyword); - pub fn format(this: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void { - const text = this.text; + pub fn format(this: @This(), comptime unused_fmt: []const u8, _: fmt.FormatOptions, writer: anytype) !void { + comptime bun.assert(unused_fmt.len == 0); + var text = this.text; if (this.limited) { if (!this.enable_colors or text.len > 2048 or text.len == 0 or !strings.isAllASCII(text)) { try writer.writeAll(text); @@ -675,22 +613,21 @@ pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { } } - var remain = text; var prev_keyword: ?Keyword = null; - outer: while (remain.len > 0) { - if (js_lexer.isIdentifierStart(remain[0])) { + outer: while (text.len > 0) { + if (js_lexer.isIdentifierStart(text[0])) { var i: usize = 1; - while (i < remain.len and js_lexer.isIdentifierContinue(remain[i])) { + while (i < text.len and js_lexer.isIdentifierContinue(text[i])) { i += 1; } - if (Keywords.get(remain[0..i])) |keyword| { + if (Keywords.get(text[0..i])) |keyword| { if (keyword != .as) prev_keyword = keyword; const code = keyword.colorCode(); - try writer.print(Output.prettyFmt("{s}{s}", true), .{ code.color(), remain[0..i] }); + try writer.print(Output.prettyFmt("{s}{s}", true), .{ code.color(), text[0..i] }); } else { write: { if (prev_keyword) |prev| { @@ -698,20 +635,20 @@ pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { .new => { prev_keyword = null; - if (i < remain.len and remain[i] == '(') { - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); + if (i < text.len and text[i] == '(') { + try writer.print(Output.prettyFmt("{s}", true), .{text[0..i]}); break :write; } }, .abstract, .namespace, .declare, .type, .interface => { - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); + try writer.print(Output.prettyFmt("{s}", true), .{text[0..i]}); prev_keyword = null; break :write; }, .import => { - if (strings.eqlComptime(remain[0..i], "from")) { + if (strings.eqlComptime(text[0..i], "from")) { const code = ColorCode.magenta; - try writer.print(Output.prettyFmt("{s}{s}", true), .{ code.color(), remain[0..i] }); + try writer.print(Output.prettyFmt("{s}{s}", true), .{ code.color(), text[0..i] }); prev_keyword = null; break :write; @@ -721,25 +658,25 @@ pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { } } - try writer.writeAll(remain[0..i]); + try writer.writeAll(text[0..i]); } } - remain = remain[i..]; + text = text[i..]; } else { - switch (remain[0]) { + switch (text[0]) { '0'...'9' => { prev_keyword = null; var i: usize = 1; - if (remain.len > 1 and remain[0] == '0' and remain[1] == 'x') { + if (text.len > 1 and text[0] == '0' and text[1] == 'x') { i += 1; - while (i < remain.len and switch (remain[i]) { + while (i < text.len and switch (text[i]) { '0'...'9', 'a'...'f', 'A'...'F' => true, else => false, }) { i += 1; } } else { - while (i < remain.len and switch (remain[i]) { + while (i < text.len and switch (text[i]) { '0'...'9', '.', 'e', 'E', 'x', 'X', 'b', 'B', 'o', 'O' => true, else => false, }) { @@ -747,30 +684,30 @@ pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { } } - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; + try writer.print(Output.prettyFmt("{s}", true), .{text[0..i]}); + text = text[i..]; }, inline '`', '"', '\'' => |char| { prev_keyword = null; var i: usize = 1; - while (i < remain.len and remain[i] != char) { - if (comptime char == '`') { - if (remain[i] == '$' and i + 1 < remain.len and remain[i + 1] == '{') { + while (i < text.len and text[i] != char) { + if (char == '`') { + if (text[i] == '$' and i + 1 < text.len and text[i + 1] == '{') { const curly_start = i; i += 2; - while (i < remain.len and remain[i] != '}') { - if (remain[i] == '\\') { + while (i < text.len and text[i] != '}') { + if (text[i] == '\\') { i += 1; } i += 1; } - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..curly_start]}); + try writer.print(Output.prettyFmt("{s}", true), .{text[0..curly_start]}); try writer.writeAll("${"); const curly_remain = QuickAndDirtyJavaScriptSyntaxHighlighter{ - .text = remain[curly_start + 2 .. i], + .text = text[curly_start + 2 .. i], .enable_colors = this.enable_colors, .limited = false, }; @@ -779,22 +716,22 @@ pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { try curly_remain.format("", .{}, writer); } - if (i < remain.len and remain[i] == '}') { + if (i < text.len and text[i] == '}') { i += 1; } try writer.writeAll("}"); - remain = remain[i..]; + text = text[i..]; i = 0; - if (remain.len > 0 and remain[0] == char) { + if (text.len > 0 and text[0] == char) { try writer.writeAll(Output.prettyFmt("`", true)); - remain = remain[1..]; + text = text[1..]; continue :outer; } continue; } } - if (i + 1 < remain.len and remain[i] == '\\') { + if (i + 1 < text.len and text[i] == '\\') { i += 1; } @@ -802,58 +739,58 @@ pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { } // Include the trailing quote, if any - i += @as(usize, @intFromBool(i > 1 and i < remain.len and remain[i] == char)); + i += @intFromBool(i < text.len); - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; + try writer.print(Output.prettyFmt("{s}", true), .{text[0..i]}); + text = text[i..]; }, '/' => { prev_keyword = null; var i: usize = 1; // the start of a line comment - if (i < remain.len and remain[i] == '/') { - while (i < remain.len and remain[i] != '\n') { + if (i < text.len and text[i] == '/') { + while (i < text.len and text[i] != '\n') { i += 1; } - const remain_to_print = remain[0..i]; - if (i < remain.len and remain[i] == '\n') { + const remain_to_print = text[0..i]; + if (i < text.len and text[i] == '\n') { i += 1; } - if (i < remain.len and remain[i] == '\r') { + if (i < text.len and text[i] == '\r') { i += 1; } try writer.print(Output.prettyFmt("{s}", true), .{remain_to_print}); - remain = remain[i..]; + text = text[i..]; continue; } as_multiline_comment: { - if (i < remain.len and remain[i] == '*') { + if (i < text.len and text[i] == '*') { i += 1; - while (i + 2 < remain.len and !strings.eqlComptime(remain[i..][0..2], "*/")) { + while (i + 2 < text.len and !strings.eqlComptime(text[i..][0..2], "*/")) { i += 1; } - if (i + 2 < remain.len and strings.eqlComptime(remain[i..][0..2], "*/")) { + if (i + 2 < text.len and strings.eqlComptime(text[i..][0..2], "*/")) { i += 2; } else { i = 1; break :as_multiline_comment; } - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; + try writer.print(Output.prettyFmt("{s}", true), .{text[0..i]}); + text = text[i..]; continue; } } - try writer.writeAll(remain[0..i]); - remain = remain[i..]; + try writer.writeAll(text[0..i]); + text = text[i..]; }, '}', '{' => { // support potentially highlighting "from" in an import statement @@ -861,39 +798,39 @@ pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { prev_keyword = null; } - try writer.writeAll(remain[0..1]); - remain = remain[1..]; + try writer.writeAll(text[0..1]); + text = text[1..]; }, '[', ']' => { prev_keyword = null; - try writer.writeAll(remain[0..1]); - remain = remain[1..]; + try writer.writeAll(text[0..1]); + text = text[1..]; }, ';' => { prev_keyword = null; try writer.print(Output.prettyFmt(";", true), .{}); - remain = remain[1..]; + text = text[1..]; }, '.' => { prev_keyword = null; var i: usize = 1; - if (remain.len > 1 and (js_lexer.isIdentifierStart(remain[1]) or remain[1] == '#')) { + if (text.len > 1 and (js_lexer.isIdentifierStart(text[1]) or text[1] == '#')) { i = 2; - while (i < remain.len and js_lexer.isIdentifierContinue(remain[i])) { + while (i < text.len and js_lexer.isIdentifierContinue(text[i])) { i += 1; } - if (i < remain.len and (remain[i] == '(')) { - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; + if (i < text.len and (text[i] == '(')) { + try writer.print(Output.prettyFmt("{s}", true), .{text[0..i]}); + text = text[i..]; continue; } i = 1; } - try writer.writeAll(remain[0..1]); - remain = remain[1..]; + try writer.writeAll(text[0..1]); + text = text[1..]; }, '<' => { @@ -901,44 +838,44 @@ pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { // JSX jsx: { - if (remain.len > 1 and remain[0] == '/') { + if (text.len > 1 and text[0] == '/') { i = 2; } prev_keyword = null; - while (i < remain.len and js_lexer.isIdentifierContinue(remain[i])) { + while (i < text.len and js_lexer.isIdentifierContinue(text[i])) { i += 1; } else { i = 1; break :jsx; } - while (i < remain.len and remain[i] != '>') { + while (i < text.len and text[i] != '>') { i += 1; - if (i < remain.len and remain[i] == '<') { + if (i < text.len and text[i] == '<') { i = 1; break :jsx; } } - if (i < remain.len and remain[i] == '>') { + if (i < text.len and text[i] == '>') { i += 1; - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; + try writer.print(Output.prettyFmt("{s}", true), .{text[0..i]}); + text = text[i..]; continue; } i = 1; } - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; + try writer.print(Output.prettyFmt("{s}", true), .{text[0..i]}); + text = text[i..]; }, else => { - try writer.writeAll(remain[0..1]); - remain = remain[1..]; + try writer.writeAll(text[0..1]); + text = text[1..]; }, } } diff --git a/src/js_ast.zig b/src/js_ast.zig index 8345e07cdb..4b1a188cef 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -2774,10 +2774,10 @@ pub const E = struct { pub const Import = struct { expr: ExprNodeIndex, + options: ExprNodeIndex = Expr.empty, import_record_index: u32, - // This will be dynamic at some point. - type_attribute: TypeAttribute = .none, + /// TODO: /// Comments inside "import()" expressions have special meaning for Webpack. /// Preserving comments inside these expressions makes it possible to use /// esbuild as a TypeScript-to-JavaScript frontend for Webpack to improve @@ -2785,30 +2785,43 @@ pub const E = struct { /// because esbuild is not Webpack. But we do preserve them since doing so is /// harmless, easy to maintain, and useful to people. See the Webpack docs for /// more info: https://webpack.js.org/api/module-methods/#magic-comments. - /// TODO: - leading_interior_comments: []G.Comment = &([_]G.Comment{}), + // leading_interior_comments: []G.Comment = &([_]G.Comment{}), pub fn isImportRecordNull(this: *const Import) bool { return this.import_record_index == std.math.maxInt(u32); } - pub const TypeAttribute = enum { - none, - json, - toml, - text, - file, + pub fn importRecordTag(import: *const Import) ?ImportRecord.Tag { + const obj = import.options.data.as(.e_object) orelse + return null; + const with = obj.get("with") orelse obj.get("assert") orelse + return null; + const with_obj = with.data.as(.e_object) orelse + return null; + const str = (with_obj.get("type") orelse + return null).data.as(.e_string) orelse + return null; - pub fn tag(this: TypeAttribute) ImportRecord.Tag { - return switch (this) { - .none => .none, - .json => .with_type_json, - .toml => .with_type_toml, - .text => .with_type_text, - .file => .with_type_file, + if (str.eqlComptime("json")) { + return .with_type_json; + } else if (str.eqlComptime("toml")) { + return .with_type_toml; + } else if (str.eqlComptime("text")) { + return .with_type_text; + } else if (str.eqlComptime("file")) { + return .with_type_file; + } else if (str.eqlComptime("sqlite")) { + const embed = brk: { + const embed = with_obj.get("embed") orelse break :brk false; + const embed_str = embed.data.as(.e_string) orelse break :brk false; + break :brk embed_str.eqlComptime("true"); }; + + return if (embed) .with_type_sqlite_embedded else .with_type_sqlite; } - }; + + return null; + } }; }; @@ -5461,9 +5474,8 @@ pub const Expr = struct { .e_import => |el| { const item = bun.create(allocator, E.Import, .{ .expr = try el.expr.deepClone(allocator), + .options = try el.options.deepClone(allocator), .import_record_index = el.import_record_index, - .type_attribute = el.type_attribute, - .leading_interior_comments = el.leading_interior_comments, }); return .{ .e_import = item }; }, diff --git a/src/js_parser.zig b/src/js_parser.zig index 55788e0ae5..b2db172ade 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -451,36 +451,29 @@ const VisitArgsOpts = struct { is_unique_formal_parameters: bool = false, }; -const BunJSX = struct { - pub threadlocal var bun_jsx_identifier: E.Identifier = undefined; -}; pub fn ExpressionTransposer( - comptime Kontext: type, - comptime visitor: fn (ptr: *Kontext, arg: Expr, state: anytype) Expr, + comptime ContextType: type, + comptime StateType: type, + comptime visitor: fn (ptr: *ContextType, arg: Expr, state: StateType) Expr, ) type { return struct { - pub const Context = Kontext; + pub const Context = ContextType; pub const This = @This(); + context: *Context, pub fn init(c: *Context) This { - return This{ - .context = c, - }; + return .{ .context = c }; } - pub fn maybeTransposeIf(self: *This, arg: Expr, state: anytype) Expr { + pub fn maybeTransposeIf(self: *This, arg: Expr, state: StateType) Expr { switch (arg.data) { .e_if => |ex| { - return Expr.init( - E.If, - E.If{ - .yes = self.maybeTransposeIf(ex.yes, state), - .no = self.maybeTransposeIf(ex.no, state), - .test_ = ex.test_, - }, - arg.loc, - ); + return Expr.init(E.If, .{ + .yes = self.maybeTransposeIf(ex.yes, state), + .no = self.maybeTransposeIf(ex.no, state), + .test_ = ex.test_, + }, arg.loc); }, else => { return visitor(self.context, arg, state); @@ -488,16 +481,12 @@ pub fn ExpressionTransposer( } } - pub fn transposeKnownToBeIf(self: *This, arg: Expr, state: anytype) Expr { - return Expr.init( - E.If, - E.If{ - .yes = self.maybeTransposeIf(arg.data.e_if.yes, state), - .no = self.maybeTransposeIf(arg.data.e_if.no, state), - .test_ = arg.data.e_if.test_, - }, - arg.loc, - ); + pub fn transposeKnownToBeIf(self: *This, arg: Expr, state: StateType) Expr { + return Expr.init(E.If, .{ + .yes = self.maybeTransposeIf(arg.data.e_if.yes, state), + .no = self.maybeTransposeIf(arg.data.e_if.no, state), + .test_ = arg.data.e_if.test_, + }, arg.loc); } }; } @@ -517,7 +506,8 @@ const TransposeState = struct { is_then_catch_target: bool = false, is_require_immediately_assigned_to_decl: bool = false, loc: logger.Loc = logger.Loc.Empty, - type_attribute: E.Import.TypeAttribute = .none, + import_record_tag: ?ImportRecord.Tag = null, + import_options: Expr = Expr.empty, }; var true_args = &[_]Expr{ @@ -5327,9 +5317,9 @@ fn NewParser_( return p.options.bundle and p.source.index.isRuntime(); } - pub fn transposeImport(p: *P, arg: Expr, state: anytype) Expr { + pub fn transposeImport(p: *P, arg: Expr, state: *const TransposeState) Expr { // The argument must be a string - if (@as(Expr.Tag, arg.data) == .e_string) { + if (arg.data.as(.e_string)) |str| { // Ignore calls to import() if the control flow is provably dead here. // We don't want to spend time scanning the required files if they will // never be used. @@ -5337,18 +5327,19 @@ fn NewParser_( return p.newExpr(E.Null{}, arg.loc); } - const import_record_index = p.addImportRecord(.dynamic, arg.loc, arg.data.e_string.slice(p.allocator)); + const import_record_index = p.addImportRecord(.dynamic, arg.loc, str.slice(p.allocator)); - if (state.type_attribute.tag() != .none) { - p.import_records.items[import_record_index].tag = state.type_attribute.tag(); + if (state.import_record_tag) |tag| { + p.import_records.items[import_record_index].tag = tag; } + p.import_records.items[import_record_index].handles_import_errors = (state.is_await_target and p.fn_or_arrow_data_visit.try_body_count != 0) or state.is_then_catch_target; p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable; + return p.newExpr(E.Import{ .expr = arg, .import_record_index = Ref.toInt(import_record_index), - .type_attribute = state.type_attribute, - // .leading_interior_comments = arg.getString(). + .options = state.import_options, }, state.loc); } @@ -5360,12 +5351,12 @@ fn NewParser_( return p.newExpr(E.Import{ .expr = arg, + .options = state.import_options, .import_record_index = std.math.maxInt(u32), - .type_attribute = state.type_attribute, }, state.loc); } - pub fn transposeRequireResolve(p: *P, arg: Expr, require_resolve_ref: anytype) Expr { + pub fn transposeRequireResolve(p: *P, arg: Expr, require_resolve_ref: Expr) Expr { // The argument must be a string if (arg.data == .e_string) { return p.transposeRequireResolveKnownString(arg); @@ -5409,7 +5400,7 @@ fn NewParser_( ); } - pub fn transposeRequire(p: *P, arg: Expr, state: anytype) Expr { + pub fn transposeRequire(p: *P, arg: Expr, state: *const TransposeState) Expr { if (!p.options.features.allow_runtime) { const args = p.allocator.alloc(Expr, 1) catch bun.outOfMemory(); args[0] = arg; @@ -5725,9 +5716,9 @@ fn NewParser_( } } - const ImportTransposer = ExpressionTransposer(P, P.transposeImport); - const RequireTransposer = ExpressionTransposer(P, P.transposeRequire); - const RequireResolveTransposer = ExpressionTransposer(P, P.transposeRequireResolve); + const ImportTransposer = ExpressionTransposer(P, *const TransposeState, P.transposeImport); + const RequireTransposer = ExpressionTransposer(P, *const TransposeState, P.transposeRequire); + const RequireResolveTransposer = ExpressionTransposer(P, Expr, P.transposeRequireResolve); const Binding2ExprWrapper = struct { pub const Namespace = Binding.ToExpr(P, P.wrapIdentifierNamespace); @@ -15643,40 +15634,17 @@ fn NewParser_( const value = try p.parseExpr(.comma); - var type_attribute = E.Import.TypeAttribute.none; - + var import_options = Expr.empty; if (p.lexer.token == .t_comma) { // "import('./foo.json', )" try p.lexer.next(); if (p.lexer.token != .t_close_paren) { - // for now, we silently strip import assertions // "import('./foo.json', { assert: { type: 'json' } })" - const import_expr = try p.parseExpr(.comma); - if (import_expr.data == .e_object) { - if (import_expr.data.e_object.get("with") orelse import_expr.data.e_object.get("assert")) |with| { - if (with.data == .e_object) { - const with_object = with.data.e_object; - if (with_object.get("type")) |field| { - if (field.data == .e_string) { - const str = field.data.e_string; - if (str.eqlComptime("json")) { - type_attribute = .json; - } else if (str.eqlComptime("toml")) { - type_attribute = .toml; - } else if (str.eqlComptime("text")) { - type_attribute = .text; - } else if (str.eqlComptime("file")) { - type_attribute = .file; - } - } - } - } - } - } + import_options = try p.parseExpr(.comma); if (p.lexer.token == .t_comma) { - // "import('./foo.json', { assert: { type: 'json' } }, , )" + // "import('./foo.json', { assert: { type: 'json' } }, )" try p.lexer.next(); } } @@ -15692,18 +15660,20 @@ fn NewParser_( return p.newExpr(E.Import{ .expr = value, - .leading_interior_comments = comments, + // .leading_interior_comments = comments, .import_record_index = import_record_index, - .type_attribute = type_attribute, + .options = import_options, }, loc); } } + _ = comments; // TODO: leading_interior comments + return p.newExpr(E.Import{ .expr = value, - .type_attribute = type_attribute, - .leading_interior_comments = comments, + // .leading_interior_comments = comments, .import_record_index = std.math.maxInt(u32), + .options = import_options, }, loc); } @@ -17566,20 +17536,6 @@ fn NewParser_( } }, .e_import => |e_| { - const state = TransposeState{ - // we must check that the await_target is an e_import or it will crash - // example from next.js where not checking causes a panic: - // ``` - // const { - // normalizeLocalePath, - // } = require('../shared/lib/i18n/normalize-locale-path') as typeof import('../shared/lib/i18n/normalize-locale-path') - // ``` - .is_await_target = if (p.await_target != null) p.await_target.? == .e_import and p.await_target.?.e_import == e_ else false, - .is_then_catch_target = p.then_catch_chain.has_catch and std.meta.activeTag(p.then_catch_chain.next_target) == .e_import and expr.data.e_import == p.then_catch_chain.next_target.e_import, - .loc = e_.expr.loc, - .type_attribute = e_.type_attribute, - }; - // We want to forcefully fold constants inside of imports // even when minification is disabled, so that if we have an // import based on a string template, it does not cause a @@ -17593,7 +17549,32 @@ fn NewParser_( p.should_fold_typescript_constant_expressions = true; e_.expr = p.visitExpr(e_.expr); - return p.import_transposer.maybeTransposeIf(e_.expr, state); + e_.options = p.visitExpr(e_.options); + + // Import transposition is able to duplicate the options structure, so + // only perform it if the expression is side effect free. + // + // TODO: make this more like esbuild by emitting warnings that explain + // why this import was not analyzed. (see esbuild 'unsupported-dynamic-import') + if (p.exprCanBeRemovedIfUnused(&e_.options)) { + const state = TransposeState{ + .is_await_target = if (p.await_target) |await_target| + await_target == .e_import and await_target.e_import == e_ + else + false, + + .is_then_catch_target = p.then_catch_chain.has_catch and + p.then_catch_chain.next_target == .e_import and + expr.data.e_import == p.then_catch_chain.next_target.e_import, + + .import_options = e_.options, + + .loc = e_.expr.loc, + .import_record_tag = e_.importRecordTag(), + }; + + return p.import_transposer.maybeTransposeIf(e_.expr, &state); + } }, .e_call => |e_| { p.call_target = e_.target.data; @@ -17605,8 +17586,8 @@ fn NewParser_( }; const target_was_identifier_before_visit = e_.target.data == .e_identifier; - e_.target = p.visitExprInOut(e_.target, ExprIn{ - .has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == .continuation, + e_.target = p.visitExprInOut(e_.target, .{ + .has_chain_parent = e_.optional_chain == .continuation, }); // Copy the call side effect flag over if this is a known target @@ -17692,17 +17673,18 @@ fn NewParser_( if (e_.args.len == 1) { const first = e_.args.first_(); const state = TransposeState{ - .is_require_immediately_assigned_to_decl = in.is_immediately_assigned_to_decl and first.data == .e_string, + .is_require_immediately_assigned_to_decl = in.is_immediately_assigned_to_decl and + first.data == .e_string, }; switch (first.data) { .e_string => { // require(FOO) => require(FOO) - return p.transposeRequire(first, state); + return p.transposeRequire(first, &state); }, .e_if => { // require(FOO ? '123' : '456') => FOO ? require('123') : require('456') // This makes static analysis later easier - return p.require_transposer.transposeKnownToBeIf(first, state); + return p.require_transposer.transposeKnownToBeIf(first, &state); }, else => {}, } diff --git a/src/js_printer.zig b/src/js_printer.zig index 32995d15f3..58537858a7 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -1105,7 +1105,14 @@ fn NewPrinter( p.print("="); p.printSpaceBeforeIdentifier(); if (comptime Statement == void) { - p.printRequireOrImportExpr(import.import_record_index, false, &.{}, Level.lowest, ExprFlag.None()); + p.printRequireOrImportExpr( + import.import_record_index, + false, + &.{}, + Expr.empty, + Level.lowest, + ExprFlag.None(), + ); } else { p.print(statement); } @@ -1119,7 +1126,14 @@ fn NewPrinter( p.printSymbol(default.ref.?); if (comptime Statement == void) { p.@"print = "(); - p.printRequireOrImportExpr(import.import_record_index, false, &.{}, Level.lowest, ExprFlag.None()); + p.printRequireOrImportExpr( + import.import_record_index, + false, + &.{}, + Expr.empty, + Level.lowest, + ExprFlag.None(), + ); } else { p.@"print = "(); p.print(statement); @@ -1161,7 +1175,7 @@ fn NewPrinter( if (import.star_name_loc == null and import.default_name == null) { if (comptime Statement == void) { - p.printRequireOrImportExpr(import.import_record_index, false, &.{}, Level.lowest, ExprFlag.None()); + p.printRequireOrImportExpr(import.import_record_index, false, &.{}, Expr.empty, Level.lowest, ExprFlag.None()); } else { p.print(statement); } @@ -1916,9 +1930,12 @@ fn NewPrinter( import_record_index: u32, was_unwrapped_require: bool, leading_interior_comments: []G.Comment, + import_options: Expr, level_: Level, flags: ExprFlag.Set, ) void { + _ = leading_interior_comments; // TODO: + var level = level_; const wrap = level.gte(.new) or flags.contains(.forbid_call); if (wrap) p.print("("); @@ -1998,7 +2015,7 @@ fn NewPrinter( } defer if (record.kind == .dynamic) p.printDotThenSuffix(); - // Make sure the comma operator is propertly wrapped + // Make sure the comma operator is properly wrapped if (meta.exports_ref.isValid() and level.gte(.comma)) { p.print("("); @@ -2090,14 +2107,14 @@ fn NewPrinter( } // External import() - if (leading_interior_comments.len > 0) { - p.printNewline(); - p.indent(); - for (leading_interior_comments) |comment| { - p.printIndentedComment(comment.text); - } - p.printIndent(); - } + // if (leading_interior_comments.len > 0) { + // p.printNewline(); + // p.indent(); + // for (leading_interior_comments) |comment| { + // p.printIndentedComment(comment.text); + // } + // p.printIndent(); + // } p.addSourceMapping(record.range.loc); p.printSpaceBeforeIdentifier(); @@ -2106,43 +2123,22 @@ fn NewPrinter( p.print("import("); p.printImportRecordPath(record); - switch (record.tag) { - .with_type_sqlite, .with_type_sqlite_embedded => { - // we do not preserve "embed": "true" since it is not necessary - p.printWhitespacer(ws(", { with: { type: \"sqlite\" } }")); - }, - .with_type_text => { - if (comptime is_bun_platform) { - p.printWhitespacer(ws(", { with: { type: \"text\" } }")); - } - }, - .with_type_json => { - // backwards compatibility: previously, we always stripped type json - if (comptime is_bun_platform) { - p.printWhitespacer(ws(", { with: { type: \"json\" } }")); - } - }, - .with_type_toml => { - // backwards compatibility: previously, we always stripped type - if (comptime is_bun_platform) { - p.printWhitespacer(ws(", { with: { type: \"toml\" } }")); - } - }, - .with_type_file => { - // backwards compatibility: previously, we always stripped type - if (comptime is_bun_platform) { - p.printWhitespacer(ws(", { with: { type: \"file\" } }")); - } - }, - else => {}, + if (!import_options.isMissing()) { + // since we previously stripped type, it is a breaking change to + // enable this for non-bun platforms + if (is_bun_platform or bun.FeatureFlags.breaking_changes_1_2) { + p.printWhitespacer(ws(", ")); + p.printExpr(import_options, .comma, .{}); + } } + p.print(")"); - if (leading_interior_comments.len > 0) { - p.printNewline(); - p.unindent(); - p.printIndent(); - } + // if (leading_interior_comments.len > 0) { + // p.printNewline(); + // p.unindent(); + // p.printIndent(); + // } return; } @@ -2485,7 +2481,14 @@ fn NewPrinter( }, .e_require_string => |e| { if (!rewrite_esm_to_cjs) { - p.printRequireOrImportExpr(e.import_record_index, e.unwrapped_id != std.math.maxInt(u32), &([_]G.Comment{}), level, flags); + p.printRequireOrImportExpr( + e.import_record_index, + e.unwrapped_id != std.math.maxInt(u32), + &([_]G.Comment{}), + Expr.empty, + level, + flags, + ); } }, .e_require_resolve_string => |e| { @@ -2514,7 +2517,6 @@ fn NewPrinter( } }, .e_import => |e| { - // Handle non-string expressions if (e.isImportRecordNull()) { const wrap = level.gte(.new) or flags.contains(.forbid_call); @@ -2525,47 +2527,45 @@ fn NewPrinter( p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("import("); - if (e.leading_interior_comments.len > 0) { - p.printNewline(); - p.indent(); - for (e.leading_interior_comments) |comment| { - p.printIndentedComment(comment.text); - } - p.printIndent(); - } + // TODO: + // if (e.leading_interior_comments.len > 0) { + // p.printNewline(); + // p.indent(); + // for (e.leading_interior_comments) |comment| { + // p.printIndentedComment(comment.text); + // } + // p.printIndent(); + // } p.printExpr(e.expr, .comma, ExprFlag.None()); - if (comptime is_bun_platform) { + if (!e.options.isMissing()) { // since we previously stripped type, it is a breaking change to // enable this for non-bun platforms - switch (e.type_attribute) { - .none => {}, - .text => { - p.printWhitespacer(ws(", { with: { type: \"text\" } }")); - }, - .json => { - p.printWhitespacer(ws(", { with: { type: \"json\" } }")); - }, - .toml => { - p.printWhitespacer(ws(", { with: { type: \"toml\" } }")); - }, - .file => { - p.printWhitespacer(ws(", { with: { type: \"file\" } }")); - }, + if (is_bun_platform or bun.FeatureFlags.breaking_changes_1_2) { + p.printWhitespacer(ws(", ")); + p.printExpr(e.options, .comma, .{}); } } - if (e.leading_interior_comments.len > 0) { - p.printNewline(); - p.unindent(); - p.printIndent(); - } + // TODO: + // if (e.leading_interior_comments.len > 0) { + // p.printNewline(); + // p.unindent(); + // p.printIndent(); + // } p.print(")"); if (wrap) { p.print(")"); } } else { - p.printRequireOrImportExpr(e.import_record_index, false, e.leading_interior_comments, level, flags); + p.printRequireOrImportExpr( + e.import_record_index, + false, + &.{}, // e.leading_interior_comments, + e.options, + level, + flags, + ); } }, .e_dot => |e| { diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 96c585af75..c7b4f1304e 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -1199,6 +1199,19 @@ describe("bundler", () => { stdout: "false", }, }); + itBundled("edgecase/ImportOptionsArgument", { + files: { + "/entry.js": ` + import('ext', { with: { get ''() { KEEP } } }) + .then(function (error) { + console.log(error); + }); + `, + }, + dce: true, + external: ["ext"], + target: "bun", + }); itBundled("edgecase/ConstantFoldingShiftOperations", { files: { "/entry.ts": ` From 34e493f94518585ffd74f372e62fcdbe7a57bb5a Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 17 Jul 2024 02:33:46 -0700 Subject: [PATCH 008/123] Experiment: disable -fPIC and relro (#12582) Co-authored-by: Jarred-Sumner --- CMakeLists.txt | 51 ++++--- Dockerfile | 46 ++++-- Makefile | 11 +- build.zig | 10 +- scripts/build-cares.sh | 7 +- scripts/build-libarchive.sh | 1 + scripts/build-lshpack.sh | 4 +- scripts/build-zlib.sh | 2 +- scripts/build-zstd.sh | 2 +- scripts/env.sh | 19 ++- src/bun.js/bindings/ProcessBindingUV.cpp | 25 ++-- src/bun.js/bindings/bindings.zig | 4 +- src/bun.js/bindings/exports.zig | 4 + .../bindings/workaround-missing-symbols.cpp | 4 + src/bun.js/node/path.zig | 82 +++++------ src/cli.zig | 17 +-- src/cli/package_manager_command.zig | 7 +- src/generated_versions_list.zig | 2 +- src/install/install.zig | 133 +++++++++--------- src/resolver/resolve_path.zig | 22 +-- src/string_immutable.zig | 41 +++--- src/symbols.dyn | 11 +- 22 files changed, 292 insertions(+), 213 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 35d7367489..b61ce51b3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_policy(SET CMP0067 NEW) set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) set(Bun_VERSION "1.1.20") -set(WEBKIT_TAG b49be549da59347762aa83f849a65158d2a0d724) +set(WEBKIT_TAG dac47fbd5444cbd4e3568267099ae276c547e897) set(BUN_WORKDIR "${CMAKE_CURRENT_BINARY_DIR}") message(STATUS "Configuring Bun ${Bun_VERSION} in ${BUN_WORKDIR}") @@ -22,6 +22,7 @@ set(REPORTED_NODEJS_VERSION "22.3.0") # If we do not set this, it will crash at startup on the first memory allocation. if(NOT WIN32 AND NOT APPLE) set(CMAKE_CXX_EXTENSIONS ON) + set(CMAKE_POSITION_INDEPENDENT_CODE FALSE) endif() # --- Build Type --- @@ -645,16 +646,6 @@ file(GLOB BUN_CPP ${CONFIGURE_DEPENDS} ) list(APPEND BUN_RAW_SOURCES ${BUN_CPP}) -# -- Brotli -- -set(BROTLI_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/deps/brotli") -file(GLOB BROTLI_FILES ${CONFIGURE_DEPENDS} - "${BROTLI_SRC}/common/*.c" - "${BROTLI_SRC}/enc/*.c" - "${BROTLI_SRC}/dec/*.c" -) -list(APPEND BUN_RAW_SOURCES ${BROTLI_FILES}) -include_directories("${BUN_DEPS_DIR}/brotli/include") - # -- uSockets -- set(USOCKETS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/packages/bun-usockets/src") file(GLOB USOCKETS_FILES ${CONFIGURE_DEPENDS} @@ -902,6 +893,7 @@ if(NOT BUN_LINK_ONLY AND NOT BUN_CPP_ONLY) "${ZIG_COMPILER}" "build" "obj" "--zig-lib-dir" "${ZIG_LIB_DIR}" "--prefix" "${BUN_ZIG_OBJ_DIR}" + "--verbose" "-Dgenerated-code=${BUN_WORKDIR}/codegen" "-freference-trace=10" "-Dversion=${Bun_VERSION}" @@ -1182,7 +1174,6 @@ if(WIN32) target_link_options(${bun} PUBLIC "/STACK:0x1200000,0x100000" "/DEF:${BUN_SRC}/symbols.def" "/errorlimit:0") else() target_compile_options(${bun} PUBLIC - -fPIC -mtune=${CPU_TARGET} -fconstexpr-steps=2542484 -fconstexpr-depth=54 @@ -1192,6 +1183,8 @@ else() -fno-rtti -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer + -fno-pic + -fno-pie -faddrsig ) endif() @@ -1208,10 +1201,11 @@ endif() if(UNIX AND NOT APPLE) target_link_options(${bun} PUBLIC - "-fuse-ld=lld" - "-static-libstdc++" - "-static-libgcc" - "-Wl,-z,now" + -fuse-ld=lld + -fno-pic + -static-libstdc++ + -static-libgcc + "-Wl,-no-pie" "-Wl,-icf=safe" "-Wl,--as-needed" "-Wl,--gc-sections" @@ -1241,6 +1235,8 @@ if(UNIX AND NOT APPLE) "-rdynamic" "-Wl,--dynamic-list=${BUN_SRC}/symbols.dyn" "-Wl,--version-script=${BUN_SRC}/linker.lds" + -Wl,-z,lazy + -Wl,-z,norelro ) target_link_libraries(${bun} PRIVATE "c") @@ -1274,11 +1270,12 @@ endif() # --- Stripped Binary "bun" if(CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT WIN32 AND NOT ASSERT_ENABLED) - if(CI AND APPLE) + # if(CI AND APPLE) + if(APPLE) add_custom_command( TARGET ${bun} POST_BUILD - COMMAND ${DSYMUTIL} -z -o ${BUN_WORKDIR}/${bun}.dSYM ${BUN_WORKDIR}/${bun} + COMMAND ${DSYMUTIL} -o ${BUN_WORKDIR}/${bun}.dSYM ${BUN_WORKDIR}/${bun} COMMENT "Generating .dSYM" ) endif() @@ -1463,6 +1460,24 @@ else() target_compile_definitions(${bun} PRIVATE "LAZY_LOAD_SQLITE=1") endif() +# -- Brotli -- +set(BROTLI_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/deps/brotli") +file(GLOB BROTLI_FILES ${CONFIGURE_DEPENDS} + "${BROTLI_SRC}/common/*.c" + "${BROTLI_SRC}/enc/*.c" + "${BROTLI_SRC}/dec/*.c" +) +add_library(brotli STATIC ${BROTLI_FILES}) +target_include_directories(brotli PRIVATE "${BROTLI_SRC}/include") +target_compile_definitions(brotli PRIVATE "BROTLI_STATIC") + +if(WIN32) + target_compile_options(brotli PRIVATE /MT /U_DLL) +endif() + +target_link_libraries(${bun} PRIVATE brotli) +include_directories("${BUN_DEPS_DIR}/brotli/include") + if(USE_CUSTOM_LSHPACK) include_directories(${BUN_DEPS_DIR}/ls-hpack) diff --git a/Dockerfile b/Dockerfile index 843f8f0ef5..6fa2ddf48b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,10 +54,6 @@ ENV BUILDARCH=${BUILDARCH} ENV BUN_DEPS_OUT_DIR=${BUN_DEPS_OUT_DIR} ENV BUN_ENABLE_LTO 1 -ENV CXX=clang++-${LLVM_VERSION} -ENV CC=clang-${LLVM_VERSION} -ENV AR=/usr/bin/llvm-ar-${LLVM_VERSION} -ENV LD=lld-${LLVM_VERSION} ENV LC_CTYPE=en_US.UTF-8 ENV LC_ALL=en_US.UTF-8 @@ -94,6 +90,8 @@ RUN install_packages \ clangd-${LLVM_VERSION} \ libc++-${LLVM_VERSION}-dev \ libc++abi-${LLVM_VERSION}-dev \ + llvm-${LLVM_VERSION}-runtime \ + llvm-${LLVM_VERSION}-dev \ make \ cmake \ ninja-build \ @@ -120,6 +118,15 @@ RUN install_packages \ && ln -sf /usr/bin/lldb-${LLVM_VERSION} /usr/bin/lldb \ && ln -sf /usr/bin/clangd-${LLVM_VERSION} /usr/bin/clangd \ && ln -sf /usr/bin/llvm-ar-${LLVM_VERSION} /usr/bin/llvm-ar \ + && ln -sf /usr/bin/ld.lld /usr/bin/ld \ + && ln -sf /usr/bin/llvm-ranlib-${LLVM_VERSION} /usr/bin/ranlib \ + && ln -sf /usr/bin/clang /usr/bin/cc \ + && ln -sf /usr/bin/clang /usr/bin/c89 \ + && ln -sf /usr/bin/clang /usr/bin/c99 \ + && ln -sf /usr/bin/clang++ /usr/bin/c++ \ + && ln -sf /usr/bin/clang++ /usr/bin/g++ \ + && ln -sf /usr/bin/llvm-ar /usr/bin/ar \ + && ln -sf /usr/bin/clang /usr/bin/gcc \ && arch="$(dpkg --print-architecture)" \ && case "${arch##*-}" in \ amd64) variant="x64";; \ @@ -132,6 +139,7 @@ RUN install_packages \ && ln -s /usr/bin/bun /usr/bin/bunx \ && rm -rf bun-linux-${variant} bun-linux-${variant}.zip \ && mkdir -p ${BUN_DIR} ${BUN_DEPS_OUT_DIR} + # && if [ -n "${SCCACHE_BUCKET}" ]; then \ # echo "Setting up sccache" \ # && wget https://github.com/mozilla/sccache/releases/download/v0.5.4/sccache-v0.5.4-${BUILD_MACHINE_ARCH}-unknown-linux-musl.tar.gz \ @@ -168,13 +176,14 @@ ENV CCACHE_DIR=${CCACHE_DIR} COPY Makefile ${BUN_DIR}/Makefile COPY src/deps/c-ares ${BUN_DIR}/src/deps/c-ares +COPY scripts ${BUN_DIR}/scripts WORKDIR $BUN_DIR RUN --mount=type=cache,target=${CCACHE_DIR} \ cd $BUN_DIR \ - && make c-ares \ - && rm -rf ${BUN_DIR}/src/deps/c-ares ${BUN_DIR}/Makefile + && bash ./scripts/build-cares.sh \ + && rm -rf ${BUN_DIR}/src/deps/c-ares ${BUN_DIR}/Makefile ${BUN_DIR}/scripts FROM bun-base as lolhtml @@ -205,13 +214,14 @@ ENV CPU_TARGET=${CPU_TARGET} COPY Makefile ${BUN_DIR}/Makefile COPY src/deps/mimalloc ${BUN_DIR}/src/deps/mimalloc +COPY scripts ${BUN_DIR}/scripts ARG CCACHE_DIR=/ccache ENV CCACHE_DIR=${CCACHE_DIR} RUN --mount=type=cache,target=${CCACHE_DIR} \ cd ${BUN_DIR} \ - && make mimalloc \ + && bash ./scripts/build-mimalloc.sh \ && rm -rf src/deps/mimalloc Makefile FROM bun-base as mimalloc-debug @@ -241,14 +251,17 @@ ARG CCACHE_DIR=/ccache ENV CCACHE_DIR=${CCACHE_DIR} COPY Makefile ${BUN_DIR}/Makefile +COPY CMakeLists.txt ${BUN_DIR}/CMakeLists.txt +COPY scripts ${BUN_DIR}/scripts COPY src/deps/zlib ${BUN_DIR}/src/deps/zlib +COPY package.json bun.lockb Makefile .gitmodules ${BUN_DIR}/ WORKDIR $BUN_DIR RUN --mount=type=cache,target=${CCACHE_DIR} \ cd $BUN_DIR \ - && make zlib \ - && rm -rf src/deps/zlib Makefile + && bash ./scripts/build-zlib.sh && rm -rf src/deps/zlib scripts + FROM bun-base as libarchive @@ -287,6 +300,7 @@ ARG CPU_TARGET ENV CPU_TARGET=${CPU_TARGET} COPY Makefile ${BUN_DIR}/Makefile +COPY scripts ${BUN_DIR}/scripts COPY src/deps/boringssl ${BUN_DIR}/src/deps/boringssl WORKDIR $BUN_DIR @@ -296,7 +310,7 @@ ENV CCACHE_DIR=${CCACHE_DIR} RUN --mount=type=cache,target=${CCACHE_DIR} \ cd ${BUN_DIR} \ - && make boringssl \ + && bash ./scripts/build-boringssl.sh \ && rm -rf src/deps/boringssl Makefile @@ -312,12 +326,14 @@ ENV CCACHE_DIR=${CCACHE_DIR} COPY Makefile ${BUN_DIR}/Makefile COPY src/deps/zstd ${BUN_DIR}/src/deps/zstd +COPY scripts ${BUN_DIR}/scripts WORKDIR $BUN_DIR RUN --mount=type=cache,target=${CCACHE_DIR} \ cd $BUN_DIR \ - && make zstd + && bash ./scripts/build-zstd.sh \ + && rm -rf src/deps/zstd scripts FROM bun-base as ls-hpack @@ -331,12 +347,14 @@ ENV CCACHE_DIR=${CCACHE_DIR} COPY Makefile ${BUN_DIR}/Makefile COPY src/deps/ls-hpack ${BUN_DIR}/src/deps/ls-hpack +COPY scripts ${BUN_DIR}/scripts WORKDIR $BUN_DIR RUN --mount=type=cache,target=${CCACHE_DIR} \ cd $BUN_DIR \ - && make lshpack + && bash ./scripts/build-lshpack.sh \ + && rm -rf src/deps/ls-hpack scripts FROM bun-base-with-zig as bun-identifier-cache @@ -492,6 +510,7 @@ RUN mkdir -p build bun-webkit # lol COPY src/bun.js/bindings/sqlite/sqlite3.c ${BUN_DIR}/src/bun.js/bindings/sqlite/sqlite3.c +COPY src/deps/brotli ${BUN_DIR}/src/deps/brotli COPY src/symbols.dyn src/linker.lds ${BUN_DIR}/src/ @@ -506,7 +525,8 @@ COPY --from=tinycc ${BUN_DEPS_OUT_DIR}/* ${BUN_DEPS_OUT_DIR}/ COPY --from=c-ares ${BUN_DEPS_OUT_DIR}/* ${BUN_DEPS_OUT_DIR}/ COPY --from=ls-hpack ${BUN_DEPS_OUT_DIR}/* ${BUN_DEPS_OUT_DIR}/ COPY --from=bun-compile-zig-obj /tmp/bun-zig.o ${BUN_DIR}/build/bun-zig.o -COPY --from=bun-cpp-objects ${BUN_DIR}/build/bun-cpp-objects.a ${BUN_DIR}/build/bun-cpp-objects.a +COPY --from=bun-cpp-objects ${BUN_DIR}/build/*.a ${BUN_DIR}/build/ +COPY --from=bun-cpp-objects ${BUN_DIR}/build/*.o ${BUN_DIR}/build/ COPY --from=bun-cpp-objects ${BUN_DIR}/bun-webkit/lib ${BUN_DIR}/bun-webkit/lib WORKDIR $BUN_DIR/build diff --git a/Makefile b/Makefile index db8d717ec9..5f941249a9 100644 --- a/Makefile +++ b/Makefile @@ -157,7 +157,12 @@ CMAKE_FLAGS_WITHOUT_RELEASE = -DCMAKE_C_COMPILER=$(CC) \ -DCMAKE_OSX_DEPLOYMENT_TARGET=$(MIN_MACOS_VERSION) \ $(CMAKE_CXX_COMPILER_LAUNCHER_FLAG) \ -DCMAKE_AR=$(AR) \ - -DCMAKE_RANLIB=$(which llvm-16-ranlib 2>/dev/null || which llvm-ranlib 2>/dev/null) + -DCMAKE_RANLIB=$(which llvm-16-ranlib 2>/dev/null || which llvm-ranlib 2>/dev/null) \ + -DCMAKE_CXX_STANDARD=20 \ + -DCMAKE_C_STANDARD=17 \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCMAKE_C_STANDARD_REQUIRED=ON \ + -DCMAKE_CXX_EXTENSIONS=ON @@ -184,8 +189,8 @@ endif OPTIMIZATION_LEVEL=-O3 $(MARCH_NATIVE) DEBUG_OPTIMIZATION_LEVEL= -O1 $(MARCH_NATIVE) -gdwarf-4 -CFLAGS_WITHOUT_MARCH = $(MACOS_MIN_FLAG) $(OPTIMIZATION_LEVEL) -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -BUN_CFLAGS = $(MACOS_MIN_FLAG) $(MARCH_NATIVE) $(OPTIMIZATION_LEVEL) -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden +CFLAGS_WITHOUT_MARCH = $(MACOS_MIN_FLAG) $(OPTIMIZATION_LEVEL) -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables -fno-pie -fno-pic +BUN_CFLAGS = $(MACOS_MIN_FLAG) $(MARCH_NATIVE) $(OPTIMIZATION_LEVEL) -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables -fno-pie -fno-pic BUN_TMP_DIR := /tmp/make-bun CFLAGS=$(CFLAGS_WITHOUT_MARCH) $(MARCH_NATIVE) diff --git a/build.zig b/build.zig index d6cae3fc03..20c65eff9e 100644 --- a/build.zig +++ b/build.zig @@ -338,10 +338,13 @@ pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile { }, .target = opts.target, .optimize = opts.optimize, + + // https://github.com/ziglang/zig/issues/17430 .pic = true, + + .omit_frame_pointer = false, .strip = false, // stripped at the end }); - obj.bundle_compiler_rt = false; obj.formatted_panics = true; obj.root_module.omit_frame_pointer = false; @@ -359,9 +362,10 @@ pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile { } if (opts.os == .linux) { - obj.link_emit_relocs = true; - obj.link_eh_frame_hdr = true; + obj.link_emit_relocs = false; + obj.link_eh_frame_hdr = false; obj.link_function_sections = true; + obj.link_data_sections = true; if (opts.optimize == .Debug) { obj.root_module.valgrind = true; diff --git a/scripts/build-cares.sh b/scripts/build-cares.sh index 266e4292b1..575a4da3d1 100755 --- a/scripts/build-cares.sh +++ b/scripts/build-cares.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash set -exo pipefail + +export FORCE_PIC=1 source $(dirname -- "${BASH_SOURCE[0]}")/env.sh cd $BUN_DEPS_DIR/c-ares -rm -rf build +rm -rf build CMakeCache.txt CMakeFiles mkdir -p build cd build @@ -12,8 +14,9 @@ cd build cmake "${CMAKE_FLAGS[@]}" .. \ -DCMAKE_INSTALL_LIBDIR=lib \ -DCARES_STATIC=ON \ - -DCARES_STATIC_PIC=ON \ + -DCARES_STATIC_PIC=OFF \ -DCARES_SHARED=OFF \ + -DCARES_BUILD_TOOLS=ON \ -G "Ninja" ninja diff --git a/scripts/build-libarchive.sh b/scripts/build-libarchive.sh index 464da3f338..d1ad80580b 100755 --- a/scripts/build-libarchive.sh +++ b/scripts/build-libarchive.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash set -exo pipefail +export FORCE_PIC=1 source $(dirname -- "${BASH_SOURCE[0]}")/env.sh mkdir -p $BUN_DEPS_OUT_DIR diff --git a/scripts/build-lshpack.sh b/scripts/build-lshpack.sh index e2b2011994..1a870efe56 100755 --- a/scripts/build-lshpack.sh +++ b/scripts/build-lshpack.sh @@ -2,11 +2,11 @@ set -exo pipefail source $(dirname -- "${BASH_SOURCE[0]}")/env.sh +rm -rf CMakeFiles CMakeCache build.ninja mkdir -p $BUN_DEPS_OUT_DIR cd $BUN_DEPS_DIR/ls-hpack - rm -rf CMakeCache* CMakeFiles cmake "${CMAKE_FLAGS[@]}" . \ @@ -15,6 +15,6 @@ cmake "${CMAKE_FLAGS[@]}" . \ -DSHARED=0 \ -GNinja -ninja +ninja libls-hpack.a cp ./libls-hpack.a $BUN_DEPS_OUT_DIR/liblshpack.a diff --git a/scripts/build-zlib.sh b/scripts/build-zlib.sh index c7cba5cf25..daedca9fb3 100755 --- a/scripts/build-zlib.sh +++ b/scripts/build-zlib.sh @@ -9,5 +9,5 @@ if [[ $(uname -s) == 'Darwin' ]]; then export CFLAGS="$CFLAGS -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}" fi CFLAGS="${CFLAGS}" ./configure --static -make -j${CPUS} +make -j${CPUS} libz.a cp ./libz.a $BUN_DEPS_OUT_DIR/libz.a diff --git a/scripts/build-zstd.sh b/scripts/build-zstd.sh index 2150d43204..3adb9fa764 100755 --- a/scripts/build-zstd.sh +++ b/scripts/build-zstd.sh @@ -7,5 +7,5 @@ mkdir -p $BUN_DEPS_OUT_DIR cd $BUN_DEPS_DIR/zstd rm -rf Release CMakeCache.txt CMakeFiles cmake "${CMAKE_FLAGS[@]}" -DZSTD_BUILD_STATIC=ON -B Release -S build/cmake -G Ninja -ninja -C Release +ninja libzstd_static -C Release cp Release/lib/libzstd.a $BUN_DEPS_OUT_DIR/libzstd.a diff --git a/scripts/env.sh b/scripts/env.sh index 012bd2be18..071ce4f989 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -27,8 +27,23 @@ export CPUS=${CPUS:-$(nproc || sysctl -n hw.ncpu || echo 1)} export CMAKE_CXX_COMPILER=${CXX} export CMAKE_C_COMPILER=${CC} -export CFLAGS='-O3 -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer' -export CXXFLAGS='-O3 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer' +export CFLAGS='-O3 -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables -faddrsig ' +export CXXFLAGS='-O3 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables -faddrsig ' + +if [[ $(uname -s) == 'Linux' ]]; then + export CFLAGS="$CFLAGS -ffunction-sections -fdata-sections" + export CXXFLAGS="$CXXFLAGS -ffunction-sections -fdata-sections" + export LDFLAGS="${LDFLAGS} -Wl,-z,norelro " +fi + +# libarchive needs position-independent executables to compile successfully +if [ -n "$FORCE_PIC" ]; then + export CFLAGS="$CFLAGS -fPIC " + export CXXFLAGS="$CXXFLAGS -fPIC " +else + export CFLAGS="$CFLAGS -fno-pie -fno-pic " + export CXXFLAGS="$CXXFLAGS -fno-pie -fno-pic " +fi if [[ $(uname -s) == 'Linux' && ($(uname -m) == 'aarch64' || $(uname -m) == 'arm64') ]]; then export CFLAGS="$CFLAGS -march=armv8-a+crc -mtune=ampere1 " diff --git a/src/bun.js/bindings/ProcessBindingUV.cpp b/src/bun.js/bindings/ProcessBindingUV.cpp index d62eefa3be..c6151ffefe 100644 --- a/src/bun.js/bindings/ProcessBindingUV.cpp +++ b/src/bun.js/bindings/ProcessBindingUV.cpp @@ -132,14 +132,15 @@ JSC_DEFINE_HOST_FUNCTION(jsGetErrorMap, (JSGlobalObject * globalObject, JSC::Cal auto& vm = globalObject->vm(); auto map = JSC::JSMap::create(vm, globalObject->mapStructure()); -#define PUT_PROPERTY(name, value, desc) \ - { \ - auto arr = JSC::constructEmptyArray(globalObject, static_cast(nullptr), 2); \ - arr->putDirectIndex(globalObject, 0, JSC::jsString(vm, String(#name##_s))); \ - arr->putDirectIndex(globalObject, 1, JSC::jsString(vm, String(desc##_s))); \ - map->set(globalObject, JSC::jsNumber(value), arr); \ - } + // Inlining each of these via macros costs like 300 KB. + const auto putProperty = [](JSC::VM& vm, JSC::JSMap* map, JSC::JSGlobalObject* globalObject, ASCIILiteral name, int value, ASCIILiteral desc) -> void { + auto arr = JSC::constructEmptyArray(globalObject, static_cast(nullptr), 2); + arr->putDirectIndex(globalObject, 0, JSC::jsString(vm, String(name))); + arr->putDirectIndex(globalObject, 1, JSC::jsString(vm, String(desc))); + map->set(globalObject, JSC::jsNumber(value), arr); + }; +#define PUT_PROPERTY(name, value, desc) putProperty(vm, map, globalObject, #name##_s, value, desc##_s); BUN_UV_ERRNO_MAP(PUT_PROPERTY) #undef PUT_PROPERTY @@ -152,9 +153,15 @@ JSObject* create(VM& vm, JSGlobalObject* globalObject) EnsureStillAliveScope ensureStillAlive(bindingObject); bindingObject->putDirect(vm, JSC::Identifier::fromString(vm, "errname"_s), JSC::JSFunction::create(vm, globalObject, 1, "errname"_s, jsErrname, ImplementationVisibility::Public)); -#define PUT_PROPERTY(name, value, desc) \ - bindingObject->putDirect(vm, JSC::Identifier::fromString(vm, "UV_" #name##_s), JSC::jsNumber(value)); + // Inlining each of these via macros costs like 300 KB. + // Before: 96305608 + // After: 95973832 + const auto putNamedProperty = [](JSC::VM& vm, JSObject* bindingObject, const ASCIILiteral name, int value) -> void { + bindingObject->putDirect(vm, JSC::Identifier::fromString(vm, makeString("UV_"_s, name)), JSC::jsNumber(value)); + }; +#define PUT_PROPERTY(name, value, desc) \ + putNamedProperty(vm, bindingObject, #name##_s, value); BUN_UV_ERRNO_MAP(PUT_PROPERTY) #undef PUT_PROPERTY diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 7c1e1e4724..5834f9dc8d 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -609,10 +609,10 @@ pub const ZigString = extern struct { pub fn static(comptime slice_: []const u8) *const ZigString { const Holder = struct { - pub const value = ZigString{ ._unsafe_ptr_do_not_use = slice_.ptr, .len = slice_.len }; + pub const value = &ZigString{ ._unsafe_ptr_do_not_use = slice_.ptr, .len = slice_.len }; }; - return &Holder.value; + return Holder.value; } pub const GithubActionFormatter = struct { diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index b2b46d7c91..dca4d934ec 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -954,6 +954,10 @@ comptime { /// Using characters16() does not seem to always have the sentinel. or something else /// broke when I just used it. Not sure. ... but this works! pub export fn Bun__LoadLibraryBunString(str: *bun.String) ?*anyopaque { + if (comptime !Environment.isWindows) { + unreachable; + } + var buf: bun.WPathBuffer = undefined; const data = switch (str.encoding()) { .utf8 => bun.strings.convertUTF8toUTF16InBuffer(&buf, str.utf8()), diff --git a/src/bun.js/bindings/workaround-missing-symbols.cpp b/src/bun.js/bindings/workaround-missing-symbols.cpp index 5db3610ca4..0b095e6c60 100644 --- a/src/bun.js/bindings/workaround-missing-symbols.cpp +++ b/src/bun.js/bindings/workaround-missing-symbols.cpp @@ -358,6 +358,10 @@ extern "C" int __ulock_wait2(uint32_t operation, void* addr, uint64_t value, #endif +#ifndef U_SHOW_CPLUSPLUS_API +#define U_SHOW_CPLUSPLUS_API 0 +#endif + #include extern "C" bool icu_hasBinaryProperty(UChar32 cp, unsigned int prop) diff --git a/src/bun.js/node/path.zig b/src/bun.js/node/path.zig index 9987cbf379..faa3d1df1c 100644 --- a/src/bun.js/node/path.zig +++ b/src/bun.js/node/path.zig @@ -117,7 +117,7 @@ pub const sep_str_windows = CHAR_STR_BACKWARD_SLASH; inline fn formatExtT(comptime T: type, ext: []const T, buf: []T) []const T { const len = ext.len; if (len == 0) { - return comptime L(T, ""); + return &.{}; } if (ext[0] == CHAR_DOT) { return ext; @@ -240,18 +240,18 @@ pub fn basenamePosixT(comptime T: type, path: []const T, suffix: ?[]const T) []c const len = path.len; // Exit early for easier number type use. if (len == 0) { - return comptime L(T, ""); + return &.{}; } var start: usize = 0; // We use an optional value instead of -1, as in Node code, for easier number type use. var end: ?usize = null; var matchedSlash: bool = true; - const _suffix = if (suffix) |_s| _s else comptime L(T, ""); + const _suffix = if (suffix) |_s| _s else &.{}; const _suffixLen = _suffix.len; if (suffix != null and _suffixLen > 0 and _suffixLen <= len) { if (std.mem.eql(T, _suffix, path)) { - return comptime L(T, ""); + return &.{}; } // We use an optional value instead of -1, as in Node code, for easier number type use. var extIdx: ?usize = _suffixLen - 1; @@ -328,7 +328,7 @@ pub fn basenamePosixT(comptime T: type, path: []const T, suffix: ?[]const T) []c return if (end) |_end| path[start.._end] else - comptime L(T, ""); + &.{}; } /// Based on Node v21.6.1 path.win32.basename: @@ -340,7 +340,7 @@ pub fn basenameWindowsT(comptime T: type, path: []const T, suffix: ?[]const T) [ const len = path.len; // Exit early for easier number type use. if (len == 0) { - return comptime L(T, ""); + return &.{}; } const isSepT = isSepWindowsT; @@ -357,11 +357,11 @@ pub fn basenameWindowsT(comptime T: type, path: []const T, suffix: ?[]const T) [ start = 2; } - const _suffix = if (suffix) |_s| _s else comptime L(T, ""); + const _suffix = if (suffix) |_s| _s else &.{}; const _suffixLen = _suffix.len; if (suffix != null and _suffixLen > 0 and _suffixLen <= len) { if (std.mem.eql(T, _suffix, path)) { - return comptime L(T, ""); + return &.{}; } // We use an optional value instead of -1, as in Node code, for easier number type use. var extIdx: ?usize = _suffixLen - 1; @@ -434,7 +434,7 @@ pub fn basenameWindowsT(comptime T: type, path: []const T, suffix: ?[]const T) [ return if (end) |_end| path[start.._end] else - comptime L(T, ""); + &.{}; } pub inline fn basenamePosixJS_T(comptime T: type, globalObject: *JSC.JSGlobalObject, path: []const T, suffix: ?[]const T) JSC.JSValue { @@ -685,7 +685,7 @@ pub fn extnamePosixT(comptime T: type, path: []const T) []const T { const len = path.len; // Exit early for easier number type use. if (len == 0) { - return comptime L(T, ""); + return &.{}; } // We use an optional value instead of -1, as in Node code, for easier number type use. var startDot: ?usize = null; @@ -746,7 +746,7 @@ pub fn extnamePosixT(comptime T: type, path: []const T) []const T { _startDot == _end - 1 and _startDot == startPart + 1)) { - return comptime L(T, ""); + return &.{}; } return path[_startDot.._end]; @@ -761,7 +761,7 @@ pub fn extnameWindowsT(comptime T: type, path: []const T) []const T { const len = path.len; // Exit early for easier number type use. if (len == 0) { - return comptime L(T, ""); + return &.{}; } var start: usize = 0; // We use an optional value instead of -1, as in Node code, for easier number type use. @@ -835,7 +835,7 @@ pub fn extnameWindowsT(comptime T: type, path: []const T) []const T { _startDot == _end - 1 and _startDot == startPart + 1)) { - return comptime L(T, ""); + return &.{}; } return path[_startDot.._end]; @@ -1085,7 +1085,7 @@ pub inline fn joinPosixT(comptime T: type, paths: []const []const T, buf: []T, b var bufOffset: usize = 0; // Back joined by expandable buf2 in case it is long. - var joined: []const T = comptime L(T, ""); + var joined: []const T = &.{}; for (paths) |path| { // validateString of `path is performed in pub fn join. @@ -1131,8 +1131,8 @@ pub fn joinWindowsT(comptime T: type, paths: []const []const T, buf: []T, buf2: var bufOffset: usize = 0; // Backed by expandable buf2 in case it is long. - var joined: []const T = comptime L(T, ""); - var firstPart: []const T = comptime L(T, ""); + var joined: []const T = &.{}; + var firstPart: []const T = &.{}; for (paths) |path| { // validateString of `path` is performed in pub fn join. @@ -1282,7 +1282,7 @@ fn normalizeStringT(comptime T: type, path: []const T, allowAboveRoot: bool, sep var bufOffset: usize = 0; var bufSize: usize = 0; - var res: []const T = comptime L(T, ""); + var res: []const T = &.{}; var lastSegmentLength: usize = 0; // We use an optional value instead of -1, as in Node code, for easier number type use. var lastSlash: ?usize = null; @@ -1317,7 +1317,7 @@ fn normalizeStringT(comptime T: type, path: []const T, allowAboveRoot: bool, sep if (bufSize > 2) { const lastSlashIndex = std.mem.lastIndexOfScalar(T, buf[0..bufSize], separator); if (lastSlashIndex == null) { - res = comptime L(T, ""); + res = &.{}; bufSize = 0; lastSegmentLength = 0; } else { @@ -1344,7 +1344,7 @@ fn normalizeStringT(comptime T: type, path: []const T, allowAboveRoot: bool, sep dots = 0; continue; } else if (bufSize != 0) { - res = comptime L(T, ""); + res = &.{}; bufSize = 0; lastSegmentLength = 0; lastSlash = i; @@ -1687,12 +1687,12 @@ pub fn parsePosixT(comptime T: type, path: []const T) PathParsed(T) { return .{}; } - var root: []const T = comptime L(T, ""); - var dir: []const T = comptime L(T, ""); - var base: []const T = comptime L(T, ""); - var ext: []const T = comptime L(T, ""); + var root: []const T = &.{}; + var dir: []const T = &.{}; + var base: []const T = &.{}; + var ext: []const T = &.{}; // Prefix with _ to avoid shadowing the identifier in the outer scope. - var _name: []const T = comptime L(T, ""); + var _name: []const T = &.{}; // Prefix with _ to avoid shadowing the identifier in the outer scope. const _isAbsolute = path[0] == CHAR_FORWARD_SLASH; var start: usize = 0; @@ -1786,12 +1786,12 @@ pub fn parseWindowsT(comptime T: type, path: []const T) PathParsed(T) { comptime validatePathT(T, "parseWindowsT"); // validateString of `path` is performed in pub fn parse. - var root: []const T = comptime L(T, ""); - var dir: []const T = comptime L(T, ""); - var base: []const T = comptime L(T, ""); - var ext: []const T = comptime L(T, ""); + var root: []const T = &.{}; + var dir: []const T = &.{}; + var base: []const T = &.{}; + var ext: []const T = &.{}; // Prefix with _ to avoid shadowing the identifier in the outer scope. - var _name: []const T = comptime L(T, ""); + var _name: []const T = &.{}; const len = path.len; if (len == 0) { @@ -2006,7 +2006,7 @@ pub fn relativePosixT(comptime T: type, from: []const T, to: []const T, buf: []T // validateString of `from` and `to` are performed in pub fn relative. if (std.mem.eql(T, from, to)) { - return MaybeSlice(T){ .result = comptime L(T, "") }; + return MaybeSlice(T){ .result = &.{} }; } // Trim leading forward slashes. @@ -2023,7 +2023,7 @@ pub fn relativePosixT(comptime T: type, from: []const T, to: []const T, buf: []T }; if (std.mem.eql(T, fromOrig, toOrig)) { - return MaybeSlice(T){ .result = comptime L(T, "") }; + return MaybeSlice(T){ .result = &.{} }; } const fromStart = 1; @@ -2081,7 +2081,7 @@ pub fn relativePosixT(comptime T: type, from: []const T, to: []const T, buf: []T var bufSize: usize = 0; // Backed by buf3. - var out: []const T = comptime L(T, ""); + var out: []const T = &.{}; // Add a block to isolate `i`. { // Generate the relative path based on the path difference between `to` @@ -2138,7 +2138,7 @@ pub fn relativeWindowsT(comptime T: type, from: []const T, to: []const T, buf: [ // validateString of `from` and `to` are performed in pub fn relative. if (std.mem.eql(T, from, to)) { - return MaybeSlice(T){ .result = comptime L(T, "") }; + return MaybeSlice(T){ .result = &.{} }; } // Backed by expandable buf2 because fromOrig may be long. @@ -2156,7 +2156,7 @@ pub fn relativeWindowsT(comptime T: type, from: []const T, to: []const T, buf: [ if (std.mem.eql(T, fromOrig, toOrig) or eqlIgnoreCaseT(T, fromOrig, toOrig)) { - return MaybeSlice(T){ .result = comptime L(T, "") }; + return MaybeSlice(T){ .result = &.{} }; } const toOrigLen = toOrig.len; @@ -2256,7 +2256,7 @@ pub fn relativeWindowsT(comptime T: type, from: []const T, to: []const T, buf: [ var bufSize: usize = 0; // Backed by buf3. - var out: []const T = comptime L(T, ""); + var out: []const T = &.{}; // Add a block to isolate `i`. { // Generate the relative path based on the path difference between `to` @@ -2374,7 +2374,7 @@ pub fn resolvePosixT(comptime T: type, paths: []const []const T, buf: []T, buf2: // Backed by expandable buf2 because resolvedPath may be long. // We use buf2 here because resolvePosixT is called by other methods and using // buf2 here avoids stepping on others' toes. - var resolvedPath: []const T = comptime L(T, ""); + var resolvedPath: []const T = &.{}; var resolvedPathLen: usize = 0; var resolvedAbsolute: bool = false; @@ -2383,7 +2383,7 @@ pub fn resolvePosixT(comptime T: type, paths: []const []const T, buf: []T, buf2: var i_i64: i64 = if (paths.len == 0) -1 else @as(i64, @intCast(paths.len - 1)); while (i_i64 > -2 and !resolvedAbsolute) : (i_i64 -= 1) { - var path: []const T = comptime L(T, ""); + var path: []const T = &.{}; if (i_i64 >= 0) { path = paths[@as(usize, @intCast(i_i64))]; } else { @@ -2460,12 +2460,12 @@ pub fn resolveWindowsT(comptime T: type, paths: []const []const T, buf: []T, buf var tmpBuf: [MAX_PATH_SIZE(T)]T = undefined; // Backed by tmpBuf. - var resolvedDevice: []const T = comptime L(T, ""); + var resolvedDevice: []const T = &.{}; var resolvedDeviceLen: usize = 0; // Backed by expandable buf2 because resolvedTail may be long. // We use buf2 here because resolvePosixT is called by other methods and using // buf2 here avoids stepping on others' toes. - var resolvedTail: []const T = comptime L(T, ""); + var resolvedTail: []const T = &.{}; var resolvedTailLen: usize = 0; var resolvedAbsolute: bool = false; @@ -2477,7 +2477,7 @@ pub fn resolveWindowsT(comptime T: type, paths: []const []const T, buf: []T, buf while (i_i64 > -2) : (i_i64 -= 1) { // Backed by expandable buf2, to not conflict with buf2 backed resolvedTail, // because path may be long. - var path: []const T = comptime L(T, ""); + var path: []const T = &.{}; if (i_i64 >= 0) { path = paths[@as(usize, @intCast(i_i64))]; // validateString of `path` is performed in pub fn resolve. @@ -2581,7 +2581,7 @@ pub fn resolveWindowsT(comptime T: type, paths: []const []const T, buf: []T, buf const len = path.len; var rootEnd: usize = 0; // Backed by tmpBuf or an anonymous buffer. - var device: []const T = comptime L(T, ""); + var device: []const T = &.{}; // Prefix with _ to avoid shadowing the identifier in the outer scope. var _isAbsolute: bool = false; const byte0 = if (len > 0) path[0] else 0; diff --git a/src/cli.zig b/src/cli.zig index 6c74ac63e1..f231501575 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -1210,13 +1210,7 @@ pub const Command = struct { }; var global_cli_ctx: Context = undefined; - - var context_data: ContextData = ContextData{ - .args = std.mem.zeroes(Api.TransformOptions), - .log = undefined, - .start_time = 0, - .allocator = undefined, - }; + var context_data: ContextData = undefined; pub const init = ContextData.create; @@ -1260,10 +1254,13 @@ pub const Command = struct { pub fn create(allocator: std.mem.Allocator, log: *logger.Log, comptime command: Command.Tag) anyerror!Context { Cli.cmd = command; + context_data = .{ + .args = std.mem.zeroes(Api.TransformOptions), + .log = log, + .start_time = start_time, + .allocator = allocator, + }; global_cli_ctx = &context_data; - global_cli_ctx.log = log; - global_cli_ctx.start_time = start_time; - global_cli_ctx.allocator = allocator; if (comptime Command.Tag.uses_global_options.get(command)) { global_cli_ctx.args = try Arguments.parse(allocator, global_cli_ctx, command); diff --git a/src/cli/package_manager_command.zig b/src/cli/package_manager_command.zig index 55631722ef..226d59d1c2 100644 --- a/src/cli/package_manager_command.zig +++ b/src/cli/package_manager_command.zig @@ -61,7 +61,8 @@ pub const PackageManagerCommand = struct { @memcpy(lockfile_buffer[0..lockfile_.len], lockfile_); lockfile_buffer[lockfile_.len] = 0; const lockfile = lockfile_buffer[0..lockfile_.len :0]; - var pm = try PackageManager.init(ctx, PackageManager.Subcommand.pm); + const cli = try PackageManager.CommandLineArguments.parse(ctx.allocator, .pm); + var pm = try PackageManager.init(ctx, cli, PackageManager.Subcommand.pm); const load_lockfile = pm.lockfile.loadFromDisk(pm, ctx.allocator, ctx.log, lockfile, true); handleLoadLockfileErrors(load_lockfile, pm); @@ -120,8 +121,8 @@ pub const PackageManagerCommand = struct { pub fn exec(ctx: Command.Context) !void { var args = try std.process.argsAlloc(ctx.allocator); args = args[1..]; - - var pm = PackageManager.init(ctx, PackageManager.Subcommand.pm) catch |err| { + const cli = try PackageManager.CommandLineArguments.parse(ctx.allocator, .pm); + var pm = PackageManager.init(ctx, cli, PackageManager.Subcommand.pm) catch |err| { if (err == error.MissingPackageJSON) { var cwd_buf: bun.PathBuffer = undefined; if (bun.getcwd(&cwd_buf)) |cwd| { diff --git a/src/generated_versions_list.zig b/src/generated_versions_list.zig index 6a39381135..6a28038a88 100644 --- a/src/generated_versions_list.zig +++ b/src/generated_versions_list.zig @@ -4,7 +4,7 @@ pub const boringssl = "29a2cd359458c9384694b75456026e4b57e3e567"; pub const libarchive = "898dc8319355b7e985f68a9819f182aaed61b53a"; pub const mimalloc = "4c283af60cdae205df5a872530c77e2a6a307d43"; pub const picohttpparser = "066d2b1e9ab820703db0837a7255d92d30f0c9f5"; -pub const webkit = "b49be549da59347762aa83f849a65158d2a0d724"; +pub const webkit = "dac47fbd5444cbd4e3568267099ae276c547e897"; pub const zig = @import("std").fmt.comptimePrint("{}", .{@import("builtin").zig_version}); pub const zlib = "886098f3f339617b4243b286f5ed364b9989e245"; pub const tinycc = "ab631362d839333660a265d3084d8ff060b96753"; diff --git a/src/install/install.zig b/src/install/install.zig index 00e4a06480..506d3c4995 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -8186,15 +8186,10 @@ pub const PackageManager = struct { } }; - pub fn init(ctx: Command.Context, comptime subcommand: Subcommand) !*PackageManager { - const cli = try CommandLineArguments.parse(ctx.allocator, subcommand); - return initWithCLI(ctx, cli, subcommand); - } - - fn initWithCLI( + pub fn init( ctx: Command.Context, cli: CommandLineArguments, - comptime subcommand: Subcommand, + subcommand: Subcommand, ) !*PackageManager { // assume that spawning a thread will take a lil so we do that asap HTTP.HTTPThread.init(); @@ -8286,7 +8281,7 @@ pub const PackageManager = struct { }; } - if (comptime subcommand == .install) { + if (subcommand == .install) { if (cli.positionals.len > 1) { // this is `bun add `. // @@ -8312,7 +8307,7 @@ pub const PackageManager = struct { // Check if this is a workspace; if so, use root package var found = false; - if (comptime subcommand != .link) { + if (subcommand != .link) { if (!created_package_json) { while (std.fs.path.dirname(this_cwd)) |parent| : (this_cwd = parent) { const parent_without_trailing_slash = strings.withoutTrailingSlash(parent); @@ -8680,29 +8675,29 @@ pub const PackageManager = struct { // parse dependency of positional arg string (may include name@version for example) // get the precise version from the lockfile (there may be multiple) // copy the contents into a temp folder - pub inline fn patch(ctx: Command.Context) !void { + pub fn patch(ctx: Command.Context) !void { try updatePackageJSONAndInstallCatchError(ctx, .patch); } - pub inline fn patchCommit(ctx: Command.Context) !void { + pub fn patchCommit(ctx: Command.Context) !void { try updatePackageJSONAndInstallCatchError(ctx, .@"patch-commit"); } - pub inline fn update(ctx: Command.Context) !void { + pub fn update(ctx: Command.Context) !void { try updatePackageJSONAndInstallCatchError(ctx, .update); } - pub inline fn add(ctx: Command.Context) !void { + pub fn add(ctx: Command.Context) !void { try updatePackageJSONAndInstallCatchError(ctx, .add); } - pub inline fn remove(ctx: Command.Context) !void { + pub fn remove(ctx: Command.Context) !void { try updatePackageJSONAndInstallCatchError(ctx, .remove); } pub fn updatePackageJSONAndInstallCatchError( ctx: Command.Context, - comptime subcommand: Subcommand, + subcommand: Subcommand, ) !void { updatePackageJSONAndInstall(ctx, subcommand) catch |err| { switch (err) { @@ -8719,11 +8714,12 @@ pub const PackageManager = struct { }; } - pub inline fn link(ctx: Command.Context) !void { - var manager = PackageManager.init(ctx, .link) catch |err| brk: { + pub fn link(ctx: Command.Context) !void { + const cli = try CommandLineArguments.parse(ctx.allocator, .link); + var manager = PackageManager.init(ctx, cli, .link) catch |err| brk: { if (err == error.MissingPackageJSON) { try attemptToCreatePackageJSON(); - break :brk try PackageManager.init(ctx, .link); + break :brk try PackageManager.init(ctx, cli, .link); } return err; @@ -8900,11 +8896,12 @@ pub const PackageManager = struct { } } - pub inline fn unlink(ctx: Command.Context) !void { - var manager = PackageManager.init(ctx, .unlink) catch |err| brk: { + pub fn unlink(ctx: Command.Context) !void { + const cli = try PackageManager.CommandLineArguments.parse(ctx.allocator, .unlink); + var manager = PackageManager.init(ctx, cli, .unlink) catch |err| brk: { if (err == error.MissingPackageJSON) { try attemptToCreatePackageJSON(); - break :brk try PackageManager.init(ctx, .unlink); + break :brk try PackageManager.init(ctx, cli, .unlink); } return err; @@ -9056,54 +9053,54 @@ pub const PackageManager = struct { clap.parseParam("-h, --help Print this help menu") catch unreachable, }; - pub const install_params = install_params_ ++ [_]ParamType{ + pub const install_params: []const ParamType = &(install_params_ ++ [_]ParamType{ clap.parseParam("-d, --dev Add dependency to \"devDependencies\"") catch unreachable, clap.parseParam("-D, --development") catch unreachable, clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable, clap.parseParam("-E, --exact Add the exact version instead of the ^range") catch unreachable, clap.parseParam(" ... ") catch unreachable, - }; + }); - pub const update_params = install_params_ ++ [_]ParamType{ + pub const update_params: []const ParamType = &(install_params_ ++ [_]ParamType{ clap.parseParam("--latest Update packages to their latest versions") catch unreachable, clap.parseParam(" ... \"name\" of packages to update") catch unreachable, - }; + }); - pub const pm_params = install_params_ ++ [_]ParamType{ + pub const pm_params: []const ParamType = &(install_params_ ++ [_]ParamType{ clap.parseParam("-a, --all") catch unreachable, clap.parseParam(" ... ") catch unreachable, - }; + }); - pub const add_params = install_params_ ++ [_]ParamType{ + pub const add_params: []const ParamType = &(install_params_ ++ [_]ParamType{ clap.parseParam("-d, --dev Add dependency to \"devDependencies\"") catch unreachable, clap.parseParam("-D, --development") catch unreachable, clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable, clap.parseParam("-E, --exact Add the exact version instead of the ^range") catch unreachable, clap.parseParam(" ... \"name\" or \"name@version\" of package(s) to install") catch unreachable, - }; + }); - pub const remove_params = install_params_ ++ [_]ParamType{ + pub const remove_params: []const ParamType = &(install_params_ ++ [_]ParamType{ clap.parseParam(" ... \"name\" of package(s) to remove from package.json") catch unreachable, - }; + }); - pub const link_params = install_params_ ++ [_]ParamType{ + pub const link_params: []const ParamType = &(install_params_ ++ [_]ParamType{ clap.parseParam(" ... \"name\" install package as a link") catch unreachable, - }; + }); - pub const unlink_params = install_params_ ++ [_]ParamType{ + pub const unlink_params: []const ParamType = &(install_params_ ++ [_]ParamType{ clap.parseParam(" ... \"name\" uninstall package as a link") catch unreachable, - }; + }); - const patch_params = install_params_ ++ [_]ParamType{ + const patch_params: []const ParamType = &(install_params_ ++ [_]ParamType{ clap.parseParam(" ... \"name\" of the package to patch") catch unreachable, clap.parseParam("--commit Install a package containing modifications in `dir`") catch unreachable, clap.parseParam("--patches-dir The directory to put the patch file in (only if --commit is used)") catch unreachable, - }; + }); - const patch_commit_params = install_params_ ++ [_]ParamType{ + const patch_commit_params: []const ParamType = &(install_params_ ++ [_]ParamType{ clap.parseParam(" ... \"dir\" containing changes to a package") catch unreachable, clap.parseParam("--patches-dir The directory to put the patch file") catch unreachable, - }; + }); pub const CommandLineArguments = struct { registry: string = "", @@ -9169,7 +9166,7 @@ pub const PackageManager = struct { } }; - pub fn printHelp(comptime subcommand: Subcommand) void { + pub fn printHelp(subcommand: Subcommand) void { switch (subcommand) { // fall back to HelpCommand.printWithReason Subcommand.install => { @@ -9192,7 +9189,7 @@ pub const PackageManager = struct { Output.flush(); Output.pretty("\n\nFlags:", .{}); Output.flush(); - clap.simpleHelp(&PackageManager.install_params); + clap.simpleHelp(PackageManager.install_params); Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); Output.flush(); }, @@ -9219,7 +9216,7 @@ pub const PackageManager = struct { Output.flush(); Output.pretty("\nFlags:", .{}); Output.flush(); - clap.simpleHelp(&PackageManager.update_params); + clap.simpleHelp(PackageManager.update_params); Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); Output.flush(); }, @@ -9235,7 +9232,7 @@ pub const PackageManager = struct { Output.flush(); Output.pretty("\nFlags:", .{}); Output.flush(); - clap.simpleHelp(&PackageManager.patch_params); + clap.simpleHelp(PackageManager.patch_params); // Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); Output.flush(); }, @@ -9260,7 +9257,7 @@ pub const PackageManager = struct { Output.flush(); Output.pretty("\nFlags:", .{}); Output.flush(); - clap.simpleHelp(&PackageManager.patch_params); + clap.simpleHelp(PackageManager.patch_params); // Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); Output.flush(); }, @@ -9290,7 +9287,7 @@ pub const PackageManager = struct { Output.flush(); Output.pretty("\n\nFlags:", .{}); Output.flush(); - clap.simpleHelp(&PackageManager.add_params); + clap.simpleHelp(PackageManager.add_params); Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); Output.flush(); }, @@ -9312,7 +9309,7 @@ pub const PackageManager = struct { Output.flush(); Output.pretty("\nFlags:", .{}); Output.flush(); - clap.simpleHelp(&PackageManager.remove_params); + clap.simpleHelp(PackageManager.remove_params); Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); Output.flush(); }, @@ -9336,7 +9333,7 @@ pub const PackageManager = struct { Output.flush(); Output.pretty("\nFlags:", .{}); Output.flush(); - clap.simpleHelp(&PackageManager.link_params); + clap.simpleHelp(PackageManager.link_params); Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); Output.flush(); }, @@ -9357,7 +9354,7 @@ pub const PackageManager = struct { Output.flush(); Output.pretty("\nFlags:", .{}); Output.flush(); - clap.simpleHelp(&PackageManager.unlink_params); + clap.simpleHelp(PackageManager.unlink_params); Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); Output.flush(); }, @@ -9367,7 +9364,7 @@ pub const PackageManager = struct { pub fn parse(allocator: std.mem.Allocator, comptime subcommand: Subcommand) !CommandLineArguments { Output.is_verbose = Output.isVerbose(); - const params: []const ParamType = &switch (subcommand) { + const params: []const ParamType = switch (subcommand) { .install => install_params, .update => update_params, .pm => pm_params, @@ -9681,9 +9678,12 @@ pub const PackageManager = struct { fn updatePackageJSONAndInstall( ctx: Command.Context, - comptime subcommand: Subcommand, + subcommand: Subcommand, ) !void { - var manager = init(ctx, subcommand) catch |err| brk: { + const cli = switch (subcommand) { + inline else => |cmd| try PackageManager.CommandLineArguments.parse(ctx.allocator, cmd), + }; + var manager = init(ctx, cli, subcommand) catch |err| brk: { if (err == error.MissingPackageJSON) { switch (subcommand) { .update => { @@ -9700,7 +9700,7 @@ pub const PackageManager = struct { }, else => { try attemptToCreatePackageJSON(); - break :brk try PackageManager.init(ctx, subcommand); + break :brk try PackageManager.init(ctx, cli, subcommand); }, } } @@ -9709,7 +9709,7 @@ pub const PackageManager = struct { }; if (manager.options.shouldPrintCommandName()) { - Output.prettyErrorln("bun " ++ @tagName(subcommand) ++ " v" ++ Global.package_json_version_with_sha ++ "\n", .{}); + Output.prettyErrorln("bun {s} v" ++ Global.package_json_version_with_sha ++ "\n", .{@tagName(subcommand)}); Output.flush(); } @@ -9882,21 +9882,19 @@ pub const PackageManager = struct { &[_]UpdateRequest{} else UpdateRequest.parse(ctx.allocator, ctx.log, manager.options.positionals[1..], &update_requests, manager.subcommand); - switch (manager.subcommand) { - inline else => |subcommand| try manager.updatePackageJSONAndInstallWithManagerWithUpdates( - ctx, - updates, - subcommand, - log_level, - ), - } + try manager.updatePackageJSONAndInstallWithManagerWithUpdates( + ctx, + updates, + manager.subcommand, + log_level, + ); } fn updatePackageJSONAndInstallWithManagerWithUpdates( manager: *PackageManager, ctx: Command.Context, updates: []UpdateRequest, - comptime subcommand: Subcommand, + subcommand: Subcommand, comptime log_level: Options.LogLevel, ) !void { if (manager.log.errors > 0) { @@ -9950,17 +9948,17 @@ pub const PackageManager = struct { if (subcommand == .remove) { if (current_package_json.root.data != .e_object) { - Output.errGeneric("package.json is not an Object {{}}, so there's nothing to " ++ @tagName(subcommand) ++ "!", .{}); + Output.errGeneric("package.json is not an Object {{}}, so there's nothing to {s}!", .{@tagName(subcommand)}); Global.crash(); } else if (current_package_json.root.data.e_object.properties.len == 0) { - Output.errGeneric("package.json is empty {{}}, so there's nothing to " ++ @tagName(subcommand) ++ "!", .{}); + Output.errGeneric("package.json is empty {{}}, so there's nothing to {s}!", .{@tagName(subcommand)}); Global.crash(); } else if (current_package_json.root.asProperty("devDependencies") == null and current_package_json.root.asProperty("dependencies") == null and current_package_json.root.asProperty("optionalDependencies") == null and current_package_json.root.asProperty("peerDependencies") == null) { - Output.prettyErrorln("package.json doesn't have dependencies, there's nothing to " ++ @tagName(subcommand) ++ "!", .{}); + Output.prettyErrorln("package.json doesn't have dependencies, there's nothing to {s}!", .{@tagName(subcommand)}); Global.exit(0); } } @@ -11443,8 +11441,9 @@ pub const PackageManager = struct { var package_json_cwd_buf: bun.PathBuffer = undefined; pub var package_json_cwd: string = ""; - pub inline fn install(ctx: Command.Context) !void { - var manager = try init(ctx, .install); + pub fn install(ctx: Command.Context) !void { + const cli = try CommandLineArguments.parse(ctx.allocator, .install); + var manager = try init(ctx, cli, .install); // switch to `bun add ` if (manager.options.positionals.len > 1) { diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig index 2a80f5b7b2..01c87e1728 100644 --- a/src/resolver/resolve_path.zig +++ b/src/resolver/resolve_path.zig @@ -608,10 +608,10 @@ fn windowsVolumeNameLenT(comptime T: type, path: []const T) struct { usize, usiz } } } else { - if (bun.strings.indexAnyComptimeT(T, path[3..], comptime strings.literal(T, "/\\"))) |idx| { + if (bun.strings.indexAnyComptimeT(T, path[3..], strings.literal(T, "/\\"))) |idx| { // TODO: handle input "//abc//def" should be picked up as a unc path if (path.len > idx + 4 and !Platform.windows.isSeparatorT(T, path[idx + 4])) { - if (bun.strings.indexAnyComptimeT(T, path[idx + 4 ..], comptime strings.literal(T, "/\\"))) |idx2| { + if (bun.strings.indexAnyComptimeT(T, path[idx + 4 ..], strings.literal(T, "/\\"))) |idx2| { return .{ idx + idx2 + 4, idx + 3 }; } else { return .{ path.len, idx + 3 }; @@ -761,7 +761,7 @@ pub fn normalizeStringGenericTZ( // // since it is theoretically possible to get here in release // we will not do this check in release. - assert(!strings.hasPrefixComptimeType(T, path_, comptime strings.literal(T, ":\\"))); + assert(!strings.hasPrefixComptimeType(T, path_, strings.literal(T, ":\\"))); } var buf_i: usize = 0; @@ -776,16 +776,16 @@ pub fn normalizeStringGenericTZ( if (isWindows and !options.allow_above_root) { if (volLen > 0) { if (options.add_nt_prefix) { - @memcpy(buf[buf_i .. buf_i + 4], comptime strings.literal(T, "\\??\\")); + @memcpy(buf[buf_i .. buf_i + 4], strings.literal(T, "\\??\\")); buf_i += 4; } if (path_[1] != ':') { // UNC paths if (options.add_nt_prefix) { - @memcpy(buf[buf_i .. buf_i + 4], comptime strings.literal(T, "UNC" ++ sep_str)); + @memcpy(buf[buf_i .. buf_i + 4], strings.literal(T, "UNC" ++ sep_str)); buf_i += 2; } else { - @memcpy(buf[buf_i .. buf_i + 2], comptime strings.literal(T, sep_str ++ sep_str)); + @memcpy(buf[buf_i .. buf_i + 2], strings.literal(T, sep_str ++ sep_str)); } @memcpy(buf[buf_i + 2 .. buf_i + indexOfThirdUNCSlash + 1], path_[2 .. indexOfThirdUNCSlash + 1]); buf[buf_i + indexOfThirdUNCSlash] = options.separator; @@ -827,7 +827,7 @@ pub fn normalizeStringGenericTZ( if (isWindows and options.allow_above_root) { if (path_.len >= 2 and path_[1] == ':') { if (options.add_nt_prefix) { - @memcpy(buf[buf_i .. buf_i + 4], &comptime strings.literalBuf(T, "\\??\\")); + @memcpy(buf[buf_i .. buf_i + 4], &strings.literalBuf(T, "\\??\\")); buf_i += 4; } buf[buf_i] = path_[0]; @@ -884,10 +884,10 @@ pub fn normalizeStringGenericTZ( } } else if (options.allow_above_root) { if (buf_i > buf_start) { - buf[buf_i..][0..3].* = (comptime strings.literal(T, sep_str ++ "..")).*; + buf[buf_i..][0..3].* = (strings.literal(T, sep_str ++ "..")).*; buf_i += 3; } else { - buf[buf_i..][0..2].* = (comptime strings.literal(T, "..")).*; + buf[buf_i..][0..2].* = (strings.literal(T, "..")).*; buf_i += 2; } dotdot = buf_i; @@ -932,7 +932,7 @@ pub fn normalizeStringGenericTZ( const result = if (options.zero_terminate) buf[0..buf_i :0] else buf[0..buf_i]; if (bun.Environment.allow_assert and isWindows) { - assert(!strings.hasPrefixComptimeType(T, result, comptime strings.literal(T, "\\:\\"))); + assert(!strings.hasPrefixComptimeType(T, result, strings.literal(T, "\\:\\"))); } return result; @@ -1616,7 +1616,7 @@ pub fn lastIndexOfSeparatorWindows(slice: []const u8) ?usize { } pub fn lastIndexOfSeparatorWindowsT(comptime T: type, slice: []const T) ?usize { - return std.mem.lastIndexOfAny(T, slice, comptime strings.literal(T, "\\/")); + return std.mem.lastIndexOfAny(T, slice, strings.literal(T, "\\/")); } pub fn lastIndexOfSeparatorPosix(slice: []const u8) ?usize { diff --git a/src/string_immutable.zig b/src/string_immutable.zig index d6a6b82197..46bff268e9 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -51,21 +51,19 @@ pub inline fn removeLeadingDotSlash(slice: []const u8) []const u8 { pub const w = toUTF16Literal; pub fn toUTF16Literal(comptime str: []const u8) [:0]const u16 { - return comptime literal(u16, str); + return literal(u16, str); } pub fn literal(comptime T: type, comptime str: []const u8) *const [literalLength(T, str):0]T { - if (!@inComptime()) @compileError("strings.literal() must be called in a comptime context"); - return comptime switch (T) { - u8 => brk: { - var data: [str.len:0]u8 = undefined; - @memcpy(&data, str); - const final = data[0..].*; - break :brk &final; - }, - u16 => return std.unicode.utf8ToUtf16LeStringLiteral(str), - else => @compileError("unsupported type " ++ @typeName(T) ++ " in strings.literal() call."), + const Holder = struct { + pub const value = switch (T) { + u8 => (str[0..str.len].* ++ .{0})[0..str.len :0], + u16 => std.unicode.utf8ToUtf16LeStringLiteral(str), + else => @compileError("unsupported type " ++ @typeName(T) ++ " in strings.literal() call."), + }; }; + + return Holder.value; } fn literalLength(comptime T: type, comptime str: string) usize { @@ -173,7 +171,7 @@ pub inline fn containsAny(in: anytype, target: string) bool { /// - The name ends up being part of a URL, an argument on the command line, and /// a folder name. Therefore, the name can't contain any non-URL-safe /// characters. -pub inline fn isNPMPackageName(target: string) bool { +pub fn isNPMPackageName(target: string) bool { if (target.len == 0) return false; if (target.len > 214) return false; @@ -207,7 +205,7 @@ pub inline fn isNPMPackageName(target: string) bool { return !scoped or slash_index > 0 and slash_index + 1 < target.len; } -pub inline fn indexAnyComptime(target: string, comptime chars: string) ?usize { +pub fn indexAnyComptime(target: string, comptime chars: string) ?usize { for (target, 0..) |parent, i| { inline for (chars) |char| { if (char == parent) return i; @@ -216,7 +214,7 @@ pub inline fn indexAnyComptime(target: string, comptime chars: string) ?usize { return null; } -pub inline fn indexAnyComptimeT(comptime T: type, target: []const T, comptime chars: []const T) ?usize { +pub fn indexAnyComptimeT(comptime T: type, target: []const T, comptime chars: []const T) ?usize { for (target, 0..) |parent, i| { inline for (chars) |char| { if (char == parent) return i; @@ -225,7 +223,7 @@ pub inline fn indexAnyComptimeT(comptime T: type, target: []const T, comptime ch return null; } -pub inline fn indexEqualAny(in: anytype, target: string) ?usize { +pub fn indexEqualAny(in: anytype, target: string) ?usize { for (in, 0..) |str, i| if (eqlLong(str, target, true)) return i; return null; } @@ -795,8 +793,9 @@ pub fn hasSuffixComptime(self: string, comptime alt: anytype) bool { return self.len >= alt.len and eqlComptimeCheckLenWithType(u8, self[self.len - alt.len ..], alt, false); } -inline fn eqlComptimeCheckLenU8(a: []const u8, comptime b: []const u8, comptime check_len: bool) bool { +fn eqlComptimeCheckLenU8(a: []const u8, comptime b: []const u8, comptime check_len: bool) bool { @setEvalBranchQuota(9999); + if (comptime check_len) { if (a.len != b.len) return false; } @@ -833,7 +832,7 @@ inline fn eqlComptimeCheckLenU8(a: []const u8, comptime b: []const u8, comptime return true; } -inline fn eqlComptimeCheckLenWithKnownType(comptime Type: type, a: []const Type, comptime b: []const Type, comptime check_len: bool) bool { +fn eqlComptimeCheckLenWithKnownType(comptime Type: type, a: []const Type, comptime b: []const Type, comptime check_len: bool) bool { if (comptime Type != u8) { return eqlComptimeCheckLenU8(std.mem.sliceAsBytes(a), comptime std.mem.sliceAsBytes(b), comptime check_len); } @@ -844,18 +843,18 @@ inline fn eqlComptimeCheckLenWithKnownType(comptime Type: type, a: []const Type, /// /// strings.eqlComptime(input, "hello world"); /// strings.eqlComptime(input, "hai"); -pub inline fn eqlComptimeCheckLenWithType(comptime Type: type, a: []const Type, comptime b: anytype, comptime check_len: bool) bool { +pub fn eqlComptimeCheckLenWithType(comptime Type: type, a: []const Type, comptime b: anytype, comptime check_len: bool) bool { return eqlComptimeCheckLenWithKnownType(comptime Type, a, if (@typeInfo(@TypeOf(b)) != .Pointer) &b else b, comptime check_len); } -pub inline fn eqlCaseInsensitiveASCIIIgnoreLength( +pub fn eqlCaseInsensitiveASCIIIgnoreLength( a: string, b: string, ) bool { return eqlCaseInsensitiveASCII(a, b, false); } -pub inline fn eqlCaseInsensitiveASCIIICheckLength( +pub fn eqlCaseInsensitiveASCIIICheckLength( a: string, b: string, ) bool { @@ -886,7 +885,7 @@ pub fn eqlLong(a_str: string, b_str: string, comptime check_len: bool) bool { return false; } } else { - if (comptime Environment.allow_assert) assert(b_str.len == a_str.len); + if (comptime Environment.allow_assert) assert(b_str.len <= a_str.len); } const end = b_str.ptr + len; diff --git a/src/symbols.dyn b/src/symbols.dyn index 0be0db03ca..44160f2867 100644 --- a/src/symbols.dyn +++ b/src/symbols.dyn @@ -51,6 +51,7 @@ _napi_define_class; _napi_define_properties; _napi_delete_async_work; + _napi_delete_element; _napi_delete_property; _napi_delete_reference; _napi_detach_arraybuffer; @@ -67,7 +68,6 @@ _napi_get_dataview_info; _napi_get_date_value; _napi_get_element; - _napi_delete_element; _napi_get_global; _napi_get_instance_data; _napi_get_last_error_info; @@ -144,9 +144,14 @@ _napi_unref_threadsafe_function; _napi_unwrap; _napi_wrap; + _node_api_create_external_string_latin1; + _node_api_create_external_string_utf16; _node_api_create_syntax_error; _node_api_symbol_for; _node_api_throw_syntax_error; - _node_api_create_external_string_latin1; - _node_api_create_external_string_utf16; + __ZN2v87Isolate10GetCurrentEv; + __ZN2v87Isolate13TryGetCurrentEv; + __ZN2v87Isolate17GetCurrentContextEv; + __ZN4node25AddEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_; + __ZN4node28RemoveEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_; }; \ No newline at end of file From c6149d36b3ec948fd67fac7211c9c9b2198791c1 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 17 Jul 2024 02:40:42 -0700 Subject: [PATCH 009/123] Bump --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b61ce51b3b..e68cbf3939 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_policy(SET CMP0091 NEW) cmake_policy(SET CMP0067 NEW) set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) -set(Bun_VERSION "1.1.20") +set(Bun_VERSION "1.1.21") set(WEBKIT_TAG dac47fbd5444cbd4e3568267099ae276c547e897) set(BUN_WORKDIR "${CMAKE_CURRENT_BINARY_DIR}") From 1a6f2d38da239647f3e6704998a6ece5d4d0fa8f Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 17 Jul 2024 03:31:12 -0700 Subject: [PATCH 010/123] Github actions --- .github/workflows/release.yml | 18 ++++++++++++++++++ .github/workflows/upload.yml | 12 ++++++++++++ 2 files changed, 30 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e0e90fac1..495d236073 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -270,6 +270,24 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY}} AWS_ENDPOINT: ${{ secrets.AWS_ENDPOINT }} AWS_BUCKET: bun + + notify-sentry: + name: Notify Sentry + runs-on: ubuntu-latest + needs: s3 + steps: + - name: Notify Sentry + uses: getsentry/action-release@v1.7.0 + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + with: + ignore_missing: true + ignore_empty: true + version: ${{ env.BUN_VERSION }} + environment: production + bump: name: "Bump version" runs-on: ubuntu-latest diff --git a/.github/workflows/upload.yml b/.github/workflows/upload.yml index 232c449e1d..d111d17f6b 100644 --- a/.github/workflows/upload.yml +++ b/.github/workflows/upload.yml @@ -80,3 +80,15 @@ jobs: bun upgrade --canary # bun upgrade --stable <- to downgrade ``` + # If notifying sentry fails, don't fail the rest of the build. + - name: Notify Sentry + uses: getsentry/action-release@v1.7.0 + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + with: + ignore_missing: true + ignore_empty: true + version: ${{ github.event.workflow_run.head_sha || github.sha }}-canary + environment: canary From 16aad326e4d5192095003666291a769efc4e8f16 Mon Sep 17 00:00:00 2001 From: Ciro Spaciari Date: Wed, 17 Jul 2024 17:09:24 -0700 Subject: [PATCH 011/123] fix(setSystemTime) fix number parameter behavior (#12630) Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> --- src/bun.js/bindings/JSMockFunction.cpp | 7 ++----- test/js/bun/test/test-timers.test.ts | 4 +++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/bun.js/bindings/JSMockFunction.cpp b/src/bun.js/bindings/JSMockFunction.cpp index 894e198db9..6073537f7b 100644 --- a/src/bun.js/bindings/JSMockFunction.cpp +++ b/src/bun.js/bindings/JSMockFunction.cpp @@ -1362,12 +1362,9 @@ BUN_DEFINE_HOST_FUNCTION(JSMock__jsSetSystemTime, (JSC::JSGlobalObject * globalO } return JSValue::encode(callFrame->thisValue()); } + // number > 0 is a valid date otherwise it's invalid and we should reset the time (set to -1) + globalObject->overridenDateNow = (argument0.isNumber() && argument0.asNumber() >= 0) ? argument0.asNumber() : -1; - if (argument0.isNumber() && argument0.asNumber() > 0) { - globalObject->overridenDateNow = argument0.asNumber(); - } - - globalObject->overridenDateNow = -1; return JSValue::encode(callFrame->thisValue()); } diff --git a/test/js/bun/test/test-timers.test.ts b/test/js/bun/test/test-timers.test.ts index 963467dee5..b216469c05 100644 --- a/test/js/bun/test/test-timers.test.ts +++ b/test/js/bun/test/test-timers.test.ts @@ -20,7 +20,9 @@ test("we can go back in time", () => { expect(DateBeforeMocked).not.toBe(Date); expect(DateBeforeMocked.now).not.toBe(Date.now); } - + jest.setSystemTime(new Date("2020-01-01T00:00:00.000Z").getTime()); + expect(new Date().toISOString()).toBe("2020-01-01T00:00:00.000Z"); + expect(Date.now()).toBe(1577836800000); jest.useRealTimers(); const now = new Date(); now.setHours(0, 0, 0, 0); From 43949151b1ceb1027cbec8193db8c6722fbf4f53 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 17 Jul 2024 17:17:00 -0700 Subject: [PATCH 012/123] fix(bundler): importing modules with trailing slash no longer uses a builtin (#12632) --- src/cli.zig | 23 ++++++++++++++++++++- src/crash_handler.zig | 20 +++++++++++++++++- src/import_record.zig | 1 - src/resolver/resolver.zig | 29 ++++++++++++++++++++++++--- test/bundler/bundler_edgecase.test.ts | 14 +++++++++++++ 5 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/cli.zig b/src/cli.zig index f231501575..42ba78602d 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -70,6 +70,19 @@ pub const Cli = struct { pub threadlocal var is_main_thread: bool = false; }; +pub const debug_flags = if (Environment.isDebug) struct { + var resolve_breakpoints: []const []const u8 = &.{}; + + pub fn hasResolveBreakpoint(str: []const u8) bool { + for (resolve_breakpoints) |bp| { + if (strings.contains(str, bp)) { + return true; + } + } + return false; + } +} else @compileError("Do not access this namespace []const u8; in a release build"); + const LoaderMatcher = strings.ExactSizeMatcher(4); const ColonListType = @import("./cli/colon_list_type.zig").ColonListType; pub const LoaderColonList = ColonListType(Api.Loader, Arguments.loader_resolver); @@ -146,7 +159,7 @@ pub const Arguments = struct { pub const ParamType = clap.Param(clap.Help); - const base_params_ = [_]ParamType{ + const base_params_ = (if (Environment.isDebug) debug_params else [_]ParamType{}) ++ [_]ParamType{ clap.parseParam("--env-file ... Load environment variables from the specified file(s)") catch unreachable, clap.parseParam("--cwd Absolute path to resolve files & entry points from. This just changes the process' cwd.") catch unreachable, clap.parseParam("-c, --config ? Specify path to Bun config file. Default $cwd/bunfig.toml") catch unreachable, @@ -157,6 +170,10 @@ pub const Arguments = struct { clap.parseParam("--verbose-error-trace") catch unreachable, } else [_]ParamType{}; + const debug_params = [_]ParamType{ + clap.parseParam("--breakpoint-resolve ... DEBUG MODE: breakpoint when resolving something that includes this string") catch unreachable, + }; + const transpiler_params_ = [_]ParamType{ clap.parseParam("--main-fields ... Main fields to lookup in package.json. Defaults to --target dependent") catch unreachable, clap.parseParam("--extension-order ... Defaults to: .tsx,.ts,.jsx,.js,.json ") catch unreachable, @@ -959,6 +976,10 @@ pub const Arguments = struct { } } + if (Environment.isDebug) { + debug_flags.resolve_breakpoints = args.options("--breakpoint-resolve"); + } + return opts; } }; diff --git a/src/crash_handler.zig b/src/crash_handler.zig index ee608efef6..4bb12cce35 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -51,7 +51,12 @@ var panic_mutex = std.Thread.Mutex{}; threadlocal var panic_stage: usize = 0; /// This can be set by various parts of the codebase to indicate a broader -/// action being taken, for example "Crashed while parsing /path/to/file.js" +/// action being taken. It is printed when a crash happens, which can help +/// narrow down what the bug is. Example: "Crashed while parsing /path/to/file.js" +/// +/// Some of these are enabled in release builds, which may encourage users to +/// attach the affected files to crash report. Others, which may have low crash +/// rate or only crash due to assertion failures, are debug-only. See `Action`. pub threadlocal var current_action: ?Action = null; const CPUFeatures = @import("./bun.js/bindings/CPUFeatures.zig").CPUFeatures; @@ -102,11 +107,24 @@ pub const Action = union(enum) { visit: []const u8, print: []const u8, + resolver: if (bun.Environment.isDebug) struct { + source_dir: []const u8, + import_path: []const u8, + kind: bun.ImportKind, + } else void, + pub fn format(act: Action, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { switch (act) { .parse => |path| try writer.print("parsing {s}", .{path}), .visit => |path| try writer.print("visiting {s}", .{path}), .print => |path| try writer.print("printing {s}", .{path}), + .resolver => |res| if (bun.Environment.isDebug) { + try writer.print("resolving {s} from {s} ({s})", .{ + res.import_path, + res.source_dir, + res.kind.label(), + }); + }, } } }; diff --git a/src/import_record.zig b/src/import_record.zig index 93e18c124d..c21e2223c8 100644 --- a/src/import_record.zig +++ b/src/import_record.zig @@ -7,7 +7,6 @@ const Index = @import("ast/base.zig").Index; const Api = @import("./api/schema.zig").Api; pub const ImportKind = enum(u8) { - // An entry point provided by the user entry_point, diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index b7d4eeb945..6d2c5221cc 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -807,6 +807,26 @@ pub const Resolver = struct { const tracer = bun.tracy.traceNamed(@src(), "ModuleResolver.resolve"); defer tracer.end(); + // Only setting 'current_action' in debug mode because module resolution + // is done very often, and has a very low crash rate. + const prev_action = if (Environment.isDebug) bun.crash_handler.current_action; + if (Environment.isDebug) bun.crash_handler.current_action = .{ .resolver = .{ + .source_dir = source_dir, + .import_path = import_path, + .kind = kind, + } }; + defer if (Environment.isDebug) { + bun.crash_handler.current_action = prev_action; + }; + + if (Environment.isDebug and bun.CLI.debug_flags.hasResolveBreakpoint(import_path)) { + bun.Output.debug("Resolving {s} from {s}", .{ + import_path, + source_dir, + }); + @breakpoint(); + } + const original_order = r.extension_order; defer r.extension_order = original_order; r.extension_order = switch (kind) { @@ -1143,13 +1163,13 @@ pub const Resolver = struct { pub fn resolveWithoutSymlinks( r: *ThisResolver, source_dir: string, - import_path_: string, + input_import_path: string, kind: ast.ImportKind, global_cache: GlobalCache, ) Result.Union { assert(std.fs.path.isAbsolute(source_dir)); - var import_path = import_path_; + var import_path = input_import_path; // This implements the module resolution algorithm from node.js, which is // described here: https://nodejs.org/api/modules.html#modules_all_together @@ -1375,7 +1395,10 @@ pub const Resolver = struct { } // Check for external packages first - if (r.opts.external.node_modules.count() > 0) { + if (r.opts.external.node_modules.count() > 0 and + // Imports like "process/" need to resolve to the filesystem, not a builtin + !strings.hasSuffixComptime(import_path, "/")) + { var query = import_path; while (true) { if (r.opts.external.node_modules.contains(query)) { diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index c7b4f1304e..6902bb7868 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -1443,6 +1443,20 @@ describe("bundler", () => { "/entry.ts": [`"Y" has already been declared`], }, }); + itBundled("edgecase/BuiltinWithTrailingSlash", { + files: { + "/entry.js": ` + import * as process from 'process/'; + console.log(JSON.stringify(process)); + `, + "/node_modules/process/index.js": ` + export default { hello: 'world' }; + `, + }, + run: { + stdout: `{"default":{"hello":"world"}}`, + }, + }); // TODO(@paperdave): test every case of this. I had already tested it manually, but it may break later const requireTranspilationListESM = [ From 79d21a0d02882e022dd9c29f3fae703cbfea5a4f Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 17 Jul 2024 17:20:48 -0700 Subject: [PATCH 013/123] Bump internal bun versions --- .github/workflows/release.yml | 8 ++++---- .github/workflows/run-format.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 495d236073..5c31ba8589 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,7 +63,7 @@ jobs: - name: Setup Bun uses: ./.github/actions/setup-bun with: - bun-version: "1.0.21" + bun-version: "1.1.20" - name: Install Dependencies run: bun install - name: Sign Release @@ -88,7 +88,7 @@ jobs: - name: Setup Bun uses: ./.github/actions/setup-bun with: - bun-version: "1.0.21" + bun-version: "1.1.20" - name: Install Dependencies run: bun install - name: Release @@ -117,7 +117,7 @@ jobs: if: ${{ env.BUN_VERSION != 'canary' }} uses: ./.github/actions/setup-bun with: - bun-version: "1.0.21" + bun-version: "1.1.20" - name: Setup Bun if: ${{ env.BUN_VERSION == 'canary' }} uses: ./.github/actions/setup-bun @@ -259,7 +259,7 @@ jobs: - name: Setup Bun uses: ./.github/actions/setup-bun with: - bun-version: "1.0.21" + bun-version: "1.1.20" - name: Install Dependencies run: bun install - name: Release diff --git a/.github/workflows/run-format.yml b/.github/workflows/run-format.yml index 2a947cc5a2..28fd7a9804 100644 --- a/.github/workflows/run-format.yml +++ b/.github/workflows/run-format.yml @@ -29,7 +29,7 @@ jobs: - name: Setup Bun uses: ./.github/actions/setup-bun with: - bun-version: "1.1.18" + bun-version: "1.1.20" - name: Setup Zig uses: goto-bus-stop/setup-zig@c7b6cdd3adba8f8b96984640ff172c37c93f73ee with: From ecf5aea071b9ad3e9dde4cecd2402320f9d3d57c Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 17 Jul 2024 18:56:22 -0700 Subject: [PATCH 014/123] Ensure undici primordials are pristine (#12635) --- src/bun.js/bindings/Undici.cpp | 59 +++++++++++++++++++ src/bun.js/bindings/Undici.h | 6 ++ src/js/thirdparty/undici.js | 49 +++++++-------- .../undici/undici-primordials.test.ts | 26 ++++++++ 4 files changed, 117 insertions(+), 23 deletions(-) create mode 100644 src/bun.js/bindings/Undici.cpp create mode 100644 src/bun.js/bindings/Undici.h create mode 100644 test/js/first_party/undici/undici-primordials.test.ts diff --git a/src/bun.js/bindings/Undici.cpp b/src/bun.js/bindings/Undici.cpp new file mode 100644 index 0000000000..879b9f4fe2 --- /dev/null +++ b/src/bun.js/bindings/Undici.cpp @@ -0,0 +1,59 @@ +#include "root.h" + +#include "JSDOMURL.h" +#include "JSURLSearchParams.h" +#include "JSAbortSignal.h" +#include "JSDOMGlobalObjectInlines.h" +#include "ZigGlobalObject.h" + +#include "JSFetchHeaders.h" +#include "JSDOMFormData.h" +#include "JavaScriptCore/ObjectConstructor.h" + +#include "helpers.h" +#include "BunClientData.h" + +#include "JavaScriptCore/AggregateError.h" +#include "JavaScriptCore/JSFunction.h" +#include "JSDOMFile.h" + +namespace Bun { + +using namespace JSC; +using namespace WebCore; + +// Ensure overriding globals doesn't impact usages. +JSC::JSValue createUndiciInternalBinding(Zig::GlobalObject* globalObject) +{ + auto& vm = globalObject->vm(); + + auto* obj = constructEmptyObject(globalObject); + obj->putDirectIndex( + globalObject, 0, + globalObject->JSResponseConstructor()); + obj->putDirectIndex( + globalObject, 1, + globalObject->JSRequestConstructor()); + obj->putDirectIndex( + globalObject, 2, + WebCore::JSFetchHeaders::getConstructor(vm, globalObject)); + obj->putDirectIndex( + globalObject, 3, + WebCore::JSDOMFormData::getConstructor(vm, globalObject)); + obj->putDirectIndex( + globalObject, 4, + globalObject->JSDOMFileConstructor()); + obj->putDirectIndex( + globalObject, 5, + JSDOMURL::getConstructor(vm, globalObject)); + obj->putDirectIndex( + globalObject, 6, + JSAbortSignal::getConstructor(vm, globalObject)); + obj->putDirectIndex( + globalObject, 7, + JSURLSearchParams::getConstructor(vm, globalObject)); + + return obj; +} + +} \ No newline at end of file diff --git a/src/bun.js/bindings/Undici.h b/src/bun.js/bindings/Undici.h new file mode 100644 index 0000000000..5bc89e5a29 --- /dev/null +++ b/src/bun.js/bindings/Undici.h @@ -0,0 +1,6 @@ + +namespace Bun { + +JSC::JSValue createUndiciInternalBinding(Zig::GlobalObject* globalObject); + +} \ No newline at end of file diff --git a/src/js/thirdparty/undici.js b/src/js/thirdparty/undici.js index ec0096ff84..b194df4db5 100644 --- a/src/js/thirdparty/undici.js +++ b/src/js/thirdparty/undici.js @@ -7,19 +7,22 @@ const ObjectCreate = Object.create; const kEmptyObject = ObjectCreate(null); var fetch = Bun.fetch; -var Response = globalThis.Response; -var Headers = globalThis.Headers; -var Request = globalThis.Request; -var URLSearchParams = globalThis.URLSearchParams; -var URL = globalThis.URL; -class File extends Blob {} +const bindings = $cpp("Undici.cpp", "createUndiciInternalBinding"); +const Response = bindings[0]; +const Request = bindings[1]; +const Headers = bindings[2]; +const FormData = bindings[3]; +const File = bindings[4]; +const URL = bindings[5]; +const AbortSignal = bindings[6]; +const URLSearchParams = bindings[7]; + class FileReader extends EventTarget { constructor() { throw new Error("Not implemented yet!"); } } -var FormData = globalThis.FormData; function notImplemented() { throw new Error("Not implemented in bun"); } @@ -301,28 +304,28 @@ Undici.buildConnector = Undici.fetch = fetch; export default { + Agent, + BalancedPool, + Client, + connect, + Dispatcher, fetch, - Response, - Headers, - Request, - URLSearchParams, - URL, File, FileReader, FormData, - request, - stream, - pipeline, - connect, - upgrade, - MockClient, - MockPool, + Headers, MockAgent, + MockClient, mockErrors, - Dispatcher, + MockPool, + pipeline, Pool, - BalancedPool, - Client, - Agent, + request, + Request, + Response, + stream, Undici, + upgrade, + URL, + URLSearchParams, }; diff --git a/test/js/first_party/undici/undici-primordials.test.ts b/test/js/first_party/undici/undici-primordials.test.ts new file mode 100644 index 0000000000..dad454a42d --- /dev/null +++ b/test/js/first_party/undici/undici-primordials.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect, beforeAll, afterAll } from "bun:test"; + +it("undici", () => { + const { Response, Request, Headers, FormData, File, URL, AbortSignal, URLSearchParams } = globalThis; + globalThis.Response = + globalThis.Request = + globalThis.Headers = + globalThis.FormData = + globalThis.File = + globalThis.URL = + globalThis.AbortSignal = + globalThis.URLSearchParams = + 42; + + const undici = require("undici"); + expect(undici).toBeDefined(); + expect(undici.Response).toBe(Response); + expect(undici.Request).toBe(Request); + expect(undici.Headers).toBe(Headers); + expect(undici.FormData).toBe(FormData); + expect(undici.File).toBe(File); + expect(undici.URL).toBe(URL); + expect(undici.URLSearchParams).toBe(URLSearchParams); + + // Note: AbortSignal is not exported. It's just used internally. +}); From cc42052039fa652dd716fe009c48bbd1aea77af8 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 17 Jul 2024 18:57:03 -0700 Subject: [PATCH 015/123] `node-fetch` polyfill shouldn't break when web globals are overriden (#12634) --- src/bun.js/bindings/NodeFetch.cpp | 50 ++++++++++++++++++ src/bun.js/bindings/NodeFetch.h | 7 +++ src/js/thirdparty/node-fetch.ts | 16 +++--- test/bun.lockb | Bin 325027 -> 325186 bytes .../node/http/node-fetch-primordials.test.ts | 20 +++++++ test/package.json | 3 ++ 6 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 src/bun.js/bindings/NodeFetch.cpp create mode 100644 src/bun.js/bindings/NodeFetch.h create mode 100644 test/js/node/http/node-fetch-primordials.test.ts diff --git a/src/bun.js/bindings/NodeFetch.cpp b/src/bun.js/bindings/NodeFetch.cpp new file mode 100644 index 0000000000..1b25af677d --- /dev/null +++ b/src/bun.js/bindings/NodeFetch.cpp @@ -0,0 +1,50 @@ +#include "root.h" +#include "JSDOMGlobalObjectInlines.h" +#include "ZigGlobalObject.h" + +#include "JSFetchHeaders.h" +#include "JSDOMFormData.h" +#include "JavaScriptCore/ObjectConstructor.h" + +#include "helpers.h" +#include "BunClientData.h" + +#include "JavaScriptCore/AggregateError.h" +#include "JavaScriptCore/JSFunction.h" +#include "JSDOMFile.h" + +namespace Bun { + +using namespace JSC; +using namespace WebCore; + +// Ensure overriding globals doesn't impact usages. +JSC::JSValue createNodeFetchInternalBinding(Zig::GlobalObject* globalObject) +{ + auto& vm = globalObject->vm(); + + auto* obj = constructEmptyObject(globalObject); + obj->putDirectIndex( + globalObject, 0, + globalObject->JSResponseConstructor()); + obj->putDirectIndex( + globalObject, 1, + globalObject->JSRequestConstructor()); + obj->putDirectIndex( + globalObject, 2, + globalObject->JSBlobConstructor()); + obj->putDirectIndex( + globalObject, 3, + WebCore::JSFetchHeaders::getConstructor(vm, globalObject)); + + obj->putDirectIndex( + globalObject, 4, + WebCore::JSDOMFormData::getConstructor(vm, globalObject)); + obj->putDirectIndex( + globalObject, 5, + globalObject->JSDOMFileConstructor()); + + return obj; +} + +} \ No newline at end of file diff --git a/src/bun.js/bindings/NodeFetch.h b/src/bun.js/bindings/NodeFetch.h new file mode 100644 index 0000000000..2b20c2d0be --- /dev/null +++ b/src/bun.js/bindings/NodeFetch.h @@ -0,0 +1,7 @@ +#include "config.h" + +namespace Bun { + +JSC::JSValue createNodeFetchInternalBinding(Zig::GlobalObject*); + +} \ No newline at end of file diff --git a/src/js/thirdparty/node-fetch.ts b/src/js/thirdparty/node-fetch.ts index 788ee6600f..d2b5831690 100644 --- a/src/js/thirdparty/node-fetch.ts +++ b/src/js/thirdparty/node-fetch.ts @@ -1,13 +1,13 @@ import type * as s from "stream"; -const { - Headers: WebHeaders, - Request: WebRequest, - Response: WebResponse, - Blob, - File = Blob, - FormData, -} = globalThis as any; +// Users may override the global fetch implementation, so we need to ensure these are the originals. +const bindings = $cpp("NodeFetch.cpp", "createNodeFetchInternalBinding"); +const WebResponse: typeof globalThis.Response = bindings[0]; +const WebRequest: typeof globalThis.Request = bindings[1]; +const Blob: typeof globalThis.Blob = bindings[2]; +const WebHeaders: typeof globalThis.Headers = bindings[3]; +const FormData: typeof globalThis.FormData = bindings[4]; +const File: typeof globalThis.File = bindings[5]; const nativeFetch = Bun.fetch; // node-fetch extends from URLSearchParams in their implementation... diff --git a/test/bun.lockb b/test/bun.lockb index b3cdc087159e70e93276956dad549dbec48565c4..b2294918f4e0c33be8a4cf4e74222a3002c556e4 100755 GIT binary patch delta 28247 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 TyKqscXfQ}zN_qOdf%d-uH(aQ7 delta 28201 zcmb`w2Uu0d_BXuuIUHrHsE85qsE8$2R75=BQMTAoL9s;CSP&3Vnt%#6u!|Mk>S*jG zF=~t&3-)dlI~p~Ky?2c*u_W>Rt(mBf1Px^)g(zpkpDraOF-R$HPE7!g%Bq;!;ETXU%s0;8~P!f^`W~wwix_?qcS&}LM2ZELZosG(Fprb)Q1FeZl zR4)d&4Cn>`lGhTr3TQP@PtZe!C8?AoWn?~sbQ0JXU8w>3IRuvlU5#2+&}>lBK3%7F z(DK0DK`Von06&#SL?$Ma8Qw!MDUuqUoD!WZNry}FhB8~DBcxarbe%esU5Gl~Maujh z45X;x%P`!Sj*4mvO*#d|83v5Gl9xyeS1xf;~er8a*sJAvHyk@-YA; zV0dg?RAfYQ6ndXZZalw|XrP;yC?6b~5apdXKxz$49f(ay>^~?vGW7uJQ;(CQBL^nN zCQHfDDT#5zqNVz9GtVX{_#6r9QCU#k#EHY?5!P~h9NSWV(lICAj6Bb*lxjMNk z{XZ9|yXNEMnXv#oWd7*1=*S2t*#WLfSOuj{_=1v*q=?jk-h-plYXFn=E9i9iz}U!v zs27_8381%Yiq34SCF;9@k8~{pO2$e|h)#w5Bp5L^(tAKgf^b(UbF!}VP*588C{UWr z(Fwz169yzoA;9Fd{-AKR%we&q(J8R&in>DKFY1W~QetB`e>U<-P6bd>I1`ktI-|Z& zOvD8YXL58zWU6;;N+1}Fo>EVefs3O;J5aJjvjzgkB@RuZ@*}|9LMW#OQhh~(Dbex6 zXnw{=`FRhFPLqNgit@O`sE8DpDhhn0NH0(YWtq__LsMhpQl!q50ldpk&?N6T7%5e1 z*hmP7(iN(q$%WhlhS)?d4a~5JI4LSRI>|eFNZbHDzdI;-Rd-!|H&8whT3Dfa@+56+ zA@__A@Fue9T~L|=XLPzlrz>?j6O?Q|0<<*f0G)Q$X`oJP>9mYaO*(yM7xgZKx}p9t zL2G4h)iYM;G#iu#ZH!LiblOu3D(oqr)FKMI$I9pkndz;!psBG5=}C!+aVe=$-f$Kv z5%G}9)ixsD?gFJw#wA7$9uONB{WUOkc3oTHV9P*h{5l7V@lQ#ONFACIozhafS=e39 z(%u!WE;rP?oZV~ILQOKm`qo0rUZCWn?Llcoy+O(38?*>#_katngdXV;adENm)QCaD z;5G1}j=~5=AgvT4_}(F(YKLl@oU3Py3lrXz4obaA(rKig-vP8Z^51n9(%jHC;cI%U zM(fdONr)dQ(!;KT?`Kd~@Erpsop*v#z4f5v&C5YC3yNfZi3FJ?Sc_2HmAN82Ge#S$ zwDHN((a?mXSegr?b)2l%S%Yp;-IJi?bA_~bif6{FULto$3Yh^R<4a-ipFl~4qoAZh za&N&G3rbzzi+rM6bvm+-=*n-Pw4la zb1mu~HXG(37pGuI%i|vy7s7XRny=FaF=A0(4oZ9#5E;lzQBqAmsu7CKMX4it5-X~=pOCu#-8i&8`8Szt1_k0uv$&lm?T znh)`yXk@pw!&Qbm0N9DJiK~YvNx5lRfW% z(kP^)3{1lCNok{n+(hkwi>G{7yXjIrI4wiSI0#Bz8wW~Vy`;-Ho+-*t1Je}w0hHR# z9xG%=q@*WA4vtMdpoJCpl-FoOino!^Ydea2di*d^jMBeHpK02Q;`M4?oh;gmMl7X1 zqzsFWOO2MKFZI}ZV~WUcrUjSq3^H1`WeF`l2c-c;e|dQL)c(E8G~sEQwx)z<#_H*! zZ${)Y;^w#+LaMRPZ3G=?4y>CgXa;C`;DtKgI7_(4GEm|hPGgV%ymj5LWee>tfRfF| zTLe zhb@34tw%ZGsMrAmNZKOpW=Z#q-{%Vh+ywm$rDs9OaE}%UDXA%Ov4}3xcI1~uel5^S zpi4l@fjWa$2Av8@Q}ziHrbq=Qtxf}11nr~ajXG{8_%kx+=oN;8Vw;;84oZiZ7NDe1 z9Z+{rFHrKk$i%qBWGK{gnedNqK#L>)Ehv>IC&tAMO_HP?D}=nopfom_I*tML01gJN zD7D9_0g1}Ua0aD@A1)X41So0#osP3XNzhPGD`*8-j6+g#VmyU#rUjL9&j|Zg*exkB zB{c@io&qkiOA>-C{g<{zOueL-qzJ5uQc{d}YI;(1O7vP`_q5dL1X@DEfoY1i1EuaY z(JA<26H@)9c*vpd0@Kbz>L=tD&FrE#@KMrkmHJ!`&=fZ{VCs4yec48#%`8wF<*}e7 zJw>M%HwjGPi1ZtvjdgSH(F3KVOZUy9RtmN;P+fWkOd;V8Xc5q1TLc}H(m#RvYD7~b zy0!sdN$?rdtCRM^%{@4Go5=kZlmbV$T+y>7It}<<$0(;(lDy+1k|2HBcESG`l(f5{ zg_U;C@YpH1&7kCeMsRt*Lrk^KyF_cPK&h5-MmGY4agaZ>TUf$4yFWsG(%m?G42MPC zfD=F!(D?L}A#n|)TGH=(#oWION+w>iPbhQ+m>AFN^n&L4nPQSrYsN*WM6Qr~UeNDBsq4luZQ@V5tAO&Z>!&pg3Fq5) zL5zt7f~a2qC%Lsg;TWpMa;V#P_q3Toj*FI|4_+!vr_LFej&IbEl%9|-rV14?|Q zbo%bTaI+gaJqAkkHwv1Oxc~`wWQ+$b1DXg*euu3US5ti|{Mb{cAD;+(3zXKsA3a+(aDWZZ>1EG*t3e@8dVgEvJ#pwJFOrvyR#1AESe(nb$&-$n6U{O%wFQ%7+NS^^@Mq-ZoSk7tEro`mz%Mx+9@5U>@6O}g^D$w&?2sTWAK zP|dW3SQHer+PY4E0F5U^Ijv>hf;E9Eh|u!H)(nJdq-1LRZiD}@%N&Zg}-&R zJXKW&Vc@B6vgYbN4m^viKqUE>ocLrz%VAk>>w3Vi`Q00kQa0}J+;xlb(i$F^?OgMk-)Wasu zNOS_Hq~)}*S=Ix6sf9GR%ZirOQk6SudH6e5Q(CFYJ9k(YoX%SQ8GmQEr9S{h1fxC_ zh?+AIlDBF~Yt{6=tk$k|plsK&TC2(|tT%{Y=w!alCLh<7HmY1$3&!6rT2>oXS%hiR zhzkhY=C3JjRmIhcu|tko3xkt&*AeQYxy3-FiO~5bP;>2+zg?*U|0gwI0zaEF42awm zDz^1^0%**cp6J?yV{9Re6~f++H=ktf)Hu=7mg*>lHlGG4+!s^{LB}7$LAdd_MKJ?>Pp!z^){ItoOfgIz^y*C|kGR8{B&In8a#a3E@lJDjouNbiT;rj)5>_)Qo%BY?=I#MEA=mtopt z3?2i~h{3w3=k>XPs9rn})f3LKPDi5NLm=)rEXOSSJ6AWxoyN605CsL;%5Jk{0d>@7 zw6iNe>-l0_`__=8P{HL7j|FN@9;-Y@s+njNmabRRFc)g{z9yTatny1m)NLwcsAAyjjcYp(@VxjGntr z^(R0BC-FgD03;?5seJ~h6&H{bWRpv4c|BE22l!o(Hlv5#@)c4oG?!L(NB4wmeD|>OFNOWQJ0h#Na6qJMt+dZQr(6r ztIcR(S4JTv+}af*y%UJIWGyV$-x)xJ!>|CGQUgiR1Zg$^hHIu&iVL?9KyQ{OvM9~`N+P+OGFMXEjLyMa_NPt{b7 zRI($r%aL-zN-!k3~u6tO-matF42+N=1BeJNVSBOT5-NCN9wdA<<>$k zm1DH5!B{|mDX_xG9c{`5pt>C8MA;N`ONZN>@z=8AR5?YhJMZc{>mXl$|ggxlmCO-WFd?rp^6D?)Zx&H<4-7tvfB+bk8~qnPJy?8+BNwd45| z7I*822Q;M+<}|ql5WKPv5Ntwz2ya>fq>pMNo8=-9HZmB`au6o+!!U_D15q!9gmFMH z2y`GnJq^^5>tJecOj@)f2WojkR3!;{R8xfVWk9qv!msQ$SRgA|m5Xb6_}ffVQdDIo zx<Q#Cau`uAuVoF#&Lhmoqo{UNQ_@sL>1>2fGF%IwCYnoMyPTorrK$2RO-WbfVp?#z zYU$O5g665YO`MUyWXg2WasqAGbSfiMxs(<>LRJ3kD)g7M{8XFLuA3NtF{*QbMEG(| zvstbHVN~PnN~JFxK>+*W{#xEhRauEV@(y?~g2qiCvK@Mfj#;~lPCzQ=d>1WmlxmrP zyk^>|AiHuHDc!GH*c4N^!Hb2@(io@__oO7GD6(L^z=mao7Cc5(enMUy4*d8_1IQ-K=ro$YQdZ>*~P4NOvCPxw<4EqI)&4E@q*2X<6$0g2Tu42ya7 z-VR$P+mz8j1yp&IJO&^8J8lCW!ui z2UK4m%+f1BI1dEal{)<#^Qn_f9--w;RFy5rqqY%IAoUp#IVxXB<*HiNBvpxu5VbHO zy=}@eAhN88KtBPAmRu1locbH{K7Wi2?>F-%t4b#FD1Inf*gSvdNYQO4zVjUfL=y&6 z4HIS~5GDo?8BdB5We9CBNoyc-P$!MT%S;{dGRsBce4_sIAi8TTMIml2gc!p|Ok30mwP`l+3QjPeus5HnJe7Kp?6Nq{$0^nvK#7hJ? z()ceR3Wt2(uCy9#I9m?nAarf90tK1nDf@lD7yAetpmDBNbr1400dw=0$*qK#sj zYl=t;mjLgm#tdf9IS`Q#w;ccW&vT*0xxRPd48^{R7??$Af!zO za+ut583=9Spy{q<%~P=n&YOqrX6nDrh+8@m2Z#DY#Y}|G7_w17^?4s*VEH>unXe*# z1oadCVt_ z@@OEM$Ow32Y|0)TK_Z6cFQB^G3@nqiM~Z-q#S~kb?m*;GCg$pdQxp(&5(6G+Q+@#o z1rokpeU#`C27aBtGeC0`@$Olc1BqQmlhKmo%k#+y6M#q|?x6BvEqJM_d_q5FcVwP6Ltd z;$&YW^Iw`)+5wS7G>UW56fJlKmMr8EKVPzxl4Hea0AW+s4v4G*#o;1LfLd}ylj0VT z-hqxbOSy3{gmy}`D_bNeh`Sl90ma^RFO$gnXj!awXAPdWiBuo4@$#8 z`9Ne6m}GFU3rL<@C@2+`ArehFw?NL8lvT` zRh4g$M@E91Vn8109M0Tp+GQC-M?N$Ohzu`$aVJnC+PzwSL#n>!l7?Nu6o;AqR=)7*Zj!dlDNll!m zOOlFGY@eadNT51E-1y25I>M|${Jy3m9Fx)QA~Os>C)5^*IxL288jy$w)WrjOnTYfs zfQ0c~=VJ=ZbgaCH0lOS1KiOt6&yu8WG;B&ZQZ$@}_~!B;PzNC4`^B?G8O)DO{1;mA z52~^ddAc?oZKmhh+VLL(m4F;$O!9xQDbs-L5W*c-{!z=?j6E0f$PD+6#X8z4Kgb!-2#GBpj$IN91EmfkZ%{Ie{~BuBy1q6M5M3pa;P~ z`n-b9=|Ch)^mQkY@Ip9&>DfH(`1U|ckp-}`c51ub@&!_%Jhcib?1r}6P45( z$h(D9XI|QH6+K-hzHFqTcVGH7Fz>LT8~{XX1ot@QTODyJ@*^$yn5y`# z7t0!+T_EnS1`^s(tH0<7+Z6Pt==Wk3K)^x&TLS4`(b?Y_KsQY@JpR&=IMTh;k=Ux$ z+8~S~I?@NIAV@3+qRCj8H<157I4v8+q{kr}YHk4{nKrMwiVD{NM zLKlGUILat?*Z4uSj+Ggm?FU4rf@Wx81&}Y0SnV(9oO}u>E}KQr_B+Pgj3KPAd2n6A?j@t{SvLm0MY)Vlooc{-x+{}h)$NsbsRp(06;XN zVohD6BP?@RQ}63Y%#v!`#UKj5=mFHgQSU3D)RAvHs2g)OCehv&0@FoROMcfT&EkeGU*A5Y3JDch-xrz(b<@Ua`3r zR*428E%}pTWhqcYUX3=0&ilk-jsD;XN>?BpgB#nGHAqoYVpDJ*NJyZy%xk|ua7Z|E zFCC$0XZ)Q2aKgoVjy$Buo3I;!4_gix+qV2*f9HdaHc=D;r27Fzel8GAC&ceoHhi&| zby-z<9TGN&r@#msfSPd*tc8~6Kscc-w_9o-rlk*0BTYjNYwel`${RH0s;U${B9ui3 zp?WJFL0O2puOn<4fV_S*x=aD5FA#N^8&qDPWnEL1UyvulF ztxic&f&*>-AIR(Uzm~lL8VFA8knoJF_ZboI#U8BESxM@TGI$*hj2ci2AYu6DKwW^u ztF7kwqF+Vn39Kapr~}^^pFpZUxWvBg15isK3}hTa>p92VhQ*cvVKW_WS8gChb#TrI zw<#sh3%3({;#3jP)Ss>~oKNOoP zT+*ic2P)k!i3;d=3v7&mXkua`@YdfMpb-G~8%6n9$l~8ISXu$8n#)&qg(2k&F8JCH zHp_7!euhz`UxY_uDOq8&gaL`Cr^}F{xCwro&h7zmZ)%>!NH`9^&6{&Wduj&${WVgbVfi|5e+TJURC$wHp+ zM8sP8l$Q5emCI<#8@yV)B|MoQ*5nde7V?_i7C{4Z_!fo_s0kl2iV5d|C<+LVbh{($ zF3tlzfQVBJ`&=OM8q75EEua7(%oqf&;&&ZV5Klva{85G(fLT8Yh~@+0IG)~a1EM%1 zqWa%Jkw9WA5phq1f6{v@H>dz+5&&62=J=*W@1khIt>A-XQ8q7+s-^2+-w6Pt?AxLq1 zfdj6Cb0AP}fzUnx4GxAQ-X_ah-bd9E@(Q0eXs15fEt`?*$5U>v$!L@}1gS51>Xai@ z^9`LosC2v|brmT*Yet^m@BFa#(Jmik!6sQ%s{A1uMbzAfk09O}{z2p1`G2694nzyp zaUhCxVq;hAow3ZhqFy@(f)vXXAk3#uO`MVF;HXmWy$}FL!P5055IHTJ2;q7W5T-Of z`MJZgEYPyn2eCcon{0U~QwkyT7&2R+4nDYwvsr5XNnw;;Nen=WmPj6gl+{4QRZ7d5 zYLg$a;KH(MY5f<)s~Iir@+g*v40-Ql#aYf(oK1=0UdAtOf3t{h*i4cn8Na@LQV5@w z<%ab)Wyo5lpA#$J-{frhwgkS}WXN=9&H9^)$%(lk{Y^1u5961ck#Vtg2c<|73F5!v zWI2OO_j̯Dq_7gQ(xKTWlhokpP(g4{B>TX$sjcOOC5ae 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 diff --git a/test/js/node/http/node-fetch-primordials.test.ts b/test/js/node/http/node-fetch-primordials.test.ts new file mode 100644 index 0000000000..de674023c3 --- /dev/null +++ b/test/js/node/http/node-fetch-primordials.test.ts @@ -0,0 +1,20 @@ +import { test, expect } from "bun:test"; + +test("fetch, Response, Request can be overriden", async () => { + const { Response, Request } = globalThis; + globalThis.Response = class BadResponse {}; + globalThis.Request = class BadRequest {}; + globalThis.fetch = function badFetch() {}; + + const fetch = require("node-fetch").fetch; + + using server = Bun.serve({ + port: 0, + async fetch(req) { + return new Response("Hello, World!"); + }, + }); + + const response = await fetch(server.url); + expect(response).toBeInstanceOf(Response); +}); diff --git a/test/package.json b/test/package.json index 3008ded2ec..e0a9a275a3 100644 --- a/test/package.json +++ b/test/package.json @@ -61,5 +61,8 @@ "private": true, "scripts": { "typecheck": "tsc --noEmit" + }, + "resolutions": { + "react": "../node_modules/react" } } From b1dce1e2410809a22cd031a251f7121c886b8949 Mon Sep 17 00:00:00 2001 From: HibanaSama <48864283+HibanaSama@users.noreply.github.com> Date: Thu, 18 Jul 2024 07:40:25 +0500 Subject: [PATCH 016/123] build(windows): fix esbuild errors when bundling node-fallbacks (#12628) --- scripts/make-old-js.ps1 | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scripts/make-old-js.ps1 b/scripts/make-old-js.ps1 index b59d95f867..b3898ef758 100755 --- a/scripts/make-old-js.ps1 +++ b/scripts/make-old-js.ps1 @@ -5,11 +5,20 @@ $npm_client = "npm" $root = Join-Path (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) "..\" # search for .cmd or .exe -$esbuild = Join-Path $root "node_modules\.bin\esbuild.cmd" -if (!(Test-Path $esbuild)) { - $esbuild = Join-Path $root "node_modules\.bin\esbuild.exe" +function Get-Esbuild-Path { + param( + $Path + ) + + $Result = Join-Path $Path "node_modules\.bin\esbuild.cmd" + if (Test-Path $Result) { + return $Result + } + + return Join-Path $Path "node_modules\.bin\esbuild.exe" } +$esbuild = Get-Esbuild-Path $root $env:NODE_ENV = "production" @@ -34,5 +43,5 @@ Pop-Location # node-fallbacks Push-Location src\node-fallbacks & ${npm_client} install -& ${esbuild} --bundle @(Get-Item .\*.js) --outdir=out --format=esm --minify --platform=browser +& (Get-Esbuild-Path (Get-Location)) --bundle @(Get-Item .\*.js) --outdir=out --format=esm --minify --platform=browser Pop-Location From 6ad3e6a5e36c9b8385cbf3bec7a49c960fcdf97b Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 17 Jul 2024 20:53:12 -0700 Subject: [PATCH 017/123] Fixes #2532 (#12633) Co-authored-by: Jarred-Sumner --- src/bun.js/bindings/NodeHTTP.cpp | 31 ++- src/js/node/http.ts | 27 +- test/bun.lockb | Bin 325186 -> 347442 bytes .../node/http/node-http-primoridals.test.ts | 118 ++++++++ .../remix/remix-build/server/index.js | 262 ++++++++++++++++++ test/js/third_party/remix/remix.test.ts | 44 +++ test/package.json | 3 + 7 files changed, 473 insertions(+), 12 deletions(-) create mode 100644 test/js/node/http/node-http-primoridals.test.ts create mode 100644 test/js/third_party/remix/remix-build/server/index.js create mode 100644 test/js/third_party/remix/remix.test.ts diff --git a/src/bun.js/bindings/NodeHTTP.cpp b/src/bun.js/bindings/NodeHTTP.cpp index a49e17adcd..2f5c810ebf 100644 --- a/src/bun.js/bindings/NodeHTTP.cpp +++ b/src/bun.js/bindings/NodeHTTP.cpp @@ -1,4 +1,5 @@ #include "root.h" +#include "JSDOMGlobalObjectInlines.h" #include "ZigGlobalObject.h" #include #include "helpers.h" @@ -259,10 +260,11 @@ JSC_DEFINE_HOST_FUNCTION(jsHTTPAssignHeaders, (JSGlobalObject * globalObject, Ca auto& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue requestValue = callFrame->argument(0); - JSObject* objectValue = callFrame->argument(1).getObject(); - - JSC::InternalFieldTuple* tuple = JSC::InternalFieldTuple::create(vm, globalObject->m_internalFieldTupleStructure.get()); + // This is an internal binding. + JSValue requestValue = callFrame->uncheckedArgument(0); + JSObject* objectValue = callFrame->uncheckedArgument(1).getObject(); + JSC::InternalFieldTuple* tuple = jsCast(callFrame->uncheckedArgument(2)); + ASSERT(callFrame->argumentCount() == 3); JSValue headersValue = JSValue(); JSValue urlValue = JSValue(); @@ -409,13 +411,28 @@ JSValue createNodeHTTPInternalBinding(Zig::GlobalObject* globalObject) VM& vm = globalObject->vm(); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "setHeader"_s)), - JSC::JSFunction::create(vm, globalObject, 3, "setHeader"_s, jsHTTPSetHeader, ImplementationVisibility::Public), NoIntrinsic); + JSC::JSFunction::create(vm, globalObject, 3, "setHeader"_s, jsHTTPSetHeader, ImplementationVisibility::Public), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "getHeader"_s)), - JSC::JSFunction::create(vm, globalObject, 2, "getHeader"_s, jsHTTPGetHeader, ImplementationVisibility::Public), NoIntrinsic); + JSC::JSFunction::create(vm, globalObject, 2, "getHeader"_s, jsHTTPGetHeader, ImplementationVisibility::Public), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "assignHeaders"_s)), - JSC::JSFunction::create(vm, globalObject, 2, "assignHeaders"_s, jsHTTPAssignHeaders, ImplementationVisibility::Public), NoIntrinsic); + JSC::JSFunction::create(vm, globalObject, 2, "assignHeaders"_s, jsHTTPAssignHeaders, ImplementationVisibility::Public), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "Response"_s)), + globalObject->JSResponseConstructor(), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "Request"_s)), + globalObject->JSRequestConstructor(), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "Blob"_s)), + globalObject->JSBlobConstructor(), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "Headers"_s)), + WebCore::JSFetchHeaders::getConstructor(vm, globalObject), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "headersTuple"_s)), + JSC::InternalFieldTuple::create(vm, globalObject->m_internalFieldTupleStructure.get()), 0); return obj; } diff --git a/src/js/node/http.ts b/src/js/node/http.ts index 87e1a47338..18c3c0a107 100644 --- a/src/js/node/http.ts +++ b/src/js/node/http.ts @@ -7,7 +7,21 @@ const { getHeader, setHeader, assignHeaders: assignHeadersFast, -} = $cpp("NodeHTTP.cpp", "createNodeHTTPInternalBinding"); + Response, + Request, + Headers, + Blob, + headersTuple, +} = $cpp("NodeHTTP.cpp", "createNodeHTTPInternalBinding") as { + getHeader: (headers: Headers, name: string) => string | undefined; + setHeader: (headers: Headers, name: string, value: string) => void; + assignHeaders: (object: any, req: Request, headersTuple: any) => boolean; + Response: (typeof globalThis)["Response"]; + Request: (typeof globalThis)["Request"]; + Headers: (typeof globalThis)["Headers"]; + Blob: (typeof globalThis)["Blob"]; + headersTuple: any; +}; const ObjectDefineProperty = Object.defineProperty; const ObjectSetPrototypeOf = Object.setPrototypeOf; @@ -664,10 +678,13 @@ function assignHeadersSlow(object, req) { function assignHeaders(object, req) { // This fast path is an 8% speedup for a "hello world" node:http server, and a 7% speedup for a "hello world" express server - const tuple = assignHeadersFast(req, object); - if (tuple !== null) { - object.headers = $getInternalField(tuple, 0); - object.rawHeaders = $getInternalField(tuple, 1); + if (assignHeadersFast(req, object, headersTuple)) { + const headers = $getInternalField(headersTuple, 0); + const rawHeaders = $getInternalField(headersTuple, 1); + $putInternalField(headersTuple, 0, undefined); + $putInternalField(headersTuple, 1, undefined); + object.headers = headers; + object.rawHeaders = rawHeaders; return true; } else { assignHeadersSlow(object, req); diff --git a/test/bun.lockb b/test/bun.lockb index b2294918f4e0c33be8a4cf4e74222a3002c556e4..9508f9459a97d8e404bb3ee2917e037dd0e7bae5 100755 GIT binary patch delta 61962 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

s5Hlnj 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 delta 48937 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

WNH3 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 { + const Response = globalThis.Response; + const Request = globalThis.Request; + const Headers = globalThis.Headers; + const Blob = globalThis.Blob; + + globalThis.Response = class MyResponse { + get body() { + throw new Error("body getter should not be called"); + } + + get headers() { + throw new Error("headers getter should not be called"); + } + + get status() { + throw new Error("status getter should not be called"); + } + + get statusText() { + throw new Error("statusText getter should not be called"); + } + + get ok() { + throw new Error("ok getter should not be called"); + } + + get url() { + throw new Error("url getter should not be called"); + } + + get type() { + throw new Error("type getter should not be called"); + } + }; + globalThis.Request = class MyRequest {}; + globalThis.Headers = class MyHeaders { + entries() { + throw new Error("entries should not be called"); + } + + get() { + throw new Error("get should not be called"); + } + + has() { + throw new Error("has should not be called"); + } + + keys() { + throw new Error("keys should not be called"); + } + + values() { + throw new Error("values should not be called"); + } + + forEach() { + throw new Error("forEach should not be called"); + } + + [Symbol.iterator]() { + throw new Error("[Symbol.iterator] should not be called"); + } + + [Symbol.toStringTag]() { + throw new Error("[Symbol.toStringTag] should not be called"); + } + + append() { + throw new Error("append should not be called"); + } + }; + globalThis.Blob = class MyBlob {}; + + const http = require("http"); + const server = http.createServer((req, res) => { + res.end("Hello World\n"); + }); + const { promise, resolve, reject } = Promise.withResolvers(); + + server.listen(0, () => { + const { port } = server.address(); + // client request + const req = http + .request(`http://localhost:${port}`, res => { + res + .on("data", data => { + expect(data.toString()).toBe("Hello World\n"); + }) + .on("end", () => { + server.close(); + console.log("closing time"); + }); + }) + .on("error", reject) + .end(); + }); + + server.on("close", () => { + resolve(); + }); + server.on("error", err => { + reject(err); + }); + + try { + await promise; + } finally { + globalThis.Response = Response; + globalThis.Request = Request; + globalThis.Headers = Headers; + globalThis.Blob = Blob; + } +}); diff --git a/test/js/third_party/remix/remix-build/server/index.js b/test/js/third_party/remix/remix-build/server/index.js new file mode 100644 index 0000000000..4e62ccc331 --- /dev/null +++ b/test/js/third_party/remix/remix-build/server/index.js @@ -0,0 +1,262 @@ +import { jsx, jsxs } from "react/jsx-runtime"; +import { PassThrough } from "node:stream"; +import { createReadableStreamFromReadable } from "@remix-run/node"; +import { RemixServer, Outlet, Meta, Links, ScrollRestoration, Scripts } from "@remix-run/react"; +import { isbot } from "isbot"; +import { renderToPipeableStream } from "react-dom/server"; +const ABORT_DELAY = 5e3; +function handleRequest(request, responseStatusCode, responseHeaders, remixContext, loadContext) { + return isbot(request.headers.get("user-agent") || "") + ? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext) + : handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext); +} +function handleBotRequest(request, responseStatusCode, responseHeaders, remixContext) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + /* @__PURE__ */ jsx(RemixServer, { + context: remixContext, + url: request.url, + abortDelay: ABORT_DELAY, + }), + { + onAllReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + responseHeaders.set("Content-Type", "text/html"); + console.log(responseHeaders); + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + pipe(body); + }, + onShellError(error) { + reject(error); + }, + onError(error) { + responseStatusCode = 500; + if (shellRendered) { + console.error(error); + } + }, + }, + ); + setTimeout(abort, ABORT_DELAY); + }); +} +function handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + /* @__PURE__ */ jsx(RemixServer, { + context: remixContext, + url: request.url, + abortDelay: ABORT_DELAY, + }), + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + responseHeaders.set("Content-Type", "text/html"); + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + pipe(body); + }, + onShellError(error) { + reject(error); + }, + onError(error) { + responseStatusCode = 500; + if (shellRendered) { + console.error(error); + } + }, + }, + ); + setTimeout(abort, ABORT_DELAY); + }); +} +const entryServer = /* @__PURE__ */ Object.freeze( + /* @__PURE__ */ Object.defineProperty( + { + __proto__: null, + default: handleRequest, + }, + Symbol.toStringTag, + { value: "Module" }, + ), +); +function Layout({ children }) { + return /* @__PURE__ */ jsxs("html", { + lang: "en", + children: [ + /* @__PURE__ */ jsxs("head", { + children: [ + /* @__PURE__ */ jsx("meta", { charSet: "utf-8" }), + /* @__PURE__ */ jsx("meta", { + name: "viewport", + content: "width=device-width, initial-scale=1", + }), + /* @__PURE__ */ jsx(Meta, {}), + /* @__PURE__ */ jsx(Links, {}), + ], + }), + /* @__PURE__ */ jsxs("body", { + children: [children, /* @__PURE__ */ jsx(ScrollRestoration, {}), /* @__PURE__ */ jsx(Scripts, {})], + }), + ], + }); +} +function App() { + return /* @__PURE__ */ jsx(Outlet, {}); +} +const route0 = /* @__PURE__ */ Object.freeze( + /* @__PURE__ */ Object.defineProperty( + { + __proto__: null, + Layout, + default: App, + }, + Symbol.toStringTag, + { value: "Module" }, + ), +); +const meta = () => { + return [{ title: "New Remix App" }, { name: "description", content: "Welcome to Remix!" }]; +}; +function Index() { + return /* @__PURE__ */ jsxs("div", { + className: "font-sans p-4", + children: [ + /* @__PURE__ */ jsx("h1", { + className: "text-3xl", + children: "Welcome to Remix", + }), + /* @__PURE__ */ jsxs("ul", { + className: "list-disc mt-4 pl-6 space-y-2", + children: [ + /* @__PURE__ */ jsx("li", { + children: /* @__PURE__ */ jsx("a", { + className: "text-blue-700 underline visited:text-purple-900", + target: "_blank", + href: "https://remix.run/start/quickstart", + rel: "noreferrer", + children: "5m Quick Start", + }), + }), + /* @__PURE__ */ jsx("li", { + children: /* @__PURE__ */ jsx("a", { + className: "text-blue-700 underline visited:text-purple-900", + target: "_blank", + href: "https://remix.run/start/tutorial", + rel: "noreferrer", + children: "30m Tutorial", + }), + }), + /* @__PURE__ */ jsx("li", { + children: /* @__PURE__ */ jsx("a", { + className: "text-blue-700 underline visited:text-purple-900", + target: "_blank", + href: "https://remix.run/docs", + rel: "noreferrer", + children: "Remix Docs", + }), + }), + ], + }), + ], + }); +} +const route1 = /* @__PURE__ */ Object.freeze( + /* @__PURE__ */ Object.defineProperty( + { + __proto__: null, + default: Index, + meta, + }, + Symbol.toStringTag, + { value: "Module" }, + ), +); +const serverManifest = { + entry: { + module: "/assets/entry.client-ER-smVHW.js", + imports: ["/assets/jsx-runtime-56DGgGmo.js", "/assets/components-BI_hnQlH.js"], + css: [], + }, + routes: { + root: { + id: "root", + parentId: void 0, + path: "", + index: void 0, + caseSensitive: void 0, + hasAction: false, + hasLoader: false, + hasClientAction: false, + hasClientLoader: false, + hasErrorBoundary: false, + module: "/assets/root-CBMuz_vA.js", + imports: ["/assets/jsx-runtime-56DGgGmo.js", "/assets/components-BI_hnQlH.js"], + css: ["/assets/root-BFUH26ow.css"], + }, + "routes/_index": { + id: "routes/_index", + parentId: "root", + path: void 0, + index: true, + caseSensitive: void 0, + hasAction: false, + hasLoader: false, + hasClientAction: false, + hasClientLoader: false, + hasErrorBoundary: false, + module: "/assets/_index-B6hwyHK-.js", + imports: ["/assets/jsx-runtime-56DGgGmo.js"], + css: [], + }, + }, + url: "/assets/manifest-c2e02a52.js", + version: "c2e02a52", +}; +const mode = "production"; +const assetsBuildDirectory = "build/client"; +const basename = "/"; +const future = { + v3_fetcherPersist: true, + v3_relativeSplatPath: true, + v3_throwAbortReason: true, + unstable_singleFetch: false, + unstable_fogOfWar: false, +}; +const isSpaMode = false; +const publicPath = "/"; +const entry = { module: entryServer }; +const routes = { + root: { + id: "root", + parentId: void 0, + path: "", + index: void 0, + caseSensitive: void 0, + module: route0, + }, + "routes/_index": { + id: "routes/_index", + parentId: "root", + path: void 0, + index: true, + caseSensitive: void 0, + module: route1, + }, +}; +export { serverManifest as assets, assetsBuildDirectory, basename, entry, future, isSpaMode, mode, publicPath, routes }; diff --git a/test/js/third_party/remix/remix.test.ts b/test/js/third_party/remix/remix.test.ts new file mode 100644 index 0000000000..16667e0f11 --- /dev/null +++ b/test/js/third_party/remix/remix.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect, test } from "bun:test"; +test("remix works", async () => { + process.env.PORT = "0"; + process.exitCode = 1; + process.env.NODE_ENV = "production"; + process.env.HOST = "localhost"; + process.argv = [process.argv[0], ".", require("path").join(__dirname, "remix-build", "server", "index.js")]; + const http = require("node:http"); + const originalListen = http.Server.prototype.listen; + let { promise, resolve, reject } = Promise.withResolvers(); + http.Server.prototype.listen = function listen(...args) { + setTimeout(() => { + resolve(this.address()); + }, 10); + return originalListen.apply(this, args); + }; + + require("@remix-run/serve/dist/cli.js"); + + // Wait long enough for the server's setTimeout to run. + await Bun.sleep(10); + + const port = (await promise).port; + + ({ promise, resolve, reject } = Promise.withResolvers()); + let chunks = []; + const req = http + .request(`http://localhost:${port}`, res => { + res + .on("data", data => { + chunks.push(data); + }) + .on("end", () => { + resolve(); + }) + .on("error", reject); + }) + .end(); + + await promise; + const data = Buffer.concat(chunks).toString(); + expect(data).toContain("Remix Docs"); + process.exitCode = 0; +}); diff --git a/test/package.json b/test/package.json index e0a9a275a3..96c4bd14c1 100644 --- a/test/package.json +++ b/test/package.json @@ -11,6 +11,8 @@ "@grpc/proto-loader": "0.7.10", "@napi-rs/canvas": "0.1.47", "@prisma/client": "5.8.0", + "@remix-run/react": "2.10.3", + "@remix-run/serve": "2.10.3", "@resvg/resvg-js": "2.4.1", "@swc/core": "1.3.38", "@types/ws": "8.5.10", @@ -23,6 +25,7 @@ "express": "4.18.2", "fast-glob": "3.3.1", "iconv-lite": "0.6.3", + "isbot": "5.1.13", "jest-extended": "4.0.0", "jsonwebtoken": "9.0.2", "jws": "4.0.0", From 6b50deb7b74d07850b2f892fe60a38f89e27b1bb Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 18 Jul 2024 01:13:36 -0700 Subject: [PATCH 018/123] Move dev dependencies to `"devDependencies"` Spammy vulnerability scanning software can't tell we aren't using these in the "bun" npm package. --- bun.lockb | Bin 183434 -> 184146 bytes package.json | 6 ++---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/bun.lockb b/bun.lockb index 7981e9c2c97cf7b32ad0d6ea1029515f4742a246..0e38e94cfcedb288a288b7865dd24efd0f27e456 100755 GIT binary patch delta 44437 zcmeFaXIK==);2sn0y63l6eS1(q6jKT6cic2far)~L@|;iXHZadz=%1N*oa|^5hG^B zoW-0IiaBR7=kH$AJ#NlEKIeO%>w15@KTcoVvufQ{wW?}WoY3^-c{MLAsyWlqUgycB zNAK><|LN7sdtv(+)%WnVji&##ae?_`^BlMM7YmgK&qyV7ot+hIDXnv2R;H8TB2FTy zC6VMfAVvn6KdZDvbr_+>P*y-TfXox66=WUgNsy+H{UMDYJBrd(lp(r;u?kq$N4%e0 zBB=rC3aJMf5gC>dg?c2UsjDNIj&yawi}qDH1~6m*LWbOwF)lJ|7$sFfuL*rdWJ&lLLjLkKfzP_H>e=V*0qInv1txjp$YMl z(A22p&{S1gq$488EfKInG8ht-n&-Ev~05|dIRC6cK4q_EKVG)ZJya%v>>%Wwz9 zhlVGmCZa;2vFdtDHllfE;1@!Y$1b1(RH2O27>C$2i6l8AF)KVNBJwWrDnuMcfLyf$ zk}9xPlx<8zS3*)jMc~OJb43}G=0J6lB%*)Fb5lfqcyvs7G%6I67MY29N-7ag6*z)g zQNxl$)1w{W=3Jy#QO4m2kUU&pkO`t5ZZ5bkK5`f}P|^=NrSlhM5F}MBA~G`BAtp@{ zF6J8=lNjNUkt_+ofT9X}i!#|EJ}Epj9f{*(5@OON-j))TIzePwMtY3ewJ8~qsacZn z=+O8$6xhN_@Jw`Qnghl`R3qqQ@Dn7p#NS$|P-I$IMofH!B-chFF$Nz8Sr4+eD1Ag} zFG?dw8iW!X_@DadMMFVe6Xju1ZV=@HQRay97T`31}@8jzWp~=Zv5=l~GIE@vFX1Zub z`-v7pA8SUvW^iiydL1gF9s^0?>8UY^k-eZdLP2#QEg^rPK^VU|6P?BBi7`Uy#q~m! zlNOmUEHYK7aTqER?<&+Z9Fh{ghNMB~?k4DAsiEPKX%fj==)_k-((GyPF2pB9rbb1& zNFB<5;jxENfn4R zRv4_2~j6&C*~< zBTUSkc0wS>)KBPYcj(m6oYunN_G&A5x*;SDmBi$P_#`Z~cI}1qCXi(K5hNLo=^*6q zFUooC1V?v-jskOH(n3>HL$f4a&}(60=2Rdf^*}91T9=|iGom8ll^Y#}mYjtoet3XT z-~&i%VXP?sxvU&U`kII@6=ew|_2574eC>>M4u}X%PQ{Wdk))-i#-LnDLQG;za%fsw zR%m>DOj^1Gb0dN(G_M_drrSVO5h&E>yeN~?;whIT1*3(ogW$;#J0Pjt^`cCONr-fa z2$Rf*UI%;(hCym%Xu@>pa_Bjb1G;>Mg{_LMOXjA*sI4z*G65X<3QkaWUxw z#q^Px>5+*M5=nSyT4a1=T3YDvG|UBwq=T4$GCG*@-w##~5LFH>lIbytX_r7y#HGGM zLHH;NMJ1@<1U$1SAz0=7!$J6#|{onL&~#{<))Je@IP0y7W*i9Jmq&3wCIL zjEIj3(+re^IL!WJcqkh6S0Sw}YC@CJ1?6B<$}xtdW%WHIwKNN-0vauI!h{uJJS2H4 z3z8}rnHC-z9~u!E9v_oIL-J0zP{Ax`PxuEFN{O$LTSQu+$;B%_j&VL>A4g?u!5UkwrDden*< z;ub4ZxEUm+heuS{bJNnPCRiyYnq{{L8K|I=IH9GBAZaaRqM_NCLsWsLNdi9sk~}gr zDb*n|G(7qu`hnv0A!!UPhXd&JQ(sIMpCWkTM~X_Qi2)o%HF}8%TApq~Qh^sCi4TP& zgEAN(PnaS-wWt!(81jZFk3&*?2_zNBLy`x_K$7dzAgQ7_C8b72MP`PnKu|$lAgQJ; zh70;+NOGYMc=CiZq#k4wNE(D+#g;vTqzd1Hq_J=qk_y-kDTk~(Lg3dyr+hpll|wrS zRa}*V0A-jqQYc_BbZS9oNUE{BC>ujkLGR&u8k{#Fsb#t75=x&4Nn=KHwC)L=(xpbG zCB+Yml(d7VEupu9q>4lcTeD4M8ziKf%+3)E*g>b3*Mziy{1MF>*0j~t%o>(l(?k_N zUNjOj6hloS=`=>@rvK@Prg@*x$V%wOPmprh)*RJZBR@HzDI_&g^Je5v_CgcWI>%@X znPBQtw+x>wRCWL)wYnXoDP%K9@=_g9exD>X_7Nm?!#Q?W@3n6qvw-uKZ#N%r?X2u& zmV0|`+V0+K)=Tx@K3<TJp*y{3Q|}+6&h~y^Zd12t)qS5^)~2;< zc_0>7+5jb{N>PbB3PGsd5lMFReZY#`dT`e_o@#fo$dbIPW=mDHm_r9}7KbeWYW7>4z5jx?z^HHa}Z5VOP82 zA9^c>j~n{Pv0H|FjfHp1$5p@Y$^s0X*g5?m-Fn*nEV^2ZJS57_A$pYds%PByk6MEPAkj{tM^@|Z)xB`KZp!_l z8)Jw1JI^gYa8UB9qwQ0vugTZ1&OJ`Fj4Z1j#QQ%6Jx3A9ux+wkdYgNqEFQ z*RirH?UggK#Gv@Ich}@Bi}45BDDt{5`7$^@dy}c9@<1J%>a4i7ol5(0e#^vy^|cHO zpBbCnz7$n=dGmwM@147Jc)|5EO|pl5E8Ea^UO{GPjmXhYho|m6S4~?xhBrK(TUr1eh6z} zWUqVX!Jg!T=^t*hXd_SE_bX~Ikj@@DlPxwf(S5m4W@H~Z?F`#*WM{Bv>U)2q*I7qf zeN3-aGmFh)pN*V!%TAqnW)?Kakr^0SIax~Y4eI{tRdQm3cl$11Yt(Ai_Ctqa8an4y z?>cgLvi+;JWl!DKW^B0iV$rbO4lx(BQ<+^|D;8AO#OY{Jr`&II^=2H+aWVe<;Y^-g zuJP3amA9%t*y7%CXST`3OO_W-6fPSVG-IX8ZJf)C7AzUeG$TlHYK}EZe$rbAa(3PIp#|kZHDCy<4$6 zbxl-;y@OY*ur|JJ_V|Z?+@O;z>-4b~^+tBj8GEIV?fs1QhNlvye_GnD(!aQQaCy(# z&#wOP9A?`sbAxAb>v=;5wmr|DzvSn{eTtVZc;&f^>;245Q{=vHLw63_`_~$uBUe*= zt2r$3YJ0*fb@<3xANLG7c+f6v)N1GIry8@=?stkTK6YNTe@y2Y&+o1F$a(ecK}Gjt zF)u9!mBg&Me5%ggdfrjzBC;pOHu@fa=tp4x%)us2#)V#5>=M-b;)_Njk_`H_ujDEp z?oHp{tew#?^U?FOSh87;)&4tUP8`n~l(1t~_U*QdhtHf>F=t!%36Gpwr-lvkHd!-# zvu^!!9NS=O!mgP+v6-e;x|;g3Yo?jHPl_k&U%8@_%!16U0+uX@FHSjRyT11f-!bbu z-?Q0S7-`aRPVAvQYXf}DgFXK`-*R)?eP=2M`8cf7jV*8RTK^qC*HUqI@!Ilw?gu`0 zJ^%2FYup2yYEl+t(Thcdxoy5?q04bv>~2#JW@6ceH41cN1(q|}0|z@6WU0HkuT?EA z_8`_x_Ed&XYp?=y>~13uS^pXmN$=m<253E?X|bXP9#V^%%uJz>#bN*NB#{0dvMtbp z)EY{XX<_U2gC=2j9X#Y6pixR}OcF!Z-J%(lEL#e;HP~wGuBC_U1vI?VpyF*jWJ+vC zZG`-&%tUDIg}4SD@{`b9plQKcHCE&xSo;CCIasOMnmzWQ&Vpv^AxneSm2!|5WCx(( zy-$v=TC0O?j!HsYBM)geeHPMOA)5>mZv?4oD839@OSNW=*8kLJg>DLIkO3=qQ^>|* zcceIug<5$?OAT0vyF%Lx2N+*gVc{i>F=XZL3TcTUGi#}ky&_&P1hauSB~Y!Zu~2&t z*(zvcNQ+q`?lv?}XlM;IW1J&|eDJ0$S=5BqY=G7lDTQwP22JoOS#ig)!3}ZR3>BBf zK=Xj6r*8LRXgFc!)S&XXTWp@AkK7CA8&`0;@N6ycYR^g&>#`76h3q6E2Ov^P8MK>V zvkqbvjlHD9>oGG2g=`N9Dhsu0>>+y#O{hOg*0#rS#-A;0;3Z8mX61GY*=7*bAT-v> zL-tP8&>`@zzX>acy)if?P)lIX!9#mDGzF`$^^$$15O^I*Ec3*fLh#yMI}d55DGRYz z$hLr_B-I&6S$#8MxWJXj5cs!-p`e21kCcdmb^#i>61@)12*(y`g3vu(pos(0!b6%@ zpOrUK$j*qQwtDQ;FlQl+71A_wRtT}noRv3L$ex>P@)e z`~*Ow3=*|AL5xE);gH>;#<8MM5A8~5F08`ROXh7Q%rdym!b6q|jjRi!tQ4A90gON` zYr#>%=nR5JuE7Yj^^o(>Xp~8C)z!>Sd4Y{E*8i1#1h&h+rFCiaCwBB7*%Nopc; z|G$Ya7ku_-YPYdUgWVQZ{q{0@1rJ?z+ zwSDwQwz=J((x(2A-2t}!zuEo%N7mQ=Pie>fkuCc-_FR8it){}l&!H7I?5>MXQ3rgAeD(pn*X(u|q;C}h2{vUf+EdH`#$fYyzcSM3i71+a=n7`zU` zAjI6o;LUTGEm%3Kd$9#G^HNCloLITHLfY1eg(wu#F%Yd4 z+S^fw*6OTp5u)PM>kPZ*UyDWbc4HtXV-odu^JA>NQxl z6>BvuBiIBTJ!D^?(KN&!J-|bzK%FTLDN*fAXl|^+&P%opAxZ@=W7DA>fChtl0h0wn zqdZsu;FO8b#D>ESyP#3YNQuq*3pB9>*k)Q|-JyKKQjreL1)9)0lP+SbL|GupYoas> zY|j=1w$WY(b1j*LgBQCNXuzU}*fE2yec3X9^&y27bTwehQibrfuH(2qY+!dU?K@u7 z`wM%t(L##`si_GF^-_n*5%Q;y%*tD+9u`(R5A6_WLiP;^1*y|g%hYM5kqTDWQz2cW zU?C8XAi61JIX=Sa2Q6ykA%6>v4l~tgX0oCP;rP)SV}Yit6ic3mEC*T;G#r1h>Ywvv zp+Sl5t`w9!P58^2UlEBOP&}e=@!~FQr zo|z3&$cA^&SV2LuUB5Nz++WcC?lN!Gj;bWA+0&q*pJ-I0s}Dg#jY-4)P_3ijFCoqw z8pR=BQxEB=j;y?&LiPk?Taa4nQPdRG60@Tc)1e8qCev@BQA*T$u%|BkCMsx91~eLq zn4|+dxlXioOJ8cPnZ7d{IntHU!`a`39 z>Z6_ZJZKp87GAQ`2w~C@k3-%MB`XY8$lS5@kPp=TA&u+8LWU@03qcA_3&pg)(S?-{ z!FW}yn%yFkGT<{Ns|rYkE18QxXnA9TEQX;)?zrjVZR%0j{vvLA>O1_gCsyKX`= zuqTIkNLAgKS-3*FyBiA$hiSwiu{aU^&`~tKFnloCvY-iGgG04KSQV$ zs~F-Xw+WU=oGG^ipKNKi9%-*&RvxR6O-CekjIO#a-=T#-6UI=VzCuC5WS9<(QmT*T z+7-}Rvx=r(+Q$88p}~r(osZBEHqgpT#=!`+9(J$?`9Z@B#KZ|5<)J+Rnj0H9p^YvA zRCAce*-zG=l_w}<0sRG1@Wx-zXuQL3Sb1+iqZ(kEU~{q_@Y^ZycO*3Ou-cg6QfOEb za}3br+Q3jWy9o=jlW_x>S#O2RW}t91!x|OgAsY#e@?wXG@{pB6qq<{`*?UO83}hjF z6tX6Res?a$Z#cBp>Lj$?tb*nOO<2uth?;O%mJb%W9|II&|&$uglSpb1;p zHfYqVXgEgZD^U|pv`$0dGQ_FhTFD|rP3XQqt2Q;d*m%da!}1Ua~sFem4f)*$*1IMBQbwSz-Gn=50 z`3@II%nBG^1PyP>-MwV55TaEE8??KJ+$mF7iiEZBE`9#eN2Uw1jbI&h-Uv-_F%2Z` zEWtT23oQ_uur|<0o(2tvC9HQB5u!qbZm}MrNkywxC^W%3xqcxu(K@R678;IVL%g&* zj#S&iK$(w_Fi@;p;^Pl!r!EwsJv4Z< za)h1{7O^(au~?e}9P5?8ixsF%!noG{U_@FaUkBge6jbcJjlNDo-R8Dyx3><6@#(6BoV_Tw3|Li0yw8wCrJ&NF6tynpCQVbB2SWP!9;!z@oW?mO35b;7fH%JmlRwib$|t=;37$- zF9ImtUjSYINlMviW^72QmjWc00TT9-8LP$zmI@qduDg+VMPU?kb;<;JR72&{xg6KahZOOhJ3 z4zfDr7Lg}O!L6eHzmZh^Jz_qRq&zRhj3lYR z*P>oka?E?4v5F!p@j@N<5t7V(5mWz3lC^JQJV{Fb6OyQ3BLBaTg5xLy8Ir;}*{DXP zh>~WDl^DzBatdn9Ev79*-kN?-<_GByz7 zNm6=CQU4dorWFeQv_>q=&n9AOJ4njVlwSS^X#l=8;;B?`NXp+%Oiz*~P-jv94^pKz z5Qv0iC`imuRZ??$iTs}=EfoC_PZbypNzEN1%1}s(9|}p=zf1HdWr#uosz5AcZOCj$ zDmX{X_$NvEa>aO(lz$u~@#953Pn1(29l@7EQib+HQo4P#qfuZ#BwZv)KOpKPsUk-p zDdPz-{-h{RiSjfg{v>Bb{TwN{NRnr+i2PMiuYe?bs(T1f%^!;Li71~#Qo*l9y%G|C zlJ_G2S(M*JsWyUml9W%2jEa&Pqz#>waQu98$ofDFqZDXtb?$bf;!*B1G@B5x|{ z=8$C2Le#B9X(LKoQQAY&D(nnN<+wtUeGf=lh$Wut06yx1#Gj;%D1Au5RaH`fzTn9~ zdq~ndh6bZiwQ`Q;TTc>lccD4FLkg9Iimi*mhA1KZ|apZ3|7b&5ddBPNm60| zJ+`aeBplzVOX#>x7fI6pdu%^I?!fgQq|Ci(Ye~<0| zJ+}Y%*#6&RyZZ3?-(&lKkL^}C=Fv&-zsGhuzSFtk|Dj`hzjf)pKh18HDVIiDIK5tb zV(I!1uLhpHFyon_vU~20OFCa7CX8Rys;7<1-O~*Mx%TGwnAZfor9-`S?mTL8s`i@d zjtz}cHp|y%a=sld^co$kRdK|%sZGP>9H*-O` z>uGtvm%*L(eVb~X=W!**{An*|pyKWJKJ(S7izI`OX$%(WQd`N+T8 z!Q4|Tu6Rwc9JTGamDdKpPWGEeZ7k64V(c-asex-Kw*f26ueCa=WipHFPv5`KeEzL|L%A zyMx(|?Q-UDPRUuax0ixh^bR>2Rjv%Q7SE6*wCca}*^)PPos+X+#9)W_GMCV4gOhX* zn|QV{|2}A$^(w!UM$=h~=!9iC8IvSSuTHP~@bSLJOEz`8eI=@$PGr>n7ffDi!loTn zayIPv*j$3eOE${m7eL_XphnP-h)i8Et|8U=CdtB$2-lq)*yIV*r`QZ ze>P~h*Gjvx!I$O_Pi+eCd%xN2I^Ob*rVitJRcS_JX1^<#W$u)-X}gqM6LuY%-7Y!v z-mMI@uiA_i74_XZt;}?3k-jH*2@KwELvq&PqLTAt^Y#Walf!bh@S-xXUDfuw_H13FVBFJBPbN$C`iedtfWi!$en_qlC_wus|TKD%}eird?(6@W%y>%*V0)_+@u1Gri zsD5tT;K}bU`;VA%W9*a4GWf>sD7<(|iQl6c z9tvjHpvj>rS-(@kEbo|{RqRx9fvnXY_`3}4$4J4Kh`%qbCtaNjx4d~=GiAYsRtLs> z>g#bg>PP<|!|4MDy0-n$z1H)%j{D!QT)u1c>Ej3P_noNKH#4r{SJ33aaSbm1So>1{ zaU)hyX2KeyPrEbwqv*Heau&E($@OG!;njc>@ai5V*PA^>`JbWXA5(I{?Cvr2+e!Gp zOv&|Qv&ztKr{wGdwEoQRIQs20dVjl;8^}txqu-!e>`-!pSh;p{lHGH4EHZzRjwiFtBP-lazUx$^2OvwrCX^n1Ol{`)SCQ|idp-+t1~ZOy6o z7N1u$x3zBlr|xrjC^N`Rxy5~}(bj9?C#~JrqsC0>p<1f5Zc&xV7sWd5gS*em*}8p7 zE{1)9b`9FV{Yoy5t=bQFUx4#5pc7aOy2A6Kyh}<+%%Q7K3W94W-YN$fbPg&>ANqDl zqV@DWwF47dg@^C*+F9~t$Scmv!(>b0xFa`5ecU)Ax_Xn2hL2MGZk}CZu&GM%No*cG z^W>tO{Wz}VQdp-G@b)EC=7f?JTt;P1Dpi^-fNofN!QEOKIkoNohDUX+Ly7lU9F9* zVZE$l<)c$uJ?38r#ExsGdum{hC$o-Zo#3uK+;cop-88{4e*5wJ(z-T0xYn^zCS`TZ zHeXq=;OsT&g(ZGI=U#Oj@7|*CghLe*!=1Xx4!$3=;C${vx4U+s+^3%NCbm9)Z;9^F zZWnao$JU~c`!qGiZ$y4yT04e+&3W66m$u~Is>t6mZ>{9@gLd4Znrjc5#{BST z*{k8;b4SZ8t?!A3sn+nM&5lW9s19T4$n5uF#aX#MmRDA94qG9?V6~9mUeQD{};8!hJ z&t^947xk%o<-PVUSq2`NpFCT8-89c%^dRWu$9{%!cT$%Q`s>T~gZ-}ds_*xq_VZ7M zxIZT&+RPH|=D=e1Z>tz*f*+Sxy!HX@hIg;-#2Q*5?w18#gAWpUs~py z0}tI!+4Mc5yt`k|8g<@uXuGl5c{lfzmIc>eZhrh}`i)9yx6^m8lwA(3cXW|z-RUM3 zUiCf4Cbj=D|9H!+<|{WZ6%0#h6ypapzb~yb&s%Laf2K9_Q&x8JUe}D{GQ$xwm0f6(A#**2T2|8TYJ>wds^ zc>jA#TW@Z$Yu!te!M3Ws&4L@uAJODPrbV|<1Cx_G#@CuRe$}_Y2Ww zwrYiU+q^z)_q0t)!z*R;hV|b+_t4uxgRaCs?4_kLusaa9_}-?DNhNy64z7<48hvG= ze2-%A^q(76bojdT^SI?3o~ue+Q`Lrb)$Scv)o`cCBgR2fuGbB8ULOD1peaAdPX zV=nyqp7dhA#Qf3t&Oh3{x)!%*%Y<1u{R)hFtSOXVDAT>Lw$tGAo|CTpvK&HRpW=@^ ztp0t8#^D|Vo@@U&YK-2Ce!Oz`cGu-E9UR39)0mi`F$$>__W7#blJmuH9$vq3-m|5xs?hxN z+E4lRQ=Tq=TVQYFKioE=T44J9)yn>jI@?VvKX&Yf?U+)(-Tm|1N9->-)o)sMSFysh zPE4$7c;oBiQwPiEhUM3vByad)uJhoJ*XJ%;Yq>hcuGG2w>egGcE*@{OId+Uk&X$kS z>sKh-O}=rr(yfDP^Z*?rBl9D3!jnY9IWU@6)$rEps>_d$?{Yo(Y{U5d%9C5m_8mSy zAaGujc_Y2w^r@B|>bd_{+c~{QRc22vap~OaoppMv`=@pc>}g|s=bM4#^BWt{t}kp( zs%rQ76Ptn%)!>u99ed7sy5hRy#Ix%UWvyKQx%rn%GyKx^_6=W_IJx3r_QhWQpI4{0 z@H=X}rr4D=dR+9^z=))=_jJ}Y6%CV%H9!3!`&w7FS#(`caxrsL^1#tL=Fd+{+onIy zX?ysQ1oUM+mS1*^(KR_cGWYTNhe(n7Q9pC)#EHzdVdZCI*a zC^WwsQX7u{_(d`-A>qFN)Q0&{mvxHc?cQ{7ynFE3^8?-w^oBPb;OlKV=lGP5`bFub zZCgLgTx)Xg!?-$M=CyvZaEIMD|0Bt2yE*Fbd+<9h^`%~jv@ToSv0Tx^yVa1f4yiUJ zV-o{5zcYVoKC7;@yGn98!MyT?Y7SeH6nF62;d`}nj1#m+Wt{F3GA=ZGl6!Q9ne8Cq z=OV(JC(X~A)D`aerIasT+~881bvCu_lcM`Qw)@m*M)6RaRY`#c)-z0ps*b8gm`pkm z+hhFQ`WA14ZkUbUvGR1>rI0OiTD(~}#yg*WAE0?xrum{pZMavIq1I;Q+gjDnR$tR8 zeE#!}m6v+%O1RNr=+Ei1+;%J})W2=KNj^Gz%8WPFbe6Rk>gjVaB>Prfvt17dHqKkO zW@z8vcKZw8*a*%*-|c!CbnML1?1)xV#)LN;^{d{n4UuN?EsUa?Igfkd%SBDAairI; zJ#h^qx^r5YM_TuNG;iQ3?XeRLinrJ%g~nGKGv~KqiDV}0bUB!t#m?Ud=4Lbdo537o zSqRTztF8od`HZ_7%*|!}@XE7|cr9SEYr)(+cHnw2SI7+UI-kYhbpa~{zmSFEbrHJ` zJAbh+u)LUchn*s3S{}?TVaM^hl;z&Ux%MH>d6$*=<+$|?oNFJ+RTtiNV<)>zR}?N^ zYnzp%Whs?7T72;$>XT)qv%cJ-E>@*E50goja)mAyg0!7(THPRZY$lh z4?FbAuhOWM8c#uLwH@&8*c7dMFOO_UKkF+oFq#$E`q{eDX*KI@>v%w4*L?l^huMCU z!pED=s+<+_YGq!7y9Yiu*+04KJ-36e3tjv7JQl(%9-H`bvLAyMZ|*)kGhVy>2JKVd zr#`sadt>>l>NgE6{rdJ$c^qn;^hR=j)PkSKAMK2&SwH?o*PaHY)80)_3G|)0);qYq%cUoBgVDgTYstU%TS+B&OuoyYC+lIE=hJ>R@fNWrsT4Yts2> z#-@bQifd+R=Y?U~m_Z^D`D-s6Uz?$+xGyZj`XMZQ#W zCG6=_9KWB+yEK3I>*nfar`%OMcJWii?AKn&K3mR z)kzNSbib#?{So&TJ>TXyORfy~=x5h@`j0BE-^k`Y!zuomTyC3o$}jVo6^uR zLC@mJBI)VRCkXmtPw~w8}p_%DZkM1n|QqO0c{I!+Wmz1=iwCocp zyQ>)8T(v*L4y+oPF|m1xX_%u!U(c3J9Bw6;e_S`hN#FG0@CgrmE!sIlbvf4I zsWNa^)zTJDPTjY-=GK?lxtm>YC!BYE{c*2s#b2@ zu6>r_^#`0r zY4&-Sm+!@$Tl|Ku^i1BB&epv$@eSG7r84{AQtkBdFAe8*b};c;dTgI_=?&)v;hg!5 zHvH`D{Q>Ru*XABrHpS<5YVOx}QZIu&M-4trUav288@|y!n`ysJW~W~(xdSZbHQv9z zk$2fqP-B`$@VS_J2E)?YImg>}SoQdoN2KGinkP-$^gDAfz4%g1IUl=c>Chut4%>${ zlk2UxlM~=lukDxygWpuz#yeGM@FCv1E7yhXd1Iow|7@2}!SK$rQ=*Muo@?_oaqsei zqpK&jjVr%$xA9*OchqX3ztcLv{$5xY*PpZBmU`TwDI+->-3ST zZrphM(Bn%nLwquhW;XMa9-Y~3XOE4Sd^{#cMu$!{D!iG#>|o1tX)1;3n)GPv?oLOz zS%=aTGYVqdsww7b4^IhcR>%_GmND&5O71v&`W|ngEAfu@gOWSR{663<^gB6Q4(&AS z^bv2N-(%hTsN~MFexEV2W#_UxHmcU9jR6<6&) z7PYFsxUu!(pMmVwD z@2$pl=3etMihuG;&JUC#{w=>s$_3{aaB@CD3;H|WP@4S81v_eD3*h8uLHuA&#eQZuO5hpMAYYz? zgUB}mVZq-eVh0f&>VmN3XVnD}Z2;l}5!Sq4JrE{_AePqyVZ*;AqKt^%#+)*+F{ZZW z(z;o5Wp45go8vV$w&+&sD6@>Y`OW`w_?M+)wyzqOUN+^}Q^UJ!yCnB&{H*!P4hyct z7dUkqs_#3z)fwX!Z!b3)d%2uXsLh%1+9oKd3E$lW1=-a>L0gHi=Q&dl*NBKT1<{P( zNJO3yh`MGV9QaT(5T12G903v7qH5jbx2Gf}z1!QZM>B_>Ce1ST_voDVn5= zPS|2qV@rAG!yTe@t@3vmU3}29%Q=Vbo-N9gCLGVbuxL)>$2NvPY`zDMYd^Ban$p5y z-$I*JSxj4TtoB!oc~efVYQ9x^*?r-(;HazBdDFuikGl=;Hlr}P!Ma|N7e6i7vS8@V zxG9-SzudVh%`t5iKL2u9>YaSY_mRbYjKdxM4_5QqSjDVYRmT+{96vmE!mrVV)p9rN zUcKAZ^lVNeNkQEe%7#}q@b&*X`TlmzGH?C6ruBO^f4lkJ!Ye6Gt*fsz-u$rV?5~nN zVGcWf@IOpBlfZYaB3~M6?aLjjpPl)ubDyt;0Y_}&UySN>wQY^M^Bp};n(rL4&~@nE zZ8=Aqzwzkya&-2FSbpk_?F+uuxiSyjJd@aEWwZ-Pqu~g{M^)d4k zz9^-B9NYP&4{u@){pC-kv>nG9e(%GVk?sw>1IL>F=)-4PK!5l{DecIy3y8OC06qUF zKBwmG8=$kULFcD6K<_E}>qO*Pg6Q24L?G{M3BuEg+r#Hug6PgawE|IKjTC{_AbRri ztU&}c1o4tm^yc3Z@tKIg<{*OkdCl=sYy;v45&ig14j}qA0U^=T z*TetC@hSGV3bfCLUA*g!(@LY6 zU+?byyh^M2C)pPr{Rg*rhn84q+b|7@UKxnrIv5@cH z9>fkJwi59d&vgJ1-3CNt2M|U4Mj}kSLDcmJv6K(>2T?`@J}<-jK)zoi5Sa=Pdx%)c zmr@El9}pHDL9F6qI)b=H#Azbd@TLJE@_a#z3IMT=FKP(F(+`BU4TuuHyA6mZL~JEu zBQI+VqM$8^BQ$n5^M~5Z|g9 zQj`&~uo;LW{8JDr%{>~lWorF=Soh|U(fh2-PrWW_y1AlNf^N+7kv&qo^qg?o#r{rC zSWjtkx2a8Q8cuWxyHdSnhCHnQ=Oe88v$_GkR(5AfEFP;ra@10PVng?MXfCZ!c9*j6 z2EMLwZR1E)x6fMrzFj`NDr#bO?uvBV1&40y8b8y^w5|X6=?`7*cWvWl(3$OUMANF&q?%kse zYHcbSG_t0Cy;FOmgxgZI)E|qUsA{;$-B%yto@+n9eX{GKo;^b@gbaA~VAM9-M<0hy zJds+qSoh@6IYu|f@AWLYw7uc?l_j5+dr#UmKWjw9pv-aG0&0b7zBHhZ)=sK_6E1pG zYt-Xm(_1wEJmXR2vp&mR{9{KfY0~-eoV3X|JyJF|Rz~Ve^QW(NynplK<06?MH|c#R z$HDiSG_p4zmzcfb){-0g+tr?s=+dW`r>a)C`@f(O2~GD)65P9e zHma4db+7M>$gyrW&)F&(N35m_bddhHVaPYa2K2%GEq&qwkYmb{tf8q@$1W zN@CQz40o?p+t004%zURGP+UHCnNAPY+JQcGzhvY^8FtVacgUb+pKD7G>EmDM8S^ic zTrY0XcE2v1s}}xkofdDTrH_fU4Bq^ZZt7BDXE+@ z6aFh}O-KWE>g|X-#i=7T{|$etHEy!`9XV4Sx&1{iu8bo?5tMd&^H}b?7UdP9dGk=z z;b|bO|E@#kcJnZ9KsCy<_+JWNI0!c=P%;%?D~i*V+799MVz~xR48ecL_+9vW zb>Rk@e?I&oR;d1VyI3wy-FUg?pVxlE*FHj}*$@l*zdTxe&QPjTu2>lTR~#+AOFvGx zX%l=MPy^q)UM`!z&B4qRi{q+?a;p42j_D=8`JAf z7iwf!jlSZ1%09!}lyN>5KYs}qijmBtK>^u`UU1FhbIZ6os=uI8HR#t{9%57xBn4@} z9|h=IB64(x!C*CmJ3B>=?w4pLe1Rfa29E9;A~Yw%xagKVvPEB5IEq{eIFzYU-$3Rh zlJspfRRn&JxQGVnK0W-=zgCvGirh93l!fm9YbkO&#JqH?qNm9164TKQ96iMF9#QIp zt3zMqQqA^(#GiUCeh5h|+Ang32-964bkRM2R50CL6bz19c?cXu*8%#A9PJbMqkmE? z2^I5d?>qE}4wrDigoTp?~x%NfAj}Z7DO|_Nuw_G6n(iBrPq()d!*wAWzb&K%B}PFQEvLE6<9g1;X^r3c2!}$TdKizR)7> zyvSK1OofnpFNmBK!ed45A|$oR8n8l`uFH@VYzW|178U(xYsnQb)D1QO-6uotEf*6u zLO4$3ZirlCaE(Q8Y3_2g1!pUA6=J$3;Fcpy*DaB=LwGpCJ5pUrgtK_zZB=um>XNh%o&i&<66M$hAOtCBk$) zf}|=t0josriIA>8{i_}c4Rp%zR3Pbpj!1Tj+%u7L1-BMqGV&af>g5J}K$xyqV!C2+ zCy2PhPd~*iQ5{6wA%JH10bn(-23QNM1J(m2zy@F=unC|~ezyQyfo;HcU;&ku z+T8$sEV>ui2kZxY0Y9KE&>rxYNchdCIeZerA2`kRSJ6gA_a|imBY=@WHZTep4XA*o z@D!~m4gjqpEdW|L+yHmL1L%#mb^~Hz&j8X8n1C>SH%Q;(EdUk*%YhZZ7+@^@bBbjM z`~@rqW&lwD4-^3NfqY;lFbkLsOabV(A0vQ~KoXD)!~qff(=(ihite9m4|D+hfsQ}` z&O^c@oq&@M0-iYm+to*4bbhFz38PcU=7#+jR0CRXpNv>?wbInzBihv~mZDzE7tN>;M(}5X)17HiRLM4{~xj;IQ2)w5-Dx~^}0Nn&Q9)(N* zrT|lcX}}C%CNK-g2j&7iFb^mM<^v0XMZjX92v`Cv1(pHJffYb8unJfWtOeEq>wyyL z{|yLi1U3Pifi1vRU>mR<*a4gb>I3G01)vWY0=0qafG)rRbkjNQOSBKszC-&A?JKmV z(6&L_1Z@kn4bZZGiFQ_6>uG7f3S0+hNv9>8mcL?vmb}$K6Tl8w3XknaVP^1JeZT~$ z348;03pfZI28@8ZKs7*pQ#RyJt<#TzC%{vHmR?$F_X6vH4M0<1KhTT-J1S?qxBzr$p+kx% z&>fLI0ArvI5QY|?0?q?hfm^_RfX*O~06JsPnPM<71W1I9Bp?gOM}FG&3xF{|6c7yz z0Yd5hPZ$EUjnnQ$320k31Kz?g-9?%L&~6(G&<;y?u+nX=bPKHq&=7@K0+eSskR~ES zl*DQ5H$s^1L`?%|7aUe?h_Ln1?X7k&(gZjvW+X$ujUfI2bo#wK?RGl>+RcdD2b2IC zfla_CU?vSHTr(f=iI+GR87!gmcyoiBj60Q0Gt4KfHrnFfR@gdfCoU0(ln+m!qgydfRRK)@I10MM>WHKlN8fLam&bOe+@cOXavd8IedALs`J1AT!IU=R=vL;?{& z7!V2!0f;A^WDLqz?-H?)v@6B~Q~>P`$v_H_2BZU1fhhp(GLwKjU?MO97!QmC#sXu2 zTp$M^`=fzTKsGQEpxrnN$OKfw@iGjU4v@==ftA26UE;0i0PzddfPXZDg<3_epq6<9)Dp@>gJm($0ca0c0h)0@GlVL! z4p$3zDop7pjY`wUWQbf_wcigw-wzxDXjSS2977mF zas)Um%A=4sfK$LZfC{2C7lGqI89;^l0pz7B6;-J-Sc5zZoCMAQWPmbKCNiqwPasTj z#8-9sY3Qo}OZJLK*?I>C1t7qFxu04gvHgr8b}s(3=)LF-#oAQC@;3Y>IGwz(nL}e4%4J9I>$RukU>VTz+8ts_8vPoRExg6<4>0Mr%kkZxkQB_wqP zwcHzFAE31;6_9PH|GfZ;^aIF10Axpi3ipTX0JI0F8#G-J2%QEWovT_yMnLufs!qe> z;FaJy12hF`;L*T_s%{jeRT0@63>l=BQe+prP^Ml$51>0hVEG?C8k803_}ne43LY7*BGPpbo^NZtOkmK zCBRBxDNqDZ9_qGUs9a-UF~W&J91sgc0yAm;#~=_5L;*v^Fts89hzF=LRAZ_jwMY|} zgz#U$A|PGl7edn8r1^lRvSgzWpc5z$P($Vd`9K);KN+stQU-lCkOs^GW&#s|89)|5 zd8Px?0IJYbU=olAOaR6M{!5Ib!pNwm0yK7LtdI=~S1p)4L^Gu+ zW&}x93%o1?NUi{u0||(%nrRg{O{Fx25mz-06;8(g$rDEnqcO7|*az$YwgcPf{TH3n zHbJ2|x)G8pvjHdp)&uxche^^j-2!X|wgS6>T|gSx< z7;svqqpr1SdPkQ(E!uWts<@+2jPMp+O8&wI> z%m6L zyGWrAUud4JhZN{Qs-Whf$eM?}sq?BkPa@;rJmpO4Yo5dG62JM~iL=pBV)@8x#mBuy ze`}tXu6d9k5};w!e9aTpHIEup$DnH13i*_0oT<6y5$u|06C!~dh6hsQ$@rh|;Dr@3 zKIc80dYG?xjvlb$kH6!ZSxalGzpTj7JaS$0m_p=t!O(M)@P6nCZFpDHDHZ(2m&otR zpM)^cJj(s}v}`?N>E%o*=cMio{@H8JTq@J&x4q)Jm}{QXo?zb~Z1tS5Zc>ii3Zr;3 zJ3sv`*G2l%fWP?`J)n6)`^4(@ivpK!T8VmKm^-#Y`G&kfC08hQtj({jh1YmX&GEF^ydije!8@Tw08K3+H^*UUiUyq3-^*86sUchld=KOck`uaK zEv)!uZ#Wm#C!~<0L&H)UdHdGX_Y`Z2`uJN5H9g;V^|?;(|9U9pM$<5I!-|cV+Q@V3 z*1m;nT=ulm!hppZjAibQ7^8U(qBRCOjj{R^AyX!V?xCk@j;iT{*FmUj1kJwJPMOyR6^tO z#3;?vGXKuBK#b8mPxJ4X4PuPuiJO1N92R3V&*c0&=9(Czd1|MY>bHMih*6pcd;Xn? zKF?FvTYU8A-!Vpr!Gz1vJRJ1z7<VRND6o+0kN)b`8V8C7?I=}z}!vHfBDKYTHCng&eS6yT5n@D1eG3suN*kZbE zP0`Ks8TFyh*nQvoD}wdq?dS9UnR3s$=bnDfz4L?1r|)Q52}_MshSmE=8z&TY;?;LG z+DIVH0+I))s+X2FB8^0|BREMu>*byuSay;$;UO_GT-}QNa@G6mm3ukoktQ}IJVwNY zdc(bXb7vz&y$W8vZnF`hULybS)=z~RR}<-;T2iOn)_7C69lzRuCznxEwfx&fJfSSr z)L}Q2!m0`5OE)w+opRH!QN3IgK_ike!fJCcuWm)2YGy_G9eiEmMgK2_Hx_vVx$+Me zUZ3X_UJe&*KBvkm{eeHXe}Kv*SIqRC!Lq5<8(pCXr4xE;EtSl91ao}g1nRA@n1eEg z%o6qVD$7X?8V{v-ZWBegR}a2>3-2h5*MHYk^XZ$g&f=SzC`IYoXgJLoy`1>e>t!X* zBn$lth!fWTd;P@^swNI8RUv%xrbfrfw?NEEyhwdl)@@Dxh}dBQD?kI(Z8Bh>0Hq*Q8|$lwQWX(D*kMLcUC>Oj71 zy)TQn;l3s!`rio`*>>(vpSs+a+}b#4;^U&ER`v`}5O$V3lw~exF_$zzDK{VBWg}jE zvQaigBM_djw#)R{&fby|B1Ds6=GQP4-N>>K1cK~RT2W<}J1onh2)YuvY&nJY!ay`? ziJ&oZ(xnHtml$qr|5h@kPZZ-A!^xu^3)`=hlV220}s}a zSLD-0dT#O%f$rW?T1<&qKCB^c`q6=Z)sViW(g->}9`s333umyHX5A&z6g_gHEkiBA%?wsC+9AciDR-%QW z3u2{TBC6FZmq`;NdJ?2$ZSZmjIAT;V`yn%P=ldF$j=R9LNCHLR&kOIlKP2oJsqcB- zNplDgQ4=}-J_-V44{M3ZkJ8XSX-zD~9qwy998}6;{=jCCi9_I_KJfN&*!Bo-S1*Ys zO)u1pNINcRL%PRS!q!4f2nq`tIU*m$i{Rs==t-EzMN-lq;;!0(ymbTO<=#)dj#`Sc zXrZ&=@m$gxax07Lp67;=TK~|ZLZu>&X`z>JKHCCfMc<4$(gH3Zw#@3vwfPV7) zo`Viuj(FP}8$kqngb0fPfC#U=Gd$DlhqDXLlBNenyfLx>??T!>J!*E`@D4@a^in_G zcV#L)8%>v0{VXHd)^GsTYe&5UFT- z0wUri??mX%U*XltfHe0)GVgVUFZJ%v zC!J}CE~US0>iXUTdh@h(*vMji2@@PIaUrcJgX8=2OD-U50u8I71=&E(MVx@(p zZLf^%$rsy`cg#}`G?d-())d)V?Izp^D$DsYOX5rNhL2L%-w8_`0EV!ddP6!elxFT& z3R{4YehL`vfT3QnF4;w*5d^74udCk6-YE4mID!`zq;fp;p{q=l=WZ$vf8TsLdu8L? zUs8DyFbWR%$h@m}xC2Ac)G=_>7VF+KYqphiOhzZYZjfxv{AZnuFU%k$oTbLoZRRlq=?3(u+sM3`}P^lO{nKi0NNGNZIJU!2BU z1ogLp;R+0&J?-LtyI!?VnkyN4OB&w=b=`-+5Iw=pkz+ls#kK!LS|2fKXSXkn!`wh) z1w33x7;q{yvukF+zaXJlA71)p8dtf&2hXJOo4^#YaoCM==|MV|ctG+YS{nNB%llf2 zqE|Bbq86CTGT6@@v{z;*u|Ye&w~k-Gt~LAKsGn>b?JI5?1q3dL82xoP_wfL2g{H1I!a>-5=e*hv`xnn& zFVld4puuYeX6KRoy?BS!+Im83TNJH@;*WPS>kZc>AA*e|>@JSvL|||-Hah{4BFlU| zTFxcox_%ZjYsrWcz(pk&)%EAmyx0>|zaGQ;@Gi|k^MpBtW7)|I(mi|bl(EwDB-L+U zb$xID=@sojRf*029?O}St7LGkIJT6Nx%5%Z)(5W%M68PoCoDJetBM34ej-zGYlB}nk(J%KmjiT_-0{CFl3*X(ac1^!%~>?|2p zI;|Q9h;XjOOgDM6%!Py&c>L1WpMT_g$Ot&ZxRQ(-4S>EMmtJjTRZBAzqdq zogB%Ze-c1{U+_Q32UThZUSgiT6}db~&ay4eH@J>$=2n5Fz=#MJGH|sIs65%+{RFL~ zc^KsV*~ajehr2Abo>NXEdxhIY03v$P8=p+tw=XDbh6)Ljo;C6ZL~Zl1n9}CW(vgqX zlkJiU?)yOFs#(qk9R*1IaE%V4T@){VzIYMMf>~+|A3s{h64CPh_d3`Txuw?k#-NpZ zYX$)#Hle7+&t`Z^cux!LV3*vWX*j>9Y_CKu3@2Ee;Y-nk#J}5@y!Z-UOmnm3sMu2wYs?}&J)hsU{qpy#S-EB>@$ggCNQ z63-bUr+33NHL@-k`Dip|T*C~Ji?7zrU$VmCyf_mS8iiaVa@pJoGoCS%Pjo_L<>&Hc zAU$VcWuYg?<1lO<`Q`5Nj~T$-kO(cRCUe z{>26zJA^hSPV53gK_)pm3i4BKR-Wsw+$2I51FttBs3Xd%s@)H_#*jM4x5i%s|jyikZS6`vh(XcS>4PE76wHMy-io7c;h)_#c7P z-C;K{y91M5x>8hRaDkkNK74KZKa*BhE|IvF3?BuENH=@W)g1a_N@kJ73tHe_{F*;` zinJ*k-jh}q^4^|M;MPLk*0s@2x)Hguyx-{do*w@Gfwhf1!C3bNrs6G%3pG-D-ONM# z2)LQ6gCN~?v#gh^)%EG!18=>hP(qDwU&KB1z*KmN=~5*3jE6SNYyZ7{%9+MBqX7|} z=<%3KnxXYtIbtOZ4Ju+CJo0}xv?`%)B4`Qkc1&D;(P!H5F<3=JC-^BX;+2Ay*yM>G zEpzC(;s22l)K}s|>ep2_R7M$xq5%2Df1-}A@h~PFqTwgY3XB+v12`x5?bP-<#23;)T*1=flb`i%1OUpvt&czJFG?rUi#W7U%oURW48cvzkg35TtFD9q zLmcoUISm0sY#BPu9MJ9BC4X`Bk8#lqWm(v=)g_j8Gr+^{U?$$Rt zseIqDQwj!>NP_~IwRXSD$>x9Rqd>q}t71MYsQVPloLz0}QILP)c3LAw_hR-7BlqUf zkGmUZ>e02mT)aFK8wik6Y!>rWV@N&ex)NE0k0)Qu_1Z9zj(WLDFGKNywmUg|a zSFA}{IIWQrbpbOlgs!xUCkNdB;mFxWjIRJ`1;|Ci7W=re_1`EEXy9}yS0TT}{00ms zU^E+9x!}Tboj3p!7C>9&1RaUFZeK6zw`YC*ZwdrhVZ1vqW-C5)RgdC;KO2|2Ens(+8-U4GnaewFgrCH z$}_e0*s81b;$#F0mO2i5D|%GdP&0&@0V*P!po5H>Z`FDjTFK`k5x|ev$pw`hA_7~@ zEb5A|r%FOu;;}?5*-+)?QO!vHc>$$b#Q&wOakXwAj0}?eL@L#)glM0=NjsIRQQ1{P zJk+%!-*Xa;!XA6aG?5%^s}idkQYC^8F(s@wqWE=`3XumF9;|Bf-I23*WZndGsye&9kcFRAwI?Z|VpS zrzmyqA=O3;#XL)%NGL;&)-GuQs}Cfcc5So*7&%ARu%uGll?-Jcan zp*2;3SWrQJXf4%6)LN|gGvDZiU8~%NxbsUdV9&06fzS7v$&=6=c=p4CRUNS4-Mz85 z>WvxVG)(IL`07Rca;`4=hphH`_3}PwE`NWaiPHZM6Q9)M2)PT0DAw(-@$wGw$5uB& zTo*`x#ao@Af7hYlq~8?<0eL8vN+hg?r-PLD)_rVNelw25Unud>I7*t? zIzzV7!m2R|OFONzy5YMcXg&Dz(U2=ky#A}B0{_&wEucmHFIRgg@So2a7Jn3rTq}y~pRs@Pi9>+})p^ z5xo8!23iXOWi=+JoPnn<;=8LLP&j^JZc1OBe_vUIsH1he;we^xf7#S`Blf(miIk8-{*J1B1Lh2DY@5uKA2oIymACyqj~!X8Z5{>Rzd-FcZSkT4d2V4L7Et8 zgg0y;$GSD6s8qwgxfBOgjnAbH+&T}t`crw-`NhdGQShLwa+5hHG{<1AG+Fdbhwvr?LJyzjVr*(IJYM?@3)Rm-Jj0ABN6h2{;Acf7{y7`)gZM+iT6Pg>)d4WlhJVT@H+3M}L>=VM zA%b6fPApJ?DHwM)Q#ju)BzF}bD+zoo62|WqNu1$xBqv(bX+iv2ej{h^nbglAy#1jh zLF`VNsNPse)5vtxXMD_GAsW4k%aQV3;8)74yi!k))v!j;e{xaHRW;qPJ{x2 z^HZ+Np`mpMD*T(Ns%&_tV>XrW$t*a4l9?5OQD)N{N=$610QoI5NY`9|Yd?ofrgFsE_g0#O z|JT>cMiof!+iXy9IA6EXPF`!p%7{X5K2S!h*lNYOJ63k(b5-QmWO->UF@f5#M+Lbx z!@;Z2bqx$5C6QNWKEmv($#DG+mB&d>C^q?AD=jLCRkCOC5cn3kWeLwp!0bm<9hSvDtd({R8VVPRe_LQT}^?! zWG02Tvl)w!4im-W47N}%S&s40Y8t|~XHvAB>)LOFv_(@X&=FOu2-OJn4Y|(BOQ#|x z1EwK?x+bB{wwgw7X?n6|AjCZ|jr!N6O{W$bZZ#du-Cu=N@@+MFxt1BMq9hp&MVaE! zw4Na`;r!)XoD=)ZqPctlgzE4$T+`NOEYoM1i?a(+y%=e;E5EjotaY)AXajM^JQ}5~ zM19p8%%(g`zQHD(LKHv#bRK!ME6yq~W|!#=HXESqypSfa|3Fl#^!do0-@byh6tDp9 zx^zCJajyk%lzy+!WWKV1;@Gkf*1R*1X7Pz(kZQ|(dV^^Z-019L#K*^TkotHI3e$J9 z0WEkD{5rl$pSOnN{kV`i*40eWo?1RFdDwmmk7T5-c{8q49(d|mJ=nycZW4KQ=V8U$Z- zExgaE7V7$7HMzE(VzB0ko<_3y!PQ_~YOAViX$fyzjU4M%OMOHmu$j|qp`uULfC;so zaIR~3kEYsdMHf<{&nhlbJWcxVO0u7aRl_TNcVOl_+tHG|oda!MT95kk@pc+rH)IFt z+i}BoG-@k8rMwn(OV&{ka#s$y)rJ0pc53R}{zB7+=wGE!iZfzT$O+lhH?xJ5nE{bN%ky7qkZi$_3i{J|lVo~F2i>Ph#= z0GMjUK8MIPK;d8N=J8TI$~eqwO1708mAm7~M|oa71-RNxyOeiizRQ1h+@JvV9Tk81 Har}P(*X)QE delta 43987 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 EKU<6mw*UYD diff --git a/package.json b/package.json index ffa7856496..a29995d963 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "workspaces": [ "./packages/bun-types" ], - "dependencies": { + "devDependencies": { "@vscode/debugadapter": "^1.65.0", "esbuild": "^0.21.4", "eslint": "^9.4.0", @@ -15,9 +15,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "source-map-js": "^1.2.0", - "typescript": "^5.4.5" - }, - "devDependencies": { + "typescript": "^5.4.5", "@types/bun": "^1.1.3", "@types/react": "^18.3.3", "@typescript-eslint/eslint-plugin": "^7.11.0", From 0be71edf3fc9e4df1aed1077de172b0fb9bc7ebb Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 18 Jul 2024 03:19:48 -0700 Subject: [PATCH 019/123] Upload .dSYM file --- .github/workflows/build-darwin.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-darwin.yml b/.github/workflows/build-darwin.yml index c59226d131..1baea12558 100644 --- a/.github/workflows/build-darwin.yml +++ b/.github/workflows/build-darwin.yml @@ -277,8 +277,11 @@ jobs: chmod +x bun-profile bun mkdir -p bun-${{ inputs.tag }}-profile/ bun-${{ inputs.tag }}/ mv bun-profile bun-${{ inputs.tag }}-profile/bun-profile + if [ -f bun-profile.dSYM ]; then + mv bun-profile.dSYM bun-${{ inputs.tag }}-profile/bun-profile.dSYM + fi if [ -f bun.dSYM ]; then - mv bun.dSYM bun-${{ inputs.tag }}-profile/bun.dSYM + mv bun.dSYM bun-${{ inputs.tag }}-profile/bun-profile.dSYM fi mv bun bun-${{ inputs.tag }}/bun zip -r bun-${{ inputs.tag }}-profile.zip bun-${{ inputs.tag }}-profile From 23fb63f45c6a80457d3278f44bebec1421acdc59 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 18 Jul 2024 09:10:15 -0700 Subject: [PATCH 020/123] Fixes #12360 (#12364) --- src/bun.js/webcore/blob/WriteFile.zig | 8 ++++-- test/regression/issue/012360.test.ts | 40 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 test/regression/issue/012360.test.ts diff --git a/src/bun.js/webcore/blob/WriteFile.zig b/src/bun.js/webcore/blob/WriteFile.zig index 06ba8e92e1..1700f476dc 100644 --- a/src/bun.js/webcore/blob/WriteFile.zig +++ b/src/bun.js/webcore/blob/WriteFile.zig @@ -507,9 +507,11 @@ pub const WriteFileWindows = struct { fn onMkdirpComplete(this: *WriteFileWindows) void { this.event_loop.unrefConcurrently(); - if (this.err) |err| { - this.throw(err); - bun.default_allocator.free(err.path); + const err = this.err; + this.err = null; + if (err) |err_| { + this.throw(err_); + bun.default_allocator.free(err_.path); return; } diff --git a/test/regression/issue/012360.test.ts b/test/regression/issue/012360.test.ts new file mode 100644 index 0000000000..6ebb7882e4 --- /dev/null +++ b/test/regression/issue/012360.test.ts @@ -0,0 +1,40 @@ +// https://github.com/oven-sh/bun/issues/12360 +import { test, expect } from "bun:test"; +import { fileURLToPath, pathToFileURL } from "bun"; +import { tmpdirSync } from "harness"; +import { join } from "path"; + +export async function validatePath(path: URL): Promise { + const filePath = fileURLToPath(path); + + if (await Bun.file(filePath).exists()) { + return pathToFileURL(filePath); + } else { + return ""; + } +} + +test("validate executable given in the config using `validatePath`: invalid value", async () => { + const dir = tmpdirSync(); + + const filePath = join(dir, "./sample.exe"); + + const newFilePath = await validatePath(pathToFileURL(filePath)); + + expect(newFilePath).toBe(""); +}); + +test("validate executable given in the config using `validatePath`: expected real implementation", async () => { + const dir = tmpdirSync(); + const editorPath: URL | string = pathToFileURL(join(dir, "./metaeditor64.exe")); + const terminalPath: URL | string = pathToFileURL(join(dir, "./terminal64.exe")); + + await Bun.write(editorPath.pathname, "im a editor"); + await Bun.write(terminalPath.pathname, "im a terminal"); + + const newEditorPath = await validatePath(editorPath); + const newTerminalPath = await validatePath(terminalPath); + + expect(newEditorPath.pathname).toBe(editorPath.pathname); + expect(newTerminalPath.pathname).toBe(terminalPath.pathname); +}); From 1d61676c7b477190db320a07107484caf0d95e03 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 18 Jul 2024 13:55:55 -0700 Subject: [PATCH 021/123] Check for file or directory --- .github/workflows/build-darwin.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-darwin.yml b/.github/workflows/build-darwin.yml index 1baea12558..9dc02b91f7 100644 --- a/.github/workflows/build-darwin.yml +++ b/.github/workflows/build-darwin.yml @@ -277,10 +277,10 @@ jobs: chmod +x bun-profile bun mkdir -p bun-${{ inputs.tag }}-profile/ bun-${{ inputs.tag }}/ mv bun-profile bun-${{ inputs.tag }}-profile/bun-profile - if [ -f bun-profile.dSYM ]; then + if [ -f bun-profile.dSYM || -d bun-profile.dSYM ]; then mv bun-profile.dSYM bun-${{ inputs.tag }}-profile/bun-profile.dSYM fi - if [ -f bun.dSYM ]; then + if [ -f bun.dSYM || -d bun.dSYM ]; then mv bun.dSYM bun-${{ inputs.tag }}-profile/bun-profile.dSYM fi mv bun bun-${{ inputs.tag }}/bun From 03024e6b4e3df9e55da0d4bbed2be2edadcc2746 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 19 Jul 2024 20:00:32 -0700 Subject: [PATCH 022/123] Fix truncating in BigIntStats (#12643) Co-authored-by: Jarred-Sumner --- src/bun.js/node/types.zig | 20 ++++++--- src/js/node/stream.ts | 2 +- test/bundler/expectBundled.ts | 2 +- test/cli/test/bun-test.test.ts | 2 +- .../next-pages/src/pages/index.tsx | 2 +- test/js/node/fs/fs-stats-truncate.test.ts | 43 +++++++++++++++++++ test/snippets/bundled-entry-point.js | 2 +- 7 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 test/js/node/fs/fs-stats-truncate.test.ts diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 50220361cf..e7591cfd53 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -1390,8 +1390,8 @@ pub fn StatType(comptime Big: bool) type { // Stats stores these as i32, but BigIntStats stores all of these as i64 // On windows, these two need to be u64 as the numbers are often very large. - dev: if (Environment.isWindows) u64 else Int, - ino: if (Environment.isWindows) u64 else Int, + dev: u64, + ino: u64, mode: Int, nlink: Int, uid: Int, @@ -1441,10 +1441,16 @@ pub fn StatType(comptime Big: bool) type { return struct { pub fn callback(this: *This, globalObject: *JSC.JSGlobalObject) JSC.JSValue { const value = @field(this, @tagName(field)); - if (comptime (Big and @typeInfo(@TypeOf(value)) == .Int)) { - return JSC.JSValue.fromInt64NoTruncate(globalObject, @intCast(value)); + const Type = @TypeOf(value); + if (comptime Big and @typeInfo(Type) == .Int) { + if (Type == u64) { + return JSC.JSValue.fromUInt64NoTruncate(globalObject, value); + } + + return JSC.JSValue.fromInt64NoTruncate(globalObject, value); } - return globalObject.toJS(value, .temporary); + + return JSC.JSValue.jsNumber(value); } }.callback; } @@ -1565,8 +1571,8 @@ pub fn StatType(comptime Big: bool) type { const cTime = stat_.ctime(); return .{ - .dev = if (Environment.isWindows) stat_.dev else @truncate(@as(i64, @intCast(stat_.dev))), - .ino = if (Environment.isWindows) stat_.ino else @truncate(@as(i64, @intCast(stat_.ino))), + .dev = @intCast(@max(stat_.dev, 0)), + .ino = @intCast(@max(stat_.ino, 0)), .mode = @truncate(@as(i64, @intCast(stat_.mode))), .nlink = @truncate(@as(i64, @intCast(stat_.nlink))), .uid = @truncate(@as(i64, @intCast(stat_.uid))), diff --git a/src/js/node/stream.ts b/src/js/node/stream.ts index ab4a9dffc1..48a8d44fe8 100644 --- a/src/js/node/stream.ts +++ b/src/js/node/stream.ts @@ -5720,7 +5720,7 @@ function createNativeStreamReadable(Readable) { ProcessNextTick(() => { this.push(null); }); - return view?.byteLength ?? 0 > 0 ? view : undefined; + return (view?.byteLength ?? 0 > 0) ? view : undefined; } else if ($isTypedArrayView(result)) { if (result.byteLength >= this[highWaterMark] && !this[hasResized] && !isClosed) { this[_adjustHighWaterMark](); diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts index ec6f4b3e6e..195a710cda 100644 --- a/test/bundler/expectBundled.ts +++ b/test/bundler/expectBundled.ts @@ -1259,7 +1259,7 @@ for (const [key, blob] of build.outputs) { const outfiletext = api.readFile(path.relative(root, outfile ?? outputPaths[0])); const regex = /\/\/\s+(.+?)\nvar\s+([a-zA-Z0-9_$]+)\s+=\s+__commonJS/g; const matches = [...outfiletext.matchAll(regex)].map(match => ("/" + match[1]).replaceAll("\\", "/")); - const expectedMatches = (cjs2esm === true ? [] : cjs2esm.unhandled ?? []).map(a => a.replaceAll("\\", "/")); + const expectedMatches = (cjs2esm === true ? [] : (cjs2esm.unhandled ?? [])).map(a => a.replaceAll("\\", "/")); try { expect(matches.sort()).toEqual(expectedMatches.sort()); } catch (error) { diff --git a/test/cli/test/bun-test.test.ts b/test/cli/test/bun-test.test.ts index 784bbfbd50..908da6f667 100644 --- a/test/cli/test/bun-test.test.ts +++ b/test/cli/test/bun-test.test.ts @@ -891,7 +891,7 @@ function createTest(input?: string | (string | { filename: string; contents: str const inputs = Array.isArray(input) ? input : [input ?? ""]; for (const input of inputs) { const contents = typeof input === "string" ? input : input.contents; - const name = typeof input === "string" ? filename ?? `bun-test-${Math.random()}.test.ts` : input.filename; + const name = typeof input === "string" ? (filename ?? `bun-test-${Math.random()}.test.ts`) : input.filename; const path = join(cwd, name); try { diff --git a/test/integration/next-pages/src/pages/index.tsx b/test/integration/next-pages/src/pages/index.tsx index 109f5e5e27..b87da9145d 100644 --- a/test/integration/next-pages/src/pages/index.tsx +++ b/test/integration/next-pages/src/pages/index.tsx @@ -122,7 +122,7 @@ export async function getStaticProps() { bunVersion: process.env.NODE_ENV === "production" ? "[production needs a constant string]" - : process.versions.bun ?? "not in bun", + : (process.versions.bun ?? "not in bun"), }, }; } diff --git a/test/js/node/fs/fs-stats-truncate.test.ts b/test/js/node/fs/fs-stats-truncate.test.ts new file mode 100644 index 0000000000..a4cda48275 --- /dev/null +++ b/test/js/node/fs/fs-stats-truncate.test.ts @@ -0,0 +1,43 @@ +// BUN-2C1 +// const value = @field(this, @tagName(field)); +// if (comptime (Big and @typeInfo(@TypeOf(value)) == .Int)) { +// return JSC.JSValue.fromInt64NoTruncate(globalObject, @intCast(value)); +// } +import { Stats, statSync } from "node:fs"; +import { test, expect } from "bun:test"; + +test("fs.stats truncate", async () => { + const stats = new Stats(...Array.from({ length: 14 }, () => Number.MAX_VALUE)); + expect(stats.dev).toBeGreaterThan(0); + expect(stats.mode).toBeGreaterThan(0); + expect(stats.nlink).toBeGreaterThan(0); + expect(stats.uid).toBeGreaterThan(0); + expect(stats.gid).toBeGreaterThan(0); + expect(stats.rdev).toBeGreaterThan(0); + expect(stats.blksize).toBeGreaterThan(0); + expect(stats.ino).toBeGreaterThan(0); + expect(stats.size).toBeGreaterThan(0); + expect(stats.blocks).toBeGreaterThan(0); + expect(stats.atimeMs).toBeGreaterThan(0); + expect(stats.mtimeMs).toBeGreaterThan(0); + expect(stats.ctimeMs).toBeGreaterThan(0); + expect(stats.birthtimeMs).toBeGreaterThan(0); +}); + +test("fs.stats truncate (bigint)", async () => { + const stats = statSync(import.meta.path, { bigint: true }); + expect(stats.dev).toBeTypeOf("bigint"); + expect(stats.mode).toBeTypeOf("bigint"); + expect(stats.nlink).toBeTypeOf("bigint"); + expect(stats.uid).toBeTypeOf("bigint"); + expect(stats.gid).toBeTypeOf("bigint"); + expect(stats.rdev).toBeTypeOf("bigint"); + expect(stats.blksize).toBeTypeOf("bigint"); + expect(stats.ino).toBeTypeOf("bigint"); + expect(stats.size).toBeTypeOf("bigint"); + expect(stats.blocks).toBeTypeOf("bigint"); + expect(stats.atimeMs).toBeTypeOf("bigint"); + expect(stats.mtimeMs).toBeTypeOf("bigint"); + expect(stats.ctimeMs).toBeTypeOf("bigint"); + expect(stats.birthtimeMs).toBeTypeOf("bigint"); +}); diff --git a/test/snippets/bundled-entry-point.js b/test/snippets/bundled-entry-point.js index a996f86327..77e119729e 100644 --- a/test/snippets/bundled-entry-point.js +++ b/test/snippets/bundled-entry-point.js @@ -1,6 +1,6 @@ import "react"; -var hello = 123 ? null ?? "world" : "ok"; +var hello = 123 ? (null ?? "world") : "ok"; export function test() { return testDone(import.meta.url); From f5d1a17a5c3324f2813317570bdfa28f36a6e4d8 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 19 Jul 2024 22:57:52 -0700 Subject: [PATCH 023/123] Fix crash in `bun exec cd` (#12676) Co-authored-by: Jarred-Sumner --- src/shell/interpreter.zig | 51 ++++++++++++++++++---------------- test/js/bun/shell/exec.test.ts | 7 +++++ 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/shell/interpreter.zig b/src/shell/interpreter.zig index dfd6b3a8aa..87e65e996b 100644 --- a/src/shell/interpreter.zig +++ b/src/shell/interpreter.zig @@ -7298,31 +7298,34 @@ pub const Interpreter = struct { return Maybe(void).success; } - const first_arg = args[0][0..std.mem.len(args[0]) :0]; - switch (first_arg[0]) { - '-' => { - switch (this.bltn.parentCmd().base.shell.changePrevCwd(this.bltn.parentCmd().base.interpreter)) { - .result => {}, - .err => |err| { - return this.handleChangeCwdErr(err, this.bltn.parentCmd().base.shell.prevCwdZ()); - }, - } - }, - '~' => { - const homedir = this.bltn.parentCmd().base.shell.getHomedir(); - homedir.deref(); - switch (this.bltn.parentCmd().base.shell.changeCwd(this.bltn.parentCmd().base.interpreter, homedir.slice())) { - .result => {}, - .err => |err| return this.handleChangeCwdErr(err, homedir.slice()), - } - }, - else => { - switch (this.bltn.parentCmd().base.shell.changeCwd(this.bltn.parentCmd().base.interpreter, first_arg)) { - .result => {}, - .err => |err| return this.handleChangeCwdErr(err, first_arg), - } - }, + if (args.len == 1) { + const first_arg = args[0][0..std.mem.len(args[0]) :0]; + switch (first_arg[0]) { + '-' => { + switch (this.bltn.parentCmd().base.shell.changePrevCwd(this.bltn.parentCmd().base.interpreter)) { + .result => {}, + .err => |err| { + return this.handleChangeCwdErr(err, this.bltn.parentCmd().base.shell.prevCwdZ()); + }, + } + }, + '~' => { + const homedir = this.bltn.parentCmd().base.shell.getHomedir(); + homedir.deref(); + switch (this.bltn.parentCmd().base.shell.changeCwd(this.bltn.parentCmd().base.interpreter, homedir.slice())) { + .result => {}, + .err => |err| return this.handleChangeCwdErr(err, homedir.slice()), + } + }, + else => { + switch (this.bltn.parentCmd().base.shell.changeCwd(this.bltn.parentCmd().base.interpreter, first_arg)) { + .result => {}, + .err => |err| return this.handleChangeCwdErr(err, first_arg), + } + }, + } } + this.bltn.done(0); return Maybe(void).success; } diff --git a/test/js/bun/shell/exec.test.ts b/test/js/bun/shell/exec.test.ts index 2fb108ddcf..cd6b074161 100644 --- a/test/js/bun/shell/exec.test.ts +++ b/test/js/bun/shell/exec.test.ts @@ -71,6 +71,13 @@ describe("bun exec", () => { } }); + TestBuilder.command`${BUN} exec cd` + .env(bunEnv) + .exitCode(0) + .stderr("") + .stdout("") + .runAsTest("cd with no arguments works"); + test("bun works even when not in PATH", async () => { const val = await $`bun exec 'bun'`.env({ ...bunEnv, PATH: "" }).nothrow(); expect(val.stderr.toString()).not.toContain("bun: command not found: bun"); From b7efeafc031ab128aa78eed5b41838d8533fad6a Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 19 Jul 2024 23:42:23 -0700 Subject: [PATCH 024/123] Deflake node-tls-connect test --- test/js/node/tls/node-tls-connect.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/js/node/tls/node-tls-connect.test.ts b/test/js/node/tls/node-tls-connect.test.ts index 68416e10e6..ceb46ee371 100644 --- a/test/js/node/tls/node-tls-connect.test.ts +++ b/test/js/node/tls/node-tls-connect.test.ts @@ -150,7 +150,7 @@ it("should have peer certificate", async () => { expect(infoAccess["OCSP - URI"]).toBeDefined(); expect(infoAccess["CA Issuers - URI"]).toBeDefined(); expect(cert.ca).toBeFalse(); - expect(cert.bits).toBe(2048); + expect(cert.bits).toBeInteger(); expect(typeof cert.modulus).toBe("string"); expect(typeof cert.exponent).toBe("string"); expect(cert.pubkey).toBeInstanceOf(Buffer); From 738947bdec0e0b6fe3c3433f11a414122ab5b084 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sat, 20 Jul 2024 02:36:08 -0700 Subject: [PATCH 025/123] Deflake node-tls-connect test --- test/js/node/tls/node-tls-connect.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/js/node/tls/node-tls-connect.test.ts b/test/js/node/tls/node-tls-connect.test.ts index ceb46ee371..123d2015f3 100644 --- a/test/js/node/tls/node-tls-connect.test.ts +++ b/test/js/node/tls/node-tls-connect.test.ts @@ -151,8 +151,9 @@ it("should have peer certificate", async () => { expect(infoAccess["CA Issuers - URI"]).toBeDefined(); expect(cert.ca).toBeFalse(); expect(cert.bits).toBeInteger(); - expect(typeof cert.modulus).toBe("string"); - expect(typeof cert.exponent).toBe("string"); + // These can change: + // expect(typeof cert.modulus).toBe("string"); + // expect(typeof cert.exponent).toBe("string"); expect(cert.pubkey).toBeInstanceOf(Buffer); expect(typeof cert.valid_from).toBe("string"); expect(typeof cert.valid_to).toBe("string"); From dc775f75f03e0ff32458fcd50d868a0f4cee5e86 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 21 Jul 2024 07:40:57 -0700 Subject: [PATCH 026/123] Fix BUN-2M9 --- src/napi/napi.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/napi/napi.zig b/src/napi/napi.zig index c8fc69d109..daa1c5f88b 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -1336,13 +1336,14 @@ pub export fn napi_get_node_version(_: napi_env, version_: ?**const napi_node_ve version.* = &napi_node_version.global; return .ok; } -pub export fn napi_get_uv_event_loop(env: napi_env, loop_: ?**JSC.EventLoop) napi_status { +const napi_event_loop = if (bun.Environment.isWindows) *bun.windows.libuv.Loop else *JSC.EventLoop; +pub export fn napi_get_uv_event_loop(env: napi_env, loop_: ?*napi_event_loop) napi_status { log("napi_get_uv_event_loop", .{}); const loop = loop_ orelse { return invalidArg(); }; if (bun.Environment.isWindows) { - loop.* = @ptrCast(@alignCast(env.bunVM().uvLoop())); + loop.* = env.bunVM().uvLoop(); } else { // there is no uv event loop on posix, we use our event loop handle. loop.* = env.bunVM().eventLoop(); From 822b725beca5efb63b34bfe6e0b2567ccf68d5d1 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 21 Jul 2024 08:49:00 -0700 Subject: [PATCH 027/123] Fix BUN-2M9, take two --- src/napi/napi.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/napi/napi.zig b/src/napi/napi.zig index daa1c5f88b..25ace47662 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -1343,7 +1343,9 @@ pub export fn napi_get_uv_event_loop(env: napi_env, loop_: ?*napi_event_loop) na return invalidArg(); }; if (bun.Environment.isWindows) { - loop.* = env.bunVM().uvLoop(); + // alignment error is incorrect. + @setRuntimeSafety(false); + loop.* = JSC.VirtualMachine.get().uvLoop(); } else { // there is no uv event loop on posix, we use our event loop handle. loop.* = env.bunVM().eventLoop(); From 9574044083dad1e64d1e6406a2dfac8c0560a959 Mon Sep 17 00:00:00 2001 From: ippsav <69125922+ippsav@users.noreply.github.com> Date: Mon, 22 Jul 2024 04:35:18 +0100 Subject: [PATCH 028/123] Fix expect.toThrow(expect.any()) matcher to correctly handle ExpectAny objects (#12670) --- src/bun.js/test/expect.zig | 6 ++++++ test/regression/issue/12650.test.js | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 test/regression/issue/12650.test.js diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index e4c8a1fa0d..509d73ea73 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -2284,6 +2284,12 @@ pub const Expect = struct { var fmt = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; globalThis.throw("Expected value must be string or Error: {any}", .{value.toFmt(globalThis, &fmt)}); return .zero; + } else if (value.isObject()) { + if (ExpectAny.fromJSDirect(value)) |_| { + if (ExpectAny.constructorValueGetCached(value)) |innerConstructorValue| { + break :brk innerConstructorValue; + } + } } break :brk value; } else .zero; diff --git a/test/regression/issue/12650.test.js b/test/regression/issue/12650.test.js new file mode 100644 index 0000000000..873bd0eca7 --- /dev/null +++ b/test/regression/issue/12650.test.js @@ -0,0 +1,23 @@ +import {expect, describe, it} from "bun:test"; + +// Custom class for testing +class CustomException extends Error { + constructor(message) { + super(message); + this.name = "CustomException"; + } +} + +describe('Test expect.toThrow(expect.any())', () => { + it('should throw an error', () => { + expect(() => { + throw new CustomException("Custom error message") + }).toThrow(expect.any(Error)) + }) + + it('should throw a CustomException', () => { + expect(() => { + throw new CustomException("Custom error message") + }).toThrow(expect.any(CustomException)) + }) +}) From bbf2f5d71648407cb268c59cbb1f5d427a9831fa Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 21 Jul 2024 21:32:52 -0700 Subject: [PATCH 029/123] Experiment: disable C++ static destructors (#12652) Co-authored-by: Jarred-Sumner --- CMakeLists.txt | 9 ++++++--- packages/bun-uws/src/Loop.h | 9 ++++++++- scripts/env.sh | 18 ++++++++++++++++-- src/bun.js/web_worker.zig | 1 + src/deps/libuwsockets.cpp | 5 +++++ src/deps/uws.zig | 5 +++++ src/generated_versions_list.zig | 2 +- test/regression/issue/12650.test.js | 22 +++++++++++----------- 8 files changed, 53 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e68cbf3939..70b8f52b67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_policy(SET CMP0067 NEW) set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) set(Bun_VERSION "1.1.21") -set(WEBKIT_TAG dac47fbd5444cbd4e3568267099ae276c547e897) +set(WEBKIT_TAG 2be773eeea48c03a4fa92c170934eb2220666809) set(BUN_WORKDIR "${CMAKE_CURRENT_BINARY_DIR}") message(STATUS "Configuring Bun ${Bun_VERSION} in ${BUN_WORKDIR}") @@ -1080,7 +1080,7 @@ elseif(CMAKE_BUILD_TYPE STREQUAL "Release") if(NOT WIN32) if(USE_LTO) - list(APPEND LTO_FLAG "-flto=full" "-emit-llvm") + list(APPEND LTO_FLAG "-flto=full" "-emit-llvm" "-fwhole-program-vtables" "-fforce-emit-vtables") endif() # Leave -Werror=unused off in release builds so we avoid errors from being used in ASSERT @@ -1169,7 +1169,7 @@ if(WIN32) # set_property(TARGET ${bun} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set_property(TARGET ${bun} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") - target_compile_options(${bun} PUBLIC "/EHsc" "/GR-") + target_compile_options(${bun} PUBLIC "/EHsc" "/GR-" -Xclang -fno-c++-static-destructors) target_link_options(${bun} PUBLIC "/STACK:0x1200000,0x100000" "/DEF:${BUN_SRC}/symbols.def" "/errorlimit:0") else() @@ -1178,6 +1178,9 @@ else() -fconstexpr-steps=2542484 -fconstexpr-depth=54 -fno-exceptions + -fno-asynchronous-unwind-tables + -fno-unwind-tables + -fno-c++-static-destructors -fvisibility=hidden -fvisibility-inlines-hidden -fno-rtti diff --git a/packages/bun-uws/src/Loop.h b/packages/bun-uws/src/Loop.h index ce4f3da95a..f8ea7f6e3a 100644 --- a/packages/bun-uws/src/Loop.h +++ b/packages/bun-uws/src/Loop.h @@ -95,13 +95,14 @@ private: // This is both a performance thing, and also to prevent freeing some things which are not meant to be freed // such as uv_tty_t if(loop && cleanMe && !bun_is_exiting()) { + cleanMe = false; loop->free(); } } Loop *loop = nullptr; bool cleanMe = false; }; - + static LoopCleaner &getLazyLoop() { static thread_local LoopCleaner lazyLoop; return lazyLoop; @@ -126,6 +127,12 @@ public: return getLazyLoop().loop; } + static void clearLoopAtThreadExit() { + if (getLazyLoop().cleanMe) { + getLazyLoop().loop->free(); + } + } + /* Freeing the default loop should be done once */ void free() { LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this); diff --git a/scripts/env.sh b/scripts/env.sh index 071ce4f989..e86241c515 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -23,17 +23,31 @@ export CC=${CC:-$(which clang-16 || which clang || which cc)} export CXX=${CXX:-$(which clang++-16 || which clang++ || which c++)} export AR=${AR:-$(which llvm-ar || which ar)} export CPUS=${CPUS:-$(nproc || sysctl -n hw.ncpu || echo 1)} +export RANLIB=${RANLIB:-$(which llvm-ranlib-16 || which llvm-ranlib || which ranlib)} + +# on Linux, force using lld as the linker +if [[ $(uname -s) == 'Linux' ]]; then + export LD=${LD:-$(which ld.lld-16 || which ld.lld || which ld)} + export LDFLAGS="${LDFLAGS} -fuse-ld=lld " +fi export CMAKE_CXX_COMPILER=${CXX} export CMAKE_C_COMPILER=${CC} export CFLAGS='-O3 -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables -faddrsig ' -export CXXFLAGS='-O3 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables -faddrsig ' +export CXXFLAGS='-O3 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables -faddrsig -fno-c++-static-destructors ' + +# Add flags for LTO +if [ "$BUN_ENABLE_LTO" == "1" ]; then + export CFLAGS="$CFLAGS -flto=full " + export CXXFLAGS="$CXXFLAGS -flto=full -fwhole-program-vtables -fforce-emit-vtables " + export LDFLAGS="$LDFLAGS -flto=full -fwhole-program-vtables -fforce-emit-vtables " +fi if [[ $(uname -s) == 'Linux' ]]; then export CFLAGS="$CFLAGS -ffunction-sections -fdata-sections" export CXXFLAGS="$CXXFLAGS -ffunction-sections -fdata-sections" - export LDFLAGS="${LDFLAGS} -Wl,-z,norelro " + export LDFLAGS="${LDFLAGS} -Wl,-z,norelro" fi # libarchive needs position-independent executables to compile successfully diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig index 28a5dbdf6d..ae0a6ac0f0 100644 --- a/src/bun.js/web_worker.zig +++ b/src/bun.js/web_worker.zig @@ -421,6 +421,7 @@ pub const WebWorker = struct { var arena = this.arena; WebWorker__dispatchExit(globalObject, cpp_worker, exit_code); + bun.uws.onThreadExit(); this.deinit(); if (vm_to_deinit) |vm| { diff --git a/src/deps/libuwsockets.cpp b/src/deps/libuwsockets.cpp index c1af335cd7..2741bfca8d 100644 --- a/src/deps/libuwsockets.cpp +++ b/src/deps/libuwsockets.cpp @@ -1632,4 +1632,9 @@ extern "C" return strlen(*dest); } } + + // we need to manually call this at thread exit + extern "C" void bun_clear_loop_at_thread_exit() { + uWS::Loop::clearLoopAtThreadExit(); + } } diff --git a/src/deps/uws.zig b/src/deps/uws.zig index c630dbaf93..eab7032d31 100644 --- a/src/deps/uws.zig +++ b/src/deps/uws.zig @@ -3090,3 +3090,8 @@ pub const udp = struct { extern fn us_udp_packet_buffer_payload(buf: ?*PacketBuffer, index: c_int) [*]u8; extern fn us_udp_packet_buffer_payload_length(buf: ?*PacketBuffer, index: c_int) c_int; }; + +extern fn bun_clear_loop_at_thread_exit() void; +pub fn onThreadExit() void { + bun_clear_loop_at_thread_exit(); +} diff --git a/src/generated_versions_list.zig b/src/generated_versions_list.zig index 6a28038a88..3e17ae8ad3 100644 --- a/src/generated_versions_list.zig +++ b/src/generated_versions_list.zig @@ -4,7 +4,7 @@ pub const boringssl = "29a2cd359458c9384694b75456026e4b57e3e567"; pub const libarchive = "898dc8319355b7e985f68a9819f182aaed61b53a"; pub const mimalloc = "4c283af60cdae205df5a872530c77e2a6a307d43"; pub const picohttpparser = "066d2b1e9ab820703db0837a7255d92d30f0c9f5"; -pub const webkit = "dac47fbd5444cbd4e3568267099ae276c547e897"; +pub const webkit = "2be773eeea48c03a4fa92c170934eb2220666809"; pub const zig = @import("std").fmt.comptimePrint("{}", .{@import("builtin").zig_version}); pub const zlib = "886098f3f339617b4243b286f5ed364b9989e245"; pub const tinycc = "ab631362d839333660a265d3084d8ff060b96753"; diff --git a/test/regression/issue/12650.test.js b/test/regression/issue/12650.test.js index 873bd0eca7..eab73865d2 100644 --- a/test/regression/issue/12650.test.js +++ b/test/regression/issue/12650.test.js @@ -1,4 +1,4 @@ -import {expect, describe, it} from "bun:test"; +import { expect, describe, it } from "bun:test"; // Custom class for testing class CustomException extends Error { @@ -8,16 +8,16 @@ class CustomException extends Error { } } -describe('Test expect.toThrow(expect.any())', () => { - it('should throw an error', () => { +describe("Test expect.toThrow(expect.any())", () => { + it("should throw an error", () => { expect(() => { - throw new CustomException("Custom error message") - }).toThrow(expect.any(Error)) - }) + throw new CustomException("Custom error message"); + }).toThrow(expect.any(Error)); + }); - it('should throw a CustomException', () => { + it("should throw a CustomException", () => { expect(() => { - throw new CustomException("Custom error message") - }).toThrow(expect.any(CustomException)) - }) -}) + throw new CustomException("Custom error message"); + }).toThrow(expect.any(CustomException)); + }); +}); From 1a6ead667b7a311ec0c69d833d7041d5829d1aa2 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 03:41:59 -0700 Subject: [PATCH 030/123] Introduce `bun --fetch-preconnect ./my-script.ts` (#12698) --- docs/api/fetch.md | 240 ++++++++++++++++++++ docs/nav.ts | 5 +- packages/bun-types/bun.d.ts | 4 + packages/bun-types/globals.d.ts | 54 +++-- src/bun.js/bindings/BunObject.cpp | 20 +- src/bun.js/bindings/BunObject.h | 2 + src/bun.js/bindings/ZigGlobalObject.cpp | 1 - src/bun.js/bindings/ZigGlobalObject.lut.txt | 2 +- src/bun.js/webcore/response.zig | 52 +++++ src/bun_js.zig | 30 +++ src/cli.zig | 4 + src/feature_flags.zig | 3 + src/http.zig | 94 +++++++- src/url.zig | 2 +- test/js/web/fetch/fetch-preconnect.test.ts | 99 ++++++++ test/js/web/fetch/fetch-redirect.test.ts | 32 +++ 16 files changed, 613 insertions(+), 31 deletions(-) create mode 100644 docs/api/fetch.md create mode 100644 test/js/web/fetch/fetch-preconnect.test.ts create mode 100644 test/js/web/fetch/fetch-redirect.test.ts diff --git a/docs/api/fetch.md b/docs/api/fetch.md new file mode 100644 index 0000000000..0e50972a8e --- /dev/null +++ b/docs/api/fetch.md @@ -0,0 +1,240 @@ +Bun implements the WHATWG `fetch` standard, with some extensions to meet the needs of server-side JavaScript. + +Bun also implements `node:http`, but `fetch` is generally recommended instead. + +## Sending an HTTP request + +To send an HTTP request, use `fetch` + +```ts +const response = await fetch("http://example.com"); + +console.log(response.status); // => 200 + +const text = await response.text(); // or response.json(), response.formData(), etc. +``` + +`fetch` also works with HTTPS URLs. + +```ts +const response = await fetch("https://example.com"); +``` + +You can also pass `fetch` a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. + +```ts +const request = new Request("http://example.com", { + method: "POST", + body: "Hello, world!", +}); + +const response = await fetch(request); +``` + +### Sending a POST request + +To send a POST request, pass an object with the `method` property set to `"POST"`. + +```ts +const response = await fetch("http://example.com", { + method: "POST", + body: "Hello, world!", +}); +``` + +`body` can be a string, a `FormData` object, an `ArrayBuffer`, a `Blob`, and more. See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Body/body) for more information. + +### Proxying requests + +To proxy a request, pass an object with the `proxy` property set to a URL. + +```ts +const response = await fetch("http://example.com", { + proxy: "http://proxy.com", +}); +``` + +### Custom headers + +To set custom headers, pass an object with the `headers` property set to an object. + +```ts +const response = await fetch("http://example.com", { + headers: { + "X-Custom-Header": "value", + }, +}); +``` + +You can also set headers using the [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object. + +```ts +const headers = new Headers(); +headers.append("X-Custom-Header", "value"); + +const response = await fetch("http://example.com", { + headers, +}); +``` + +### Response bodies + +To read the response body, use one of the following methods: + +- `response.text(): Promise`: Returns a promise that resolves with the response body as a string. +- `response.json(): Promise`: Returns a promise that resolves with the response body as a JSON object. +- `response.formData(): Promise`: Returns a promise that resolves with the response body as a `FormData` object. +- `response.bytes(): Promise`: Returns a promise that resolves with the response body as a `Uint8Array`. +- `response.arrayBuffer(): Promise`: Returns a promise that resolves with the response body as an `ArrayBuffer`. +- `response.blob(): Promise`: Returns a promise that resolves with the response body as a `Blob`. + +#### Streaming response bodies + +You can use async iterators to stream the response body. + +```ts +const response = await fetch("http://example.com"); + +for await (const chunk of response.body) { + console.log(chunk); +} +``` + +You can also more directly access the `ReadableStream` object. + +```ts +const response = await fetch("http://example.com"); + +const stream = response.body; + +const reader = stream.getReader(); +const { value, done } = await reader.read(); +``` + +### Fetching a URL with a timeout + +To fetch a URL with a timeout, use `AbortSignal.timeout`: + +```ts +const response = await fetch("http://example.com", { + signal: AbortSignal.timeout(1000), +}); +``` + +#### Canceling a request + +To cancel a request, use an `AbortController`: + +```ts +const controller = new AbortController(); + +const response = await fetch("http://example.com", { + signal: controller.signal, +}); + +controller.abort(); +``` + +## Debugging + +To help with debugging, you can pass `verbose: true` to `fetch`: + +```ts +const response = await fetch("http://example.com", { + verbose: true, +}); +``` + +This will print the request and response headers to your terminal: + +```sh +[fetch] > HTTP/1.1 GET http://example.com/ +[fetch] > Connection: keep-alive +[fetch] > User-Agent: Bun/1.1.21 +[fetch] > Accept: */* +[fetch] > Host: example.com +[fetch] > Accept-Encoding: gzip, deflate, br + +[fetch] < 200 OK +[fetch] < Content-Encoding: gzip +[fetch] < Age: 201555 +[fetch] < Cache-Control: max-age=604800 +[fetch] < Content-Type: text/html; charset=UTF-8 +[fetch] < Date: Sun, 21 Jul 2024 02:41:14 GMT +[fetch] < Etag: "3147526947+gzip" +[fetch] < Expires: Sun, 28 Jul 2024 02:41:14 GMT +[fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT +[fetch] < Server: ECAcc (sac/254F) +[fetch] < Vary: Accept-Encoding +[fetch] < X-Cache: HIT +[fetch] < Content-Length: 648 +``` + +Note: `verbose: boolean` is not part of the Web standard `fetch` API and is specific to Bun. + +## Performance + +Before an HTTP request can be sent, the DNS lookup must be performed. This can take a significant amount of time, especially if the DNS server is slow or the network connection is poor. + +After the DNS lookup, the TCP socket must be connected and the TLS handshake might need to be performed. This can also take a significant amount of time. + +After the request completes, consuming the response body can also take a significant amount of time and memory. + +At every step of the way, Bun provides APIs to help you optimize the performance of your application. + +### DNS prefetching + +To prefetch a DNS entry, you can use the `dns.prefetch` API. This API is useful when you know you'll need to connect to a host soon and want to avoid the initial DNS lookup. + +```ts +import { dns } from "bun"; + +dns.prefetch("bun.sh", 443); +``` + +### Preconnect to a host + +To preconnect to a host, you can use the `http.prefetch` API. This API is useful when you know you'll need to connect to a host soon and want to start the initial DNS lookup, TCP socket connection, and TLS handshake early. + +```ts +import { fetch } from "bun"; + +fetch.preconnect("https://bun.sh"); +``` + +Note: calling `fetch` immediately after `fetch.preconnect` will not make your request faster. Preconnecting only helps if you know you'll need to connect to a host soon, but you're not ready to make the request yet. + +#### Preconnect at startup + +To preconnect to a host at startup, you can pass `--fetch-preconnect`: + +```sh +$ bun --fetch-preconnect https://bun.sh ./my-script.ts +``` + +This is sort of like `` in HTML. + +This feature is not implemented on Windows yet. If you're interested in using this feature on Windows, please file an issue and we can implement support for it on Windows. + +### Connection pooling & HTTP keep-alive + +Bun automatically reuses connections to the same host. This is known as connection pooling. This can significantly reduce the time it takes to establish a connection. You don't need to do anything to enable this; it's automatic. + +### Response buffering + +Bun goes to great lengths to optimize the performance of reading the response body. The fastest way to read the response body is to use one of these methods: + +- `response.text(): Promise` +- `response.json(): Promise` +- `response.formData(): Promise` +- `response.bytes(): Promise` +- `response.arrayBuffer(): Promise` +- `response.blob(): Promise` + +You can also use `Bun.write` to write the response body to a file on disk: + +```ts +import { write } from "bun"; + +await write("output.txt", response); +``` diff --git a/docs/nav.ts b/docs/nav.ts index 41da0c4281..93331f983f 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -287,8 +287,11 @@ export default { divider("API"), page("api/http", "HTTP server", { - description: `Bun implements Web-standard fetch, plus a Bun-native API for building fast HTTP servers.`, + description: `Bun implements a fast HTTP server built on Request/Response objects, along with supporting node:http APIs.`, }), // "`Bun.serve`"), + page("api/fetch", "HTTP client", { + description: `Bun implements Web-standard fetch with some Bun-native extensions.`, + }), // "fetch"), page("api/websockets", "WebSockets", { description: `Bun supports server-side WebSockets with on-the-fly compression, TLS support, and a Bun-native pubsub API.`, }), // "`Bun.serve`"), diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 7dc3808ce3..9a91cc8e46 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -3100,6 +3100,10 @@ declare module "bun" { */ function openInEditor(path: string, options?: EditorOptions): void; + const fetch: typeof globalThis.fetch & { + preconnect(url: string): void; + }; + interface EditorOptions { editor?: "vscode" | "subl"; line?: number; diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index 60f2c4090f..3e5a252fa3 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -907,26 +907,42 @@ declare global { new (): ShadowRealm; }; - /** - * Send a HTTP(s) request - * - * @param request Request object - * @param init A structured value that contains settings for the fetch() request. - * - * @returns A promise that resolves to {@link Response} object. - */ + interface Fetch { + /** + * Send a HTTP(s) request + * + * @param request Request object + * @param init A structured value that contains settings for the fetch() request. + * + * @returns A promise that resolves to {@link Response} object. + */ + (request: Request, init?: RequestInit): Promise; - // tslint:disable-next-line:unified-signatures - function fetch(request: Request, init?: RequestInit): Promise; - /** - * Send a HTTP(s) request - * - * @param url URL string - * @param init A structured value that contains settings for the fetch() request. - * - * @returns A promise that resolves to {@link Response} object. - */ - function fetch(url: string | URL | Request, init?: FetchRequestInit): Promise; + /** + * Send a HTTP(s) request + * + * @param url URL string + * @param init A structured value that contains settings for the fetch() request. + * + * @returns A promise that resolves to {@link Response} object. + */ + (url: string | URL | Request, init?: FetchRequestInit): Promise; + + (input: string | URL | globalThis.Request, init?: RequestInit): Promise; + + /** + * Start the DNS resolution, TCP connection, and TLS handshake for a request + * before the request is actually sent. + * + * This can reduce the latency of a request when you know there's some + * long-running task that will delay the request starting. + * + * This is a bun-specific API and is not part of the Fetch API specification. + */ + preconnect(url: string | URL): void; + } + + var fetch: Fetch; function queueMicrotask(callback: (...args: any[]) => void): void; /** diff --git a/src/bun.js/bindings/BunObject.cpp b/src/bun.js/bindings/BunObject.cpp index c15cb12a5a..f29203a995 100644 --- a/src/bun.js/bindings/BunObject.cpp +++ b/src/bun.js/bindings/BunObject.cpp @@ -49,6 +49,7 @@ BUN_DECLARE_HOST_FUNCTION(Bun__DNSResolver__lookupService); BUN_DECLARE_HOST_FUNCTION(Bun__DNSResolver__prefetch); BUN_DECLARE_HOST_FUNCTION(Bun__DNSResolver__getCacheStats); BUN_DECLARE_HOST_FUNCTION(Bun__fetch); +BUN_DECLARE_HOST_FUNCTION(Bun__fetchPreconnect); namespace Bun { @@ -267,6 +268,17 @@ static JSValue constructPasswordObject(VM& vm, JSObject* bunObject) return JSValue::decode(JSPasswordObject__create(bunObject->globalObject())); } +JSValue constructBunFetchObject(VM& vm, JSObject* bunObject) +{ + JSFunction* fetchFn = JSFunction::create(vm, bunObject->globalObject(), 1, "fetch"_s, Bun__fetch, ImplementationVisibility::Public, NoIntrinsic); + + auto* globalObject = jsCast(bunObject->globalObject()); + fetchFn->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "preconnect"_s), 1, Bun__fetchPreconnect, ImplementationVisibility::Public, NoIntrinsic, + JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + + return fetchFn; +} + static JSValue constructBunShell(VM& vm, JSObject* bunObject) { auto* globalObject = jsCast(bunObject->globalObject()); @@ -549,14 +561,14 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj cwd BunObject_getter_wrap_cwd DontEnum|DontDelete|PropertyCallback deepEquals functionBunDeepEquals DontDelete|Function 2 deepMatch functionBunDeepMatch DontDelete|Function 2 - deflateSync BunObject_callback_deflateSync DontDelete|Function 1 + deflateSync BunObject_callback_deflateSync DontDelete|Function 1 dns constructDNSObject ReadOnly|DontDelete|PropertyCallback enableANSIColors BunObject_getter_wrap_enableANSIColors DontDelete|PropertyCallback env constructEnvObject ReadOnly|DontDelete|PropertyCallback escapeHTML functionBunEscapeHTML DontDelete|Function 2 - fetch Bun__fetch ReadOnly|DontDelete|Function 1 - file BunObject_callback_file DontDelete|Function 1 - fileURLToPath functionFileURLToPath DontDelete|Function 1 + fetch constructBunFetchObject ReadOnly|DontDelete|PropertyCallback + file BunObject_callback_file DontDelete|Function 1 + fileURLToPath functionFileURLToPath DontDelete|Function 1 gc BunObject_callback_gc DontDelete|Function 1 generateHeapSnapshot BunObject_callback_generateHeapSnapshot DontDelete|Function 1 gunzipSync BunObject_callback_gunzipSync DontDelete|Function 1 diff --git a/src/bun.js/bindings/BunObject.h b/src/bun.js/bindings/BunObject.h index f5c167e2fb..2ddd9ab04a 100644 --- a/src/bun.js/bindings/BunObject.h +++ b/src/bun.js/bindings/BunObject.h @@ -12,5 +12,7 @@ JSC_DECLARE_HOST_FUNCTION(functionBunNanoseconds); JSC_DECLARE_HOST_FUNCTION(functionPathToFileURL); JSC_DECLARE_HOST_FUNCTION(functionFileURLToPath); +JSC::JSValue constructBunFetchObject(VM& vm, JSObject* bunObject); JSC::JSObject* createBunObject(VM& vm, JSObject* globalObject); + } diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index b97bf396d1..e56b0edc10 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -165,7 +165,6 @@ using namespace Bun; BUN_DECLARE_HOST_FUNCTION(Bun__NodeUtil__jsParseArgs); -BUN_DECLARE_HOST_FUNCTION(Bun__fetch); BUN_DECLARE_HOST_FUNCTION(BUN__HTTP2__getUnpackedSettings); BUN_DECLARE_HOST_FUNCTION(BUN__HTTP2_getPackedSettings); diff --git a/src/bun.js/bindings/ZigGlobalObject.lut.txt b/src/bun.js/bindings/ZigGlobalObject.lut.txt index 1e57ace3f5..91eb9d3e94 100644 --- a/src/bun.js/bindings/ZigGlobalObject.lut.txt +++ b/src/bun.js/bindings/ZigGlobalObject.lut.txt @@ -11,7 +11,7 @@ clearTimeout functionClearTimeout Function 1 confirm WebCore__confirm Function 1 dispatchEvent jsFunctionDispatchEvent Function 1 - fetch Bun__fetch Function 2 + fetch constructBunFetchObject PropertyCallback postMessage jsFunctionPostMessage Function 1 prompt WebCore__prompt Function 1 queueMicrotask functionQueueMicrotask Function 2 diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 496737f1c7..4abe1d3662 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -713,6 +713,7 @@ pub const Fetch = struct { comptime { if (!JSC.is_bindgen) { _ = Bun__fetch; + _ = Bun__fetchPreconnect; } } @@ -1836,6 +1837,57 @@ pub const Fetch = struct { return JSPromise.resolvedPromiseValue(globalThis, response.toJS(globalThis)); } + pub export fn Bun__fetchPreconnect( + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(JSC.conv) JSC.JSValue { + const arguments = callframe.arguments(1).slice(); + + if (arguments.len < 1) { + globalObject.throwNotEnoughArguments("fetch.preconnect", 1, arguments.len); + return .zero; + } + + var url_str = JSC.URL.hrefFromJS(arguments[0], globalObject); + defer url_str.deref(); + + if (globalObject.hasException()) { + return .zero; + } + + if (url_str.tag == .Dead) { + globalObject.throwValue(JSC.toTypeError(.ERR_INVALID_ARG_TYPE, "Invalid URL", .{}, globalObject)); + return .zero; + } + + if (url_str.isEmpty()) { + globalObject.throwValue(JSC.toTypeError(.ERR_INVALID_ARG_VALUE, fetch_error_blank_url, .{}, globalObject)); + return .zero; + } + + const url = ZigURL.parse(url_str.toOwnedSlice(bun.default_allocator) catch bun.outOfMemory()); + if (!url.isHTTP() and !url.isHTTPS()) { + globalObject.throwInvalidArguments("URL must be HTTP or HTTPS", .{}); + bun.default_allocator.free(url.href); + return .zero; + } + + if (url.hostname.len == 0) { + globalObject.throwValue(JSC.toTypeError(.ERR_INVALID_ARG_VALUE, fetch_error_blank_url, .{}, globalObject)); + bun.default_allocator.free(url.href); + return .zero; + } + + if (!url.hasValidPort()) { + globalObject.throwInvalidArguments("Invalid port", .{}); + bun.default_allocator.free(url.href); + return .zero; + } + + bun.http.AsyncHTTP.preconnect(url, true); + return .undefined; + } + pub export fn Bun__fetch( ctx: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, diff --git a/src/bun_js.zig b/src/bun_js.zig index 91f97accf3..d1d9fe6d34 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -118,10 +118,38 @@ pub const Run = struct { vm.is_main_thread = true; JSC.VirtualMachine.is_main_thread_vm = true; + doPreconnect(ctx.runtime_options.preconnect); + const callback = OpaqueWrap(Run, Run.start); vm.global.vm().holdAPILock(&run, callback); } + fn doPreconnect(preconnect: []const string) void { + if (preconnect.len == 0) return; + bun.HTTPThread.init(); + + for (preconnect) |url_str| { + const url = bun.URL.parse(url_str); + + if (!url.isHTTP() and !url.isHTTPS()) { + Output.errGeneric("preconnect URL must be HTTP or HTTPS: {}", .{bun.fmt.quote(url_str)}); + Global.exit(1); + } + + if (url.hostname.len == 0) { + Output.errGeneric("preconnect URL must have a hostname: {}", .{bun.fmt.quote(url_str)}); + Global.exit(1); + } + + if (!url.hasValidPort()) { + Output.errGeneric("preconnect URL must have a valid port: {}", .{bun.fmt.quote(url_str)}); + Global.exit(1); + } + + AsyncHTTP.preconnect(url, false); + } + } + fn bootBunShell(ctx: Command.Context, entry_path: []const u8) !bun.shell.ExitCode { @setCold(true); @@ -242,6 +270,8 @@ pub const Run = struct { vm.bundler.env.loadTracy(); + doPreconnect(ctx.runtime_options.preconnect); + const callback = OpaqueWrap(Run, Run.start); vm.global.vm().holdAPILock(&run, callback); } diff --git a/src/cli.zig b/src/cli.zig index 42ba78602d..e3da040156 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -206,6 +206,7 @@ pub const Arguments = struct { clap.parseParam("-p, --port Set the default port for Bun.serve") catch unreachable, clap.parseParam("-u, --origin ") catch unreachable, clap.parseParam("--conditions ... Pass custom conditions to resolve") catch unreachable, + clap.parseParam("--fetch-preconnect ... Preconnect to a URL while code is loading") catch unreachable, }; const auto_or_run_params = [_]ParamType{ @@ -649,6 +650,8 @@ pub const Arguments = struct { } ctx.runtime_options.if_present = args.flag("--if-present"); ctx.runtime_options.smol = args.flag("--smol"); + ctx.runtime_options.preconnect = args.options("--fetch-preconnect"); + if (args.option("--inspect")) |inspect_flag| { ctx.runtime_options.debugger = if (inspect_flag.len == 0) Command.Debugger{ .enable = .{} } @@ -1228,6 +1231,7 @@ pub const Command = struct { script: []const u8 = "", eval_and_print: bool = false, } = .{}, + preconnect: []const []const u8 = &[_][]const u8{}, }; var global_cli_ctx: Context = undefined; diff --git a/src/feature_flags.zig b/src/feature_flags.zig index 7b2e14dbbe..4e25f5442f 100644 --- a/src/feature_flags.zig +++ b/src/feature_flags.zig @@ -181,3 +181,6 @@ pub const breaking_changes_1_2 = false; pub const nonblocking_stdout_and_stderr_on_posix = false; pub const postgresql = env.is_canary or env.isDebug; + +// TODO: fix Windows-only test failures in fetch-preconnect.test.ts +pub const is_fetch_preconnect_supported = env.isPosix; diff --git a/src/http.zig b/src/http.zig index 187ae3d31d..d1ff0f9c84 100644 --- a/src/http.zig +++ b/src/http.zig @@ -1408,6 +1408,7 @@ pub const InternalState = struct { received_last_chunk: bool = false, did_set_content_encoding: bool = false, is_redirect_pending: bool = false, + resend_request_body_on_redirect: bool = false, transfer_encoding: Encoding = Encoding.identity, encoding: Encoding = Encoding.identity, content_encoding_i: u8 = std.math.maxInt(u8), @@ -1591,8 +1592,8 @@ signals: Signals = .{}, async_http_id: u32 = 0, hostname: ?[]u8 = null, reject_unauthorized: bool = true, - unix_socket_path: JSC.ZigString.Slice = JSC.ZigString.Slice.empty, +is_preconnect_only: bool = false, pub fn deinit(this: *HTTPClient) void { if (this.redirect.len > 0) { @@ -1833,6 +1834,51 @@ pub const AsyncHTTP = struct { tls_props: ?*SSLConfig = null, }; + const Preconnect = struct { + async_http: AsyncHTTP, + response_buffer: MutableString, + url: bun.URL, + is_url_owned: bool, + + pub usingnamespace bun.New(@This()); + + pub fn onResult(this: *Preconnect, _: *AsyncHTTP, _: HTTPClientResult) void { + this.response_buffer.deinit(); + this.async_http.clearData(); + this.async_http.client.deinit(); + if (this.is_url_owned) { + bun.default_allocator.free(this.url.href); + } + + this.destroy(); + } + }; + + pub fn preconnect( + url: URL, + is_url_owned: bool, + ) void { + if (!FeatureFlags.is_fetch_preconnect_supported) { + if (is_url_owned) { + bun.default_allocator.free(url.href); + } + + return; + } + + var this = Preconnect.new(.{ + .async_http = undefined, + .response_buffer = MutableString{ .allocator = default_allocator, .list = .{} }, + .url = url, + .is_url_owned = is_url_owned, + }); + + this.async_http = AsyncHTTP.init(bun.default_allocator, .GET, url, .{}, "", &this.response_buffer, "", 0, HTTPClientResult.Callback.New(*Preconnect, Preconnect.onResult).init(this), .manual, .{}); + this.async_http.client.is_preconnect_only = true; + + http_thread.schedule(Batch.from(&this.async_http.task)); + } + pub fn init( allocator: std.mem.Allocator, method: Method, @@ -2236,11 +2282,21 @@ pub fn buildRequest(this: *HTTPClient, body_len: usize) picohttp.Request { }; } -pub fn doRedirect(this: *HTTPClient, comptime is_ssl: bool, ctx: *NewHTTPContext(is_ssl), socket: NewHTTPContext(is_ssl).HTTPSocket) void { +pub fn doRedirect( + this: *HTTPClient, + comptime is_ssl: bool, + ctx: *NewHTTPContext(is_ssl), + socket: NewHTTPContext(is_ssl).HTTPSocket, +) void { this.unix_socket_path.deinit(); this.unix_socket_path = JSC.ZigString.Slice.empty; + const request_body = if (this.state.resend_request_body_on_redirect and this.state.original_request_body == .bytes) + this.state.original_request_body.bytes + else + ""; this.state.response_message_buffer.deinit(); + // we need to clean the client reference before closing the socket because we are going to reuse the same ref in a another request socket.ext(**anyopaque).* = bun.cast(**anyopaque, NewHTTPContext(is_ssl).ActiveSocket.init(&dead_socket).ptr()); if (this.isKeepAlivePossible()) { @@ -2278,7 +2334,8 @@ pub fn doRedirect(this: *HTTPClient, comptime is_ssl: bool, ctx: *NewHTTPContext if (this.signals.aborted != null) { _ = socket_async_http_abort_tracker.swapRemove(this.async_http_id); } - return this.start(.{ .bytes = "" }, body_out_str); + + return this.start(.{ .bytes = request_body }, body_out_str); } pub fn isHTTPS(this: *HTTPClient) bool { if (this.http_proxy) |proxy| { @@ -2368,12 +2425,39 @@ fn printResponse(response: picohttp.Response) void { Output.flush(); } +pub fn onPreconnect(this: *HTTPClient, comptime is_ssl: bool, socket: NewHTTPContext(is_ssl).HTTPSocket) void { + log("onPreconnect({})", .{this.url}); + _ = socket_async_http_abort_tracker.swapRemove(this.async_http_id); + const ctx = if (comptime is_ssl) &http_thread.https_context else &http_thread.http_context; + + ctx.releaseSocket( + socket, + this.did_have_handshaking_error and !this.reject_unauthorized, + this.url.hostname, + this.url.getPortAuto(), + ); + + this.state.reset(this.allocator); + this.state.response_stage = .done; + this.state.request_stage = .done; + this.state.stage = .done; + this.proxy_tunneling = false; + this.result_callback.run(@fieldParentPtr("client", this), HTTPClientResult{ .fail = null, .metadata = null, .has_more = false }); +} + pub fn onWritable(this: *HTTPClient, comptime is_first_call: bool, comptime is_ssl: bool, socket: NewHTTPContext(is_ssl).HTTPSocket) void { if (this.signals.get(.aborted)) { this.closeAndAbort(is_ssl, socket); return; } + if (comptime FeatureFlags.is_fetch_preconnect_supported) { + if (this.is_preconnect_only) { + this.onPreconnect(is_ssl, socket); + return; + } + } + switch (this.state.request_stage) { .pending, .headers => { var stack_fallback = std.heap.stackFallback(16384, default_allocator); @@ -2787,7 +2871,6 @@ pub fn onData(this: *HTTPClient, comptime is_ssl: bool, incoming_data: []const u } return; } - const should_continue = this.handleResponseMetadata( &response, ) catch |err| { @@ -3785,6 +3868,9 @@ pub fn handleResponseMetadata( } this.state.is_redirect_pending = true; + if (this.method.hasRequestBody()) { + this.state.resend_request_body_on_redirect = true; + } }, else => {}, } diff --git a/src/url.zig b/src/url.zig index 9f4614b3b2..38e3b6aab1 100644 --- a/src/url.zig +++ b/src/url.zig @@ -130,7 +130,7 @@ pub const URL = struct { } pub fn hasValidPort(this: *const URL) bool { - return (this.getPort() orelse 0) > 1; + return (this.getPort() orelse 0) > 0; } pub fn isEmpty(this: *const URL) bool { diff --git a/test/js/web/fetch/fetch-preconnect.test.ts b/test/js/web/fetch/fetch-preconnect.test.ts new file mode 100644 index 0000000000..88a842e5cc --- /dev/null +++ b/test/js/web/fetch/fetch-preconnect.test.ts @@ -0,0 +1,99 @@ +import { describe, it, expect } from "bun:test"; +import "harness"; +import { isWindows } from "harness"; + +// TODO: on Windows, these tests fail. +// This feature is mostly meant for serverless JS environments, so we can no-op it on Windows. +describe.todoIf(isWindows)("fetch.preconnect", () => { + it("fetch.preconnect works", async () => { + const { promise, resolve } = Promise.withResolvers(); + using listener = Bun.listen({ + port: 0, + hostname: "localhost", + socket: { + open(socket) { + resolve(socket); + }, + data() {}, + close() {}, + }, + }); + fetch.preconnect(`http://localhost:${listener.port}`); + const socket = await promise; + const fetchPromise = fetch(`http://localhost:${listener.port}`); + await Bun.sleep(64); + socket.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + socket.end(); + + const response = await fetchPromise; + expect(response.status).toBe(200); + }); + + describe("closing the connection doesn't break the request", () => { + for (let at of ["before", "after"]) { + it(at, async () => { + let { promise, resolve } = Promise.withResolvers(); + using listener = Bun.listen({ + port: 0, + hostname: "localhost", + socket: { + open(socket) { + resolve(socket); + }, + data() {}, + close() {}, + }, + }); + fetch.preconnect(`http://localhost:${listener.port}`); + let socket = await promise; + ({ promise, resolve } = Promise.withResolvers()); + if (at === "before") { + await Bun.sleep(16); + socket.end(); + } + const fetchPromise = fetch(`http://localhost:${listener.port}`); + if (at === "after") { + await Bun.sleep(16); + socket.end(); + } + socket = await promise; + socket.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + socket.end(); + + const response = await fetchPromise; + expect(response.status).toBe(200); + }); + } + }); + + it("--fetch-preconnect works", async () => { + const { promise, resolve } = Promise.withResolvers(); + const listener = Bun.listen({ + port: 0, + hostname: "localhost", + socket: { + open(socket) { + socket.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + socket.end(); + resolve(); + }, + data() {}, + close() {}, + }, + }); + + // Do --fetch-preconnect, but don't actually send a request. + expect([`--fetch-preconnect=http://localhost:${listener.port}`, "--eval", "Bun.sleep(64)"]).toRun(); + + await promise; + listener.stop(true); + }); + + it("fetch.preconnect validates the URL", async () => { + expect(() => fetch.preconnect("http://localhost:0")).toThrow(); + expect(() => fetch.preconnect("")).toThrow(); + expect(() => fetch.preconnect(" ")).toThrow(); + expect(() => fetch.preconnect("unix:///tmp/foo")).toThrow(); + expect(() => fetch.preconnect("http://:0")).toThrow(); + }); +}); diff --git a/test/js/web/fetch/fetch-redirect.test.ts b/test/js/web/fetch/fetch-redirect.test.ts new file mode 100644 index 0000000000..f02fb5fc5c --- /dev/null +++ b/test/js/web/fetch/fetch-redirect.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from "bun:test"; + +// https://github.com/oven-sh/bun/issues/12701 +it("fetch() preserves body on redirect", async () => { + using server = Bun.serve({ + port: 0, + + async fetch(req) { + const { pathname } = new URL(req.url); + if (pathname === "/redirect") { + return new Response(null, { + status: 308, + headers: { + Location: "/redirect2", + }, + }); + } + if (pathname === "/redirect2") { + return new Response(req.body, { status: 200 }); + } + return new Response("you shouldnt see this?", { status: 200 }); + }, + }); + + const res = await fetch(new URL("/redirect", server.url), { + method: "POST", + body: "hello", + }); + + expect(res.status).toBe(200); + expect(await res.text()).toBe("hello"); +}); From 696f209ec1df76c995d8abd256dc33a4166a3d40 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 03:49:32 -0700 Subject: [PATCH 031/123] Update fetch.md --- docs/api/fetch.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/api/fetch.md b/docs/api/fetch.md index 0e50972a8e..bb5bbb48f7 100644 --- a/docs/api/fetch.md +++ b/docs/api/fetch.md @@ -220,6 +220,23 @@ This feature is not implemented on Windows yet. If you're interested in using th Bun automatically reuses connections to the same host. This is known as connection pooling. This can significantly reduce the time it takes to establish a connection. You don't need to do anything to enable this; it's automatic. +#### Simultaneous connection limit + +By default, Bun limits the maximum number of simultaneous `fetch` requests to 256. We do this for several reasons: + +- It improves overall system stability. Operating systems have an upper limit on the number of simultaneous open TCP sockets, usually in the low thousands. Nearing this limit causes your entire computer to behave strangely. Applications hang and crash. +- It encourages HTTP Keep-Alive connection reuse. For short-lived HTTP requests, the slowest step is often the initial connection setup. Reusing connections can save a lot of time. + +Browsers limit the number of simultaneous connections to a host to 6 usually. We don't limit by host, we just have a global limit. + +You can increase the maximum number of simultaneous connections via the BUN_CONFIG_MAX_HTTP_REQUESTS environment variable: + +```sh +$ BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun ./my-script.ts +``` + +The max value for this limit is currently set to 65,336. The maximum port number is 65,535, so it's quite difficult for any one computer to exceed this limit. + ### Response buffering Bun goes to great lengths to optimize the performance of reading the response body. The fastest way to read the response body is to use one of these methods: From 599d27d93e4b3e93af9de552f459cf994681d74d Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 03:50:22 -0700 Subject: [PATCH 032/123] Update fetch.md --- docs/api/fetch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/fetch.md b/docs/api/fetch.md index bb5bbb48f7..055c8b2022 100644 --- a/docs/api/fetch.md +++ b/docs/api/fetch.md @@ -194,7 +194,7 @@ dns.prefetch("bun.sh", 443); ### Preconnect to a host -To preconnect to a host, you can use the `http.prefetch` API. This API is useful when you know you'll need to connect to a host soon and want to start the initial DNS lookup, TCP socket connection, and TLS handshake early. +To preconnect to a host, you can use the `fetch.preconnect` API. This API is useful when you know you'll need to connect to a host soon and want to start the initial DNS lookup, TCP socket connection, and TLS handshake early. ```ts import { fetch } from "bun"; From 2f0020f00f3369f251091bb63cc493e785e5430e Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 03:52:33 -0700 Subject: [PATCH 033/123] Update fetch.md --- docs/api/fetch.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/fetch.md b/docs/api/fetch.md index 055c8b2022..32afdb96b7 100644 --- a/docs/api/fetch.md +++ b/docs/api/fetch.md @@ -227,9 +227,9 @@ By default, Bun limits the maximum number of simultaneous `fetch` requests to 25 - It improves overall system stability. Operating systems have an upper limit on the number of simultaneous open TCP sockets, usually in the low thousands. Nearing this limit causes your entire computer to behave strangely. Applications hang and crash. - It encourages HTTP Keep-Alive connection reuse. For short-lived HTTP requests, the slowest step is often the initial connection setup. Reusing connections can save a lot of time. -Browsers limit the number of simultaneous connections to a host to 6 usually. We don't limit by host, we just have a global limit. +Browsers limit the number of simultaneous connections to a host to 6 usually. We don't limit by host, it's a global limit. When the limit is exceeded, the requests are queued and sent as soon as the next request ends. -You can increase the maximum number of simultaneous connections via the BUN_CONFIG_MAX_HTTP_REQUESTS environment variable: +You can increase the maximum number of simultaneous connections via the `BUN_CONFIG_MAX_HTTP_REQUESTS` environment variable: ```sh $ BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun ./my-script.ts From 9daa7ea555cecef21d8c4df09586960fa485ed6f Mon Sep 17 00:00:00 2001 From: huseeiin <122984423+huseeiin@users.noreply.github.com> Date: Mon, 22 Jul 2024 06:55:14 -0400 Subject: [PATCH 034/123] Update bun.d.ts (#12719) --- packages/bun-types/bun.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 9a91cc8e46..8307d88ec1 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -1455,7 +1455,7 @@ declare module "bun" { * ```js * const {imports, exports} = transpiler.scan(` * import {foo} from "baz"; - * const hello = "hi!"; + * export const hello = "hi!"; * `); * * console.log(imports); // ["baz"] From ca44df7c888fb32b9fcfd219fd2042561377ffa5 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 03:57:18 -0700 Subject: [PATCH 035/123] Update fetch.md --- docs/api/fetch.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api/fetch.md b/docs/api/fetch.md index 32afdb96b7..cd56832f24 100644 --- a/docs/api/fetch.md +++ b/docs/api/fetch.md @@ -192,6 +192,12 @@ import { dns } from "bun"; dns.prefetch("bun.sh", 443); ``` +#### DNS caching + +By default, Bun caches and deduplicates DNS queries in-memory for up to 30 seconds. You can see the cache stats by calling `dns.getCacheStats()`: + +To learn more about DNS caching in Bun, see the [DNS caching](/docs/api/dns) documentation. + ### Preconnect to a host To preconnect to a host, you can use the `fetch.preconnect` API. This API is useful when you know you'll need to connect to a host soon and want to start the initial DNS lookup, TCP socket connection, and TLS handshake early. From ff17b427c86fe378155de30fbadd64aafb0fb92e Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 04:02:48 -0700 Subject: [PATCH 036/123] Update fetch.md --- docs/api/fetch.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/api/fetch.md b/docs/api/fetch.md index cd56832f24..86b9f0ee6e 100644 --- a/docs/api/fetch.md +++ b/docs/api/fetch.md @@ -135,6 +135,51 @@ const response = await fetch("http://example.com", { controller.abort(); ``` +### Unix domain sockets + +To fetch a URL using a Unix domain socket, use the `unix: string` option: + +```ts +const response = await fetch("https://hostname/a/path", { + unix: "/var/run/path/to/unix.sock", + method: "POST", + body: JSON.stringify({ message: "Hello from Bun!" }), + headers: { + "Content-Type": "application/json", + }, +}); +``` + +### TLS + +To use a client certificate, use the `tls` option: + +```ts +await fetch("https://example.com", { + tls: { + key: Bun.file("/path/to/key.pem"), + cert: Bun.file("/path/to/cert.pem"), + // ca: [Bun.file("/path/to/ca.pem")], + }, +}); +``` + +#### Custom TLS Validation + +To customize the TLS validation, use the `checkServerIdentity` option in `tls` + +```ts +await fetch("https://example.com", { + tls: { + checkServerIdentity: (hostname, peerCertificate) => { + // Return an error if the certificate is invalid + }, + }, +}); +``` + +This is similar to how it works in Node's `net` module. + ## Debugging To help with debugging, you can pass `verbose: true` to `fetch`: From 63fab9a82bbfc37ebb45e7c3b97edfc32d3d001f Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 04:06:00 -0700 Subject: [PATCH 037/123] Update fetch.md --- docs/api/fetch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/fetch.md b/docs/api/fetch.md index 86b9f0ee6e..e02f064e18 100644 --- a/docs/api/fetch.md +++ b/docs/api/fetch.md @@ -278,7 +278,7 @@ By default, Bun limits the maximum number of simultaneous `fetch` requests to 25 - It improves overall system stability. Operating systems have an upper limit on the number of simultaneous open TCP sockets, usually in the low thousands. Nearing this limit causes your entire computer to behave strangely. Applications hang and crash. - It encourages HTTP Keep-Alive connection reuse. For short-lived HTTP requests, the slowest step is often the initial connection setup. Reusing connections can save a lot of time. -Browsers limit the number of simultaneous connections to a host to 6 usually. We don't limit by host, it's a global limit. When the limit is exceeded, the requests are queued and sent as soon as the next request ends. +When the limit is exceeded, the requests are queued and sent as soon as the next request ends. You can increase the maximum number of simultaneous connections via the `BUN_CONFIG_MAX_HTTP_REQUESTS` environment variable: From 732ed2b7dfaf9b32b45a63da404e9cde30d4263a Mon Sep 17 00:00:00 2001 From: Dariush Alipour Date: Mon, 22 Jul 2024 22:25:42 +0200 Subject: [PATCH 038/123] Fix: test coverage node_modules exclusion in Windows (#12691) --- src/cli/test_command.zig | 2 +- test/cli/test/coverage.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index ebf9ef5b06..86e04c4ddb 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -598,7 +598,7 @@ const Scanner = struct { }; // always ignore node_modules. - if (strings.contains(slice, "/" ++ "node_modules" ++ "/")) { + if (strings.contains(slice, "/node_modules/") or strings.contains(slice, "\\node_modules\\")) { return false; } diff --git a/test/cli/test/coverage.test.ts b/test/cli/test/coverage.test.ts index d5bb7bdb7a..c598cc7269 100644 --- a/test/cli/test/coverage.test.ts +++ b/test/cli/test/coverage.test.ts @@ -54,3 +54,26 @@ export class Y { expect(result.signalCode).toBeUndefined(); expect(readFileSync(path.join(dir, "coverage", "lcov.info"), "utf-8")).toMatchSnapshot(); }); + +test("coverage excludes node_modules directory", () => { + const dir = tempDirWithFiles("cov", { + "node_modules/pi/index.js": ` + export const pi = 3.14; + `, + "demo.test.ts": ` + import { pi } from 'pi'; + console.log(pi); + `, + }); + const result = Bun.spawnSync([bunExe(), "test", "--coverage"], { + cwd: dir, + env: { + ...bunEnv, + }, + stdio: [null, null, "pipe"], + }); + expect(result.stderr.toString("utf-8")).toContain("demo.test.ts"); + expect(result.stderr.toString("utf-8")).not.toContain("node_modules"); + expect(result.exitCode).toBe(0); + expect(result.signalCode).toBeUndefined(); +}); From a4759eb14780dcc449209a1caa680849c0732d89 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 14:50:07 -0700 Subject: [PATCH 039/123] Bump minimum macOS build to 13.0 --- .github/workflows/build-darwin.yml | 2 +- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/create-release-build.yml | 6 +++--- .github/workflows/run-lint-cpp.yml | 2 +- CMakeLists.txt | 2 +- Makefile | 2 +- scripts/env.sh | 2 +- src/bun.js/RuntimeTranspilerCache.zig | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-darwin.yml b/.github/workflows/build-darwin.yml index 9dc02b91f7..e16be8814f 100644 --- a/.github/workflows/build-darwin.yml +++ b/.github/workflows/build-darwin.yml @@ -9,7 +9,7 @@ on: inputs: runs-on: type: string - default: macos-12-large + default: macos-13-large tag: type: string required: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7acf37682..203190c58f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,7 +84,7 @@ jobs: uses: ./.github/workflows/build-darwin.yml secrets: inherit with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-12-large' || 'macos-12' }} + runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-13-large' || 'macos-13' }} tag: darwin-x64 arch: x64 cpu: haswell @@ -95,7 +95,7 @@ jobs: uses: ./.github/workflows/build-darwin.yml secrets: inherit with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-12-large' || 'macos-12' }} + runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-13-large' || 'macos-13' }} tag: darwin-x64-baseline arch: x64 cpu: nehalem @@ -106,7 +106,7 @@ jobs: uses: ./.github/workflows/build-darwin.yml secrets: inherit with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'namespace-profile-bun-ci-darwin-aarch64' || 'macos-12' }} + runs-on: ${{ github.repository_owner == 'oven-sh' && 'namespace-profile-bun-ci-darwin-aarch64' || 'macos-13' }} tag: darwin-aarch64 arch: aarch64 cpu: native @@ -175,7 +175,7 @@ jobs: with: run-id: ${{ inputs.run-id }} pr-number: ${{ github.event.number }} - runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-12-large' || 'macos-12' }} + runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-13-large' || 'macos-13' }} tag: darwin-x64 darwin-x64-baseline-test: if: ${{ inputs.run-id || github.event_name == 'pull_request' }} @@ -186,7 +186,7 @@ jobs: with: run-id: ${{ inputs.run-id }} pr-number: ${{ github.event.number }} - runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-12-large' || 'macos-12' }} + runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-13-large' || 'macos-13' }} tag: darwin-x64-baseline darwin-aarch64-test: if: ${{ inputs.run-id || github.event_name == 'pull_request' }} @@ -197,7 +197,7 @@ jobs: with: run-id: ${{ inputs.run-id }} pr-number: ${{ github.event.number }} - runs-on: ${{ github.repository_owner == 'oven-sh' && 'namespace-profile-bun-ci-darwin-aarch64' || 'macos-12' }} + runs-on: ${{ github.repository_owner == 'oven-sh' && 'namespace-profile-bun-ci-darwin-aarch64' || 'macos-13' }} tag: darwin-aarch64 windows-x64-test: if: ${{ inputs.run-id || github.event_name == 'pull_request' }} diff --git a/.github/workflows/create-release-build.yml b/.github/workflows/create-release-build.yml index e9aa5796fe..42adea0585 100644 --- a/.github/workflows/create-release-build.yml +++ b/.github/workflows/create-release-build.yml @@ -90,7 +90,7 @@ jobs: uses: ./.github/workflows/build-darwin.yml secrets: inherit with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-12-large' || 'macos-12' }} + runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-13-large' || 'macos-13' }} tag: darwin-x64 arch: x64 cpu: haswell @@ -100,7 +100,7 @@ jobs: uses: ./.github/workflows/build-darwin.yml secrets: inherit with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-12-large' || 'macos-12' }} + runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-13-large' || 'macos-13' }} tag: darwin-x64-baseline arch: x64 cpu: nehalem @@ -110,7 +110,7 @@ jobs: uses: ./.github/workflows/build-darwin.yml secrets: inherit with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'namespace-profile-bun-ci-darwin-aarch64' || 'macos-12' }} + runs-on: ${{ github.repository_owner == 'oven-sh' && 'namespace-profile-bun-ci-darwin-aarch64' || 'macos-13' }} tag: darwin-aarch64 arch: aarch64 cpu: native diff --git a/.github/workflows/run-lint-cpp.yml b/.github/workflows/run-lint-cpp.yml index 12abea144b..7aae7eaacb 100644 --- a/.github/workflows/run-lint-cpp.yml +++ b/.github/workflows/run-lint-cpp.yml @@ -17,7 +17,7 @@ on: jobs: lint-cpp: name: Lint C++ - runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-13-xlarge' || 'macos-12' }} + runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-13-xlarge' || 'macos-13' }} steps: - name: Checkout uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 70b8f52b67..18981fd5cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ endif() # --- MacOS SDK --- if(APPLE AND DEFINED ENV{CI}) - set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0") + set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0") endif() if(APPLE AND NOT CMAKE_OSX_DEPLOYMENT_TARGET) diff --git a/Makefile b/Makefile index 5f941249a9..c997d7da7b 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ ifeq ($(ARCH_NAME_RAW),arm64) ARCH_NAME = aarch64 DOCKER_BUILDARCH = arm64 BREW_PREFIX_PATH = /opt/homebrew -DEFAULT_MIN_MACOS_VERSION = 11.0 +DEFAULT_MIN_MACOS_VERSION = 13.0 MARCH_NATIVE = -mtune=$(CPU_TARGET) ifeq ($(OS_NAME),linux) MARCH_NATIVE = -march=armv8-a+crc -mtune=ampere1 diff --git a/scripts/env.sh b/scripts/env.sh index e86241c515..658f16ba8b 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -90,7 +90,7 @@ if [[ $(uname -s) == 'Linux' ]]; then fi if [[ $(uname -s) == 'Darwin' ]]; then - export CMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET:-12.0} + export CMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET:-13.0} CMAKE_FLAGS+=(-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}) export CFLAGS="$CFLAGS -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET} -D__DARWIN_NON_CANCELABLE=1 " diff --git a/src/bun.js/RuntimeTranspilerCache.zig b/src/bun.js/RuntimeTranspilerCache.zig index 9b57d24fb6..551659c6e0 100644 --- a/src/bun.js/RuntimeTranspilerCache.zig +++ b/src/bun.js/RuntimeTranspilerCache.zig @@ -201,7 +201,7 @@ pub const RuntimeTranspilerCache = struct { try metadata.encode(metadata_stream.writer()); - if (comptime bun.Environment.allow_assert) { + if (comptime bun.Environment.isDebug) { var metadata_stream2 = std.io.fixedBufferStream(metadata_buf[0..Metadata.size]); var metadata2 = Metadata{}; metadata2.decode(metadata_stream2.reader()) catch |err| bun.Output.panic("Metadata did not roundtrip encode -> decode successfully: {s}", .{@errorName(err)}); From a6b5543bd831d8c8c5e11d297b0adad8d5aa3083 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 15:41:41 -0700 Subject: [PATCH 040/123] Don't set fuse-ld=lld in boringssl script --- scripts/build-boringssl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-boringssl.sh b/scripts/build-boringssl.sh index 26a84d1398..34749705e1 100755 --- a/scripts/build-boringssl.sh +++ b/scripts/build-boringssl.sh @@ -6,7 +6,7 @@ cd $BUN_DEPS_DIR/boringssl mkdir -p build cd build -cmake "${CMAKE_FLAGS[@]}" -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" -GNinja .. +cmake "${CMAKE_FLAGS[@]}" -GNinja .. ninja libcrypto.a libssl.a libdecrepit.a cp **/libcrypto.a $BUN_DEPS_OUT_DIR/libcrypto.a From 6e9b592c56567ab119012da399440e26f1ee25e5 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 20:44:29 -0700 Subject: [PATCH 041/123] try using LLVM 18 on macOS (#12727) Co-authored-by: Jarred-Sumner --- .github/workflows/build-darwin.yml | 43 +++--------------------- .gitignore | 1 + CMakeLists.txt | 12 +++++-- scripts/build-zlib.sh | 11 +++--- scripts/env.sh | 47 +++++++++++++++++++++----- scripts/setup.sh | 54 ++++++++++++++++++++++++------ src/bun.js/bindings/bindings.cpp | 2 +- src/generated_versions_list.zig | 2 +- 8 files changed, 104 insertions(+), 68 deletions(-) diff --git a/.github/workflows/build-darwin.yml b/.github/workflows/build-darwin.yml index e16be8814f..b2bf1f12da 100644 --- a/.github/workflows/build-darwin.yml +++ b/.github/workflows/build-darwin.yml @@ -27,11 +27,12 @@ on: type: boolean env: - LLVM_VERSION: 16 + LLVM_VERSION: 18 BUN_VERSION: 1.1.8 LC_CTYPE: "en_US.UTF-8" LC_ALL: "en_US.UTF-8" - BUN_ENABLE_LTO: "1" + # LTO is disabled because we cannot use lld on macOS currently + BUN_ENABLE_LTO: "0" jobs: build-submodules: @@ -54,16 +55,7 @@ jobs: cat $(echo scripts/build*.sh scripts/all-dependencies.sh | tr " " "\n" | sort) } echo "hash=$(print_versions | shasum)" >> $GITHUB_OUTPUT - - if: ${{ !inputs.no-cache }} - name: Restore Cache - id: cache - uses: actions/cache/restore@v4 - with: - path: ${{ runner.temp }}/bun-deps - key: bun-${{ inputs.tag }}-deps-${{ steps.hash.outputs.hash }} - # TODO: Figure out how to cache homebrew dependencies - - if: ${{ inputs.no-cache || !steps.cache.outputs.cache-hit }} - name: Install Dependencies + - name: Install Dependencies env: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 HOMEBREW_NO_AUTO_UPDATE: 1 @@ -87,24 +79,16 @@ jobs: echo "$(brew --prefix coreutils)/libexec/gnubin" >> $GITHUB_PATH echo "$(brew --prefix llvm@$LLVM_VERSION)/bin" >> $GITHUB_PATH brew link --overwrite llvm@$LLVM_VERSION - - if: ${{ inputs.no-cache || !steps.cache.outputs.cache-hit }} - name: Clone Submodules + - name: Clone Submodules run: | ./scripts/update-submodules.sh - name: Build Submodules - if: ${{ inputs.no-cache || !steps.cache.outputs.cache-hit }} env: CPU_TARGET: ${{ inputs.cpu }} BUN_DEPS_OUT_DIR: ${{ runner.temp }}/bun-deps run: | mkdir -p $BUN_DEPS_OUT_DIR ./scripts/all-dependencies.sh - - name: Save Cache - if: ${{ inputs.no-cache || !steps.cache.outputs.cache-hit }} - uses: actions/cache/save@v4 - with: - path: ${{ runner.temp }}/bun-deps - key: ${{ steps.cache.outputs.cache-primary-key }} - name: Upload bun-${{ inputs.tag }}-deps uses: actions/upload-artifact@v4 with: @@ -148,14 +132,6 @@ jobs: uses: ./.github/actions/setup-bun with: bun-version: ${{ env.BUN_VERSION }} - - if: ${{ !inputs.no-cache }} - name: Restore Cache - uses: actions/cache@v4 - with: - path: ${{ runner.temp }}/ccache - key: bun-${{ inputs.tag }}-cpp-${{ hashFiles('Dockerfile', 'Makefile', 'CMakeLists.txt', 'build.zig', 'scripts/**', 'src/**', 'packages/bun-usockets/src/**', 'packages/bun-uws/src/**') }} - restore-keys: | - bun-${{ inputs.tag }}-cpp- - name: Compile env: CPU_TARGET: ${{ inputs.cpu }} @@ -245,18 +221,9 @@ jobs: with: name: bun-${{ inputs.tag }}-zig path: ${{ runner.temp }}/release - - if: ${{ !inputs.no-cache }} - name: Restore Cache - uses: actions/cache@v4 - with: - path: ${{ runner.temp }}/ccache - key: bun-${{ inputs.tag }}-cpp-${{ hashFiles('Dockerfile', 'Makefile', 'CMakeLists.txt', 'build.zig', 'scripts/**', 'src/**', 'packages/bun-usockets/src/**', 'packages/bun-uws/src/**') }} - restore-keys: | - bun-${{ inputs.tag }}-cpp- - name: Link env: CPU_TARGET: ${{ inputs.cpu }} - CCACHE_DIR: ${{ runner.temp }}/ccache run: | SRC_DIR=$PWD mkdir ${{ runner.temp }}/link-build diff --git a/.gitignore b/.gitignore index 849c532b2b..a0c4419668 100644 --- a/.gitignore +++ b/.gitignore @@ -145,3 +145,4 @@ zig-cache zig-out test/node.js/upstream .zig-cache +scripts/env.local diff --git a/CMakeLists.txt b/CMakeLists.txt index 18981fd5cd..e18c6d831a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_policy(SET CMP0067 NEW) set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) set(Bun_VERSION "1.1.21") -set(WEBKIT_TAG 2be773eeea48c03a4fa92c170934eb2220666809) +set(WEBKIT_TAG 49907bff8781719bc2ded068b0c934f6d0074d1e) set(BUN_WORKDIR "${CMAKE_CURRENT_BINARY_DIR}") message(STATUS "Configuring Bun ${Bun_VERSION} in ${BUN_WORKDIR}") @@ -116,7 +116,7 @@ endif() # we do some extra work afterwards to double-check, and we will rerun BUN_FIND_LLVM if the compiler did not match. # # If the user passes -DLLVM_PREFIX, most of this logic is skipped, but we still warn if invalid. -if(WIN32) +if(WIN32 OR APPLE) set(LLVM_VERSION 18) else() set(LLVM_VERSION 16) @@ -154,11 +154,12 @@ macro(BUN_FIND_LLVM) PATHS ENV PATH ${PLATFORM_LLVM_SEARCH_PATHS} DOC "Path to LLVM ${LLVM_VERSION}'s llvm-strip binary" ) + find_program( STRIP NAMES strip PATHS ENV PATH ${PLATFORM_LLVM_SEARCH_PATHS} - DOC "Path to LLVM ${LLVM_VERSION}'s llvm-strip binary" + DOC "Path to strip binary" ) find_program( DSYMUTIL @@ -330,6 +331,11 @@ option(USE_STATIC_LIBATOMIC "Statically link libatomic, requires the presence of option(USE_LTO "Enable Link-Time Optimization" ${DEFAULT_LTO}) +if(APPLE AND USE_LTO) + set(USE_LTO OFF) + message(WARNING "Link-Time Optimization is not supported on macOS because it requires -fuse-ld=lld and lld causes many segfaults on macOS (likely related to stack size)") +endif() + if(WIN32 AND USE_LTO) set(CMAKE_LINKER_TYPE LLD) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF) diff --git a/scripts/build-zlib.sh b/scripts/build-zlib.sh index daedca9fb3..2377c7f198 100755 --- a/scripts/build-zlib.sh +++ b/scripts/build-zlib.sh @@ -4,10 +4,9 @@ source $(dirname -- "${BASH_SOURCE[0]}")/env.sh mkdir -p $BUN_DEPS_OUT_DIR cd $BUN_DEPS_DIR/zlib -export CFLAGS="-O3" -if [[ $(uname -s) == 'Darwin' ]]; then - export CFLAGS="$CFLAGS -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}" -fi -CFLAGS="${CFLAGS}" ./configure --static -make -j${CPUS} libz.a +rm -rf build +mkdir build +cd build +cmake $CMAKE_FLAGS -G Ninja -DCMAKE_BUILD_TYPE=Release .. +ninja cp ./libz.a $BUN_DEPS_OUT_DIR/libz.a diff --git a/scripts/env.sh b/scripts/env.sh index 658f16ba8b..c60d9edfe8 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -7,6 +7,12 @@ if [[ "${CI:-}" == "1" || "${CI:-}" == "true" ]]; then fi fi +if [[ $(uname -s) == 'Darwin' ]]; then + export LLVM_VERSION=18 +else + export LLVM_VERSION=16 +fi + # this is the environment script for building bun's dependencies # it sets c compiler and flags export SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) @@ -18,26 +24,42 @@ export BUN_DEPS_OUT_DIR=${BUN_DEPS_OUT_DIR:-$BUN_BASE_DIR/build/bun-deps} export LC_CTYPE="en_US.UTF-8" export LC_ALL="en_US.UTF-8" +if [[ "$CI" != "1" && "$CI" != "true" ]]; then + if [ -f $SCRIPT_DIR/env.local ]; then + echo "Sourcing $SCRIPT_DIR/env.local" + source $SCRIPT_DIR/env.local + fi +elif [[ $(uname -s) == 'Darwin' ]]; then + export CXX="$(brew --prefix llvm)@$LLVM_VERSION/bin/clang++" + export CC="$(brew --prefix llvm)@$LLVM_VERSION/bin/clang" + export AR="$(brew --prefix llvm)@$LLVM_VERSION/bin/llvm-ar" + export RANLIB="$(brew --prefix llvm)@$LLVM_VERSION/bin/llvm-ranlib" + export LIBTOOL="$(brew --prefix llvm)@$LLVM_VERSION/bin/llvm-libtool-darwin" + export PATH="$(brew --prefix llvm)@$LLVM_VERSION/bin:$PATH" + ln -sf $LIBTOOL "$(brew --prefix llvm)@$LLVM_VERSION/bin/libtool" || true +fi + # this compiler detection could be better -export CC=${CC:-$(which clang-16 || which clang || which cc)} -export CXX=${CXX:-$(which clang++-16 || which clang++ || which c++)} +export CC=${CC:-$(which clang-$LLVM_VERSION || which clang || which cc)} +export CXX=${CXX:-$(which clang++-$LLVM_VERSION || which clang++ || which c++)} export AR=${AR:-$(which llvm-ar || which ar)} export CPUS=${CPUS:-$(nproc || sysctl -n hw.ncpu || echo 1)} -export RANLIB=${RANLIB:-$(which llvm-ranlib-16 || which llvm-ranlib || which ranlib)} +export RANLIB=${RANLIB:-$(which llvm-ranlib-$LLVM_VERSION || which llvm-ranlib || which ranlib)} # on Linux, force using lld as the linker if [[ $(uname -s) == 'Linux' ]]; then - export LD=${LD:-$(which ld.lld-16 || which ld.lld || which ld)} + export LD=${LD:-$(which ld.lld-$LLVM_VERSION || which ld.lld || which ld)} export LDFLAGS="${LDFLAGS} -fuse-ld=lld " fi export CMAKE_CXX_COMPILER=${CXX} export CMAKE_C_COMPILER=${CC} -export CFLAGS='-O3 -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables -faddrsig ' -export CXXFLAGS='-O3 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables -faddrsig -fno-c++-static-destructors ' +export CFLAGS='-O3 -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables ' +export CXXFLAGS='-O3 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-asynchronous-unwind-tables -fno-unwind-tables -fno-c++-static-destructors ' # Add flags for LTO +# We cannot enable LTO on macOS for dependencies because it requires -fuse-ld=lld and lld causes many segfaults on macOS (likely related to stack size) if [ "$BUN_ENABLE_LTO" == "1" ]; then export CFLAGS="$CFLAGS -flto=full " export CXXFLAGS="$CXXFLAGS -flto=full -fwhole-program-vtables -fforce-emit-vtables " @@ -45,16 +67,23 @@ if [ "$BUN_ENABLE_LTO" == "1" ]; then fi if [[ $(uname -s) == 'Linux' ]]; then - export CFLAGS="$CFLAGS -ffunction-sections -fdata-sections" - export CXXFLAGS="$CXXFLAGS -ffunction-sections -fdata-sections" + export CFLAGS="$CFLAGS -ffunction-sections -fdata-sections -faddrsig " + export CXXFLAGS="$CXXFLAGS -ffunction-sections -fdata-sections -faddrsig " export LDFLAGS="${LDFLAGS} -Wl,-z,norelro" fi +# Clang 18 on macOS needs to have -fno-define-target-os-macros to fix a zlib build issue +# https://gitlab.kitware.com/cmake/cmake/-/issues/25755 +if [[ $(uname -s) == 'Darwin' && $LLVM_VERSION == '18' ]]; then + export CFLAGS="$CFLAGS -fno-define-target-os-macros " + export CXXFLAGS="$CXXFLAGS -fno-define-target-os-macros " +fi + # libarchive needs position-independent executables to compile successfully if [ -n "$FORCE_PIC" ]; then export CFLAGS="$CFLAGS -fPIC " export CXXFLAGS="$CXXFLAGS -fPIC " -else +elif [[ $(uname -s) == 'Linux' ]]; then export CFLAGS="$CFLAGS -fno-pie -fno-pic " export CXXFLAGS="$CXXFLAGS -fno-pie -fno-pic " fi diff --git a/scripts/setup.sh b/scripts/setup.sh index fb57795fc0..bd58cdabed 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -15,12 +15,35 @@ fail() { printf "${C_RED}setup error${C_RESET}: %s\n" "$@" } -LLVM_VERSION=16 +if [[ $(uname -s) == 'Darwin' ]]; then + export LLVM_VERSION=18 -# this compiler detection could be better -# it is copy pasted from ./env.sh -CC=${CC:-$(which clang-16 || which clang || which cc)} -CXX=${CXX:-$(which clang++-16 || which clang++ || which c++)} + # Use from brew --prefix if available + if has_exec brew; then + export PKG_CONFIG_PATH=$(brew --prefix)/lib/pkgconfig:$PKG_CONFIG_PATH + + # if llvm@18/bin/clang exists, use it + if [ -x "$(brew --prefix)/opt/llvm@$LLVM_VERSION/bin/clang" ]; then + export PATH=$(brew --prefix)/opt/llvm@$LLVM_VERSION/bin:$PATH + export CC=$(brew --prefix)/opt/llvm@$LLVM_VERSION/bin/clang + export CXX=$(brew --prefix)/opt/llvm@$LLVM_VERSION/bin/clang++ + export AR=$(brew --prefix)/opt/llvm@$LLVM_VERSION/bin/llvm-ar + else + export CC=$(which clang-$LLVM_VERSION || which clang || which cc) + export CXX=$(which clang++-$LLVM_VERSION || which clang++ || which c++) + export AR=$(which llvm-ar-$LLVM_VERSION || which llvm-ar || which ar) + fi + fi + + test -n "$CC" || fail "missing LLVM $LLVM_VERSION (could not find clang)" + test -n "$CXX" || fail "missing LLVM $LLVM_VERSION (could not find clang++)" +else + export LLVM_VERSION=16 + + export CC=$(which clang-$LLVM_VERSION || which clang || which cc) + export CXX=$(which clang++-$LLVM_VERSION || which clang++ || which c++) + export AR=$(which llvm-ar-$LLVM_VERSION || which llvm-ar || which ar) +fi test -n "$CC" || fail "missing LLVM $LLVM_VERSION (could not find clang)" test -n "$CXX" || fail "missing LLVM $LLVM_VERSION (could not find clang++)" @@ -36,9 +59,9 @@ has_exec "bun" || fail "you need an existing copy of 'bun' in your path to build has_exec "cmake" || fail "'cmake' is missing" has_exec "ninja" || fail "'ninja' is missing" $( - has_exec "rustc" \ - && (test $(cargo --version | awk '{print $2}' | cut -d. -f2) -gt 57) \ - && has_exec "cargo" + has_exec "rustc" && + (test $(cargo --version | awk '{print $2}' | cut -d. -f2) -gt 57) && + has_exec "cargo" ) || fail "Rust and Cargo version must be installed (minimum version 1.57)" has_exec "go" || fail "'go' is missing" @@ -59,6 +82,15 @@ printf "C Compiler for dependencies: ${CC}\n" printf "C++ Compiler for dependencies: ${CXX}\n" cd "$(dirname "${BASH_SOURCE[0]}")" + +rm -rf env.local +echo "# Environment variables as of last setup.sh run at $(date)" >env.local +echo "export CC=\"${CC}\"" >>env.local +echo "export CXX\"=${CXX}\"" >>env.local +echo "export AR=\"${AR}\"" >>env.local +echo "export PATH=\"${PATH}\"" >>env.local +echo "Saved environment variables to $(pwd)/env.local" + bash ./update-submodules.sh bash ./all-dependencies.sh @@ -67,7 +99,9 @@ cd ../ # Install bun dependencies bun i # Install test dependencies -cd test; bun i; cd .. +cd test +bun i +cd .. # TODO(@paperdave): do not use the Makefile please has_exec "make" || fail "'make' is missing" @@ -81,7 +115,7 @@ cmake -B build -S . \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_C_COMPILER="$CC" \ -DCMAKE_CXX_COMPILER="$CXX" \ - -UZIG_COMPILER "$*" \ + -UZIG_COMPILER "$*" ninja -C build diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 8e6104573f..5a1462e869 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -5560,7 +5560,7 @@ extern "C" EncodedJSValue JSC__JSValue__dateInstanceFromNullTerminatedString(JSC // this is largely copied from dateProtoFuncToISOString extern "C" int JSC__JSValue__toISOString(JSC::JSGlobalObject* globalObject, EncodedJSValue dateValue, char* buf) { - char buffer[28]; + char buffer[29]; JSC::DateInstance* thisDateObj = JSC::jsDynamicCast(JSC::JSValue::decode(dateValue)); if (!thisDateObj) return -1; diff --git a/src/generated_versions_list.zig b/src/generated_versions_list.zig index 3e17ae8ad3..82773c4c8e 100644 --- a/src/generated_versions_list.zig +++ b/src/generated_versions_list.zig @@ -4,7 +4,7 @@ pub const boringssl = "29a2cd359458c9384694b75456026e4b57e3e567"; pub const libarchive = "898dc8319355b7e985f68a9819f182aaed61b53a"; pub const mimalloc = "4c283af60cdae205df5a872530c77e2a6a307d43"; pub const picohttpparser = "066d2b1e9ab820703db0837a7255d92d30f0c9f5"; -pub const webkit = "2be773eeea48c03a4fa92c170934eb2220666809"; +pub const webkit = "49907bff8781719bc2ded068b0c934f6d0074d1e"; pub const zig = @import("std").fmt.comptimePrint("{}", .{@import("builtin").zig_version}); pub const zlib = "886098f3f339617b4243b286f5ed364b9989e245"; pub const tinycc = "ab631362d839333660a265d3084d8ff060b96753"; From 3ef84816a6d0d74f4694fd09d5ba7444e657105e Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 20:47:53 -0700 Subject: [PATCH 042/123] Update WebKit --- src/bun.js/WebKit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/WebKit b/src/bun.js/WebKit index 615e8585f9..49018961cc 160000 --- a/src/bun.js/WebKit +++ b/src/bun.js/WebKit @@ -1 +1 @@ -Subproject commit 615e8585f96aa718b0f5158210259b83fe8440ea +Subproject commit 49018961cccf8cdcb3fd98e75a8a2226a295ed3c From 1a702dfdc799d25d881d9de844f9d7531296e8b5 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 22 Jul 2024 22:16:27 -0700 Subject: [PATCH 043/123] Add canary to cache key --- .github/workflows/build-zig.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-zig.yml b/.github/workflows/build-zig.yml index 097fe082e7..eaea6bc1ef 100644 --- a/.github/workflows/build-zig.yml +++ b/.github/workflows/build-zig.yml @@ -48,7 +48,7 @@ jobs: - name: Calculate Cache Key id: cache run: | - echo "key=${{ hashFiles('Dockerfile', 'Makefile', 'CMakeLists.txt', 'build.zig', 'scripts/**', 'src/**', 'packages/bun-usockets/src/**', 'packages/bun-uws/src/**') }}" >> $GITHUB_OUTPUT + echo "key=${{ hashFiles('Dockerfile', 'Makefile', 'CMakeLists.txt', 'build.zig', 'scripts/**', 'src/**', 'packages/bun-usockets/src/**', 'packages/bun-uws/src/**') }}-canary-${{inputs.canary}}" >> $GITHUB_OUTPUT - if: ${{ !inputs.no-cache }} name: Restore Cache uses: actions/cache@v4 From 4e5d759c37cc4631efbb28a6cbd8e97f6663fb42 Mon Sep 17 00:00:00 2001 From: Ivan Baksheev Date: Tue, 23 Jul 2024 14:33:18 +0700 Subject: [PATCH 044/123] fix(bundler): ignore external rules for entrypoint (#12736) --- src/resolver/resolver.zig | 17 ++++++----------- test/bundler/bundler_edgecase.test.ts | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 6d2c5221cc..74cbb8687b 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -481,13 +481,6 @@ pub fn ResolveWatcher(comptime Context: type, comptime onWatch: anytype) type { }; } -fn isExternalModuleLike(import_path: string) bool { - if (strings.startsWith(import_path, ".") or strings.startsWith(import_path, "/") or strings.startsWith(import_path, "..")) { - return false; - } - return true; -} - pub const Resolver = struct { const ThisResolver = @This(); opts: options.BundleOptions, @@ -632,7 +625,7 @@ pub const Resolver = struct { } pub fn isExternalPattern(r: *ThisResolver, import_path: string) bool { - if (r.opts.packages == .external and isExternalModuleLike(import_path)) { + if (r.opts.packages == .external and isPackagePath(import_path)) { return true; } for (r.opts.external.patterns) |pattern| { @@ -873,8 +866,10 @@ pub const Resolver = struct { } } - // Certain types of URLs default to being external for convenience - if (r.isExternalPattern(import_path) or + // Certain types of URLs default to being external for convenience, + // while these rules should not be applied to the entrypoint as it is never external (#12734) + if (kind != .entry_point and + (r.isExternalPattern(import_path) or // "fill: url(#filter);" (kind.isFromCSS() and strings.startsWith(import_path, "#")) or @@ -885,7 +880,7 @@ pub const Resolver = struct { strings.startsWith(import_path, "https://") or // "background: url(//example.com/images/image.png);" - strings.startsWith(import_path, "//")) + strings.startsWith(import_path, "//"))) { if (r.debug_logs) |*debug| { debug.addNote("Marking this path as implicitly external"); diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 6902bb7868..862f72484a 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -1347,6 +1347,27 @@ describe("bundler", () => { `, }, }); + itBundled("edgecase/EntrypointWithoutPrefixSlashOrDotIsNotConsideredExternal#12734", { + files: { + "/src/entry.ts": /* ts */ ` + import { helloWorld } from "./second.ts"; + console.log(helloWorld); + `, + "/src/second.ts": /* ts */ ` + export const helloWorld = "Hello World"; + `, + }, + root: "/src", + entryPointsRaw: ["src/entry.ts"], + packages: "external", + target: "bun", + run: { + file: "/src/entry.ts", + stdout: ` + Hello World + `, + }, + }); itBundled("edgecase/IntegerUnderflow#12547", { files: { "/entry.js": ` From 5a5f3d6b30a112ad2277ce4952540c5976c6a84c Mon Sep 17 00:00:00 2001 From: Ciro Spaciari Date: Tue, 23 Jul 2024 01:13:43 -0700 Subject: [PATCH 045/123] fix(http) timeout (#12728) Co-authored-by: cirospaciari Co-authored-by: Jarred Sumner --- misctools/fetch.zig | 1 - misctools/http_bench.zig | 7 -- src/bun.js/webcore/response.zig | 3 - src/cli/create_command.zig | 5 - src/cli/upgrade_command.zig | 4 - src/compile_target.zig | 3 - src/http.zig | 115 +++++++++++---------- src/install/install.zig | 4 +- test/js/web/fetch/fetch-preconnect.test.ts | 86 +++++++++------ 9 files changed, 117 insertions(+), 111 deletions(-) diff --git a/misctools/fetch.zig b/misctools/fetch.zig index 7c91b6d9ec..5d0b9f1e52 100644 --- a/misctools/fetch.zig +++ b/misctools/fetch.zig @@ -195,7 +195,6 @@ pub fn main() anyerror!void { args.headers_buf, response_body_string, args.body, - 0, HTTP.FetchRedirect.follow, ), }; diff --git a/misctools/http_bench.zig b/misctools/http_bench.zig index b736eab9d3..3cd147d2ea 100644 --- a/misctools/http_bench.zig +++ b/misctools/http_bench.zig @@ -31,7 +31,6 @@ const params = [_]clap.Param(clap.Help){ clap.parseParam("-b, --body HTTP request body as a string") catch unreachable, clap.parseParam("-f, --file File path to load as body") catch unreachable, clap.parseParam("-n, --count How many runs? Default 10") catch unreachable, - clap.parseParam("-t, --timeout Max duration per request") catch unreachable, clap.parseParam("-r, --retry Max retry count") catch unreachable, clap.parseParam("--no-gzip Disable gzip") catch unreachable, clap.parseParam("--no-deflate Disable deflate") catch unreachable, @@ -75,7 +74,6 @@ pub const Arguments = struct { body: string = "", turbo: bool = false, count: usize = 10, - timeout: usize = 0, repeat: usize = 0, concurrency: u16 = 32, @@ -165,10 +163,6 @@ pub const Arguments = struct { // .keep_alive = !args.flag("--no-keep-alive"), .concurrency = std.fmt.parseInt(u16, args.option("--max-concurrency") orelse "32", 10) catch 32, .turbo = args.flag("--turbo"), - .timeout = std.fmt.parseInt(usize, args.option("--timeout") orelse "0", 10) catch |err| { - Output.prettyErrorln("{s} parsing timeout", .{@errorName(err)}); - Global.exit(1); - }, .count = std.fmt.parseInt(usize, args.option("--count") orelse "10", 10) catch |err| { Output.prettyErrorln("{s} parsing count", .{@errorName(err)}); Global.exit(1); @@ -225,7 +219,6 @@ pub fn main() anyerror!void { args.headers_buf, response_body, "", - args.timeout, ), }; ctx.http.client.verbose = args.verbose; diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 4abe1d3662..a97124aa0b 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -1623,7 +1623,6 @@ pub const Fetch = struct { fetch_options.headers.buf.items, &fetch_tasklet.response_buffer, fetch_tasklet.request_body.slice(), - fetch_options.timeout, http.HTTPClientResult.Callback.New( *FetchTasklet, FetchTasklet.callback, @@ -1681,7 +1680,6 @@ pub const Fetch = struct { method: Method, headers: Headers, body: HTTPRequestBody, - timeout: usize, disable_timeout: bool, disable_keepalive: bool, disable_decompression: bool, @@ -2799,7 +2797,6 @@ pub const Fetch = struct { .allocator = allocator, }, .body = http_body, - .timeout = std.time.ns_per_hour, .disable_keepalive = disable_keepalive, .disable_timeout = disable_timeout, .disable_decompression = disable_decompression, diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index aaf2f44cf4..870360f506 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -1811,7 +1811,6 @@ pub const Example = struct { const examples_url: string = "https://registry.npmjs.org/bun-examples-all/latest"; var url: URL = undefined; - pub const timeout: u32 = 6000; var app_name_buf: [512]u8 = undefined; pub fn print(examples: []const Example, default_app_name: ?string) void { @@ -1977,7 +1976,6 @@ pub const Example = struct { headers_buf, mutable, "", - 60 * std.time.ns_per_min, http_proxy, null, HTTP.FetchRedirect.follow, @@ -2055,7 +2053,6 @@ pub const Example = struct { "", mutable, "", - 60 * std.time.ns_per_min, http_proxy, null, HTTP.FetchRedirect.follow, @@ -2145,7 +2142,6 @@ pub const Example = struct { "", mutable, "", - 60 * std.time.ns_per_min, http_proxy, null, HTTP.FetchRedirect.follow, @@ -2188,7 +2184,6 @@ pub const Example = struct { "", mutable, "", - 60 * std.time.ns_per_min, http_proxy, null, HTTP.FetchRedirect.follow, diff --git a/src/cli/upgrade_command.zig b/src/cli/upgrade_command.zig index 2f731d6701..0c5ba5c28e 100644 --- a/src/cli/upgrade_command.zig +++ b/src/cli/upgrade_command.zig @@ -163,7 +163,6 @@ pub const UpgradeCheckerThread = struct { }; pub const UpgradeCommand = struct { - pub const timeout: u32 = 30000; const default_github_headers: string = "Acceptapplication/vnd.github.v3+json"; var github_repository_url_buf: bun.PathBuffer = undefined; var current_executable_buf: bun.PathBuffer = undefined; @@ -245,7 +244,6 @@ pub const UpgradeCommand = struct { headers_buf, &metadata_body, "", - 60 * std.time.ns_per_min, http_proxy, null, HTTP.FetchRedirect.follow, @@ -528,12 +526,10 @@ pub const UpgradeCommand = struct { "", zip_file_buffer, "", - timeout, http_proxy, null, HTTP.FetchRedirect.follow, ); - async_http.client.timeout = timeout; async_http.client.progress_node = progress; async_http.client.reject_unauthorized = env_loader.getTLSRejectUnauthorized(); diff --git a/src/compile_target.zig b/src/compile_target.zig index 0a8ec3ff79..c840c018dd 100644 --- a/src/compile_target.zig +++ b/src/compile_target.zig @@ -153,7 +153,6 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc { var progress = refresher.start("Downloading", 0); defer progress.end(); - const timeout = 30000; const http_proxy: ?bun.URL = env.getHttpProxy(url); async_http.* = HTTP.AsyncHTTP.initSync( @@ -164,12 +163,10 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc "", compressed_archive_bytes, "", - timeout, http_proxy, null, HTTP.FetchRedirect.follow, ); - async_http.client.timeout = timeout; async_http.client.progress_node = progress; async_http.client.reject_unauthorized = env.getTLSRejectUnauthorized(); diff --git a/src/http.zig b/src/http.zig index d1ff0f9c84..99635e0451 100644 --- a/src/http.zig +++ b/src/http.zig @@ -350,6 +350,11 @@ fn NewHTTPContext(comptime ssl: bool) type { socket.close(.failure); } + fn closeSocket(socket: HTTPSocket) void { + markSocketAsDead(socket); + socket.close(.normal); + } + fn getTagged(ptr: *anyopaque) ActiveSocket { return ActiveSocket.from(bun.cast(**anyopaque, ptr).*); } @@ -472,8 +477,7 @@ fn NewHTTPContext(comptime ssl: bool) type { } } - markSocketAsDead(socket); - socket.close(.normal); + closeSocket(socket); } pub const Handler = struct { @@ -487,7 +491,7 @@ fn NewHTTPContext(comptime ssl: bool) type { } if (active.get(PooledSocket)) |pooled| { - assert(context().pending_sockets.put(pooled)); + addMemoryBackToPool(pooled); return; } @@ -523,33 +527,43 @@ fn NewHTTPContext(comptime ssl: bool) type { if (!client.checkServerIdentity(comptime ssl, socket, handshake_error)) { client.did_have_handshaking_error = true; + if (!socket.isClosed()) terminateSocket(socket); return; } return client.firstCall(comptime ssl, socket); } else { // if authorized it self is false, this means that the connection was rejected - markSocketAsDead(socket); + terminateSocket(socket); if (client.state.stage != .done and client.state.stage != .fail) client.fail(error.ConnectionRefused); return; } } - // we can reach here if we are aborted - if (!socket.isClosed()) { + if (socket.isClosed()) { + markSocketAsDead(socket); if (active.get(PooledSocket)) |pooled| { - assert(context().pending_sockets.put(pooled)); - return; + addMemoryBackToPool(pooled); } - terminateSocket(socket); - } else { - if (active.get(PooledSocket)) |pooled| { - assert(context().pending_sockets.put(pooled)); + return; + } + + if (authorized) { + if (active.is(PooledSocket)) { + // Allow pooled sockets to be reused if the handshake was successful. + socket.setTimeout(0); + socket.setTimeoutMinutes(5); return; } } + + if (active.get(PooledSocket)) |pooled| { + addMemoryBackToPool(pooled); + } + + terminateSocket(socket); } pub fn onClose( ptr: *anyopaque, @@ -565,11 +579,16 @@ fn NewHTTPContext(comptime ssl: bool) type { } if (tagged.get(PooledSocket)) |pooled| { - assert(context().pending_sockets.put(pooled)); + addMemoryBackToPool(pooled); } return; } + + fn addMemoryBackToPool(pooled: *PooledSocket) void { + assert(context().pending_sockets.put(pooled)); + } + pub fn onData( ptr: *anyopaque, socket: HTTPSocket, @@ -620,14 +639,12 @@ fn NewHTTPContext(comptime ssl: bool) type { socket: HTTPSocket, ) void { const tagged = getTagged(ptr); - if (tagged.get(HTTPClient)) |client| { - return client.onTimeout( - comptime ssl, - socket, - ); + return client.onTimeout(comptime ssl, socket); } else if (tagged.get(PooledSocket)) |pooled| { - assert(context().pending_sockets.put(pooled)); + // If a socket has been sitting around for 5 minutes + // Let's close it and remove it from the pool. + addMemoryBackToPool(pooled); } terminateSocket(socket); @@ -640,23 +657,23 @@ fn NewHTTPContext(comptime ssl: bool) type { const tagged = getTagged(ptr); markSocketAsDead(socket); if (tagged.get(HTTPClient)) |client| { - return client.onConnectError( - comptime ssl, - socket, - ); + client.onConnectError(); } else if (tagged.get(PooledSocket)) |pooled| { - assert(context().pending_sockets.put(pooled)); + addMemoryBackToPool(pooled); } - - if (comptime Environment.isDebug) - // caller should already have closed it. - bun.debugAssert(socket.isClosed()); + // us_connecting_socket_close is always called internally by uSockets } pub fn onEnd( _: *anyopaque, socket: HTTPSocket, ) void { - // TCP fin gets closed immediately. + // TCP fin must be closed, but we must keep the original tagged + // pointer so that their onClose callback is called. + // + // Three possible states: + // 1. HTTP Keep-Alive socket: it must be removed from the pool + // 2. HTTP Client socket: it might need to be retried + // 3. Dead socket: it is already marked as dead socket.close(.failure); } }; @@ -1095,6 +1112,13 @@ pub fn firstCall( comptime is_ssl: bool, socket: NewHTTPContext(is_ssl).HTTPSocket, ) void { + if (comptime FeatureFlags.is_fetch_preconnect_supported) { + if (client.is_preconnect_only) { + client.onPreconnect(is_ssl, socket); + return; + } + } + if (client.state.request_stage == .pending) { client.onWritable(true, comptime is_ssl, socket); } @@ -1144,17 +1168,17 @@ pub fn onTimeout( comptime is_ssl: bool, socket: NewHTTPContext(is_ssl).HTTPSocket, ) void { - _ = socket; + if (client.disable_timeout) return; log("Timeout {s}\n", .{client.url.href}); + defer NewHTTPContext(is_ssl).terminateSocket(socket); + if (client.state.stage != .done and client.state.stage != .fail) { client.fail(error.Timeout); } } pub fn onConnectError( client: *HTTPClient, - comptime is_ssl: bool, - _: NewHTTPContext(is_ssl).HTTPSocket, ) void { log("onConnectError {s}\n", .{client.url.href}); if (client.state.stage != .done and client.state.stage != .fail) @@ -1567,7 +1591,6 @@ remaining_redirect_count: i8 = default_redirect_count, allow_retry: bool = false, redirect_type: FetchRedirect = FetchRedirect.follow, redirect: []u8 = &.{}, -timeout: usize = 0, progress_node: ?*Progress.Node = null, disable_timeout: bool = false, disable_keepalive: bool = false, @@ -1745,8 +1768,6 @@ pub const AsyncHTTP = struct { task: ThreadPool.Task = ThreadPool.Task{ .callback = &startAsyncHTTP }, result_callback: HTTPClientResult.Callback = undefined, - /// Timeout in nanoseconds - timeout: usize = 0, redirected: bool = false, response_encoding: Encoding = Encoding.identity, @@ -1873,7 +1894,7 @@ pub const AsyncHTTP = struct { .is_url_owned = is_url_owned, }); - this.async_http = AsyncHTTP.init(bun.default_allocator, .GET, url, .{}, "", &this.response_buffer, "", 0, HTTPClientResult.Callback.New(*Preconnect, Preconnect.onResult).init(this), .manual, .{}); + this.async_http = AsyncHTTP.init(bun.default_allocator, .GET, url, .{}, "", &this.response_buffer, "", HTTPClientResult.Callback.New(*Preconnect, Preconnect.onResult).init(this), .manual, .{}); this.async_http.client.is_preconnect_only = true; http_thread.schedule(Batch.from(&this.async_http.task)); @@ -1887,7 +1908,6 @@ pub const AsyncHTTP = struct { headers_buf: string, response_buffer: *MutableString, request_body: []const u8, - timeout: usize, callback: HTTPClientResult.Callback, redirect_type: FetchRedirect, options: Options, @@ -1904,7 +1924,6 @@ pub const AsyncHTTP = struct { .http_proxy = options.http_proxy, .signals = options.signals orelse .{}, .async_http_id = if (options.signals != null and options.signals.?.aborted != null) async_http_id.fetchAdd(1, .monotonic) else 0, - .timeout = timeout, }; this.client = .{ @@ -1916,7 +1935,6 @@ pub const AsyncHTTP = struct { .hostname = options.hostname, .signals = options.signals orelse this.signals, .async_http_id = this.async_http_id, - .timeout = timeout, .http_proxy = this.http_proxy, .redirect_type = redirect_type, }; @@ -2002,20 +2020,17 @@ pub const AsyncHTTP = struct { return this; } - pub fn initSync(allocator: std.mem.Allocator, method: Method, url: URL, headers: Headers.Entries, headers_buf: string, response_buffer: *MutableString, request_body: []const u8, timeout: usize, http_proxy: ?URL, hostname: ?[]u8, redirect_type: FetchRedirect) AsyncHTTP { - return @This().init(allocator, method, url, headers, headers_buf, response_buffer, request_body, timeout, undefined, redirect_type, .{ + pub fn initSync(allocator: std.mem.Allocator, method: Method, url: URL, headers: Headers.Entries, headers_buf: string, response_buffer: *MutableString, request_body: []const u8, http_proxy: ?URL, hostname: ?[]u8, redirect_type: FetchRedirect) AsyncHTTP { + return @This().init(allocator, method, url, headers, headers_buf, response_buffer, request_body, undefined, redirect_type, .{ .http_proxy = http_proxy, .hostname = hostname, }); } fn reset(this: *AsyncHTTP) !void { - const timeout = this.timeout; const aborted = this.client.aborted; this.client = try HTTPClient.init(this.allocator, this.method, this.client.url, this.client.header_entries, this.client.header_buf, aborted); - this.client.timeout = timeout; this.client.http_proxy = this.http_proxy; - this.timeout = timeout; if (this.http_proxy) |proxy| { //TODO: need to understand how is possible to reuse Proxy with TSL, so disable keepalive if url is HTTPS @@ -2298,7 +2313,6 @@ pub fn doRedirect( this.state.response_message_buffer.deinit(); // we need to clean the client reference before closing the socket because we are going to reuse the same ref in a another request - socket.ext(**anyopaque).* = bun.cast(**anyopaque, NewHTTPContext(is_ssl).ActiveSocket.init(&dead_socket).ptr()); if (this.isKeepAlivePossible()) { assert(this.connected_url.hostname.len > 0); ctx.releaseSocket( @@ -2308,8 +2322,7 @@ pub fn doRedirect( this.connected_url.getPortAuto(), ); } else { - NewHTTPContext(is_ssl).markSocketAsDead(socket); - socket.close(.normal); + NewHTTPContext(is_ssl).closeSocket(socket); } this.connected_url = URL{}; @@ -2383,6 +2396,7 @@ fn start_(this: *HTTPClient, comptime is_ssl: bool) void { }; if (socket.isClosed() and (this.state.response_stage != .done and this.state.response_stage != .fail)) { + NewHTTPContext(is_ssl).markSocketAsDead(socket); this.fail(error.ConnectionClosed); assert(this.state.fail != null); return; @@ -2429,7 +2443,6 @@ pub fn onPreconnect(this: *HTTPClient, comptime is_ssl: bool, socket: NewHTTPCon log("onPreconnect({})", .{this.url}); _ = socket_async_http_abort_tracker.swapRemove(this.async_http_id); const ctx = if (comptime is_ssl) &http_thread.https_context else &http_thread.http_context; - ctx.releaseSocket( socket, this.did_have_handshaking_error and !this.reject_unauthorized, @@ -2740,8 +2753,7 @@ pub fn closeAndFail(this: *HTTPClient, err: anyerror, comptime is_ssl: bool, soc if (this.state.stage != .fail and this.state.stage != .done) { log("closeAndFail: {s}", .{@errorName(err)}); if (!socket.isClosed()) { - socket.ext(**anyopaque).* = bun.cast(**anyopaque, NewHTTPContext(is_ssl).ActiveSocket.init(&dead_socket).ptr()); - socket.close(.failure); + NewHTTPContext(is_ssl).terminateSocket(socket); } this.fail(err); } @@ -3158,8 +3170,7 @@ pub fn progressUpdate(this: *HTTPClient, comptime is_ssl: bool, ctx: *NewHTTPCon this.connected_url.getPortAuto(), ); } else if (!socket.isClosed()) { - NewHTTPContext(is_ssl).markSocketAsDead(socket); - socket.close(.normal); + NewHTTPContext(is_ssl).closeSocket(socket); } this.state.reset(this.allocator); diff --git a/src/install/install.zig b/src/install/install.zig index 506d3c4995..89d94fc801 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -426,7 +426,7 @@ const NetworkTask = struct { this.allocator = allocator; const url = URL.parse(this.url_buf); - this.http = AsyncHTTP.init(allocator, .GET, url, header_builder.entries, header_builder.content.ptr.?[0..header_builder.content.len], &this.response_buffer, "", 0, this.getCompletionCallback(), HTTP.FetchRedirect.follow, .{ + this.http = AsyncHTTP.init(allocator, .GET, url, header_builder.entries, header_builder.content.ptr.?[0..header_builder.content.len], &this.response_buffer, "", this.getCompletionCallback(), HTTP.FetchRedirect.follow, .{ .http_proxy = this.package_manager.httpProxy(url), }); this.http.client.reject_unauthorized = this.package_manager.tlsRejectUnauthorized(); @@ -510,7 +510,7 @@ const NetworkTask = struct { const url = URL.parse(this.url_buf); - this.http = AsyncHTTP.init(allocator, .GET, url, header_builder.entries, header_buf, &this.response_buffer, "", 0, this.getCompletionCallback(), HTTP.FetchRedirect.follow, .{ + this.http = AsyncHTTP.init(allocator, .GET, url, header_builder.entries, header_buf, &this.response_buffer, "", this.getCompletionCallback(), HTTP.FetchRedirect.follow, .{ .http_proxy = this.package_manager.httpProxy(url), }); this.http.client.reject_unauthorized = this.package_manager.tlsRejectUnauthorized(); diff --git a/test/js/web/fetch/fetch-preconnect.test.ts b/test/js/web/fetch/fetch-preconnect.test.ts index 88a842e5cc..1259b0cd45 100644 --- a/test/js/web/fetch/fetch-preconnect.test.ts +++ b/test/js/web/fetch/fetch-preconnect.test.ts @@ -29,46 +29,65 @@ describe.todoIf(isWindows)("fetch.preconnect", () => { expect(response.status).toBe(200); }); - describe("closing the connection doesn't break the request", () => { - for (let at of ["before", "after"]) { - it(at, async () => { - let { promise, resolve } = Promise.withResolvers(); - using listener = Bun.listen({ - port: 0, - hostname: "localhost", - socket: { - open(socket) { - resolve(socket); - }, - data() {}, - close() {}, - }, - }); - fetch.preconnect(`http://localhost:${listener.port}`); - let socket = await promise; - ({ promise, resolve } = Promise.withResolvers()); - if (at === "before") { - await Bun.sleep(16); - socket.end(); - } - const fetchPromise = fetch(`http://localhost:${listener.port}`); - if (at === "after") { - await Bun.sleep(16); - socket.end(); - } - socket = await promise; - socket.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); - socket.end(); + describe("doesn't break the request when", () => { + for (let endOrTerminate of ["end", "terminate", "shutdown"]) { + describe(endOrTerminate, () => { + for (let at of ["before", "middle", "after"]) { + it(at, async () => { + let { promise, resolve } = Promise.withResolvers(); + using listener = Bun.listen({ + port: 0, + hostname: "localhost", + socket: { + open(socket) { + resolve(socket); + }, + data() {}, + close() {}, + }, + }); + fetch.preconnect(`http://localhost:${listener.port}`); + let socket = await promise; + ({ promise, resolve } = Promise.withResolvers()); + if (at === "before") { + await Bun.sleep(16); + socket[endOrTerminate](); + if (endOrTerminate === "shutdown") { + await Bun.sleep(0); + socket.end(); + } + } + const fetchPromise = fetch(`http://localhost:${listener.port}`); + if (at === "middle") { + socket[endOrTerminate](); + if (endOrTerminate === "shutdown") { + socket.end(); + } + await Bun.sleep(16); + } - const response = await fetchPromise; - expect(response.status).toBe(200); + if (at === "after") { + await Bun.sleep(16); + socket[endOrTerminate](); + if (endOrTerminate === "shutdown") { + socket.end(); + } + } + socket = await promise; + socket.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + socket.end(); + + const response = await fetchPromise; + expect(response.status).toBe(200); + }); + } }); } }); it("--fetch-preconnect works", async () => { const { promise, resolve } = Promise.withResolvers(); - const listener = Bun.listen({ + using listener = Bun.listen({ port: 0, hostname: "localhost", socket: { @@ -86,7 +105,6 @@ describe.todoIf(isWindows)("fetch.preconnect", () => { expect([`--fetch-preconnect=http://localhost:${listener.port}`, "--eval", "Bun.sleep(64)"]).toRun(); await promise; - listener.stop(true); }); it("fetch.preconnect validates the URL", async () => { From 177f3a8622a15b95ce07041011404dbe695e77d6 Mon Sep 17 00:00:00 2001 From: David Stevens Date: Wed, 24 Jul 2024 01:07:38 +0700 Subject: [PATCH 046/123] Fixes #12182 - update default port when server is created (#12201) --- src/js/node/http.ts | 3 +++ test/js/node/http/node-http.test.ts | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/js/node/http.ts b/src/js/node/http.ts index 18c3c0a107..6a7b821428 100644 --- a/src/js/node/http.ts +++ b/src/js/node/http.ts @@ -504,6 +504,9 @@ Server.prototype.address = function () { Server.prototype.listen = function (port, host, backlog, onListen) { const server = this; let socketPath; + if (typeof port === "undefined") { + port = 0; + } if (typeof port == "string" && !Number.isSafeInteger(Number(port))) { socketPath = port; } diff --git a/test/js/node/http/node-http.test.ts b/test/js/node/http/node-http.test.ts index d151e47415..cec90d9e85 100644 --- a/test/js/node/http/node-http.test.ts +++ b/test/js/node/http/node-http.test.ts @@ -25,7 +25,7 @@ import { unlinkSync } from "node:fs"; import { PassThrough } from "node:stream"; const { describe, expect, it, beforeAll, afterAll, createDoneDotAll, mock } = createTest(import.meta.path); import { bunExe } from "bun:harness"; -import { bunEnv, disableAggressiveGCScope, tmpdirSync } from "harness"; +import { bunEnv, disableAggressiveGCScope, tmpdirSync, randomPort } from "harness"; import * as stream from "node:stream"; import * as zlib from "node:zlib"; @@ -158,6 +158,28 @@ describe("node:http", () => { server.close(); }); + it("should use the provided port", async () => { + const server = http.createServer(() => {}); + const random_port = randomPort(); + server.listen(random_port); + const { port } = server.address(); + expect(port).toEqual(random_port); + server.close(); + }); + + it("should assign a random port when undefined", async () => { + const server1 = http.createServer(() => {}); + const server2 = http.createServer(() => {}); + server1.listen(undefined); + server2.listen(undefined); + const { port: port1 } = server1.address(); + const { port: port2 } = server2.address(); + expect(port1).not.toEqual(port2); + expect(port1).toBeWithin(1024, 65535); + server1.close(); + server2.close(); + }); + it("option method should be uppercase (#7250)", async () => { try { var server = createServer((req, res) => { From 79ddf0e47a718f1e4a546c2c078165a3e0713bb2 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 23 Jul 2024 22:02:51 -0700 Subject: [PATCH 047/123] Fix assertion failure in bun build when entry point is a file loader (#12683) Co-authored-by: dave caruso --- src/bundler/bundle_v2.zig | 16 ++++++++++------ test/bundler/bundler_edgecase.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 1f8478a90a..404e0d2cc9 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -657,14 +657,13 @@ pub const BundleV2 = struct { var result = resolve; var path = result.path() orelse return null; - const loader = this.bundler.options.loaders.get(path.name.ext) orelse .file; - const entry = try this.graph.path_to_source_index_map.getOrPut(this.graph.allocator, hash orelse path.hashKey()); if (entry.found_existing) { return null; } _ = @atomicRmw(usize, &this.graph.parse_pending, .Add, 1, .monotonic); const source_index = Index.source(this.graph.input_files.len); + const loader = this.bundler.options.loaders.get(path.name.ext) orelse .file; if (path.pretty.ptr == path.text.ptr) { // TODO: outbase @@ -4767,7 +4766,7 @@ const LinkerContext = struct { } } - if (comptime Environment.allow_assert) { + if (comptime Environment.enable_logs) { var cjs_count: usize = 0; var esm_count: usize = 0; var wrap_cjs_count: usize = 0; @@ -9577,9 +9576,14 @@ const LinkerContext = struct { from_chunk_dir = ""; const additional_files: []AdditionalFile = c.graph.bundler_graph.input_files.items(.additional_files)[piece.index.index].slice(); - bun.assert(additional_files.len == 1); - const path = c.graph.bundler_graph.additional_output_files.items[additional_files[0].output_file].dest_path; - hash.write(bun.path.relativePlatform(from_chunk_dir, path, .posix, false)); + bun.assert(additional_files.len > 0); + switch (additional_files[0]) { + .output_file => |output_file_id| { + const path = c.graph.bundler_graph.additional_output_files.items[output_file_id].dest_path; + hash.write(bun.path.relativePlatform(from_chunk_dir, path, .posix, false)); + }, + .source_index => {}, + } } }, else => {}, diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 862f72484a..0d12a3192d 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -668,6 +668,29 @@ describe("bundler", () => { "": ['ModuleNotFound resolving "/entry.js" (entry point)'], }, }); + itBundled("edgecase/AssetEntryPoint", { + files: { + "/entry.zig": ` + const std = @import("std"); + + pub fn main() void { + std.debug.print("Hello, world!\\n", .{}); + } + `, + }, + outdir: "/out", + entryPointsRaw: ["./entry.zig"], + runtimeFiles: { + "/exec.js": ` + import assert from 'node:assert'; + import the_path from './out/entry.js'; + assert.strictEqual(the_path, './entry-6dhkdck1.zig'); + `, + }, + run: { + file: "./exec.js", + }, + }); itBundled("edgecase/ExportDefaultUndefined", { files: { "/entry.ts": /* ts */ ` From f9371e59f25b8bde5eddfdc178f0038078f7af13 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 23 Jul 2024 23:49:01 -0700 Subject: [PATCH 048/123] fix(bundler): fix part liveness calculation (#12758) --- src/ast/base.zig | 2 +- src/bundler/bundle_v2.zig | 338 ++++++++++++-------------- test/bundler/bundler_edgecase.test.ts | 117 +++++++++ 3 files changed, 279 insertions(+), 178 deletions(-) diff --git a/src/ast/base.zig b/src/ast/base.zig index 160bb28815..2c096a676b 100644 --- a/src/ast/base.zig +++ b/src/ast/base.zig @@ -140,8 +140,8 @@ pub const Ref = packed struct(u64) { writer, "Ref[inner={d}, src={d}, .{s}]", .{ - ref.sourceIndex(), ref.innerIndex(), + ref.sourceIndex(), @tagName(ref.tag), }, ); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 404e0d2cc9..fa848d8139 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -126,6 +126,8 @@ const debugTreeShake = Output.scoped(.TreeShake, true); const BitSet = bun.bit_set.DynamicBitSetUnmanaged; const Async = bun.Async; +const logPartDependencyTree = Output.scoped(.part_dep_tree, false); + fn tracer(comptime src: std.builtin.SourceLocation, comptime name: [:0]const u8) bun.tracy.Ctx { return bun.tracy.traceNamed(src, "Bundler." ++ name); } @@ -3357,7 +3359,7 @@ const LinkerGraph = struct { } pub fn generateNewSymbol(this: *LinkerGraph, source_index: u32, kind: Symbol.Kind, original_name: string) Ref { - var source_symbols = &this.symbols.symbols_for_source.slice()[source_index]; + const source_symbols = &this.symbols.symbols_for_source.slice()[source_index]; var ref = Ref.init( @as(Ref.Int, @truncate(source_symbols.len)), @@ -3747,9 +3749,9 @@ const LinkerGraph = struct { } } - const in_resolved_exports: []ResolvedExports = this.meta.items(.resolved_exports); - const src_resolved_exports: []js_ast.Ast.NamedExports = this.ast.items(.named_exports); - for (src_resolved_exports, in_resolved_exports, 0..) |src, *dest, source_index| { + const src_named_exports: []js_ast.Ast.NamedExports = this.ast.items(.named_exports); + const dest_resolved_exports: []ResolvedExports = this.meta.items(.resolved_exports); + for (src_named_exports, dest_resolved_exports, 0..) |src, *dest, source_index| { var resolved = ResolvedExports{}; resolved.ensureTotalCapacity(this.allocator, src.count()) catch unreachable; for (src.keys(), src.values()) |key, value| { @@ -3812,7 +3814,6 @@ const LinkerContext = struct { resolver: *Resolver = undefined, cycle_detector: std.ArrayList(ImportTracker) = undefined, - swap_cycle_detector: std.ArrayList(ImportTracker) = undefined, /// We may need to refer to the "__esm" and/or "__commonJS" runtime symbols cjs_runtime_ref: Ref = Ref.None, @@ -3978,7 +3979,6 @@ const LinkerContext = struct { this.resolver = &bundle.bundler.resolver; this.cycle_detector = std.ArrayList(ImportTracker).init(this.allocator); - this.swap_cycle_detector = std.ArrayList(ImportTracker).init(this.allocator); this.graph.reachable_files = reachable; @@ -4860,11 +4860,12 @@ const LinkerContext = struct { const source_index = source_index_.get(); const id = source_index; - // -- + // Expression-style loaders defer code generation until linking. Code + // generation is done here because at this point we know that the + // "ExportsKind" field has its final value and will not be changed. if (ast_flags_list[id].has_lazy_export) { try this.generateCodeForLazyExport(id); } - // -- // Propagate exports for export star statements const export_star_ids = export_star_import_records[id]; @@ -4882,8 +4883,6 @@ const LinkerContext = struct { .exports_kind = exports_kind, .named_exports = this.graph.ast.items(.named_exports), }; - } else { - export_star_ctx.?.source_index_stack.clearRetainingCapacity(); } export_star_ctx.?.addExports(&resolved_exports[id], source_index); } @@ -4910,22 +4909,21 @@ const LinkerContext = struct { this.cycle_detector.clearRetainingCapacity(); const trace = tracer(@src(), "MatchImportsWithExports"); defer trace.end(); - var wrapper_part_indices = this.graph.meta.items(.wrapper_part_index); - var imports_to_bind = this.graph.meta.items(.imports_to_bind); + const wrapper_part_indices = this.graph.meta.items(.wrapper_part_index); + const imports_to_bind = this.graph.meta.items(.imports_to_bind); for (reachable) |source_index_| { const source_index = source_index_.get(); - const id = source_index; // not a JS ast or empty - if (id >= named_imports.len) { + if (source_index >= named_imports.len) { continue; } - var named_imports_ = &named_imports[id]; + const named_imports_ = &named_imports[source_index]; if (named_imports_.count() > 0) { this.matchImportsWithExportsForFile( named_imports_, - &imports_to_bind[id], + &imports_to_bind[source_index], source_index, ); @@ -4933,8 +4931,8 @@ const LinkerContext = struct { return error.ImportResolutionFailed; } } - const export_kind = exports_kind[id]; - var flag = flags[id]; + const export_kind = exports_kind[source_index]; + var flag = flags[source_index]; // If we're exporting as CommonJS and this file was originally CommonJS, // then we'll be using the actual CommonJS "exports" and/or "module" // symbols. In that case make sure to mark them as such so they don't @@ -4943,16 +4941,16 @@ const LinkerContext = struct { entry_point_kinds[source_index].isEntryPoint() and export_kind == .cjs and flag.wrap == .none) { - const exports_ref = symbols.follow(exports_refs[id]); - const module_ref = symbols.follow(module_refs[id]); + const exports_ref = symbols.follow(exports_refs[source_index]); + const module_ref = symbols.follow(module_refs[source_index]); symbols.get(exports_ref).?.kind = .unbound; symbols.get(module_ref).?.kind = .unbound; } else if (flag.force_include_exports_for_entry_point or export_kind != .cjs) { flag.needs_exports_variable = true; - flags[id] = flag; + flags[source_index] = flag; } - const wrapped_ref = this.graph.ast.items(.wrapper_ref)[id]; + const wrapped_ref = this.graph.ast.items(.wrapper_ref)[source_index]; if (wrapped_ref.isNull() or wrapped_ref.isEmpty()) continue; // Create the wrapper part for wrapped files. This is needed by a later step. @@ -4960,7 +4958,7 @@ const LinkerContext = struct { flag.wrap, // if this one is null, the AST does not need to be wrapped. wrapped_ref, - &wrapper_part_indices[id], + &wrapper_part_indices[source_index], source_index, ); } @@ -5120,19 +5118,19 @@ const LinkerContext = struct { var imports_to_bind_list: []RefImportData = this.graph.meta.items(.imports_to_bind); var parts_list: []js_ast.Part.List = ast_fields.items(.parts); - var imports_to_bind = &imports_to_bind_list[id]; var parts: []js_ast.Part = parts_list[id].slice(); - for (0..imports_to_bind.count()) |i| { - const ref = imports_to_bind.keys()[i]; - const import = imports_to_bind.values()[i]; + const imports_to_bind = &imports_to_bind_list[id]; + for (imports_to_bind.keys(), imports_to_bind.values()) |ref_untyped, import_untyped| { + const ref: Ref = ref_untyped; // ZLS + const import: ImportData = import_untyped; // ZLS const import_source_index = import.data.source_index.get(); if (named_imports[id].get(ref)) |named_import| { for (named_import.local_parts_with_uses.slice()) |part_index| { var part: *js_ast.Part = &parts[part_index]; - const parts_declaring_symbol: []const u32 = this.graph.topLevelSymbolToParts(import_source_index, ref); + const parts_declaring_symbol: []const u32 = this.graph.topLevelSymbolToParts(import_source_index, import.data.import_ref); const total_len = parts_declaring_symbol.len + @as(usize, import.re_exports.len) + @as(usize, part.dependencies.len); if (part.dependencies.cap < total_len) { @@ -5144,12 +5142,10 @@ const LinkerContext = struct { // Depend on the file containing the imported symbol for (parts_declaring_symbol) |resolved_part_index| { - part.dependencies.appendAssumeCapacity( - .{ - .source_index = Index.source(import_source_index), - .part_index = resolved_part_index, - }, - ); + part.dependencies.appendAssumeCapacity(.{ + .source_index = Index.source(import_source_index), + .part_index = resolved_part_index, + }); } // Also depend on any files that re-exported this symbol in between the @@ -5165,40 +5161,38 @@ const LinkerContext = struct { if (is_entry_point) { const force_include_exports = flag.force_include_exports_for_entry_point; const add_wrapper = wrap != .none; - var dependencies = std.ArrayList(js_ast.Dependency).initCapacity( - this.allocator, - @as(usize, @intFromBool(force_include_exports)) + @as(usize, @intFromBool(add_wrapper)), - ) catch unreachable; + + const extra_count = @as(usize, @intFromBool(force_include_exports)) + + @as(usize, @intFromBool(add_wrapper)); + + var dependencies = std.ArrayList(js_ast.Dependency).initCapacity(this.allocator, extra_count) catch bun.outOfMemory(); + var resolved_exports_list: *ResolvedExports = &this.graph.meta.items(.resolved_exports)[id]; for (aliases) |alias| { - var export_ = resolved_exports_list.get(alias).?; - var target_source_index = export_.data.source_index.get(); - var target_id = target_source_index; - var target_ref = export_.data.import_ref; + const exp = resolved_exports_list.get(alias).?; + var target_source_index = exp.data.source_index; + var target_ref = exp.data.import_ref; // If this is an import, then target what the import points to - - if (imports_to_bind.get(target_ref)) |import_data| { - target_source_index = import_data.data.source_index.get(); - target_id = target_source_index; + if (imports_to_bind_list[target_source_index.get()].get(target_ref)) |import_data| { + target_source_index = import_data.data.source_index; target_ref = import_data.data.import_ref; - dependencies.appendSlice(import_data.re_exports.slice()) catch unreachable; + + dependencies.appendSlice(import_data.re_exports.slice()) catch bun.outOfMemory(); } - const top_to_parts = this.topLevelSymbolsToParts(target_id, target_ref); - dependencies.ensureUnusedCapacity(top_to_parts.len) catch unreachable; // Pull in all declarations of this symbol + const top_to_parts = this.topLevelSymbolsToParts(target_source_index.get(), target_ref); + dependencies.ensureUnusedCapacity(top_to_parts.len) catch bun.outOfMemory(); for (top_to_parts) |part_index| { - dependencies.appendAssumeCapacity( - .{ - .source_index = Index.source(target_source_index), - .part_index = part_index, - }, - ); + dependencies.appendAssumeCapacity(.{ + .source_index = target_source_index, + .part_index = part_index, + }); } } - dependencies.ensureUnusedCapacity(@as(usize, @intFromBool(force_include_exports)) + @as(usize, @intFromBool(add_wrapper))) catch unreachable; + dependencies.ensureUnusedCapacity(extra_count) catch bun.outOfMemory(); // Ensure "exports" is included if the current output format needs it if (force_include_exports) { @@ -5207,6 +5201,7 @@ const LinkerContext = struct { ); } + // Include the wrapper if present if (add_wrapper) { dependencies.appendAssumeCapacity( .{ @@ -5223,7 +5218,8 @@ const LinkerContext = struct { .dependencies = js_ast.Dependency.List.fromList(dependencies), .can_be_removed_if_unused = false, }, - ) catch unreachable; + ) catch bun.outOfMemory(); + parts = parts_list[id].slice(); this.graph.meta.items(.entry_point_part_index)[id] = Index.part(entry_point_part_index); @@ -5461,7 +5457,7 @@ const LinkerContext = struct { pub fn createExportsForFile( c: *LinkerContext, - allocator_: std.mem.Allocator, + allocator: std.mem.Allocator, id: u32, resolved_exports: *ResolvedExports, imports_to_bind: []RefImportData, @@ -5480,10 +5476,10 @@ const LinkerContext = struct { // 1 property per export var properties = std.ArrayList(js_ast.G.Property) - .initCapacity(allocator_, export_aliases.len) catch unreachable; + .initCapacity(allocator, export_aliases.len) catch bun.outOfMemory(); var ns_export_symbol_uses = js_ast.Part.SymbolUseMap{}; - ns_export_symbol_uses.ensureTotalCapacity(allocator_, export_aliases.len) catch unreachable; + ns_export_symbol_uses.ensureTotalCapacity(allocator, export_aliases.len) catch bun.outOfMemory(); const needs_exports_variable = c.graph.meta.items(.flags)[id].needs_exports_variable; @@ -5495,35 +5491,34 @@ const LinkerContext = struct { // + 1 if we need to inject the exports variable @as(usize, @intFromBool(needs_exports_variable)); - var stmts = js_ast.Stmt.Batcher.init(allocator_, stmts_count) catch unreachable; + var stmts = js_ast.Stmt.Batcher.init(allocator, stmts_count) catch bun.outOfMemory(); defer stmts.done(); const loc = Logger.Loc.Empty; // todo: investigate if preallocating this array is faster - var ns_export_dependencies = std.ArrayList(js_ast.Dependency).initCapacity(allocator_, re_exports_count) catch unreachable; + var ns_export_dependencies = std.ArrayList(js_ast.Dependency).initCapacity(allocator, re_exports_count) catch bun.outOfMemory(); for (export_aliases) |alias| { - var export_ = resolved_exports.getPtr(alias).?; - - const other_id = export_.data.source_index.get(); + var exp = resolved_exports.getPtr(alias).?.*; // If this is an export of an import, reference the symbol that the import // was eventually resolved to. We need to do this because imports have // already been resolved by this point, so we can't generate a new import // and have that be resolved later. - if (imports_to_bind[other_id].get(export_.data.import_ref)) |import_data| { - export_.data = import_data.data; - ns_export_dependencies.appendSlice(import_data.re_exports.slice()) catch unreachable; + if (imports_to_bind[exp.data.source_index.get()].get(exp.data.import_ref)) |import_data| { + exp.data.import_ref = import_data.data.import_ref; + exp.data.source_index = import_data.data.source_index; + ns_export_dependencies.appendSlice(import_data.re_exports.slice()) catch bun.outOfMemory(); } // Exports of imports need EImportIdentifier in case they need to be re- // written to a property access later on // note: this is stack allocated const value: js_ast.Expr = brk: { - if (c.graph.symbols.getConst(export_.data.import_ref)) |symbol| { + if (c.graph.symbols.getConst(exp.data.import_ref)) |symbol| { if (symbol.namespace_alias != null) { break :brk js_ast.Expr.init( js_ast.E.ImportIdentifier, js_ast.E.ImportIdentifier{ - .ref = export_.data.import_ref, + .ref = exp.data.import_ref, }, loc, ); @@ -5533,7 +5528,7 @@ const LinkerContext = struct { break :brk js_ast.Expr.init( js_ast.E.Identifier, js_ast.E.Identifier{ - .ref = export_.data.import_ref, + .ref = exp.data.import_ref, }, loc, ); @@ -5542,7 +5537,7 @@ const LinkerContext = struct { const fn_body = js_ast.G.FnBody{ .stmts = stmts.eat1( js_ast.Stmt.allocate( - allocator_, + allocator, js_ast.S.Return, .{ .value = value }, loc, @@ -5550,42 +5545,38 @@ const LinkerContext = struct { ), .loc = loc, }; - properties.appendAssumeCapacity( - .{ - .key = js_ast.Expr.allocate( - allocator_, - js_ast.E.String, - .{ - // TODO: test emoji work as expected - // relevant for WASM exports - .data = alias, - }, - loc, - ), - .value = js_ast.Expr.allocate( - allocator_, - js_ast.E.Arrow, - .{ .prefer_expr = true, .body = fn_body }, - loc, - ), - }, - ); - ns_export_symbol_uses.putAssumeCapacity(export_.data.import_ref, .{ .count_estimate = 1 }); + properties.appendAssumeCapacity(.{ + .key = js_ast.Expr.allocate( + allocator, + js_ast.E.String, + .{ + // TODO: test emoji work as expected + // relevant for WASM exports + .data = alias, + }, + loc, + ), + .value = js_ast.Expr.allocate( + allocator, + js_ast.E.Arrow, + .{ .prefer_expr = true, .body = fn_body }, + loc, + ), + }); + ns_export_symbol_uses.putAssumeCapacity(exp.data.import_ref, .{ .count_estimate = 1 }); // Make sure the part that declares the export is included - const parts = c.topLevelSymbolsToParts(other_id, export_.data.import_ref); + const parts = c.topLevelSymbolsToParts(exp.data.source_index.get(), exp.data.import_ref); ns_export_dependencies.ensureUnusedCapacity(parts.len) catch unreachable; - var ptr = ns_export_dependencies.items.ptr + ns_export_dependencies.items.len; - ns_export_dependencies.items.len += parts.len; - - for (parts, ptr[0..parts.len]) |part_id, *dependency| { + for (parts, ns_export_dependencies.unusedCapacitySlice()[0..parts.len]) |part_id, *dest| { // Use a non-local dependency since this is likely from a different // file if it came in through an export star - dependency.* = .{ - .source_index = export_.data.source_index, + dest.* = .{ + .source_index = exp.data.source_index, .part_index = part_id, }; } + ns_export_dependencies.items.len += parts.len; } var declared_symbols = js_ast.DeclaredSymbol.List{}; @@ -5597,19 +5588,19 @@ const LinkerContext = struct { // Prefix this part with "var exports = {}" if this isn't a CommonJS entry point if (needs_exports_variable) { - var decls = allocator_.alloc(js_ast.G.Decl, 1) catch unreachable; + var decls = allocator.alloc(js_ast.G.Decl, 1) catch unreachable; decls[0] = .{ .binding = js_ast.Binding.alloc( - allocator_, + allocator, js_ast.B.Identifier{ .ref = exports_ref, }, loc, ), - .value = js_ast.Expr.allocate(allocator_, js_ast.E.Object, .{}, loc), + .value = js_ast.Expr.allocate(allocator, js_ast.E.Object, .{}, loc), }; remaining_stmts[0] = js_ast.Stmt.allocate( - allocator_, + allocator, js_ast.S.Local, .{ .decls = G.Decl.List.init(decls), @@ -5617,24 +5608,24 @@ const LinkerContext = struct { loc, ); remaining_stmts = remaining_stmts[1..]; - declared_symbols.append(allocator_, .{ .ref = exports_ref, .is_top_level = true }) catch unreachable; + declared_symbols.append(allocator, .{ .ref = exports_ref, .is_top_level = true }) catch unreachable; } // "__export(exports, { foo: () => foo })" var export_ref = Ref.None; if (properties.items.len > 0) { export_ref = c.graph.ast.items(.module_scope)[Index.runtime.get()].members.get("__export").?.ref; - var args = allocator_.alloc(js_ast.Expr, 2) catch unreachable; + var args = allocator.alloc(js_ast.Expr, 2) catch unreachable; args[0..2].* = [_]js_ast.Expr{ js_ast.Expr.initIdentifier(exports_ref, loc), - js_ast.Expr.allocate(allocator_, js_ast.E.Object, .{ .properties = js_ast.G.Property.List.fromList(properties) }, loc), + js_ast.Expr.allocate(allocator, js_ast.E.Object, .{ .properties = js_ast.G.Property.List.fromList(properties) }, loc), }; remaining_stmts[0] = js_ast.Stmt.allocate( - allocator_, + allocator, js_ast.S.SExpr, .{ .value = js_ast.Expr.allocate( - allocator_, + allocator, js_ast.E.Call, .{ .target = js_ast.Expr.initIdentifier(export_ref, loc), @@ -10166,14 +10157,15 @@ const LinkerContext = struct { import_records: []bun.BabyList(bun.ImportRecord), entry_point_kinds: []EntryPoint.Kind, ) bool { - var part: *js_ast.Part = &parts[id].slice()[part_index]; + const part: *js_ast.Part = &parts[id].slice()[part_index]; + // only once if (part.is_live) { return false; } - part.is_live = true; - if (comptime bun.Environment.allow_assert) + + if (comptime bun.Environment.isDebug) debugTreeShake("markPartLiveForTreeShaking({d}): {s}:{d} = {d}, {s}", .{ id, c.parse_graph.input_files.get(id).source.path.text, @@ -10191,7 +10183,19 @@ const LinkerContext = struct { entry_point_kinds, ); + if (Environment.enable_logs and part.dependencies.slice().len == 0) { + logPartDependencyTree("markPartLiveForTreeShaking {d}:{d} | EMPTY", .{ + id, part_index, + }); + } + for (part.dependencies.slice()) |dependency| { + if (Environment.enable_logs and id != 0 and dependency.source_index.get() != 0) { + logPartDependencyTree("markPartLiveForTreeShaking: {d}:{d} --> {d}:{d}\n", .{ + id, part_index, dependency.source_index.get(), dependency.part_index, + }); + } + _ = c.markPartLiveForTreeShaking( dependency.part_index, dependency.source_index.get(), @@ -10207,12 +10211,16 @@ const LinkerContext = struct { pub fn matchImportWithExport( c: *LinkerContext, - init_tracker: *ImportTracker, + init_tracker: ImportTracker, re_exports: *std.ArrayList(js_ast.Dependency), ) MatchImport { + const cycle_detector_top = c.cycle_detector.items.len; + defer c.cycle_detector.shrinkRetainingCapacity(cycle_detector_top); + var tracker = init_tracker; var ambiguous_results = std.ArrayList(MatchImport).init(c.allocator); defer ambiguous_results.clearAndFree(); + var result: MatchImport = MatchImport{}; const named_imports = c.graph.ast.items(.named_imports); @@ -10226,30 +10234,26 @@ const LinkerContext = struct { // // This uses a O(n^2) array scan instead of a O(n) map because the vast // majority of cases have one or two elements - for (c.cycle_detector.items) |prev_tracker| { - if (std.meta.eql(tracker.*, prev_tracker)) { + for (c.cycle_detector.items[cycle_detector_top..]) |prev_tracker| { + if (std.meta.eql(tracker, prev_tracker)) { result = .{ .kind = .cycle }; break :loop; } } - const prev_import_ref = tracker.import_ref; - if (tracker.source_index.isInvalid()) { // External break; } const prev_source_index = tracker.source_index.get(); - c.cycle_detector.append(tracker.*) catch unreachable; + c.cycle_detector.append(tracker) catch bun.outOfMemory(); // Resolve the import by one step - var advanced = c.advanceImportTracker(tracker); - advanced.tracker.* = advanced.value; - const next_tracker = advanced.tracker.*; + const advanced = c.advanceImportTracker(&tracker); + const next_tracker = advanced.value; const status = advanced.status; const potentially_ambiguous_export_star_refs = advanced.import_data; - const other_id = advanced.value.source_index.get(); switch (status) { .cjs, .cjs_without_exports, .disabled, .external => { @@ -10263,7 +10267,7 @@ const LinkerContext = struct { // property access. Don't do this if the namespace reference is invalid // though. This is the case for star imports, where the import is the // namespace. - const named_import: js_ast.NamedImport = named_imports[prev_source_index].get(prev_import_ref).?; + const named_import: js_ast.NamedImport = named_imports[prev_source_index].get(tracker.import_ref).?; if (named_import.namespace_ref != null and named_import.namespace_ref.?.isValid()) { if (result.kind == .normal) { @@ -10299,13 +10303,13 @@ const LinkerContext = struct { // if the file was rewritten from CommonJS into ESM // and the developer imported an export that doesn't exist // We don't do a runtime error since that CJS would have returned undefined. - const named_import: js_ast.NamedImport = named_imports[prev_source_index].get(prev_import_ref).?; + const named_import: js_ast.NamedImport = named_imports[prev_source_index].get(tracker.import_ref).?; if (named_import.namespace_ref != null and named_import.namespace_ref.?.isValid()) { - const symbol = c.graph.symbols.get(prev_import_ref).?; + const symbol = c.graph.symbols.get(tracker.import_ref).?; symbol.import_item_status = .missing; result.kind = .normal_and_namespace; - result.namespace_ref = prev_import_ref; + result.namespace_ref = tracker.import_ref; result.alias = named_import.alias.?; result.name_loc = named_import.alias_loc orelse Logger.Loc.Empty; } @@ -10313,7 +10317,7 @@ const LinkerContext = struct { .dynamic_fallback => { // If it's a file with dynamic export fallback, rewrite the import to a property access - const named_import: js_ast.NamedImport = named_imports[prev_source_index].get(prev_import_ref).?; + const named_import: js_ast.NamedImport = named_imports[prev_source_index].get(tracker.import_ref).?; if (named_import.namespace_ref != null and named_import.namespace_ref.?.isValid()) { if (result.kind == .normal) { result.kind = .normal_and_namespace; @@ -10330,8 +10334,8 @@ const LinkerContext = struct { }, .no_match => { // Report mismatched imports and exports - const symbol = c.graph.symbols.get(prev_import_ref).?; - const named_import: js_ast.NamedImport = named_imports[prev_source_index].get(prev_import_ref).?; + const symbol = c.graph.symbols.get(tracker.import_ref).?; + const named_import: js_ast.NamedImport = named_imports[prev_source_index].get(tracker.import_ref).?; const source = c.source_(prev_source_index); const next_source = c.source_(next_tracker.source_index.get()); @@ -10412,15 +10416,7 @@ const LinkerContext = struct { for (potentially_ambiguous_export_star_refs) |*ambiguous_tracker| { // If this is a re-export of another import, follow the import if (named_imports[ambiguous_tracker.data.source_index.get()].contains(ambiguous_tracker.data.import_ref)) { - c.cycle_detector.clearRetainingCapacity(); - c.swap_cycle_detector.clearRetainingCapacity(); - - const old_cycle_detector = c.cycle_detector; - c.cycle_detector = c.swap_cycle_detector; - const ambig = c.matchImportWithExport(&ambiguous_tracker.data, re_exports); - c.cycle_detector.clearRetainingCapacity(); - c.swap_cycle_detector = c.cycle_detector; - c.cycle_detector = old_cycle_detector; + const ambig = c.matchImportWithExport(ambiguous_tracker.data, re_exports); ambiguous_results.append(ambig) catch unreachable; } else { ambiguous_results.append(.{ @@ -10447,7 +10443,7 @@ const LinkerContext = struct { // Depend on the statement(s) that declared this import symbol in the // original file { - const deps = c.topLevelSymbolsToParts(other_id, tracker.import_ref); + const deps = c.topLevelSymbolsToParts(prev_source_index, tracker.import_ref); re_exports.ensureUnusedCapacity(deps.len) catch unreachable; for (deps) |dep| { re_exports.appendAssumeCapacity( @@ -10463,7 +10459,7 @@ const LinkerContext = struct { // iteration of the loop to resolve that import as well const next_id = next_tracker.source_index.get(); if (named_imports[next_id].contains(next_tracker.import_ref)) { - tracker.* = next_tracker; + tracker = next_tracker; continue :loop; } }, @@ -10634,7 +10630,7 @@ const LinkerContext = struct { } } - pub fn advanceImportTracker(c: *LinkerContext, tracker: *ImportTracker) ImportTracker.Iterator { + pub fn advanceImportTracker(c: *LinkerContext, tracker: *const ImportTracker) ImportTracker.Iterator { const id = tracker.source_index.get(); var named_imports: *JSAst.NamedImports = &c.graph.ast.items(.named_imports)[id]; var import_records = c.graph.ast.items(.import_records)[id]; @@ -10647,7 +10643,6 @@ const LinkerContext = struct { return .{ .value = .{}, .status = .external, - .tracker = tracker, }; // Is this an external file? @@ -10656,7 +10651,6 @@ const LinkerContext = struct { return .{ .value = .{}, .status = .external, - .tracker = tracker, }; } @@ -10670,7 +10664,6 @@ const LinkerContext = struct { .source_index = record.source_index, }, .status = .disabled, - .tracker = tracker, }; } @@ -10692,7 +10685,6 @@ const LinkerContext = struct { .import_ref = Ref.None, }, .status = .cjs_without_exports, - .tracker = tracker, }; } const other_kind = exports_kind[other_id]; @@ -10704,7 +10696,6 @@ const LinkerContext = struct { .import_ref = Ref.None, }, .status = .cjs, - .tracker = tracker, }; } @@ -10717,7 +10708,6 @@ const LinkerContext = struct { .value = matching_export.data, .status = .found, .import_data = matching_export.potentially_ambiguous_export_star_refs.slice(), - .tracker = tracker, }; } } @@ -10733,7 +10723,6 @@ const LinkerContext = struct { }, .status = .found, .import_data = matching_export.potentially_ambiguous_export_star_refs.slice(), - .tracker = tracker, }; } @@ -10749,7 +10738,6 @@ const LinkerContext = struct { .dynamic_fallback_interop_default else .dynamic_fallback, - .tracker = tracker, }; } @@ -10759,7 +10747,6 @@ const LinkerContext = struct { return .{ .value = .{}, .status = .probably_typescript_type, - .tracker = tracker, }; } @@ -10768,7 +10755,6 @@ const LinkerContext = struct { .source_index = Index.source(other_source_index), }, .status = .no_match, - .tracker = tracker, }; } @@ -10802,15 +10788,12 @@ const LinkerContext = struct { const import_ref = ref; - var import_tracker = ImportData{ - .data = .{ + var re_exports = std.ArrayList(js_ast.Dependency).init(c.allocator); + const result = c.matchImportWithExport( + .{ .source_index = Index.source(source_index), .import_ref = import_ref, }, - }; - var re_exports = std.ArrayList(js_ast.Dependency).init(c.allocator); - const result = c.matchImportWithExport( - &import_tracker.data, &re_exports, ); @@ -10928,14 +10911,13 @@ const LinkerContext = struct { if (i == source_index) return; } - - this.source_index_stack.append(source_index) catch unreachable; + this.source_index_stack.append(source_index) catch bun.outOfMemory(); const stack_end_pos = this.source_index_stack.items.len; - const id = source_index; + defer this.source_index_stack.shrinkRetainingCapacity(stack_end_pos - 1); - const import_records = this.import_records_list[id].slice(); + const import_records = this.import_records_list[source_index].slice(); - for (this.export_star_records[id]) |import_id| { + for (this.export_star_records[source_index]) |import_id| { const other_source_index = import_records[import_id].source_index.get(); const other_id = other_source_index; @@ -10952,9 +10934,11 @@ const LinkerContext = struct { // re-exports as property accesses off of a generated require() call. if (this.exports_kind[other_id] == .cjs) continue; + var iter = this.named_exports[other_id].iterator(); next_export: while (iter.next()) |entry| { const alias = entry.key_ptr.*; + const name = entry.value_ptr.*; // ES6 export star statements ignore exports named "default" if (strings.eqlComptime(alias, "default")) @@ -10966,34 +10950,35 @@ const LinkerContext = struct { continue :next_export; } } - const ref = entry.value_ptr.ref; - var resolved = resolved_exports.getOrPut(this.allocator, entry.key_ptr.*) catch unreachable; - if (!resolved.found_existing) { - resolved.value_ptr.* = .{ + + const gop = resolved_exports.getOrPut(this.allocator, alias) catch bun.outOfMemory(); + if (!gop.found_existing) { + // Initialize the re-export + gop.value_ptr.* = .{ .data = .{ - .import_ref = ref, + .import_ref = name.ref, .source_index = Index.source(other_source_index), - .name_loc = entry.value_ptr.alias_loc, + .name_loc = name.alias_loc, }, }; // Make sure the symbol is marked as imported so that code splitting // imports it correctly if it ends up being shared with another chunk - this.imports_to_bind[id].put(this.allocator, entry.value_ptr.ref, .{ + this.imports_to_bind[source_index].put(this.allocator, name.ref, .{ .data = .{ - .import_ref = ref, + .import_ref = name.ref, .source_index = Index.source(other_source_index), }, - }) catch unreachable; - } else if (resolved.value_ptr.data.source_index.get() != other_source_index) { + }) catch bun.outOfMemory(); + } else if (gop.value_ptr.data.source_index.get() != other_source_index) { // Two different re-exports colliding makes it potentially ambiguous - resolved.value_ptr.potentially_ambiguous_export_star_refs.push(this.allocator, .{ + gop.value_ptr.potentially_ambiguous_export_star_refs.push(this.allocator, .{ .data = .{ .source_index = Index.source(other_source_index), - .import_ref = ref, - .name_loc = entry.value_ptr.alias_loc, + .import_ref = name.ref, + .name_loc = name.alias_loc, }, - }) catch unreachable; + }) catch bun.outOfMemory(); } } @@ -11240,7 +11225,6 @@ pub const ImportTracker = struct { status: Status = Status.no_match, value: ImportTracker = .{}, import_data: []ImportData = &.{}, - tracker: *ImportTracker, }; }; diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 0d12a3192d..c51cda9f18 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -1487,6 +1487,123 @@ describe("bundler", () => { "/entry.ts": [`"Y" has already been declared`], }, }); + // This specifically only happens with 'export { ... } from ...' syntax + itBundled("edgecase/EsmSideEffectsFalseWithSideEffectsExportFrom", { + files: { + "/file1.js": ` + import("./file2.js"); + `, + "/file2.js": ` + export { a } from './file3.js'; + `, + "/file3.js": ` + export function a(input) { + return 42; + } + console.log('side effect'); + `, + "/package.json": ` + { + "name": "my-package", + "sideEffects": false + } + `, + }, + run: { + stdout: "side effect", + }, + }); + itBundled("edgecase/EsmSideEffectsFalseWithSideEffectsExportFromCodeSplitting", { + files: { + "/file1.js": ` + import("./file2.js"); + console.log('file1'); + `, + "/file1b.js": ` + import("./file2.js"); + console.log('file2'); + `, + "/file2.js": ` + export { a } from './file3.js'; + `, + "/file3.js": ` + export function a(input) { + return 42; + } + console.log('side effect'); + `, + "/package.json": ` + { + "name": "my-package", + "sideEffects": false + } + `, + }, + splitting: true, + outdir: "out", + entryPoints: ["./file1.js", "./file1b.js"], + run: [ + { + file: "/out/file1.js", + stdout: "file1\nside effect", + }, + { + file: "/out/file1b.js", + stdout: "file2\nside effect", + }, + ], + }); + itBundled("edgecase/RequireSideEffectsFalseWithSideEffectsExportFrom", { + files: { + "/file1.js": ` + require("./file2.js"); + `, + "/file2.js": ` + export { a } from './file3.js'; + `, + "/file3.js": ` + export function a(input) { + return 42; + } + console.log('side effect'); + `, + "/package.json": ` + { + "name": "my-package", + "sideEffects": false + } + `, + }, + run: { + stdout: "side effect", + }, + }); + itBundled("edgecase/SideEffectsFalseWithSideEffectsExportFrom", { + files: { + "/file1.js": ` + import("./file2.js"); + `, + "/file2.js": ` + import * as foo from './file3.js'; + export default foo; + `, + "/file3.js": ` + export function a(input) { + return 42; + } + console.log('side effect'); + `, + "/package.json": ` + { + "name": "my-package", + "sideEffects": false + } + `, + }, + run: { + stdout: "side effect", + }, + }); itBundled("edgecase/BuiltinWithTrailingSlash", { files: { "/entry.js": ` From 8ba0791dc8585ea3cb3dc4df488b4fd91b576ec4 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 24 Jul 2024 01:26:09 -0700 Subject: [PATCH 049/123] Use one JSC::SourceProvider instead of 322 (#12761) --- src/bun.js/bindings/BunClientData.cpp | 13 +- src/bun.js/bindings/BunClientData.h | 2 +- .../bindings/InternalModuleRegistry.cpp | 1 - src/codegen/bundle-functions.ts | 227 ++++++++++++------ src/codegen/helpers.ts | 12 +- src/js/builtins/BunBuiltinNames.h | 6 +- 6 files changed, 183 insertions(+), 78 deletions(-) diff --git a/src/bun.js/bindings/BunClientData.cpp b/src/bun.js/bindings/BunClientData.cpp index 544827ae7e..3aaf738664 100644 --- a/src/bun.js/bindings/BunClientData.cpp +++ b/src/bun.js/bindings/BunClientData.cpp @@ -26,6 +26,8 @@ namespace WebCore { using namespace JSC; +RefPtr createBuiltinsSourceProvider(); + JSHeapData::JSHeapData(Heap& heap) : m_heapCellTypeForJSWorkerGlobalScope(JSC::IsoHeapCellType::Args()) , m_domBuiltinConstructorSpace ISO_SUBSPACE_INIT(heap, heap.cellHeapCellType, JSDOMBuiltinConstructorBase) @@ -38,15 +40,14 @@ JSHeapData::JSHeapData(Heap& heap) #define CLIENT_ISO_SUBSPACE_INIT(subspace) subspace(m_heapData->subspace) -JSVMClientData::JSVMClientData(VM& vm) - : m_builtinFunctions(vm) - , m_builtinNames(vm) +JSVMClientData::JSVMClientData(VM& vm, RefPtr sourceProvider) + : m_builtinNames(vm) + , m_builtinFunctions(vm, sourceProvider, m_builtinNames) , m_heapData(JSHeapData::ensureHeapData(vm.heap)) , CLIENT_ISO_SUBSPACE_INIT(m_domBuiltinConstructorSpace) , CLIENT_ISO_SUBSPACE_INIT(m_domConstructorSpace) , CLIENT_ISO_SUBSPACE_INIT(m_domNamespaceObjectSpace) , m_clientSubspaces(makeUnique()) - { } @@ -72,7 +73,8 @@ JSVMClientData::~JSVMClientData() } void JSVMClientData::create(VM* vm, void* bunVM) { - JSVMClientData* clientData = new JSVMClientData(*vm); + auto provider = WebCore::createBuiltinsSourceProvider(); + JSVMClientData* clientData = new JSVMClientData(*vm, provider); clientData->bunVM = bunVM; vm->deferredWorkTimer->onAddPendingWork = Bun::JSCTaskScheduler::onAddPendingWork; vm->deferredWorkTimer->onScheduleWorkSoon = Bun::JSCTaskScheduler::onScheduleWorkSoon; @@ -83,6 +85,7 @@ void JSVMClientData::create(VM* vm, void* bunVM) vm->heap.addMarkingConstraint(makeUnique(*vm, clientData->heapData())); vm->m_typedArrayController = adoptRef(new WebCoreTypedArrayController(true)); + clientData->builtinFunctions().exportNames(); } } // namespace WebCore \ No newline at end of file diff --git a/src/bun.js/bindings/BunClientData.h b/src/bun.js/bindings/BunClientData.h index e6862b9553..e0f8fb3034 100644 --- a/src/bun.js/bindings/BunClientData.h +++ b/src/bun.js/bindings/BunClientData.h @@ -76,7 +76,7 @@ class JSVMClientData : public JSC::VM::ClientData { WTF_MAKE_FAST_ALLOCATED; public: - explicit JSVMClientData(JSC::VM&); + explicit JSVMClientData(JSC::VM&, RefPtr); virtual ~JSVMClientData(); diff --git a/src/bun.js/bindings/InternalModuleRegistry.cpp b/src/bun.js/bindings/InternalModuleRegistry.cpp index 501c7a92aa..fdf5c3eb55 100644 --- a/src/bun.js/bindings/InternalModuleRegistry.cpp +++ b/src/bun.js/bindings/InternalModuleRegistry.cpp @@ -1,5 +1,4 @@ #include "InternalModuleRegistry.h" - #include "ZigGlobalObject.h" #include #include diff --git a/src/codegen/bundle-functions.ts b/src/codegen/bundle-functions.ts index dfc66b8368..79f4d2e956 100644 --- a/src/codegen/bundle-functions.ts +++ b/src/codegen/bundle-functions.ts @@ -8,13 +8,19 @@ // library, instead of RegExp hacks. // // For explanation on this, please nag @paperdave to write documentation on how everything works. +// +// The output is intended to be similar to what WebCore does internally with a couple differences: +// +// - We concatenate all the sources into one big string, which then createsa +// single JSC::SourceProvider and pass start/end positions to each function's +// JSC::SourceCode. JSC does this, but WebCore does not seem to. import { readdirSync, rmSync } from "fs"; import path from "path"; import { sliceSourceCode } from "./builtin-parser"; -import { applyGlobalReplacements, define } from "./replacements"; -import { cap, fmtCPPCharArray, low, writeIfNotChanged } from "./helpers"; import { createAssertClientJS, createLogClientJS } from "./client-js"; import { getJS2NativeDTS } from "./generate-js2native"; +import { addCPPCharArray, cap, low, writeIfNotChanged } from "./helpers"; +import { applyGlobalReplacements, define } from "./replacements"; const PARALLEL = false; const KEEP_TMP = true; @@ -52,6 +58,7 @@ interface BundledBuiltin { source: string; params: string[]; visibility: string; + sourceOffset: number; } /** @@ -228,6 +235,10 @@ $$capture_start$$(${fn.async ? "async " : ""}${ constructKind: fn.directives.ConstructKind ?? "None", isLinkTimeConstant: !!fn.directives.linkTimeConstant, intrinsic: fn.directives.intrinsic ?? "NoIntrinsic", + + // Not known yet. + sourceOffset: 0, + overriddenName: fn.directives.getter ? `"get ${fn.name}"_s` : fn.directives.overriddenName @@ -275,6 +286,34 @@ export async function bundleBuiltinFunctions({ requireTransformer }: BundleBuilt } } + let combinedSourceCodeChars = ""; + let combinedSourceCodeLength = 0; + // Compute source offsets + { + for (const { basename, functions } of files) { + for (const fn of functions) { + fn.sourceOffset = combinedSourceCodeLength; + combinedSourceCodeLength += fn.source.length; + if (combinedSourceCodeChars && !combinedSourceCodeChars.endsWith(",")) { + combinedSourceCodeChars += ","; + } + combinedSourceCodeChars += addCPPCharArray(fn.source, false); + + // If you want to see the individual function sources: + // if (true) { + // Bun.write(CODEGEN_DIR + "/functions/" + low(basename) + cap(fn.name) + ".js", fn.source + "\n"); + // } + } + } + } + + let additionalPrivateNames = new Set(); + + function privateName(name) { + additionalPrivateNames.add(name); + return "builtinNames." + name + "PrivateName()"; + } + // C++ codegen let bundledCPP = `// Generated by ${import.meta.path} namespace Zig { class GlobalObject; } @@ -283,48 +322,78 @@ export async function bundleBuiltinFunctions({ requireTransformer }: BundleBuilt #include "JSDOMGlobalObject.h" #include "WebCoreJSClientData.h" #include - + #include "BunBuiltinNames.h" + namespace WebCore { - + static const LChar combinedSourceCodeBuffer[${combinedSourceCodeLength + 1}] = { ${combinedSourceCodeChars}, 0 }; + static const std::span internalCombinedSource = { combinedSourceCodeBuffer, ${combinedSourceCodeLength} }; `; for (const { basename, functions } of files) { - bundledCPP += `/* ${basename}.ts */\n`; + bundledCPP += ` +#pragma mark ${basename} +`; + const lowerBasename = low(basename); for (const fn of functions) { - const [code, count] = fmtCPPCharArray(fn.source, true); - const name = `${lowerBasename}${cap(fn.name)}Code`; - bundledCPP += `// ${fn.name} - const JSC::ConstructAbility s_${name}ConstructAbility = JSC::ConstructAbility::${fn.constructAbility}; - const JSC::InlineAttribute s_${name}InlineAttribute = JSC::InlineAttribute::${ - fn.directives.alwaysInline ? "Always" : "None" - }; - const JSC::ConstructorKind s_${name}ConstructorKind = JSC::ConstructorKind::${fn.constructKind}; - const JSC::ImplementationVisibility s_${name}ImplementationVisibility = JSC::ImplementationVisibility::${ - fn.visibility - }; - const int s_${name}Length = ${fn.source.length}; - const JSC::Intrinsic s_${name}Intrinsic = JSC::NoIntrinsic; - const char s_${name}Bytes[${count}] = ${code}; - const char* s_${name} = s_${name}Bytes; - `; + const name = `${basename}${cap(fn.name)}`; + bundledCPP += ` +JSC::FunctionExecutable* ${lowerBasename}${cap(fn.name)}CodeGenerator(JSC::VM& vm) +{ + auto &builtins = static_cast(vm.clientData)->builtinFunctions().${lowerBasename}Builtins(); + auto *executable = builtins.${lowerBasename}${cap(fn.name)}CodeExecutable(); + return executable->link(vm, nullptr, builtins.${lowerBasename}${cap(fn.name)}CodeSource(), std::nullopt, JSC::NoIntrinsic); +} +`; } - bundledCPP += `#define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \\ - JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \\ - {\\ - JSVMClientData* clientData = static_cast(vm.clientData); \\ - return clientData->builtinFunctions().${lowerBasename}Builtins().codeName##Executable()->link(vm, nullptr, clientData->builtinFunctions().${lowerBasename}Builtins().codeName##Source(), std::nullopt, s_##codeName##Intrinsic); \\ + } + + const initializeSourceCodeFn = (fn: BundledBuiltin, basename: string) => { + const name = `${low(basename)}${cap(fn.name)}CodeSource`; + return `m_${name}(SourceCode(sourceProvider.copyRef(), ${fn.sourceOffset}, ${fn.source.length + fn.sourceOffset}, 1, 1))`; + }; + for (const { basename, internal, functions } of files) { + bundledCPP += ` +#pragma mark ${basename} + +${basename}BuiltinsWrapper::${basename}BuiltinsWrapper(JSC::VM& vm, RefPtr sourceProvider, BunBuiltinNames &builtinNames) + : m_vm(vm)`; + + if (internal) { + bundledCPP += `, ${functions.map(fn => `m_${fn.name}PrivateName(${privateName(fn.name)})`).join(",\n ")}`; } - WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(DEFINE_BUILTIN_GENERATOR) - #undef DEFINE_BUILTIN_GENERATOR - - `; + bundledCPP += `, ${functions.map(fn => initializeSourceCodeFn(fn, basename)).join(",\n ")} {} +`; } + bundledCPP += ` +RefPtr createBuiltinsSourceProvider() { + return JSC::StringSourceProvider::create(StringImpl::createWithoutCopying(internalCombinedSource), SourceOrigin(), String(), SourceTaintedOrigin()); +} +`; + + bundledCPP += ` +JSBuiltinFunctions::JSBuiltinFunctions(JSC::VM& vm, RefPtr provider, BunBuiltinNames& builtinNames) : m_vm(vm), + ${files.map(({ basename }) => `m_${low(basename)}Builtins(vm, provider, builtinNames)`).join(", ")} +{} + +void JSBuiltinFunctions::exportNames() { +`; + + for (const { basename, internal } of files) { + if (internal) { + bundledCPP += ` m_${low(basename)}Builtins.exportNames();\n`; + } + } + + bundledCPP += ` +} + +`; + bundledCPP += ` - JSBuiltinInternalFunctions::JSBuiltinInternalFunctions(JSC::VM& vm) - : m_vm(vm) +JSBuiltinInternalFunctions::JSBuiltinInternalFunctions(JSC::VM& vm) : m_vm(vm) `; for (const { basename, internal } of files) { @@ -333,10 +402,9 @@ export async function bundleBuiltinFunctions({ requireTransformer }: BundleBuilt } } - bundledCPP += ` - { - UNUSED_PARAM(vm); - } + bundledCPP += `{ + UNUSED_PARAM(vm); + } template void JSBuiltinInternalFunctions::visit(Visitor& visitor) @@ -417,12 +485,10 @@ export async function bundleBuiltinFunctions({ requireTransformer }: BundleBuilt const name = `${lowerBasename}${cap(fn.name)}Code`; bundledHeader += `// ${fn.name} #define WEBCORE_BUILTIN_${basename.toUpperCase()}_${fn.name.toUpperCase()} 1 - extern const char* s_${name}; - extern const int s_${name}Length; - extern const JSC::ConstructAbility s_${name}ConstructAbility; - extern const JSC::InlineAttribute s_${name}InlineAttribute; - extern const JSC::ConstructorKind s_${name}ConstructorKind; - extern const JSC::ImplementationVisibility s_${name}ImplementationVisibility; + static constexpr JSC::ConstructAbility s_${name}ConstructAbility = JSC::ConstructAbility::${fn.constructAbility}; + static constexpr JSC::InlineAttribute s_${name}InlineAttribute = JSC::InlineAttribute::${fn.directives.alwaysInline ? "Always" : "None"}; + static constexpr JSC::ConstructorKind s_${name}ConstructorKind = JSC::ConstructorKind::${fn.constructKind}; + static constexpr JSC::ImplementationVisibility s_${name}ImplementationVisibility = JSC::ImplementationVisibility::${fn.visibility}; `; } @@ -450,14 +516,7 @@ export async function bundleBuiltinFunctions({ requireTransformer }: BundleBuilt class ${basename}BuiltinsWrapper : private JSC::WeakHandleOwner { public: - explicit ${basename}BuiltinsWrapper(JSC::VM& vm) - : m_vm(vm) - WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(INITIALIZE_BUILTIN_NAMES) - #define INITIALIZE_BUILTIN_SOURCE_MEMBERS(name, functionName, overriddenName, length) , m_##name##Source(JSC::makeSource(StringImpl::createWithoutCopying({reinterpret_cast(s_##name), static_cast(length)}), { }, JSC::SourceTaintedOrigin::Untainted)) - WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(INITIALIZE_BUILTIN_SOURCE_MEMBERS) - #undef INITIALIZE_BUILTIN_SOURCE_MEMBERS - { - } + explicit ${basename}BuiltinsWrapper(JSC::VM& vm, RefPtr sourceProvider, BunBuiltinNames &builtinNames); #define EXPOSE_BUILTIN_EXECUTABLES(name, functionName, overriddenName, length) \\ JSC::UnlinkedFunctionExecutable* name##Executable(); \\ @@ -544,25 +603,9 @@ export async function bundleBuiltinFunctions({ requireTransformer }: BundleBuilt } bundledHeader += `class JSBuiltinFunctions { public: - explicit JSBuiltinFunctions(JSC::VM& vm) - : m_vm(vm) - `; - - for (const { basename } of files) { - bundledHeader += ` , m_${low(basename)}Builtins(m_vm)\n`; - } - - bundledHeader += ` - { - `; - - for (const { basename, internal } of files) { - if (internal) { - bundledHeader += ` m_${low(basename)}Builtins.exportNames();\n`; - } - } - - bundledHeader += ` } + explicit JSBuiltinFunctions(JSC::VM& vm, RefPtr provider, BunBuiltinNames &builtinNames); + void exportNames(); + `; for (const { basename } of files) { @@ -613,7 +656,53 @@ export async function bundleBuiltinFunctions({ requireTransformer }: BundleBuilt } // namespace WebCore `; + // Handle builtin names + { + const BunBuiltinNamesHeader = require("fs").readFileSync( + path.join(import.meta.dir, "../js/builtins/BunBuiltinNames.h"), + "utf8", + ); + let definedBuiltinNamesStartI = BunBuiltinNamesHeader.indexOf( + "#define BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME", + ); + let definedBuiltinNamesMacroEndI = BunBuiltinNamesHeader.indexOf( + "--- END of BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME ---", + ); + const definedBuiltinNames = BunBuiltinNamesHeader.slice(definedBuiltinNamesStartI, definedBuiltinNamesMacroEndI) + .split("\n") + .map(x => x.trim()) + .filter(x => x.startsWith("macro(")) + .map(x => x.slice(x.indexOf("(") + 1, x.indexOf(")"))) + .map(x => x.trim()) + .sort(); + const uniqueDefinedBuiltinNames = new Set(); + for (let name of definedBuiltinNames) { + const prevSize = uniqueDefinedBuiltinNames.size; + uniqueDefinedBuiltinNames.add(name); + if (uniqueDefinedBuiltinNames.size === prevSize) { + throw new Error(`Duplicate private name "${name}" in BunBuiltinNames.h`); + } + } + for (let additionalPrivateName of additionalPrivateNames) { + if (uniqueDefinedBuiltinNames.has(additionalPrivateName)) { + additionalPrivateNames.delete(additionalPrivateName); + } + } + + let additionalPrivateNamesHeader = `// Generated by ${import.meta.path} +#pragma once + +#ifndef BUN_ADDITIONAL_BUILTIN_NAMES +#define BUN_ADDITIONAL_BUILTIN_NAMES(macro) \\ + ${Array.from(additionalPrivateNames) + .map(x => `macro(${x})`) + .join(" \\\n ")} +#endif +`; + + writeIfNotChanged(path.join(CODEGEN_DIR, "BunBuiltinNames+extras.h"), additionalPrivateNamesHeader); + } writeIfNotChanged(path.join(CODEGEN_DIR, "WebCoreJSBuiltins.h"), bundledHeader); writeIfNotChanged(path.join(CODEGEN_DIR, "WebCoreJSBuiltins.cpp"), bundledCPP); diff --git a/src/codegen/helpers.ts b/src/codegen/helpers.ts index 04f436c25f..a97007b767 100644 --- a/src/codegen/helpers.ts +++ b/src/codegen/helpers.ts @@ -18,7 +18,17 @@ export function fmtCPPCharArray(str: string, nullTerminated: boolean = true) { .join(",") + (nullTerminated ? ",0" : "") + "}"; - return [chars, normalized.length + (nullTerminated ? 1 : 0)]; + return [chars, normalized.length + (nullTerminated ? 1 : 0)] as const; +} + +export function addCPPCharArray(str: string, nullTerminated: boolean = true) { + const normalized = str.trim() + "\n"; + return ( + normalized + .split("") + .map(a => a.charCodeAt(0)) + .join(",") + (nullTerminated ? ",0" : "") + ); } export function declareASCIILiteral(name: string, value: string) { diff --git a/src/js/builtins/BunBuiltinNames.h b/src/js/builtins/BunBuiltinNames.h index 226a340ae3..1ea5e53986 100644 --- a/src/js/builtins/BunBuiltinNames.h +++ b/src/js/builtins/BunBuiltinNames.h @@ -14,6 +14,7 @@ #include #include #include +#include "BunBuiltinNames+extras.h" namespace WebCore { @@ -194,7 +195,6 @@ using namespace JSC; macro(requireESM) \ macro(requireMap) \ macro(requireNativeModule) \ - macro(resolve) \ macro(resolveSync) \ macro(resume) \ macro(self) \ @@ -250,6 +250,8 @@ using namespace JSC; macro(writeRequests) \ macro(writing) \ macro(written) \ + BUN_ADDITIONAL_BUILTIN_NAMES(macro) +// --- END of BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME --- class BunBuiltinNames { public: @@ -268,6 +270,8 @@ public: BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(DECLARE_BUILTIN_IDENTIFIER_ACCESSOR) + const JSC::Identifier& resolvePublicName() const { return m_vm.propertyNames->resolve;} + private: JSC::VM& m_vm; BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(DECLARE_BUILTIN_NAMES) From c37891471a2690a7a47154c00aeac841e6ee86ee Mon Sep 17 00:00:00 2001 From: ippsav <69125922+ippsav@users.noreply.github.com> Date: Wed, 24 Jul 2024 09:30:11 +0100 Subject: [PATCH 050/123] Fix alignment calculation in Zone.create function (#12748) --- src/heap_breakdown.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/heap_breakdown.zig b/src/heap_breakdown.zig index 747d2a769d..fc934545ab 100644 --- a/src/heap_breakdown.zig +++ b/src/heap_breakdown.zig @@ -97,8 +97,10 @@ pub const Zone = opaque { /// Create a single-item pointer with initialized data. pub inline fn create(zone: *Zone, comptime T: type, data: T) *T { + const align_of_t: usize = @alignOf(T); + const log2_align_of_t = @ctz(align_of_t); const ptr: *T = @alignCast(@ptrCast( - rawAlloc(zone, @sizeOf(T), @alignOf(T), @returnAddress()) orelse bun.outOfMemory(), + rawAlloc(zone, @sizeOf(T), log2_align_of_t, @returnAddress()) orelse bun.outOfMemory(), )); ptr.* = data; return ptr; From 57c6a7db35d42b6bc088982c53b68bca630212e5 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 24 Jul 2024 01:30:31 -0700 Subject: [PATCH 051/123] libdeflate (#12741) --- .gitmodules | 4 + CMakeLists.txt | 14 + Dockerfile | 25 ++ LICENSE.md | 2 + bench/gzip/bun.js | 39 ++- bench/gzip/bun.lockb | Bin 0 -> 1254 bytes bench/gzip/node.mjs | 11 +- bench/gzip/package.json | 3 + packages/bun-types/bun.d.ts | 27 +- packages/bun-uws/src/PerMessageDeflate.h | 19 +- scripts/all-dependencies.ps1 | 4 + scripts/all-dependencies.sh | 3 +- scripts/build-libdeflate.ps1 | 16 + scripts/build-libdeflate.sh | 10 + scripts/write-versions.sh | 2 + src/bun.js/api/BunObject.zig | 380 ++++++++++++++++------ src/bun.js/bindings/BunProcess.cpp | 4 +- src/bun.js/bindings/headers-handwritten.h | 1 + src/bun.js/javascript.zig | 3 + src/bun.js/node/types.zig | 1 + src/bun.js/rare_data.zig | 11 + src/bun.zig | 1 + src/deps/libdeflate | 1 + src/deps/libdeflate.zig | 149 +++++++++ src/feature_flags.zig | 11 + src/generated_versions_list.zig | 1 + src/http.zig | 137 ++++++-- src/install/extract_tarball.zig | 77 ++++- src/install/install.zig | 2 +- test/js/node/zlib/zlib.test.js | 35 +- test/js/web/fetch/fetch.stream.test.ts | 29 +- 31 files changed, 838 insertions(+), 184 deletions(-) create mode 100755 bench/gzip/bun.lockb create mode 100644 scripts/build-libdeflate.ps1 create mode 100755 scripts/build-libdeflate.sh create mode 160000 src/deps/libdeflate create mode 100644 src/deps/libdeflate.zig diff --git a/.gitmodules b/.gitmodules index 98845d5097..c5069240a4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -82,3 +82,7 @@ url = https://github.com/oven-sh/zig depth = 1 shallow = true fetchRecurseSubmodules = false +[submodule "src/deps/libdeflate"] +path = src/deps/libdeflate +url = https://github.com/ebiggers/libdeflate +ignore = "dirty" diff --git a/CMakeLists.txt b/CMakeLists.txt index e18c6d831a..2045d03341 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -310,6 +310,7 @@ endif() # -- Build Flags -- option(USE_STATIC_SQLITE "Statically link SQLite?" ${DEFAULT_ON_UNLESS_APPLE}) option(USE_CUSTOM_ZLIB "Use Bun's recommended version of zlib" ON) +option(USE_CUSTOM_LIBDEFLATE "Use Bun's recommended version of libdeflate" ON) option(USE_CUSTOM_BORINGSSL "Use Bun's recommended version of BoringSSL" ON) option(USE_CUSTOM_LIBARCHIVE "Use Bun's recommended version of libarchive" ON) option(USE_CUSTOM_MIMALLOC "Use Bun's recommended version of Mimalloc" ON) @@ -1358,6 +1359,19 @@ else() target_link_libraries(${bun} PRIVATE LibArchive::LibArchive) endif() +if(USE_CUSTOM_LIBDEFLATE) + include_directories(${BUN_DEPS_DIR}/libdeflate) + + if(WIN32) + target_link_libraries(${bun} PRIVATE "${BUN_DEPS_OUT_DIR}/deflate.lib") + else() + target_link_libraries(${bun} PRIVATE "${BUN_DEPS_OUT_DIR}/libdeflate.a") + endif() +else() + find_package(LibDeflate REQUIRED) + target_link_libraries(${bun} PRIVATE LibDeflate::LibDeflate) +endif() + if(USE_CUSTOM_MIMALLOC) include_directories(${BUN_DEPS_DIR}/mimalloc/include) diff --git a/Dockerfile b/Dockerfile index 6fa2ddf48b..7b707f03e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -263,6 +263,27 @@ RUN --mount=type=cache,target=${CCACHE_DIR} \ && bash ./scripts/build-zlib.sh && rm -rf src/deps/zlib scripts +FROM bun-base as libdeflate + +ARG BUN_DIR +ARG CPU_TARGET +ENV CPU_TARGET=${CPU_TARGET} +ARG CCACHE_DIR=/ccache +ENV CCACHE_DIR=${CCACHE_DIR} + +COPY Makefile ${BUN_DIR}/Makefile +COPY CMakeLists.txt ${BUN_DIR}/CMakeLists.txt +COPY scripts ${BUN_DIR}/scripts +COPY src/deps/libdeflate ${BUN_DIR}/src/deps/libdeflate +COPY package.json bun.lockb Makefile .gitmodules ${BUN_DIR}/ + +WORKDIR $BUN_DIR + +RUN --mount=type=cache,target=${CCACHE_DIR} \ + cd $BUN_DIR \ + && bash ./scripts/build-libdeflate.sh && rm -rf src/deps/libdeflate scripts + + FROM bun-base as libarchive ARG BUN_DIR @@ -412,6 +433,9 @@ COPY src ${BUN_DIR}/src COPY CMakeLists.txt ${BUN_DIR}/CMakeLists.txt COPY src/deps/boringssl/include ${BUN_DIR}/src/deps/boringssl/include +# for uWebSockets +COPY src/deps/libdeflate ${BUN_DIR}/src/deps/libdeflate + ARG CCACHE_DIR=/ccache ENV CCACHE_DIR=${CCACHE_DIR} @@ -516,6 +540,7 @@ COPY src/symbols.dyn src/linker.lds ${BUN_DIR}/src/ COPY CMakeLists.txt ${BUN_DIR}/CMakeLists.txt COPY --from=zlib ${BUN_DEPS_OUT_DIR}/* ${BUN_DEPS_OUT_DIR}/ +COPY --from=libdeflate ${BUN_DEPS_OUT_DIR}/* ${BUN_DEPS_OUT_DIR}/ COPY --from=libarchive ${BUN_DEPS_OUT_DIR}/* ${BUN_DEPS_OUT_DIR}/ COPY --from=boringssl ${BUN_DEPS_OUT_DIR}/* ${BUN_DEPS_OUT_DIR}/ COPY --from=lolhtml ${BUN_DEPS_OUT_DIR}/* ${BUN_DEPS_OUT_DIR}/ diff --git a/LICENSE.md b/LICENSE.md index 719bf08d76..4cc901b7bc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -34,6 +34,8 @@ Bun statically links these libraries: | [`c-ares`](https://github.com/c-ares/c-ares) | MIT licensed | | [`libicu`](https://github.com/unicode-org/icu) 72 | [license here](https://github.com/unicode-org/icu/blob/main/icu4c/LICENSE) | | [`libbase64`](https://github.com/aklomp/base64/blob/master/LICENSE) | BSD 2-Clause | +| [`libuv`](https://github.com/libuv/libuv) (on Windows) | MIT | +| [`libdeflate`](https://github.com/ebiggers/libdeflate) | MIT | | A fork of [`uWebsockets`](https://github.com/jarred-sumner/uwebsockets) | Apache 2.0 licensed | | Parts of [Tigerbeetle's IO code](https://github.com/tigerbeetle/tigerbeetle/blob/532c8b70b9142c17e07737ab6d3da68d7500cbca/src/io/windows.zig#L1) | Apache 2.0 licensed | diff --git a/bench/gzip/bun.js b/bench/gzip/bun.js index 1c5cdcaddd..6b69ae1fbb 100644 --- a/bench/gzip/bun.js +++ b/bench/gzip/bun.js @@ -1,20 +1,43 @@ -import { run, bench } from "mitata"; +import { run, bench, group } from "mitata"; import { gzipSync, gunzipSync } from "bun"; -const data = new TextEncoder().encode("Hello World!".repeat(9999)); +const data = await Bun.file(require.resolve("@babel/standalone/babel.min.js")).arrayBuffer(); const compressed = gzipSync(data); -bench(`roundtrip - "Hello World!".repeat(9999))`, () => { - gunzipSync(gzipSync(data)); +const libraries = ["zlib"]; +if (Bun.semver.satisfies(Bun.version.replaceAll("-debug", ""), ">=1.1.21")) { + libraries.push("libdeflate"); +} +const options = { library: undefined }; +const benchFn = (name, fn) => { + if (libraries.length > 1) { + group(name, () => { + for (const library of libraries) { + bench(library, () => { + options.library = library; + fn(); + }); + } + }); + } else { + options.library = libraries[0]; + bench(name, () => { + fn(); + }); + } +}; + +benchFn(`roundtrip - @babel/standalone/babel.min.js`, () => { + gunzipSync(gzipSync(data, options), options); }); -bench(`gzipSync("Hello World!".repeat(9999)))`, () => { - gzipSync(data); +benchFn(`gzipSync(@babel/standalone/babel.min.js`, () => { + gzipSync(data, options); }); -bench(`gunzipSync("Hello World!".repeat(9999)))`, () => { - gunzipSync(compressed); +benchFn(`gunzipSync(@babel/standalone/babel.min.js`, () => { + gunzipSync(compressed, options); }); await run(); diff --git a/bench/gzip/bun.lockb b/bench/gzip/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..96feac42873a3135230446e8084058ca8334a8a9 GIT binary patch literal 1254 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_p)&jeLAw zt2WizKHi?}b11vla)JI_p1kg~EfvBBTi57J3u6H)0s@d)5a57NaJm7?uYxIHNJ`C1 z&VY&vGB7mssh?w*^~y6=y1vSI#;a;mMyMRQ6wD%^*$l2g^Vxy41Q2tPU?9j1$nHSp z|KtCMEJ=d7IF$m;V`PAsi)=K@oP_OfoaWDbnR!C>?c%6{mkKHT(*j&COY1M~@Y3CL zG-K`8+exRdJGJ_THMoD?_Ueki)H91r9$u+KColRi@9xV)HVmib&V-+^k}~{GxPy47K{Y XNE&s`^^8pP3=Q;3(yJihKKMug_SeLD literal 0 HcmV?d00001 diff --git a/bench/gzip/node.mjs b/bench/gzip/node.mjs index 0d6ea51249..d7a1abade7 100644 --- a/bench/gzip/node.mjs +++ b/bench/gzip/node.mjs @@ -1,19 +1,22 @@ import { run, bench } from "mitata"; import { gzipSync, gunzipSync } from "zlib"; +import { createRequire } from "module"; +import { readFileSync } from "fs"; -const data = new TextEncoder().encode("Hello World!".repeat(9999)); +const require = createRequire(import.meta.url); +const data = readFileSync(require.resolve("@babel/standalone/babel.min.js")); const compressed = gzipSync(data); -bench(`roundtrip - "Hello World!".repeat(9999))`, () => { +bench(`roundtrip - @babel/standalone/babel.min.js)`, () => { gunzipSync(gzipSync(data)); }); -bench(`gzipSync("Hello World!".repeat(9999)))`, () => { +bench(`gzipSync(@babel/standalone/babel.min.js))`, () => { gzipSync(data); }); -bench(`gunzipSync("Hello World!".repeat(9999)))`, () => { +bench(`gunzipSync(@babel/standalone/babel.min.js))`, () => { gunzipSync(compressed); }); diff --git a/bench/gzip/package.json b/bench/gzip/package.json index f5c377686b..49e6c3a890 100644 --- a/bench/gzip/package.json +++ b/bench/gzip/package.json @@ -7,5 +7,8 @@ "bench:node": "$NODE node.mjs", "bench:deno": "$DENO run -A --unstable deno.js", "bench": "bun run bench:bun && bun run bench:node && bun run bench:deno" + }, + "dependencies": { + "@babel/standalone": "7.24.10" } } diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 8307d88ec1..6e7309a89d 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -3481,6 +3481,13 @@ declare module "bun" { * Filtered data consists mostly of small values with a somewhat random distribution. */ strategy?: number; + + library?: "zlib"; + } + + interface LibdeflateCompressionOptions { + level?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + library?: "libdeflate"; } /** @@ -3489,26 +3496,38 @@ declare module "bun" { * @param options Compression options to use * @returns The output buffer with the compressed data */ - function deflateSync(data: Uint8Array | string | ArrayBuffer, options?: ZlibCompressionOptions): Uint8Array; + function deflateSync( + data: Uint8Array | string | ArrayBuffer, + options?: ZlibCompressionOptions | LibdeflateCompressionOptions, + ): Uint8Array; /** * Compresses a chunk of data with `zlib` GZIP algorithm. * @param data The buffer of data to compress * @param options Compression options to use * @returns The output buffer with the compressed data */ - function gzipSync(data: Uint8Array | string | ArrayBuffer, options?: ZlibCompressionOptions): Uint8Array; + function gzipSync( + data: Uint8Array | string | ArrayBuffer, + options?: ZlibCompressionOptions | LibdeflateCompressionOptions, + ): Uint8Array; /** * Decompresses a chunk of data with `zlib` INFLATE algorithm. * @param data The buffer of data to decompress * @returns The output buffer with the decompressed data */ - function inflateSync(data: Uint8Array | string | ArrayBuffer): Uint8Array; + function inflateSync( + data: Uint8Array | string | ArrayBuffer, + options?: ZlibCompressionOptions | LibdeflateCompressionOptions, + ): Uint8Array; /** * Decompresses a chunk of data with `zlib` GUNZIP algorithm. * @param data The buffer of data to decompress * @returns The output buffer with the decompressed data */ - function gunzipSync(data: Uint8Array | string | ArrayBuffer): Uint8Array; + function gunzipSync( + data: Uint8Array | string | ArrayBuffer, + options?: ZlibCompressionOptions | LibdeflateCompressionOptions, + ): Uint8Array; type Target = /** diff --git a/packages/bun-uws/src/PerMessageDeflate.h b/packages/bun-uws/src/PerMessageDeflate.h index 17832c7165..e4ebaf0ac5 100644 --- a/packages/bun-uws/src/PerMessageDeflate.h +++ b/packages/bun-uws/src/PerMessageDeflate.h @@ -20,6 +20,8 @@ #ifndef UWS_PERMESSAGEDEFLATE_H #define UWS_PERMESSAGEDEFLATE_H +#define UWS_USE_LIBDEFLATE 1 + #include #include @@ -134,6 +136,9 @@ struct ZlibContext { struct DeflationStream { z_stream deflationStream = {}; +#ifdef UWS_USE_LIBDEFLATE + unsigned char reset_buffer[4096 + 1]; +#endif DeflationStream(CompressOptions compressOptions) { @@ -154,13 +159,11 @@ struct DeflationStream { /* Run a fast path in case of shared_compressor */ if (reset) { size_t written = 0; - static unsigned char buf[1024 + 1]; - - written = libdeflate_deflate_compress(zlibContext->compressor, raw.data(), raw.length(), buf, 1024); + written = libdeflate_deflate_compress(zlibContext->compressor, raw.data(), raw.length(), reset_buffer, 4096); if (written) { - memcpy(&buf[written], "\x00", 1); - return std::string_view((char *) buf, written + 1); + memcpy(&reset_buffer[written], "\x00", 1); + return std::string_view((char *) reset_buffer, written + 1); } } #endif @@ -214,6 +217,9 @@ struct DeflationStream { struct InflationStream { z_stream inflationStream = {}; +#ifdef UWS_USE_LIBDEFLATE + char buf[4096]; +#endif InflationStream(CompressOptions compressOptions) { /* Inflation windowBits are the top 8 bits of the 16 bit compressOptions */ @@ -230,13 +236,12 @@ struct InflationStream { #ifdef UWS_USE_LIBDEFLATE /* Try fast path first */ size_t written = 0; - static char buf[1024]; /* We have to pad 9 bytes and restore those bytes when done since 9 is more than 6 of next WebSocket message */ char tmp[9]; memcpy(tmp, (char *) compressed.data() + compressed.length(), 9); memcpy((char *) compressed.data() + compressed.length(), "\x00\x00\xff\xff\x01\x00\x00\xff\xff", 9); - libdeflate_result res = libdeflate_deflate_decompress(zlibContext->decompressor, compressed.data(), compressed.length() + 9, buf, 1024, &written); + libdeflate_result res = libdeflate_deflate_decompress(zlibContext->decompressor, compressed.data(), compressed.length() + 9, buf, 4096, &written); memcpy((char *) compressed.data() + compressed.length(), tmp, 9); if (res == 0) { diff --git a/scripts/all-dependencies.ps1 b/scripts/all-dependencies.ps1 index 23838d1a99..24ac5513d8 100755 --- a/scripts/all-dependencies.ps1 +++ b/scripts/all-dependencies.ps1 @@ -79,6 +79,10 @@ Build-Dependency ` -Script "lshpack" ` -Outputs @("lshpack.lib") +Build-Dependency ` + -Script "libdeflate" ` + -Outputs @("deflate.lib") + if (!($Script:DidAnything)) { Write-Host "(run with -Force to rebuild all)" } diff --git a/scripts/all-dependencies.sh b/scripts/all-dependencies.sh index e3ddd6c476..50a22fe8f8 100755 --- a/scripts/all-dependencies.sh +++ b/scripts/all-dependencies.sh @@ -3,7 +3,7 @@ set -eo pipefail source "$(dirname -- "${BASH_SOURCE[0]}")/env.sh" if [[ "$CI" ]]; then - $(dirname -- "${BASH_SOURCE[0]}")/update-submodules.sh + $(dirname -- "${BASH_SOURCE[0]}")/update-submodules.sh fi FORCE= @@ -92,6 +92,7 @@ dep mimalloc mimalloc libmimalloc.a libmimalloc.o dep tinycc tinycc libtcc.a dep zlib zlib libz.a dep zstd zstd libzstd.a +dep libdeflate libdeflate libdeflate.a dep ls-hpack lshpack liblshpack.a if [ "$BUILT_ANY" -eq 0 ]; then diff --git a/scripts/build-libdeflate.ps1 b/scripts/build-libdeflate.ps1 new file mode 100644 index 0000000000..1d9b9b957b --- /dev/null +++ b/scripts/build-libdeflate.ps1 @@ -0,0 +1,16 @@ +$ErrorActionPreference = 'Stop' # Setting strict mode, similar to 'set -euo pipefail' in bash +. (Join-Path $PSScriptRoot "env.ps1") + +Push-Location (Join-Path $BUN_DEPS_DIR 'libdeflate') +try { + Remove-Item CMakeCache.txt, CMakeFiles, build -Recurse -ErrorAction SilentlyContinue + mkdir -Force build + + Run cmake -S "." -B build @CMAKE_FLAGS -DLIBDEFLATE_BUILD_STATIC_LIB=ON -DLIBDEFLATE_BUILD_SHARED_LIB=OFF -DLIBDEFLATE_BUILD_GZIP=OFF + Run cmake --build build --clean-first --config Release + + # In https://github.com/ebiggers/libdeflate/releases/tag/v1.20, it's outputting libdeflate.a even on Windows + Copy-Item build/deflatestatic.lib $BUN_DEPS_OUT_DIR/deflate.lib + Write-Host "-> deflate.lib" +} finally { Pop-Location } + diff --git a/scripts/build-libdeflate.sh b/scripts/build-libdeflate.sh new file mode 100755 index 0000000000..0bfd2cb565 --- /dev/null +++ b/scripts/build-libdeflate.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -exo pipefail +source $(dirname -- "${BASH_SOURCE[0]}")/env.sh + +mkdir -p $BUN_DEPS_OUT_DIR +cd $BUN_DEPS_DIR/libdeflate +rm -rf build CMakeCache.txt CMakeFiles +cmake "${CMAKE_FLAGS[@]}" -DLIBDEFLATE_BUILD_STATIC_LIB=ON -DLIBDEFLATE_BUILD_SHARED_LIB=OFF -DLIBDEFLATE_BUILD_GZIP=OFF -B build -S . -G Ninja +ninja libdeflate.a -C build +cp build/libdeflate.a $BUN_DEPS_OUT_DIR/libdeflate.a diff --git a/scripts/write-versions.sh b/scripts/write-versions.sh index 74bc29c68c..389acf702c 100755 --- a/scripts/write-versions.sh +++ b/scripts/write-versions.sh @@ -12,6 +12,7 @@ TINYCC=$(git rev-parse HEAD:./src/deps/tinycc) C_ARES=$(git rev-parse HEAD:./src/deps/c-ares) ZSTD=$(git rev-parse HEAD:./src/deps/zstd) LSHPACK=$(git rev-parse HEAD:./src/deps/ls-hpack) +LIBDEFLATE=$(git rev-parse HEAD:./src/deps/libdeflate) rm -rf src/generated_versions_list.zig echo "// AUTO-GENERATED FILE. Created via .scripts/write-versions.sh" >src/generated_versions_list.zig @@ -26,6 +27,7 @@ echo "pub const zlib = \"$ZLIB_VERSION\";" >>src/generated_versions_list.zig echo "pub const tinycc = \"$TINYCC\";" >>src/generated_versions_list.zig echo "pub const lolhtml = \"$LOLHTML\";" >>src/generated_versions_list.zig echo "pub const c_ares = \"$C_ARES\";" >>src/generated_versions_list.zig +echo "pub const libdeflate = \"$LIBDEFLATE\";" >>src/generated_versions_list.zig echo "pub const zstd = \"$ZSTD\";" >>src/generated_versions_list.zig echo "pub const lshpack = \"$LSHPACK\";" >>src/generated_versions_list.zig echo "" >>src/generated_versions_list.zig diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index ce7a0f7b73..5748039d20 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -12,14 +12,14 @@ pub const BunObject = struct { pub const allocUnsafe = toJSCallback(Bun.allocUnsafe); pub const build = toJSCallback(Bun.JSBundler.buildFn); pub const connect = toJSCallback(JSC.wrapStaticMethod(JSC.API.Listener, "connect", false)); - pub const deflateSync = toJSCallback(JSC.wrapStaticMethod(JSZlib, "deflateSync", true)); + pub const deflateSync = toJSCallback(JSZlib.deflateSync); pub const file = toJSCallback(WebCore.Blob.constructBunFile); pub const gc = toJSCallback(Bun.runGC); pub const generateHeapSnapshot = toJSCallback(Bun.generateHeapSnapshot); - pub const gunzipSync = toJSCallback(JSC.wrapStaticMethod(JSZlib, "gunzipSync", true)); - pub const gzipSync = toJSCallback(JSC.wrapStaticMethod(JSZlib, "gzipSync", true)); + pub const gunzipSync = toJSCallback(JSZlib.gunzipSync); + pub const gzipSync = toJSCallback(JSZlib.gzipSync); pub const indexOfLine = toJSCallback(Bun.indexOfLine); - pub const inflateSync = toJSCallback(JSC.wrapStaticMethod(JSZlib, "inflateSync", true)); + pub const inflateSync = toJSCallback(JSZlib.inflateSync); pub const jest = toJSCallback(@import("../test/jest.zig").Jest.call); pub const listen = toJSCallback(JSC.wrapStaticMethod(JSC.API.Listener, "listen", false)); pub const udpSocket = toJSCallback(JSC.wrapStaticMethod(JSC.API.UDPSocket, "udpSocket", false)); @@ -4647,27 +4647,220 @@ pub const JSZlib = struct { reader.list.deinit(reader.allocator); reader.deinit(); } - + export fn global_deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void { + comptime assert(bun.use_mimalloc); + bun.Mimalloc.mi_free(ctx); + } export fn compressor_deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void { var compressor: *zlib.ZlibCompressorArrayList = bun.cast(*zlib.ZlibCompressorArrayList, ctx.?); compressor.list.deinit(compressor.allocator); compressor.deinit(); } + const Library = enum { + zlib, + libdeflate, + + pub const map = bun.ComptimeEnumMap(Library); + }; + + // This has to be `inline` due to the callframe. + inline fn getOptions(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) ?struct { JSC.Node.StringOrBuffer, ?JSValue } { + const arguments = callframe.arguments(2).slice(); + const buffer_value = if (arguments.len > 0) arguments[0] else JSC.JSValue.jsUndefined(); + const options_val: ?JSValue = + if (arguments.len > 1 and arguments[1].isObject()) + arguments[1] + else if (arguments.len > 1 and !arguments[1].isUndefined()) { + globalThis.throwInvalidArguments("Expected options to be an object", .{}); + return null; + } else null; + + if (JSC.Node.StringOrBuffer.fromJS(globalThis, bun.default_allocator, buffer_value)) |buffer| { + return .{ buffer, options_val }; + } + + globalThis.throwInvalidArguments("Expected buffer to be a string or buffer", .{}); + return null; + } + pub fn gzipSync( globalThis: *JSGlobalObject, - buffer: JSC.Node.StringOrBuffer, - options_val_: ?JSValue, + callframe: *JSC.CallFrame, ) JSValue { - return gzipOrDeflateSync(globalThis, buffer, options_val_, true); + const buffer, const options_val = getOptions(globalThis, callframe) orelse return .zero; + defer buffer.deinit(); + return gzipOrDeflateSync(globalThis, buffer, options_val, true); + } + + pub fn inflateSync( + globalThis: *JSGlobalObject, + callframe: *JSC.CallFrame, + ) JSValue { + const buffer, const options_val = getOptions(globalThis, callframe) orelse return .zero; + defer buffer.deinit(); + return gunzipOrInflateSync(globalThis, buffer, options_val, false); } pub fn deflateSync( + globalThis: *JSGlobalObject, + callframe: *JSC.CallFrame, + ) JSValue { + const buffer, const options_val = getOptions(globalThis, callframe) orelse return .zero; + defer buffer.deinit(); + return gzipOrDeflateSync(globalThis, buffer, options_val, false); + } + + pub fn gunzipSync( + globalThis: *JSGlobalObject, + callframe: *JSC.CallFrame, + ) JSValue { + const buffer, const options_val = getOptions(globalThis, callframe) orelse return .zero; + defer buffer.deinit(); + return gunzipOrInflateSync(globalThis, buffer, options_val, true); + } + + pub fn gunzipOrInflateSync( globalThis: *JSGlobalObject, buffer: JSC.Node.StringOrBuffer, options_val_: ?JSValue, + is_gzip: bool, ) JSValue { - return gzipOrDeflateSync(globalThis, buffer, options_val_, false); + var opts = zlib.Options{ + .gzip = is_gzip, + .windowBits = if (is_gzip) 31 else -15, + }; + + var library: Library = .zlib; + if (options_val_) |options_val| { + if (options_val.get(globalThis, "windowBits")) |window| { + opts.windowBits = window.coerce(i32, globalThis); + library = .zlib; + } + + if (options_val.get(globalThis, "level")) |level| { + opts.level = level.coerce(i32, globalThis); + } + + if (options_val.get(globalThis, "memLevel")) |memLevel| { + opts.memLevel = memLevel.coerce(i32, globalThis); + library = .zlib; + } + + if (options_val.get(globalThis, "strategy")) |strategy| { + opts.strategy = strategy.coerce(i32, globalThis); + library = .zlib; + } + + if (options_val.getTruthy(globalThis, "library")) |library_value| { + if (!library_value.isString()) { + globalThis.throwInvalidArguments("Expected library to be a string", .{}); + return .zero; + } + + library = Library.map.fromJS(globalThis, library_value) orelse { + globalThis.throwInvalidArguments("Expected library to be one of 'zlib' or 'libdeflate'", .{}); + return .zero; + }; + } + } + + if (globalThis.hasException()) return .zero; + + const compressed = buffer.slice(); + const allocator = JSC.VirtualMachine.get().allocator; + + var list = brk: { + if (is_gzip and compressed.len > 64) { + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | CRC32 | ISIZE | + // +---+---+---+---+---+---+---+---+ + const estimated_size: u32 = @bitCast(compressed[compressed.len - 4 ..][0..4].*); + // If it's > 256 MB, let's rely on dynamic allocation to minimize the risk of OOM. + if (estimated_size > 0 and estimated_size < 256 * 1024 * 1024) { + break :brk std.ArrayListUnmanaged(u8).initCapacity(allocator, @max(estimated_size, 64)) catch { + globalThis.throwOutOfMemory(); + return .zero; + }; + } + } + + break :brk std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch { + globalThis.throwOutOfMemory(); + return .zero; + }; + }; + + switch (library) { + .zlib => { + var reader = zlib.ZlibReaderArrayList.initWithOptions(compressed, &list, allocator, .{ + .windowBits = opts.windowBits, + .level = opts.level, + }) catch |err| { + list.deinit(allocator); + if (err == error.InvalidArgument) { + globalThis.throw("Zlib error: Invalid argument", .{}); + return .zero; + } + + globalThis.throwError(err, "Zlib error"); + return .zero; + }; + + reader.readAll() catch { + defer reader.deinit(); + globalThis.throwValue(ZigString.init(reader.errorMessage() orelse "Zlib returned an error").toErrorInstance(globalThis)); + return .zero; + }; + reader.list = .{ .items = reader.list.items }; + reader.list.capacity = reader.list.items.len; + reader.list_ptr = &reader.list; + + var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array); + return array_buffer.toJSWithContext(globalThis, reader, reader_deallocator, null); + }, + .libdeflate => { + var decompressor: *bun.libdeflate.Decompressor = bun.libdeflate.Decompressor.alloc() orelse { + list.deinit(allocator); + globalThis.throwOutOfMemory(); + return .zero; + }; + defer decompressor.deinit(); + while (true) { + const result = decompressor.decompress(compressed, list.allocatedSlice(), if (is_gzip) .gzip else .deflate); + + list.items.len = result.written; + + if (result.status == .insufficient_space) { + if (list.capacity > 1024 * 1024 * 1024) { + list.deinit(allocator); + globalThis.throwOutOfMemory(); + return .zero; + } + + list.ensureTotalCapacity(allocator, list.capacity * 2) catch { + list.deinit(allocator); + globalThis.throwOutOfMemory(); + return .zero; + }; + continue; + } + + if (result.status == .success) { + list.items.len = result.written; + break; + } + + list.deinit(allocator); + globalThis.throw("libdeflate returned an error: {s}", .{@tagName(result.status)}); + return .zero; + } + + var array_buffer = JSC.ArrayBuffer.fromBytes(list.items, .Uint8Array); + return array_buffer.toJSWithContext(globalThis, list.items.ptr, global_deallocator, null); + }, + } } pub fn gzipOrDeflateSync( @@ -4676,107 +4869,112 @@ pub const JSZlib = struct { options_val_: ?JSValue, is_gzip: bool, ) JSValue { - var opts = zlib.Options{ .gzip = is_gzip }; + var level: ?i32 = null; + var library: Library = .zlib; + var windowBits: i32 = 0; + if (options_val_) |options_val| { - if (options_val.isObject()) { - if (options_val.get(globalThis, "windowBits")) |window| { - opts.windowBits = window.coerce(i32, globalThis); + if (options_val.get(globalThis, "windowBits")) |window| { + windowBits = window.coerce(i32, globalThis); + library = .zlib; + } + + if (options_val.getTruthy(globalThis, "library")) |library_value| { + if (!library_value.isString()) { + globalThis.throwInvalidArguments("Expected library to be a string", .{}); + return .zero; } - if (options_val.get(globalThis, "level")) |level| { - opts.level = level.coerce(i32, globalThis); - } + library = Library.map.fromJS(globalThis, library_value) orelse { + globalThis.throwInvalidArguments("Expected library to be one of 'zlib' or 'libdeflate'", .{}); + return .zero; + }; + } - if (options_val.get(globalThis, "memLevel")) |memLevel| { - opts.memLevel = memLevel.coerce(i32, globalThis); - } - - if (options_val.get(globalThis, "strategy")) |strategy| { - opts.strategy = strategy.coerce(i32, globalThis); - } + if (options_val.get(globalThis, "level")) |level_value| { + level = level_value.coerce(i32, globalThis); + if (globalThis.hasException()) return .zero; } } + if (globalThis.hasException()) return .zero; + const compressed = buffer.slice(); - const allocator = JSC.VirtualMachine.get().allocator; - var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable; - var reader = zlib.ZlibCompressorArrayList.init(compressed, &list, allocator, opts) catch |err| { - if (err == error.InvalidArgument) { - return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis); - } + const allocator = bun.default_allocator; - return JSC.toInvalidArguments("Unexpected", .{}, globalThis); - }; + switch (library) { + .zlib => { + var list = std.ArrayListUnmanaged(u8).initCapacity( + allocator, + if (compressed.len > 512) compressed.len else 32, + ) catch { + globalThis.throwOutOfMemory(); + return .zero; + }; - reader.readAll() catch { - defer reader.deinit(); - globalThis.throwValue(ZigString.init(reader.errorMessage() orelse "Zlib returned an error").toErrorInstance(globalThis)); - return .zero; - }; - reader.list = .{ .items = reader.list.toOwnedSlice(allocator) catch @panic("TODO") }; - reader.list.capacity = reader.list.items.len; - reader.list_ptr = &reader.list; + var reader = zlib.ZlibCompressorArrayList.init(compressed, &list, allocator, .{ + .windowBits = 15, + .gzip = is_gzip, + .level = level orelse 6, + }) catch |err| { + defer list.deinit(allocator); + if (err == error.InvalidArgument) { + globalThis.throw("Zlib error: Invalid argument", .{}); + return .zero; + } - var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array); - return array_buffer.toJSWithContext(globalThis, reader, reader_deallocator, null); - } + globalThis.throwError(err, "Zlib error"); + return .zero; + }; - pub fn inflateSync( - globalThis: *JSGlobalObject, - buffer: JSC.Node.StringOrBuffer, - ) JSValue { - const compressed = buffer.slice(); - const allocator = JSC.VirtualMachine.get().allocator; - var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable; - var reader = zlib.ZlibReaderArrayList.initWithOptions(compressed, &list, allocator, .{ - .windowBits = -15, - }) catch |err| { - if (err == error.InvalidArgument) { - return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis); - } + reader.readAll() catch { + defer reader.deinit(); + globalThis.throwValue(ZigString.init(reader.errorMessage() orelse "Zlib returned an error").toErrorInstance(globalThis)); + return .zero; + }; + reader.list = .{ .items = reader.list.toOwnedSlice(allocator) catch @panic("TODO") }; + reader.list.capacity = reader.list.items.len; + reader.list_ptr = &reader.list; - return JSC.toInvalidArguments("Unexpected", .{}, globalThis); - }; + var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array); + return array_buffer.toJSWithContext(globalThis, reader, reader_deallocator, null); + }, + .libdeflate => { + var compressor: *bun.libdeflate.Compressor = bun.libdeflate.Compressor.alloc(level orelse 6) orelse { + globalThis.throwOutOfMemory(); + return .zero; + }; + const encoding: bun.libdeflate.Encoding = if (is_gzip) .gzip else .deflate; + defer compressor.deinit(); - reader.readAll() catch { - defer reader.deinit(); - globalThis.throwValue(ZigString.init(reader.errorMessage() orelse "Zlib returned an error").toErrorInstance(globalThis)); - return .zero; - }; - reader.list = .{ .items = reader.list.toOwnedSlice(allocator) catch @panic("TODO") }; - reader.list.capacity = reader.list.items.len; - reader.list_ptr = &reader.list; + var list = std.ArrayListUnmanaged(u8).initCapacity( + allocator, + // This allocation size is unfortunate, but it's not clear how to avoid it with libdeflate. + compressor.maxBytesNeeded(compressed, encoding), + ) catch { + globalThis.throwOutOfMemory(); + return .zero; + }; - var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array); - return array_buffer.toJSWithContext(globalThis, reader, reader_deallocator, null); - } + while (true) { + const result = compressor.compress(compressed, list.allocatedSlice(), encoding); - pub fn gunzipSync( - globalThis: *JSGlobalObject, - buffer: JSC.Node.StringOrBuffer, - ) JSValue { - const compressed = buffer.slice(); - const allocator = JSC.VirtualMachine.get().allocator; - var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable; - var reader = zlib.ZlibReaderArrayList.init(compressed, &list, allocator) catch |err| { - if (err == error.InvalidArgument) { - return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis); - } + list.items.len = result.written; - return JSC.toInvalidArguments("Unexpected", .{}, globalThis); - }; + if (result.status == .success) { + list.items.len = result.written; + break; + } - reader.readAll() catch { - defer reader.deinit(); - globalThis.throwValue(ZigString.init(reader.errorMessage() orelse "Zlib returned an error").toErrorInstance(globalThis)); - return .zero; - }; - reader.list = .{ .items = reader.list.toOwnedSlice(allocator) catch @panic("TODO") }; - reader.list.capacity = reader.list.items.len; - reader.list_ptr = &reader.list; + list.deinit(allocator); + globalThis.throw("libdeflate error: {s}", .{@tagName(result.status)}); + return .zero; + } - var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array); - return array_buffer.toJSWithContext(globalThis, reader, reader_deallocator, null); + var array_buffer = JSC.ArrayBuffer.fromBytes(list.items, .Uint8Array); + return array_buffer.toJSWithContext(globalThis, list.items.ptr, global_deallocator, null); + }, + } } }; diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 3b02306650..a2e0dfc3c6 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -143,7 +143,7 @@ static JSValue constructPlatform(VM& vm, JSObject* processObject) static JSValue constructVersions(VM& vm, JSObject* processObject) { auto* globalObject = processObject->globalObject(); - JSC::JSObject* object = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 23); + JSC::JSObject* object = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 24); object->putDirect(vm, JSC::Identifier::fromString(vm, "node"_s), JSC::JSValue(JSC::jsOwnedString(vm, makeAtomString(ASCIILiteral::fromLiteralUnsafe(REPORTED_NODEJS_VERSION))))); @@ -176,6 +176,8 @@ static JSValue constructVersions(VM& vm, JSObject* processObject) JSC::JSValue(JSC::jsString(vm, makeString(ASCIILiteral::fromLiteralUnsafe(Bun__versions_lolhtml)))), 0); object->putDirect(vm, JSC::Identifier::fromString(vm, "ares"_s), JSC::JSValue(JSC::jsString(vm, makeString(ASCIILiteral::fromLiteralUnsafe(Bun__versions_c_ares)))), 0); + object->putDirect(vm, JSC::Identifier::fromString(vm, "libdeflate"_s), + JSC::JSValue(JSC::jsString(vm, makeString(ASCIILiteral::fromLiteralUnsafe(Bun__versions_libdeflate)))), 0); object->putDirect(vm, JSC::Identifier::fromString(vm, "usockets"_s), JSC::JSValue(JSC::jsString(vm, makeString(ASCIILiteral::fromLiteralUnsafe(Bun__versions_usockets)))), 0); object->putDirect(vm, JSC::Identifier::fromString(vm, "lshpack"_s), diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index e8068957f4..4b6fa3b0bd 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -348,6 +348,7 @@ extern "C" const char* Bun__versions_mimalloc; extern "C" const char* Bun__versions_picohttpparser; extern "C" const char* Bun__versions_uws; extern "C" const char* Bun__versions_webkit; +extern "C" const char* Bun__versions_libdeflate; extern "C" const char* Bun__versions_zig; extern "C" const char* Bun__versions_zlib; extern "C" const char* Bun__versions_tinycc; diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 177193e6b4..c1cea3acfa 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -2398,6 +2398,9 @@ pub const VirtualMachine = struct { // TODO: pub fn deinit(this: *VirtualMachine) void { this.source_mappings.deinit(); + if (this.rare_data) |rare_data| { + rare_data.deinit(); + } this.has_terminated = true; } diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index e7591cfd53..adf5bb2184 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -2102,6 +2102,7 @@ pub const Process = struct { pub export const Bun__versions_tinycc: [*:0]const u8 = bun.Global.versions.tinycc; pub export const Bun__versions_lolhtml: [*:0]const u8 = bun.Global.versions.lolhtml; pub export const Bun__versions_c_ares: [*:0]const u8 = bun.Global.versions.c_ares; + pub export const Bun__versions_libdeflate: [*:0]const u8 = bun.Global.versions.libdeflate; pub export const Bun__versions_usockets: [*:0]const u8 = bun.Environment.git_sha; pub export const Bun__version_sha: [*:0]const u8 = bun.Environment.git_sha; pub export const Bun__versions_lshpack: [*:0]const u8 = bun.Global.versions.lshpack; diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig index 5782200373..1a65e3e287 100644 --- a/src/bun.js/rare_data.zig +++ b/src/bun.js/rare_data.zig @@ -392,3 +392,14 @@ pub fn nodeFSStatWatcherScheduler(rare: *RareData, vm: *JSC.VirtualMachine) *Sta return rare.node_fs_stat_watcher_scheduler.?; }; } + +pub fn deinit(this: *RareData) void { + if (this.temp_pipe_read_buffer) |pipe| { + this.temp_pipe_read_buffer = null; + bun.default_allocator.destroy(pipe); + } + + if (this.boring_ssl_engine) |engine| { + _ = bun.BoringSSL.ENGINE_free(engine); + } +} diff --git a/src/bun.zig b/src/bun.zig index 14bb4cc27c..c6c2d5592a 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -3614,3 +3614,4 @@ pub fn memmove(output: []u8, input: []const u8) void { } pub const hmac = @import("./hmac.zig"); +pub const libdeflate = @import("./deps/libdeflate.zig"); diff --git a/src/deps/libdeflate b/src/deps/libdeflate new file mode 160000 index 0000000000..dc76454a39 --- /dev/null +++ b/src/deps/libdeflate @@ -0,0 +1 @@ +Subproject commit dc76454a39e7e83b68c3704b6e3784654f8d5ac5 diff --git a/src/deps/libdeflate.zig b/src/deps/libdeflate.zig new file mode 100644 index 0000000000..d38d6dcb9f --- /dev/null +++ b/src/deps/libdeflate.zig @@ -0,0 +1,149 @@ +const std = @import("std"); +const bun = @import("root").bun; +pub const Options = extern struct { + sizeof_options: usize = @sizeOf(Options), + malloc_func: ?*const fn (usize) callconv(.C) ?*anyopaque = @import("std").mem.zeroes(?*const fn (usize) callconv(.C) ?*anyopaque), + free_func: ?*const fn (?*anyopaque) callconv(.C) void = @import("std").mem.zeroes(?*const fn (?*anyopaque) callconv(.C) void), +}; +pub extern fn libdeflate_alloc_compressor(compression_level: c_int) ?*Compressor; +pub extern fn libdeflate_alloc_compressor_ex(compression_level: c_int, options: ?*const Options) ?*Compressor; +pub extern fn libdeflate_deflate_compress(compressor: *Compressor, in: ?*const anyopaque, in_nbytes: usize, out: ?*anyopaque, out_nbytes_avail: usize) usize; +pub extern fn libdeflate_deflate_compress_bound(compressor: *Compressor, in_nbytes: usize) usize; +pub extern fn libdeflate_zlib_compress(compressor: *Compressor, in: ?*const anyopaque, in_nbytes: usize, out: ?*anyopaque, out_nbytes_avail: usize) usize; +pub extern fn libdeflate_zlib_compress_bound(compressor: *Compressor, in_nbytes: usize) usize; +pub extern fn libdeflate_gzip_compress(compressor: *Compressor, in: ?*const anyopaque, in_nbytes: usize, out: ?*anyopaque, out_nbytes_avail: usize) usize; +pub extern fn libdeflate_gzip_compress_bound(compressor: *Compressor, in_nbytes: usize) usize; +pub extern fn libdeflate_free_compressor(compressor: *Compressor) void; + +fn load_once() void { + libdeflate_set_memory_allocator(bun.Mimalloc.mi_malloc, bun.Mimalloc.mi_free); +} + +var loaded_once = std.once(load_once); + +pub fn load() void { + loaded_once.call(); +} + +pub const Compressor = opaque { + pub fn alloc(compression_level: c_int) ?*Compressor { + return libdeflate_alloc_compressor(compression_level); + } + + pub fn alloc_ex(compression_level: c_int, options: ?*const Options) ?*Compressor { + return libdeflate_alloc_compressor_ex(compression_level, options); + } + + pub fn deinit(this: *Compressor) void { + return libdeflate_free_compressor(this); + } + + /// Compresses `input` into `output` and returns the number of bytes written. + pub fn inflate(this: *Compressor, input: []const u8, output: []u8) Result { + const written = libdeflate_deflate_compress(this, input.ptr, input.len, output.ptr, output.len); + return Result{ .read = input.len, .written = written, .status = Status.success }; + } + + pub fn maxBytesNeeded(this: *Compressor, input: []const u8, encoding: Encoding) usize { + return switch (encoding) { + Encoding.deflate => return libdeflate_deflate_compress_bound(this, input.len), + Encoding.zlib => return libdeflate_zlib_compress_bound(this, input.len), + Encoding.gzip => return libdeflate_gzip_compress_bound(this, input.len), + }; + } + + pub fn compress(this: *Compressor, input: []const u8, output: []u8, encoding: Encoding) Result { + switch (encoding) { + Encoding.deflate => return this.inflate(input, output), + Encoding.zlib => return this.zlib(input, output), + Encoding.gzip => return this.gzip(input, output), + } + } + + pub fn zlib(this: *Compressor, input: []const u8, output: []u8) Result { + const result = libdeflate_zlib_compress(this, input.ptr, input.len, output.ptr, output.len); + return Result{ .read = input.len, .written = result, .status = Status.success }; + } + + pub fn gzip(this: *Compressor, input: []const u8, output: []u8) Result { + const result = libdeflate_gzip_compress(this, input.ptr, input.len, output.ptr, output.len); + return Result{ .read = input.len, .written = result, .status = Status.success }; + } +}; + +pub const Decompressor = opaque { + pub fn alloc() ?*Decompressor { + return libdeflate_alloc_decompressor(); + } + + pub fn deinit(this: *Decompressor) void { + return libdeflate_free_decompressor(this); + } + + pub fn deflate(this: *Decompressor, input: []const u8, output: []u8) Result { + var actual_in_bytes_ret: usize = input.len; + var actual_out_bytes_ret: usize = output.len; + const result = libdeflate_deflate_decompress_ex(this, input.ptr, input.len, output.ptr, output.len, &actual_in_bytes_ret, &actual_out_bytes_ret); + return Result{ .read = actual_in_bytes_ret, .written = actual_out_bytes_ret, .status = result }; + } + + pub fn zlib(this: *Decompressor, input: []const u8, output: []u8) Result { + var actual_in_bytes_ret: usize = input.len; + var actual_out_bytes_ret: usize = output.len; + const result = libdeflate_zlib_decompress_ex(this, input.ptr, input.len, output.ptr, output.len, &actual_in_bytes_ret, &actual_out_bytes_ret); + return Result{ .read = actual_in_bytes_ret, .written = actual_out_bytes_ret, .status = result }; + } + + pub fn gzip(this: *Decompressor, input: []const u8, output: []u8) Result { + var actual_in_bytes_ret: usize = input.len; + var actual_out_bytes_ret: usize = output.len; + const result = libdeflate_gzip_decompress_ex(this, input.ptr, input.len, output.ptr, output.len, &actual_in_bytes_ret, &actual_out_bytes_ret); + return Result{ .read = actual_in_bytes_ret, .written = actual_out_bytes_ret, .status = result }; + } + + pub fn decompress(this: *Decompressor, input: []const u8, output: []u8, encoding: Encoding) Result { + switch (encoding) { + Encoding.deflate => return this.deflate(input, output), + Encoding.zlib => return this.zlib(input, output), + Encoding.gzip => return this.gzip(input, output), + } + } +}; + +pub const Result = struct { + read: usize, + written: usize, + status: Status, +}; + +pub const Encoding = enum { + deflate, + zlib, + gzip, +}; + +pub extern fn libdeflate_alloc_decompressor() ?*Decompressor; +pub extern fn libdeflate_alloc_decompressor_ex(options: ?*const Options) ?*Decompressor; +pub const LIBDEFLATE_SUCCESS = 0; +pub const LIBDEFLATE_BAD_DATA = 1; +pub const LIBDEFLATE_SHORT_OUTPUT = 2; +pub const LIBDEFLATE_INSUFFICIENT_SPACE = 3; +pub const Status = enum(c_uint) { + success = LIBDEFLATE_SUCCESS, + bad_data = LIBDEFLATE_BAD_DATA, + short_output = LIBDEFLATE_SHORT_OUTPUT, + insufficient_space = LIBDEFLATE_INSUFFICIENT_SPACE, +}; +pub extern fn libdeflate_deflate_decompress(decompressor: *Decompressor, in: ?*const anyopaque, in_nbytes: usize, out: ?*anyopaque, out_nbytes_avail: usize, actual_out_nbytes_ret: *usize) Status; +pub extern fn libdeflate_deflate_decompress_ex(decompressor: *Decompressor, in: ?*const anyopaque, in_nbytes: usize, out: ?*anyopaque, out_nbytes_avail: usize, actual_in_nbytes_ret: *usize, actual_out_nbytes_ret: *usize) Status; +pub extern fn libdeflate_zlib_decompress(decompressor: *Decompressor, in: ?*const anyopaque, in_nbytes: usize, out: ?*anyopaque, out_nbytes_avail: usize, actual_out_nbytes_ret: *usize) Status; +pub extern fn libdeflate_zlib_decompress_ex(decompressor: *Decompressor, in: ?*const anyopaque, in_nbytes: usize, out: ?*anyopaque, out_nbytes_avail: usize, actual_in_nbytes_ret: *usize, actual_out_nbytes_ret: *usize) Status; +pub extern fn libdeflate_gzip_decompress(decompressor: *Decompressor, in: ?*const anyopaque, in_nbytes: usize, out: ?*anyopaque, out_nbytes_avail: usize, actual_out_nbytes_ret: *usize) Status; +pub extern fn libdeflate_gzip_decompress_ex(decompressor: *Decompressor, in: ?*const anyopaque, in_nbytes: usize, out: ?*anyopaque, out_nbytes_avail: usize, actual_in_nbytes_ret: *usize, actual_out_nbytes_ret: *usize) Status; +pub extern fn libdeflate_free_decompressor(decompressor: *Decompressor) void; +pub extern fn libdeflate_adler32(adler: u32, buffer: ?*const anyopaque, len: usize) u32; +pub extern fn libdeflate_crc32(crc: u32, buffer: ?*const anyopaque, len: usize) u32; +pub extern fn libdeflate_set_memory_allocator(malloc_func: ?*const fn (usize) callconv(.C) ?*anyopaque, free_func: ?*const fn (?*anyopaque) callconv(.C) void) void; +pub const libdeflate_compressor = Compressor; +pub const libdeflate_options = Options; +pub const libdeflate_decompressor = Decompressor; diff --git a/src/feature_flags.zig b/src/feature_flags.zig index 4e25f5442f..76b843e61c 100644 --- a/src/feature_flags.zig +++ b/src/feature_flags.zig @@ -184,3 +184,14 @@ pub const postgresql = env.is_canary or env.isDebug; // TODO: fix Windows-only test failures in fetch-preconnect.test.ts pub const is_fetch_preconnect_supported = env.isPosix; + +pub const libdeflate_supported = env.isNative; + +// Mostly exists as a way to turn it off later, if necessary. +pub fn isLibdeflateEnabled() bool { + if (!libdeflate_supported) { + return false; + } + + return !bun.getRuntimeFeatureFlag("BUN_FEATURE_FLAG_NO_LIBDEFLATE"); +} diff --git a/src/generated_versions_list.zig b/src/generated_versions_list.zig index 82773c4c8e..56658e0e50 100644 --- a/src/generated_versions_list.zig +++ b/src/generated_versions_list.zig @@ -10,5 +10,6 @@ pub const zlib = "886098f3f339617b4243b286f5ed364b9989e245"; pub const tinycc = "ab631362d839333660a265d3084d8ff060b96753"; pub const lolhtml = "8d4c273ded322193d017042d1f48df2766b0f88b"; pub const c_ares = "d1722e6e8acaf10eb73fa995798a9cd421d9f85e"; +pub const libdeflate = "dc76454a39e7e83b68c3704b6e3784654f8d5ac5"; pub const zstd = "794ea1b0afca0f020f4e57b6732332231fb23c70"; pub const lshpack = "3d0f1fc1d6e66a642e7a98c55deb38aa986eb4b0"; diff --git a/src/http.zig b/src/http.zig index 99635e0451..a9e22f8553 100644 --- a/src/http.zig +++ b/src/http.zig @@ -777,6 +777,8 @@ pub const HTTPThread = struct { has_awoken: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), timer: std.time.Timer, + lazy_libdeflater: ?*LibdeflateState = null, + const ShutdownMessage = struct { async_http_id: u32, is_tls: bool, @@ -784,6 +786,23 @@ pub const HTTPThread = struct { const threadlog = Output.scoped(.HTTPThread, true); + pub const LibdeflateState = struct { + decompressor: *bun.libdeflate.Decompressor = undefined, + shared_buffer: [512 * 1024]u8 = undefined, + + pub usingnamespace bun.New(@This()); + }; + + pub fn deflater(this: *@This()) *LibdeflateState { + if (this.lazy_libdeflater == null) { + this.lazy_libdeflater = LibdeflateState.new(.{ + .decompressor = bun.libdeflate.Decompressor.alloc() orelse bun.outOfMemory(), + }); + } + + return this.lazy_libdeflater.?; + } + fn initOnce() void { http_thread = .{ .loop = undefined, @@ -795,7 +814,7 @@ pub const HTTPThread = struct { }, .timer = std.time.Timer.start() catch unreachable, }; - + bun.libdeflate.load(); const thread = std.Thread.spawn( .{ .stack_size = bun.default_thread_stack_size, @@ -1432,6 +1451,7 @@ pub const InternalState = struct { received_last_chunk: bool = false, did_set_content_encoding: bool = false, is_redirect_pending: bool = false, + is_libdeflate_fast_path_disabled: bool = false, resend_request_body_on_redirect: bool = false, transfer_encoding: Encoding = Encoding.identity, encoding: Encoding = Encoding.identity, @@ -1520,40 +1540,93 @@ pub const InternalState = struct { return this.received_last_chunk; } - fn decompressBytes(this: *InternalState, buffer: []const u8, body_out_str: *MutableString) !void { - log("Decompressing {d} bytes\n", .{buffer.len}); - + fn decompressBytes(this: *InternalState, buffer: []const u8, body_out_str: *MutableString, is_final_chunk: bool) !void { defer this.compressed_body.reset(); var gzip_timer: std.time.Timer = undefined; if (extremely_verbose) gzip_timer = std.time.Timer.start() catch @panic("Timer failure"); - try this.decompressor.updateBuffers(this.encoding, buffer, body_out_str); - this.decompressor.readAll(this.isDone()) catch |err| { - if (this.isDone() or error.ShortRead != err) { - Output.prettyErrorln("Decompression error: {s}", .{bun.asByteSlice(@errorName(err))}); - Output.flush(); - return err; + var still_needs_to_decompress = true; + + if (FeatureFlags.isLibdeflateEnabled()) { + // Fast-path: use libdeflate + if (is_final_chunk and !this.is_libdeflate_fast_path_disabled and this.encoding.canUseLibDeflate() and this.isDone()) libdeflate: { + this.is_libdeflate_fast_path_disabled = true; + + log("Decompressing {d} bytes with libdeflate\n", .{buffer.len}); + var deflater = http_thread.deflater(); + + // gzip stores the size of the uncompressed data in the last 4 bytes of the stream + // But it's only valid if the stream is less than 4.7 GB, since it's 4 bytes. + // If we know that the stream is going to be larger than our + // pre-allocated buffer, then let's dynamically allocate the exact + // size. + if (this.encoding == Encoding.gzip and buffer.len > 16 and buffer.len < 1024 * 1024 * 1024) { + const estimated_size: u32 = @bitCast(buffer[buffer.len - 4 ..][0..4].*); + // Since this is arbtirary input from the internet, let's set an upper bound of 32 MB for the allocation size. + if (estimated_size > deflater.shared_buffer.len and estimated_size < 32 * 1024 * 1024) { + try body_out_str.list.ensureTotalCapacityPrecise(body_out_str.allocator, estimated_size); + const result = deflater.decompressor.decompress(buffer, body_out_str.list.allocatedSlice(), .gzip); + + if (result.status == .success) { + body_out_str.list.items.len = result.written; + still_needs_to_decompress = false; + } + + break :libdeflate; + } + } + + const result = deflater.decompressor.decompress(buffer, &deflater.shared_buffer, switch (this.encoding) { + .gzip => .gzip, + .deflate => .deflate, + else => unreachable, + }); + + if (result.status == .success) { + try body_out_str.list.ensureTotalCapacityPrecise(body_out_str.allocator, result.written); + body_out_str.list.appendSliceAssumeCapacity(deflater.shared_buffer[0..result.written]); + still_needs_to_decompress = false; + } } - }; + } + + // Slow path, or brotli: use the .decompressor + if (still_needs_to_decompress) { + log("Decompressing {d} bytes\n", .{buffer.len}); + if (body_out_str.list.capacity == 0) { + const min = @min(@ceil(@as(f64, @floatFromInt(buffer.len)) * 1.5), @as(f64, 1024 * 1024 * 2)); + try body_out_str.growBy(@max(@as(usize, @intFromFloat(min)), 32)); + } + + try this.decompressor.updateBuffers(this.encoding, buffer, body_out_str); + + this.decompressor.readAll(this.isDone()) catch |err| { + if (this.isDone() or error.ShortRead != err) { + Output.prettyErrorln("Decompression error: {s}", .{bun.asByteSlice(@errorName(err))}); + Output.flush(); + return err; + } + }; + } if (extremely_verbose) this.gzip_elapsed = gzip_timer.read(); } - fn decompress(this: *InternalState, buffer: MutableString, body_out_str: *MutableString) !void { - try this.decompressBytes(buffer.list.items, body_out_str); + fn decompress(this: *InternalState, buffer: MutableString, body_out_str: *MutableString, is_final_chunk: bool) !void { + try this.decompressBytes(buffer.list.items, body_out_str, is_final_chunk); } - pub fn processBodyBuffer(this: *InternalState, buffer: MutableString) !bool { + pub fn processBodyBuffer(this: *InternalState, buffer: MutableString, is_final_chunk: bool) !bool { if (this.is_redirect_pending) return false; var body_out_str = this.body_out_str.?; switch (this.encoding) { Encoding.brotli, Encoding.gzip, Encoding.deflate => { - try this.decompress(buffer, body_out_str); + try this.decompress(buffer, body_out_str, is_final_chunk); }, else => { if (!body_out_str.owns(buffer.list.items)) { @@ -1696,6 +1769,13 @@ pub const Encoding = enum { brotli, chunked, + pub fn canUseLibDeflate(this: Encoding) bool { + return switch (this) { + .gzip, .deflate => true, + else => false, + }; + } + pub fn isCompressed(this: Encoding) bool { return switch (this) { .brotli, .gzip, .deflate => true, @@ -3328,14 +3408,7 @@ fn handleResponseBodyFromSinglePacket(this: *HTTPClient, incoming_data: []const if (this.state.is_redirect_pending) return; if (this.state.encoding.isCompressed()) { - var body_buffer = this.state.body_out_str.?; - if (body_buffer.list.capacity == 0) { - const min = @min(@ceil(@as(f64, @floatFromInt(incoming_data.len)) * 1.5), @as(f64, 1024 * 1024 * 2)); - try body_buffer.growBy(@max(@as(usize, @intFromFloat(min)), 32)); - } - - // assert(!body_buffer.owns(b)); - try this.state.decompressBytes(incoming_data, body_buffer); + try this.state.decompressBytes(incoming_data, this.state.body_out_str.?, true); } else { try this.state.getBodyBuffer().appendSliceExact(incoming_data); } @@ -3383,7 +3456,12 @@ fn handleResponseBodyFromMultiplePackets(this: *HTTPClient, incoming_data: []con // done or streaming const is_done = content_length != null and this.state.total_body_received >= content_length.?; if (is_done or this.signals.get(.body_streaming) or content_length == null) { - const processed = try this.state.processBodyBuffer(buffer.*); + const is_final_chunk = is_done; + const processed = try this.state.processBodyBuffer(buffer.*, is_final_chunk); + + // We can only use the libdeflate fast path when we are not streaming + // If we ever call processBodyBuffer again, it cannot go through the fast path. + this.state.is_libdeflate_fast_path_disabled = true; if (this.progress_node) |progress| { progress.activate(); @@ -3448,7 +3526,9 @@ fn handleResponseBodyChunkedEncodingFromMultiplePackets( } // streaming chunks if (this.signals.get(.body_streaming)) { - return try this.state.processBodyBuffer(buffer); + // If we're streaming, we cannot use the libdeflate fast path + this.state.is_libdeflate_fast_path_disabled = true; + return try this.state.processBodyBuffer(buffer, false); } return false; @@ -3458,6 +3538,7 @@ fn handleResponseBodyChunkedEncodingFromMultiplePackets( this.state.received_last_chunk = true; _ = try this.state.processBodyBuffer( buffer, + true, ); if (this.progress_node) |progress| { @@ -3526,7 +3607,10 @@ fn handleResponseBodyChunkedEncodingFromSinglePacket( // streaming chunks if (this.signals.get(.body_streaming)) { - return try this.state.processBodyBuffer(body_buffer.*); + // If we're streaming, we cannot use the libdeflate fast path + this.state.is_libdeflate_fast_path_disabled = true; + + return try this.state.processBodyBuffer(body_buffer.*, true); } return false; @@ -3534,7 +3618,6 @@ fn handleResponseBodyChunkedEncodingFromSinglePacket( // Done else => { this.state.received_last_chunk = true; - try this.handleResponseBodyFromSinglePacket(buffer); assert(this.state.body_out_str.?.list.items.ptr != buffer.ptr); if (this.progress_node) |progress| { diff --git a/src/install/extract_tarball.zig b/src/install/extract_tarball.zig index fc740cb479..85e01170d8 100644 --- a/src/install/extract_tarball.zig +++ b/src/install/extract_tarball.zig @@ -198,28 +198,70 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD defer extract_destination.close(); - if (PackageManager.verbose_install) { - Output.prettyErrorln("[{s}] Start extracting {s}", .{ name, tmpname }); - Output.flush(); - } - const Archive = @import("../libarchive/libarchive.zig").Archive; const Zlib = @import("../zlib.zig"); var zlib_pool = Npm.Registry.BodyPool.get(default_allocator); zlib_pool.data.reset(); defer Npm.Registry.BodyPool.release(zlib_pool); - var zlib_entry = try Zlib.ZlibReaderArrayList.init(tgz_bytes, &zlib_pool.data.list, default_allocator); - zlib_entry.readAll() catch |err| { - this.package_manager.log.addErrorFmt( - null, - logger.Loc.Empty, - this.package_manager.allocator, - "{s} decompressing \"{s}\" to \"{}\"", - .{ @errorName(err), name, bun.fmt.fmtPath(u8, std.mem.span(tmpname), .{}) }, - ) catch unreachable; - return error.InstallFailed; - }; + var esimated_output_size: usize = 0; + + const time_started_for_verbose_logs: u64 = if (PackageManager.verbose_install) bun.getRoughTickCount().ns() else 0; + + { + // Last 4 bytes of a gzip-compressed file are the uncompressed size. + if (tgz_bytes.len > 16) { + // If the file claims to be larger than 16 bytes and smaller than 64 MB, we'll preallocate the buffer. + // If it's larger than that, we'll do it incrementally. We want to avoid OOMing. + const last_4_bytes: u32 = @bitCast(tgz_bytes[tgz_bytes.len - 4 ..][0..4].*); + if (last_4_bytes > 16 and last_4_bytes < 64 * 1024 * 1024) { + // It's okay if this fails. We will just allocate as we go and that will error if we run out of memory. + esimated_output_size = last_4_bytes; + if (zlib_pool.data.list.capacity == 0) { + zlib_pool.data.list.ensureTotalCapacityPrecise(zlib_pool.data.allocator, last_4_bytes) catch {}; + } else { + zlib_pool.data.ensureUnusedCapacity(last_4_bytes) catch {}; + } + } + } + } + + var needs_to_decompress = true; + if (bun.FeatureFlags.isLibdeflateEnabled() and zlib_pool.data.list.capacity > 16 and esimated_output_size > 0) use_libdeflate: { + const decompressor = bun.libdeflate.Decompressor.alloc() orelse break :use_libdeflate; + defer decompressor.deinit(); + + const result = decompressor.gzip(tgz_bytes, zlib_pool.data.list.allocatedSlice()); + + if (result.status == .success) { + zlib_pool.data.list.items.len = result.written; + needs_to_decompress = false; + } + + // If libdeflate fails for any reason, fallback to zlib. + } + + if (needs_to_decompress) { + zlib_pool.data.list.clearRetainingCapacity(); + var zlib_entry = try Zlib.ZlibReaderArrayList.init(tgz_bytes, &zlib_pool.data.list, default_allocator); + zlib_entry.readAll() catch |err| { + this.package_manager.log.addErrorFmt( + null, + logger.Loc.Empty, + this.package_manager.allocator, + "{s} decompressing \"{s}\" to \"{}\"", + .{ @errorName(err), name, bun.fmt.fmtPath(u8, std.mem.span(tmpname), .{}) }, + ) catch unreachable; + return error.InstallFailed; + }; + } + + if (PackageManager.verbose_install) { + const decompressing_ended_at: u64 = bun.getRoughTickCount().ns(); + const elapsed = decompressing_ended_at - time_started_for_verbose_logs; + Output.prettyErrorln("[{s}] Extract {s} (decompressed {} tgz file in {})", .{ name, tmpname, bun.fmt.size(tgz_bytes.len), bun.fmt.fmtDuration(elapsed) }); + } + switch (this.resolution.tag) { .github => { const DirnameReader = struct { @@ -278,7 +320,8 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD } if (PackageManager.verbose_install) { - Output.prettyErrorln("[{s}] Extracted", .{name}); + const elapsed = bun.getRoughTickCount().ns() - time_started_for_verbose_logs; + Output.prettyErrorln("[{s}] Extracted to {s} ({})", .{ name, tmpname, bun.fmt.fmtDuration(elapsed) }); Output.flush(); } } diff --git a/src/install/install.zig b/src/install/install.zig index 89d94fc801..4b71015595 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -6485,7 +6485,7 @@ pub const PackageManager = struct { if (comptime log_level.isVerbose()) { Output.prettyError(" ", .{}); Output.printElapsed(@as(f64, @floatCast(@as(f64, @floatFromInt(task.http.elapsed)) / std.time.ns_per_ms))); - Output.prettyError("Downloaded {s} tarball\n", .{extract.name.slice()}); + Output.prettyError(" Downloaded {s} tarball\n", .{extract.name.slice()}); Output.flush(); } diff --git a/test/js/node/zlib/zlib.test.js b/test/js/node/zlib/zlib.test.js index 35bd2ffdc5..40f9670856 100644 --- a/test/js/node/zlib/zlib.test.js +++ b/test/js/node/zlib/zlib.test.js @@ -9,28 +9,35 @@ import { tmpdirSync } from "harness"; import * as stream from "node:stream"; describe("zlib", () => { - it("should be able to deflate and inflate", () => { - const data = new TextEncoder().encode("Hello World!".repeat(1)); - const compressed = deflateSync(data); - const decompressed = inflateSync(compressed); - expect(decompressed.join("")).toBe(data.join("")); - }); + for (let library of ["zlib", "libdeflate"]) { + for (let outputLibrary of ["zlib", "libdeflate"]) { + describe(`${library} -> ${outputLibrary}`, () => { + it("should be able to deflate and inflate", () => { + const data = new TextEncoder().encode("Hello World!".repeat(1)); + const compressed = deflateSync(data, { library }); + console.log(compressed); + const decompressed = inflateSync(compressed, { library: outputLibrary }); + expect(decompressed.join("")).toBe(data.join("")); + }); - it("should be able to gzip and gunzip", () => { - const data = new TextEncoder().encode("Hello World!".repeat(1)); - const compressed = gzipSync(data); - const decompressed = gunzipSync(compressed); - expect(decompressed.join("")).toBe(data.join("")); - }); + it("should be able to gzip and gunzip", () => { + const data = new TextEncoder().encode("Hello World!".repeat(1)); + const compressed = gzipSync(data, { library }); + const decompressed = gunzipSync(compressed, { library: outputLibrary }); + expect(decompressed.join("")).toBe(data.join("")); + }); + }); + } + } it("should throw on invalid raw deflate data", () => { const data = new TextEncoder().encode("Hello World!".repeat(1)); - expect(() => inflateSync(data)).toThrow(new Error("invalid stored block lengths")); + expect(() => inflateSync(data, { library: "zlib" })).toThrow(new Error("invalid stored block lengths")); }); it("should throw on invalid gzip data", () => { const data = new TextEncoder().encode("Hello World!".repeat(1)); - expect(() => gunzipSync(data)).toThrow(new Error("incorrect header check")); + expect(() => gunzipSync(data, { library: "zlib" })).toThrow(new Error("incorrect header check")); }); }); diff --git a/test/js/web/fetch/fetch.stream.test.ts b/test/js/web/fetch/fetch.stream.test.ts index eac19feffc..7d7908f6a3 100644 --- a/test/js/web/fetch/fetch.stream.test.ts +++ b/test/js/web/fetch/fetch.stream.test.ts @@ -652,24 +652,28 @@ describe("fetch() with streaming", () => { } } - type CompressionType = "no" | "gzip" | "deflate" | "br" | "deflate_with_headers"; - type TestType = { headers: Record; compression: CompressionType; skip?: boolean }; - const types: Array = [ + const types = [ { headers: {}, compression: "no" }, { headers: { "Content-Encoding": "gzip" }, compression: "gzip" }, + { headers: { "Content-Encoding": "gzip" }, compression: "gzip-libdeflate" }, { headers: { "Content-Encoding": "deflate" }, compression: "deflate" }, + { headers: { "Content-Encoding": "deflate" }, compression: "deflate-libdeflate" }, { headers: { "Content-Encoding": "deflate" }, compression: "deflate_with_headers" }, - // { headers: { "Content-Encoding": "br" }, compression: "br", skip: true }, // not implemented yet - ]; + { headers: { "Content-Encoding": "br" }, compression: "br" }, + ] as const; - function compress(compression: CompressionType, data: Uint8Array) { + function compress(compression, data: Uint8Array) { switch (compression) { + case "gzip-libdeflate": case "gzip": - return Bun.gzipSync(data); + return Bun.gzipSync(data, { library: compression === "gzip-libdeflate" ? "libdeflate" : "zlib" }); + case "deflate-libdeflate": case "deflate": - return Bun.deflateSync(data); + return Bun.deflateSync(data, { library: compression === "deflate-libdeflate" ? "libdeflate" : "zlib" }); case "deflate_with_headers": return zlib.deflateSync(data); + case "br": + return zlib.brotliCompressSync(data); default: return data; } @@ -1186,7 +1190,14 @@ describe("fetch() with streaming", () => { gcTick(false); expect(buffer.toString("utf8")).toBe("unreachable"); } catch (err) { - expect((err as Error).name).toBe("ZlibError"); + if (compression === "br") { + expect((err as Error).name).toBe("BrotliDecompressionError"); + } else if (compression === "deflate-libdeflate") { + // Since the compressed data is different, the error ends up different. + expect((err as Error).name).toBe("ShortRead"); + } else { + expect((err as Error).name).toBe("ZlibError"); + } } } }); From e2c3749965229785c822bf9a9631c7f099863155 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 24 Jul 2024 02:00:20 -0700 Subject: [PATCH 052/123] fix(bundler): become smarter with `__esm` wrappers (#12729) --- build.zig | 7 + src/ast/base.zig | 20 +- src/bun.zig | 5 +- src/bundler/bundle_v2.zig | 508 ++++++++++-------- src/crash_handler.zig | 37 +- src/js_ast.zig | 60 ++- src/js_parser.zig | 104 +++- src/js_printer.zig | 27 +- src/options.zig | 1 - src/resolver/package_json.zig | 36 +- src/resolver/resolver.zig | 6 +- src/runtime.js | 35 +- .../__snapshots__/bun-build-api.test.ts.snap | 21 +- test/bundler/bundler_cjs2esm.test.ts | 33 +- test/bundler/bundler_edgecase.test.ts | 47 ++ .../__snapshots__/transpiler.test.js.snap | 0 .../transpiler/async-transpiler-entry.js | 0 .../transpiler/async-transpiler-imported.js | 0 ...rator-export-default-class-fixture-anon.ts | 0 .../decorator-export-default-class-fixture.ts | 0 .../transpiler/decorator-metadata.test.ts | 0 .../transpiler/decorators.test.ts | 0 .../export-default-with-static-initializer.js | 0 .../transpiler/export-default.test.js | 0 test/{ => bundler}/transpiler/handlebars.hbs | 0 test/{ => bundler}/transpiler/inline.macro.js | 0 test/{ => bundler}/transpiler/macro-check.js | 0 .../transpiler/macro-test.test.ts | 0 test/{ => bundler}/transpiler/macro.ts | 0 .../transpiler/property-non-ascii-fixture.js | 0 .../{ => bundler}/transpiler/property.test.ts | 0 ...ime-transpiler-fixture-duplicate-keys.json | 0 .../runtime-transpiler-json-fixture.json | 0 .../transpiler/runtime-transpiler.test.ts | 0 .../template-literal-fixture-test.js | 0 .../transpiler/template-literal.test.ts | 0 .../transpiler-stack-overflow.test.ts | 0 .../transpiler/transpiler.test.js | 2 +- .../transpiler/tsconfig.is-just-a-number.json | 0 .../transpiler/tsconfig.with-commas.json | 0 .../transpiler/with-statement-works.js | 0 .../__snapshots__/snapshot.test.ts.snap | 2 +- 42 files changed, 613 insertions(+), 338 deletions(-) rename test/{ => bundler}/transpiler/__snapshots__/transpiler.test.js.snap (100%) rename test/{ => bundler}/transpiler/async-transpiler-entry.js (100%) rename test/{ => bundler}/transpiler/async-transpiler-imported.js (100%) rename test/{ => bundler}/transpiler/decorator-export-default-class-fixture-anon.ts (100%) rename test/{ => bundler}/transpiler/decorator-export-default-class-fixture.ts (100%) rename test/{ => bundler}/transpiler/decorator-metadata.test.ts (100%) rename test/{ => bundler}/transpiler/decorators.test.ts (100%) rename test/{ => bundler}/transpiler/export-default-with-static-initializer.js (100%) rename test/{ => bundler}/transpiler/export-default.test.js (100%) rename test/{ => bundler}/transpiler/handlebars.hbs (100%) rename test/{ => bundler}/transpiler/inline.macro.js (100%) rename test/{ => bundler}/transpiler/macro-check.js (100%) rename test/{ => bundler}/transpiler/macro-test.test.ts (100%) rename test/{ => bundler}/transpiler/macro.ts (100%) rename test/{ => bundler}/transpiler/property-non-ascii-fixture.js (100%) rename test/{ => bundler}/transpiler/property.test.ts (100%) rename test/{ => bundler}/transpiler/runtime-transpiler-fixture-duplicate-keys.json (100%) rename test/{ => bundler}/transpiler/runtime-transpiler-json-fixture.json (100%) rename test/{ => bundler}/transpiler/runtime-transpiler.test.ts (100%) rename test/{ => bundler}/transpiler/template-literal-fixture-test.js (100%) rename test/{ => bundler}/transpiler/template-literal.test.ts (100%) rename test/{ => bundler}/transpiler/transpiler-stack-overflow.test.ts (100%) rename test/{ => bundler}/transpiler/transpiler.test.js (99%) rename test/{ => bundler}/transpiler/tsconfig.is-just-a-number.json (100%) rename test/{ => bundler}/transpiler/tsconfig.with-commas.json (100%) rename test/{ => bundler}/transpiler/with-statement-works.js (100%) diff --git a/build.zig b/build.zig index 20c65eff9e..cdf321c248 100644 --- a/build.zig +++ b/build.zig @@ -49,6 +49,7 @@ const BunBuildOptions = struct { reported_nodejs_version: Version, generated_code_dir: []const u8, + no_llvm: bool, cached_options_module: ?*Module = null, windows_shim: ?WindowsShim = null, @@ -181,6 +182,8 @@ pub fn build(b: *Build) !void { const obj_format = b.option(ObjectFormat, "obj_format", "Output file for object files") orelse .obj; + const no_llvm = b.option(bool, "no_llvm", "Experiment with Zig self hosted backends. No stability guaranteed") orelse false; + var build_options = BunBuildOptions{ .target = target, .optimize = optimize, @@ -189,6 +192,7 @@ pub fn build(b: *Build) !void { .arch = arch, .generated_code_dir = generated_code_dir, + .no_llvm = no_llvm, .version = try Version.parse(bun_version), .canary_revision = canary: { @@ -320,6 +324,7 @@ pub inline fn addMultiCheck( .version = root_build_options.version, .reported_nodejs_version = root_build_options.reported_nodejs_version, .generated_code_dir = root_build_options.generated_code_dir, + .no_llvm = root_build_options.no_llvm, }; var obj = addBunObject(b, &options); @@ -338,6 +343,8 @@ pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile { }, .target = opts.target, .optimize = opts.optimize, + .use_llvm = !opts.no_llvm, + .use_lld = if (opts.os == .mac) false else !opts.no_llvm, // https://github.com/ziglang/zig/issues/17430 .pic = true, diff --git a/src/ast/base.zig b/src/ast/base.zig index 2c096a676b..151a771919 100644 --- a/src/ast/base.zig +++ b/src/ast/base.zig @@ -100,6 +100,8 @@ pub const Index = packed struct(u32) { /// The maps can be merged quickly by creating a single outer array containing /// all inner arrays from all parsed files. pub const Ref = packed struct(u64) { + pub const Int = u31; + inner_index: Int = 0, tag: enum(u2) { @@ -114,6 +116,10 @@ pub const Ref = packed struct(u64) { /// Represents a null state without using an extra bit pub const None = Ref{ .inner_index = 0, .source_index = 0, .tag = .invalid }; + comptime { + bun.assert(None.isEmpty()); + } + pub inline fn isEmpty(this: Ref) bool { return this.asU64() == 0; } @@ -121,12 +127,6 @@ pub const Ref = packed struct(u64) { pub const ArrayHashCtx = RefHashCtx; pub const HashCtx = RefCtx; - pub const Int = std.meta.Int(.unsigned, (64 - 2) / 2); - - pub fn toInt(value: anytype) Int { - return @as(Int, @intCast(value)); - } - pub fn isSourceIndexNull(this: anytype) bool { return this == std.math.maxInt(Int); } @@ -177,11 +177,11 @@ pub const Ref = packed struct(u64) { } pub fn hash(key: Ref) u32 { - return @as(u32, @truncate(key.hash64())); + return @truncate(key.hash64()); } pub inline fn asU64(key: Ref) u64 { - return @as(u64, @bitCast(key)); + return @bitCast(key); } pub inline fn hash64(key: Ref) u64 { @@ -192,9 +192,7 @@ pub const Ref = packed struct(u64) { return ref.asU64() == other.asU64(); } - pub inline fn isNull(self: Ref) bool { - return self.tag == .invalid; - } + pub const isNull = isEmpty; // deprecated pub fn jsonStringify(self: *const Ref, writer: anytype) !void { return try writer.write([2]u32{ self.sourceIndex(), self.innerIndex() }); diff --git a/src/bun.zig b/src/bun.zig index c6c2d5592a..a47637e2c2 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -1859,8 +1859,9 @@ pub const StringMap = struct { }; pub const DotEnv = @import("./env_loader.zig"); -pub const BundleV2 = @import("./bundler/bundle_v2.zig").BundleV2; -pub const ParseTask = @import("./bundler/bundle_v2.zig").ParseTask; +pub const bundle_v2 = @import("./bundler/bundle_v2.zig"); +pub const BundleV2 = bundle_v2.BundleV2; +pub const ParseTask = bundle_v2.ParseTask; pub const Lock = @import("./lock.zig").Lock; pub const UnboundedQueue = @import("./bun.js/unbounded_queue.zig").UnboundedQueue; diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index fa848d8139..e8534cee92 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -3294,6 +3294,8 @@ const AstSourceIDMapping = struct { const LinkerGraph = struct { const debug = Output.scoped(.LinkerGraph, false); + /// TODO(@paperdave): remove this. i added it before realizing this is available + /// via LinkerContext.parse_graph. it may also be worth removing the other cloned data. bundler_graph: *const Graph, files: File.List = .{}, @@ -3804,7 +3806,7 @@ const LinkerGraph = struct { }; }; -const LinkerContext = struct { +pub const LinkerContext = struct { const debug = Output.scoped(.LinkerCtx, false); parse_graph: *Graph = undefined, @@ -4951,7 +4953,6 @@ const LinkerContext = struct { } const wrapped_ref = this.graph.ast.items(.wrapper_ref)[source_index]; - if (wrapped_ref.isNull() or wrapped_ref.isEmpty()) continue; // Create the wrapper part for wrapped files. This is needed by a later step. this.createWrapperForFile( @@ -5011,7 +5012,6 @@ const LinkerContext = struct { const module_ref = module_refs[id]; - // TODO: see if counting and batching into a single large allocation instead of per-file improves perf const string_buffer_len: usize = brk: { var count: usize = 0; if (is_entry_point and this.options.output_format == .esm) { @@ -5025,7 +5025,7 @@ const LinkerContext = struct { else std.fmt.count("{}", .{source.fmtIdentifier()}); - if (wrap == .esm) { + if (wrap == .esm and wrapper_refs[id].isValid()) { count += "init_".len + ident_fmt_len; } @@ -5061,14 +5061,15 @@ const LinkerContext = struct { // Use "init_*" for ESM wrappers instead of "require_*" if (wrap == .esm) { - const original_name = builder.fmt( - "init_{}", - .{ - source.fmtIdentifier(), - }, - ); + const ref = wrapper_refs[id]; + if (ref.isValid()) { + const original_name = builder.fmt( + "init_{}", + .{source.fmtIdentifier()}, + ); - this.graph.symbols.get(wrapper_refs[id]).?.original_name = original_name; + this.graph.symbols.get(ref).?.original_name = original_name; + } } // If this isn't CommonJS, then rename the unused "exports" and "module" @@ -5310,13 +5311,15 @@ const LinkerContext = struct { if (other_flags.wrap != .none) { // Depend on the automatically-generated require wrapper symbol const wrapper_ref = wrapper_refs[other_id]; - this.graph.generateSymbolImportAndUse( - source_index, - @as(u32, @intCast(part_index)), - wrapper_ref, - 1, - Index.source(other_source_index), - ) catch unreachable; + if (wrapper_ref.isValid()) { + this.graph.generateSymbolImportAndUse( + source_index, + @as(u32, @intCast(part_index)), + wrapper_ref, + 1, + Index.source(other_source_index), + ) catch unreachable; + } // This is an ES6 import of a CommonJS module, so it needs the // "__toESM" wrapper as long as it's not a bare "require()" @@ -5861,7 +5864,7 @@ const LinkerContext = struct { const other_parts = c.topLevelSymbolsToParts(id, ref); for (other_parts) |other_part_index| { - const local = local_dependencies.getOrPut(@as(u32, @intCast(other_part_index))) catch unreachable; + const local = local_dependencies.getOrPut(other_part_index) catch unreachable; if (!local.found_existing or local.value_ptr.* != part_index) { local.value_ptr.* = @as(u32, @intCast(part_index)); // note: if we crash on append, it is due to threadlocal heaps in mimalloc @@ -5877,7 +5880,7 @@ const LinkerContext = struct { // Also map from imports to parts that use them if (named_imports.getPtr(ref)) |existing| { - existing.local_parts_with_uses.push(allocator, @as(u32, @intCast(part_index))) catch unreachable; + existing.local_parts_with_uses.push(allocator, @intCast(part_index)) catch unreachable; } } } @@ -6048,26 +6051,28 @@ const LinkerContext = struct { const used_refs = part.symbol_uses.keys(); - for (used_refs) |ref_| { + // Record each symbol used in this part. This will later be matched up + // with our map of which chunk a given symbol is declared in to + // determine if the symbol needs to be imported from another chunk. + for (used_refs) |ref| { const ref_to_use = brk: { - var ref = ref_; - var symbol = deps.symbols.getConst(ref).?; + var ref_to_use = ref; + var symbol = deps.symbols.getConst(ref_to_use).?; // Ignore unbound symbols if (symbol.kind == .unbound) continue; // Ignore symbols that are going to be replaced by undefined - if (symbol.import_item_status == .missing) { + if (symbol.import_item_status == .missing) continue; - } // If this is imported from another file, follow the import // reference and reference the symbol in that file instead - if (imports_to_bind.get(ref)) |import_data| { - ref = import_data.data.import_ref; - symbol = deps.symbols.getConst(ref).?; - } else if (wrap == .cjs and ref.eql(wrapper_ref)) { + if (imports_to_bind.get(ref_to_use)) |import_data| { + ref_to_use = import_data.data.import_ref; + symbol = deps.symbols.getConst(ref_to_use).?; + } else if (wrap == .cjs and ref_to_use.eql(wrapper_ref)) { // The only internal symbol that wrapped CommonJS files export // is the wrapper itself. continue; @@ -6078,9 +6083,9 @@ const LinkerContext = struct { // identifier. In that case we want to pull in the namespace symbol // instead. The namespace symbol stores the result of "require()". if (symbol.namespace_alias) |*namespace_alias| { - ref = namespace_alias.namespace_ref; + ref_to_use = namespace_alias.namespace_ref; } - break :brk ref; + break :brk ref_to_use; }; if (comptime Environment.allow_assert) @@ -6703,6 +6708,17 @@ const LinkerContext = struct { defer ctx.wg.finish(); var worker = ThreadPool.Worker.get(@fieldParentPtr("linker", ctx.c)); defer worker.unget(); + + const prev_action = if (Environment.isDebug) bun.crash_handler.current_action; + defer if (Environment.isDebug) { + bun.crash_handler.current_action = prev_action; + }; + if (Environment.isDebug) bun.crash_handler.current_action = .{ .bundle_generate_chunk = .{ + .chunk = ctx.chunk, + .context = ctx.c, + .part_range = &part_range.part_range, + } }; + ctx.chunk.compile_results_for_chunk[part_range.i] = generateCompileResultForJSChunk_(worker, ctx.c, ctx.chunk, part_range.part_range); } @@ -7331,7 +7347,7 @@ const LinkerContext = struct { ) catch unreachable; }, else => { - if (flags.wrap == .esm) { + if (flags.wrap == .esm and ast.wrapper_ref.isValid()) { if (flags.is_async_or_has_async_dependency) { // "await init_foo();" stmts.append( @@ -7867,33 +7883,21 @@ const LinkerContext = struct { .cjs => { // Replace the statement with a call to "require()" if this module is not wrapped try stmts.inside_wrapper_prefix.append( - Stmt.alloc( - S.Local, - S.Local{ - .decls = try G.Decl.List.fromSlice( - allocator, - &.{ - .{ - .binding = Binding.alloc( - allocator, - B.Identifier{ - .ref = namespace_ref, - }, - loc, - ), - .value = Expr.init( - E.RequireString, - E.RequireString{ - .import_record_index = import_record_index, - }, - loc, - ), - }, + Stmt.alloc(S.Local, .{ + .decls = try G.Decl.List.fromSlice( + allocator, + &.{ + .{ + .binding = Binding.alloc(allocator, B.Identifier{ + .ref = namespace_ref, + }, loc), + .value = Expr.init(E.RequireString, .{ + .import_record_index = import_record_index, + }, loc), }, - ), - }, - loc, - ), + }, + ), + }, loc), ); }, .esm => { @@ -7904,43 +7908,36 @@ const LinkerContext = struct { return true; } + const wrapper_ref = c.graph.ast.items(.wrapper_ref)[record.source_index.get()]; + if (wrapper_ref.isEmpty()) { + return true; + } + // Replace the statement with a call to "init()" const value: Expr = brk: { - const default = Expr.init( - E.Call, - E.Call{ - .target = Expr.initIdentifier( - c.graph.ast.items(.wrapper_ref)[record.source_index.get()], - loc, - ), - }, - loc, - ); + const default = Expr.init(E.Call, .{ + .target = Expr.initIdentifier( + wrapper_ref, + loc, + ), + }, loc); if (other_flags.is_async_or_has_async_dependency) { // This currently evaluates sibling dependencies in serial instead of in // parallel, which is incorrect. This should be changed to store a promise // and await all stored promises after all imports but before any code. - break :brk Expr.init( - E.Await, - E.Await{ - .value = default, - }, - loc, - ); + break :brk Expr.init(E.Await, .{ + .value = default, + }, loc); } break :brk default; }; try stmts.inside_wrapper_prefix.append( - Stmt.alloc( - S.SExpr, - S.SExpr{ - .value = value, - }, - loc, - ), + Stmt.alloc(S.SExpr, .{ + .value = value, + }, loc), ); }, } @@ -7957,12 +7954,12 @@ const LinkerContext = struct { /// /// prefix - outer /// ... - /// init_esm = () => { + /// var init_foo = __esm(() => { /// prefix - inner /// ... /// suffix - inenr - /// }; - /// ... + /// }); + /// ... /// suffix - outer /// /// Keep in mind that we may need to wrap ES modules in some cases too @@ -8152,27 +8149,20 @@ const LinkerContext = struct { } else { if (record.source_index.isValid()) { const flag = flags[record.source_index.get()]; - if (flag.wrap == .esm) { + const wrapper_ref = c.graph.ast.items(.wrapper_ref)[record.source_index.get()]; + if (flag.wrap == .esm and wrapper_ref.isValid()) { try stmts.inside_wrapper_prefix.append( - Stmt.alloc( - S.SExpr, - .{ - .value = Expr.init( - E.Call, - E.Call{ - .target = Expr.init( - E.Identifier, - E.Identifier{ - .ref = c.graph.ast.items(.wrapper_ref)[record.source_index.get()], - }, - stmt.loc, - ), + Stmt.alloc(S.SExpr, .{ + .value = Expr.init(E.Call, .{ + .target = Expr.init( + E.Identifier, + E.Identifier{ + .ref = wrapper_ref, }, stmt.loc, ), - }, - stmt.loc, - ), + }, stmt.loc), + }, stmt.loc), ); } } @@ -8243,7 +8233,6 @@ const LinkerContext = struct { .s_export_from => |s| { // "export {foo} from 'path'" - if (try c.shouldRemoveImportExportStmt( stmts, stmt.loc, @@ -8291,7 +8280,6 @@ const LinkerContext = struct { if (shouldStripExports) { // Remove export statements entirely - continue; } @@ -8796,13 +8784,16 @@ const LinkerContext = struct { // isn't async because then calling "require()" on that module would // swallow any exceptions thrown during module initialization. const is_async = flags.is_async_or_has_async_dependency; - const Hoisty = struct { - decls: std.ArrayList(G.Decl), + + const ExportHoist = struct { + decls: std.ArrayListUnmanaged(G.Decl), allocator: std.mem.Allocator, + next_value: ?Expr = null, pub fn wrapIdentifier(w: *@This(), loc: Logger.Loc, ref: Ref) Expr { w.decls.append( - G.Decl{ + w.allocator, + .{ .binding = Binding.alloc( w.allocator, B.Identifier{ @@ -8810,113 +8801,119 @@ const LinkerContext = struct { }, loc, ), + .value = w.next_value, }, - ) catch unreachable; - return Expr.init( - E.Identifier, - E.Identifier{ - .ref = ref, - }, - loc, - ); + ) catch bun.outOfMemory(); + + return Expr.initIdentifier(ref, loc); } }; - var hoisty = Hoisty{ - .decls = std.ArrayList(G.Decl).init(temp_allocator), + + var hoist = ExportHoist{ + .decls = .{}, .allocator = temp_allocator, }; + var inner_stmts = stmts.all_stmts.items; + // Hoist all top-level "var" and "function" declarations out of the closure { var end: usize = 0; - for (stmts.all_stmts.items) |stmt_| { - var stmt: Stmt = stmt_; - switch (stmt.data) { - .s_local => |local| { - if (local.was_commonjs_export or ast.commonjs_named_exports.count() == 0) { - var value: Expr = Expr.init(E.Missing, E.Missing{}, Logger.Loc.Empty); - for (local.decls.slice()) |*decl| { - const binding = decl.binding.toExpr(&hoisty); - if (decl.value) |other| { + for (stmts.all_stmts.items) |stmt| { + const transformed = switch (stmt.data) { + .s_local => |local| stmt: { + // Convert the declarations to assignments + var value = Expr.empty; + for (local.decls.slice()) |*decl| { + if (decl.value) |initializer| { + const can_be_moved = initializer.canBeMoved(); + hoist.next_value = if (can_be_moved) initializer else null; + const binding = decl.binding.toExpr(&hoist); + if (!can_be_moved) { value = value.joinWithComma( - binding.assign( - other, - ), + binding.assign(initializer), temp_allocator, ); } + } else { + _ = decl.binding.toExpr(&hoist); } - - if (value.isEmpty()) { - continue; - } - stmt = Stmt.alloc( - S.SExpr, - S.SExpr{ - .value = value, - }, - stmt.loc, - ); } + + if (value.isEmpty()) { + continue; + } + + break :stmt Stmt.allocateExpr(temp_allocator, value); }, - .s_class, .s_function => { - stmts.outside_wrapper_prefix.append(stmt) catch unreachable; + .s_function => { + stmts.outside_wrapper_prefix.append(stmt) catch bun.outOfMemory(); continue; }, - else => {}, - } - inner_stmts[end] = stmt; + .s_class => |class| stmt: { + if (class.class.canBeMoved()) { + stmts.outside_wrapper_prefix.append(stmt) catch bun.outOfMemory(); + continue; + } + + hoist.next_value = null; + + break :stmt Stmt.allocateExpr( + temp_allocator, + Expr.assign(hoist.wrapIdentifier( + class.class.class_name.?.loc, + class.class.class_name.?.ref.?, + ), .{ + .data = .{ .e_class = &class.class }, + .loc = stmt.loc, + }), + ); + }, + else => stmt, + }; + + inner_stmts[end] = transformed; end += 1; } inner_stmts.len = end; } - if (hoisty.decls.items.len > 0) { + if (hoist.decls.items.len > 0) { stmts.outside_wrapper_prefix.append( Stmt.alloc( S.Local, S.Local{ - .decls = G.Decl.List.fromList(hoisty.decls), + .decls = G.Decl.List.fromList(hoist.decls), }, Logger.Loc.Empty, ), ) catch unreachable; - hoisty.decls.items.len = 0; + hoist.decls.items.len = 0; } - // "__esm(() => { ... })" - var esm_args = temp_allocator.alloc(Expr, 1) catch unreachable; - esm_args[0] = Expr.init( - E.Arrow, - E.Arrow{ + if (inner_stmts.len > 0) { + // See the comment in needsWrapperRef for why the symbol + // is sometimes not generated. + bun.assert(!ast.wrapper_ref.isEmpty()); // js_parser's needsWrapperRef thought wrapper was not needed + + // "__esm(() => { ... })" + var esm_args = temp_allocator.alloc(Expr, 1) catch bun.outOfMemory(); + esm_args[0] = Expr.init(E.Arrow, .{ .args = &.{}, .is_async = is_async, .body = .{ .stmts = inner_stmts, .loc = Logger.Loc.Empty, }, - }, - Logger.Loc.Empty, - ); + }, Logger.Loc.Empty); - // "var init_foo = __esm(...);" - { - const value = Expr.init( - E.Call, - E.Call{ - .target = Expr.init( - E.Identifier, - E.Identifier{ - .ref = c.esm_runtime_ref, - }, - Logger.Loc.Empty, - ), - .args = bun.BabyList(Expr).init(esm_args), - }, - Logger.Loc.Empty, - ); + // "var init_foo = __esm(...);" + const value = Expr.init(E.Call, .{ + .target = Expr.initIdentifier(c.esm_runtime_ref, Logger.Loc.Empty), + .args = bun.BabyList(Expr).init(esm_args), + }, Logger.Loc.Empty); - var decls = temp_allocator.alloc(G.Decl, 1) catch unreachable; + var decls = temp_allocator.alloc(G.Decl, 1) catch bun.outOfMemory(); decls[0] = G.Decl{ .binding = Binding.alloc( temp_allocator, @@ -8929,14 +8926,53 @@ const LinkerContext = struct { }; stmts.outside_wrapper_prefix.append( - Stmt.alloc( - S.Local, - S.Local{ - .decls = G.Decl.List.init(decls), + Stmt.alloc(S.Local, .{ + .decls = G.Decl.List.init(decls), + }, Logger.Loc.Empty), + ) catch bun.outOfMemory(); + } else { + // // If this fails, then there will be places we reference + // // `init_foo` without it actually existing. + // bun.assert(ast.wrapper_ref.isEmpty()); + + // TODO: the edge case where we are wrong is when there + // are references to other ESM modules, but those get + // fully hoisted. The look like side effects, but they + // are removed. + // + // It is too late to retroactively delete the + // wrapper_ref, since printing has already begun. The + // most we can do to salvage the situation is to print + // an empty arrow function. + // + // This is marked as a TODO, because this can be solved + // via a count of external modules, decremented during + // linking. + if (!ast.wrapper_ref.isEmpty()) { + const value = Expr.init(E.Arrow, .{ + .args = &.{}, + .is_async = is_async, + .body = .{ + .stmts = inner_stmts, + .loc = Logger.Loc.Empty, }, - Logger.Loc.Empty, - ), - ) catch unreachable; + }, Logger.Loc.Empty); + + stmts.outside_wrapper_prefix.append( + Stmt.alloc(S.Local, .{ + .decls = G.Decl.List.fromSlice(temp_allocator, &.{.{ + .binding = Binding.alloc( + temp_allocator, + B.Identifier{ + .ref = ast.wrapper_ref, + }, + Logger.Loc.Empty, + ), + .value = value, + }}) catch bun.outOfMemory(), + }, Logger.Loc.Empty), + ) catch bun.outOfMemory(); + } } }, else => {}, @@ -10064,40 +10100,43 @@ const LinkerContext = struct { import_records: []bun.BabyList(bun.ImportRecord), entry_point_kinds: []EntryPoint.Kind, ) void { - if (comptime bun.Environment.allow_assert) - debugTreeShake( - "markFileLiveForTreeShaking({d}, {s}) = {s}", - .{ - source_index, - c.parse_graph.input_files.get(source_index).source.path.text, - if (c.graph.files_live.isSet(source_index)) "seen" else "not seen", - }, - ); + if (comptime bun.Environment.allow_assert) { + debugTreeShake("markFileLiveForTreeShaking({d}, {s}) = {s}", .{ + source_index, + c.parse_graph.input_files.get(source_index).source.path.text, + if (c.graph.files_live.isSet(source_index)) "seen" else "not seen", + }); + } - if (c.graph.files_live.isSet(source_index)) + defer if (Environment.allow_assert) { + debugTreeShake("end()", .{}); + }; + + if (c.graph.files_live.isSet(source_index)) { + if (Environment.allow_assert) { + debugTreeShake("already set", .{}); + } return; - + } c.graph.files_live.set(source_index); - // TODO: CSS source index - - const id = source_index; - if (@as(usize, id) >= c.graph.ast.len) + if (source_index >= c.graph.ast.len) { + bun.assert(false); return; - const _parts = parts[id].slice(); - for (_parts, 0..) |part, part_index| { + } + + for (parts[source_index].slice(), 0..) |part, part_index| { var can_be_removed_if_unused = part.can_be_removed_if_unused; if (can_be_removed_if_unused and part.tag == .commonjs_named_export) { - if (c.graph.meta.items(.flags)[id].wrap == .cjs) { + if (c.graph.meta.items(.flags)[source_index].wrap == .cjs) { can_be_removed_if_unused = false; } } // Also include any statement-level imports - for (part.import_record_indices.slice()) |import_record_Index| { - var record: *ImportRecord = &import_records[source_index].slice()[import_record_Index]; - + for (part.import_record_indices.slice()) |import_index| { + const record = import_records[source_index].at(import_index); if (record.kind != .stmt) continue; @@ -10106,7 +10145,11 @@ const LinkerContext = struct { // Don't include this module for its side effects if it can be // considered to have no side effects - if (side_effects[other_source_index] != .has_side_effects and !c.options.ignore_dce_annotations) { + const se = side_effects[other_source_index]; + + if (se != .has_side_effects and + !c.options.ignore_dce_annotations) + { continue; } @@ -10134,11 +10177,11 @@ const LinkerContext = struct { if (!can_be_removed_if_unused or (!part.force_tree_shaking and !c.options.tree_shaking and - entry_point_kinds[id].isEntryPoint())) + entry_point_kinds[source_index].isEntryPoint())) { - _ = c.markPartLiveForTreeShaking( - @as(u32, @intCast(part_index)), - id, + c.markPartLiveForTreeShaking( + @intCast(part_index), + source_index, side_effects, parts, import_records, @@ -10151,32 +10194,37 @@ const LinkerContext = struct { pub fn markPartLiveForTreeShaking( c: *LinkerContext, part_index: Index.Int, - id: Index.Int, + source_index: Index.Int, side_effects: []_resolver.SideEffects, parts: []bun.BabyList(js_ast.Part), import_records: []bun.BabyList(bun.ImportRecord), entry_point_kinds: []EntryPoint.Kind, - ) bool { - const part: *js_ast.Part = &parts[id].slice()[part_index]; + ) void { + const part: *js_ast.Part = &parts[source_index].slice()[part_index]; // only once if (part.is_live) { - return false; + return; } part.is_live = true; - if (comptime bun.Environment.isDebug) + if (comptime bun.Environment.isDebug) { debugTreeShake("markPartLiveForTreeShaking({d}): {s}:{d} = {d}, {s}", .{ - id, - c.parse_graph.input_files.get(id).source.path.text, + source_index, + c.parse_graph.input_files.get(source_index).source.path.text, part_index, if (part.stmts.len > 0) part.stmts[0].loc.start else Logger.Loc.Empty.start, if (part.stmts.len > 0) @tagName(part.stmts[0].data) else @tagName(Stmt.empty().data), }); + } + + defer if (Environment.allow_assert) { + debugTreeShake("end()", .{}); + }; // Include the file containing this part c.markFileLiveForTreeShaking( - id, + source_index, side_effects, parts, import_records, @@ -10185,18 +10233,18 @@ const LinkerContext = struct { if (Environment.enable_logs and part.dependencies.slice().len == 0) { logPartDependencyTree("markPartLiveForTreeShaking {d}:{d} | EMPTY", .{ - id, part_index, + source_index, part_index, }); } for (part.dependencies.slice()) |dependency| { - if (Environment.enable_logs and id != 0 and dependency.source_index.get() != 0) { + if (Environment.enable_logs and source_index != 0 and dependency.source_index.get() != 0) { logPartDependencyTree("markPartLiveForTreeShaking: {d}:{d} --> {d}:{d}\n", .{ - id, part_index, dependency.source_index.get(), dependency.part_index, + source_index, part_index, dependency.source_index.get(), dependency.part_index, }); } - _ = c.markPartLiveForTreeShaking( + c.markPartLiveForTreeShaking( dependency.part_index, dependency.source_index.get(), side_effects, @@ -10205,8 +10253,6 @@ const LinkerContext = struct { entry_point_kinds, ); } - - return true; } pub fn matchImportWithExport( @@ -10589,7 +10635,10 @@ const LinkerContext = struct { // // This depends on the "__esm" symbol and declares the "init_foo" symbol // for similar reasons to the CommonJS closure above. - const esm_parts = c.topLevelSymbolsToPartsForRuntime(c.esm_runtime_ref); + const esm_parts = if (wrapper_ref.isValid()) + c.topLevelSymbolsToPartsForRuntime(c.esm_runtime_ref) + else + &.{}; // generate a dummy part that depends on the "__esm" symbol const dependencies = c.allocator.alloc(js_ast.Dependency, esm_parts.len) catch unreachable; @@ -10618,13 +10667,16 @@ const LinkerContext = struct { ) catch unreachable; bun.assert(part_index != js_ast.namespace_export_part_index); wrapper_part_index.* = Index.part(part_index); - c.graph.generateSymbolImportAndUse( - source_index, - part_index, - c.esm_runtime_ref, - 1, - Index.runtime, - ) catch unreachable; + + if (wrapper_ref.isValid()) { + c.graph.generateSymbolImportAndUse( + source_index, + part_index, + c.esm_runtime_ref, + 1, + Index.runtime, + ) catch unreachable; + } }, else => {}, } diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 4bb12cce35..3603b1de02 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -107,6 +107,17 @@ pub const Action = union(enum) { visit: []const u8, print: []const u8, + /// bun.bundle_v2.LinkerContext.generateCompileResultForJSChunk + bundle_generate_chunk: if (bun.Environment.isDebug) struct { + context: *const anyopaque, // unfortunate dependency loop workaround + chunk: *const bun.bundle_v2.Chunk, + part_range: *const bun.bundle_v2.PartRange, + + pub fn linkerContext(data: *const @This()) *const bun.bundle_v2.LinkerContext { + return @ptrCast(@alignCast(data.context)); + } + } else void, + resolver: if (bun.Environment.isDebug) struct { source_dir: []const u8, import_path: []const u8, @@ -118,6 +129,25 @@ pub const Action = union(enum) { .parse => |path| try writer.print("parsing {s}", .{path}), .visit => |path| try writer.print("visiting {s}", .{path}), .print => |path| try writer.print("printing {s}", .{path}), + .bundle_generate_chunk => |data| if (bun.Environment.isDebug) { + try writer.print( + \\generating bundler chunk + \\ chunk entry point: {s} + \\ source: {s} + \\ part range: {d}..{d} + , + .{ + data.linkerContext().graph.bundler_graph.input_files + .items(.source)[data.chunk.entry_point.source_index] + .path.text, + data.linkerContext().graph.bundler_graph.input_files + .items(.source)[data.part_range.source_index.get()] + .path.text, + data.part_range.part_index_begin, + data.part_range.part_index_end, + }, + ); + }, .resolver => |res| if (bun.Environment.isDebug) { try writer.print("resolving {s} from {s} ({s})", .{ res.import_path, @@ -193,11 +223,10 @@ pub fn crashHandler( } writer.writeAll("oh no") catch std.posix.abort(); if (Output.enable_ansi_colors) { - writer.writeAll(Output.prettyFmt(": ", true)) catch std.posix.abort(); + writer.writeAll(Output.prettyFmt(": multiple threads are crashing\n", true)) catch std.posix.abort(); } else { - writer.writeAll(Output.prettyFmt(": ", true)) catch std.posix.abort(); + writer.writeAll(Output.prettyFmt(": multiple threads are crashing\n", true)) catch std.posix.abort(); } - writer.writeAll("multiple threads are crashing") catch std.posix.abort(); } if (reason != .out_of_memory or debug_trace) { @@ -252,6 +281,8 @@ pub fn crashHandler( }; if (debug_trace) { + has_printed_message = true; + dumpStackTrace(trace.*); trace_str_buf.writer().print("{}", .{TraceString{ diff --git a/src/js_ast.zig b/src/js_ast.zig index 4b1a188cef..323ccbda76 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -776,7 +776,7 @@ pub const G = struct { switch (val.data) { .e_arrow, .e_function => {}, else => { - if (!val.canBeConstValue()) { + if (!val.canBeMoved()) { return false; } }, @@ -3031,6 +3031,10 @@ pub const Stmt = struct { }; } + pub fn allocateExpr(allocator: std.mem.Allocator, expr: Expr) Stmt { + return Stmt.allocate(allocator, S.SExpr, S.SExpr{ .value = expr }, expr.loc); + } + pub const Tag = enum(u6) { s_block, s_break, @@ -3257,10 +3261,15 @@ pub const Expr = struct { else => true, }; } + pub fn canBeConstValue(this: Expr) bool { return this.data.canBeConstValue(); } + pub fn canBeMoved(expr: Expr) bool { + return expr.data.canBeMoved(); + } + pub fn unwrapInlined(expr: Expr) Expr { if (expr.data.as(.e_inlined_enum)) |inlined| return inlined.value; return expr; @@ -5507,9 +5516,17 @@ pub const Expr = struct { }; } + /// "const values" here refers to expressions that can participate in constant + /// inlining, as they have no side effects on instantiation, and there would be + /// no observable difference if duplicated. This is a subset of canBeMoved() pub fn canBeConstValue(this: Expr.Data) bool { return switch (this) { - .e_number, .e_boolean, .e_null, .e_undefined => true, + .e_number, + .e_boolean, + .e_null, + .e_undefined, + .e_inlined_enum, + => true, .e_string => |str| str.next == null, .e_array => |array| array.was_originally_macro, .e_object => |object| object.was_originally_macro, @@ -5517,6 +5534,39 @@ pub const Expr = struct { }; } + /// Expressions that can be moved are those that do not have side + /// effects on their own. This is used to determine what can be moved + /// outside of a module wrapper (__esm/__commonJS). + pub fn canBeMoved(data: Expr.Data) bool { + return switch (data) { + .e_class => |class| class.canBeMoved(), + + .e_arrow, + .e_function, + + .e_number, + .e_boolean, + .e_null, + .e_undefined, + // .e_reg_exp, + .e_big_int, + .e_string, + .e_inlined_enum, + .e_import_meta, + .e_utf8_string, + => true, + + .e_template => |template| template.parts.len == 0, + + .e_array => |array| array.was_originally_macro, + .e_object => |object| object.was_originally_macro, + + // TODO: experiment with allowing some e_binary, e_unary, e_if as movable + + else => false, + }; + } + pub fn knownPrimitive(data: Expr.Data) PrimitiveType { return switch (data) { .e_big_int => .bigint, @@ -6057,11 +6107,7 @@ pub const S = struct { pub fn canBeMoved(self: *const ExportDefault) bool { return switch (self.value) { - .expr => |e| switch (e.data) { - .e_class => |class| class.canBeMoved(), - .e_arrow, .e_function => true, - else => e.canBeConstValue(), - }, + .expr => |e| e.canBeMoved(), .stmt => |s| switch (s.data) { .s_class => |class| class.class.canBeMoved(), .s_function => true, diff --git a/src/js_parser.zig b/src/js_parser.zig index b2db172ade..30e164ef98 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -5338,7 +5338,7 @@ fn NewParser_( return p.newExpr(E.Import{ .expr = arg, - .import_record_index = Ref.toInt(import_record_index), + .import_record_index = @intCast(import_record_index), .options = state.import_options, }, state.loc); } @@ -5393,7 +5393,7 @@ fn NewParser_( return p.newExpr( E.RequireResolveString{ - .import_record_index = Ref.toInt(import_record_index), + .import_record_index = import_record_index, // .leading_interior_comments = arg.getString(). }, arg.loc, @@ -5428,11 +5428,12 @@ fn NewParser_( const handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0; - if ( // For unwrapping CommonJS into ESM to fully work // we must also unwrap requires into imports. - (p.unwrap_all_requires or p.options.features.shouldUnwrapRequire(path.packageName() orelse "")) and + const should_unwrap_require = p.unwrap_all_requires or + if (path.packageName()) |pkg| p.options.features.shouldUnwrapRequire(pkg) else false; + if (should_unwrap_require and // We cannot unwrap a require wrapped in a try/catch because // import statements cannot be wrapped in a try/catch and // require cannot return a promise. @@ -9473,7 +9474,7 @@ fn NewParser_( return Ref{ .inner_index = inner_index, - .source_index = Ref.toInt(p.source.index.get()), + .source_index = @intCast(p.source.index.get()), .tag = .symbol, }; } @@ -12891,13 +12892,19 @@ fn NewParser_( } if (@intFromPtr(p.source.contents.ptr) <= @intFromPtr(name.ptr) and (@intFromPtr(name.ptr) + name.len) <= (@intFromPtr(p.source.contents.ptr) + p.source.contents.len)) { - const start = Ref.toInt(@intFromPtr(name.ptr) - @intFromPtr(p.source.contents.ptr)); - const end = Ref.toInt(name.len); - return Ref.initSourceEnd(.{ .source_index = start, .inner_index = end, .tag = .source_contents_slice }); + return Ref.initSourceEnd(.{ + .source_index = @intCast(@intFromPtr(name.ptr) - @intFromPtr(p.source.contents.ptr)), + .inner_index = @intCast(name.len), + .tag = .source_contents_slice, + }); } else { - const inner_index = Ref.toInt(p.allocated_names.items.len); + const inner_index: u31 = @intCast(p.allocated_names.items.len); try p.allocated_names.append(p.allocator, name); - return Ref.init(inner_index, p.source.index.get(), false); + return Ref.init( + inner_index, + p.source.index.get(), + false, + ); } } @@ -22062,14 +22069,12 @@ fn NewParser_( }, .s_function => |data| { if ( - // Hoist module-level functions when - ((FeatureFlags.unwrap_commonjs_to_esm and p.current_scope == p.module_scope and !data.func.flags.contains(.is_export)) or - - // Manually hoist block-level function declarations to preserve semantics. - // This is only done for function declarations that are not generators - // or async functions, since this is a backwards-compatibility hack from - // Annex B of the JavaScript standard. - !p.current_scope.kindStopsHoisting()) and p.symbols.items[data.func.name.?.ref.?.innerIndex()].kind == .hoisted_function) + // Manually hoist block-level function declarations to preserve semantics. + // This is only done for function declarations that are not generators + // or async functions, since this is a backwards-compatibility hack from + // Annex B of the JavaScript standard. + !p.current_scope.kindStopsHoisting() and + p.symbols.items[data.func.name.?.ref.?.innerIndex()].kind == .hoisted_function) { break :list_getter &before; } @@ -22118,11 +22123,11 @@ fn NewParser_( // Merge the two identifiers back into a single one p.symbols.items[hoisted_ref.innerIndex()].link = name_ref; } - non_fn_stmts.append(stmt) catch unreachable; + non_fn_stmts.append(stmt) catch bun.outOfMemory(); continue; } - const gpe = fn_stmts.getOrPut(name_ref) catch unreachable; + const gpe = fn_stmts.getOrPut(name_ref) catch bun.outOfMemory(); var index = gpe.value_ptr.*; if (!gpe.found_existing) { index = @as(u32, @intCast(let_decls.items.len)); @@ -22147,7 +22152,7 @@ fn NewParser_( }, data.func.name.?.loc, ), - }) catch unreachable; + }) catch bun.outOfMemory(); } } @@ -23836,17 +23841,15 @@ fn NewParser_( } const wrapper_ref: Ref = brk: { - if (p.options.bundle) { + if (p.options.bundle and p.needsWrapperRef(parts)) { break :brk p.newSymbol( .other, std.fmt.allocPrint( p.allocator, "require_{any}", - .{ - p.source.fmtIdentifier(), - }, - ) catch unreachable, - ) catch unreachable; + .{p.source.fmtIdentifier()}, + ) catch bun.outOfMemory(), + ) catch bun.outOfMemory(); } break :brk Ref.None; @@ -23907,6 +23910,53 @@ fn NewParser_( }; } + /// The bundler will generate wrappers to contain top-level side effects using + /// the '__esm' helper. Example: + /// + /// var init_foo = __esm(() => { + /// someExport = Math.random(); + /// }); + /// + /// This wrapper can be removed if all of the constructs get moved + /// outside of the file. Due to paralleization, we can't retroactively + /// delete the `init_foo` symbol, but instead it must be known far in + /// advance if the symbol is needed or not. + /// + /// The logic in this function must be in sync with the hoisting + /// logic in `LinkerContext.generateCodeForFileInChunkJS` + fn needsWrapperRef(p: *const P, parts: []const js_ast.Part) bool { + bun.assert(p.options.bundle); + for (parts) |part| { + for (part.stmts) |stmt| { + switch (stmt.data) { + .s_function => {}, + .s_class => |class| if (!class.class.canBeMoved()) return true, + .s_local => |local| { + if (local.was_commonjs_export or p.commonjs_named_exports.count() == 0) { + for (local.decls.slice()) |decl| { + if (decl.value) |value| + if (value.data != .e_missing and !value.canBeMoved()) + return true; + } + continue; + } + return true; + }, + .s_export_default => |ed| { + if (!ed.canBeMoved()) + return true; + }, + .s_export_equals => |e| { + if (!e.value.canBeMoved()) + return true; + }, + else => return true, + } + } + } + return false; + } + pub fn init( allocator: Allocator, log: *logger.Log, diff --git a/src/js_printer.zig b/src/js_printer.zig index 58537858a7..63438f717e 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -2016,11 +2016,11 @@ fn NewPrinter( defer if (record.kind == .dynamic) p.printDotThenSuffix(); // Make sure the comma operator is properly wrapped - - if (meta.exports_ref.isValid() and level.gte(.comma)) { - p.print("("); - } - defer if (meta.exports_ref.isValid() and level.gte(.comma)) p.print(")"); + const wrap_comma_operator = meta.exports_ref.isValid() and + meta.wrapper_ref.isValid() and + level.gte(.comma); + if (wrap_comma_operator) p.print("("); + defer if (wrap_comma_operator) p.print(")"); // Wrap this with a call to "__toESM()" if this is a CommonJS file const wrap_with_to_esm = record.wrap_with_to_esm; @@ -2031,17 +2031,20 @@ fn NewPrinter( } if (!meta.was_unwrapped_require) { - // Call the wrapper - p.printSpaceBeforeIdentifier(); - p.printSymbol(meta.wrapper_ref); - p.print("()"); + if (meta.wrapper_ref.isValid()) { + p.printSpaceBeforeIdentifier(); + p.printSymbol(meta.wrapper_ref); + p.print("()"); + + if (meta.exports_ref.isValid()) { + p.print(","); + p.printSpace(); + } + } // Return the namespace object if this is an ESM file if (meta.exports_ref.isValid()) { - p.print(","); - p.printSpace(); - // Wrap this with a call to "__toCommonJS()" if this is an ESM file const wrap_with_to_cjs = record.wrap_with_to_commonjs; if (wrap_with_to_cjs) { diff --git a/src/options.zig b/src/options.zig index 23a43b04a7..a1f7f0a074 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1545,7 +1545,6 @@ pub const BundleOptions = struct { "react-client", "react-server", "react-refresh", - "__bun-test-unwrap-commonjs__", }; pub inline fn cssImportBehavior(this: *const BundleOptions) Api.CssInJsBehavior { diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig index c74338ecf7..50fdd81052 100644 --- a/src/resolver/package_json.zig +++ b/src/resolver/package_json.zig @@ -37,7 +37,6 @@ const FolderResolver = @import("../install/resolvers/folder_resolver.zig"); const Architecture = @import("../install/npm.zig").Architecture; const OperatingSystem = @import("../install/npm.zig").OperatingSystem; -pub const SideEffectsMap = std.HashMapUnmanaged(bun.StringHashMapUnowned.Key, void, bun.StringHashMapUnowned.Adapter, 80); pub const DependencyMap = struct { map: HashMap = .{}, source_buf: []const u8 = "", @@ -112,11 +111,7 @@ pub const PackageJSON = struct { package_manager_package_id: Install.PackageID = Install.invalid_package_id, dependencies: DependencyMap = .{}, - side_effects: union(enum) { - unspecified: void, - false: void, - map: SideEffectsMap, - } = .{ .unspecified = {} }, + side_effects: SideEffects = .unspecified, // Present if the "browser" field is present. This field is intended to be // used by bundlers and lets you redirect the paths of certain 3rd-party @@ -148,6 +143,33 @@ pub const PackageJSON = struct { exports: ?ExportsMap = null, imports: ?ExportsMap = null, + pub const SideEffects = union(enum) { + /// either `package.json` is missing "sideEffects", it is true, or some + /// other unsupported value. Treat all files as side effects + unspecified: void, + /// "sideEffects": false + false: void, + /// "sideEffects": ["file.js", "other.js"] + map: Map, + // /// "sideEffects": ["side_effects/*.js"] + // glob: TODO, + + pub const Map = std.HashMapUnmanaged( + bun.StringHashMapUnowned.Key, + void, + bun.StringHashMapUnowned.Adapter, + 80, + ); + + pub fn hasSideEffects(side_effects: SideEffects, path: []const u8) bool { + return switch (side_effects) { + .unspecified => true, + .false => false, + .map => |map| map.contains(bun.StringHashMapUnowned.Key.init(path)), + }; + } + }; + pub inline fn isAppPackage(this: *const PackageJSON) bool { return this.hash == 0xDEADBEEF; } @@ -776,7 +798,7 @@ pub const PackageJSON = struct { } else if (side_effects_field.asArray()) |array_| { var array = array_; // TODO: switch to only storing hashes - var map = SideEffectsMap{}; + var map = SideEffects.Map{}; map.ensureTotalCapacity(allocator, array.array.items.len) catch unreachable; while (array.next()) |item| { if (item.asString(allocator)) |name| { diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 74cbb8687b..f8f027e9e9 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -171,9 +171,9 @@ pub const SideEffects = enum { /// known to not have side effects. no_side_effects__pure_data, - // / Same as above but it came from a plugin. We don't want to warn about - // / unused imports to these files since running the plugin is a side effect. - // / Removing the import would not call the plugin which is observable. + // /// Same as above but it came from a plugin. We don't want to warn about + // /// unused imports to these files since running the plugin is a side effect. + // /// Removing the import would not call the plugin which is observable. // no_side_effects__pure_data_from_plugin, }; diff --git a/src/runtime.js b/src/runtime.js index 460a1768f7..f1adb2e6c8 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -1,13 +1,17 @@ +// Since runtime.js loads first in the bundler, Ref.none will point at this +// value. And since it isnt exported, it will always be tree-shaken away. +var __INVALID__REF__; + var tagSymbol; var cjsRequireSymbol; +// This ordering is deliberate so that the printer does optimizes these into a +// single destructuring assignment. var __create = Object.create; var __descs = Object.getOwnPropertyDescriptors; -var __defProp = Object.defineProperty; var __getProtoOf = Object.getPrototypeOf; +var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; - -// This order is deliberate so that the printer does the {} optimization here var __hasOwnProp = Object.prototype.hasOwnProperty; export var __markAsModule = target => __defProp(target, "__esModule", { value: true, configurable: true }); @@ -63,22 +67,23 @@ export var __toESM = (mod, isNodeMode, target) => { // Converts the module from ESM to CommonJS. This clones the input module // object with the addition of a non-enumerable "__esModule" property set // to "true", which overwrites any existing export named "__esModule". +var __moduleCache = /* @__PURE__ */ new WeakMap(); export var __toCommonJS = /* @__PURE__ */ from => { - const moduleCache = (__toCommonJS.moduleCache ??= new WeakMap()); - var cached = moduleCache.get(from); - if (cached) return cached; - var to = __defProp({}, "__esModule", { value: true }); - var desc = { enumerable: false }; + var entry = __moduleCache.get(from), + desc; + if (entry) return entry; + entry = __defProp({}, "__esModule", { value: true }); if ((from && typeof from === "object") || typeof from === "function") - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key)) - __defProp(to, key, { + __getOwnPropNames(from).map( + key => + !__hasOwnProp.call(entry, key) && + __defProp(entry, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, - }); - - moduleCache.set(from, to); - return to; + }), + ); + __moduleCache.set(from, entry); + return entry; }; // lazy require to prevent loading one icon from a design system diff --git a/test/bundler/__snapshots__/bun-build-api.test.ts.snap b/test/bundler/__snapshots__/bun-build-api.test.ts.snap index 32cf05e8c8..e17a2204bc 100644 --- a/test/bundler/__snapshots__/bun-build-api.test.ts.snap +++ b/test/bundler/__snapshots__/bun-build-api.test.ts.snap @@ -11,7 +11,6 @@ var __export = (target, all) => { set: (newValue) => all[name] = () => newValue }); }; -var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res); // test/bundler/fixtures/trivial/fn.js var exports_fn = {}; @@ -21,11 +20,9 @@ __export(exports_fn, { function fn(a) { return a + 42; } -var init_fn = __esm(() => { -}); // test/bundler/fixtures/trivial/index.js -var NS = Promise.resolve().then(() => (init_fn(), exports_fn)); +var NS = Promise.resolve().then(() => exports_fn); NS.then(({ fn: fn2 }) => { console.log(fn2(42)); }); @@ -43,7 +40,6 @@ var __export = (target, all) => { set: (newValue) => all[name] = () => newValue }); }; -var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res); // test/bundler/fixtures/trivial/fn.js var exports_fn = {}; @@ -53,22 +49,20 @@ __export(exports_fn, { function fn(a) { return a + 42; } -var init_fn = __esm(() => { -}); // test/bundler/fixtures/trivial/index.js -var NS = Promise.resolve().then(() => (init_fn(), exports_fn)); +var NS = Promise.resolve().then(() => exports_fn); NS.then(({ fn: fn2 }) => { console.log(fn2(42)); }); " `; -exports[`Bun.build BuildArtifact properties: hash 1`] = `"cv02d0ez"`; +exports[`Bun.build BuildArtifact properties: hash 1`] = `"r6c8x1cc"`; -exports[`Bun.build BuildArtifact properties + entry.naming: hash 1`] = `"3v155a0d"`; +exports[`Bun.build BuildArtifact properties + entry.naming: hash 1`] = `"vanwb97w"`; -exports[`Bun.build BuildArtifact properties sourcemap: hash index.js 1`] = `"cv02d0ez"`; +exports[`Bun.build BuildArtifact properties sourcemap: hash index.js 1`] = `"r6c8x1cc"`; exports[`Bun.build BuildArtifact properties sourcemap: hash index.js.map 1`] = `"00000000"`; @@ -83,7 +77,6 @@ var __export = (target, all) => { set: (newValue) => all[name] = () => newValue }); }; -var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res); // test/bundler/fixtures/trivial/fn.js var exports_fn = {}; @@ -93,11 +86,9 @@ __export(exports_fn, { function fn(a) { return a + 42; } -var init_fn = __esm(() => { -}); // test/bundler/fixtures/trivial/index.js -var NS = Promise.resolve().then(() => (init_fn(), exports_fn)); +var NS = Promise.resolve().then(() => exports_fn); NS.then(({ fn: fn2 }) => { console.log(fn2(42)); }); diff --git a/test/bundler/bundler_cjs2esm.test.ts b/test/bundler/bundler_cjs2esm.test.ts index 9cfce2d77b..6aff61dfbc 100644 --- a/test/bundler/bundler_cjs2esm.test.ts +++ b/test/bundler/bundler_cjs2esm.test.ts @@ -238,19 +238,19 @@ describe("bundler", () => { "/entry.js": /* js */ ` const react = require("react"); console.log(react.react); - + const react1 = (console.log(require("react").react), require("react")); console.log(react1.react); - + const react2 = (require("react"), console.log(require("react").react)); console.log(react2); - + let x = {}; x.react = require("react"); console.log(x.react.react); - + console.log(require("react").react); - + let y = {}; y[require("react")] = require("react"); console.log(y[require("react")].react); @@ -284,4 +284,27 @@ describe("bundler", () => { stdout: "react\nreact\nreact\nreact\nundefined\nreact\nreact\nreact\nreact\nreact\nreact\n1 react\nreact\nreact", }, }); + itBundled("cjs2esm/ReactSpecificUnwrapping", { + files: { + "/entry.js": /* js */ ` + import { renderToReadableStream } from "react"; + console.log(renderToReadableStream()); + `, + "/node_modules/react/index.js": /* js */ ` + console.log('side effect'); + module.exports = require('./main'); + `, + "/node_modules/react/main.js": /* js */ ` + "use strict"; + var REACT_ELEMENT_TYPE = Symbol.for("pass"); + exports.renderToReadableStream = (e, t) => { + return REACT_ELEMENT_TYPE; + } + `, + }, + run: { + stdout: "side effect\nSymbol(pass)", + }, + minifySyntax: true, + }); }); diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index c51cda9f18..49b992fba1 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -1618,6 +1618,53 @@ describe("bundler", () => { stdout: `{"default":{"hello":"world"}}`, }, }); + itBundled("edgecase/EsmWrapperClassHoisting", { + files: { + "/entry.ts": ` + async function hi() { + const { default: MyInherited } = await import('./hello'); + const myInstance = new MyInherited(); + console.log(myInstance.greet()) + } + + hi(); + `, + "/hello.ts": ` + const MyReassignedSuper = class MySuper { + greet() { + return 'Hello, world!'; + } + }; + + class MyInherited extends MyReassignedSuper {}; + + export default MyInherited; + `, + }, + run: { + stdout: "Hello, world!", + }, + }); + itBundled("edgecase/EsmWrapperElimination1", { + files: { + "/entry.ts": ` + async function load() { + return import('./hello'); + } + load().then(({ default: def }) => console.log(def())); + `, + "/hello.ts": ` + export var x = 123; + export var y = function() { return x; }; + export function z() { return y(); } + function a() { return z(); } + export default function c() { return a(); } + `, + }, + run: { + stdout: "123", + }, + }); // TODO(@paperdave): test every case of this. I had already tested it manually, but it may break later const requireTranspilationListESM = [ diff --git a/test/transpiler/__snapshots__/transpiler.test.js.snap b/test/bundler/transpiler/__snapshots__/transpiler.test.js.snap similarity index 100% rename from test/transpiler/__snapshots__/transpiler.test.js.snap rename to test/bundler/transpiler/__snapshots__/transpiler.test.js.snap diff --git a/test/transpiler/async-transpiler-entry.js b/test/bundler/transpiler/async-transpiler-entry.js similarity index 100% rename from test/transpiler/async-transpiler-entry.js rename to test/bundler/transpiler/async-transpiler-entry.js diff --git a/test/transpiler/async-transpiler-imported.js b/test/bundler/transpiler/async-transpiler-imported.js similarity index 100% rename from test/transpiler/async-transpiler-imported.js rename to test/bundler/transpiler/async-transpiler-imported.js diff --git a/test/transpiler/decorator-export-default-class-fixture-anon.ts b/test/bundler/transpiler/decorator-export-default-class-fixture-anon.ts similarity index 100% rename from test/transpiler/decorator-export-default-class-fixture-anon.ts rename to test/bundler/transpiler/decorator-export-default-class-fixture-anon.ts diff --git a/test/transpiler/decorator-export-default-class-fixture.ts b/test/bundler/transpiler/decorator-export-default-class-fixture.ts similarity index 100% rename from test/transpiler/decorator-export-default-class-fixture.ts rename to test/bundler/transpiler/decorator-export-default-class-fixture.ts diff --git a/test/transpiler/decorator-metadata.test.ts b/test/bundler/transpiler/decorator-metadata.test.ts similarity index 100% rename from test/transpiler/decorator-metadata.test.ts rename to test/bundler/transpiler/decorator-metadata.test.ts diff --git a/test/transpiler/decorators.test.ts b/test/bundler/transpiler/decorators.test.ts similarity index 100% rename from test/transpiler/decorators.test.ts rename to test/bundler/transpiler/decorators.test.ts diff --git a/test/transpiler/export-default-with-static-initializer.js b/test/bundler/transpiler/export-default-with-static-initializer.js similarity index 100% rename from test/transpiler/export-default-with-static-initializer.js rename to test/bundler/transpiler/export-default-with-static-initializer.js diff --git a/test/transpiler/export-default.test.js b/test/bundler/transpiler/export-default.test.js similarity index 100% rename from test/transpiler/export-default.test.js rename to test/bundler/transpiler/export-default.test.js diff --git a/test/transpiler/handlebars.hbs b/test/bundler/transpiler/handlebars.hbs similarity index 100% rename from test/transpiler/handlebars.hbs rename to test/bundler/transpiler/handlebars.hbs diff --git a/test/transpiler/inline.macro.js b/test/bundler/transpiler/inline.macro.js similarity index 100% rename from test/transpiler/inline.macro.js rename to test/bundler/transpiler/inline.macro.js diff --git a/test/transpiler/macro-check.js b/test/bundler/transpiler/macro-check.js similarity index 100% rename from test/transpiler/macro-check.js rename to test/bundler/transpiler/macro-check.js diff --git a/test/transpiler/macro-test.test.ts b/test/bundler/transpiler/macro-test.test.ts similarity index 100% rename from test/transpiler/macro-test.test.ts rename to test/bundler/transpiler/macro-test.test.ts diff --git a/test/transpiler/macro.ts b/test/bundler/transpiler/macro.ts similarity index 100% rename from test/transpiler/macro.ts rename to test/bundler/transpiler/macro.ts diff --git a/test/transpiler/property-non-ascii-fixture.js b/test/bundler/transpiler/property-non-ascii-fixture.js similarity index 100% rename from test/transpiler/property-non-ascii-fixture.js rename to test/bundler/transpiler/property-non-ascii-fixture.js diff --git a/test/transpiler/property.test.ts b/test/bundler/transpiler/property.test.ts similarity index 100% rename from test/transpiler/property.test.ts rename to test/bundler/transpiler/property.test.ts diff --git a/test/transpiler/runtime-transpiler-fixture-duplicate-keys.json b/test/bundler/transpiler/runtime-transpiler-fixture-duplicate-keys.json similarity index 100% rename from test/transpiler/runtime-transpiler-fixture-duplicate-keys.json rename to test/bundler/transpiler/runtime-transpiler-fixture-duplicate-keys.json diff --git a/test/transpiler/runtime-transpiler-json-fixture.json b/test/bundler/transpiler/runtime-transpiler-json-fixture.json similarity index 100% rename from test/transpiler/runtime-transpiler-json-fixture.json rename to test/bundler/transpiler/runtime-transpiler-json-fixture.json diff --git a/test/transpiler/runtime-transpiler.test.ts b/test/bundler/transpiler/runtime-transpiler.test.ts similarity index 100% rename from test/transpiler/runtime-transpiler.test.ts rename to test/bundler/transpiler/runtime-transpiler.test.ts diff --git a/test/transpiler/template-literal-fixture-test.js b/test/bundler/transpiler/template-literal-fixture-test.js similarity index 100% rename from test/transpiler/template-literal-fixture-test.js rename to test/bundler/transpiler/template-literal-fixture-test.js diff --git a/test/transpiler/template-literal.test.ts b/test/bundler/transpiler/template-literal.test.ts similarity index 100% rename from test/transpiler/template-literal.test.ts rename to test/bundler/transpiler/template-literal.test.ts diff --git a/test/transpiler/transpiler-stack-overflow.test.ts b/test/bundler/transpiler/transpiler-stack-overflow.test.ts similarity index 100% rename from test/transpiler/transpiler-stack-overflow.test.ts rename to test/bundler/transpiler/transpiler-stack-overflow.test.ts diff --git a/test/transpiler/transpiler.test.js b/test/bundler/transpiler/transpiler.test.js similarity index 99% rename from test/transpiler/transpiler.test.js rename to test/bundler/transpiler/transpiler.test.js index 357925e6ae..581591fb3e 100644 --- a/test/transpiler/transpiler.test.js +++ b/test/bundler/transpiler/transpiler.test.js @@ -617,7 +617,7 @@ describe("Bun.Transpiler", () => { exp("class Foo {}", "class Foo {\n}"); exp("Foo = class {}", "Foo = class {\n}"); exp("Foo = class Bar {}", "Foo = class Bar {\n}"); - exp("function foo() {}", "let foo = function() {\n}"); + exp("function foo() {}", "function foo() {\n}"); exp("foo = function () {}", "foo = function() {\n}"); exp("foo = function bar() {}", "foo = function bar() {\n}"); exp("class Foo { bar() {} }", "class Foo {\n bar() {\n }\n}"); diff --git a/test/transpiler/tsconfig.is-just-a-number.json b/test/bundler/transpiler/tsconfig.is-just-a-number.json similarity index 100% rename from test/transpiler/tsconfig.is-just-a-number.json rename to test/bundler/transpiler/tsconfig.is-just-a-number.json diff --git a/test/transpiler/tsconfig.with-commas.json b/test/bundler/transpiler/tsconfig.with-commas.json similarity index 100% rename from test/transpiler/tsconfig.with-commas.json rename to test/bundler/transpiler/tsconfig.with-commas.json diff --git a/test/transpiler/with-statement-works.js b/test/bundler/transpiler/with-statement-works.js similarity index 100% rename from test/transpiler/with-statement-works.js rename to test/bundler/transpiler/with-statement-works.js diff --git a/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/snapshot.test.ts.snap b/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/snapshot.test.ts.snap index 51953013d8..47112120bc 100644 --- a/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/snapshot.test.ts.snap +++ b/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/snapshot.test.ts.snap @@ -172,7 +172,7 @@ exports[`most types: Float32Array 1`] = `Float32Array []`; exports[`most types: Float64Array 1`] = `Float64Array []`; -exports[`most types: Function 1`] = `[Function]`; +exports[`most types: Function 1`] = `[Function: test1000000]`; exports[`most types: Int8Array 1`] = `Int8Array []`; From 2da57f6d7b30630d3441a07e1bc5c3445565de05 Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:27:51 -0700 Subject: [PATCH 053/123] `napi_threadsafe_function` async tracker (#12780) --- .lldbinit | 2 + .vscode/settings.json | 5 ++- src/bun.js/ConsoleObject.zig | 3 +- src/bun.js/bindings/bindings.cpp | 1 + src/bun.js/event_loop.zig | 8 ++-- src/linker.lds | 1 + src/napi/napi.zig | 41 +++++++++++++------- src/symbols.dyn | 1 + src/symbols.txt | 1 + test/js/web/console/console-log.expected.txt | 1 + test/js/web/console/console-log.js | 3 ++ 11 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 .lldbinit diff --git a/.lldbinit b/.lldbinit new file mode 100644 index 0000000000..2d527f4e63 --- /dev/null +++ b/.lldbinit @@ -0,0 +1,2 @@ +command script import src/deps/zig/tools/lldb_pretty_printers.py +command script import src/bun.js/WebKit/Tools/lldb/lldb_webkit.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 91939e9e58..1701cb55df 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,8 +42,11 @@ "editor.defaultFormatter": "ziglang.vscode-zig", }, - // C++ + // lldb + "lldb.launch.initCommands": ["command source ${workspaceFolder}/.lldbinit"], "lldb.verboseLogging": false, + + // C++ "cmake.configureOnOpen": false, "C_Cpp.errorSquiggles": "enabled", "[cpp]": { diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index 04553fa537..4c0c584847 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -1283,6 +1283,7 @@ pub const Formatter = struct { writer.writeAll(end); // then skip the second % so we dont hit it again slice = slice[@min(slice.len, i + 1)..]; + len = @truncate(slice.len); i = 0; continue; }, @@ -1295,7 +1296,7 @@ pub const Formatter = struct { slice = slice[@min(slice.len, i + 1)..]; i = 0; hit_percent = true; - len = @as(u32, @truncate(slice.len)); + len = @truncate(slice.len); const next_value = this.remaining_values[0]; this.remaining_values = this.remaining_values[1..]; diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 5a1462e869..876d97b8fd 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -3056,6 +3056,7 @@ void JSC__JSPromise__reject(JSC__JSPromise* arg0, JSC__JSGlobalObject* globalObj JSC__JSValue JSValue2) { JSValue value = JSC::JSValue::decode(JSValue2); + ASSERT_WITH_MESSAGE(!value.isEmpty(), "Promise.reject cannot be called with a empty JSValue"); auto& vm = globalObject->vm(); ASSERT_WITH_MESSAGE(arg0->inherits(), "Argument is not a promise"); ASSERT_WITH_MESSAGE(arg0->status(vm) == JSC::JSPromise::Status::Pending, "Promise is already resolved or rejected"); diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index f8ec533624..468ae8ba67 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -771,7 +771,7 @@ pub const EventLoop = struct { waker: ?Waker = null, forever_timer: ?*uws.Timer = null, deferred_tasks: DeferredTaskQueue = .{}, - uws_loop: if (Environment.isWindows) *uws.Loop else void = undefined, + uws_loop: if (Environment.isWindows) ?*uws.Loop else void = if (Environment.isWindows) null else {}, debug: Debug = .{}, entered_event_loop_count: isize = 0, @@ -1324,7 +1324,7 @@ pub const EventLoop = struct { inline fn usocketsLoop(this: *const EventLoop) *uws.Loop { if (comptime Environment.isWindows) { - return this.uws_loop; + return this.uws_loop.?; } return this.virtual_machine.event_loop_handle.?; @@ -1572,7 +1572,9 @@ pub const EventLoop = struct { pub fn wakeup(this: *EventLoop) void { if (comptime Environment.isWindows) { - this.uws_loop.wakeup(); + if (this.uws_loop) |loop| { + loop.wakeup(); + } return; } diff --git a/src/linker.lds b/src/linker.lds index 27c44da312..9aee7b8227 100644 --- a/src/linker.lds +++ b/src/linker.lds @@ -5,6 +5,7 @@ BUN_1.1 { extern "C++" { v8::*; node::*; + JSC::CallFrame::describeFrame; }; local: *; diff --git a/src/napi/napi.zig b/src/napi/napi.zig index 25ace47662..ac89ea3349 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -972,7 +972,7 @@ pub export fn napi_resolve_deferred(env: napi_env, deferred: napi_deferred, reso log("napi_resolve_deferred", .{}); var prom = deferred.get(); prom.resolve(env, resolution); - deferred.*.strong.deinit(); + deferred.deinit(); bun.default_allocator.destroy(deferred); return .ok; } @@ -980,7 +980,7 @@ pub export fn napi_reject_deferred(env: napi_env, deferred: napi_deferred, rejec log("napi_reject_deferred", .{}); var prom = deferred.get(); prom.reject(env, rejection); - deferred.*.strong.deinit(); + deferred.deinit(); bun.default_allocator.destroy(deferred); return .ok; } @@ -990,9 +990,8 @@ pub export fn napi_is_promise(_: napi_env, value: napi_value, is_promise_: ?*boo return invalidArg(); }; - if (value.isEmptyOrUndefinedOrNull()) { - is_promise.* = false; - return .ok; + if (value.isEmpty()) { + return invalidArg(); } is_promise.* = value.asAnyPromise() != null; @@ -1422,10 +1421,10 @@ pub const ThreadSafeFunction = struct { thread_count: usize = 0, owning_thread_lock: Lock = Lock.init(), event_loop: *JSC.EventLoop, + tracker: JSC.AsyncTaskTracker, env: napi_env, - finalizer_task: JSC.AnyTask = undefined, finalizer: Finalizer = Finalizer{ .fun = null, .data = null }, channel: Queue, @@ -1503,18 +1502,30 @@ pub const ThreadSafeFunction = struct { pub fn call(this: *ThreadSafeFunction) void { const task = this.channel.tryReadItem() catch null orelse return; + const vm = this.event_loop.virtual_machine; + const globalObject = this.env; + + this.tracker.willDispatch(globalObject); + defer this.tracker.didDispatch(globalObject); + switch (this.callback) { .js => |js_function| { if (js_function.isEmptyOrUndefinedOrNull()) { return; } - const err = js_function.call(this.env, &.{}); + const err = js_function.call(globalObject, &.{}); if (err.isAnyError()) { - _ = this.env.bunVM().uncaughtException(this.env, err, false); + _ = vm.uncaughtException(globalObject, err, false); } }, .c => |cb| { - cb.napi_threadsafe_function_call_js(this.env, cb.js, this.ctx, task); + if (comptime bun.Environment.isDebug) { + const str = cb.js.toBunString(globalObject); + defer str.deref(); + log("call() {}", .{str}); + } + + cb.napi_threadsafe_function_call_js(globalObject, cb.js, this.ctx, task); }, } } @@ -1584,7 +1595,6 @@ pub const ThreadSafeFunction = struct { } if (mode == .abort or this.thread_count == 0) { - this.finalizer_task = JSC.AnyTask{ .ctx = this, .callback = finalize }; this.event_loop.enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, finalize)); } @@ -1618,27 +1628,30 @@ pub export fn napi_create_threadsafe_function( func.protect(); } + const vm = env.bunVM(); var function = bun.default_allocator.create(ThreadSafeFunction) catch return genericFailure(); function.* = .{ - .event_loop = env.bunVM().eventLoop(), + .event_loop = vm.eventLoop(), .env = env, .callback = if (call_js_cb) |c| .{ .c = .{ .napi_threadsafe_function_call_js = c, - .js = if (func == .zero) JSC.JSValue.jsUndefined() else func, + .js = if (func == .zero) JSC.JSValue.jsUndefined() else func.withAsyncContextIfNeeded(env), }, } else .{ - .js = if (func == .zero) JSC.JSValue.jsUndefined() else func, + .js = if (func == .zero) JSC.JSValue.jsUndefined() else func.withAsyncContextIfNeeded(env), }, .ctx = context, .channel = ThreadSafeFunction.Queue.init(max_queue_size, bun.default_allocator), .thread_count = initial_thread_count, .poll_ref = Async.KeepAlive.init(), + .tracker = JSC.AsyncTaskTracker.init(vm), }; function.finalizer = .{ .data = thread_finalize_data, .fun = thread_finalize_cb }; // nodejs by default keeps the event loop alive until the thread-safe function is unref'd function.ref(); + function.tracker.didSchedule(vm.global); result.* = function; return .ok; @@ -1673,14 +1686,12 @@ pub export fn napi_release_threadsafe_function(func: napi_threadsafe_function, m pub export fn napi_unref_threadsafe_function(env: napi_env, func: napi_threadsafe_function) napi_status { log("napi_unref_threadsafe_function", .{}); bun.assert(func.event_loop.global == env); - func.unref(); return .ok; } pub export fn napi_ref_threadsafe_function(env: napi_env, func: napi_threadsafe_function) napi_status { log("napi_ref_threadsafe_function", .{}); bun.assert(func.event_loop.global == env); - func.ref(); return .ok; } diff --git a/src/symbols.dyn b/src/symbols.dyn index 44160f2867..2d16df145d 100644 --- a/src/symbols.dyn +++ b/src/symbols.dyn @@ -154,4 +154,5 @@ __ZN2v87Isolate17GetCurrentContextEv; __ZN4node25AddEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_; __ZN4node28RemoveEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_; + __ZN3JSC9CallFrame13describeFrameEv; }; \ No newline at end of file diff --git a/src/symbols.txt b/src/symbols.txt index 577035fa93..2e24afa1c4 100644 --- a/src/symbols.txt +++ b/src/symbols.txt @@ -153,3 +153,4 @@ __ZN2v87Isolate13TryGetCurrentEv __ZN2v87Isolate17GetCurrentContextEv __ZN4node25AddEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_ __ZN4node28RemoveEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_ +__ZN3JSC9CallFrame13describeFrameEv \ No newline at end of file diff --git a/test/js/web/console/console-log.expected.txt b/test/js/web/console/console-log.expected.txt index 167512e7bd..86cff95206 100644 --- a/test/js/web/console/console-log.expected.txt +++ b/test/js/web/console/console-log.expected.txt @@ -284,3 +284,4 @@ Hello NaN % 1 Hello NaN %j 1 Hello \5 6, Hello %i 5 6 +%d 1 diff --git a/test/js/web/console/console-log.js b/test/js/web/console/console-log.js index 46357f219d..32a817bc78 100644 --- a/test/js/web/console/console-log.js +++ b/test/js/web/console/console-log.js @@ -261,3 +261,6 @@ console.log("Hello %i %", [1, 2, 3, 4], 1); console.log("Hello %i %j", [1, 2, 3, 4], 1); console.log("Hello \\%i %i,", 5, 6); console.log("Hello %%i %i", 5, 6); + +// doesn't go out of bounds when printing +console.log("%%d", 1); From 24574dddb276d916418f0533c2834c210eba156e Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 24 Jul 2024 15:56:05 -0700 Subject: [PATCH 054/123] Ensure LLVM 18 with Homebrew on macOS --- scripts/env.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/env.sh b/scripts/env.sh index c60d9edfe8..1ec1248e4c 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -24,12 +24,7 @@ export BUN_DEPS_OUT_DIR=${BUN_DEPS_OUT_DIR:-$BUN_BASE_DIR/build/bun-deps} export LC_CTYPE="en_US.UTF-8" export LC_ALL="en_US.UTF-8" -if [[ "$CI" != "1" && "$CI" != "true" ]]; then - if [ -f $SCRIPT_DIR/env.local ]; then - echo "Sourcing $SCRIPT_DIR/env.local" - source $SCRIPT_DIR/env.local - fi -elif [[ $(uname -s) == 'Darwin' ]]; then +if [[ $(uname -s) == 'Darwin' ]]; then export CXX="$(brew --prefix llvm)@$LLVM_VERSION/bin/clang++" export CC="$(brew --prefix llvm)@$LLVM_VERSION/bin/clang" export AR="$(brew --prefix llvm)@$LLVM_VERSION/bin/llvm-ar" @@ -37,6 +32,11 @@ elif [[ $(uname -s) == 'Darwin' ]]; then export LIBTOOL="$(brew --prefix llvm)@$LLVM_VERSION/bin/llvm-libtool-darwin" export PATH="$(brew --prefix llvm)@$LLVM_VERSION/bin:$PATH" ln -sf $LIBTOOL "$(brew --prefix llvm)@$LLVM_VERSION/bin/libtool" || true +elif [[ "$CI" != "1" && "$CI" != "true" ]]; then + if [[ -f $SCRIPT_DIR/env.local ]]; then + echo "Sourcing $SCRIPT_DIR/env.local" + source $SCRIPT_DIR/env.local + fi fi # this compiler detection could be better From ac4523e903045bd6be84a8f3d50af903262b076c Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:57:01 -0700 Subject: [PATCH 055/123] fix(napi): unref threadsafe functions on finalize (#12788) --- src/napi/napi.zig | 2 ++ test/napi/napi-app/main.cpp | 39 +++++++++++++++++++++++++++++++++---- test/napi/napi.test.ts | 5 +++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/napi/napi.zig b/src/napi/napi.zig index ac89ea3349..3c0363ec8f 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -1544,6 +1544,8 @@ pub const ThreadSafeFunction = struct { pub fn finalize(opaq: *anyopaque) void { var this = bun.cast(*ThreadSafeFunction, opaq); + this.unref(); + if (this.finalizer.fun) |fun| { fun(this.event_loop.global, this.finalizer.data, this.ctx); } diff --git a/test/napi/napi-app/main.cpp b/test/napi/napi-app/main.cpp index f443ee79d7..61f7c564c7 100644 --- a/test/napi/napi-app/main.cpp +++ b/test/napi/napi-app/main.cpp @@ -69,6 +69,33 @@ static napi_value test_issue_11949(const Napi::CallbackInfo &info) { return result; } +static void callback_1(napi_env env, napi_value js_callback, void *context, + void *data) {} + +napi_value test_napi_threadsafe_function_does_not_hang_after_finalize( + const Napi::CallbackInfo &info) { + + Napi::Env env = info.Env(); + napi_status status; + + napi_value resource_name; + status = napi_create_string_utf8(env, "simple", 6, &resource_name); + assert(status == napi_ok); + + napi_threadsafe_function cb; + status = napi_create_threadsafe_function(env, nullptr, nullptr, resource_name, + 0, 1, nullptr, nullptr, nullptr, + &callback_1, &cb); + assert(status == napi_ok); + + status = napi_release_threadsafe_function(cb, napi_tsfn_release); + assert(status == napi_ok); + + printf("success!"); + + return ok(env); +} + napi_value test_napi_get_value_string_utf8_with_buffer(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -123,10 +150,8 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports1) { Napi::Object exports = Init2(env, exports1); - node::AddEnvironmentCleanupHook( - isolate, [](void *) {}, isolate); - node::RemoveEnvironmentCleanupHook( - isolate, [](void *) {}, isolate); + node::AddEnvironmentCleanupHook(isolate, [](void *) {}, isolate); + node::RemoveEnvironmentCleanupHook(isolate, [](void *) {}, isolate); exports.Set("test_issue_7685", Napi::Function::New(env, test_issue_7685)); exports.Set("test_issue_11949", Napi::Function::New(env, test_issue_11949)); @@ -134,6 +159,12 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports1) { exports.Set( "test_napi_get_value_string_utf8_with_buffer", Napi::Function::New(env, test_napi_get_value_string_utf8_with_buffer)); + + exports.Set( + "test_napi_threadsafe_function_does_not_hang_after_finalize", + Napi::Function::New( + env, test_napi_threadsafe_function_does_not_hang_after_finalize)); + return exports; } diff --git a/test/napi/napi.test.ts b/test/napi/napi.test.ts index 2c8a64c262..90e800446b 100644 --- a/test/napi/napi.test.ts +++ b/test/napi/napi.test.ts @@ -59,6 +59,11 @@ describe("napi", () => { }); }); + it("threadsafe function does not hang on finalize", () => { + const result = checkSameOutput("test_napi_threadsafe_function_does_not_hang_after_finalize", []); + expect(result).toBe("success!"); + }); + it("#1288", async () => { const result = checkSameOutput("self", []); expect(result).toBe("hello world!"); From 907cd8d45d4cc7fe4bfcc86bb820c6842588e433 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Wed, 24 Jul 2024 21:08:25 -0700 Subject: [PATCH 056/123] fix crash in populateStackTrace() (#12793) --- src/bun.js/bindings/bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 876d97b8fd..c5f9f11c89 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -4311,7 +4311,7 @@ static void populateStackTrace(JSC::VM& vm, const WTF::Vector& while (frame_i < frame_count && stack_frame_i < total_frame_count) { // Skip native frames - while (stack_frame_i < total_frame_count && !(&frames.at(stack_frame_i))->codeBlock() && !(&frames.at(stack_frame_i))->isWasmFrame()) { + while (stack_frame_i < total_frame_count && !(&frames.at(stack_frame_i))->hasLineAndColumnInfo() && !(&frames.at(stack_frame_i))->isWasmFrame()) { stack_frame_i++; } if (stack_frame_i >= total_frame_count) From f6c89f4c2535ef1e3474bb6b0898d87f65c19769 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Wed, 24 Jul 2024 22:03:51 -0700 Subject: [PATCH 057/123] JSValue.toFmt doesn't need a globalThis param because ConsoleObject.Formatter already has one (#12790) --- src/bun.js/ConsoleObject.zig | 4 +- src/bun.js/api/bun/spawn/stdio.zig | 2 +- src/bun.js/api/server.zig | 2 +- src/bun.js/bindings/bindings.zig | 2 - src/bun.js/test/diff_format.zig | 8 +- src/bun.js/test/expect.zig | 313 ++++++++++++++--------------- src/bun.js/test/jest.zig | 2 +- 7 files changed, 163 insertions(+), 170 deletions(-) diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index 4c0c584847..c5a2ae63f8 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -869,7 +869,6 @@ pub const Formatter = struct { pub const ZigFormatter = struct { formatter: *ConsoleObject.Formatter, - global: *JSGlobalObject, value: JSValue, pub const WriteError = error{UhOh}; @@ -878,9 +877,8 @@ pub const Formatter = struct { defer { self.formatter.remaining_values = &[_]JSValue{}; } - self.formatter.globalThis = self.global; self.formatter.format( - Tag.get(self.value, self.global), + Tag.get(self.value, self.formatter.globalThis), @TypeOf(writer), writer, self.value, diff --git a/src/bun.js/api/bun/spawn/stdio.zig b/src/bun.js/api/bun/spawn/stdio.zig index ec5a6053bf..9b5b132c6e 100644 --- a/src/bun.js/api/bun/spawn/stdio.zig +++ b/src/bun.js/api/bun/spawn/stdio.zig @@ -330,7 +330,7 @@ pub const Stdio = union(enum) { if (file_fd >= std.math.maxInt(i32)) { var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis }; globalThis.throwInvalidArguments("file descriptor must be a valid integer, received: {}", .{ - value.toFmt(globalThis, &formatter), + value.toFmt(&formatter), }); return false; } diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 78af6dac27..d220d139d4 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -1505,7 +1505,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp .globalThis = globalThis, .quote_strings = true, }; - Output.errGeneric("Expected a Response object, but received '{}'", .{value.toFmt(formatter.globalThis, &formatter)}); + Output.errGeneric("Expected a Response object, but received '{}'", .{value.toFmt(&formatter)}); } else { Output.errGeneric("Expected a Response object", .{}); } diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 5834f9dc8d..d1509dc903 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -5234,7 +5234,6 @@ pub const JSValue = enum(JSValueReprInt) { pub fn toFmt( this: JSValue, - global: *JSGlobalObject, formatter: *Exports.ConsoleObject.Formatter, ) Exports.ConsoleObject.Formatter.ZigFormatter { formatter.remaining_values = &[_]JSValue{}; @@ -5246,7 +5245,6 @@ pub const JSValue = enum(JSValueReprInt) { return Exports.ConsoleObject.Formatter.ZigFormatter{ .formatter = formatter, .value = this, - .global = global, }; } diff --git a/src/bun.js/test/diff_format.zig b/src/bun.js/test/diff_format.zig index 5d28d2532c..c907d16fd4 100644 --- a/src/bun.js/test/diff_format.zig +++ b/src/bun.js/test/diff_format.zig @@ -148,15 +148,15 @@ pub const DiffFormatter = struct { var formatter = ConsoleObject.Formatter{ .globalThis = this.globalThis, .quote_strings = true }; if (Output.enable_ansi_colors) { try writer.print(Output.prettyFmt(fmt, true), .{ - expected.toFmt(this.globalThis, &formatter), - received.toFmt(this.globalThis, &formatter), + expected.toFmt(&formatter), + received.toFmt(&formatter), }); return; } try writer.print(Output.prettyFmt(fmt, true), .{ - expected.toFmt(this.globalThis, &formatter), - received.toFmt(this.globalThis, &formatter), + expected.toFmt(&formatter), + received.toFmt(&formatter), }); return; }, diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index 509d73ea73..54d8cafba1 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -228,7 +228,7 @@ pub const Expect = struct { if (!silent) { var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; const message = "Expected promise that rejects\nReceived promise that resolved: {any}\n"; - throwPrettyMatcherError(globalThis, custom_label, matcher_name, matcher_params, flags, message, .{value.toFmt(globalThis, &formatter)}); + throwPrettyMatcherError(globalThis, custom_label, matcher_name, matcher_params, flags, message, .{value.toFmt(&formatter)}); } return null; }, @@ -240,7 +240,7 @@ pub const Expect = struct { if (!silent) { var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; const message = "Expected promise that resolves\nReceived promise that rejected: {any}\n"; - throwPrettyMatcherError(globalThis, custom_label, matcher_name, matcher_params, flags, message, .{value.toFmt(globalThis, &formatter)}); + throwPrettyMatcherError(globalThis, custom_label, matcher_name, matcher_params, flags, message, .{value.toFmt(&formatter)}); } return null; }, @@ -255,7 +255,7 @@ pub const Expect = struct { if (!silent) { var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; const message = "Expected promise\nReceived: {any}\n"; - throwPrettyMatcherError(globalThis, custom_label, matcher_name, matcher_params, flags, message, .{value.toFmt(globalThis, &formatter)}); + throwPrettyMatcherError(globalThis, custom_label, matcher_name, matcher_params, flags, message, .{value.toFmt(&formatter)}); } return null; } @@ -534,7 +534,7 @@ pub const Expect = struct { inline else => |has_custom_label| { if (not) { const signature = comptime getSignature("toBe", "expected", true); - this.throw(globalThis, signature, "\n\nExpected: not {any}\n", .{right.toFmt(globalThis, &formatter)}); + this.throw(globalThis, signature, "\n\nExpected: not {any}\n", .{right.toFmt(&formatter)}); return .zero; } @@ -544,7 +544,7 @@ pub const Expect = struct { (if (!has_custom_label) "\n\nIf this test should pass, replace \"toBe\" with \"toEqual\" or \"toStrictEqual\"" else "") ++ "\n\nExpected: {any}\n" ++ "Received: serializes to the same string\n"; - this.throw(globalThis, signature, fmt, .{right.toFmt(globalThis, &formatter)}); + this.throw(globalThis, signature, fmt, .{right.toFmt(&formatter)}); return .zero; } @@ -560,8 +560,8 @@ pub const Expect = struct { } this.throw(globalThis, signature, "\n\nExpected: {any}\nReceived: {any}\n", .{ - right.toFmt(globalThis, &formatter), - left.toFmt(globalThis, &formatter), + right.toFmt(&formatter), + left.toFmt(&formatter), }); return .zero; }, @@ -590,20 +590,20 @@ pub const Expect = struct { if (!value.isObject() and !value.isString()) { var fmt = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - globalThis.throw("Received value does not have a length property: {any}", .{value.toFmt(globalThis, &fmt)}); + globalThis.throw("Received value does not have a length property: {any}", .{value.toFmt(&fmt)}); return .zero; } if (!expected.isNumber()) { var fmt = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - globalThis.throw("Expected value must be a non-negative integer: {any}", .{expected.toFmt(globalThis, &fmt)}); + globalThis.throw("Expected value must be a non-negative integer: {any}", .{expected.toFmt(&fmt)}); return .zero; } const expected_length: f64 = expected.asNumber(); if (@round(expected_length) != expected_length or std.math.isInf(expected_length) or std.math.isNan(expected_length) or expected_length < 0) { var fmt = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - globalThis.throw("Expected value must be a non-negative integer: {any}", .{expected.toFmt(globalThis, &fmt)}); + globalThis.throw("Expected value must be a non-negative integer: {any}", .{expected.toFmt(&fmt)}); return .zero; } @@ -614,7 +614,7 @@ pub const Expect = struct { if (actual_length == std.math.inf(f64)) { var fmt = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - globalThis.throw("Received value does not have a length property: {any}", .{value.toFmt(globalThis, &fmt)}); + globalThis.throw("Received value does not have a length property: {any}", .{value.toFmt(&fmt)}); return .zero; } else if (std.math.isNan(actual_length)) { globalThis.throw("Received value has non-number length property: {}", .{actual_length}); @@ -712,10 +712,10 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = list_value.toFmt(globalThis, &formatter); - const expected_fmt = expected.toFmt(globalThis, &formatter); + const value_fmt = list_value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { - const received_fmt = list_value.toFmt(globalThis, &formatter); + const received_fmt = list_value.toFmt(&formatter); const expected_line = "Expected to not be one of: {any}\nReceived: {any}\n"; const signature = comptime getSignature("toBeOneOf", "expected", true); this.throw(globalThis, signature, "\n\n" ++ expected_line, .{ received_fmt, expected_fmt }); @@ -810,10 +810,10 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = expected.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { - const received_fmt = value.toFmt(globalThis, &formatter); + const received_fmt = value.toFmt(&formatter); const expected_line = "Expected to not contain: {any}\nReceived: {any}\n"; const signature = comptime getSignature("toContain", "expected", true); this.throw(globalThis, signature, "\n\n" ++ expected_line, .{ expected_fmt, received_fmt }); @@ -851,10 +851,7 @@ pub const Expect = struct { const not = this.flags.not; if (!value.isObject()) { - const err = globalThis.createTypeErrorInstance("Expected value must be an object\nReceived: {}", .{value.toFmt( - globalThis, - &formatter, - )}); + const err = globalThis.createTypeErrorInstance("Expected value must be an object\nReceived: {}", .{value.toFmt(&formatter)}); globalThis.throwValue(err); return .zero; } @@ -870,10 +867,10 @@ pub const Expect = struct { // handle failure - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = expected.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { - const received_fmt = value.toFmt(globalThis, &formatter); + const received_fmt = value.toFmt(&formatter); const expected_line = "Expected to not contain: {any}\nReceived: {any}\n"; const signature = comptime getSignature("toContainKey", "expected", true); this.throw(globalThis, signature, "\n\n" ++ expected_line, .{ expected_fmt, received_fmt }); @@ -943,10 +940,10 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = expected.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { - const received_fmt = value.toFmt(globalThis, &formatter); + const received_fmt = value.toFmt(&formatter); const expected_line = "Expected to not contain: {any}\nReceived: {any}\n"; const signature = comptime getSignature("toContainKeys", "expected", true); this.throw(globalThis, signature, "\n\n" ++ expected_line, .{ expected_fmt, received_fmt }); @@ -1011,10 +1008,10 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalObject, .quote_strings = true }; - const value_fmt = keys.toFmt(globalObject, &formatter); - const expected_fmt = expected.toFmt(globalObject, &formatter); + const value_fmt = keys.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { - const received_fmt = keys.toFmt(globalObject, &formatter); + const received_fmt = keys.toFmt(&formatter); const expected_line = "Expected to not contain all keys: {any}\nReceived: {any}\n"; const fmt = "\n\n" ++ expected_line; this.throw(globalObject, comptime getSignature("toContainAllKeys", "expected", true), fmt, .{ expected_fmt, received_fmt }); @@ -1079,10 +1076,10 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = expected.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { - const received_fmt = value.toFmt(globalThis, &formatter); + const received_fmt = value.toFmt(&formatter); const expected_line = "Expected to not contain: {any}\nReceived: {any}\n"; const signature = comptime getSignature("toContainAnyKeys", "expected", true); this.throw(globalThis, signature, "\n\n" ++ expected_line, .{ expected_fmt, received_fmt }); @@ -1136,10 +1133,10 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalObject, .quote_strings = true }; - const value_fmt = value.toFmt(globalObject, &formatter); - const expected_fmt = expected.toFmt(globalObject, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { - const received_fmt = value.toFmt(globalObject, &formatter); + const received_fmt = value.toFmt(&formatter); const expected_line = "Expected to not contain: {any}\nReceived: {any}\n"; const fmt = "\n\n" ++ expected_line; this.throw(globalObject, comptime getSignature("toContainValue", "expected", true), fmt, .{ expected_fmt, received_fmt }); @@ -1203,10 +1200,10 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalObject, .quote_strings = true }; - const value_fmt = value.toFmt(globalObject, &formatter); - const expected_fmt = expected.toFmt(globalObject, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { - const received_fmt = value.toFmt(globalObject, &formatter); + const received_fmt = value.toFmt(&formatter); const expected_line = "Expected to not contain: {any}\nReceived: {any}\n"; const fmt = "\n\n" ++ expected_line; this.throw(globalObject, comptime getSignature("toContainValues", "expected", true), fmt, .{ expected_fmt, received_fmt }); @@ -1276,10 +1273,10 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalObject, .quote_strings = true }; - const value_fmt = value.toFmt(globalObject, &formatter); - const expected_fmt = expected.toFmt(globalObject, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { - const received_fmt = value.toFmt(globalObject, &formatter); + const received_fmt = value.toFmt(&formatter); const expected_line = "Expected to not contain all values: {any}\nReceived: {any}\n"; const fmt = "\n\n" ++ expected_line; this.throw(globalObject, comptime getSignature("toContainAllValues", "expected", true), fmt, .{ expected_fmt, received_fmt }); @@ -1343,10 +1340,10 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalObject, .quote_strings = true }; - const value_fmt = value.toFmt(globalObject, &formatter); - const expected_fmt = expected.toFmt(globalObject, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { - const received_fmt = value.toFmt(globalObject, &formatter); + const received_fmt = value.toFmt(&formatter); const expected_line = "Expected to not contain any of the following values: {any}\nReceived: {any}\n"; const fmt = "\n\n" ++ expected_line; this.throw(globalObject, comptime getSignature("toContainAnyValues", "expected", true), fmt, .{ expected_fmt, received_fmt }); @@ -1450,8 +1447,8 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = expected.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { const expected_line = "Expected to not contain: {any}\n"; const signature = comptime getSignature("toContainEqual", "expected", true); @@ -1484,7 +1481,7 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const received_line = "Received: {any}\n"; const signature = comptime getSignature("toBeTruthy", "", true); @@ -1514,7 +1511,7 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const received_line = "Received: {any}\n"; const signature = comptime getSignature("toBeUndefined", "", true); @@ -1548,7 +1545,7 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const received_line = "Received: {any}\n"; const signature = comptime getSignature("toBeNaN", "", true); @@ -1577,7 +1574,7 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const received_line = "Received: {any}\n"; const signature = comptime getSignature("toBeNull", "", true); @@ -1606,7 +1603,7 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const received_line = "Received: {any}\n"; const signature = comptime getSignature("toBeDefined", "", true); @@ -1640,7 +1637,7 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const received_line = "Received: {any}\n"; const signature = comptime getSignature("toBeFalsy", "", true); @@ -1785,8 +1782,8 @@ pub const Expect = struct { const signature = comptime getSignature("toHaveProperty", "path, value", true); if (!received_property.isEmpty()) { this.throw(globalThis, signature, "\n\nExpected path: {any}\n\nExpected value: not {any}\n", .{ - expected_property_path.toFmt(globalThis, &formatter), - expected_property.?.toFmt(globalThis, &formatter), + expected_property_path.toFmt(&formatter), + expected_property.?.toFmt(&formatter), }); return .zero; } @@ -1794,8 +1791,8 @@ pub const Expect = struct { const signature = comptime getSignature("toHaveProperty", "path", true); this.throw(globalThis, signature, "\n\nExpected path: not {any}\n\nReceived value: {any}\n", .{ - expected_property_path.toFmt(globalThis, &formatter), - received_property.toFmt(globalThis, &formatter), + expected_property_path.toFmt(&formatter), + received_property.toFmt(&formatter), }); return .zero; } @@ -1817,14 +1814,14 @@ pub const Expect = struct { const fmt = "\n\nExpected path: {any}\n\nExpected value: {any}\n\n" ++ "Unable to find property\n"; this.throw(globalThis, signature, fmt, .{ - expected_property_path.toFmt(globalThis, &formatter), - expected_property.?.toFmt(globalThis, &formatter), + expected_property_path.toFmt(&formatter), + expected_property.?.toFmt(&formatter), }); return .zero; } const signature = comptime getSignature("toHaveProperty", "path", false); - this.throw(globalThis, signature, "\n\nExpected path: {any}\n\nUnable to find property\n", .{expected_property_path.toFmt(globalThis, &formatter)}); + this.throw(globalThis, signature, "\n\nExpected path: {any}\n\nUnable to find property\n", .{expected_property_path.toFmt(&formatter)}); return .zero; } @@ -1868,7 +1865,7 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const received_line = "Received: {any}\n"; const signature = comptime getSignature("toBeEven", "", true); @@ -1928,8 +1925,8 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = other_value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = other_value.toFmt(&formatter); if (not) { const expected_line = "Expected: not \\> {any}\n"; const received_line = "Received: {any}\n"; @@ -1991,8 +1988,8 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = other_value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = other_value.toFmt(&formatter); if (not) { const expected_line = "Expected: not \\>= {any}\n"; const received_line = "Received: {any}\n"; @@ -2054,8 +2051,8 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = other_value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = other_value.toFmt(&formatter); if (not) { const expected_line = "Expected: not \\< {any}\n"; const received_line = "Received: {any}\n"; @@ -2117,8 +2114,8 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = other_value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = other_value.toFmt(&formatter); if (not) { const expected_line = "Expected: not \\<= {any}\n"; const received_line = "Received: {any}\n"; @@ -2195,8 +2192,8 @@ pub const Expect = struct { var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const expected_fmt = expected_.toFmt(globalThis, &formatter); - const received_fmt = received_.toFmt(globalThis, &formatter); + const expected_fmt = expected_.toFmt(&formatter); + const received_fmt = received_.toFmt(&formatter); const expected_line = "Expected: {any}\n"; const received_line = "Received: {any}\n"; @@ -2255,7 +2252,7 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const received_line = "Received: {any}\n"; const signature = comptime getSignature("toBeOdd", "", true); @@ -2282,7 +2279,7 @@ pub const Expect = struct { const value = arguments[0]; if (value.isEmptyOrUndefinedOrNull() or !value.isObject() and !value.isString()) { var fmt = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - globalThis.throw("Expected value must be string or Error: {any}", .{value.toFmt(globalThis, &fmt)}); + globalThis.throw("Expected value must be string or Error: {any}", .{value.toFmt(&fmt)}); return .zero; } else if (value.isObject()) { if (ExpectAny.fromJSDirect(value)) |_| { @@ -2375,15 +2372,15 @@ pub const Expect = struct { const message = err.getTruthyComptime(globalThis, "message") orelse JSValue.undefined; const fmt = signature_no_args ++ "\n\nError name: {any}\nError message: {any}\n"; globalThis.throwPretty(fmt, .{ - name.toFmt(globalThis, &formatter), - message.toFmt(globalThis, &formatter), + name.toFmt(&formatter), + message.toFmt(&formatter), }); return .zero; } // non error thrown const fmt = signature_no_args ++ "\n\nThrown value: {any}\n"; - globalThis.throwPretty(fmt, .{result.toFmt(globalThis, &formatter)}); + globalThis.throwPretty(fmt, .{result.toFmt(&formatter)}); return .zero; } @@ -2401,8 +2398,8 @@ pub const Expect = struct { } this.throw(globalThis, signature, "\n\nExpected substring: not {any}\nReceived message: {any}\n", .{ - expected_value.toFmt(globalThis, &formatter), - received_message.toFmt(globalThis, &formatter), + expected_value.toFmt(&formatter), + received_message.toFmt(&formatter), }); return .zero; } @@ -2417,8 +2414,8 @@ pub const Expect = struct { } this.throw(globalThis, signature, "\n\nExpected pattern: not {any}\nReceived message: {any}\n", .{ - expected_value.toFmt(globalThis, &formatter), - received_message.toFmt(globalThis, &formatter), + expected_value.toFmt(&formatter), + received_message.toFmt(&formatter), }); return .zero; } @@ -2428,7 +2425,7 @@ pub const Expect = struct { // no partial match for this case if (!expected_message.isSameValue(received_message, globalThis)) return .undefined; - this.throw(globalThis, signature, "\n\nExpected message: not {any}\n", .{expected_message.toFmt(globalThis, &formatter)}); + this.throw(globalThis, signature, "\n\nExpected message: not {any}\n", .{expected_message.toFmt(&formatter)}); return .zero; } @@ -2437,7 +2434,7 @@ pub const Expect = struct { var expected_class = ZigString.Empty; expected_value.getClassName(globalThis, &expected_class); const received_message = result.fastGet(globalThis, .message) orelse .undefined; - this.throw(globalThis, signature, "\n\nExpected constructor: not {s}\n\nReceived message: {any}\n", .{ expected_class, received_message.toFmt(globalThis, &formatter) }); + this.throw(globalThis, signature, "\n\nExpected constructor: not {s}\n\nReceived message: {any}\n", .{ expected_class, received_message.toFmt(&formatter) }); return .zero; } @@ -2473,14 +2470,14 @@ pub const Expect = struct { const signature = comptime getSignature("toThrow", "expected", false); if (_received_message) |received_message| { - const expected_value_fmt = expected_value.toFmt(globalThis, &formatter); - const received_message_fmt = received_message.toFmt(globalThis, &formatter); + const expected_value_fmt = expected_value.toFmt(&formatter); + const received_message_fmt = received_message.toFmt(&formatter); this.throw(globalThis, signature, "\n\n" ++ "Expected substring: {any}\nReceived message: {any}\n", .{ expected_value_fmt, received_message_fmt }); return .zero; } - const expected_fmt = expected_value.toFmt(globalThis, &formatter); - const received_fmt = result.toFmt(globalThis, &formatter); + const expected_fmt = expected_value.toFmt(&formatter); + const received_fmt = result.toFmt(&formatter); this.throw(globalThis, signature, "\n\n" ++ "Expected substring: {any}\nReceived value: {any}", .{ expected_fmt, received_fmt }); return .zero; @@ -2499,8 +2496,8 @@ pub const Expect = struct { var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; if (_received_message) |received_message| { - const expected_value_fmt = expected_value.toFmt(globalThis, &formatter); - const received_message_fmt = received_message.toFmt(globalThis, &formatter); + const expected_value_fmt = expected_value.toFmt(&formatter); + const received_message_fmt = received_message.toFmt(&formatter); const signature = comptime getSignature("toThrow", "expected", false); this.throw(globalThis, signature, "\n\n" ++ "Expected pattern: {any}\nReceived message: {any}\n", .{ expected_value_fmt, received_message_fmt }); @@ -2508,8 +2505,8 @@ pub const Expect = struct { return .zero; } - const expected_fmt = expected_value.toFmt(globalThis, &formatter); - const received_fmt = result.toFmt(globalThis, &formatter); + const expected_fmt = expected_value.toFmt(&formatter); + const received_fmt = result.toFmt(&formatter); const signature = comptime getSignature("toThrow", "expected", false); this.throw(globalThis, signature, "\n\n" ++ "Expected pattern: {any}\nReceived value: {any}", .{ expected_fmt, received_fmt }); return .zero; @@ -2529,14 +2526,14 @@ pub const Expect = struct { var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; if (_received_message) |received_message| { - const expected_fmt = expected_message.toFmt(globalThis, &formatter); - const received_fmt = received_message.toFmt(globalThis, &formatter); + const expected_fmt = expected_message.toFmt(&formatter); + const received_fmt = received_message.toFmt(&formatter); this.throw(globalThis, signature, "\n\nExpected message: {any}\nReceived message: {any}\n", .{ expected_fmt, received_fmt }); return .zero; } - const expected_fmt = expected_message.toFmt(globalThis, &formatter); - const received_fmt = result.toFmt(globalThis, &formatter); + const expected_fmt = expected_message.toFmt(&formatter); + const received_fmt = result.toFmt(&formatter); this.throw(globalThis, signature, "\n\nExpected message: {any}\nReceived value: {any}\n", .{ expected_fmt, received_fmt }); return .zero; } @@ -2554,7 +2551,7 @@ pub const Expect = struct { if (_received_message) |received_message| { const message_fmt = fmt ++ "Received message: {any}\n"; - const received_message_fmt = received_message.toFmt(globalThis, &formatter); + const received_message_fmt = received_message.toFmt(&formatter); globalThis.throwPretty(message_fmt, .{ expected_class, @@ -2564,7 +2561,7 @@ pub const Expect = struct { return .zero; } - const received_fmt = result.toFmt(globalThis, &formatter); + const received_fmt = result.toFmt(&formatter); const value_fmt = fmt ++ "Received value: {any}\n"; globalThis.throwPretty(value_fmt, .{ @@ -2589,19 +2586,19 @@ pub const Expect = struct { if (expected_value.isString()) { const expected_fmt = "\n\nExpected substring: {any}\n\n" ++ received_line; - this.throw(globalThis, signature, expected_fmt, .{expected_value.toFmt(globalThis, &formatter)}); + this.throw(globalThis, signature, expected_fmt, .{expected_value.toFmt(&formatter)}); return .zero; } if (expected_value.isRegExp()) { const expected_fmt = "\n\nExpected pattern: {any}\n\n" ++ received_line; - this.throw(globalThis, signature, expected_fmt, .{expected_value.toFmt(globalThis, &formatter)}); + this.throw(globalThis, signature, expected_fmt, .{expected_value.toFmt(&formatter)}); return .zero; } if (expected_value.fastGet(globalThis, .message)) |expected_message| { const expected_fmt = "\n\nExpected message: {any}\n\n" ++ received_line; - this.throw(globalThis, signature, expected_fmt, .{expected_message.toFmt(globalThis, &formatter)}); + this.throw(globalThis, signature, expected_fmt, .{expected_message.toFmt(&formatter)}); return .zero; } @@ -2679,7 +2676,7 @@ pub const Expect = struct { "\n\nReceived: {any}\n"; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis }; - globalThis.throwPretty(fmt, .{value.toFmt(globalThis, &formatter)}); + globalThis.throwPretty(fmt, .{value.toFmt(&formatter)}); return .zero; } } @@ -2692,7 +2689,7 @@ pub const Expect = struct { error.FailedToMakeSnapshotDirectory => globalThis.throw("Failed to make snapshot directory for test file: {s}", .{test_file_path}), error.FailedToWriteSnapshotFile => globalThis.throw("Failed write to snapshot file: {s}", .{test_file_path}), error.ParseError => globalThis.throw("Failed to parse snapshot file for: {s}", .{test_file_path}), - else => globalThis.throw("Failed to snapshot value: {any}", .{value.toFmt(globalThis, &formatter)}), + else => globalThis.throw("Failed to snapshot value: {any}", .{value.toFmt(&formatter)}), } return .zero; }; @@ -2701,7 +2698,7 @@ pub const Expect = struct { var pretty_value: MutableString = MutableString.init(default_allocator, 0) catch unreachable; value.jestSnapshotPrettyFormat(&pretty_value, globalThis) catch { var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis }; - globalThis.throw("Failed to pretty format value: {s}", .{value.toFmt(globalThis, &formatter)}); + globalThis.throw("Failed to pretty format value: {s}", .{value.toFmt(&formatter)}); return .zero; }; defer pretty_value.deinit(); @@ -2769,7 +2766,7 @@ pub const Expect = struct { const signature = comptime getSignature("toBeEmpty", "", false); const fmt = signature ++ "\n\nExpected value to be a string, object, or iterable" ++ "\n\nReceived: {any}\n"; - globalThis.throwPretty(fmt, .{value.toFmt(globalThis, &formatter)}); + globalThis.throwPretty(fmt, .{value.toFmt(&formatter)}); return .zero; } } else if (std.math.isNan(actual_length)) { @@ -2783,7 +2780,7 @@ pub const Expect = struct { const signature = comptime getSignature("toBeEmpty", "", true); const fmt = signature ++ "\n\nExpected value not to be a string, object, or iterable" ++ "\n\nReceived: {any}\n"; - globalThis.throwPretty(fmt, .{value.toFmt(globalThis, &formatter)}); + globalThis.throwPretty(fmt, .{value.toFmt(&formatter)}); return .zero; } @@ -2794,14 +2791,14 @@ pub const Expect = struct { const signature = comptime getSignature("toBeEmpty", "", true); const fmt = signature ++ "\n\nExpected value not to be empty" ++ "\n\nReceived: {any}\n"; - globalThis.throwPretty(fmt, .{value.toFmt(globalThis, &formatter)}); + globalThis.throwPretty(fmt, .{value.toFmt(&formatter)}); return .zero; } const signature = comptime getSignature("toBeEmpty", "", false); const fmt = signature ++ "\n\nExpected value to be empty" ++ "\n\nReceived: {any}\n"; - globalThis.throwPretty(fmt, .{value.toFmt(globalThis, &formatter)}); + globalThis.throwPretty(fmt, .{value.toFmt(&formatter)}); return .zero; } @@ -2820,7 +2817,7 @@ pub const Expect = struct { if (pass) return thisValue; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeEmptyObject", "", true); @@ -2847,7 +2844,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeNil", "", true); @@ -2874,7 +2871,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeArray", "", true); @@ -2918,7 +2915,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeArrayOfSize", "", true); @@ -2945,7 +2942,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeBoolean", "", true); @@ -3021,8 +3018,8 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); - const expected_str = expected.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); + const expected_str = expected.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeTypeOf", "", true); @@ -3049,7 +3046,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeTrue", "", true); @@ -3076,7 +3073,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeFalse", "", true); @@ -3103,7 +3100,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeNumber", "", true); @@ -3130,7 +3127,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeInteger", "", true); @@ -3157,7 +3154,7 @@ pub const Expect = struct { if (pass) return thisValue; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeObject", "", true); @@ -3190,7 +3187,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeFinite", "", true); @@ -3223,7 +3220,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBePositive", "", true); @@ -3256,7 +3253,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeNegative", "", true); @@ -3313,9 +3310,9 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const start_fmt = startValue.toFmt(globalThis, &formatter); - const end_fmt = endValue.toFmt(globalThis, &formatter); - const received_fmt = value.toFmt(globalThis, &formatter); + const start_fmt = startValue.toFmt(&formatter); + const end_fmt = endValue.toFmt(&formatter); + const received_fmt = value.toFmt(&formatter); if (not) { const expected_line = "Expected: not between {any} (inclusive) and {any} (exclusive)\n"; @@ -3400,8 +3397,8 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const expected_fmt = expected.toFmt(globalThis, &formatter); - const value_fmt = value.toFmt(globalThis, &formatter); + const expected_fmt = expected.toFmt(&formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toEqualIgnoringWhitespace", "expected", true); @@ -3428,7 +3425,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeSymbol", "", true); @@ -3455,7 +3452,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeFunction", "", true); @@ -3482,7 +3479,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeDate", "", true); @@ -3510,7 +3507,7 @@ pub const Expect = struct { if (pass) return thisValue; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeValidDate", "", true); @@ -3537,7 +3534,7 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received = value.toFmt(globalThis, &formatter); + const received = value.toFmt(&formatter); if (not) { const signature = comptime getSignature("toBeString", "", true); @@ -3589,8 +3586,8 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = expected.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { const expected_line = "Expected to not include: {any}\n"; @@ -3677,9 +3674,9 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const expect_string_fmt = expect_string.toFmt(globalThis, &formatter); - const substring_fmt = substring.toFmt(globalThis, &formatter); - const times_fmt = count.toFmt(globalThis, &formatter); + const expect_string_fmt = expect_string.toFmt(&formatter); + const substring_fmt = substring.toFmt(&formatter); + const times_fmt = count.toFmt(&formatter); const received_line = "Received: {any}\n"; @@ -3768,15 +3765,15 @@ pub const Expect = struct { if (not) { const signature = comptime getSignature("toSatisfy", "expected", true); - this.throw(globalThis, signature, "\n\nExpected: not {any}\n", .{predicate.toFmt(globalThis, &formatter)}); + this.throw(globalThis, signature, "\n\nExpected: not {any}\n", .{predicate.toFmt(&formatter)}); return .zero; } const signature = comptime getSignature("toSatisfy", "expected", false); this.throw(globalThis, signature, "\n\nExpected: {any}\nReceived: {any}\n", .{ - predicate.toFmt(globalThis, &formatter), - value.toFmt(globalThis, &formatter), + predicate.toFmt(&formatter), + value.toFmt(&formatter), }); return .zero; @@ -3821,8 +3818,8 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = expected.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { const expected_line = "Expected to not start with: {any}\n"; @@ -3878,8 +3875,8 @@ pub const Expect = struct { if (pass) return .undefined; var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const value_fmt = value.toFmt(globalThis, &formatter); - const expected_fmt = expected.toFmt(globalThis, &formatter); + const value_fmt = value.toFmt(&formatter); + const expected_fmt = expected.toFmt(&formatter); if (not) { const expected_line = "Expected to not end with: {any}\n"; @@ -3913,7 +3910,7 @@ pub const Expect = struct { const expected_value = arguments[0]; if (!expected_value.isConstructor()) { - globalThis.throw("Expected value must be a function: {any}", .{expected_value.toFmt(globalThis, &formatter)}); + globalThis.throw("Expected value must be a function: {any}", .{expected_value.toFmt(&formatter)}); return .zero; } expected_value.ensureStillAlive(); @@ -3926,8 +3923,8 @@ pub const Expect = struct { if (pass) return .undefined; // handle failure - const expected_fmt = expected_value.toFmt(globalThis, &formatter); - const value_fmt = value.toFmt(globalThis, &formatter); + const expected_fmt = expected_value.toFmt(&formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const expected_line = "Expected constructor: not {any}\n"; const received_line = "Received value: {any}\n"; @@ -3963,7 +3960,7 @@ pub const Expect = struct { const expected_value = arguments[0]; if (!expected_value.isString() and !expected_value.isRegExp()) { - globalThis.throw("Expected value must be a string or regular expression: {any}", .{expected_value.toFmt(globalThis, &formatter)}); + globalThis.throw("Expected value must be a string or regular expression: {any}", .{expected_value.toFmt(&formatter)}); return .zero; } expected_value.ensureStillAlive(); @@ -3971,7 +3968,7 @@ pub const Expect = struct { const value: JSValue = this.getValue(globalThis, thisValue, "toMatch", "expected") orelse return .zero; if (!value.isString()) { - globalThis.throw("Received value must be a string: {any}", .{value.toFmt(globalThis, &formatter)}); + globalThis.throw("Received value must be a string: {any}", .{value.toFmt(&formatter)}); return .zero; } @@ -3989,8 +3986,8 @@ pub const Expect = struct { if (pass) return .undefined; // handle failure - const expected_fmt = expected_value.toFmt(globalThis, &formatter); - const value_fmt = value.toFmt(globalThis, &formatter); + const expected_fmt = expected_value.toFmt(&formatter); + const value_fmt = value.toFmt(&formatter); if (not) { const expected_line = "Expected substring or pattern: not {any}\n"; @@ -4259,7 +4256,7 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received_fmt = lastCallValue.toFmt(globalThis, &formatter); + const received_fmt = lastCallValue.toFmt(&formatter); if (not) { const signature = comptime getSignature("toHaveBeenLastCalledWith", "expected", true); @@ -4327,7 +4324,7 @@ pub const Expect = struct { // handle failure var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - const received_fmt = nthCallValue.toFmt(globalThis, &formatter); + const received_fmt = nthCallValue.toFmt(&formatter); if (not) { const signature = comptime getSignature("toHaveBeenNthCalledWith", "expected", true); @@ -4422,7 +4419,7 @@ pub const Expect = struct { .globalThis = globalThis, .quote_strings = true, }; - globalThis.throwPretty(fmt, .{times_value.get(globalThis, "value").?.toFmt(globalThis, &formatter)}); + globalThis.throwPretty(fmt, .{times_value.get(globalThis, "value").?.toFmt(&formatter)}); return .zero; } @@ -4598,7 +4595,7 @@ pub const Expect = struct { " {{message?: string | function, pass: boolean}}\n" ++ "'{any}' was returned"; const err = switch (Output.enable_ansi_colors) { - inline else => |colors| globalThis.createErrorInstance(Output.prettyFmt(fmt, colors), .{ matcher_name, result.toFmt(globalThis, &formatter) }), + inline else => |colors| globalThis.createErrorInstance(Output.prettyFmt(fmt, colors), .{ matcher_name, result.toFmt(&formatter) }), }; err.put(globalThis, ZigString.static("name"), ZigString.init("InvalidMatcherError").toJS(globalThis)); globalThis.throwValue(err); @@ -4705,7 +4702,7 @@ pub const Expect = struct { }; globalThis.throw( "Expected custom matcher message to return a string, but got: {}", - .{message_result.toFmt(globalThis, &formatter)}, + .{message_result.toFmt(&formatter)}, ); return false; } @@ -4808,14 +4805,14 @@ pub const Expect = struct { if (!expected.isNumber()) { var fmt = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - globalThis.throw("Expected value must be a non-negative integer: {any}", .{expected.toFmt(globalThis, &fmt)}); + globalThis.throw("Expected value must be a non-negative integer: {any}", .{expected.toFmt(&fmt)}); return .zero; } const expected_assertions: f64 = expected.asNumber(); if (@round(expected_assertions) != expected_assertions or std.math.isInf(expected_assertions) or std.math.isNan(expected_assertions) or expected_assertions < 0) { var fmt = JSC.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true }; - globalThis.throw("Expected value must be a non-negative integer: {any}", .{expected.toFmt(globalThis, &fmt)}); + globalThis.throw("Expected value must be a non-negative integer: {any}", .{expected.toFmt(&fmt)}); return .zero; } @@ -5486,7 +5483,7 @@ pub const ExpectMatcherUtils = struct { .globalThis = globalThis, .quote_strings = true, }; - try writer.print("{}", .{value.toFmt(globalThis, &formatter)}); + try writer.print("{}", .{value.toFmt(&formatter)}); if (comptime color_or_null) |_| { if (Output.enable_ansi_colors) { diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index fd587b3c71..f1415ae7ca 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -1829,7 +1829,7 @@ fn formatLabel(globalThis: *JSGlobalObject, label: string, function_args: []JSVa .globalThis = globalThis, .quote_strings = true, }; - const value_fmt = current_arg.toFmt(globalThis, &formatter); + const value_fmt = current_arg.toFmt(&formatter); const test_index_str = try std.fmt.allocPrint(allocator, "{any}", .{value_fmt}); defer allocator.free(test_index_str); try list.appendSlice(allocator, test_index_str); From 1e0b20f5148796197591546ffe1ccb8d5da44c57 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Wed, 24 Jul 2024 22:26:07 -0700 Subject: [PATCH 058/123] node: fix observable value of process.exitCode (#12799) --- src/bun.js/bindings/BunProcess.cpp | 9 +++ src/bun.js/bindings/BunProcess.h | 8 +- test/harness.ts | 18 ++++- test/js/node/process/process.test.js | 105 +++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 6 deletions(-) diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index a2e0dfc3c6..921fe00b3e 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -466,6 +466,8 @@ extern "C" void Process__dispatchOnExit(Zig::GlobalObject* globalObject, uint8_t } auto* process = jsCast(globalObject->processObject()); + if (exitCode > 0) + process->m_isExitCodeObservable = true; dispatchExitInternal(globalObject, process, exitCode); } @@ -489,6 +491,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionExit, RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::JSValue {})); exitCode = static_cast(extiCode32); + Bun__setExitCode(Bun__getVM(), exitCode); } else if (!arg0.isUndefinedOrNull()) { throwTypeError(globalObject, throwScope, "The \"code\" argument must be an integer"_s); return JSC::JSValue::encode(JSC::JSValue {}); @@ -500,6 +503,8 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionExit, if (UNLIKELY(!zigGlobal)) { zigGlobal = Bun__getDefaultGlobal(); } + auto process = jsCast(zigGlobal->processObject()); + process->m_isExitCodeObservable = true; Process__dispatchOnExit(zigGlobal, exitCode); Bun__Process__exit(zigGlobal, exitCode); @@ -1093,6 +1098,9 @@ JSC_DEFINE_CUSTOM_GETTER(processExitCode, (JSC::JSGlobalObject * lexicalGlobalOb if (!process) { return JSValue::encode(jsUndefined()); } + if (!process->m_isExitCodeObservable) { + return JSValue::encode(jsUndefined()); + } return JSValue::encode(jsNumber(Bun__getExitCode(jsCast(process->globalObject())->bunVM()))); } @@ -1113,6 +1121,7 @@ JSC_DEFINE_CUSTOM_SETTER(setProcessExitCode, (JSC::JSGlobalObject * lexicalGloba int exitCodeInt = exitCode.toInt32(lexicalGlobalObject) % 256; RETURN_IF_EXCEPTION(throwScope, false); + process->m_isExitCodeObservable = true; void* ptr = jsCast(process->globalObject())->bunVM(); Bun__setExitCode(ptr, static_cast(exitCodeInt)); diff --git a/src/bun.js/bindings/BunProcess.h b/src/bun.js/bindings/BunProcess.h index 653f4d54a4..6b11936372 100644 --- a/src/bun.js/bindings/BunProcess.h +++ b/src/bun.js/bindings/BunProcess.h @@ -37,6 +37,8 @@ public: ~Process(); + bool m_isExitCodeObservable = false; + static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable; static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, @@ -71,11 +73,13 @@ public: void finishCreation(JSC::VM& vm); - inline void setUncaughtExceptionCaptureCallback(JSC::JSValue callback) { + inline void setUncaughtExceptionCaptureCallback(JSC::JSValue callback) + { m_uncaughtExceptionCaptureCallback.set(vm(), this, callback); } - inline JSC::JSValue getUncaughtExceptionCaptureCallback() { + inline JSC::JSValue getUncaughtExceptionCaptureCallback() + { return m_uncaughtExceptionCaptureCallback.get(); } diff --git a/test/harness.ts b/test/harness.ts index 9944544ed3..18ba4fed31 100644 --- a/test/harness.ts +++ b/test/harness.ts @@ -357,21 +357,21 @@ expect.extend({ } } }, - toRun(cmds: string[], optionalStdout?: string) { + toRun(cmds: string[], optionalStdout?: string, expectedCode: number = 0) { const result = Bun.spawnSync({ cmd: [bunExe(), ...cmds], env: bunEnv, stdio: ["inherit", "pipe", "inherit"], }); - if (result.exitCode !== 0) { + if (result.exitCode !== expectedCode) { return { pass: false, message: () => `Command ${cmds.join(" ")} failed:` + "\n" + result.stdout.toString("utf-8"), }; } - if (optionalStdout) { + if (optionalStdout != null) { return { pass: result.stdout.toString("utf-8") === optionalStdout, message: () => @@ -384,6 +384,15 @@ expect.extend({ message: () => `Expected ${cmds.join(" ")} to fail`, }; }, + toRunInlineFixture(input: [string, string?, number?]) { + const script = input[0]; + const optionalStdout = input[1]; + const expectedCode = input[2]; + const x = tmpdirSync(); + const path = join(x, "index.js"); + fs.writeFileSync(path, script); + return expect([path]).toRun(optionalStdout, expectedCode); + }, }); export function ospath(path: string) { @@ -1030,7 +1039,8 @@ interface BunHarnessTestMatchers { toBeUTF16String(): void; toHaveTestTimedOutAfter(expected: number): void; toBeBinaryType(expected: keyof typeof binaryTypes): void; - toRun(optionalStdout?: string): void; + toRun(optionalStdout?: string, expectedCode?: number): void; + toRunInlineFixture(input: [string, string?]): void; } declare module "bun:test" { diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index cb8b466fb5..b17f1a4062 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -655,3 +655,108 @@ it("process.execArgv", async () => { expect(result, `bun ${cmd}`).toEqual({ execArgv, argv }); } }); + +describe("process.exitCode", () => { + it("normal", () => { + expect([ + ` + process.on("exit", (code) => console.log("exit", code, process.exitCode)); + process.on("beforeExit", (code) => console.log("beforeExit", code, process.exitCode)); + `, + "beforeExit 0 undefined\nexit 0 undefined\n", + 0, + ]).toRunInlineFixture(); + }); + + it("setter", () => { + expect([ + ` + process.on("exit", (code) => console.log("exit", code, process.exitCode)); + process.on("beforeExit", (code) => console.log("beforeExit", code, process.exitCode)); + + process.exitCode = 0; + `, + "beforeExit 0 0\nexit 0 0\n", + 0, + ]).toRunInlineFixture(); + }); + + it("setter non-zero", () => { + expect([ + ` + process.on("exit", (code) => console.log("exit", code, process.exitCode)); + process.on("beforeExit", (code) => console.log("beforeExit", code, process.exitCode)); + + process.exitCode = 3; + `, + "beforeExit 3 3\nexit 3 3\n", + 3, + ]).toRunInlineFixture(); + }); + + it("exit", () => { + expect([ + ` + process.on("exit", (code) => console.log("exit", code, process.exitCode)); + process.on("beforeExit", (code) => console.log("beforeExit", code, process.exitCode)); + + process.exit(0); + `, + "exit 0 0\n", + 0, + ]).toRunInlineFixture(); + }); + + it("exit non-zero", () => { + expect([ + ` + process.on("exit", (code) => console.log("exit", code, process.exitCode)); + process.on("beforeExit", (code) => console.log("beforeExit", code, process.exitCode)); + + process.exit(3); + `, + "exit 3 3\n", + 3, + ]).toRunInlineFixture(); + }); + + it("property access on undefined", () => { + expect([ + ` + process.on("exit", (code) => console.log("exit", code, process.exitCode)); + process.on("beforeExit", (code) => console.log("beforeExit", code, process.exitCode)); + + const x = {}; + x.y.z(); + `, + "exit 1 1\n", + 1, + ]).toRunInlineFixture(); + }); + + it("thrown Error", () => { + expect([ + ` + process.on("exit", (code) => console.log("exit", code, process.exitCode)); + process.on("beforeExit", (code) => console.log("beforeExit", code, process.exitCode)); + + throw new Error("oops"); + `, + "exit 1 1\n", + 1, + ]).toRunInlineFixture(); + }); + + it("unhandled rejected promise", () => { + expect([ + ` + process.on("exit", (code) => console.log("exit", code, process.exitCode)); + process.on("beforeExit", (code) => console.log("beforeExit", code, process.exitCode)); + + await Promise.reject(); + `, + "exit 1 1\n", + 1, + ]).toRunInlineFixture(); + }); +}); From 610c7f5e47d08b276e871c636dcd84aef2b14edb Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 24 Jul 2024 22:27:14 -0700 Subject: [PATCH 059/123] fix memory lifetime of define expressions (#12784) --- src/defines.zig | 88 ++++++++++++++----------------------------------- src/options.zig | 2 +- 2 files changed, 26 insertions(+), 64 deletions(-) diff --git a/src/defines.zig b/src/defines.zig index 2974547b7c..296bd987a4 100644 --- a/src/defines.zig +++ b/src/defines.zig @@ -66,8 +66,8 @@ pub const DefineData = struct { }; } - pub fn from_mergable_input(defines: RawDefines, user_defines: *UserDefines, log: *logger.Log, allocator: std.mem.Allocator) !void { - try user_defines.ensureUnusedCapacity(@as(u32, @truncate(defines.count()))); + pub fn fromMergeableInput(defines: RawDefines, user_defines: *UserDefines, log: *logger.Log, allocator: std.mem.Allocator) !void { + try user_defines.ensureUnusedCapacity(@truncate(defines.count())); var iter = defines.iterator(); while (iter.next()) |entry| { var keySplitter = std.mem.split(u8, entry.key_ptr.*, "."); @@ -85,42 +85,33 @@ pub const DefineData = struct { // check for nested identifiers var valueSplitter = std.mem.split(u8, entry.value_ptr.*, "."); var isIdent = true; + while (valueSplitter.next()) |part| { if (!js_lexer.isIdentifier(part) or js_lexer.Keywords.has(part)) { isIdent = false; break; } } - if (isIdent) { + if (isIdent) { // Special-case undefined. it's not an identifier here // https://github.com/evanw/esbuild/issues/1407 - if (strings.eqlComptime(entry.value_ptr.*, "undefined")) { - user_defines.putAssumeCapacity( - entry.key_ptr.*, - DefineData{ - .value = js_ast.Expr.Data{ .e_undefined = js_ast.E.Undefined{} }, - .original_name = entry.value_ptr.*, - .can_be_removed_if_unused = true, - }, - ); - } else { - const ident = js_ast.E.Identifier{ .ref = Ref.None, .can_be_removed_if_unused = true }; + const value = if (strings.eqlComptime(entry.value_ptr.*, "undefined")) + js_ast.Expr.Data{ .e_undefined = js_ast.E.Undefined{} } + else + js_ast.Expr.Data{ .e_identifier = .{ + .ref = Ref.None, + .can_be_removed_if_unused = true, + } }; - user_defines.putAssumeCapacity( - entry.key_ptr.*, - DefineData{ - .value = js_ast.Expr.Data{ .e_identifier = ident }, - .original_name = entry.value_ptr.*, - .can_be_removed_if_unused = true, - }, - ); - } - - // user_defines.putAssumeCapacity( - // entry.key_ptr, - // DefineData{ .value = js_ast.Expr.Data{.e_identifier = } }, - // ); + user_defines.putAssumeCapacity( + entry.key_ptr.*, + DefineData{ + .value = value, + .original_name = entry.value_ptr.*, + .can_be_removed_if_unused = true, + }, + ); continue; } const _log = log; @@ -129,47 +120,18 @@ pub const DefineData = struct { .path = defines_path, .key_path = fs.Path.initWithNamespace("defines", "internal"), }; - var expr = try json_parser.ParseEnvJSON(&source, _log, allocator); - var data: js_ast.Expr.Data = undefined; - switch (expr.data) { - .e_missing => { - data = .{ .e_missing = js_ast.E.Missing{} }; - }, - // We must copy so we don't recycle - .e_string => { - data = .{ .e_string = try allocator.create(js_ast.E.String) }; - data.e_string.* = try expr.data.e_string.clone(allocator); - }, - .e_null, .e_boolean, .e_number => { - data = expr.data; - }, - // We must copy so we don't recycle - .e_object => |obj| { - expr.data.e_object = try allocator.create(js_ast.E.Object); - expr.data.e_object.* = obj.*; - data = expr.data; - }, - // We must copy so we don't recycle - .e_array => |obj| { - expr.data.e_array = try allocator.create(js_ast.E.Array); - expr.data.e_array.* = obj.*; - data = expr.data; - }, - else => { - continue; - }, - } - + const expr = try json_parser.ParseEnvJSON(&source, _log, allocator); + const cloned = try expr.data.deepClone(allocator); user_defines.putAssumeCapacity(entry.key_ptr.*, DefineData{ - .value = data, - .can_be_removed_if_unused = @as(js_ast.Expr.Tag, data).isPrimitiveLiteral(), + .value = cloned, + .can_be_removed_if_unused = expr.isPrimitiveLiteral(), }); } } - pub fn from_input(defines: RawDefines, log: *logger.Log, allocator: std.mem.Allocator) !UserDefines { + pub fn fromInput(defines: RawDefines, log: *logger.Log, allocator: std.mem.Allocator) !UserDefines { var user_defines = UserDefines.init(allocator); - try from_mergable_input(defines, &user_defines, log, allocator); + try fromMergeableInput(defines, &user_defines, log, allocator); return user_defines; } diff --git a/src/options.zig b/src/options.zig index a1f7f0a074..b2b60949cb 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1240,7 +1240,7 @@ pub fn definesFromTransformOptions( } } - const resolved_defines = try defines.DefineData.from_input(user_defines, log, allocator); + const resolved_defines = try defines.DefineData.fromInput(user_defines, log, allocator); return try defines.Define.init( allocator, From 5f118704ec04f03554bbccad5c2eed3c0edfa89b Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Wed, 24 Jul 2024 22:27:32 -0700 Subject: [PATCH 060/123] web-apis.md: make this list diff better; does not change presentation (#12795) --- docs/runtime/web-apis.md | 45 +++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/docs/runtime/web-apis.md b/docs/runtime/web-apis.md index 98c822274f..4280aa4078 100644 --- a/docs/runtime/web-apis.md +++ b/docs/runtime/web-apis.md @@ -7,22 +7,36 @@ The following Web APIs are partially or completely supported. --- - HTTP -- [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) +- [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) + [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) + [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) + [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) + [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) + [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) --- - URLs -- [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) +- [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) + [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) --- - Web Workers -- [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker) [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/postMessage) [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) [`MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort) [`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel), [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel). +- [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker) + [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/postMessage) + [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) + [`MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort) + [`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel), [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel). --- - Streams -- [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy) [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) and associated classes +- [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) + [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) + [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) + [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy) + [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) and associated classes --- @@ -37,7 +51,10 @@ The following Web APIs are partially or completely supported. --- - Encoding and decoding -- [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob) [`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/btoa) [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) +- [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob) + [`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/btoa) + [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) + [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) --- @@ -47,7 +64,8 @@ The following Web APIs are partially or completely supported. --- - Timeouts -- [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout) +- [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) + [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout) --- @@ -57,14 +75,16 @@ The following Web APIs are partially or completely supported. --- - Crypto -- [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) +- [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) + [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey) --- - Debugging -- [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/Performance) +- [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) + [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/Performance) --- @@ -79,7 +99,9 @@ The following Web APIs are partially or completely supported. --- - User interaction -- [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) (intended for interactive CLIs) +- [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) + [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) + [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) (intended for interactive CLIs) " >> comment.md - - name: Find Comment - id: comment - uses: peter-evans/find-comment@v3 - with: - issue-number: ${{ steps.env.outputs.pr-number }} - comment-author: github-actions[bot] - body-includes: - - name: Write Comment - uses: peter-evans/create-or-update-comment@v4 - with: - comment-id: ${{ steps.comment.outputs.comment-id }} - issue-number: ${{ steps.env.outputs.pr-number }} - body-path: comment.md - edit-mode: replace diff --git a/.github/workflows/create-release-build.yml b/.github/workflows/create-release-build.yml deleted file mode 100644 index 42adea0585..0000000000 --- a/.github/workflows/create-release-build.yml +++ /dev/null @@ -1,183 +0,0 @@ -name: Create Release Build -run-name: Compile Bun v${{ inputs.version }} by ${{ github.actor }} - -concurrency: - group: release - cancel-in-progress: true - -permissions: - contents: write - actions: write - -on: - workflow_dispatch: - inputs: - version: - type: string - required: true - description: "Release version. Example: 1.1.4. Exclude the 'v' prefix." - tag: - type: string - required: true - description: "GitHub tag to use" - clobber: - type: boolean - required: false - default: false - description: "Overwrite existing release artifacts?" - release: - types: - - created - -jobs: - notify-start: - if: ${{ github.repository_owner == 'oven-sh' }} - name: Notify Start - runs-on: ubuntu-latest - steps: - - name: Send Message - uses: sarisia/actions-status-discord@v1 - with: - webhook: ${{ secrets.DISCORD_WEBHOOK_PUBLIC }} - nodetail: true - color: "#1F6FEB" - title: "Bun v${{ inputs.version }} is compiling" - description: | - ### @${{ github.actor }} started compiling Bun v${{inputs.version}} - - name: Send Message - uses: sarisia/actions-status-discord@v1 - with: - webhook: ${{ secrets.BUN_DISCORD_GITHUB_CHANNEL_WEBHOOK }} - nodetail: true - color: "#1F6FEB" - title: "Bun v${{ inputs.version }} is compiling" - description: | - ### @${{ github.actor }} started compiling Bun v${{inputs.version}} - - **[View logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})** - linux-x64: - name: Build linux-x64 - uses: ./.github/workflows/build-linux.yml - secrets: inherit - with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'namespace-profile-bun-ci-linux-x64' || 'ubuntu-latest' }} - tag: linux-x64 - arch: x64 - cpu: haswell - canary: false - linux-x64-baseline: - name: Build linux-x64-baseline - uses: ./.github/workflows/build-linux.yml - secrets: inherit - with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'namespace-profile-bun-ci-linux-x64' || 'ubuntu-latest' }} - tag: linux-x64-baseline - arch: x64 - cpu: nehalem - canary: false - linux-aarch64: - name: Build linux-aarch64 - uses: ./.github/workflows/build-linux.yml - secrets: inherit - with: - runs-on: namespace-profile-bun-ci-linux-aarch64 - tag: linux-aarch64 - arch: aarch64 - cpu: native - canary: false - darwin-x64: - name: Build darwin-x64 - uses: ./.github/workflows/build-darwin.yml - secrets: inherit - with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-13-large' || 'macos-13' }} - tag: darwin-x64 - arch: x64 - cpu: haswell - canary: false - darwin-x64-baseline: - name: Build darwin-x64-baseline - uses: ./.github/workflows/build-darwin.yml - secrets: inherit - with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'macos-13-large' || 'macos-13' }} - tag: darwin-x64-baseline - arch: x64 - cpu: nehalem - canary: false - darwin-aarch64: - name: Build darwin-aarch64 - uses: ./.github/workflows/build-darwin.yml - secrets: inherit - with: - runs-on: ${{ github.repository_owner == 'oven-sh' && 'namespace-profile-bun-ci-darwin-aarch64' || 'macos-13' }} - tag: darwin-aarch64 - arch: aarch64 - cpu: native - canary: false - windows-x64: - name: Build windows-x64 - uses: ./.github/workflows/build-windows.yml - secrets: inherit - with: - runs-on: windows - tag: windows-x64 - arch: x64 - cpu: haswell - canary: false - windows-x64-baseline: - name: Build windows-x64-baseline - uses: ./.github/workflows/build-windows.yml - secrets: inherit - with: - runs-on: windows - tag: windows-x64-baseline - arch: x64 - cpu: nehalem - canary: false - - upload-artifacts: - needs: - - linux-x64 - - linux-x64-baseline - - linux-aarch64 - - darwin-x64 - - darwin-x64-baseline - - darwin-aarch64 - - windows-x64 - - windows-x64-baseline - runs-on: ubuntu-latest - steps: - - name: Download Artifacts - uses: actions/download-artifact@v4 - with: - path: bun-releases - pattern: bun-* - merge-multiple: true - github-token: ${{ github.token }} - - name: Check for Artifacts - run: | - if [ ! -d "bun-releases" ] || [ -z "$(ls -A bun-releases)" ]; then - echo "Error: No artifacts were downloaded or 'bun-releases' directory does not exist." - exit 1 # Fail the job if the condition is met - else - echo "Artifacts downloaded successfully." - fi - - name: Send Message - uses: sarisia/actions-status-discord@v1 - with: - webhook: ${{ secrets.DISCORD_WEBHOOK }} - nodetail: true - color: "#FF0000" - title: "Bun v${{ inputs.version }} release artifacts uploaded" - - name: "Upload Artifacts" - env: - GH_TOKEN: ${{ github.token }} - run: | - # Unzip one level deep each artifact - cd bun-releases - for f in *.zip; do - unzip -o $f - done - cd .. - gh release upload --repo=${{ github.repository }} ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.event.release.id }} ${{ inputs.clobber && '--clobber' || '' }} bun-releases/*.zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c31ba8589..cbe6b3e93a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,6 @@ +# TODO: Move this to bash scripts intead of Github Actions +# so it can be run from Buildkite, see: .buildkite/scripts/release.sh + name: Release concurrency: release diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml deleted file mode 100644 index 6efe322a54..0000000000 --- a/.github/workflows/run-test.yml +++ /dev/null @@ -1,224 +0,0 @@ -name: Test - -permissions: - contents: read - actions: read - -on: - workflow_call: - inputs: - runs-on: - type: string - required: true - tag: - type: string - required: true - pr-number: - type: string - required: true - run-id: - type: string - default: ${{ github.run_id }} - -jobs: - test: - name: Tests - runs-on: ${{ inputs.runs-on }} - steps: - - if: ${{ runner.os == 'Windows' }} - name: Setup Git - run: | - git config --global core.autocrlf false - git config --global core.eol lf - - name: Checkout - uses: actions/checkout@v4 - with: - sparse-checkout: | - package.json - bun.lockb - test - packages/bun-internal-test - packages/bun-types - - name: Setup Environment - shell: bash - run: | - echo "${{ inputs.pr-number }}" > pr-number.txt - - name: Download Bun - uses: actions/download-artifact@v4 - with: - name: bun-${{ inputs.tag }} - path: bun - github-token: ${{ github.token }} - run-id: ${{ inputs.run-id || github.run_id }} - - name: Download pnpm - uses: pnpm/action-setup@v4 - with: - version: 8 - - if: ${{ runner.os != 'Windows' }} - name: Setup Bun - shell: bash - run: | - unzip bun/bun-*.zip - cd bun-* - pwd >> $GITHUB_PATH - - if: ${{ runner.os == 'Windows' }} - name: Setup Cygwin - uses: secondlife/setup-cygwin@v3 - with: - packages: bash - - if: ${{ runner.os == 'Windows' }} - name: Setup Bun (Windows) - run: | - unzip bun/bun-*.zip - cd bun-* - pwd >> $env:GITHUB_PATH - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - name: Install Dependencies - timeout-minutes: 5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - bun install - - name: Install Dependencies (test) - timeout-minutes: 5 - run: | - bun install --cwd test - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Install Dependencies (runner) - timeout-minutes: 5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - bun install --cwd packages/bun-internal-test - - name: Run Tests - id: test - timeout-minutes: 90 - shell: bash - env: - IS_BUN_CI: 1 - TMPDIR: ${{ runner.temp }} - BUN_TAG: ${{ inputs.tag }} - BUN_FEATURE_FLAG_INTERNAL_FOR_TESTING: "true" - SMTP_SENDGRID_SENDER: ${{ secrets.SMTP_SENDGRID_SENDER }} - TLS_MONGODB_DATABASE_URL: ${{ secrets.TLS_MONGODB_DATABASE_URL }} - TLS_POSTGRES_DATABASE_URL: ${{ secrets.TLS_POSTGRES_DATABASE_URL }} - TEST_INFO_STRIPE: ${{ secrets.TEST_INFO_STRIPE }} - TEST_INFO_AZURE_SERVICE_BUS: ${{ secrets.TEST_INFO_AZURE_SERVICE_BUS }} - SHELLOPTS: igncr - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - node packages/bun-internal-test/src/runner.node.mjs $(which bun) - - if: ${{ always() }} - name: Upload Results - uses: actions/upload-artifact@v4 - with: - name: bun-${{ inputs.tag }}-tests - path: | - test-report.* - comment.md - pr-number.txt - if-no-files-found: error - overwrite: true - - if: ${{ always() && steps.test.outputs.failing_tests != '' && github.event.pull_request && github.repository_owner == 'oven-sh' }} - name: Send Message - uses: sarisia/actions-status-discord@v1 - with: - webhook: ${{ secrets.DISCORD_WEBHOOK }} - nodetail: true - color: "#FF0000" - title: "" - description: | - ### ❌ [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }}) - - @${{ github.actor }}, there are ${{ steps.test.outputs.failing_tests_count || 'some' }} failing tests on bun-${{ inputs.tag }}. - - ${{ steps.test.outputs.failing_tests }} - - **[View logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})** - - name: Fail - if: ${{ failure() || always() && steps.test.outputs.failing_tests != '' }} - run: | - echo "There are ${{ steps.test.outputs.failing_tests_count || 'some' }} failing tests on bun-${{ inputs.tag }}." - exit 1 - test-node: - name: Node.js Tests - # TODO: enable when we start paying attention to the results. In the meantime, this causes CI to queue jobs wasting developer time. - if: 0 - runs-on: ${{ inputs.runs-on }} - steps: - - if: ${{ runner.os == 'Windows' }} - name: Setup Git - run: | - git config --global core.autocrlf false - git config --global core.eol lf - - name: Checkout - uses: actions/checkout@v4 - with: - sparse-checkout: | - test/node.js - - name: Setup Environment - shell: bash - run: | - echo "${{ inputs.pr-number }}" > pr-number.txt - - name: Download Bun - uses: actions/download-artifact@v4 - with: - name: bun-${{ inputs.tag }} - path: bun - github-token: ${{ github.token }} - run-id: ${{ inputs.run-id || github.run_id }} - - if: ${{ runner.os != 'Windows' }} - name: Setup Bun - shell: bash - run: | - unzip bun/bun-*.zip - cd bun-* - pwd >> $GITHUB_PATH - - if: ${{ runner.os == 'Windows' }} - name: Setup Cygwin - uses: secondlife/setup-cygwin@v3 - with: - packages: bash - - if: ${{ runner.os == 'Windows' }} - name: Setup Bun (Windows) - run: | - unzip bun/bun-*.zip - cd bun-* - pwd >> $env:GITHUB_PATH - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - name: Checkout Tests - shell: bash - working-directory: test/node.js - run: | - node runner.mjs --pull - - name: Install Dependencies - timeout-minutes: 5 - shell: bash - working-directory: test/node.js - run: | - bun install - - name: Run Tests - timeout-minutes: 10 # Increase when more tests are added - shell: bash - working-directory: test/node.js - env: - TMPDIR: ${{ runner.temp }} - BUN_GARBAGE_COLLECTOR_LEVEL: "0" - BUN_FEATURE_FLAG_INTERNAL_FOR_TESTING: "true" - run: | - node runner.mjs - - name: Upload Results - uses: actions/upload-artifact@v4 - with: - name: bun-${{ inputs.tag }}-node-tests - path: | - test/node.js/summary/*.json - if-no-files-found: error - overwrite: true diff --git a/.github/workflows/upload.yml b/.github/workflows/upload.yml deleted file mode 100644 index d111d17f6b..0000000000 --- a/.github/workflows/upload.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Upload Artifacts -run-name: Canary release ${{github.sha}} upload - -permissions: - contents: write - -on: - workflow_run: - workflows: - - CI - types: - - completed - branches: - - main - -jobs: - upload: - if: ${{ github.repository_owner == 'oven-sh' }} - name: Upload Artifacts - runs-on: ubuntu-latest - steps: - - name: Download Artifacts - uses: actions/download-artifact@v4 - with: - path: bun - pattern: bun-* - merge-multiple: true - github-token: ${{ github.token }} - run-id: ${{ github.event.workflow_run.id }} - - name: Check for Artifacts - run: | - if [ ! -d "bun" ] || [ -z "$(ls -A bun)" ]; then - echo "Error: No artifacts were downloaded or 'bun' directory does not exist." - exit 1 # Fail the job if the condition is met - else - echo "Artifacts downloaded successfully." - fi - - name: Upload to GitHub Releases - uses: ncipollo/release-action@v1 - with: - tag: canary - name: Canary (${{ github.sha }}) - prerelease: true - body: This canary release of Bun corresponds to the commit [${{ github.sha }}] - allowUpdates: true - replacesArtifacts: true - generateReleaseNotes: true - artifactErrorsFailBuild: true - artifacts: bun/**/bun-*.zip - token: ${{ github.token }} - - name: Upload to S3 (using SHA) - uses: shallwefootball/s3-upload-action@4350529f410221787ccf424e50133cbc1b52704e - with: - endpoint: ${{ secrets.AWS_ENDPOINT }} - aws_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} - aws_bucket: ${{ secrets.AWS_BUCKET }} - source_dir: bun - destination_dir: releases/${{ github.event.workflow_run.head_sha || github.sha }}-canary - - name: Upload to S3 (using tag) - uses: shallwefootball/s3-upload-action@4350529f410221787ccf424e50133cbc1b52704e - with: - endpoint: ${{ secrets.AWS_ENDPOINT }} - aws_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} - aws_bucket: ${{ secrets.AWS_BUCKET }} - source_dir: bun - destination_dir: releases/canary - - name: Announce on Discord - uses: sarisia/actions-status-discord@v1 - with: - webhook: ${{ secrets.BUN_DISCORD_GITHUB_CHANNEL_WEBHOOK }} - nodetail: true - color: "#1F6FEB" - title: "New Bun Canary available" - url: https://github.com/oven-sh/bun/commit/${{ github.sha }} - description: | - A new canary build of Bun has been automatically uploaded. To upgrade, run: - ```sh - bun upgrade --canary - # bun upgrade --stable <- to downgrade - ``` - # If notifying sentry fails, don't fail the rest of the build. - - name: Notify Sentry - uses: getsentry/action-release@v1.7.0 - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_ORG: ${{ secrets.SENTRY_ORG }} - SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - with: - ignore_missing: true - ignore_empty: true - version: ${{ github.event.workflow_run.head_sha || github.sha }}-canary - environment: canary diff --git a/scripts/all-dependencies.sh b/scripts/all-dependencies.sh index 50a22fe8f8..627339a077 100755 --- a/scripts/all-dependencies.sh +++ b/scripts/all-dependencies.sh @@ -36,9 +36,11 @@ fi dep() { local submodule="$1" local script="$2" - CACHE_KEY= if [ "$CACHE" == "1" ]; then - CACHE_KEY="$submodule/$(echo "$SUBMODULES" | grep "$submodule" | git hash-object --stdin)" + local hash="$(echo "$SUBMODULES" | grep "$submodule" | awk '{print $1}')" + local os="$(uname -s | tr '[:upper:]' '[:lower:]')" + local arch="$(uname -m)" + CACHE_KEY="$submodule/$hash-$os-$arch-$CPU_TARGET" fi if [ -z "$FORCE" ]; then HAS_ALL_DEPS=1 diff --git a/scripts/build-bun-cpp.ps1 b/scripts/build-bun-cpp.ps1 index ab79b7f1b3..adb1a57cf7 100755 --- a/scripts/build-bun-cpp.ps1 +++ b/scripts/build-bun-cpp.ps1 @@ -1,29 +1,29 @@ -param ( - [switch] $Baseline = $False, - [switch] $Fast = $False -) - $ErrorActionPreference = 'Stop' # Setting strict mode, similar to 'set -euo pipefail' in bash -$Tag = If ($Baseline) { "-Baseline" } Else { "" } -$UseBaselineBuild = If ($Baseline) { "ON" } Else { "OFF" } -$UseLto = If ($Fast) { "OFF" } Else { "ON" } - -# $CANARY_REVISION = if (Test-Path build/.canary_revision) { Get-Content build/.canary_revision } else { "0" } -$CANARY_REVISION = 0 -.\scripts\env.ps1 $Tag +.\scripts\env.ps1 .\scripts\update-submodules.ps1 .\scripts\build-libuv.ps1 -CloneOnly $True -cd build -cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release ` +# libdeflate.h is needed otherwise the build fails +git submodule update --init --recursive --progress --depth=1 --checkout src/deps/libdeflate + +cd build +cmake .. @CMAKE_FLAGS ` + -G Ninja ` + -DCMAKE_BUILD_TYPE=Release ` -DNO_CODEGEN=0 ` -DNO_CONFIGURE_DEPENDS=1 ` - "-DUSE_BASELINE_BUILD=${UseBaselineBuild}" ` - "-DUSE_LTO=${UseLto}" ` - "-DCANARY=${CANARY_REVISION}" ` - -DBUN_CPP_ONLY=1 $Flags + -DBUN_CPP_ONLY=1 if ($LASTEXITCODE -ne 0) { throw "CMake configuration failed" } -.\compile-cpp-only.ps1 -v -if ($LASTEXITCODE -ne 0) { throw "C++ compilation failed" } \ No newline at end of file +.\compile-cpp-only.ps1 -v -j $env:CPUS +if ($LASTEXITCODE -ne 0) { throw "C++ compilation failed" } + +# HACK: For some reason, the buildkite agent is hanging when uploading bun-cpp-objects.a +# Best guess is that there is an issue when uploading files larger than 500 MB +# +# For now, use FileSplitter to split the file into smaller chunks: +# https://www.powershellgallery.com/packages/FileSplitter/1.3 +if ($env:BUILDKITE) { + Split-File -Path (Resolve-Path "bun-cpp-objects.a") -PartSizeBytes "50MB" -Verbose +} diff --git a/scripts/build-bun-cpp.sh b/scripts/build-bun-cpp.sh deleted file mode 100755 index 631452d942..0000000000 --- a/scripts/build-bun-cpp.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -set -exo pipefail -source $(dirname -- "${BASH_SOURCE[0]}")/env.sh - -export USE_LTO="${USE_LTO:-ON}" -case "$(uname -m)" in - aarch64|arm64) - export CPU_TARGET="${CPU_TARGET:-native}" - ;; - *) - export CPU_TARGET="${CPU_TARGET:-haswell}" - ;; -esac - -while [[ $# -gt 0 ]]; do - case "$1" in - --fast|--no-lto) - export USE_LTO="OFF" - shift - ;; - --baseline) - export CPU_TARGET="nehalem" - shift - ;; - --cpu) - export CPU_TARGET="$2" - shift - shift - ;; - *|-*|--*) - echo "Unknown option $1" - exit 1 - ;; - esac -done - -mkdir -p build -cd build -mkdir -p tmp_modules tmp_functions js codegen -cmake .. \ - -GNinja \ - -DCMAKE_BUILD_TYPE=Release \ - -DUSE_LTO=${USE_LTO} \ - -DCPU_TARGET=${CPU_TARGET} \ - -DBUN_CPP_ONLY=1 \ - -DNO_CONFIGURE_DEPENDS=1 -chmod +x ./compile-cpp-only.sh -bash ./compile-cpp-only.sh -v diff --git a/scripts/build-bun-zig.sh b/scripts/build-bun-zig.sh deleted file mode 100755 index 489c635d12..0000000000 --- a/scripts/build-bun-zig.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env bash -set -exo pipefail -source $(dirname -- "${BASH_SOURCE[0]}")/env.sh - -cwd=$(pwd) -zig= - -if [[ "$CI" ]]; then - # Since the zig build depends on files from the zig submodule, - # make sure to update the submodule before building. - git submodule update --init --recursive --progress --depth=1 --checkout src/deps/zig - - # Also update the correct version of zig in the submodule. - $(dirname -- "${BASH_SOURCE[0]}")/download-zig.sh -fi - -if [ -f "$cwd/.cache/zig/zig" ]; then - zig="$cwd/.cache/zig/zig" -else - zig=$(which zig) -fi - -ZIG_OPTIMIZE="${ZIG_OPTIMIZE:-ReleaseFast}" -CANARY="${CANARY:-0}" -GIT_SHA="${GIT_SHA:-$(git rev-parse HEAD)}" - -BUILD_MACHINE_ARCH="${BUILD_MACHINE_ARCH:-$(uname -m)}" -DOCKER_MACHINE_ARCH="" -if [[ "$BUILD_MACHINE_ARCH" == "x86_64" || "$BUILD_MACHINE_ARCH" == "amd64" ]]; then - BUILD_MACHINE_ARCH="x86_64" - DOCKER_MACHINE_ARCH="amd64" -elif [[ "$BUILD_MACHINE_ARCH" == "aarch64" || "$BUILD_MACHINE_ARCH" == "arm64" ]]; then - BUILD_MACHINE_ARCH="aarch64" - DOCKER_MACHINE_ARCH="arm64" -fi - -TARGET_OS="${1:-linux}" -TARGET_ARCH="${2:-x64}" -TARGET_CPU="${3:-${CPU_TARGET:-native}}" - -BUILDARCH="" -if [[ "$TARGET_ARCH" == "x64" || "$TARGET_ARCH" == "x86_64" || "$TARGET_ARCH" == "amd64" ]]; then - TARGET_ARCH="x86_64" - BUILDARCH="amd64" -elif [[ "$TARGET_ARCH" == "aarch64" || "$TARGET_ARCH" == "arm64" ]]; then - TARGET_ARCH="aarch64" - BUILDARCH="arm64" -fi - -TRIPLET="" -if [[ "$TARGET_OS" == "linux" ]]; then - TRIPLET="$TARGET_ARCH-linux-gnu" -elif [[ "$TARGET_OS" == "darwin" ]]; then - TRIPLET="$TARGET_ARCH-macos-none" -elif [[ "$TARGET_OS" == "windows" ]]; then - TRIPLET="$TARGET_ARCH-windows-msvc" -fi - -echo "--- Building identifier-cache" -$zig run src/js_lexer/identifier_data.zig - -echo "--- Building node-fallbacks" -cd src/node-fallbacks -bun install --frozen-lockfile -bun run build -cd "$cwd" - -echo "--- Building codegen" -bun install --frozen-lockfile -make runtime_js fallback_decoder bun_error - -echo "--- Building modules" -mkdir -p build -bun run src/codegen/bundle-modules.ts --debug=OFF build - -echo "--- Building zig" -cd build -cmake .. \ - -GNinja \ - -DCMAKE_BUILD_TYPE=Release \ - -DUSE_LTO=ON \ - -DZIG_OPTIMIZE="${ZIG_OPTIMIZE}" \ - -DGIT_SHA="${GIT_SHA}" \ - -DARCH="${TARGET_ARCH}" \ - -DBUILDARCH="${BUILDARCH}" \ - -DCPU_TARGET="${TARGET_CPU}" \ - -DZIG_TARGET="${TRIPLET}" \ - -DASSERTIONS="OFF" \ - -DWEBKIT_DIR="omit" \ - -DNO_CONFIGURE_DEPENDS=1 \ - -DNO_CODEGEN=1 \ - -DBUN_ZIG_OBJ_DIR="$cwd/build" \ - -DCANARY="$CANARY" \ - -DZIG_LIB_DIR=src/deps/zig/lib -ONLY_ZIG=1 ninja "$cwd/build/bun-zig.o" -v diff --git a/scripts/build-tinycc.ps1 b/scripts/build-tinycc.ps1 index 78a10f42e8..095fd97dd0 100755 --- a/scripts/build-tinycc.ps1 +++ b/scripts/build-tinycc.ps1 @@ -20,8 +20,6 @@ try { Run clang-cl -DTCC_TARGET_PE -DTCC_TARGET_X86_64 config.h -DC2STR -o c2str.exe conftest.c Run .\c2str.exe .\include\tccdefs.h tccdefs_.h - $Baseline = $env:BUN_DEV_ENV_SET -eq "Baseline=True" - Run clang-cl @($env:CFLAGS -split ' ') libtcc.c -o tcc.obj "-DTCC_TARGET_PE" "-DTCC_TARGET_X86_64" "-O2" "-W2" "-Zi" "-MD" "-GS-" "-c" "-MT" Run llvm-lib "tcc.obj" "-OUT:tcc.lib" diff --git a/scripts/buildkite-link-bun.ps1 b/scripts/buildkite-link-bun.ps1 index b56e0eefc1..caa7d98ce7 100755 --- a/scripts/buildkite-link-bun.ps1 +++ b/scripts/buildkite-link-bun.ps1 @@ -1,17 +1,13 @@ -param ( - [switch] $Baseline = $False, - [switch] $Fast = $False +param( + [switch]$Baseline = $false ) $ErrorActionPreference = 'Stop' # Setting strict mode, similar to 'set -euo pipefail' in bash $Target = If ($Baseline) { "windows-x64-baseline" } Else { "windows-x64" } $Tag = "bun-$Target" -$TagSuffix = If ($Baseline) { "-Baseline" } Else { "" } -$UseBaselineBuild = If ($Baseline) { "ON" } Else { "OFF" } -$UseLto = If ($Fast) { "OFF" } Else { "ON" } -.\scripts\env.ps1 $TagSuffix +.\scripts\env.ps1 mkdir -Force build buildkite-agent artifact download "**" build --step "${Target}-build-zig" @@ -21,29 +17,24 @@ mv -Force -ErrorAction SilentlyContinue build\build\bun-deps\* build\bun-deps mv -Force -ErrorAction SilentlyContinue build\build\* build Set-Location build -$CANARY_REVISION = 0 -cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release ` + +# HACK: See scripts/build-bun-cpp.ps1 +Join-File -Path "$(Resolve-Path .)\bun-cpp-objects.a" -Verbose -DeletePartFiles + +cmake .. @CMAKE_FLAGS ` + -G Ninja ` + -DCMAKE_BUILD_TYPE=Release ` -DNO_CODEGEN=1 ` -DNO_CONFIGURE_DEPENDS=1 ` - "-DCPU_TARGET=${CPU_TARGET}" ` - "-DCANARY=${CANARY_REVISION}" ` -DBUN_LINK_ONLY=1 ` - "-DUSE_BASELINE_BUILD=${UseBaselineBuild}" ` - "-DUSE_LTO=${UseLto}" ` "-DBUN_DEPS_OUT_DIR=$(Resolve-Path bun-deps)" ` "-DBUN_CPP_ARCHIVE=$(Resolve-Path bun-cpp-objects.a)" ` - "-DBUN_ZIG_OBJ_DIR=$(Resolve-Path .)" ` - "$Flags" + "-DBUN_ZIG_OBJ_DIR=$(Resolve-Path .)" if ($LASTEXITCODE -ne 0) { throw "CMake configuration failed" } -ninja -v +ninja -v -j $env:CPUS if ($LASTEXITCODE -ne 0) { throw "Link failed!" } -ls -if ($Fast) { - $Tag = "$Tag-nolto" -} - Set-Location .. $Dist = mkdir -Force "${Tag}" cp -r build\bun.exe "$Dist\bun.exe" diff --git a/scripts/buildkite-link-bun.sh b/scripts/buildkite-link-bun.sh deleted file mode 100755 index d0456e25ff..0000000000 --- a/scripts/buildkite-link-bun.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env bash -set -exo pipefail -source $(dirname -- "${BASH_SOURCE[0]}")/env.sh - -export USE_LTO="${USE_LTO:-ON}" -case "$(uname -m)" in - aarch64|arm64) - export CPU_TARGET="${CPU_TARGET:-native}" - ;; - *) - export CPU_TARGET="${CPU_TARGET:-haswell}" - ;; -esac - -export TAG="" -while [[ $# -gt 0 ]]; do - case "$1" in - --tag) - export TAG="$2" - shift - shift - ;; - --fast|--no-lto) - export USE_LTO="OFF" - shift - ;; - --baseline) - export CPU_TARGET="nehalem" - shift - ;; - --cpu) - export CPU_TARGET="$2" - shift - shift - ;; - *|-*|--*) - echo "Unknown option $1" - exit 1 - ;; - esac -done - -if [[ -z "$TAG" ]]; then - echo "--tag is required" - exit 1 -fi - -rm -rf release -mkdir -p release -buildkite-agent artifact download '**' release --step $TAG-build-deps -buildkite-agent artifact download '**' release --step $TAG-build-zig -buildkite-agent artifact download '**' release --step $TAG-build-cpp - -cd release -cmake .. \ - -GNinja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCPU_TARGET=${CPU_TARGET} \ - -DUSE_LTO=${USE_LTO} \ - -DBUN_LINK_ONLY=1 \ - -DBUN_ZIG_OBJ_DIR="$(pwd)/build" \ - -DBUN_CPP_ARCHIVE="$(pwd)/build/bun-cpp-objects.a" \ - -DBUN_DEPS_OUT_DIR="$(pwd)/build/bun-deps" \ - -DNO_CONFIGURE_DEPENDS=1 -ninja -v - -if [[ "${USE_LTO}" == "OFF" ]]; then - TAG="${TAG}-nolto" -fi - -chmod +x bun-profile bun -mkdir -p bun-$TAG-profile/ bun-$TAG/ -mv bun-profile bun-$TAG-profile/bun-profile -mv bun bun-$TAG/bun -zip -r bun-$TAG-profile.zip bun-$TAG-profile -zip -r bun-$TAG.zip bun-$TAG - -cd .. -mv release/bun-$TAG.zip bun-$TAG.zip -mv release/bun-$TAG-profile.zip bun-$TAG-profile.zip diff --git a/scripts/env.ps1 b/scripts/env.ps1 index e9492abee4..02c63a68f3 100755 --- a/scripts/env.ps1 +++ b/scripts/env.ps1 @@ -1,11 +1,3 @@ -param( - [switch]$Baseline = $false -) - -if ($ENV:BUN_DEV_ENV_SET -eq "Baseline=True") { - $Baseline = $true -} - $ErrorActionPreference = 'Stop' # Setting strict mode, similar to 'set -euo pipefail' in bash # this is the environment script for building bun's dependencies @@ -38,13 +30,19 @@ if($Env:VSCMD_ARG_TGT_ARCH -eq "x86") { throw "Visual Studio environment is targetting 32 bit. This configuration is definetly a mistake." } -$ENV:BUN_DEV_ENV_SET = "Baseline=$Baseline"; - $BUN_BASE_DIR = if ($env:BUN_BASE_DIR) { $env:BUN_BASE_DIR } else { Join-Path $ScriptDir '..' } $BUN_DEPS_DIR = if ($env:BUN_DEPS_DIR) { $env:BUN_DEPS_DIR } else { Join-Path $BUN_BASE_DIR 'src\deps' } $BUN_DEPS_OUT_DIR = if ($env:BUN_DEPS_OUT_DIR) { $env:BUN_DEPS_OUT_DIR } else { Join-Path $BUN_BASE_DIR 'build\bun-deps' } $CPUS = if ($env:CPUS) { $env:CPUS } else { (Get-CimInstance -Class Win32_Processor).NumberOfCores } +$Lto = if ($env:USE_LTO) { $env:USE_LTO -eq "1" } else { True } +$Baseline = if ($env:USE_BASELINE_BUILD) { + $env:USE_BASELINE_BUILD -eq "1" +} elseif ($env:BUILDKITE_STEP_KEY -match "baseline") { + True +} else { + False +} $CC = "clang-cl" $CXX = "clang-cl" @@ -52,7 +50,7 @@ $CXX = "clang-cl" $CFLAGS = '/O2 /Z7 /MT /O2 /Ob2 /DNDEBUG /U_DLL' $CXXFLAGS = '/O2 /Z7 /MT /O2 /Ob2 /DNDEBUG /U_DLL' -if ($env:USE_LTO -eq "1") { +if ($Lto) { $CXXFLAGS += " -fuse-ld=lld -flto -Xclang -emit-llvm-bc" $CFLAGS += " -fuse-ld=lld -flto -Xclang -emit-llvm-bc" } @@ -63,6 +61,14 @@ $env:CPU_TARGET = $CPU_NAME $CFLAGS += " -march=${CPU_NAME}" $CXXFLAGS += " -march=${CPU_NAME}" +$Canary = If ($env:CANARY) { + $env:CANARY +} ElseIf ($env:BUILDKITE -eq "true") { + (buildkite-agent meta-data get canary) +} Else { + "1" +} + $CMAKE_FLAGS = @( "-GNinja", "-DCMAKE_BUILD_TYPE=Release", @@ -72,7 +78,8 @@ $CMAKE_FLAGS = @( "-DCMAKE_CXX_FLAGS=$CXXFLAGS", "-DCMAKE_C_FLAGS_RELEASE=$CFLAGS", "-DCMAKE_CXX_FLAGS_RELEASE=$CXXFLAGS", - "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded" + "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded", + "-DCANARY=$Canary" ) if (Get-Command llvm-lib -ErrorAction SilentlyContinue) { @@ -92,6 +99,10 @@ if ($Baseline) { $CMAKE_FLAGS += "-DUSE_BASELINE_BUILD=ON" } +if ($Lto) { + $CMAKE_FLAGS += "-DUSE_LTO=ON" +} + if (Get-Command sccache -ErrorAction SilentlyContinue) { # Continue with local compiler if sccache has an error $env:SCCACHE_IGNORE_SERVER_IO_ERROR = "1" diff --git a/scripts/env.sh b/scripts/env.sh index 6ff0e225af..617c1c75ed 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Hack for Buildkite sometimes not having the right path +# Hack for buildkite sometimes not having the right path if [[ "${CI:-}" == "1" || "${CI:-}" == "true" ]]; then if [ -f ~/.bashrc ]; then source ~/.bashrc diff --git a/scripts/experimental-build.mjs b/scripts/experimental-build.mjs index 33b26ab02f..0dcc53138c 100755 --- a/scripts/experimental-build.mjs +++ b/scripts/experimental-build.mjs @@ -6,16 +6,162 @@ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdir import { basename, dirname, join } from "node:path"; import { tmpdir } from "node:os"; -const projectPath = dirname(import.meta.dirname); -const vendorPath = process.env.BUN_VENDOR_PATH || join(projectPath, "vendor"); - const isWindows = process.platform === "win32"; const isMacOS = process.platform === "darwin"; const isLinux = process.platform === "linux"; +const cwd = dirname(import.meta.dirname); const spawnSyncTimeout = 1000 * 60; const spawnTimeout = 1000 * 60 * 3; +/** + * @typedef {Object} S3UploadOptions + * @property {string} [bucket] + * @property {string} filename + * @property {string} content + * @property {Record} [headers] + */ + +/** + * @param {S3UploadOptions} options + */ +async function uploadFileToS3(options) { + const { AwsV4Signer } = await import("aws4fetch"); + + const { bucket, filename, content, ...extra } = options; + const baseUrl = getEnv(["S3_ENDPOINT", "S3_BASE_URL", "AWS_ENDPOINT"], "https://s3.amazonaws.com"); + const bucketUrl = new URL(bucket || getEnv(["S3_BUCKET", "AWS_BUCKET"]), baseUrl); + + const signer = new AwsV4Signer({ + accessKeyId: getSecret(["S3_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID"]), + secretAccessKey: getSecret(["S3_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY"]), + url: new URL(filename, bucketUrl), + method: "PUT", + body: content, + ...extra, + }); + + const { url, method, headers, body } = signer.sign(); + await fetchSafe(url, { + method, + headers, + body, + }); + + console.log("Uploaded file to S3:", { + url: `${bucketUrl}`, + filename, + }); +} + +/** + * @typedef {Object} SentryRelease + * @property {string} organizationId + * @property {string} projectId + * @property {string} version + * @property {string} [url] + * @property {string} [ref] + * @property {string} [dateReleased] + */ + +/** + * @param {SentryRelease} options + * @returns {Promise} + */ +async function createSentryRelease(options) { + const { organizationId, projectId, ...body } = options; + + const baseUrl = getEnv("SENTRY_BASE_URL", "https://sentry.io"); + const url = new URL(`api/0/organizations/${organizationId}/releases`, baseUrl); + const accessToken = getSecret(["SENTRY_AUTH_TOKEN", "SENTRY_TOKEN"]); + + const release = await fetchSafe(url, { + method: "POST", + headers: { + "Authorization": `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + format: "json", + }); + + console.log("Created Sentry release:", release); +} + +/** + * @return {string} + */ +function getGithubToken() { + const token = getEnv("GITHUB_TOKEN", null); + if (token) { + return token; + } + + const gh = which("gh"); + if (gh) { + const { exitCode, stdout } = spawnSyncSafe(gh, ["auth", "token"]); + if (exitCode === 0) { + return stdout.trim(); + } + } + + throw new Error("Failed to get GitHub token (set GITHUB_TOKEN or run `gh auth login`)"); +} + +/** + * @param {string | string[]} name + * @return {string} + */ +function getSecret(name) { + return getEnv(name); +} + +/** + * @param {string | string[]} name + * @param {string | null} [defaultValue] + * @returns {string | undefined} + */ +function getEnv(name, defaultValue) { + let result = defaultValue; + + for (const key of typeof name === "string" ? [name] : name) { + const value = process.env[key]; + if (value) { + result = value; + break; + } + } + + if (result || result === null) { + return result; + } + + throw new Error(`Environment variable is required: ${name}`); +} + +/** + * @typedef {Object} SpawnOptions + * @property {boolean} [throwOnError] + * @property {string} [cwd] + * @property {string} [env] + * @property {string} [encoding] + * @property {number} [timeout] + */ + +/** + * @typedef {Object} SpawnResult + * @property {number | null} exitCode + * @property {number | null} signalCode + * @property {string} stdout + * @property {string} stderr + */ + +/** + * @param {string} command + * @param {string[]} [args] + * @param {SpawnOptions} [options] + * @returns {Promise} + */ async function spawnSafe(command, args, options = {}) { const result = new Promise((resolve, reject) => { let stdout = ""; @@ -60,6 +206,12 @@ async function spawnSafe(command, args, options = {}) { } } +/** + * @param {string} command + * @param {string[]} [args] + * @param {SpawnOptions} [options] + * @returns {SpawnResult} + */ function spawnSyncSafe(command, args, options = {}) { try { const { error, status, signal, stdout, stderr } = spawnSync(command, args, { @@ -86,6 +238,20 @@ function spawnSyncSafe(command, args, options = {}) { } } +/** + * @typedef {Object} FetchOptions + * @property {string} [method] + * @property {Record} [headers] + * @property {string | Uint8Array} [body] + * @property {"json" | "text" | "bytes"} [format] + * @property {boolean} [throwOnError] + */ + +/** + * @param {string | URL} url + * @param {FetchOptions} [options] + * @returns {Promise} + */ async function fetchSafe(url, options = {}) { let response; try { @@ -138,47 +304,6 @@ function which(command, path) { return result.trimEnd(); } -function getZigTarget(os = process.platform, arch = process.arch) { - if (arch === "x64") { - if (os === "linux") return "linux-x86_64"; - if (os === "darwin") return "macos-x86_64"; - if (os === "win32") return "windows-x86_64"; - } - if (arch === "arm64") { - if (os === "linux") return "linux-aarch64"; - if (os === "darwin") return "macos-aarch64"; - } - throw new Error(`Unsupported zig target: os=${os}, arch=${arch}`); -} - -function getRecommendedZigVersion() { - const scriptPath = join(projectPath, "build.zig"); - try { - const scriptContent = readFileSync(scriptPath, "utf-8"); - const match = scriptContent.match(/recommended_zig_version = "([^"]+)"/); - if (!match) { - throw new Error("File does not contain string: 'recommended_zig_version'"); - } - return match[1]; - } catch (cause) { - throw new Error("Failed to find recommended Zig version", { cause }); - } -} - -/** - * @returns {Promise} - */ -async function getLatestZigVersion() { - try { - const response = await fetchSafe("https://ziglang.org/download/index.json", { format: "json" }); - const { master } = response; - const { version } = master; - return version; - } catch (cause) { - throw new Error("Failed to get latest Zig version", { cause }); - } -} - /** * @param {string} execPath * @returns {string | undefined} @@ -191,110 +316,3 @@ function getVersion(execPath) { } return result.trim(); } - -/** - * @returns {string} - */ -function getTmpdir() { - if (isMacOS && existsSync("/tmp")) { - return "/tmp"; - } - return tmpdir(); -} - -/** - * @returns {string} - */ -function mkTmpdir() { - return mkdtempSync(join(getTmpdir(), "bun-")); -} - -/** - * @param {string} url - * @param {string} [path] - * @returns {Promise} - */ -async function downloadFile(url, path) { - const outPath = path || join(mkTmpdir(), basename(url)); - const bytes = await fetchSafe(url, { format: "bytes" }); - mkdirSync(dirname(outPath), { recursive: true }); - writeFileSync(outPath, bytes); - return outPath; -} - -/** - * @param {string} tarPath - * @param {string} [path] - * @returns {Promise} - */ -async function extractFile(tarPath, path) { - const outPath = path || join(mkTmpdir(), basename(tarPath)); - mkdirSync(outPath, { recursive: true }); - await spawnSafe("tar", ["-xf", tarPath, "-C", outPath, "--strip-components=1"]); - return outPath; -} - -const dependencies = [ - { - name: "zig", - version: getRecommendedZigVersion(), - download: downloadZig, - }, -]; - -async function getDependencyPath(name) { - let dependency; - for (const entry of dependencies) { - if (name === entry.name) { - dependency = entry; - break; - } - } - if (!dependency) { - throw new Error(`Unknown dependency: ${name}`); - } - const { version, download } = dependency; - mkdirSync(vendorPath, { recursive: true }); - for (const path of readdirSync(vendorPath)) { - if (!path.startsWith(name)) { - continue; - } - const dependencyPath = join(vendorPath, path); - const dependencyVersion = getVersion(dependencyPath); - if (dependencyVersion === version) { - return dependencyPath; - } - } - if (!download) { - throw new Error(`Dependency not found: ${name}`); - } - return await download(version); -} - -/** - * @param {string} [version] - */ -async function downloadZig(version) { - const target = getZigTarget(); - const expectedVersion = version || getRecommendedZigVersion(); - const url = `https://ziglang.org/builds/zig-${target}-${expectedVersion}.tar.xz`; - const tarPath = await downloadFile(url); - const extractedPath = await extractFile(tarPath); - const zigPath = join(extractedPath, exePath("zig")); - const actualVersion = getVersion(zigPath); - const outPath = join(vendorPath, exePath(`zig-${actualVersion}`)); - mkdirSync(dirname(outPath), { recursive: true }); - copyFileSync(zigPath, outPath); - return outPath; -} - -/** - * @param {string} path - * @returns {string} - */ -function exePath(path) { - return isWindows ? `${path}.exe` : path; -} - -const execPath = await getDependencyPath("zig"); -console.log(execPath); diff --git a/scripts/runner.node.mjs b/scripts/runner.node.mjs index 7d50b148cb..2523da8586 100755 --- a/scripts/runner.node.mjs +++ b/scripts/runner.node.mjs @@ -26,7 +26,7 @@ import { normalize as normalizeWindows } from "node:path/win32"; import { isIP } from "node:net"; import { parseArgs } from "node:util"; -const spawnTimeout = 30_000; +const spawnTimeout = 5_000; const testTimeout = 3 * 60_000; const integrationTimeout = 5 * 60_000; @@ -231,18 +231,20 @@ async function runTests() { */ /** - * @param {SpawnOptions} request + * @param {SpawnOptions} options * @returns {Promise} */ -async function spawnSafe({ - command, - args, - cwd, - env, - timeout = spawnTimeout, - stdout = process.stdout.write.bind(process.stdout), - stderr = process.stderr.write.bind(process.stderr), -}) { +async function spawnSafe(options) { + const { + command, + args, + cwd, + env, + timeout = spawnTimeout, + stdout = process.stdout.write.bind(process.stdout), + stderr = process.stderr.write.bind(process.stderr), + retries = 0, + } = options; let exitCode; let signalCode; let spawnError; @@ -318,6 +320,16 @@ async function spawnSafe({ resolve(); } }); + if (spawnError && retries < 5) { + const { code } = spawnError; + if (code === "EBUSY" || code === "UNKNOWN") { + await new Promise(resolve => setTimeout(resolve, 1000 * (retries + 1))); + return spawnSafe({ + ...options, + retries: retries + 1, + }); + } + } let error; if (exitCode === 0) { // ... @@ -1332,7 +1344,7 @@ function formatTestToMarkdown(result, concise) { let markdown = ""; for (const { testPath, ok, tests, error, stdoutPreview: stdout } of results) { - if (ok) { + if (ok || error === "SIGTERM") { continue; } diff --git a/test/cli/run/run-crash-handler.test.ts b/test/cli/run/run-crash-handler.test.ts index 0769129f8a..4923c6f06d 100644 --- a/test/cli/run/run-crash-handler.test.ts +++ b/test/cli/run/run-crash-handler.test.ts @@ -13,13 +13,11 @@ test.if(process.platform === "darwin")("macOS has the assumed image offset", () test("raise ignoring panic handler does not trigger the panic handler", async () => { let sent = false; - let onresolve = Promise.withResolvers(); using server = Bun.serve({ port: 0, fetch(request, server) { sent = true; - onresolve.resolve(); return new Response("OK"); }, }); @@ -34,11 +32,8 @@ test("raise ignoring panic handler does not trigger the panic handler", async () }, ]), }); - await proc.exited; - await Promise.race([onresolve.promise, Bun.sleep(1000)]); - - expect(proc.exitCode).not.toBe(0); + expect(proc.exited).resolves.not.toBe(0); expect(sent).toBe(false); }); @@ -46,7 +41,6 @@ describe("automatic crash reporter", () => { for (const approach of ["panic", "segfault", "outOfMemory"]) { test(`${approach} should report`, async () => { let sent = false; - let onresolve = Promise.withResolvers(); // Self host the crash report backend. using server = Bun.serve({ @@ -54,7 +48,6 @@ describe("automatic crash reporter", () => { fetch(request, server) { expect(request.url).toEndWith("/ack"); sent = true; - onresolve.resolve(); return new Response("OK"); }, }); @@ -72,15 +65,12 @@ describe("automatic crash reporter", () => { ]), stdio: ["ignore", "pipe", "pipe"], }); - await proc.exited; - - await Promise.race([onresolve.promise, Bun.sleep(1000)]); - + const exitCode = await proc.exited; const stderr = await Bun.readableStreamToText(proc.stderr); console.log(stderr); - expect(proc.exitCode).not.toBe(0); + expect(exitCode).not.toBe(0); expect(stderr).toContain(server.url.toString()); if (approach !== "outOfMemory") { expect(stderr).toContain("oh no: Bun has crashed. This indicates a bug in Bun, not your code"); diff --git a/test/js/bun/dns/resolve-dns.test.ts b/test/js/bun/dns/resolve-dns.test.ts index b237d43a3f..747a2aa3a2 100644 --- a/test/js/bun/dns/resolve-dns.test.ts +++ b/test/js/bun/dns/resolve-dns.test.ts @@ -107,7 +107,7 @@ describe("dns", () => { test.each(malformedHostnames)("'%s'", hostname => { // @ts-expect-error expect(dns.lookup(hostname, { backend })).rejects.toMatchObject({ - code: "DNS_ENOTFOUND", + code: expect.stringMatching(/^DNS_ENOTFOUND|DNS_ESERVFAIL|DNS_ENOTIMP$/), name: "DNSException", }); }); From 005dd776b64444f8a1008df60609b116ba144964 Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Mon, 29 Jul 2024 14:50:26 -0700 Subject: [PATCH 116/123] Allow creating release builds with 'RELEASE=1' --- .buildkite/scripts/prepare-build.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.buildkite/scripts/prepare-build.sh b/.buildkite/scripts/prepare-build.sh index faaa046811..1c245d9618 100755 --- a/.buildkite/scripts/prepare-build.sh +++ b/.buildkite/scripts/prepare-build.sh @@ -48,6 +48,12 @@ function assert_command() { fi } +function assert_release() { + if [ "$RELEASE" == "1" ]; then + run_command buildkite-agent meta-data set canary "0" + fi +} + function assert_canary() { local canary="$(buildkite-agent meta-data get canary 2>/dev/null)" if [ -z "$canary" ]; then @@ -63,8 +69,8 @@ function assert_canary() { canary="$revision" fi fi + run_command buildkite-agent meta-data set canary "$canary" fi - run_command buildkite-agent meta-data set canary "$canary" } function upload_buildkite_pipeline() { @@ -86,5 +92,6 @@ assert_build assert_buildkite_agent assert_jq assert_curl +assert_release assert_canary upload_buildkite_pipeline ".buildkite/ci.yml" From 175746e5694b7322af7f5b29ec81a41c71aaaa6c Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Mon, 29 Jul 2024 14:54:29 -0700 Subject: [PATCH 117/123] Only upload canary artifacts when the build is canary --- .buildkite/scripts/upload-release.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.buildkite/scripts/upload-release.sh b/.buildkite/scripts/upload-release.sh index b3d4c0a415..def9394ee1 100755 --- a/.buildkite/scripts/upload-release.sh +++ b/.buildkite/scripts/upload-release.sh @@ -181,4 +181,13 @@ function create_release() { create_sentry_release "$tag" } +function assert_canary() { + local canary="$(buildkite-agent meta-data get canary 2>/dev/null)" + if [ -z "$canary" ] || [ "$canary" == "0" ]; then + echo "warn: Skipping release because this is not a canary build" + exit 0 + fi +} + +assert_canary create_release "canary" From 1f1ea7bf24d7067e262aa26f72b56ec7fff3187b Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Mon, 29 Jul 2024 15:29:46 -0700 Subject: [PATCH 118/123] Fix release script --- .buildkite/scripts/upload-release.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.buildkite/scripts/upload-release.sh b/.buildkite/scripts/upload-release.sh index def9394ee1..5627b53430 100755 --- a/.buildkite/scripts/upload-release.sh +++ b/.buildkite/scripts/upload-release.sh @@ -171,13 +171,10 @@ function create_release() { bun-windows-x64-baseline-profile.zip ) - for artifact in "${artifacts[@]}"; do - download_buildkite_artifact "$artifact" - done - - upload_github_assets "$tag" "${artifacts[@]}" + download_buildkite_artifacts "." "${artifacts[@]}" upload_s3_files "releases/$BUILDKITE_COMMIT" "${artifacts[@]}" upload_s3_files "releases/$tag" "${artifacts[@]}" + upload_github_assets "$tag" "${artifacts[@]}" create_sentry_release "$tag" } From e5662caa3368810c34fecc56ac8e389e44c9047f Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Mon, 29 Jul 2024 16:21:55 -0700 Subject: [PATCH 119/123] Fix release script, again --- .buildkite/scripts/upload-release.sh | 65 ++++++++++++++++------------ 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/.buildkite/scripts/upload-release.sh b/.buildkite/scripts/upload-release.sh index 5627b53430..edc03eb78d 100755 --- a/.buildkite/scripts/upload-release.sh +++ b/.buildkite/scripts/upload-release.sh @@ -114,44 +114,50 @@ function create_sentry_release() { fi } -function download_buildkite_artifacts() { - local dir="$1" - local names="${@:2}" - for name in "${names[@]}"; do - run_command buildkite-agent artifact download "$name" "$dir" - if [ ! -f "$dir/$name" ]; then - echo "error: Cannot find Buildkite artifact: $name" - exit 1 - fi - done +function download_buildkite_artifact() { + local name="$1" + local dir="$2" + if [ -z "$dir" ]; then + dir="." + fi + run_command buildkite-agent artifact download "$name" "$dir" + if [ ! -f "$dir/$name" ]; then + echo "error: Cannot find Buildkite artifact: $name" + exit 1 + fi } -function upload_github_assets() { +function upload_github_asset() { local version="$1" local tag="$(release_tag "$version")" - local files="${@:2}" - for file in "${files[@]}"; do - run_command gh release upload "$tag" "$file" --clobber --repo "$BUILDKITE_REPO" - done - if [ "$version" == "canary" ]; then + local file="$2" + run_command gh release upload "$tag" "$file" --clobber --repo "$BUILDKITE_REPO" + if [ "$tag" == "canary" ]; then run_command gh release edit "$tag" --repo "$BUILDKITE_REPO" \ --notes "This canary release of Bun corresponds to the commit: $BUILDKITE_COMMIT" fi } -function upload_s3_files() { +function update_github_release() { + local version="$1" + local tag="$(release_tag "$version")" + if [ "$tag" == "canary" ]; then + run_command gh release edit "$tag" --repo "$BUILDKITE_REPO" \ + --notes "This release of Bun corresponds to the commit: $BUILDKITE_COMMIT" + fi +} + +function upload_s3_file() { local folder="$1" - local files="${@:2}" - for file in "${files[@]}"; do - run_command aws --endpoint-url="$AWS_ENDPOINT" s3 cp "$file" "s3://$AWS_BUCKET/$folder/$file" - done + local file="$2" + run_command aws --endpoint-url="$AWS_ENDPOINT" s3 cp "$file" "s3://$AWS_BUCKET/$folder/$file" } function create_release() { assert_main - assert_buildkite_agent - assert_github - assert_sentry + #assert_buildkite_agent + #assert_github + #assert_sentry local tag="$1" # 'canary' or 'x.y.z' local artifacts=( @@ -171,10 +177,13 @@ function create_release() { bun-windows-x64-baseline-profile.zip ) - download_buildkite_artifacts "." "${artifacts[@]}" - upload_s3_files "releases/$BUILDKITE_COMMIT" "${artifacts[@]}" - upload_s3_files "releases/$tag" "${artifacts[@]}" - upload_github_assets "$tag" "${artifacts[@]}" + for name in "${artifacts[@]}"; do + download_buildkite_artifact "$name" + upload_s3_file "releases/$BUILDKITE_COMMIT" "$name" + upload_s3_file "releases/$tag" "$name" + upload_github_asset "$tag" "$name" + done + update_github_release "$tag" create_sentry_release "$tag" } From a2b4e3d4c2745ebfa22876bf5075d6c64fef5072 Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Mon, 29 Jul 2024 16:25:50 -0700 Subject: [PATCH 120/123] Fix syntax in env.ps1 --- scripts/env.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/env.ps1 b/scripts/env.ps1 index 02c63a68f3..406c168169 100755 --- a/scripts/env.ps1 +++ b/scripts/env.ps1 @@ -35,13 +35,13 @@ $BUN_DEPS_DIR = if ($env:BUN_DEPS_DIR) { $env:BUN_DEPS_DIR } else { Join-Path $B $BUN_DEPS_OUT_DIR = if ($env:BUN_DEPS_OUT_DIR) { $env:BUN_DEPS_OUT_DIR } else { Join-Path $BUN_BASE_DIR 'build\bun-deps' } $CPUS = if ($env:CPUS) { $env:CPUS } else { (Get-CimInstance -Class Win32_Processor).NumberOfCores } -$Lto = if ($env:USE_LTO) { $env:USE_LTO -eq "1" } else { True } +$Lto = if ($env:USE_LTO) { $env:USE_LTO -eq "1" } else { $True } $Baseline = if ($env:USE_BASELINE_BUILD) { $env:USE_BASELINE_BUILD -eq "1" } elseif ($env:BUILDKITE_STEP_KEY -match "baseline") { - True + $True } else { - False + $False } $CC = "clang-cl" From 30881444df533dee4c525e67347c706426dd952f Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Mon, 29 Jul 2024 16:34:01 -0700 Subject: [PATCH 121/123] Fix flaky C++ build with missing submodules --- .buildkite/scripts/env.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.buildkite/scripts/env.sh b/.buildkite/scripts/env.sh index 61b8382e35..b09f799bf6 100755 --- a/.buildkite/scripts/env.sh +++ b/.buildkite/scripts/env.sh @@ -72,6 +72,7 @@ function assert_buildkite_agent() { function export_environment() { source "$(realpath $(dirname "$0")/../../scripts/env.sh)" + source "$(realpath $(dirname "$0")/../../scripts/update-submodules.sh)" { set +x; } 2>/dev/null export GIT_SHA="$BUILDKITE_COMMIT" export CCACHE_DIR="$HOME/.cache/ccache/$BUILDKITE_STEP_KEY" From 1d9a8b41346bc556cfdb9bbc2a640faf47ded4c6 Mon Sep 17 00:00:00 2001 From: Dariush Alipour Date: Tue, 30 Jul 2024 01:36:46 +0200 Subject: [PATCH 122/123] fix: child_process test with specified shell for windows (#12926) --- test/js/node/child_process/child_process.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/js/node/child_process/child_process.test.ts b/test/js/node/child_process/child_process.test.ts index a124c6f64c..f2ecb17f7a 100644 --- a/test/js/node/child_process/child_process.test.ts +++ b/test/js/node/child_process/child_process.test.ts @@ -223,7 +223,7 @@ describe("spawn()", () => { it("should allow us to spawn in the default shell", async () => { const shellPath: string = await new Promise(resolve => { - const child = spawn("echo", [isWindows ? "$env:SHELL" : "$SHELL"], { shell: true }); + const child = spawn("echo", [isWindows ? "$PSHOME" : "$SHELL"], { shell: true }); child.stdout.on("data", data => { resolve(data.toString().trim()); }); @@ -240,7 +240,7 @@ describe("spawn()", () => { it("should allow us to spawn in a specified shell", async () => { const shell = shellExe(); const shellPath: string = await new Promise(resolve => { - const child = spawn("echo", [isWindows ? "$env:SHELL" : "$SHELL"], { shell }); + const child = spawn("echo", [isWindows ? "$PSHOME" : "$SHELL"], { shell }); child.stdout.on("data", data => { resolve(data.toString().trim()); }); From 5aeb4d9f79d9ce857d28f7a55adff54590038e80 Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Mon, 29 Jul 2024 16:57:12 -0700 Subject: [PATCH 123/123] Fix missing assert in release script --- .buildkite/scripts/upload-release.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.buildkite/scripts/upload-release.sh b/.buildkite/scripts/upload-release.sh index edc03eb78d..7ea85aec55 100755 --- a/.buildkite/scripts/upload-release.sh +++ b/.buildkite/scripts/upload-release.sh @@ -155,9 +155,10 @@ function upload_s3_file() { function create_release() { assert_main - #assert_buildkite_agent - #assert_github - #assert_sentry + assert_buildkite_agent + assert_github + assert_aws + assert_sentry local tag="$1" # 'canary' or 'x.y.z' local artifacts=(