diff --git a/.vscode/launch.json b/.vscode/launch.json index 6abecdcb83..fdede61672 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,11 +17,11 @@ "cwd": "${workspaceFolder}/test", "env": { "FORCE_COLOR": "1", - "BUN_DEBUG_QUIET_LOGS": "1", - "BUN_GARBAGE_COLLECTOR_LEVEL": "2", + "BUN_GARBAGE_COLLECTOR_LEVEL": "1", }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -42,6 +42,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -66,6 +67,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -84,6 +86,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -102,6 +105,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -120,6 +124,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -139,6 +144,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -163,6 +169,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -188,6 +195,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -206,6 +214,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -224,6 +233,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -242,6 +252,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -260,6 +271,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -279,6 +291,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -303,6 +316,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -327,6 +341,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -345,6 +360,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -363,6 +379,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -381,6 +398,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -399,6 +417,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -418,6 +437,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -442,6 +462,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -466,6 +487,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -485,6 +507,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -503,6 +526,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -522,6 +546,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -545,6 +570,7 @@ }, "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, @@ -558,6 +584,7 @@ "cwd": "${workspaceFolder}/packages/bun-internal-test", "console": "internalConsole", "sourceMap": { + "/build/bun": "${workspaceFolder}", "/webkit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", "/Users/runner/work/WebKit/WebKit/Source": "${workspaceFolder}/src/bun.js/WebKit/Source", }, diff --git a/.vscode/settings.json b/.vscode/settings.json index 3a3e5fe4f4..11bbed6f28 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -147,4 +147,5 @@ "WebKit/WebKitBuild": true, "WebKit/WebInspectorUI": true, }, + "git.detectSubmodules": false, } diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 0106fb757c..96cf1f8c65 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -308,7 +308,7 @@ pub fn shell( const allocator = getAllocator(globalThis); var arena = bun.ArenaAllocator.init(allocator); - const arguments_ = callframe.arguments(1); + const arguments_ = callframe.arguments(8); var arguments = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments_.slice()); const string_args = arguments.nextEat() orelse { globalThis.throw("shell: expected 2 arguments, got 0", .{}); @@ -318,13 +318,20 @@ pub fn shell( const template_args = callframe.argumentsPtr()[1..callframe.argumentsCount()]; var jsobjs = std.ArrayList(JSValue).init(arena.allocator()); var script = std.ArrayList(u8).init(arena.allocator()); + if (!(bun.shell.shellCmdFromJS(globalThis, string_args, template_args, &jsobjs, &script) catch { - globalThis.throwOutOfMemory(); + if (!globalThis.hasException()) + globalThis.throwOutOfMemory(); return JSValue.undefined; })) { return .undefined; } + if (globalThis.hasException()) { + arena.deinit(); + return .undefined; + } + const lex_result = brk: { if (bun.strings.isAllASCII(script.items[0..])) { var lexer = Shell.LexerAscii.new(arena.allocator(), script.items[0..]); @@ -383,13 +390,15 @@ pub fn shellEscape( globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - if (callframe.argumentsCount() < 0) { + const arguments = callframe.arguments(1); + if (arguments.len < 1) { globalThis.throw("shell escape expected at least 1 argument", .{}); return .undefined; } - const jsval = callframe.argument(0); + const jsval = arguments.ptr[0]; const bunstr = jsval.toBunString(globalThis); + if (globalThis.hasException()) return .zero; defer bunstr.deref(); var outbuf = std.ArrayList(u8).init(bun.default_allocator); @@ -435,6 +444,8 @@ pub fn braces( }; const brace_str = brace_str_js.toBunString(globalThis); defer brace_str.deref(); + if (globalThis.hasException()) return .zero; + const brace_slice = brace_str.toUTF8(bun.default_allocator); defer brace_slice.deinit(); @@ -453,6 +464,7 @@ pub fn braces( } } } + if (globalThis.hasException()) return .zero; var arena = std.heap.ArenaAllocator.init(bun.default_allocator); defer arena.deinit(); @@ -552,6 +564,9 @@ pub fn which( } bin_str = path_arg.toSlice(globalThis, globalThis.bunVM().allocator); + if (globalThis.hasException()) { + return .zero; + } if (bin_str.len >= bun.MAX_PATH_BYTES) { globalThis.throw("bin path is too long", .{}); @@ -767,8 +782,9 @@ pub fn getStdin( var rare_data = globalThis.bunVM().rareData(); var store = rare_data.stdin(); store.ref(); - var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable; - blob.* = JSC.WebCore.Blob.initWithStore(store, globalThis); + var blob = JSC.WebCore.Blob.new( + JSC.WebCore.Blob.initWithStore(store, globalThis), + ); blob.allocator = bun.default_allocator; return blob.toJS(globalThis); } @@ -780,8 +796,9 @@ pub fn getStderr( var rare_data = globalThis.bunVM().rareData(); var store = rare_data.stderr(); store.ref(); - var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable; - blob.* = JSC.WebCore.Blob.initWithStore(store, globalThis); + var blob = JSC.WebCore.Blob.new( + JSC.WebCore.Blob.initWithStore(store, globalThis), + ); blob.allocator = bun.default_allocator; return blob.toJS(globalThis); } @@ -793,8 +810,9 @@ pub fn getStdout( var rare_data = globalThis.bunVM().rareData(); var store = rare_data.stdout(); store.ref(); - var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable; - blob.* = JSC.WebCore.Blob.initWithStore(store, globalThis); + var blob = JSC.WebCore.Blob.new( + JSC.WebCore.Blob.initWithStore(store, globalThis), + ); blob.allocator = bun.default_allocator; return blob.toJS(globalThis); } @@ -3548,6 +3566,11 @@ const UnsafeObject = struct { callframe: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { const args = callframe.arguments(2).slice(); + if (args.len < 1 or !args[0].isCell() or !args[0].jsType().isTypedArray()) { + globalThis.throwInvalidArguments("Expected an ArrayBuffer", .{}); + return .zero; + } + const array_buffer = JSC.ArrayBuffer.fromTypedArray(globalThis, args[0]); switch (array_buffer.typed_array_type) { .Uint16Array, .Int16Array => { @@ -3592,6 +3615,10 @@ const TOMLObject = struct { defer arena.deinit(); var log = logger.Log.init(default_allocator); const arguments = callframe.arguments(1).slice(); + if (arguments.len == 0 or arguments[0].isEmptyOrUndefinedOrNull()) { + globalThis.throwInvalidArguments("Expected a string to parse", .{}); + return .zero; + } var input_slice = arguments[0].toSlice(globalThis, bun.default_allocator); defer input_slice.deinit(); @@ -3696,116 +3723,164 @@ pub const FFIObject = struct { } pub fn @"u8"( - _: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) u8, @ptrFromInt(addr)).*; return JSValue.jsNumber(value); } pub fn @"u16"( - _: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) u16, @ptrFromInt(addr)).*; return JSValue.jsNumber(value); } pub fn @"u32"( - _: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) u32, @ptrFromInt(addr)).*; return JSValue.jsNumber(value); } pub fn ptr( - _: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) u64, @ptrFromInt(addr)).*; return JSValue.jsNumber(value); } pub fn @"i8"( - _: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) i8, @ptrFromInt(addr)).*; return JSValue.jsNumber(value); } pub fn @"i16"( - _: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) i16, @ptrFromInt(addr)).*; return JSValue.jsNumber(value); } pub fn @"i32"( - _: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) i32, @ptrFromInt(addr)).*; return JSValue.jsNumber(value); } pub fn intptr( - _: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) i64, @ptrFromInt(addr)).*; return JSValue.jsNumber(value); } pub fn @"f32"( - _: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) f32, @ptrFromInt(addr)).*; return JSValue.jsNumber(value); } pub fn @"f64"( - _: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) f64, @ptrFromInt(addr)).*; return JSValue.jsNumber(value); } pub fn @"i64"( - global: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) i64, @ptrFromInt(addr)).*; - return JSValue.fromInt64NoTruncate(global, value); + return JSValue.fromInt64NoTruncate(globalObject, value); } pub fn @"u64"( - global: *JSGlobalObject, + globalObject: *JSGlobalObject, _: JSValue, arguments: []const JSValue, ) JSValue { + if (arguments.len == 0 or !arguments[0].isNumber()) { + globalObject.throwInvalidArguments("Expected a pointer", .{}); + return .zero; + } const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0); const value = @as(*align(1) u64, @ptrFromInt(addr)).*; - return JSValue.fromUInt64NoTruncate(global, value); + return JSValue.fromUInt64NoTruncate(globalObject, value); } pub fn u8WithoutTypeChecks( diff --git a/src/bun.js/api/brotli.zig b/src/bun.js/api/brotli.zig index ba98e76798..6512ccc461 100644 --- a/src/bun.js/api/brotli.zig +++ b/src/bun.js/api/brotli.zig @@ -125,7 +125,7 @@ pub const BrotliEncoder = struct { }); if (result.toError()) |err| { - this.globalThis.bunVM().runErrorHandler(err, null); + _ = this.globalThis.bunVM().uncaughtException(this.globalThis, err, false); } } @@ -430,7 +430,7 @@ pub const BrotliDecoder = struct { }); if (result.toError()) |err| { - this.globalThis.bunVM().runErrorHandler(err, null); + _ = this.globalThis.bunVM().uncaughtException(this.globalThis, err, false); } } diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp index ab8bb91e4f..b83a729a3a 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.cpp +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -512,6 +512,7 @@ JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject * #else JSValue dirnameValue = JSValue::decode(Bun__Path__dirname(globalObject, false, &encodedFilename, 1)); #endif + RETURN_IF_EXCEPTION(throwScope, JSValue::encode({})); String dirnameString = dirnameValue.toWTFString(globalObject); diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig index 41ae7fdea3..f50c6566b1 100644 --- a/src/bun.js/rare_data.zig +++ b/src/bun.js/rare_data.zig @@ -271,7 +271,6 @@ pub fn boringEngine(rare: *RareData) *BoringSSL.ENGINE { pub fn stderr(rare: *RareData) *Blob.Store { bun.Analytics.Features.@"Bun.stderr" += 1; return rare.stderr_store orelse brk: { - const store = default_allocator.create(Blob.Store) catch unreachable; var mode: bun.Mode = 0; const fd = if (Environment.isWindows) FDImpl.fromUV(2).encode() else bun.STDERR_FD; @@ -282,7 +281,7 @@ pub fn stderr(rare: *RareData) *Blob.Store { .err => {}, } - store.* = Blob.Store{ + const store = Blob.Store.new(.{ .ref_count = std.atomic.Value(u32).init(2), .allocator = default_allocator, .data = .{ @@ -294,7 +293,7 @@ pub fn stderr(rare: *RareData) *Blob.Store { .mode = mode, }, }, - }; + }); rare.stderr_store = store; break :brk store; @@ -304,7 +303,6 @@ pub fn stderr(rare: *RareData) *Blob.Store { pub fn stdout(rare: *RareData) *Blob.Store { bun.Analytics.Features.@"Bun.stdout" += 1; return rare.stdout_store orelse brk: { - const store = default_allocator.create(Blob.Store) catch unreachable; var mode: bun.Mode = 0; const fd = if (Environment.isWindows) FDImpl.fromUV(1).encode() else bun.STDOUT_FD; @@ -314,7 +312,7 @@ pub fn stdout(rare: *RareData) *Blob.Store { }, .err => {}, } - store.* = Blob.Store{ + const store = Blob.Store.new(.{ .ref_count = std.atomic.Value(u32).init(2), .allocator = default_allocator, .data = .{ @@ -326,7 +324,7 @@ pub fn stdout(rare: *RareData) *Blob.Store { .mode = mode, }, }, - }; + }); rare.stdout_store = store; break :brk store; }; @@ -335,7 +333,6 @@ pub fn stdout(rare: *RareData) *Blob.Store { pub fn stdin(rare: *RareData) *Blob.Store { bun.Analytics.Features.@"Bun.stdin" += 1; return rare.stdin_store orelse brk: { - const store = default_allocator.create(Blob.Store) catch unreachable; var mode: bun.Mode = 0; const fd = if (Environment.isWindows) FDImpl.fromUV(0).encode() else bun.STDIN_FD; @@ -345,7 +342,7 @@ pub fn stdin(rare: *RareData) *Blob.Store { }, .err => {}, } - store.* = Blob.Store{ + const store = Blob.Store.new(.{ .allocator = default_allocator, .ref_count = std.atomic.Value(u32).init(2), .data = .{ @@ -357,7 +354,7 @@ pub fn stdin(rare: *RareData) *Blob.Store { .mode = mode, }, }, - }; + }); rare.stdin_store = store; break :brk store; }; diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index e948620548..4689a831b3 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -78,6 +78,7 @@ const WriteFileWindows = @import("./blob/WriteFile.zig").WriteFileWindows; pub const Blob = struct { const bloblog = Output.scoped(.Blob, false); + pub usingnamespace bun.New(@This()); pub usingnamespace JSC.Codegen.JSBlob; pub usingnamespace @import("./blob/WriteFile.zig"); pub usingnamespace @import("./blob/ReadFile.zig"); @@ -408,7 +409,7 @@ pub const Blob = struct { const bytes = try readSlice(reader, bytes_len, allocator); const blob = Blob.init(bytes, allocator, globalThis); - const blob_ = bun.new(Blob, blob); + const blob_ = Blob.new(blob); break :brk blob_; }, @@ -422,7 +423,7 @@ pub const Blob = struct { var path_or_fd = JSC.Node.PathOrFileDescriptor{ .fd = fd, }; - const blob = bun.new(Blob, Blob.findOrCreateFileFromPath( + const blob = Blob.new(Blob.findOrCreateFileFromPath( &path_or_fd, globalThis, )); @@ -438,7 +439,7 @@ pub const Blob = struct { .string = bun.PathString.init(path), }, }; - const blob = bun.new(Blob, Blob.findOrCreateFileFromPath( + const blob = Blob.new(Blob.findOrCreateFileFromPath( &dest, globalThis, )); @@ -450,7 +451,7 @@ pub const Blob = struct { return .zero; }, .empty => brk: { - break :brk bun.new(Blob, Blob.initEmpty(globalThis)); + break :brk Blob.new(Blob.initEmpty(globalThis)); }, }; blob.allocator = allocator; @@ -584,7 +585,7 @@ pub const Blob = struct { export fn Blob__dupe(ptr: *anyopaque) *Blob { var this = bun.cast(*Blob, ptr); - var new = bun.new(Blob, this.dupeWithContentType(true)); + var new = Blob.new(this.dupeWithContentType(true)); new.allocator = bun.default_allocator; return new; } @@ -858,7 +859,7 @@ pub const Blob = struct { // eventually, this could be like Buffer.concat var clone = source_blob.dupe(); clone.allocator = bun.default_allocator; - const cloned = bun.new(Blob, clone); + const cloned = Blob.new(clone); cloned.allocator = bun.default_allocator; return JSPromise.resolvedPromiseValue(ctx.ptr(), cloned.toJS(ctx)); } else if (destination_type == .bytes and source_type == .file) { @@ -1347,7 +1348,7 @@ pub const Blob = struct { callframe: *JSC.CallFrame, ) callconv(.C) ?*Blob { JSC.markBinding(@src()); - var allocator = bun.default_allocator; + const allocator = bun.default_allocator; var blob: Blob = undefined; var arguments = callframe.arguments(3); const args = arguments.slice(); @@ -1419,7 +1420,7 @@ pub const Blob = struct { blob.content_type_was_set = false; } - var blob_ = bun.new(Blob, blob); + var blob_ = Blob.new(blob); blob_.allocator = allocator; blob_.is_jsdom_file = true; return blob_; @@ -1512,7 +1513,7 @@ pub const Blob = struct { } } - var ptr = bun.new(Blob, blob); + var ptr = Blob.new(blob); ptr.allocator = bun.default_allocator; return ptr.toJS(globalObject); } @@ -1586,6 +1587,8 @@ pub const Blob = struct { is_all_ascii: ?bool = null, allocator: std.mem.Allocator, + pub usingnamespace bun.New(@This()); + pub fn size(this: *const Store) SizeType { return switch (this.data) { .bytes => this.data.bytes.len, @@ -1612,7 +1615,7 @@ pub const Blob = struct { } pub fn initFile(pathlike: JSC.Node.PathOrFileDescriptor, mime_type: ?http.MimeType, allocator: std.mem.Allocator) !*Store { - const store = bun.newWithAlloc(allocator, Blob.Store, .{ + const store = Blob.Store.new(.{ .data = .{ .file = FileStore.init( pathlike, @@ -1639,7 +1642,7 @@ pub const Blob = struct { } pub fn init(bytes: []u8, allocator: std.mem.Allocator) !*Store { - const store = bun.newWithAlloc(allocator, Store, .{ + const store = Blob.Store.new(.{ .data = .{ .bytes = ByteStore.init(bytes, allocator), }, @@ -1682,7 +1685,7 @@ pub const Blob = struct { }, } - bun.destroyWithAlloc(allocator, this); + this.destroy(); } const SerializeTag = enum(u8) { @@ -3096,13 +3099,13 @@ pub const Blob = struct { globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - var allocator = bun.default_allocator; + const allocator = bun.default_allocator; var arguments_ = callframe.arguments(3); var args = arguments_.ptr[0..arguments_.len]; if (this.size == 0) { const empty = Blob.initEmpty(globalThis); - var ptr = bun.new(Blob, empty); + var ptr = Blob.new(empty); ptr.allocator = allocator; return ptr.toJS(globalThis); } @@ -3194,7 +3197,7 @@ pub const Blob = struct { blob.content_type_allocated = content_type_was_allocated; blob.content_type_was_set = this.content_type_was_set or content_type_was_allocated; - var blob_ = bun.new(Blob, blob); + var blob_ = Blob.new(blob); blob_.allocator = allocator; return blob_.toJS(globalThis); } @@ -3396,7 +3399,7 @@ pub const Blob = struct { globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, ) callconv(.C) ?*Blob { - var allocator = bun.default_allocator; + const allocator = bun.default_allocator; var blob: Blob = undefined; var arguments = callframe.arguments(2); const args = arguments.slice(); @@ -3456,7 +3459,7 @@ pub const Blob = struct { blob.calculateEstimatedByteSize(); - var blob_ = bun.new(Blob, blob); + var blob_ = Blob.new(blob); blob_.allocator = allocator; return blob_; } @@ -3524,9 +3527,8 @@ pub const Blob = struct { switch (bun.linux.memfd_allocator.create(bytes_)) { .err => {}, .result => |result| { - const store = bun.new( - Store, - Store{ + const store = Store.new( + .{ .data = .{ .bytes = result, }, @@ -3638,9 +3640,11 @@ pub const Blob = struct { pub fn deinit(this: *Blob) void { this.detach(); + // TODO: remove this field, make it a boolean. if (this.allocator) |alloc| { this.allocator = null; - bun.destroyWithAlloc(alloc, this); + bun.debugAssert(alloc.vtable == bun.default_allocator.vtable); + this.destroy(); } } diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig index 27193bb750..a356634f2d 100644 --- a/src/bun.js/webcore/body.zig +++ b/src/bun.js/webcore/body.zig @@ -683,7 +683,7 @@ pub const Body = struct { async_form_data.toJS(global, blob.slice(), promise); }, else => { - var ptr = bun.new(Blob, new.use()); + var ptr = Blob.new(new.use()); ptr.allocator = bun.default_allocator; promise.resolve(global, ptr.toJS(global)); }, @@ -1169,7 +1169,7 @@ pub fn BodyMixin(comptime Type: type) type { return value.Locked.setPromise(globalObject, .{ .getBlob = {} }); } - var blob = bun.new(Blob, value.use()); + var blob = Blob.new(value.use()); blob.allocator = getAllocator(globalObject); if (blob.content_type.len == 0 and blob.store != null) { diff --git a/src/bun.zig b/src/bun.zig index 2f27c4d1fc..90901541eb 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -2877,6 +2877,10 @@ pub const HeapBreakdown = if (is_heap_breakdown_enabled) @import("./heap_breakdo /// On macOS, you can use `Bun.unsafe.mimallocDump()` /// to dump the heap. pub inline fn new(comptime T: type, t: T) *T { + if (comptime @hasDecl(T, "is_bun.New()")) { + // You will get weird memory bugs in debug builds if you use the wrong allocator. + @compileError("Use " ++ @typeName(T) ++ ".new() instead of bun.new()"); + } if (comptime is_heap_breakdown_enabled) { const ptr = HeapBreakdown.allocator(T).create(T) catch outOfMemory(); ptr.* = t; @@ -2888,6 +2892,9 @@ pub inline fn new(comptime T: type, t: T) *T { return ptr; } +pub const newWithAlloc = @compileError("If you're going to use a global allocator, don't conditionally use it. Use bun.New() instead."); +pub const destroyWithAlloc = @compileError("If you're going to use a global allocator, don't conditionally use it. Use bun.New() instead."); + pub inline fn dupe(comptime T: type, t: *T) *T { if (comptime is_heap_breakdown_enabled) { const ptr = HeapBreakdown.allocator(T).create(T) catch outOfMemory(); @@ -2900,24 +2907,10 @@ pub inline fn dupe(comptime T: type, t: *T) *T { return ptr; } -/// Free a globally-allocated a value -/// -/// On macOS, you can use `Bun.unsafe.mimallocDump()` -/// to dump the heap. -pub inline fn destroyWithAlloc(allocator: std.mem.Allocator, t: anytype) void { - if (comptime is_heap_breakdown_enabled) { - if (allocator.vtable == default_allocator.vtable) { - destroy(t); - return; - } - } - - allocator.destroy(t); -} - pub fn New(comptime T: type) type { return struct { const allocation_logger = Output.scoped(.alloc, @hasDecl(T, "logAllocations")); + pub const @"is_bun.New()" = true; pub inline fn destroy(self: *T) void { if (comptime Environment.allow_assert) { @@ -3048,18 +3041,6 @@ pub inline fn destroy(t: anytype) void { } } -pub inline fn newWithAlloc(allocator: std.mem.Allocator, comptime T: type, t: T) *T { - if (comptime is_heap_breakdown_enabled) { - if (allocator.vtable == default_allocator.vtable) { - return new(T, t); - } - } - - const ptr = allocator.create(T) catch outOfMemory(); - ptr.* = t; - return ptr; -} - pub fn exitThread() noreturn { const exiter = struct { pub extern "C" fn pthread_exit(?*anyopaque) noreturn; diff --git a/src/codegen/generate-classes.ts b/src/codegen/generate-classes.ts index 7d67a8b048..ef7c8cf865 100644 --- a/src/codegen/generate-classes.ts +++ b/src/codegen/generate-classes.ts @@ -497,6 +497,7 @@ function generateConstructorHeader(typeName) { // Must be defined for each specialization class. static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); DECLARE_EXPORT_INFO; private: @@ -528,7 +529,7 @@ void ${name}::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, ${protot } ${name}::${name}(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure, ${ - obj.call ? classSymbolName(typeName, "call") : "construct" + obj.call ? classSymbolName(typeName, "call") : "call" }, construct) { } @@ -541,17 +542,41 @@ ${name}* ${name}::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::St return ptr; } +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${name}::call(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) +{ + Zig::GlobalObject *globalObject = reinterpret_cast(lexicalGlobalObject); + JSC::VM &vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + void* ptr = ${classSymbolName(typeName, "construct")}(globalObject, callFrame); + + if (UNLIKELY(!ptr || scope.exception())) { + return JSValue::encode(JSC::jsUndefined()); + } + + Structure* structure = globalObject->${className(typeName)}Structure(); + ${className(typeName)}* instance = ${className(typeName)}::create(vm, globalObject, structure, ptr); + RETURN_IF_EXCEPTION(scope, {}); + ${ + obj.estimatedSize + ? ` + auto size = ${symbolName(typeName, "estimatedSize")}(ptr); + vm.heap.reportExtraMemoryAllocated(instance, size);` + : "" + } + + RELEASE_AND_RETURN(scope, JSValue::encode(instance)); +} + JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${name}::construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { Zig::GlobalObject *globalObject = reinterpret_cast(lexicalGlobalObject); JSC::VM &vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); JSObject* newTarget = asObject(callFrame->newTarget()); auto* constructor = globalObject->${className(typeName)}Constructor(); Structure* structure = globalObject->${className(typeName)}Structure(); - if (constructor != newTarget) { - auto scope = DECLARE_THROW_SCOPE(vm); - + if (UNLIKELY(constructor != newTarget)) { auto* functionGlobalObject = reinterpret_cast( // ShadowRealm functions belong to a different global object. getFunctionRealm(globalObject, newTarget) @@ -566,7 +591,7 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${name}::construct(JSC::JSGlobalObj void* ptr = ${classSymbolName(typeName, "construct")}(globalObject, callFrame); - if (UNLIKELY(!ptr)) { + if (UNLIKELY(!ptr || scope.exception())) { return JSValue::encode(JSC::jsUndefined()); } @@ -579,7 +604,7 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${name}::construct(JSC::JSGlobalObj : "" } - return JSValue::encode(instance); + RELEASE_AND_RETURN(scope, JSValue::encode(instance)); } void ${name}::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, ${prototypeName(typeName)}* prototype) @@ -1326,7 +1351,7 @@ ${renderCallbacksCppImpl(typeName, callbacks)} output += ` ${name}::~${name}() { - if (m_ctx) { + if (LIKELY(m_ctx)) { ${classSymbolName(typeName, "finalize")}(m_ctx); } } diff --git a/src/js/node/dgram.ts b/src/js/node/dgram.ts index 6e2e115bd5..b3fb856096 100644 --- a/src/js/node/dgram.ts +++ b/src/js/node/dgram.ts @@ -764,15 +764,16 @@ Socket.prototype[SymbolAsyncDispose] = async function () { if (!this[kStateSymbol].handle.socket) { return; } - return new Promise((resolve, reject) => { - this.close(err => { - if (err) { - reject(err); - } else { - resolve(); - } - }); + const { promise, resolve, reject } = $newPromiseCapability(Promise); + this.close(err => { + if (err) { + reject(err); + } else { + resolve(); + } }); + + return promise; }; function socketCloseNT(self) { diff --git a/src/js/node/events.ts b/src/js/node/events.ts index f69eaf8dff..75f610921f 100644 --- a/src/js/node/events.ts +++ b/src/js/node/events.ts @@ -43,7 +43,7 @@ EventEmitterPrototype.setMaxListeners = function setMaxListeners(n) { EventEmitterPrototype.constructor = EventEmitter; EventEmitterPrototype.getMaxListeners = function getMaxListeners() { - return this._maxListeners ?? defaultMaxListeners; + return this?._maxListeners ?? defaultMaxListeners; }; function emitError(emitter, args) { @@ -323,38 +323,39 @@ function once(emitter, type, options) { if (signal?.aborted) { throw new AbortError(undefined, { cause: signal?.reason }); } - return new Promise((resolve, reject) => { - const errorListener = err => { - emitter.removeListener(type, resolver); - if (signal != null) { - eventTargetAgnosticRemoveListener(signal, "abort", abortListener); - } - reject(err); - }; - const resolver = (...args) => { - if (typeof emitter.removeListener === "function") { - emitter.removeListener("error", errorListener); - } - if (signal != null) { - eventTargetAgnosticRemoveListener(signal, "abort", abortListener); - } - resolve(args); - }; - eventTargetAgnosticAddListener(emitter, type, resolver, { once: true }); - if (type !== "error" && typeof emitter.once === "function") { - // EventTarget does not have `error` event semantics like Node - // EventEmitters, we listen to `error` events only on EventEmitters. - emitter.once("error", errorListener); + const { resolve, reject, promise } = $newPromiseCapability(Promise); + const errorListener = err => { + emitter.removeListener(type, resolver); + if (signal != null) { + eventTargetAgnosticRemoveListener(signal, "abort", abortListener); } - function abortListener() { - eventTargetAgnosticRemoveListener(emitter, type, resolver); - eventTargetAgnosticRemoveListener(emitter, "error", errorListener); - reject(new AbortError(undefined, { cause: signal?.reason })); + reject(err); + }; + const resolver = (...args) => { + if (typeof emitter.removeListener === "function") { + emitter.removeListener("error", errorListener); } if (signal != null) { - eventTargetAgnosticAddListener(signal, "abort", abortListener, { once: true }); + eventTargetAgnosticRemoveListener(signal, "abort", abortListener); } - }); + resolve(args); + }; + eventTargetAgnosticAddListener(emitter, type, resolver, { once: true }); + if (type !== "error" && typeof emitter.once === "function") { + // EventTarget does not have `error` event semantics like Node + // EventEmitters, we listen to `error` events only on EventEmitters. + emitter.once("error", errorListener); + } + function abortListener() { + eventTargetAgnosticRemoveListener(emitter, type, resolver); + eventTargetAgnosticRemoveListener(emitter, "error", errorListener); + reject(new AbortError(undefined, { cause: signal?.reason })); + } + if (signal != null) { + eventTargetAgnosticAddListener(signal, "abort", abortListener, { once: true }); + } + + return promise; } function on(emitter, event, options = {}) { @@ -423,8 +424,9 @@ function on(emitter, event, options = {}) { return iterator(); } +const toStringTag = Symbol.toStringTag; function getEventListeners(emitter, type) { - if (emitter instanceof EventTarget) { + if (emitter?.[toStringTag] === "EventTarget") { throwNotImplemented("getEventListeners with an EventTarget", 2678); } return emitter.listeners(type); diff --git a/src/js/node/http.ts b/src/js/node/http.ts index ba56c32b98..eeedcd945d 100644 --- a/src/js/node/http.ts +++ b/src/js/node/http.ts @@ -1215,9 +1215,9 @@ function ensureReadableStreamController(run) { firstWrite = undefined; run(controller); if (!this[finishedSymbol]) { - return new Promise(resolve => { - this[deferredSymbol] = resolve; - }); + const { promise, resolve } = $newPromiseCapability(GlobalPromise); + this[deferredSymbol] = resolve; + return promise; } }, }), diff --git a/src/js/node/readline.ts b/src/js/node/readline.ts index aa18f4a25f..916f115b32 100644 --- a/src/js/node/readline.ts +++ b/src/js/node/readline.ts @@ -2989,10 +2989,11 @@ class Readline { * flushed to the associated `stream`. */ commit() { - return new Promise(resolve => { - this.#stream.write(ArrayPrototypeJoin.$call(this.#todo, ""), resolve); - this.#todo = []; - }); + const { resolve, promise } = $newPromiseCapability(Promise); + this.#stream.write(ArrayPrototypeJoin.$call(this.#todo, ""), resolve); + this.#todo = []; + + return promise; } /** @@ -3019,21 +3020,21 @@ var PromisesInterface = class Interface extends _Interface { return PromiseReject(new AbortError(undefined, { cause: signal.reason })); } } - return new Promise((resolve, reject) => { - var cb = resolve; - if (options?.signal) { - var onAbort = () => { - this[kQuestionCancel](); - reject(new AbortError(undefined, { cause: signal.reason })); - }; - signal.addEventListener("abort", onAbort, { once: true }); - cb = answer => { - signal.removeEventListener("abort", onAbort); - resolve(answer); - }; - } - this[kQuestion](query, cb); - }); + const { promise, resolve, reject } = $newPromiseCapability(Promise); + var cb = resolve; + if (options?.signal) { + var onAbort = () => { + this[kQuestionCancel](); + reject(new AbortError(undefined, { cause: signal.reason })); + }; + signal.addEventListener("abort", onAbort, { once: true }); + cb = answer => { + signal.removeEventListener("abort", onAbort); + resolve(answer); + }; + } + this[kQuestion](query, cb); + return promise; } }; diff --git a/src/js/node/stream.ts b/src/js/node/stream.ts index dc321744c9..ab4a9dffc1 100644 --- a/src/js/node/stream.ts +++ b/src/js/node/stream.ts @@ -1304,15 +1304,15 @@ var require_end_of_stream = __commonJS({ return cleanup; } function finished(stream, opts) { - return new Promise2((resolve, reject) => { - eos(stream, opts, err => { - if (err) { - reject(err); - } else { - resolve(); - } - }); + const { promise, resolve, reject } = $newPromiseCapability(Promise); + eos(stream, opts, err => { + if (err) { + reject(err); + } else { + resolve(); + } }); + return promise; } module.exports = eos; module.exports.finished = finished; @@ -5403,30 +5403,30 @@ var require_promises = __commonJS({ var { pipelineImpl: pl } = require_pipeline(); var { finished } = require_end_of_stream(); function pipeline(...streams) { - return new Promise2((resolve, reject) => { - let signal; - let end; - const lastArg = streams[streams.length - 1]; - if (lastArg && typeof lastArg === "object" && !isNodeStream(lastArg) && !isIterable(lastArg)) { - const options = ArrayPrototypePop(streams); - signal = options.signal; - end = options.end; - } - pl( - streams, - (err, value) => { - if (err) { - reject(err); - } else { - resolve(value); - } - }, - { - signal, - end, - }, - ); - }); + const { promise, resolve, reject } = $newPromiseCapability(Promise); + let signal; + let end; + const lastArg = streams[streams.length - 1]; + if (lastArg && typeof lastArg === "object" && !isNodeStream(lastArg) && !isIterable(lastArg)) { + const options = ArrayPrototypePop(streams); + signal = options.signal; + end = options.end; + } + pl( + streams, + (err, value) => { + if (err) { + reject(err); + } else { + resolve(value); + } + }, + { + signal, + end, + }, + ); + return promise; } module.exports = { finished, diff --git a/test/js/bun/util/fuzzy-wuzzy.test.ts b/test/js/bun/util/fuzzy-wuzzy.test.ts new file mode 100644 index 0000000000..34e4859a40 --- /dev/null +++ b/test/js/bun/util/fuzzy-wuzzy.test.ts @@ -0,0 +1,379 @@ +/** + * + * This file attempts to run practically every function in Bun with no + * arguments. This is sort of like a fuzzer. + * + * If you have a test failure pointing to this file, or if this file suddenly + * started becoming flaky, that usually means a JS bindings issue or a memory bug. + * + * What this does: + * + * Go through most of the methods & constructors in Bun: + * - Try to call them with no arguments - Foo() + * - Try to construct them with no arguments - new Foo() + * + * If your code panics or crashes with an uncatchable exception when no + * arguments are passed, that's a bug you should fix. + * + */ + +const ENABLE_LOGGING = false; + +import { describe, expect, test } from "bun:test"; +import { isWindows } from "harness"; + +const Promise = globalThis.Promise; +globalThis.Promise = function (...args) { + if (args.length === 0) { + return Promise.resolve(); + } + + const { resolve, reject, promise } = Promise.withResolvers(); + args[0](resolve, reject); + + return promise?.catch?.(e => { + if (ENABLE_LOGGING) { + console.log(e); + } + }); +}; +globalThis.Promise.prototype = Promise.prototype; +Object.assign(globalThis.Promise, Promise); + +function wrap(input) { + if (typeof input?.catch === "function") { + return input?.catch?.(e => { + if (ENABLE_LOGGING) { + console.error(e); + } + }); + } + + return input; +} + +// Don't allow these to be called +delete process.exit; +delete process._reallyExit; +delete process.reallyExit; +delete process.abort; +delete process.kill; +delete process._kill; +delete process._destroy; +delete process._events; +delete process.openStdin; +delete process.emitWarning; +delete require("stream").Readable.prototype.destroy; +delete globalThis.Loader; +// ** Uncatchable errors in tests ** +delete ReadableStreamDefaultReader.prototype["closed"]; +delete ReadableStreamBYOBReader.prototype["closed"]; +delete WritableStreamDefaultWriter.prototype["ready"]; +delete WritableStreamDefaultWriter.prototype["closed"]; +WebAssembly.compile = () => {}; +WebAssembly.instantiate = () => {}; +// ** Uncatchable errors in tests ** + +const banned = [ + "alert", + "prompt", + "confirm", + "open", + "close", + "connect", + "listen", + "_start", + "wait", + "wait", + "sleep", + "exit", + "kill", + // "_read", + // "read", + // "_write", + // "resume", +]; +const drainMicrotasks = require("bun:jsc").drainMicrotasks; + +import.meta.require.cache["bun:jsc"] = {}; +delete console.takeHeapSnapshot; +delete console.clear; +delete console.warn; +delete console.time; +delete console.timeEnd; +delete console.trace; +delete console.timeLog; +delete console.assert; +Bun.generateHeapSnapshot = () => {}; + +const TODOs = [ + "ByteLengthQueuingStrategy", + "CountQueuingStrategy", + "ReadableByteStreamController", + "ReadableStream", + "ReadableStreamBYOBReader", + "ReadableStreamBYOBRequest", + "ReadableStreamDefaultController", + "ReadableStreamDefaultReader", + "TransformStream", + "TransformStreamDefaultController", + "WritableStream", + "WritableStreamDefaultController", + "WritableStreamDefaultWriter", +]; + +const ignoreList = [ + Object.prototype, + Function.prototype, + Array.prototype, + async function () {}.prototype, + function* () {}.prototype, + async function* () {}.prototype, + function* () {}.prototype, + Uint8Array.prototype, + Uint16Array.prototype, + Uint32Array.prototype, + Int8Array.prototype, + Int16Array.prototype, + Int32Array.prototype, + Float32Array.prototype, + Float64Array.prototype, + BigInt64Array.prototype, + BigUint64Array.prototype, + ArrayBuffer.prototype, + DataView.prototype, + Promise.prototype, + SharedArrayBuffer.prototype, + Error.prototype, + EvalError.prototype, + RangeError.prototype, + ReferenceError.prototype, + SyntaxError.prototype, + TypeError.prototype, + URIError.prototype, + RegExp.prototype, + Date.prototype, + String.prototype, + + // TODO: getFunctionRealm() on these. + ReadableStream.prototype, +]; + +const constructBanned = banned; +const callBanned = [...TODOs, ...banned]; + +function allThePropertyNames(object, banned) { + if (!object) { + return []; + } + const names = Object.getOwnPropertyNames(object); + var pro = Object.getPrototypeOf(object); + + while (pro) { + if (ignoreList.includes(pro)) { + break; + } + + names.push(...Object.getOwnPropertyNames(pro)); + pro = Object.getPrototypeOf(pro); + } + + for (const ban of banned) { + const index = names.indexOf(ban); + if (index !== -1) { + names.splice(index, 1); + } + } + + return names; +} + +if (ENABLE_LOGGING) { + { + const original = Reflect.construct; + Reflect.construct = function (...args) { + try { + console.log(args?.[0]?.name || args?.[1]?.name || args?.[0]?.name || args?.[0]?.[Symbol.toStringTag]); + } catch (e) {} + return original(...args); + }; + } + { + const original = Reflect.apply; + Reflect.apply = function (...args) { + try { + console.log(args?.[0]?.name || args?.[1]?.name || args?.[0]?.name || args?.[0]?.[Symbol.toStringTag]); + } catch (e) {} + return original(...args); + }; + } +} + +const seenValues = new WeakSet(); +var callAllMethodsCount = 0; +function callAllMethods(object) { + callAllMethodsCount++; + const queue = []; + const seen = new Set([object, object?.subarray]); + for (const methodName of allThePropertyNames(object, callBanned)) { + try { + try { + const returnValue = wrap(Reflect.apply(object?.[methodName], object, [])); + Bun.inspect?.(returnValue), queue.push(returnValue); + } catch (e) { + const returnValue = wrap(Reflect.apply(object.constructor?.[methodName], object?.constructor, [])); + Bun.inspect?.(returnValue), queue.push(returnValue); + } + } catch (e) { + const val = object?.[methodName]; + if (val && (typeof val === "object" || typeof val === "function") && !seenValues.has(val)) { + seenValues.add(val); + queue.push(val); + } + } finally { + } + } + + while (queue.length) { + const value = queue.shift(); + if (value) { + for (const methodName of allThePropertyNames(value, callBanned)) { + try { + const method = value?.[methodName]; + if (method && seen.has(method)) { + continue; + } + seen.add(method); + const returnValue = wrap(Reflect?.apply?.(method, value, [])); + if (returnValue?.then) { + continue; + } + Bun.inspect?.(returnValue), queue.push(returnValue); + } catch (e) {} + } + } + } +} + +function constructAllConstructors(object) { + const queue = []; + const seen = new Set([object?.subarray]); + for (const methodName of allThePropertyNames(object, constructBanned)) { + const method = object?.[methodName]; + try { + try { + const returnValue = Reflect.construct(object, [], method); + Bun.inspect?.(returnValue), queue.push(returnValue); + } catch (e) { + const returnValue = Reflect.construct(object?.constructor, [], method); + Bun.inspect?.(returnValue), queue.push(returnValue); + } + } catch (e) { + try { + const returnValue = Reflect.construct(object?.prototype?.constructor, [], method); + Bun.inspect?.(returnValue), queue.push(returnValue); + } catch (e) { + Error.captureStackTrace(e); + } + } + } + + while (queue.length) { + const value = queue.shift(); + for (const methodName of allThePropertyNames(value, constructBanned)) { + try { + const method = value?.[methodName]; + if (method && seen.has(method)) { + continue; + } + + const returnValue = Reflect.construct(value, [], method); + if (seen.has(returnValue)) { + continue; + } + + Bun.inspect?.(returnValue), queue.push(returnValue); + seen.add(returnValue); + } catch (e) {} + } + } +} + +const modules = [ + "module", + "util", + "url", + "path", + "path/posix", + "path/win32", + "perf_hooks", + "os", + "dgram", + "domain", + // "crypto", + "timers", + "punycode", + "trace_events", + "child_process", + "diagnostics_channel", + "http2", + "bun:ffi", + "string_decoder", + "bun:sqlite", +]; + +for (const mod of modules) { + describe(mod, () => { + test("call", () => callAllMethods(require(mod))); + test("construct", () => constructAllConstructors(require(mod))); + }); +} + +for (const HardCodedClass of [ + require("fs").ReadStream, + require("fs").WriteStream, + require("tty").ReadStream, + require("tty").WriteStream, + require("fs").Stats, + require("fs").Dirent, + Intl, + Intl.Collator, + Intl.DateTimeFormat, + Intl.ListFormat, + Intl.NumberFormat, + Intl.PluralRules, + Intl.RelativeTimeFormat, + Intl.Locale, + Intl.DisplayNames, + Intl.Segmenter, + + // TODO: undefined is not an object + // require("fs").FSWatcher, + + process, +]) { + test("call " + (HardCodedClass.name || HardCodedClass.toString()), () => constructAllConstructors(HardCodedClass)); + test("construct " + (HardCodedClass.name || HardCodedClass.toString()), () => callAllMethods(HardCodedClass)); +} + +const globals = [ + [globalThis, "globalThis"], + [Bun, "Bun"], +] as const; + +for (const [Global, name] of globals) { + describe(name, () => { + // TODO: hangs in CI on Windows. + test.skipIf(isWindows && Global === Bun)("call", async () => { + await Bun.sleep(1); + callAllMethods(Global); + await Bun.sleep(1); + }); + // TODO: hangs in CI on Windows. + test.skipIf(isWindows && Global === Bun)("construct", async () => { + await Bun.sleep(1); + constructAllConstructors(Global); + await Bun.sleep(1); + }); + }); +} diff --git a/test/js/node/http/node-http.test.ts b/test/js/node/http/node-http.test.ts index 94cd221bea..4658956d27 100644 --- a/test/js/node/http/node-http.test.ts +++ b/test/js/node/http/node-http.test.ts @@ -1047,7 +1047,8 @@ describe("node:http", () => { }); }); - test("error event not fired, issue#4651", done => { + test("error event not fired, issue#4651", async () => { + const { promise, resolve } = Promise.withResolvers(); const server = createServer((req, res) => { res.end(); }); @@ -1056,11 +1057,12 @@ describe("node:http", () => { res.end(); }); server2.on("error", err => { - expect(err.code).toBe("EADDRINUSE"); - done(); + resolve(err); }); server2.listen({ port: 42069 }, () => {}); }); + const err = await promise; + expect(err.code).toBe("EADDRINUSE"); }); }); describe("node https server", async () => { diff --git a/test/js/web/websocket/websocket.test.js b/test/js/web/websocket/websocket.test.js index 5f7f231fab..48cfd9e88a 100644 --- a/test/js/web/websocket/websocket.test.js +++ b/test/js/web/websocket/websocket.test.js @@ -552,6 +552,7 @@ describe("websocket in subprocess", () => { it("should exit after timeout", async () => { let messageReceived = false; let start = 0; + let end = 0; using server = Bun.serve({ port: 0, fetch(req, server) { @@ -568,7 +569,7 @@ describe("websocket in subprocess", () => { }, message(ws, message) { messageReceived = true; - expect(performance.now() - start >= 300).toBe(true); + end = performance.now(); ws.close(); }, close(ws) {}, @@ -584,6 +585,7 @@ describe("websocket in subprocess", () => { expect(await subprocess.exited).toBe(0); expect(messageReceived).toBe(true); + expect(Math.ceil(end - start)).toBeGreaterThanOrEqual(290); }); it("should exit after server stop and 0 messages", async () => {