diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/global.zig | 1 | ||||
-rw-r--r-- | src/javascript/jsc/api/html_rewriter.zig | 29 | ||||
-rw-r--r-- | src/javascript/jsc/base.zig | 50 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/ZigSourceProvider.cpp | 3 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/bindings.cpp | 27 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/bindings.zig | 27 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers.h | 3 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers.zig | 1 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 214 | ||||
-rw-r--r-- | src/javascript/jsc/webcore.zig | 118 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/response.zig | 515 | ||||
-rw-r--r-- | src/ref_count.zig | 82 | ||||
-rw-r--r-- | src/string_immutable.zig | 25 |
14 files changed, 742 insertions, 355 deletions
diff --git a/src/global.zig b/src/global.zig index e2e99bab8..1e16db216 100644 --- a/src/global.zig +++ b/src/global.zig @@ -83,6 +83,7 @@ pub const PathString = StringTypes.PathString; pub const HashedString = StringTypes.HashedString; pub const strings = @import("string_immutable.zig"); pub const MutableString = @import("string_mutable.zig").MutableString; +pub const RefCount = @import("./ref_count.zig").RefCount; pub inline fn constStrToU8(s: []const u8) []u8 { return @intToPtr([*]u8, @ptrToInt(s.ptr))[0..s.len]; diff --git a/src/javascript/jsc/api/html_rewriter.zig b/src/javascript/jsc/api/html_rewriter.zig index 255ad14ea..e8728a46d 100644 --- a/src/javascript/jsc/api/html_rewriter.zig +++ b/src/javascript/jsc/api/html_rewriter.zig @@ -232,7 +232,6 @@ pub const HTMLRewriter = struct { pub fn init(context: LOLHTMLContext, global: *JSGlobalObject, original: *Response, builder: *LOLHTML.HTMLRewriter.Builder) JSValue { var result = bun.default_allocator.create(Response) catch unreachable; - var sink = bun.default_allocator.create(BufferOutputSink) catch unreachable; sink.* = BufferOutputSink{ .global = global, @@ -282,16 +281,23 @@ pub const HTMLRewriter = struct { }, }, }; + { + var input = original.body.value.use(); - sink.rewriter.write(original.body.slice()) catch { - sink.deinit(); - bun.default_allocator.destroy(result); + defer input.detach(); + sink.rewriter.write(input.sharedView()) catch { + sink.deinit(); + bun.default_allocator.destroy(result); - return throwLOLHTMLError(global); - }; + return throwLOLHTMLError(global); + }; + } // Hold off on cloning until we're actually done. - result.body.init = original.body.init.clone(bun.default_allocator); + result.body.init.headers = original.body.init.headers; + result.body.init.method = original.body.init.method; + result.body.init.status_code = original.body.init.status_code; + result.url = bun.default_allocator.dupe(u8, original.url) catch unreachable; result.status_text = bun.default_allocator.dupe(u8, original.status_text) catch unreachable; @@ -474,7 +480,6 @@ fn HandlerCallback( var zig_element = bun.default_allocator.create(ZigType) catch unreachable; @field(zig_element, field_name) = value; // At the end of this scope, the value is no longer valid - var args = [1]JSC.C.JSObjectRef{ ZigType.Class.make(this.global.ref(), zig_element), }; @@ -497,14 +502,10 @@ fn HandlerCallback( var promise = promise_ orelse JSC.JSInternalPromise.resolvedPromise(this.global, result); promise_ = promise; + JavaScript.VirtualMachine.vm.event_loop.waitForPromise(promise); switch (promise.status(this.global.vm())) { - JSC.JSPromise.Status.Pending => { - while (promise.status(this.global.vm()) == .Pending) { - JavaScript.VirtualMachine.vm.tick(); - } - result = promise.result(this.global.vm()); - }, + JSC.JSPromise.Status.Pending => unreachable, JSC.JSPromise.Status.Rejected => { JavaScript.VirtualMachine.vm.defaultErrorHandler(promise.result(this.global.vm()), null); @field(zig_element, field_name) = null; diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig index 5d1dbcc31..6fa5bc511 100644 --- a/src/javascript/jsc/base.zig +++ b/src/javascript/jsc/base.zig @@ -21,7 +21,7 @@ const Response = WebCore.Response; const Request = WebCore.Request; const Router = @import("./api/router.zig"); const FetchEvent = WebCore.FetchEvent; -const Headers = WebCore.Headers; +const Headers = WebCore.Headers.RefCountedHeaders; const Body = WebCore.Body; const TaggedPointerTypes = @import("../../tagged_pointer.zig"); @@ -1742,6 +1742,35 @@ pub const ArrayBuffer = extern struct { )); } + pub fn toJSWithContext( + this: ArrayBuffer, + ctx: JSC.C.JSContextRef, + deallocator: *anyopaque, + callback: JSC.C.JSTypedArrayBytesDeallocator, + exception: JSC.C.ExceptionRef, + ) JSC.JSValue { + if (this.typed_array_type == .ArrayBuffer) { + return JSC.JSValue.fromRef(JSC.C.JSObjectMakeArrayBufferWithBytesNoCopy( + ctx, + this.ptr, + this.byte_len, + callback, + deallocator, + exception, + )); + } + + return JSC.JSValue.fromRef(JSC.C.JSObjectMakeTypedArrayWithBytesNoCopy( + ctx, + this.typed_array_type.toC(), + this.ptr, + this.byte_len, + callback, + deallocator, + exception, + )); + } + pub const fromArrayBuffer = fromTypedArray; pub inline fn slice(this: *const @This()) []u8 { @@ -1749,15 +1778,15 @@ pub const ArrayBuffer = extern struct { } pub inline fn asU16(this: *const @This()) []u16 { - const sliced = this.slice(); - const len = if (sliced.len > 0) sliced.len / 2 else 0; - return @ptrCast([*]u16, @alignCast(@alignOf(@TypeOf(this.ptr)), this.ptr))[0..len]; + return std.mem.bytesAsSlice(u16, @alignCast(@alignOf([*]u16), this.ptr[this.offset..this.byte_len])); + } + + pub inline fn asU16Unaligned(this: *const @This()) []align(1) u16 { + return std.mem.bytesAsSlice(u16, @alignCast(@alignOf([*]align(1) u16), this.ptr[this.offset..this.byte_len])); } pub inline fn asU32(this: *const @This()) []u32 { - const sliced = this.slice(); - const len = if (sliced.len > 0) sliced.len / 4 else 0; - return @ptrCast([*]u32, @alignCast(@alignOf(@TypeOf(this.ptr)), this.ptr))[0..len]; + return std.mem.bytesAsSlice(u32, @alignCast(@alignOf([*]u32), this.ptr)[this.offset..this.byte_len]); } }; @@ -1846,6 +1875,13 @@ export fn MarkedArrayBuffer_deallocator(bytes_: *anyopaque, _: *anyopaque) void // mimalloc knows the size of things // but we don't mimalloc.mi_free(bytes_); + +pub export fn BlobArrayBuffer_deallocator(_: *anyopaque, blob: *anyopaque) void { + // zig's memory allocator interface won't work here + // mimalloc knows the size of things + // but we don't + var store = bun.cast(*JSC.WebCore.Blob.Store, blob); + store.deref(); } pub fn castObj(obj: js.JSObjectRef, comptime Type: type) *Type { diff --git a/src/javascript/jsc/bindings/ZigSourceProvider.cpp b/src/javascript/jsc/bindings/ZigSourceProvider.cpp index c7394f1cb..de46bde77 100644 --- a/src/javascript/jsc/bindings/ZigSourceProvider.cpp +++ b/src/javascript/jsc/bindings/ZigSourceProvider.cpp @@ -35,7 +35,8 @@ Ref<SourceProvider> SourceProvider::create(ResolvedSource resolvedSource) if (allocator) { Ref<WTF::ExternalStringImpl> stringImpl_ = WTF::ExternalStringImpl::create( resolvedSource.source_code.ptr, resolvedSource.source_code.len, - [allocator](WTF::ExternalStringImpl* str, void* ptr, unsigned int len) { + nullptr, + [allocator](void* str, void* ptr, unsigned int len) { ZigString__free((const unsigned char*)ptr, len, allocator); }); return adoptRef(*new SourceProvider( diff --git a/src/javascript/jsc/bindings/bindings.cpp b/src/javascript/jsc/bindings/bindings.cpp index bfdb24600..e9ee4b380 100644 --- a/src/javascript/jsc/bindings/bindings.cpp +++ b/src/javascript/jsc/bindings/bindings.cpp @@ -740,7 +740,7 @@ JSC__JSValue ZigString__to16BitValue(const ZigString* arg0, JSC__JSGlobalObject* return JSC::JSValue::encode(JSC::JSValue(JSC::jsString(arg1->vm(), str))); } -void free_global_string(WTF::ExternalStringImpl* str, void* ptr, unsigned len) +void free_global_string(void* str, void* ptr, unsigned len) { ZigString__free_global(reinterpret_cast<const unsigned char*>(ptr), len); } @@ -749,7 +749,7 @@ JSC__JSValue ZigString__toExternalU16(const uint16_t* arg0, size_t len, JSC__JSG { return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString( global->vm(), - ExternalStringImpl::create(reinterpret_cast<const UChar*>(arg0), len, free_global_string)))); + ExternalStringImpl::create(reinterpret_cast<const UChar*>(arg0), len, nullptr, free_global_string)))); } // This must be a globally allocated string JSC__JSValue ZigString__toExternalValue(const ZigString* arg0, JSC__JSGlobalObject* arg1) @@ -758,12 +758,12 @@ JSC__JSValue ZigString__toExternalValue(const ZigString* arg0, JSC__JSGlobalObje if (Zig::isTaggedUTF16Ptr(str.ptr)) { return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString( arg1->vm(), - ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, free_global_string)))); + ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, nullptr, free_global_string)))); } return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString( arg1->vm(), - ExternalStringImpl::create(Zig::untag(str.ptr), str.len, free_global_string)))); + ExternalStringImpl::create(Zig::untag(str.ptr), str.len, nullptr, free_global_string)))); } JSC__JSValue ZigString__toValueGC(const ZigString* arg0, JSC__JSGlobalObject* arg1) @@ -792,6 +792,21 @@ void JSC__JSValue__toZigString(JSC__JSValue JSValue0, ZigString* arg1, JSC__JSGl arg1->len = str.length(); } +JSC__JSValue ZigString__external(const ZigString* arg0, JSC__JSGlobalObject* arg1, void* arg2, void (*ArgFn3)(void* arg0, void* arg1, size_t arg2)) +{ + ZigString str + = *arg0; + if (Zig::isTaggedUTF16Ptr(str.ptr)) { + return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString( + arg1->vm(), + WTF::String(ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, arg2, ArgFn3))))); + } else { + return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString( + arg1->vm(), + WTF::String(ExternalStringImpl::create(reinterpret_cast<const LChar*>(Zig::untag(str.ptr)), str.len, arg2, ArgFn3))))); + } +} + JSC__JSValue ZigString__toExternalValueWithCallback(const ZigString* arg0, JSC__JSGlobalObject* arg1, void (*ArgFn2)(void* arg2, void* arg0, size_t arg1)) { @@ -800,11 +815,11 @@ JSC__JSValue ZigString__toExternalValueWithCallback(const ZigString* arg0, JSC__ if (Zig::isTaggedUTF16Ptr(str.ptr)) { return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString( arg1->vm(), - WTF::String(ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, ArgFn2))))); + WTF::String(ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, nullptr, ArgFn2))))); } else { return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString( arg1->vm(), - WTF::String(ExternalStringImpl::create(reinterpret_cast<const LChar*>(Zig::untag(str.ptr)), str.len, ArgFn2))))); + WTF::String(ExternalStringImpl::create(reinterpret_cast<const LChar*>(Zig::untag(str.ptr)), str.len, nullptr, ArgFn2))))); } } diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig index f33d82ad6..25adec479 100644 --- a/src/javascript/jsc/bindings/bindings.zig +++ b/src/javascript/jsc/bindings/bindings.zig @@ -169,6 +169,10 @@ pub const ZigString = extern struct { return ZigString{ .ptr = slice_.ptr, .len = slice_.len }; } + pub fn from(slice_: JSC.C.JSValueRef, ctx: JSC.C.JSContextRef) ZigString { + return JSC.JSValue.fromRef(slice_).getZigString(ctx.ptr()); + } + pub fn toBase64DataURL(this: ZigString, allocator: std.mem.Allocator) ![]const u8 { const slice_ = this.slice(); const size = std.base64.standard.Encoder.calcSize(slice_.len); @@ -242,6 +246,10 @@ pub const ZigString = extern struct { return untagged(this.ptr)[0..@minimum(this.len, std.math.maxInt(u32))]; } + pub fn dupe(this: ZigString, allocator: std.mem.Allocator) ![]const u8 { + return try allocator.dupe(u8, this.slice()); + } + pub fn toSlice(this: ZigString, allocator: std.mem.Allocator) Slice { if (this.len == 0) return Slice{ .ptr = "", .len = 0, .allocator = allocator, .allocated = false }; @@ -300,6 +308,15 @@ pub const ZigString = extern struct { return shim.cppFn("toExternalValueWithCallback", .{ this, global, callback }); } + pub fn external( + this: *const ZigString, + global: *JSGlobalObject, + ctx: ?*anyopaque, + callback: fn (ctx: ?*anyopaque, ptr: ?*anyopaque, len: usize) callconv(.C) void, + ) JSValue { + return shim.cppFn("external", .{ this, global, ctx, callback }); + } + pub fn to16BitValue(this: *const ZigString, global: *JSGlobalObject) JSValue { return shim.cppFn("to16BitValue", .{ this, global }); } @@ -329,15 +346,7 @@ pub const ZigString = extern struct { return shim.cppFn("toErrorInstance", .{ this, global }); } - pub const Extern = [_][]const u8{ - "toValue", - "toExternalValue", - "to16BitValue", - "toValueGC", - "toErrorInstance", - "toExternalU16", - "toExternalValueWithCallback", - }; + pub const Extern = [_][]const u8{ "toValue", "toExternalValue", "to16BitValue", "toValueGC", "toErrorInstance", "toExternalU16", "toExternalValueWithCallback", "external" }; }; pub const SystemError = extern struct { diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h index c620b6e98..dccb07f35 100644 --- a/src/javascript/jsc/bindings/headers-cpp.h +++ b/src/javascript/jsc/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1647165586 +//-- AUTOGENERATED FILE -- 1647230769 // clang-format off #pragma once diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h index f63a87319..afe6eb575 100644 --- a/src/javascript/jsc/bindings/headers.h +++ b/src/javascript/jsc/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format: off -//-- AUTOGENERATED FILE -- 1647165586 +//-- AUTOGENERATED FILE -- 1647230769 #pragma once #include <stddef.h> @@ -246,6 +246,7 @@ CPP_DECL size_t JSC__JSObject__getArrayLength(JSC__JSObject* arg0); CPP_DECL JSC__JSValue JSC__JSObject__getDirect(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, const ZigString* arg2); CPP_DECL JSC__JSValue JSC__JSObject__getIndex(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, uint32_t arg2); CPP_DECL void JSC__JSObject__putRecord(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, ZigString* arg2, ZigString* arg3, size_t arg4); +CPP_DECL JSC__JSValue ZigString__external(const ZigString* arg0, JSC__JSGlobalObject* arg1, void* arg2, void (* ArgFn3)(void* arg0, void* arg1, size_t arg2)); CPP_DECL JSC__JSValue ZigString__to16BitValue(const ZigString* arg0, JSC__JSGlobalObject* arg1); CPP_DECL JSC__JSValue ZigString__toErrorInstance(const ZigString* arg0, JSC__JSGlobalObject* arg1); CPP_DECL JSC__JSValue ZigString__toExternalU16(const uint16_t* arg0, size_t arg1, JSC__JSGlobalObject* arg2); diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig index 0932fb5f5..3729be6b9 100644 --- a/src/javascript/jsc/bindings/headers.zig +++ b/src/javascript/jsc/bindings/headers.zig @@ -136,6 +136,7 @@ pub extern fn JSC__JSObject__getArrayLength(arg0: [*c]JSC__JSObject) usize; pub extern fn JSC__JSObject__getDirect(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]const ZigString) JSC__JSValue; pub extern fn JSC__JSObject__getIndex(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: u32) JSC__JSValue; pub extern fn JSC__JSObject__putRecord(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString, arg3: [*c]ZigString, arg4: usize) void; +pub extern fn ZigString__external(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject, arg2: ?*anyopaque, ArgFn3: ?fn (?*anyopaque, ?*anyopaque, usize) callconv(.C) void) JSC__JSValue; pub extern fn ZigString__to16BitValue(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; pub extern fn ZigString__toErrorInstance(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; pub extern fn ZigString__toExternalU16(arg0: [*c]const u16, arg1: usize, arg2: [*c]JSC__JSGlobalObject) JSC__JSValue; diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index 60ba111de..0eedd8266 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -93,10 +93,10 @@ pub const GlobalClasses = [_]type{ Bun.Class, Fetch.Class, js_ast.Macro.JSNode.BunJSXCallbackFunction, - Performance.Class, + WebCore.Performance.Class, - Crypto.Class, - Crypto.Prototype, + WebCore.Crypto.Class, + WebCore.Crypto.Prototype, WebCore.TextEncoder.Constructor.Class, WebCore.TextDecoder.Constructor.Class, @@ -107,89 +107,10 @@ pub const GlobalClasses = [_]type{ // The last item in this array becomes "process.env" Bun.EnvironmentVariables.Class, }; -const UUID = @import("./uuid.zig"); const Blob = @import("../../blob.zig"); pub const Buffer = MarkedArrayBuffer; const Lock = @import("../../lock.zig").Lock; -pub const Crypto = struct { - pub const Class = NewClass(void, .{ .name = "crypto" }, .{ - .getRandomValues = .{ - .rfn = getRandomValues, - }, - .randomUUID = .{ - .rfn = randomUUID, - }, - }, .{}); - pub const Prototype = NewClass( - void, - .{ .name = "Crypto" }, - .{ - .call = .{ - .rfn = call, - }, - }, - .{}, - ); - - pub fn getRandomValues( - // this - _: void, - ctx: js.JSContextRef, - // function - _: js.JSObjectRef, - // thisObject - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSValueRef { - if (arguments.len == 0) { - JSError(getAllocator(ctx), "Expected typed array but received nothing", .{}, ctx, exception); - return JSValue.jsUndefined().asObjectRef(); - } - var array_buffer = MarkedArrayBuffer.fromJS(ctx.ptr(), JSValue.fromRef(arguments[0]), exception) orelse { - JSError(getAllocator(ctx), "Expected typed array", .{}, ctx, exception); - return JSValue.jsUndefined().asObjectRef(); - }; - var slice = array_buffer.slice(); - if (slice.len > 0) - std.crypto.random.bytes(slice); - - return arguments[0]; - } - - pub fn call( - // this - _: void, - _: js.JSContextRef, - // function - _: js.JSObjectRef, - // thisObject - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return JSValue.jsUndefined().asObjectRef(); - } - - pub fn randomUUID( - // this - _: void, - ctx: js.JSContextRef, - // function - _: js.JSObjectRef, - // thisObject - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var uuid = UUID.init(); - var out: [128]u8 = undefined; - var str = std.fmt.bufPrint(&out, "{s}", .{uuid}) catch unreachable; - return ZigString.init(str).toValueGC(ctx.ptr()).asObjectRef(); - } -}; - pub const Bun = struct { threadlocal var css_imports_list_strings: [512]ZigString = undefined; threadlocal var css_imports_list: [512]Api.StringPointer = undefined; @@ -865,9 +786,7 @@ pub const Bun = struct { .rfn = Router.match, .ts = Router.match_type_definition, }, - .__debug__doSegfault = .{ - .rfn = Bun.__debug__doSegfault, - }, + .sleepSync = .{ .rfn = sleepSync, }, @@ -999,6 +918,9 @@ pub const Bun = struct { .get = getTOMLObject, .ts = d.ts{ .name = "TOML", .@"return" = "TOML.prototype" }, }, + .unsafe = .{ + .get = getUnsafe, + }, }, ); @@ -1022,20 +944,69 @@ pub const Bun = struct { return js.JSObjectMake(ctx, TOML.Class.get().?[0], null); } - // For testing the segfault handler - pub fn __debug__doSegfault( + pub fn getUnsafe( _: void, ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, + _: js.JSValueRef, + _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - _ = ctx; - const Reporter = @import("../../report.zig"); - Reporter.globalError(error.SegfaultTest); + return js.JSObjectMake(ctx, Unsafe.Class.get().?[0], null); } + pub const Unsafe = struct { + pub const Class = NewClass( + void, + .{ .name = "Unsafe", .read_only = true }, + .{ + .segfault = .{ + .rfn = __debug__doSegfault, + }, + .arrayBufferToString = .{ + .rfn = arrayBufferToString, + }, + }, + .{}, + ); + + // For testing the segfault handler + pub fn __debug__doSegfault( + _: void, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + _: []const js.JSValueRef, + _: js.ExceptionRef, + ) js.JSValueRef { + _ = ctx; + const Reporter = @import("../../report.zig"); + Reporter.globalError(error.SegfaultTest); + } + + pub fn arrayBufferToString( + _: void, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + args: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + const array_buffer = JSC.ArrayBuffer.fromTypedArray(ctx, JSC.JSValue.fromRef(args[0]), exception); + switch (array_buffer.typed_array_type) { + .Uint16Array, .Int16Array => { + var zig_str = ZigString.init(""); + zig_str.ptr = @ptrCast([*]const u8, @alignCast(@alignOf([*]align(1) const u16), array_buffer.ptr)); + zig_str.len = array_buffer.len; + zig_str.markUTF16(); + return ZigString.toValue(&zig_str, ctx.ptr()).asObjectRef(); + }, + else => { + return ZigString.init(array_buffer.slice()).toValue(ctx.ptr()).asObjectRef(); + }, + } + } + }; + // pub const Lockfile = struct { // const BunLockfile = @import("../../install/install.zig").Lockfile; // pub const Class = NewClass( @@ -1551,42 +1522,6 @@ pub fn OpaqueWrap(comptime Context: type, comptime Function: fn (this: *Context) }.callback; } -pub const Performance = struct { - pub const Class = NewClass( - void, - .{ - .name = "performance", - .read_only = true, - }, - .{ - .now = .{ - .rfn = Performance.now, - }, - }, - .{}, - ); - - pub fn now( - _: void, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return js.JSValueMakeNumber( - ctx, - @floatCast( - f64, - @intToFloat( - f128, - VirtualMachine.vm.origin_timer.read(), - ) / std.time.ns_per_ms, - ), - ); - } -}; - const bun_file_import_path = "/node_modules.server.bun"; const FetchTasklet = Fetch.FetchTasklet; @@ -1961,6 +1896,18 @@ pub const VirtualMachine = struct { } } + pub fn waitForPromise(this: *EventLoop, promise: *JSC.JSInternalPromise) void { + var _vm = this.global.vm(); + switch (promise.status(_vm)) { + JSC.JSPromise.Status.Pending => { + while (promise.status(_vm) == .Pending) { + this.tick(); + } + }, + else => {}, + } + } + pub fn waitForTasks(this: *EventLoop) void { this.tick(); while (this.pending_tasks_count.load(.Monotonic) > 0) { @@ -3098,7 +3045,7 @@ pub const VirtualMachine = struct { } } - fn remapZigException( + pub fn remapZigException( this: *VirtualMachine, exception: *ZigException, error_instance: JSValue, @@ -3442,9 +3389,12 @@ pub const EventListenerMixin = struct { for (listeners.items) |listener_ref| { fetch_args[0] = FetchEvent.Class.make(vm.global.ref(), fetch_event); + vm.tick(); var result = js.JSObjectCallAsFunctionReturnValue(vm.global.ref(), listener_ref, null, 1, &fetch_args); - var promise = JSPromise.resolvedPromise(vm.global, result); - vm.waitForTasks(); + vm.tick(); + var promise = JSInternalPromise.resolvedPromise(vm.global, result); + + vm.event_loop.waitForPromise(promise); if (fetch_event.rejected) return; diff --git a/src/javascript/jsc/webcore.zig b/src/javascript/jsc/webcore.zig index 488bcae0e..1a25c658d 100644 --- a/src/javascript/jsc/webcore.zig +++ b/src/javascript/jsc/webcore.zig @@ -1,2 +1,120 @@ pub usingnamespace @import("./webcore/response.zig"); pub usingnamespace @import("./webcore/encoding.zig"); +const JSC = @import("../../jsc.zig"); +const std = @import("std"); + +pub const Crypto = struct { + const UUID = @import("./uuid.zig"); + + pub const Class = JSC.NewClass(void, .{ .name = "crypto" }, .{ + .getRandomValues = .{ + .rfn = getRandomValues, + }, + .randomUUID = .{ + .rfn = randomUUID, + }, + }, .{}); + pub const Prototype = JSC.NewClass( + void, + .{ .name = "Crypto" }, + .{ + .call = .{ + .rfn = call, + }, + }, + .{}, + ); + + pub fn getRandomValues( + // this + _: void, + ctx: JSC.C.JSContextRef, + // function + _: JSC.C.JSObjectRef, + // thisObject + _: JSC.C.JSObjectRef, + arguments: []const JSC.C.JSValueRef, + exception: JSC.C.ExceptionRef, + ) JSC.C.JSValueRef { + if (arguments.len == 0) { + JSC.JSError(JSC.getAllocator(ctx), "Expected typed array but received nothing", .{}, ctx, exception); + return JSC.JSValue.jsUndefined().asObjectRef(); + } + var array_buffer = JSC.MarkedArrayBuffer.fromJS(ctx.ptr(), JSC.JSValue.fromRef(arguments[0]), exception) orelse { + JSC.JSError(JSC.getAllocator(ctx), "Expected typed array", .{}, ctx, exception); + return JSC.JSValue.jsUndefined().asObjectRef(); + }; + var slice = array_buffer.slice(); + if (slice.len > 0) + std.crypto.random.bytes(slice); + + return arguments[0]; + } + + pub fn call( + // this + _: void, + _: JSC.C.JSContextRef, + // function + _: JSC.C.JSObjectRef, + // thisObject + _: JSC.C.JSObjectRef, + _: []const JSC.C.JSValueRef, + _: JSC.C.ExceptionRef, + ) JSC.C.JSValueRef { + return JSC.JSValue.jsUndefined().asObjectRef(); + } + + pub fn randomUUID( + // this + _: void, + ctx: JSC.C.JSContextRef, + // function + _: JSC.C.JSObjectRef, + // thisObject + _: JSC.C.JSObjectRef, + _: []const JSC.C.JSValueRef, + _: JSC.C.ExceptionRef, + ) JSC.C.JSValueRef { + var uuid = UUID.init(); + var out: [128]u8 = undefined; + var str = std.fmt.bufPrint(&out, "{s}", .{uuid}) catch unreachable; + return JSC.ZigString.init(str).toValueGC(ctx.ptr()).asObjectRef(); + } +}; + +pub const Performance = struct { + pub const Class = JSC.NewClass( + void, + .{ + .name = "performance", + .read_only = true, + }, + .{ + .now = .{ + .rfn = Performance.now, + }, + }, + .{}, + ); + + pub fn now( + _: void, + ctx: JSC.C.JSContextRef, + _: JSC.C.JSObjectRef, + _: JSC.C.JSObjectRef, + _: []const JSC.C.JSValueRef, + _: JSC.C.ExceptionRef, + ) JSC.C.JSValueRef { + return JSC.C.JSValueMakeNumber( + ctx, + @floatCast( + f64, + @intToFloat( + f128, + JSC.VirtualMachine.vm.origin_timer.read(), + ) / std.time.ns_per_ms, + ), + ); + } +}; diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig index 6449cb173..0880c78e8 100644 --- a/src/javascript/jsc/webcore/response.zig +++ b/src/javascript/jsc/webcore/response.zig @@ -47,6 +47,7 @@ pub const Response = struct { .{ .name = "Response" }, .{ .@"constructor" = constructor, + .@"finalize" = finalize, .@"text" = .{ .rfn = Response.getText, .ts = d.ts{}, @@ -202,12 +203,10 @@ pub const Response = struct { _: js.ExceptionRef, ) js.JSValueRef { if (this.body.init.headers == null) { - var headers = getAllocator(ctx).create(Headers) catch unreachable; - headers.* = Headers.empty(getAllocator(ctx)); - this.body.init.headers = headers; + this.body.init.headers = Headers.RefCountedHeaders.init(Headers.empty(getAllocator(ctx)), getAllocator(ctx)) catch unreachable; } - return Headers.Class.make(ctx, this.body.init.headers.?); + return Headers.Class.make(ctx, this.body.init.headers.?.getRef()); } pub fn doClone( @@ -270,7 +269,9 @@ pub const Response = struct { } pub fn mimeType(response: *const Response, request_ctx_: ?*const RequestContext) string { - if (response.body.init.headers) |headers| { + if (response.body.init.headers) |headers_ref| { + var headers = headers_ref.get(); + defer headers_ref.deref(); // Remember, we always lowercase it // hopefully doesn't matter here tho if (headers.getHeaderIndex("content-type")) |content_type| { @@ -429,6 +430,8 @@ pub const Fetch = struct { reject: js.JSObjectRef = null, context: FetchTaskletContext = undefined, + blob_store: ?*Blob.Store = null, + const Pool = ObjectPool(FetchTasklet, init, true, 32); const BodyPool = ObjectPool(MutableString, MutableString.init2048, true, 8); pub const FetchTaskletContext = struct { @@ -516,6 +519,9 @@ pub const Fetch = struct { }; pub fn onReject(this: *FetchTasklet) JSValue { + if (this.blob_store) |store| { + store.deref(); + } const fetch_error = std.fmt.allocPrint( default_allocator, "fetch() failed – {s}\nurl: \"{s}\"", @@ -530,12 +536,11 @@ pub const Fetch = struct { pub fn onResolve(this: *FetchTasklet) JSValue { var allocator = default_allocator; var http_response = this.http.response.?; - var response_headers = Headers.fromPicoHeaders(allocator, http_response.headers) catch unreachable; - response_headers.guard = .immutable; var response = allocator.create(Response) catch unreachable; var duped = allocator.dupe(u8, this.http.response_buffer.toOwnedSlice()) catch unreachable; - var headers = allocator.create(Headers) catch unreachable; - headers.* = response_headers; + if (this.blob_store) |store| { + store.deref(); + } response.* = Response{ .allocator = allocator, .url = allocator.dupe(u8, this.http.url.href) catch unreachable, @@ -543,7 +548,10 @@ pub const Fetch = struct { .redirected = this.http.redirect_count > 0, .body = .{ .init = .{ - .headers = headers, + .headers = Headers.RefCountedHeaders.init( + Headers.fromPicoHeaders(allocator, http_response.headers) catch unreachable, + allocator, + ) catch unreachable, .status_code = @truncate(u16, http_response.status_code), }, .value = .{ @@ -562,11 +570,13 @@ pub const Fetch = struct { headers_buf: string, request_body: ?*MutableString, timeout: usize, + request_body_store: ?*Blob.Store, ) !*FetchTasklet.Pool.Node { var linked_list = FetchTasklet.Pool.get(allocator); linked_list.data.javascript_vm = VirtualMachine.vm; linked_list.data.empty_request_body = MutableString.init(allocator, 0) catch unreachable; linked_list.data.pooled_body = BodyPool.get(allocator); + linked_list.data.blob_store = request_body_store; linked_list.data.http = try HTTPClient.AsyncHTTP.init( allocator, method, @@ -592,8 +602,9 @@ pub const Fetch = struct { headers_buf: string, request_body: ?*MutableString, timeout: usize, + request_body_store: ?*Blob.Store, ) !*FetchTasklet.Pool.Node { - var node = try get(allocator, method, url, headers, headers_buf, request_body, timeout); + var node = try get(allocator, method, url, headers, headers_buf, request_body, timeout, request_body_store); node.data.promise = JSInternalPromise.create(global); node.data.global_this = global; @@ -633,6 +644,7 @@ pub const Fetch = struct { var args = JSC.Node.ArgumentsSlice.from(arguments); var url: ZigURL = undefined; var first_arg = args.nextEat().?; + var blob_store: ?*Blob.Store = null; if (first_arg.isString()) { var url_zig_str = ZigString.init(""); JSValue.fromRef(arguments[0]).toZigString(&url_zig_str, globalThis); @@ -662,8 +674,8 @@ pub const Fetch = struct { if (options.get(ctx.ptr(), "headers")) |headers_| { var headers2: Headers = undefined; - if (headers_.as(Headers)) |headers__| { - headers__.clone(&headers2) catch unreachable; + if (headers_.as(Headers.RefCountedHeaders)) |headers__| { + headers__.leak().clone(&headers2) catch unreachable; headers = headers2; } else if (Headers.JS.headersInit(ctx, headers_.asObjectRef()) catch null) |headers__| { headers__.clone(&headers2) catch unreachable; @@ -672,23 +684,39 @@ pub const Fetch = struct { } if (options.get(ctx.ptr(), "body")) |body__| { - if (Blob.fromJS(ctx.ptr(), body__)) |new_blob| { - body = MutableString{ .list = new_blob.asArrayList(), .allocator = bun.default_allocator }; + if (Blob.fromJS(ctx.ptr(), body__, true)) |new_blob| { + body = MutableString{ + .list = std.ArrayListUnmanaged(u8){ + .items = bun.constStrToU8(new_blob.sharedView()), + .capacity = new_blob.size, + }, + .allocator = bun.default_allocator, + }; + blob_store = new_blob.store; + // transfer is unnecessary here because this is a new slice + //new_blob.transfer(); } else |_| {} } } } else if (Request.Class.loaded and first_arg.as(Request) != null) { var request = first_arg.as(Request).?; - url = ZigURL.parse(request.url.slice()); + url = ZigURL.parse(request.url.dupe(getAllocator(ctx)) catch unreachable); method = request.method; if (request.headers) |head| { - headers = head.*; + var for_clone: Headers = undefined; + head.leak().clone(&for_clone) catch unreachable; + headers = for_clone; } var blob = request.body.use(); + // TODO: make RequestBody _NOT_ a MutableString body = MutableString{ - .list = blob.asArrayList(), + .list = std.ArrayListUnmanaged(u8){ + .items = bun.constStrToU8(blob.sharedView()), + .capacity = bun.constStrToU8(blob.sharedView()).len, + }, .allocator = blob.allocator orelse bun.default_allocator, }; + blob_store = blob.store; } else { const fetch_error = fetch_type_error_strings.get(js.JSValueGetType(ctx, arguments[0])); return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef(); @@ -729,6 +757,7 @@ pub const Fetch = struct { header_buf, request_body, std.time.ns_per_hour, + blob_store, ) catch unreachable; queued.data.this_object = js.JSObjectMake(ctx, null, JSPrivateDataPtr.from(&queued.data.context).ptr()); js.JSValueProtect(ctx, queued.data.this_object); @@ -751,6 +780,8 @@ pub const Headers = struct { used: u32 = 0, guard: Guard = Guard.none, + pub const RefCountedHeaders = bun.RefCount(Headers, true); + pub fn deinit( headers: *Headers, ) void { @@ -774,24 +805,25 @@ pub const Headers = struct { // https://developer.mozilla.org/en-US/docs/Web/API/Headers/get pub fn get( - this: *Headers, + ref: *RefCountedHeaders, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, arguments: []const js.JSValueRef, _: js.ExceptionRef, ) js.JSValueRef { + var this = ref.leak(); if (arguments.len == 0 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string)) { return js.JSValueMakeNull(ctx); } - const key_len = js.JSStringGetUTF8CString(arguments[0], &header_kv_buf, header_kv_buf.len); - const key = header_kv_buf[0 .. key_len - 1]; - if (this.getHeaderIndex(key)) |index| { - var str = this.asStr(this.entries.items(.value)[index]); - var ref = js.JSStringCreateWithUTF8CString(str.ptr); - defer js.JSStringRelease(ref); - return js.JSValueMakeString(ctx, ref); + const key = ZigString.from(arguments[0], ctx); + if (key.is16Bit()) + return js.JSValueMakeNull(ctx); + + if (this.getHeaderIndex(key.slice())) |index| { + return ZigString.init(this.asStr(this.entries.items(.value)[index])) + .toValue(ctx.ptr()).asObjectRef(); } else { return js.JSValueMakeNull(ctx); } @@ -801,13 +833,14 @@ pub const Headers = struct { // > The difference between set() and Headers.append is that if the specified header already exists and accepts multiple values // > set() overwrites the existing value with the new one, whereas Headers.append appends the new value to the end of the set of values. pub fn set( - this: *Headers, + ref: *RefCountedHeaders, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, arguments: []const js.JSValueRef, _: js.ExceptionRef, ) js.JSValueRef { + var this = ref.leak(); if (this.guard == .request or arguments.len < 2 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string) or !js.JSValueIsString(ctx, arguments[1])) { return js.JSValueMakeUndefined(ctx); } @@ -818,13 +851,14 @@ pub const Headers = struct { // https://developer.mozilla.org/en-US/docs/Web/API/Headers/append pub fn append( - this: *Headers, + ref: *RefCountedHeaders, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, arguments: []const js.JSValueRef, _: js.ExceptionRef, ) js.JSValueRef { + var this = ref.leak(); if (this.guard == .request or arguments.len < 2 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string) or !js.JSValueIsString(ctx, arguments[1])) { return js.JSValueMakeUndefined(ctx); } @@ -833,13 +867,14 @@ pub const Headers = struct { return js.JSValueMakeUndefined(ctx); } pub fn delete( - this: *Headers, + ref: *RefCountedHeaders, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, arguments: []const js.JSValueRef, _: js.ExceptionRef, ) js.JSValueRef { + var this = ref.leak(); if (this.guard == .request or arguments.len < 1 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string)) { return js.JSValueMakeUndefined(ctx); } @@ -854,7 +889,7 @@ pub const Headers = struct { return js.JSValueMakeUndefined(ctx); } pub fn entries( - _: *Headers, + _: *RefCountedHeaders, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, @@ -865,7 +900,7 @@ pub const Headers = struct { return js.JSValueMakeNull(ctx); } pub fn keys( - _: *Headers, + _: *RefCountedHeaders, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, @@ -876,7 +911,7 @@ pub const Headers = struct { return js.JSValueMakeNull(ctx); } pub fn values( - _: *Headers, + _: *RefCountedHeaders, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, @@ -965,33 +1000,43 @@ pub const Headers = struct { arguments: []const js.JSValueRef, _: js.ExceptionRef, ) js.JSObjectRef { - var headers = getAllocator(ctx).create(Headers) catch unreachable; + var headers = getAllocator(ctx).create(RefCountedHeaders) catch unreachable; if (arguments.len > 0 and js.JSValueIsObjectOfClass(ctx, arguments[0], Headers.Class.get().*)) { - var other = castObj(arguments[0], Headers); - other.clone(headers) catch unreachable; + var other = castObj(arguments[0], RefCountedHeaders).leak(); + other.clone(&headers.value) catch unreachable; + headers.count = 1; + headers.allocator = getAllocator(ctx); } else if (arguments.len == 1 and js.JSValueIsObject(ctx, arguments[0])) { - headers.* = (JS.headersInit(ctx, arguments[0]) catch unreachable) orelse Headers{ - .entries = @TypeOf(headers.entries){}, - .buf = @TypeOf(headers.buf){}, - .used = 0, + headers.* = .{ + .value = (JS.headersInit(ctx, arguments[0]) catch unreachable) orelse Headers{ + .entries = .{}, + .buf = .{}, + .used = 0, + .allocator = getAllocator(ctx), + .guard = Guard.none, + }, .allocator = getAllocator(ctx), - .guard = Guard.none, + .count = 1, }; } else { - headers.* = Headers.empty(getAllocator(ctx)); + headers.* = .{ + .value = Headers.empty(getAllocator(ctx)), + .allocator = getAllocator(ctx), + .count = 1, + }; } return Headers.Class.make(ctx, headers); } pub fn finalize( - this: *Headers, + this: *RefCountedHeaders, ) void { - this.deinit(); + this.deref(); } }; pub const Class = NewClass( - Headers, + RefCountedHeaders, .{ .name = "Headers", .read_only = true, @@ -1247,13 +1292,14 @@ pub const Headers = struct { } pub fn toJSON( - this: *Headers, + ref: *RefCountedHeaders, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, _: []const js.JSValueRef, _: js.ExceptionRef, ) js.JSValueRef { + var this = ref.leak(); const slice = this.entries.slice(); const keys = slice.items(.name); const values = slice.items(.value); @@ -1352,27 +1398,92 @@ pub const Headers = struct { }; pub const Blob = struct { - size: usize = 0, - ptr: ?[*]u8 = null, - cap: usize = 0, - ptr_allocator: ?std.mem.Allocator = null, + size: u32 = 0, + offset: u32 = 0, allocator: ?std.mem.Allocator = null, - + store: ?*Store = null, content_type: string = "", content_type_allocated: bool = false, - detached: bool = false, globalThis: *JSGlobalObject, - pub fn asArrayList(this: *const Blob) std.ArrayListUnmanaged(u8) { - if (this.ptr == null or this.size == 0) - return .{}; + pub const Store = struct { + ptr: [*]u8 = undefined, + len: u32 = 0, + ref_count: u32 = 0, + cap: u32 = 0, + allocator: std.mem.Allocator, - return .{ - .items = this.ptr.?[0..this.size], - .capacity = this.cap, - }; - } + pub inline fn ref(this: *Store) void { + this.ref_count += 1; + } + + pub fn init(bytes: []u8, allocator: std.mem.Allocator) !*Store { + var store = try allocator.create(Store); + store.* = .{ + .ptr = bytes.ptr, + .len = @truncate(u32, bytes.len), + .ref_count = 1, + .cap = @truncate(u32, bytes.len), + .allocator = allocator, + }; + return store; + } + + pub fn external(_: ?*anyopaque, ptr: ?*anyopaque, _: usize) callconv(.C) void { + if (ptr == null) return; + var this = bun.cast(*Store, ptr); + this.deref(); + } + + pub fn fromArrayList(list: std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator) !*Store { + var store = try allocator.create(Store); + store.* = .{ + .ptr = list.items.ptr, + .len = @truncate(u32, list.items.len), + .ref_count = 1, + .cap = @truncate(u32, list.capacity), + .allocator = allocator, + }; + return store; + } + + pub fn leakSlice(this: *const Store) []const u8 { + return this.ptr[0..this.len]; + } + + pub fn slice(this: *Store) []u8 { + this.ref_count += 1; + return this.leakSlice(); + } + + pub fn isOnlyOneRef(this: *const Store) bool { + return this.ref_count <= 1; + } + + pub fn deref(this: *Store) void { + this.ref_count -= 1; + if (this.ref_count == 0) { + var allocated_slice = this.ptr[0..this.cap]; + var allocator = this.allocator; + allocator.free(allocated_slice); + allocator.destroy(this); + } + } + + pub fn asArrayList(this: *Store) std.ArrayListUnmanaged(u8) { + this.ref_count += 1; + + return this.asArrayListLeak(); + } + + pub fn asArrayListLeak(this: *const Store) std.ArrayListUnmanaged(u8) { + return .{ + .items = this.ptr[0..this.len], + .capacity = this.cap, + }; + } + }; pub const Class = NewClass( Blob, @@ -1519,11 +1630,9 @@ pub const Blob = struct { const len = @intCast(u32, @maximum(relativeEnd - relativeStart, 0)); - var blob = Blob.init( - getAllocator(ctx).dupe(u8, this.sharedView()[@intCast(usize, relativeStart)..][0..len]) catch unreachable, - getAllocator(ctx), - ctx.ptr(), - ); + var blob = this.dupe(); + blob.offset = @intCast(u32, relativeStart); + blob.size = len; blob.content_type = content_type; blob.content_type_allocated = content_type.len > 0; @@ -1590,7 +1699,7 @@ pub const Blob = struct { blob = Blob.init(empty, getAllocator(ctx), ctx.ptr()); }, else => { - blob = fromJS(ctx.ptr(), JSValue.fromRef(args[0])) catch { + blob = fromJS(ctx.ptr(), JSValue.fromRef(args[0]), false) catch { JSC.JSError(getAllocator(ctx), "out of memory :(", .{}, ctx, exception); return null; }; @@ -1634,13 +1743,10 @@ pub const Blob = struct { pub fn init(bytes: []u8, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) Blob { return Blob{ - .size = bytes.len, - .ptr = bytes.ptr, - .cap = bytes.len, - .ptr_allocator = allocator, + .size = @truncate(u32, bytes.len), + .store = Blob.Store.init(bytes, allocator) catch unreachable, .allocator = null, .content_type = "", - .detached = false, .globalThis = globalThis, }; } @@ -1648,39 +1754,38 @@ pub const Blob = struct { pub fn initEmpty(globalThis: *JSGlobalObject) Blob { return Blob{ .size = 0, - .ptr = undefined, - .cap = 0, - .ptr_allocator = null, + .store = null, .allocator = null, .content_type = "", - .detached = false, .globalThis = globalThis, }; } + // Transferring doesn't change the reference count + // It is a move + inline fn transfer(this: *Blob) void { + this.store = null; + } + pub fn detach(this: *Blob) void { - var blob = this.*; - if (blob.ptr_allocator) |alloc| { - alloc.free(blob.sharedView()); - this.ptr_allocator = null; - this.ptr = null; - this.size = 0; - this.cap = 0; + if (this.store) |store| { + store.deref(); + this.store = null; } - - this.detached = true; } - pub fn dupe(this: *const Blob, allocator: std.mem.Allocator) !Blob { - var data = try allocator.dupe(u8, this.sharedView()); + /// This does not duplicate + /// This creates a new view + /// and increment the reference count + pub fn dupe(this: *const Blob) Blob { + if (this.store) |store| { + store.ref(); + } + return Blob{ - .size = data.len, - .ptr = data.ptr, - .cap = data.len, - .ptr_allocator = allocator, - .allocator = allocator, + .store = this.store, + .size = this.size, .content_type = this.content_type, - .detached = false, .globalThis = this.globalThis, }; } @@ -1697,8 +1802,13 @@ pub const Blob = struct { } pub fn sharedView(this: *const Blob) []const u8 { - if (this.size == 0 or this.ptr == null) return ""; - return this.ptr.?[0..this.size]; + if (this.size == 0 or this.store == null) return ""; + return this.store.?.leakSlice()[this.offset..][0..this.size]; + } + + pub fn view(this: *const Blob) []const u8 { + if (this.size == 0 or this.store == null) return ""; + return this.store.?.slice()[this.offset..][0..this.size]; } pub const Lifetime = enum { @@ -1708,7 +1818,8 @@ pub const Blob = struct { }; pub fn toString(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue { - var view_ = this.sharedView(); + var view_: []const u8 = + this.sharedView(); if (view_.len == 0) return ZigString.Empty.toValue(global); @@ -1717,6 +1828,10 @@ pub const Blob = struct { var buf = view_; if (!strings.isAllASCII(buf)) { var external = (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) orelse &[_]u16{}; + if (lifetime == .transfer) { + this.detach(); + } + return ZigString.toExternalU16(external.ptr, external.len, global); } @@ -1725,15 +1840,13 @@ pub const Blob = struct { return ZigString.init(buf).toValueGC(global); }, .transfer => { - this.detached = true; - this.ptr_allocator = null; - this.ptr = null; - this.size = 0; - this.cap = 0; - return ZigString.init(buf).toExternalValue(global); + var store = this.store.?; + this.transfer(); + return ZigString.init(buf).external(global, store, Store.external); }, .share => { - return ZigString.init(buf).toValue(global); + this.store.?.ref(); + return ZigString.init(buf).external(global, this.store.?, Store.external); }, } } @@ -1752,7 +1865,11 @@ pub const Blob = struct { return ZigString.toExternalU16(external.ptr, external.len, global).parseJSON(global); } - return ZigString.init(buf).toValue(global).parseJSON(global); + return ZigString.init(buf).external( + global, + this.store.?, + Store.external, + ).parseJSON(global); } pub fn toArrayBuffer(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue { var view_ = this.sharedView(); @@ -1768,38 +1885,69 @@ pub const Blob = struct { return JSC.ArrayBuffer.fromBytes(clone, .ArrayBuffer).toJS(global.ref(), null); }, .share => { - return JSC.ArrayBuffer.fromBytes(view_.ptr, .ArrayBuffer).toJS(global.ref(), null); + this.store.?.ref(); + return JSC.ArrayBuffer.fromBytes(bun.constStrToU8(view_), .ArrayBuffer).toJSWithContext( + global.ref(), + this.store.?, + JSC.BlobArrayBuffer_deallocator, + null, + ); }, .transfer => { - this.detached = true; - this.ptr_allocator = null; - this.ptr = null; - this.size = 0; - this.cap = 0; - return JSC.ArrayBuffer.fromBytes(bun.constStrToU8(view_), .ArrayBuffer).toJS(global.ref(), null); + var store = this.store.?; + this.transfer(); + return JSC.ArrayBuffer.fromBytes(bun.constStrToU8(view_), .ArrayBuffer).toJSWithContext( + global.ref(), + store, + JSC.BlobArrayBuffer_deallocator, + null, + ); }, } } - pub fn fromJS(global: *JSGlobalObject, arg: JSValue) anyerror!Blob { + pub inline fn fromJS(global: *JSGlobalObject, arg: JSValue, comptime move: bool) anyerror!Blob { + if (comptime move) { + return fromJSMove(global, arg); + } else { + return fromJSClone(global, arg); + } + } + + pub inline fn fromJSMove(global: *JSGlobalObject, arg: JSValue) anyerror!Blob { + return fromJSWithoutDeferGC(global, arg, true); + } + + pub inline fn fromJSClone(global: *JSGlobalObject, arg: JSValue) anyerror!Blob { + return fromJSWithoutDeferGC(global, arg, false); + } + + fn fromJSMovable(global: *JSGlobalObject, arg: JSValue, comptime move: bool) anyerror!Blob { + const FromJSFunction = if (comptime move) + fromJSMove + else + fromJSClone; const DeferCtx = struct { - args: std.meta.ArgsTuple(@TypeOf(Blob.fromJSWithoutDeferGC)), + args: std.meta.ArgsTuple(@TypeOf(FromJSFunction)), ret: anyerror!Blob = undefined, pub fn run(ctx: ?*anyopaque) callconv(.C) void { var that = bun.cast(*@This(), ctx.?); - that.ret = @call(.{}, Blob.fromJSWithoutDeferGC, that.args); + that.ret = @call(.{}, FromJSFunction, that.args); } }; var ctx = DeferCtx{ - .args = .{ global, arg }, + .args = .{ + global, + arg, + }, .ret = undefined, }; JSC.VirtualMachine.vm.global.vm().deferGC(&ctx, DeferCtx.run); return ctx.ret; } - fn fromJSWithoutDeferGC(global: *JSGlobalObject, arg: JSValue) anyerror!Blob { + fn fromJSWithoutDeferGC(global: *JSGlobalObject, arg: JSValue, comptime move: bool) anyerror!Blob { var current = arg; if (current.isUndefinedOrNull()) { return Blob{ .globalThis = global }; @@ -1814,19 +1962,13 @@ pub const Blob = struct { JSC.JSValue.JSType.DerivedStringObject, => { var sliced = current.toSlice(global, bun.default_allocator); + if (!sliced.allocated) { sliced.ptr = @ptrCast([*]const u8, (try bun.default_allocator.dupe(u8, sliced.slice())).ptr); sliced.allocated = true; } - return Blob{ - .size = sliced.len, - .ptr = bun.constStrToU8(sliced.ptr[0..sliced.len]).ptr, - .cap = sliced.len, - .ptr_allocator = bun.default_allocator, - .allocator = null, - .globalThis = global, - }; + return Blob.init(bun.constStrToU8(sliced.slice()), bun.default_allocator, global); }, JSC.JSValue.JSType.ArrayBuffer, @@ -1844,14 +1986,7 @@ pub const Blob = struct { JSC.JSValue.JSType.DataView, => { var buf = try bun.default_allocator.dupe(u8, current.asArrayBuffer(global).?.slice()); - return Blob{ - .size = buf.len, - .ptr = buf.ptr, - .cap = buf.len, - .ptr_allocator = bun.default_allocator, - .allocator = null, - .globalThis = global, - }; + return Blob.init(buf, bun.default_allocator, global); }, else => { @@ -1860,7 +1995,13 @@ pub const Blob = struct { switch (data.tag()) { .Blob => { var blob: *Blob = data.as(Blob); - return try blob.dupe(bun.default_allocator); + if (comptime move) { + var _blob = blob.*; + blob.transfer(); + return _blob; + } else { + return blob.dupe(); + } }, else => return Blob.initEmpty(global), @@ -2007,14 +2148,7 @@ pub const Blob = struct { } var joined = try joiner.done(bun.default_allocator); - return Blob{ - .ptr_allocator = bun.default_allocator, - .allocator = null, - .size = joined.len, - .ptr = joined.ptr, - .cap = joined.len, - .globalThis = global, - }; + return Blob.init(joined, bun.default_allocator, global); } }; @@ -2038,7 +2172,7 @@ pub const Body = struct { pub fn clone(this: Body, allocator: std.mem.Allocator) Body { return Body{ .init = this.init.clone(allocator), - .value = this.value.clone(allocator) catch Value.empty, + .value = this.value.clone(allocator), }; } @@ -2054,7 +2188,7 @@ pub const Body = struct { if (this.init.headers) |headers| { try formatter.writeIndent(Writer, writer); try writer.writeAll("headers: "); - try headers.writeFormat(formatter, writer, comptime enable_ansi_colors); + try headers.leak().writeFormat(formatter, writer, comptime enable_ansi_colors); try writer.writeAll("\n"); } @@ -2065,14 +2199,14 @@ pub const Body = struct { pub fn deinit(this: *Body, _: std.mem.Allocator) void { if (this.init.headers) |headers| { - headers.deinit(); + headers.deref(); } this.value.deinit(); } pub const Init = struct { - headers: ?*Headers = null, + headers: ?*Headers.RefCountedHeaders = null, status_code: u16, method: Method = Method.GET, @@ -2080,9 +2214,11 @@ pub const Body = struct { var that = this; var headers = this.headers; if (headers) |head| { - headers.?.allocator = allocator; - var new_headers = allocator.create(Headers) catch unreachable; - head.clone(new_headers) catch unreachable; + headers.?.value.allocator = allocator; + var new_headers = allocator.create(Headers.RefCountedHeaders) catch unreachable; + new_headers.allocator = allocator; + new_headers.count = 1; + head.leak().clone(&new_headers.value) catch unreachable; that.headers = new_headers; } @@ -2106,8 +2242,7 @@ pub const Body = struct { switch (js.JSValueGetType(ctx, header_prop)) { js.JSType.kJSTypeObject => { if (try Headers.JS.headersInit(ctx, header_prop)) |headers| { - result.headers = try allocator.create(Headers); - result.headers.?.* = headers; + result.headers = try Headers.RefCountedHeaders.init(headers, allocator); } }, else => {}, @@ -2194,9 +2329,9 @@ pub const Body = struct { } } - pub fn clone(this: Value, allocator: std.mem.Allocator) !Value { + pub fn clone(this: Value, _: std.mem.Allocator) Value { if (this == .Blob) { - return Value{ .Blob = try this.Blob.dupe(allocator) }; + return Value{ .Blob = this.Blob.dupe() }; } return Value{ .Empty = .{} }; @@ -2266,7 +2401,7 @@ pub const Body = struct { } body.value = .{ - .Blob = Blob.fromJS(ctx.ptr(), value) catch { + .Blob = Blob.fromJS(ctx.ptr(), value, true) catch { JSC.JSError(allocator, "Out of memory", .{}, ctx, exception); return body; }, @@ -2279,25 +2414,25 @@ pub const Body = struct { // https://developer.mozilla.org/en-US/docs/Web/API/Request pub const Request = struct { url: ZigString = ZigString.Empty, - headers: ?*Headers = null, + headers: ?*Headers.RefCountedHeaders = null, body: Body.Value = Body.Value{ .Empty = .{} }, method: Method = Method.GET, pub fn fromRequestContext(ctx: *RequestContext) !Request { - var headers_ = bun.default_allocator.create(Headers) catch unreachable; var req = Request{ .url = ZigString.init(std.mem.span(ctx.getFullURL())), .body = Body.Value.empty, .method = ctx.method, - .headers = headers_, + .headers = try Headers.RefCountedHeaders.init(Headers.fromRequestCtx(bun.default_allocator, ctx) catch unreachable, bun.default_allocator), }; - headers_.* = Headers.fromRequestCtx(bun.default_allocator, ctx) catch unreachable; req.url.mark(); return req; } pub fn mimeType(this: *const Request) string { - if (this.headers) |headers| { + if (this.headers) |headers_ref| { + var headers = headers_ref.get(); + defer headers_ref.deref(); // Remember, we always lowercase it // hopefully doesn't matter here tho if (headers.getHeaderIndex("content-type")) |content_type| { @@ -2413,21 +2548,7 @@ pub const Request = struct { ) js.JSValueRef { return js.JSValueMakeString(ctx, ZigString.init("").toValueGC(ctx.ptr()).asRef()); } - pub fn getHeaders( - this: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - if (this.headers == null) { - var headers = getAllocator(ctx).create(Headers) catch unreachable; - headers.* = Headers.empty(getAllocator(ctx)); - this.headers = headers; - } - return Headers.Class.make(ctx, this.headers.?); - } pub fn getIntegrity( _: *Request, ctx: js.JSContextRef, @@ -2468,6 +2589,10 @@ pub const Request = struct { } pub fn finalize(this: *Request) void { + if (this.headers) |headers| { + headers.deref(); + } + bun.default_allocator.destroy(this); } @@ -2487,7 +2612,8 @@ pub const Request = struct { _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - if (this.headers) |headers| { + if (this.headers) |headers_ref| { + var headers = headers_ref.leak(); if (headers.getHeaderIndex("referrer")) |i| { return ZigString.init(headers.asStr(headers.entries.get(i).value)).toValueGC(ctx.ptr()).asObjectRef(); } @@ -2536,7 +2662,7 @@ pub const Request = struct { } if (JSC.JSValue.fromRef(arguments[1]).get(ctx.ptr(), "body")) |body_| { - if (Blob.fromJS(ctx.ptr(), body_) catch null) |blob| { + if (Blob.fromJS(ctx.ptr(), body_, true) catch null) |blob| { request.body = Body.Value{ .Blob = blob }; } } @@ -2575,14 +2701,28 @@ pub const Request = struct { return Request.Class.make(ctx, cloned); } + pub fn getHeaders( + this: *Request, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSStringRef, + _: js.ExceptionRef, + ) js.JSValueRef { + if (this.headers == null) { + this.headers = Headers.RefCountedHeaders.init(Headers.empty(bun.default_allocator), bun.default_allocator) catch unreachable; + } + + return Headers.Class.make(ctx, this.headers.?.getRef()); + } + pub fn cloneInto(this: *const Request, req: *Request, allocator: std.mem.Allocator) void { req.* = Request{ - .body = this.body.clone(allocator) catch unreachable, + .body = this.body.clone(allocator), .url = ZigString.init(allocator.dupe(u8, this.url.slice()) catch unreachable), }; if (this.headers) |head| { - var new_headers = allocator.create(Headers) catch unreachable; - head.clone(new_headers) catch unreachable; + var new_headers = Headers.RefCountedHeaders.init(undefined, allocator) catch unreachable; + head.leak().clone(&new_headers.value) catch unreachable; req.headers = new_headers; } } @@ -2642,6 +2782,7 @@ fn BlobInterface(comptime Type: type) type { var blob = this.body.use(); var ptr = getAllocator(ctx).create(Blob) catch unreachable; ptr.* = blob; + blob.allocator = getAllocator(ctx); return JSC.JSPromise.resolvedPromiseValue(ctx.ptr(), JSValue.fromRef(Blob.Class.make(ctx, ptr))).asObjectRef(); } }; @@ -2751,14 +2892,9 @@ pub const FetchEvent = struct { } if (this.pending_promise) |promise| { - var status = promise.status(globalThis.vm()); - - while (status == .Pending) { - VirtualMachine.vm.tick(); - status = promise.status(globalThis.vm()); - } + VirtualMachine.vm.event_loop.waitForPromise(promise); - switch (status) { + switch (promise.status(ctx.ptr().vm())) { .Fulfilled => {}, else => { this.rejected = true; @@ -2811,7 +2947,9 @@ pub const FetchEvent = struct { defer this.pending_promise = null; var needs_mime_type = true; var content_length: ?usize = null; - if (response.body.init.headers) |headers| { + if (response.body.init.headers) |headers_ref| { + var headers = headers_ref.get(); + defer headers_ref.deref(); this.request_context.clearHeaders() catch {}; var i: usize = 0; while (i < headers.entries.len) : (i += 1) { @@ -2826,6 +2964,15 @@ pub const FetchEvent = struct { continue; } + // Some headers need to be managed by bun + if (strings.eqlComptime(name, "transfer-encoding") or + strings.eqlComptime(name, "content-encoding") or + strings.eqlComptime(name, "strict-transport-security") or + strings.eqlComptime(name, "content-security-policy")) + { + continue; + } + this.request_context.appendHeaderSlow( name, headers.asStr(header.value), @@ -2838,7 +2985,7 @@ pub const FetchEvent = struct { } var blob = response.body.value.use(); - // defer blob.deinit(); + defer blob.deinit(); const content_length_ = content_length orelse blob.size; diff --git a/src/ref_count.zig b/src/ref_count.zig new file mode 100644 index 000000000..2c72aaeb5 --- /dev/null +++ b/src/ref_count.zig @@ -0,0 +1,82 @@ +const std = @import("std"); + +pub const RefCountedSlice = struct {}; + +pub fn RefCount(comptime Type: type, comptime deinit_on_zero: bool) type { + return struct { + const AllocatorType = if (deinit_on_zero) std.mem.Allocator else void; + + value: Type, + count: i32 = 1, + allocator: AllocatorType = undefined, + + pub inline fn ref(this: *@This()) void { + this.count += 1; + } + + /// Create a new reference counted value. + pub inline fn init( + value: Type, + allocator: std.mem.Allocator, + ) !*@This() { + var ptr = try allocator.create(@This()); + ptr.create(value, allocator); + return ptr; + } + + /// Get the value & increment the reference count. + pub inline fn get(this: *@This()) *Type { + std.debug.assert(this.count >= 0); + + this.count += 1; + return this.leak(); + } + + /// Get the value without incrementing the reference count. + pub inline fn leak(this: *@This()) *Type { + return &this.value; + } + + pub inline fn getRef(this: *@This()) *@This() { + this.count += 1; + return this; + } + + pub inline fn create( + this: *@This(), + value: Type, + allocator: AllocatorType, + ) void { + this.* = .{ + .value = value, + .allocator = allocator, + .count = 1, + }; + } + + pub inline fn deinit(this: *@This()) void { + if (comptime @hasDecl(Type, "deinit")) { + this.value.deinit(); + } + + if (comptime deinit_on_zero) { + var allocator = this.allocator; + allocator.destroy(this); + } + } + + pub inline fn deref(this: *@This()) void { + this.count -= 1; + + std.debug.assert(this.count >= 0); + + if (comptime deinit_on_zero) { + if (this.count <= 0) { + this.deinit(); + } + } + } + + pub const Type = Type; + }; +} diff --git a/src/string_immutable.zig b/src/string_immutable.zig index a5e3e7104..7e9895a42 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -709,6 +709,31 @@ pub inline fn copyU8IntoU16(output_: []u16, input_: []const u8) void { } } +pub inline fn appendUTF8MachineWordToUTF16MachineWordUnaligned(comptime alignment: u21, output: *align(alignment) [@sizeOf(usize) / 2]u16, input: *const [@sizeOf(usize) / 2]u8) void { + comptime var i: usize = 0; + inline while (i < @sizeOf(usize) / 2) : (i += 1) { + output[i] = input[i]; + } +} + +pub fn copyU8IntoU16WithAlignment(comptime alignment: u21, output_: []align(alignment) u16, input_: []const u8) void { + var output = output_; + var input = input_; + const word = @sizeOf(usize) / 2; + if (comptime Environment.allow_assert) { + std.debug.assert(input.len <= output.len); + } + while (input.len >= word) { + appendUTF8MachineWordToUTF16MachineWordUnaligned(alignment, output[0..word], input[0..word]); + output = output[word..]; + input = input[word..]; + } + + for (input) |c, i| { + output[i] = c; + } +} + // pub inline fn copy(output_: []u8, input_: []const u8) void { // var output = output_; // var input = input_; |