diff --git a/bench/crypto/primes.mjs b/bench/crypto/primes.mjs new file mode 100644 index 0000000000..7398d60bf2 --- /dev/null +++ b/bench/crypto/primes.mjs @@ -0,0 +1,43 @@ +import { checkPrime, checkPrimeSync, generatePrime, generatePrimeSync } from "node:crypto"; +import { bench, run } from "../runner.mjs"; + +const prime512 = generatePrimeSync(512); +const prime2048 = generatePrimeSync(2048); + +bench("checkPrimeSync 512", () => { + return checkPrimeSync(prime512); +}); + +bench("checkPrimeSync 2048", () => { + return checkPrimeSync(prime2048); +}); + +bench("checkPrime 512", async () => { + const promises = Array.from({ length: 10 }, () => new Promise(resolve => checkPrime(prime512, resolve))); + await Promise.all(promises); +}); + +bench("checkPrime 2048", async () => { + const promises = Array.from({ length: 10 }, () => new Promise(resolve => checkPrime(prime2048, resolve))); + await Promise.all(promises); +}); + +bench("generatePrimeSync 512", () => { + return generatePrimeSync(512); +}); + +bench("generatePrimeSync 2048", () => { + return generatePrimeSync(2048); +}); + +bench("generatePrime 512", async () => { + const promises = Array.from({ length: 10 }, () => new Promise(resolve => generatePrime(512, resolve))); + await Promise.all(promises); +}); + +bench("generatePrime 2048", async () => { + const promises = Array.from({ length: 10 }, () => new Promise(resolve => generatePrime(2048, resolve))); + await Promise.all(promises); +}); + +await run(); diff --git a/src/bake/DevServer.zig b/src/bake/DevServer.zig index b65c72a9ce..2a6bb81673 100644 --- a/src/bake/DevServer.zig +++ b/src/bake/DevServer.zig @@ -945,7 +945,7 @@ fn initServerRuntime(dev: *DevServer) void { if (!interface.isObject()) @panic("Internal assertion failure: expected interface from HMR runtime to be an object"); const fetch_function = interface.get(dev.vm.global, "handleRequest") catch null orelse @panic("Internal assertion failure: expected interface from HMR runtime to contain handleRequest"); - bun.assert(fetch_function.isCallable(dev.vm.jsc)); + bun.assert(fetch_function.isCallable()); dev.server_fetch_function_callback = JSC.Strong.create(fetch_function, dev.vm.global); const register_update = interface.get(dev.vm.global, "registerUpdate") catch null orelse @panic("Internal assertion failure: expected interface from HMR runtime to contain registerUpdate"); diff --git a/src/bake/FrameworkRouter.zig b/src/bake/FrameworkRouter.zig index 9c5954eafb..00ada78cba 100644 --- a/src/bake/FrameworkRouter.zig +++ b/src/bake/FrameworkRouter.zig @@ -430,7 +430,7 @@ pub const Style = union(enum) { if (map.get(utf8.slice())) |style| { return style; } - } else if (value.isCallable(global.vm())) { + } else if (value.isCallable()) { return .{ .javascript_defined = JSC.Strong.create(value, global) }; } diff --git a/src/bake/production.zig b/src/bake/production.zig index 9e1fc0d59a..e4441ee5df 100644 --- a/src/bake/production.zig +++ b/src/bake/production.zig @@ -375,7 +375,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa const server_render_func = brk: { const raw = BakeGetOnModuleNamespace(global, server_entry_point, "prerender") orelse break :brk null; - if (!raw.isCallable(vm.jsc)) { + if (!raw.isCallable()) { break :brk null; } break :brk raw; @@ -391,7 +391,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa brk: { const raw = BakeGetOnModuleNamespace(global, server_entry_point, "getParams") orelse break :brk null; - if (!raw.isCallable(vm.jsc)) { + if (!raw.isCallable()) { break :brk null; } break :brk raw; diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index e7202d4126..4c02d210dd 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -1238,7 +1238,7 @@ pub const Formatter = struct { if (js_type.canGet() and js_type != .ProxyObject and !opts.disable_inspect_custom) { // Attempt to get custom formatter if (value.fastGet(globalThis, .inspectCustom)) |callback_value| { - if (callback_value.isCallable(globalThis.vm())) { + if (callback_value.isCallable()) { return .{ .tag = .{ .CustomFormattedObject = .{ @@ -1262,7 +1262,7 @@ pub const Formatter = struct { // If we check an Object has a method table and it does not // it will crash - if (js_type != .Object and js_type != .ProxyObject and value.isCallable(globalThis.vm())) { + if (js_type != .Object and js_type != .ProxyObject and value.isCallable()) { if (value.isClass(globalThis)) { return .{ .tag = .{ .Class = {} }, @@ -2638,7 +2638,7 @@ pub const Formatter = struct { } else if (JestPrettyFormat.printAsymmetricMatcher(this, Format, &writer, writer_, name_buf, value, enable_ansi_colors)) { return; } else if (jsType != .DOMWrapper) { - if (value.isCallable(this.globalThis.vm())) { + if (value.isCallable()) { return try this.printAs(.Function, Writer, writer_, value, jsType, enable_ansi_colors); } @@ -3038,7 +3038,7 @@ pub const Formatter = struct { if (_tag.cell == .Symbol) {} else if (_tag.cell.isStringLike()) { try type_value.toZigString(&tag_name_str, this.globalThis); is_tag_kind_primitive = true; - } else if (_tag.cell.isObject() or type_value.isCallable(this.globalThis.vm())) { + } else if (_tag.cell.isObject() or type_value.isCallable()) { type_value.getNameProperty(this.globalThis, &tag_name_str); if (tag_name_str.len == 0) { tag_name_str = ZigString.init("NoName"); @@ -3136,15 +3136,15 @@ pub const Formatter = struct { } if (!this.single_line and ( - // count_without_children is necessary to prevent printing an extra newline - // if there are children and one prop and the child prop is the last prop + // count_without_children is necessary to prevent printing an extra newline + // if there are children and one prop and the child prop is the last prop props_iter.i + 1 < count_without_children and - // 3 is arbitrary but basically - // - // ^ should be one line - // - // ^ should be multiple lines - props_iter.i > 3)) + // 3 is arbitrary but basically + // + // ^ should be one line + // + // ^ should be multiple lines + props_iter.i > 3)) { writer.writeAll("\n"); this.writeIndent(Writer, writer_) catch unreachable; @@ -3316,7 +3316,7 @@ pub const Formatter = struct { if (iter.i == 0) { if (value.isClass(this.globalThis)) try this.printAs(.Class, Writer, writer_, value, jsType, enable_ansi_colors) - else if (value.isCallable(this.globalThis.vm())) + else if (value.isCallable()) try this.printAs(.Function, Writer, writer_, value, jsType, enable_ansi_colors) else { if (getObjectName(this.globalThis, value)) |name_str| { diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index bcf9566542..a162566d3a 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -587,7 +587,7 @@ pub fn registerMacro(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFram return globalObject.throwInvalidArguments("Internal error registering macros: invalid id", .{}); } - if (!arguments[1].isCell() or !arguments[1].isCallable(globalObject.vm())) { + if (!arguments[1].isCell() or !arguments[1].isCallable()) { // TODO: add "toTypeOf" helper return globalObject.throw("Macro must be a function", .{}); } diff --git a/src/bun.js/api/bun/h2_frame_parser.zig b/src/bun.js/api/bun/h2_frame_parser.zig index 7a1501aab7..8f4aa64b8a 100644 --- a/src/bun.js/api/bun/h2_frame_parser.zig +++ b/src/bun.js/api/bun/h2_frame_parser.zig @@ -550,7 +550,7 @@ const Handlers = struct { } pub fn callWriteCallback(this: *Handlers, callback: JSC.JSValue, data: []const JSValue) bool { - if (!callback.isCallable(this.globalObject.vm())) return false; + if (!callback.isCallable()) return false; this.vm.eventLoop().runCallback(callback, this.globalObject, .undefined, data); return true; } @@ -595,7 +595,7 @@ const Handlers = struct { inline for (pairs) |pair| { if (try opts.getTruthy(globalObject, pair.@"1")) |callback_value| { - if (!callback_value.isCell() or !callback_value.isCallable(globalObject.vm())) { + if (!callback_value.isCell() or !callback_value.isCallable()) { return globalObject.throwInvalidArguments("Expected \"{s}\" callback to be a function", .{pair[1]}); } @@ -604,7 +604,7 @@ const Handlers = struct { } if (opts.fastGet(globalObject, .@"error")) |callback_value| { - if (!callback_value.isCell() or !callback_value.isCallable(globalObject.vm())) { + if (!callback_value.isCell() or !callback_value.isCallable()) { return globalObject.throwInvalidArguments("Expected \"error\" callback to be a function", .{}); } @@ -1020,7 +1020,7 @@ pub const H2FrameParser = struct { .len = @intCast(bytes.len), // we need to clone this data to send it later .buffer = if (bytes.len == 0) "" else client.allocator.alloc(u8, MAX_PAYLOAD_SIZE_WITHOUT_FRAME) catch bun.outOfMemory(), - .callback = if (callback.isCallable(globalThis.vm())) JSC.Strong.create(callback, globalThis) else .empty, + .callback = if (callback.isCallable()) JSC.Strong.create(callback, globalThis) else .empty, }; if (bytes.len > 0) { @memcpy(frame.buffer[0..bytes.len], bytes); diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 1d045304e9..ee56fc1d05 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -245,7 +245,7 @@ const Handlers = struct { }; inline for (pairs) |pair| { if (try opts.getTruthyComptime(globalObject, pair.@"1")) |callback_value| { - if (!callback_value.isCell() or !callback_value.isCallable(globalObject.vm())) { + if (!callback_value.isCell() or !callback_value.isCallable()) { return globalObject.throwInvalidArguments("Expected \"{s}\" callback to be a function", .{pair[1]}); } diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index fb19d960f0..93d4c9d10d 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -1599,9 +1599,9 @@ pub fn onProcessExit(this: *Subprocess, process: *Process, status: bun.spawn.Sta if (this.on_exit_callback.trySwap()) |callback| { const waitpid_value: JSValue = if (status == .err) - status.err.toJSC(globalThis) - else - .undefined; + status.err.toJSC(globalThis) + else + .undefined; const this_value = if (this_jsvalue.isEmptyOrUndefinedOrNull()) .undefined else this_jsvalue; this_value.ensureStillAlive(); @@ -1955,7 +1955,7 @@ pub fn spawnMaybeSync( // This must run before the stdio parsing happens if (!is_sync) { if (try args.getTruthy(globalThis, "ipc")) |val| { - if (val.isCell() and val.isCallable(globalThis.vm())) { + if (val.isCell() and val.isCallable()) { maybe_ipc_mode = ipc_mode: { if (try args.getTruthy(globalThis, "serialization")) |mode_val| { if (mode_val.isString()) { @@ -1989,7 +1989,7 @@ pub fn spawnMaybeSync( } if (try args.getTruthy(globalThis, "onDisconnect")) |onDisconnect_| { - if (!onDisconnect_.isCell() or !onDisconnect_.isCallable(globalThis.vm())) { + if (!onDisconnect_.isCell() or !onDisconnect_.isCallable()) { return globalThis.throwInvalidArguments("onDisconnect must be a function or undefined", .{}); } @@ -2000,7 +2000,7 @@ pub fn spawnMaybeSync( } if (try args.getTruthy(globalThis, "onExit")) |onExit_| { - if (!onExit_.isCell() or !onExit_.isCallable(globalThis.vm())) { + if (!onExit_.isCell() or !onExit_.isCallable()) { return globalThis.throwInvalidArguments("onExit must be a function or undefined", .{}); } diff --git a/src/bun.js/api/bun/udp_socket.zig b/src/bun.js/api/bun/udp_socket.zig index 93965c51eb..d9043c6e17 100644 --- a/src/bun.js/api/bun/udp_socket.zig +++ b/src/bun.js/api/bun/udp_socket.zig @@ -197,7 +197,7 @@ pub const UDPSocketConfig = struct { inline for (handlers) |handler| { if (try socket.getTruthyComptime(globalThis, handler.@"0")) |value| { - if (!value.isCell() or !value.isCallable(globalThis.vm())) { + if (!value.isCell() or !value.isCallable()) { return globalThis.throwInvalidArguments("Expected \"socket.{s}\" to be a function", .{handler.@"0"}); } @field(config, handler.@"1") = value; diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index ec9d7cafa8..61b878bfda 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -818,7 +818,7 @@ pub const FFI = struct { return JSC.toInvalidArguments("Expected object", .{}, globalThis); } - if (js_callback.isEmptyOrUndefinedOrNull() or !js_callback.isCallable(globalThis.vm())) { + if (js_callback.isEmptyOrUndefinedOrNull() or !js_callback.isCallable()) { return JSC.toInvalidArguments("Expected callback function", .{}, globalThis); } diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig index df253e0fbe..e22072c620 100644 --- a/src/bun.js/api/html_rewriter.zig +++ b/src/bun.js/api/html_rewriter.zig @@ -809,7 +809,7 @@ const DocumentHandler = struct { } if (try thisObject.get(global, "doctype")) |val| { - if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { + if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable()) { return global.throwInvalidArguments("doctype must be a function", .{}); } val.protect(); @@ -817,7 +817,7 @@ const DocumentHandler = struct { } if (try thisObject.get(global, "comments")) |val| { - if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { + if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable()) { return global.throwInvalidArguments("comments must be a function", .{}); } val.protect(); @@ -825,7 +825,7 @@ const DocumentHandler = struct { } if (try thisObject.get(global, "text")) |val| { - if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { + if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable()) { return global.throwInvalidArguments("text must be a function", .{}); } val.protect(); @@ -833,7 +833,7 @@ const DocumentHandler = struct { } if (try thisObject.get(global, "end")) |val| { - if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { + if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable()) { return global.throwInvalidArguments("end must be a function", .{}); } val.protect(); @@ -954,7 +954,7 @@ const ElementHandler = struct { } if (try thisObject.get(global, "element")) |val| { - if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { + if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable()) { return global.throwInvalidArguments("element must be a function", .{}); } val.protect(); @@ -962,7 +962,7 @@ const ElementHandler = struct { } if (try thisObject.get(global, "comments")) |val| { - if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { + if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable()) { return global.throwInvalidArguments("comments must be a function", .{}); } val.protect(); @@ -970,7 +970,7 @@ const ElementHandler = struct { } if (try thisObject.get(global, "text")) |val| { - if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { + if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable()) { return global.throwInvalidArguments("text must be a function", .{}); } val.protect(); @@ -1614,7 +1614,7 @@ pub const Element = struct { ) bun.JSError!JSValue { if (this.element == null) return JSValue.jsNull(); - if (function.isUndefinedOrNull() or !function.isCallable(globalObject.vm())) { + if (function.isUndefinedOrNull() or !function.isCallable()) { return ZigString.init("Expected a function").withEncoding().toJS(globalObject); } diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 3521dab350..f34a7f52ed 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -142,8 +142,8 @@ fn getContentType(headers: ?*JSC.FetchHeaders, blob: *const JSC.WebCore.AnyBlob, content else if (blob.wasString()) MimeType.text - // TODO: should we get the mime type off of the Blob.Store if it exists? - // A little wary of doing this right now due to causing some breaking change + // TODO: should we get the mime type off of the Blob.Store if it exists? + // A little wary of doing this right now due to causing some breaking change else MimeType.other; }; @@ -1398,7 +1398,7 @@ pub const ServerConfig = struct { continue; } - if (value.isCallable(global.vm())) { + if (value.isCallable()) { try validateRouteName(global, path); args.user_routes_to_build.append(.{ .route = .{ @@ -1424,7 +1424,7 @@ pub const ServerConfig = struct { var found = false; inline for (methods) |method| { if (value.getOwn(global, @tagName(method))) |function| { - if (!function.isCallable(global.vm())) { + if (!function.isCallable()) { return global.throwInvalidArguments("Expected {s} in {} route to be a function", .{ @tagName(method), bun.fmt.quote(path) }); } if (!found) { @@ -1654,7 +1654,7 @@ pub const ServerConfig = struct { if (global.hasException()) return error.JSError; if (try arg.getTruthyComptime(global, "error")) |onError| { - if (!onError.isCallable(global.vm())) { + if (!onError.isCallable()) { return global.throwInvalidArguments("Expected error to be a function", .{}); } const onErrorSnapshot = onError.withAsyncContextIfNeeded(global); @@ -1664,7 +1664,7 @@ pub const ServerConfig = struct { if (global.hasException()) return error.JSError; if (try arg.getTruthy(global, "onNodeHTTPRequest")) |onRequest_| { - if (!onRequest_.isCallable(global.vm())) { + if (!onRequest_.isCallable()) { return global.throwInvalidArguments("Expected onNodeHTTPRequest to be a function", .{}); } const onRequest = onRequest_.withAsyncContextIfNeeded(global); @@ -1673,7 +1673,7 @@ pub const ServerConfig = struct { } if (try arg.getTruthy(global, "fetch")) |onRequest_| { - if (!onRequest_.isCallable(global.vm())) { + if (!onRequest_.isCallable()) { return global.throwInvalidArguments("Expected fetch() to be a function", .{}); } const onRequest = onRequest_.withAsyncContextIfNeeded(global); @@ -4608,13 +4608,12 @@ pub const WebSocketServer = struct { } pub fn fromJS(globalObject: *JSC.JSGlobalObject, object: JSC.JSValue) bun.JSError!Handler { - const vm = globalObject.vm(); var handler = Handler{ .globalObject = globalObject, .vm = VirtualMachine.get() }; var valid = false; if (try object.getTruthyComptime(globalObject, "message")) |message_| { - if (!message_.isCallable(vm)) { + if (!message_.isCallable()) { return globalObject.throwInvalidArguments("websocket expects a function for the message option", .{}); } const message = message_.withAsyncContextIfNeeded(globalObject); @@ -4624,7 +4623,7 @@ pub const WebSocketServer = struct { } if (try object.getTruthy(globalObject, "open")) |open_| { - if (!open_.isCallable(vm)) { + if (!open_.isCallable()) { return globalObject.throwInvalidArguments("websocket expects a function for the open option", .{}); } const open = open_.withAsyncContextIfNeeded(globalObject); @@ -4634,7 +4633,7 @@ pub const WebSocketServer = struct { } if (try object.getTruthy(globalObject, "close")) |close_| { - if (!close_.isCallable(vm)) { + if (!close_.isCallable()) { return globalObject.throwInvalidArguments("websocket expects a function for the close option", .{}); } const close = close_.withAsyncContextIfNeeded(globalObject); @@ -4644,7 +4643,7 @@ pub const WebSocketServer = struct { } if (try object.getTruthy(globalObject, "drain")) |drain_| { - if (!drain_.isCallable(vm)) { + if (!drain_.isCallable()) { return globalObject.throwInvalidArguments("websocket expects a function for the drain option", .{}); } const drain = drain_.withAsyncContextIfNeeded(globalObject); @@ -4654,7 +4653,7 @@ pub const WebSocketServer = struct { } if (try object.getTruthy(globalObject, "onError")) |onError_| { - if (!onError_.isCallable(vm)) { + if (!onError_.isCallable()) { return globalObject.throwInvalidArguments("websocket expects a function for the onError option", .{}); } const onError = onError_.withAsyncContextIfNeeded(globalObject); @@ -4663,7 +4662,7 @@ pub const WebSocketServer = struct { } if (try object.getTruthy(globalObject, "ping")) |cb| { - if (!cb.isCallable(vm)) { + if (!cb.isCallable()) { return globalObject.throwInvalidArguments("websocket expects a function for the ping option", .{}); } handler.onPing = cb; @@ -4672,7 +4671,7 @@ pub const WebSocketServer = struct { } if (try object.getTruthy(globalObject, "pong")) |cb| { - if (!cb.isCallable(vm)) { + if (!cb.isCallable()) { return globalObject.throwInvalidArguments("websocket expects a function for the pong option", .{}); } handler.onPong = cb; @@ -5558,7 +5557,7 @@ pub const ServerWebSocket = struct { } const callback = args.ptr[0]; - if (callback.isEmptyOrUndefinedOrNull() or !callback.isCallable(globalThis.vm())) { + if (callback.isEmptyOrUndefinedOrNull() or !callback.isCallable()) { return globalThis.throwInvalidArgumentTypeValue("cork", "callback", callback); } @@ -6921,13 +6920,13 @@ pub const NodeHTTPResponse = struct { const input_value = if (arguments.len > 0) arguments[0] else .undefined; var encoding_value = if (arguments.len > 1) arguments[1] else .undefined; const callback_value = brk: { - if ((encoding_value != .null and encoding_value != .undefined) and encoding_value.isCallable(globalObject.vm())) { + if ((encoding_value != .null and encoding_value != .undefined) and encoding_value.isCallable()) { encoding_value = .undefined; break :brk arguments[1]; } if (arguments.len > 2 and arguments[2] != .undefined) { - if (!arguments[2].isCallable(globalObject.vm())) { + if (!arguments[2].isCallable()) { return globalObject.throwInvalidArgumentTypeValue("callback", "function", arguments[2]); } @@ -7129,7 +7128,7 @@ pub const NodeHTTPResponse = struct { return globalObject.throwNotEnoughArguments("cork", 1, 0); } - if (!arguments[0].isCallable(globalObject.vm())) { + if (!arguments[0].isCallable()) { return globalObject.throwInvalidArgumentTypeValue("cork", "function", arguments[0]); } diff --git a/src/bun.js/bindings/JSValue.zig b/src/bun.js/bindings/JSValue.zig index 32a31f80cc..93f20f7306 100644 --- a/src/bun.js/bindings/JSValue.zig +++ b/src/bun.js/bindings/JSValue.zig @@ -627,7 +627,7 @@ pub const JSValue = enum(i64) { pub fn callNextTick(function: JSValue, global: *JSGlobalObject, args: anytype) void { if (Environment.isDebug) { - bun.assert(function.isCallable(global.vm())); + bun.assert(function.isCallable()); } const num_args = @typeInfo(@TypeOf(args)).array.len; switch (num_args) { @@ -1398,8 +1398,8 @@ pub const JSValue = enum(i64) { return cppFn("asCell", .{this}); } - pub fn isCallable(this: JSValue, vm: *VM) bool { - return cppFn("isCallable", .{ this, vm }); + pub fn isCallable(this: JSValue) bool { + return cppFn("isCallable", .{this}); } pub fn isException(this: JSValue, vm: *VM) bool { @@ -1784,7 +1784,7 @@ pub const JSValue = enum(i64) { return false; const function = this.fastGet(global, BuiltinName.toString) orelse return false; - return function.isCell() and function.isCallable(global.vm()); + return function.isCell() and function.isCallable(); } // TODO: replace calls to this function with `getOptional` @@ -2000,7 +2000,7 @@ pub const JSValue = enum(i64) { pub fn getFunction(this: JSValue, globalThis: *JSGlobalObject, comptime property_name: []const u8) JSError!?JSValue { if (try this.getOptional(globalThis, property_name, JSValue)) |prop| { - if (!prop.isCell() or !prop.isCallable(globalThis.vm())) { + if (!prop.isCell() or !prop.isCallable()) { return globalThis.throwInvalidArguments(property_name ++ " must be a function", .{}); } @@ -2012,7 +2012,7 @@ pub const JSValue = enum(i64) { pub fn getOwnFunction(this: JSValue, globalThis: *JSGlobalObject, comptime property_name: []const u8) JSError!?JSValue { if (getOwnTruthy(this, globalThis, property_name)) |prop| { - if (!prop.isCell() or !prop.isCallable(globalThis.vm())) { + if (!prop.isCell() or !prop.isCallable()) { return globalThis.throwInvalidArguments(property_name ++ " must be a function", .{}); } diff --git a/src/bun.js/bindings/NodeValidator.cpp b/src/bun.js/bindings/NodeValidator.cpp index 408914d761..ba41b196b3 100644 --- a/src/bun.js/bindings/NodeValidator.cpp +++ b/src/bun.js/bindings/NodeValidator.cpp @@ -215,8 +215,8 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_checkRangesOrGetDefault, (JSC::JSGlobalObjec JSC::EncodedJSValue V::validateFunction(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name) { - if (JSC::getCallData(value).type == JSC::CallData::Type::None) { - return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "function"_s, value); + if (!value.isCallable()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, name, "function"_s, value); } return JSValue::encode(jsUndefined()); @@ -230,8 +230,8 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateFunction, (JSC::JSGlobalObject * glo auto value = callFrame->argument(0); auto name = callFrame->argument(1); - if (JSC::getCallData(value).type == JSC::CallData::Type::None) { - return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "function"_s, value); + if (!value.isCallable()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, name, "function"_s, value); } return JSValue::encode(jsUndefined()); } @@ -250,6 +250,14 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateBoolean, (JSC::JSGlobalObject * glob return JSValue::encode(jsUndefined()); } +JSC::EncodedJSValue V::validateBoolean(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name) +{ + if (!value.isBoolean()) { + return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "boolean"_s, value); + } + return JSValue::encode(jsUndefined()); +} + JSC_DEFINE_HOST_FUNCTION(jsFunction_validatePort, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(globalObject); diff --git a/src/bun.js/bindings/NodeValidator.h b/src/bun.js/bindings/NodeValidator.h index 5de8fe915b..3878ce6a88 100644 --- a/src/bun.js/bindings/NodeValidator.h +++ b/src/bun.js/bindings/NodeValidator.h @@ -49,6 +49,7 @@ JSC::EncodedJSValue validateInt32(JSC::ThrowScope& scope, JSC::JSGlobalObject* g JSC::EncodedJSValue validateFunction(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name); JSC::EncodedJSValue validateOneOf(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSValue value, const WTF::Vector& oneOf); JSC::EncodedJSValue validateObject(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name); +JSC::EncodedJSValue validateBoolean(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name); } diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 8ff2219f55..d94a173325 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -65,6 +65,8 @@ class EventTarget; extern "C" void Bun__reportError(JSC__JSGlobalObject*, JSC__JSValue); extern "C" void Bun__reportUnhandledError(JSC__JSGlobalObject*, JSC::EncodedJSValue); +extern "C" bool Bun__VirtualMachine__isShuttingDown(void* /* BunVM */); + #if OS(WINDOWS) #include extern "C" uv_loop_t* Bun__ZigGlobalObject__uvLoop(void* /* BunVM */); @@ -86,6 +88,11 @@ public: // Move this to the front for better cache locality. void* m_bunVM; + bool isShuttingDown() const + { + return Bun__VirtualMachine__isShuttingDown(m_bunVM); + } + static const JSC::ClassInfo s_info; static const JSC::GlobalObjectMethodTable s_globalObjectMethodTable; diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 28361ac008..1009d36849 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -3655,7 +3655,7 @@ void JSC__JSValue__forEach(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, voi }); } -bool JSC__JSValue__isCallable(JSC__JSValue JSValue0, JSC__VM* arg1) +bool JSC__JSValue__isCallable(JSC__JSValue JSValue0) { return JSC::JSValue::decode(JSValue0).isCallable(); } diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 2f751c4448..37f17850fb 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -390,6 +390,9 @@ extern "C" size_t Bun__encoding__byteLengthUTF16AsUTF8(const UChar* ptr, size_t extern "C" int64_t Bun__encoding__constructFromLatin1(void*, const unsigned char* ptr, size_t len, Encoding encoding); extern "C" int64_t Bun__encoding__constructFromUTF16(void*, const UChar* ptr, size_t len, Encoding encoding); +extern "C" void Bun__EventLoop__runCallback1(JSC::JSGlobalObject* global, JSC::EncodedJSValue callback, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue arg1); +extern "C" void Bun__EventLoop__runCallback2(JSC::JSGlobalObject* global, JSC::EncodedJSValue callback, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue arg1, JSC::EncodedJSValue arg2); + /// @note throws a JS exception and returns false if a stack overflow occurs template bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSC::JSValue v1, JSC::JSValue v2, JSC::MarkedArgumentBuffer&, Vector, 16>& stack, JSC::ThrowScope* scope, bool addToStack); diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index b7af9c4d9e..23de469423 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -342,7 +342,7 @@ CPP_DECL bool JSC__JSValue__isAnyError(JSC__JSValue JSValue0); CPP_DECL bool JSC__JSValue__isAnyInt(JSC__JSValue JSValue0); CPP_DECL bool JSC__JSValue__isBigInt(JSC__JSValue JSValue0); CPP_DECL bool JSC__JSValue__isBigInt32(JSC__JSValue JSValue0); -CPP_DECL bool JSC__JSValue__isCallable(JSC__JSValue JSValue0, JSC__VM* arg1); +CPP_DECL bool JSC__JSValue__isCallable(JSC__JSValue JSValue0); CPP_DECL bool JSC__JSValue__isClass(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); CPP_DECL bool JSC__JSValue__isConstructor(JSC__JSValue JSValue0); CPP_DECL bool JSC__JSValue__isCustomGetterSetter(JSC__JSValue JSValue0); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index ad07e315c7..54b73959fe 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -166,7 +166,7 @@ pub extern fn JSC__JSValue__isAnyError(JSValue0: JSC__JSValue) bool; pub extern fn JSC__JSValue__isAnyInt(JSValue0: JSC__JSValue) bool; pub extern fn JSC__JSValue__isBigInt(JSValue0: JSC__JSValue) bool; pub extern fn JSC__JSValue__isBigInt32(JSValue0: JSC__JSValue) bool; -pub extern fn JSC__JSValue__isCallable(JSValue0: JSC__JSValue, arg1: *bindings.VM) bool; +pub extern fn JSC__JSValue__isCallable(JSValue0: JSC__JSValue) bool; pub extern fn JSC__JSValue__isClass(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject) bool; pub extern fn JSC__JSValue__isConstructor(JSValue0: JSC__JSValue) bool; pub extern fn JSC__JSValue__isCustomGetterSetter(JSValue0: JSC__JSValue) bool; diff --git a/src/bun.js/bindings/ncrypto.h b/src/bun.js/bindings/ncrypto.h index 81085497d4..c5d9d3f16b 100644 --- a/src/bun.js/bindings/ncrypto.h +++ b/src/bun.js/bindings/ncrypto.h @@ -504,7 +504,7 @@ public: return static_cast(data_); } - inline std::span span() const { return { get(), len_ }; } + inline std::span span() const { return { get(), len_ }; } inline size_t size() const noexcept { return len_; } void reset(void* data = nullptr, size_t len = 0); void reset(const Buffer& buffer); diff --git a/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp b/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp new file mode 100644 index 0000000000..72fadb6cae --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp @@ -0,0 +1,510 @@ +#include "CryptoPrimes.h" +#include "KeyObject.h" +#include "ErrorCode.h" +#include "helpers.h" +#include "CryptoUtil.h" +#include "NodeValidator.h" + +CheckPrimeJobCtx::CheckPrimeJobCtx(ncrypto::BignumPointer candidate, int32_t checks, JSValue callback) + : m_candidate(WTFMove(candidate)) + , m_checks(checks) + , m_callback(callback) +{ + gcProtect(m_callback); +} + +CheckPrimeJobCtx::~CheckPrimeJobCtx() +{ + gcUnprotect(m_callback); +} + +extern "C" void Bun__CheckPrimeJobCtx__runTask(CheckPrimeJobCtx* ctx, JSGlobalObject* lexicalGlobalObject) +{ + ctx->runTask(lexicalGlobalObject); +} +void CheckPrimeJobCtx::runTask(JSGlobalObject* lexicalGlobalObject) +{ + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + auto res = m_candidate.isPrime(m_checks, [globalObject](int32_t a, int32_t b) -> bool { + // TODO(dylan-conway): needs to be thread safe!!!!!!!! + return !globalObject->isShuttingDown(); + }); + + m_result = res != 0; +} + +extern "C" void Bun__CheckPrimeJobCtx__runFromJS(CheckPrimeJobCtx* ctx, JSGlobalObject* lexicalGlobalObject) +{ + ctx->runFromJS(lexicalGlobalObject); +} +void CheckPrimeJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject) +{ + Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(jsBoolean(m_result))); +} + +extern "C" void Bun__CheckPrimeJobCtx__deinit(CheckPrimeJobCtx* ctx) +{ + ctx->deinit(); +} +void CheckPrimeJobCtx::deinit() +{ + delete this; +} + +extern "C" CheckPrimeJob* Bun__CheckPrimeJob__create(JSGlobalObject*, CheckPrimeJobCtx*); +CheckPrimeJob* CheckPrimeJob::create(JSGlobalObject* globalObject, ncrypto::BignumPointer candidate, int32_t checks, JSValue callback) +{ + CheckPrimeJobCtx* ctx = new CheckPrimeJobCtx(WTFMove(candidate), checks, callback); + return Bun__CheckPrimeJob__create(globalObject, ctx); +} + +extern "C" void Bun__CheckPrimeJob__schedule(CheckPrimeJob*); +void CheckPrimeJob::schedule() +{ + Bun__CheckPrimeJob__schedule(this); +} + +extern "C" void Bun__CheckPrimeJob__createAndSchedule(JSGlobalObject*, CheckPrimeJobCtx*); +void CheckPrimeJob::createAndSchedule(JSGlobalObject* globalObject, ncrypto::BignumPointer candidate, int32_t checks, JSValue callback) +{ + CheckPrimeJobCtx* ctx = new CheckPrimeJobCtx(WTFMove(candidate), checks, callback); + return Bun__CheckPrimeJob__createAndSchedule(globalObject, ctx); +} + +JSC_DEFINE_HOST_FUNCTION(jsCheckPrimeSync, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue candidateValue = callFrame->argument(0); + + if (candidateValue.isBigInt()) { + candidateValue = unsignedBigIntToBuffer(lexicalGlobalObject, scope, candidateValue, "candidate"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + + auto* candidateView = getArrayBufferOrView(lexicalGlobalObject, scope, candidateValue, "candidate"_s, jsUndefined()); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue optionsValue = callFrame->argument(1); + if (!optionsValue.isUndefined()) { + V::validateObject(scope, lexicalGlobalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + + int32_t checks = 0; + if (optionsValue.isObject()) { + JSObject* options = optionsValue.getObject(); + JSValue checksValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "checks"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (!checksValue.isUndefined()) { + V::validateInt32(scope, lexicalGlobalObject, checksValue, "options.checks"_s, jsNumber(0), jsUndefined(), &checks); + RETURN_IF_EXCEPTION(scope, {}); + } + } + + ncrypto::BignumPointer candidate = ncrypto::BignumPointer(reinterpret_cast(candidateView->vector()), candidateView->byteLength()); + if (!candidate) { + throwCryptoError(lexicalGlobalObject, scope, ERR_get_error(), "BignumPointer"_s); + return JSValue::encode({}); + } + + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + auto res = candidate.isPrime(checks, [globalObject](int32_t a, int32_t b) -> bool { + return !globalObject->isShuttingDown(); + }); + + return JSValue::encode(jsBoolean(res != 0)); +} + +JSC_DEFINE_HOST_FUNCTION(jsCheckPrime, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue candidateValue = callFrame->argument(0); + if (candidateValue.isBigInt()) { + candidateValue = unsignedBigIntToBuffer(lexicalGlobalObject, scope, candidateValue, "candidate"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } + + auto* candidateView = jsDynamicCast(candidateValue); + if (!candidateView) { + return ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "candidate"_s, "ArrayBuffer, TypedArray, Buffer, DataView, or bigint"_s, candidateValue); + } + + JSValue optionsValue = callFrame->argument(1); + JSValue callback = callFrame->argument(2); + if (optionsValue.isCallable()) { + callback = optionsValue; + optionsValue = jsUndefined(); + } + + V::validateFunction(scope, lexicalGlobalObject, callback, "callback"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + if (!optionsValue.isUndefined()) { + V::validateObject(scope, lexicalGlobalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } + + int32_t checks = 0; + if (optionsValue.isObject()) { + JSObject* options = optionsValue.getObject(); + JSValue checksValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "checks"_s)); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + if (!checksValue.isUndefined()) { + V::validateInt32(scope, lexicalGlobalObject, checksValue, "options.checks"_s, jsNumber(0), jsUndefined(), &checks); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } + } + + ncrypto::BignumPointer candidate = ncrypto::BignumPointer(reinterpret_cast(candidateView->vector()), candidateView->byteLength()); + if (!candidate) { + throwCryptoError(lexicalGlobalObject, scope, ERR_get_error(), "BignumPointer"_s); + return JSValue::encode({}); + } + + CheckPrimeJob::createAndSchedule(lexicalGlobalObject, WTFMove(candidate), checks, callback); + + return JSValue::encode(jsUndefined()); +} + +GeneratePrimeJobCtx::GeneratePrimeJobCtx(int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback) + : m_size(size) + , m_safe(safe) + , m_bigint(bigint) + , m_add(WTFMove(add)) + , m_rem(WTFMove(rem)) + , m_prime(WTFMove(prime)) + , m_callback(callback) +{ + gcProtect(m_callback); +} + +GeneratePrimeJobCtx::~GeneratePrimeJobCtx() +{ + gcUnprotect(m_callback); +} + +extern "C" void Bun__GeneratePrimeJobCtx__runTask(GeneratePrimeJobCtx* ctx, JSGlobalObject* lexicalGlobalObject) +{ + ctx->runTask(lexicalGlobalObject); +} +void GeneratePrimeJobCtx::runTask(JSGlobalObject* lexicalGlobalObject) +{ + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + m_prime.generate({ .bits = m_size, .safe = m_safe, .add = m_add, .rem = m_rem }, [globalObject](int32_t a, int32_t b) -> bool { + // TODO(dylan-conway): needs to be thread safe!!!!!!!! + return !globalObject->isShuttingDown(); + }); +} + +extern "C" void Bun__GeneratePrimeJobCtx__runFromJS(GeneratePrimeJobCtx* ctx, JSGlobalObject* lexicalGlobalObject) +{ + ctx->runFromJS(lexicalGlobalObject); +} +void GeneratePrimeJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (m_bigint) { + ncrypto::DataPointer primeHex = m_prime.toHex(); + if (!primeHex) { + JSObject* err = createOutOfMemoryError(lexicalGlobalObject, "could not generate prime"_s); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + return; + } + + JSValue result = JSBigInt::parseInt(lexicalGlobalObject, vm, primeHex.span(), 16, JSBigInt::ErrorParseMode::IgnoreExceptions, JSBigInt::ParseIntSign::Unsigned); + if (result.isEmpty()) { + JSObject* err = createError(lexicalGlobalObject, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "could not generate prime"_s); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + return; + } + + Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(result)); + return; + } + + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + JSC::JSUint8Array* result = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), m_prime.byteLength()); + if (!result) { + JSObject* err = createOutOfMemoryError(lexicalGlobalObject, "could not generate prime"_s); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + return; + } + + ncrypto::BignumPointer::EncodePaddedInto(m_prime.get(), reinterpret_cast(result->vector()), result->byteLength()); + + Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(result)); +} + +extern "C" void Bun__GeneratePrimeJobCtx__deinit(GeneratePrimeJobCtx* ctx) +{ + ctx->deinit(); +} +void GeneratePrimeJobCtx::deinit() +{ + delete this; +} + +extern "C" GeneratePrimeJob* Bun__GeneratePrimeJob__create(JSGlobalObject*, GeneratePrimeJobCtx*); +GeneratePrimeJob* GeneratePrimeJob::create(JSGlobalObject* globalObject, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback) +{ + GeneratePrimeJobCtx* ctx = new GeneratePrimeJobCtx(size, safe, WTFMove(prime), WTFMove(add), WTFMove(rem), bigint, callback); + return Bun__GeneratePrimeJob__create(globalObject, ctx); +} + +extern "C" void Bun__GeneratePrimeJob__schedule(GeneratePrimeJob*); +void GeneratePrimeJob::schedule() +{ + Bun__GeneratePrimeJob__schedule(this); +} + +extern "C" void Bun__GeneratePrimeJob__createAndSchedule(JSGlobalObject*, GeneratePrimeJobCtx*); +void GeneratePrimeJob::createAndSchedule(JSGlobalObject* globalObject, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback) +{ + GeneratePrimeJobCtx* ctx = new GeneratePrimeJobCtx(size, safe, WTFMove(prime), WTFMove(add), WTFMove(rem), bigint, callback); + Bun__GeneratePrimeJob__createAndSchedule(globalObject, ctx); +} + +JSC_DEFINE_HOST_FUNCTION(jsGeneratePrime, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue sizeValue = callFrame->argument(0); + int32_t size = 0; + V::validateInt32(scope, lexicalGlobalObject, sizeValue, "size"_s, jsNumber(1), jsUndefined(), &size); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue optionsValue = callFrame->argument(1); + JSValue callback = callFrame->argument(2); + if (optionsValue.isCallable()) { + callback = optionsValue; + optionsValue = jsUndefined(); + } + V::validateFunction(scope, lexicalGlobalObject, callback, "callback"_s); + RETURN_IF_EXCEPTION(scope, {}); + + if (!optionsValue.isUndefined()) { + V::validateObject(scope, lexicalGlobalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + + bool safe = false; + bool bigint = false; + JSValue addValue = jsUndefined(); + JSValue remValue = jsUndefined(); + if (optionsValue.isObject()) { + JSObject* options = optionsValue.getObject(); + + JSValue safeValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "safe"_s)); + RETURN_IF_EXCEPTION(scope, {}); + JSValue bigintValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "bigint"_s)); + RETURN_IF_EXCEPTION(scope, {}); + addValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "add"_s)); + RETURN_IF_EXCEPTION(scope, {}); + remValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "rem"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (!safeValue.isUndefined()) { + V::validateBoolean(scope, lexicalGlobalObject, safeValue, "options.safe"_s); + RETURN_IF_EXCEPTION(scope, {}); + safe = safeValue.asBoolean(); + } + + if (!bigintValue.isUndefined()) { + V::validateBoolean(scope, lexicalGlobalObject, bigintValue, "options.bigint"_s); + RETURN_IF_EXCEPTION(scope, {}); + bigint = bigintValue.asBoolean(); + } + } + + ncrypto::ClearErrorOnReturn clear; + + ncrypto::BignumPointer add; + if (!addValue.isUndefined()) { + if (addValue.isBigInt()) { + addValue = unsignedBigIntToBuffer(lexicalGlobalObject, scope, addValue, "options.add"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + auto* addView = jsDynamicCast(addValue); + if (!addView) { + return ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "options.add"_s, "ArrayBuffer, Buffer, TypedArray, DataView, or bigint"_s, addValue); + } + add.reset(reinterpret_cast(addView->vector()), addView->byteLength()); + if (!add) { + return ERR::CRYPTO_OPERATION_FAILED(scope, lexicalGlobalObject, "could not generate prime"_s); + } + } + + ncrypto::BignumPointer rem; + if (!remValue.isUndefined()) { + if (remValue.isBigInt()) { + remValue = unsignedBigIntToBuffer(lexicalGlobalObject, scope, remValue, "options.rem"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + auto* remView = jsDynamicCast(remValue); + if (!remView) { + return ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "options.rem"_s, "ArrayBuffer, Buffer, TypedArray, DataView, or bigint"_s, remValue); + } + rem.reset(reinterpret_cast(remView->vector()), remView->byteLength()); + if (!rem) { + return ERR::CRYPTO_OPERATION_FAILED(scope, lexicalGlobalObject, "could not generate prime"_s); + } + } + + if (add) { + if (UNLIKELY(ncrypto::BignumPointer::GetBitCount(add.get()) > size)) { + throwError(lexicalGlobalObject, scope, ErrorCode::ERR_OUT_OF_RANGE, "invalid options.add"_s); + return JSValue::encode({}); + } + + if (UNLIKELY(rem && add <= rem)) { + throwError(lexicalGlobalObject, scope, ErrorCode::ERR_OUT_OF_RANGE, "invalid options.rem"_s); + return JSValue::encode({}); + } + } + + ncrypto::BignumPointer prime = ncrypto::BignumPointer::NewSecure(); + if (!prime) { + return ERR::CRYPTO_OPERATION_FAILED(scope, lexicalGlobalObject, "could not generate prime"_s); + } + + GeneratePrimeJob::createAndSchedule(lexicalGlobalObject, size, safe, WTFMove(prime), WTFMove(add), WTFMove(rem), bigint, callback); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsGeneratePrimeSync, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue sizeValue = callFrame->argument(0); + int32_t size = 0; + V::validateInt32(scope, lexicalGlobalObject, sizeValue, "size"_s, jsNumber(1), jsUndefined(), &size); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue optionsValue = callFrame->argument(1); + if (!optionsValue.isUndefined()) { + V::validateObject(scope, lexicalGlobalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + + bool safe = false; + bool bigint = false; + JSValue addValue = jsUndefined(); + JSValue remValue = jsUndefined(); + if (optionsValue.isObject()) { + JSObject* options = optionsValue.getObject(); + + JSValue safeValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "safe"_s)); + RETURN_IF_EXCEPTION(scope, {}); + JSValue bigintValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "bigint"_s)); + RETURN_IF_EXCEPTION(scope, {}); + addValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "add"_s)); + RETURN_IF_EXCEPTION(scope, {}); + remValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "rem"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (!safeValue.isUndefined()) { + V::validateBoolean(scope, lexicalGlobalObject, safeValue, "options.safe"_s); + RETURN_IF_EXCEPTION(scope, {}); + safe = safeValue.asBoolean(); + } + + if (!bigintValue.isUndefined()) { + V::validateBoolean(scope, lexicalGlobalObject, bigintValue, "options.bigint"_s); + RETURN_IF_EXCEPTION(scope, {}); + bigint = bigintValue.asBoolean(); + } + } + + ncrypto::ClearErrorOnReturn clear; + + ncrypto::BignumPointer add; + if (!addValue.isUndefined()) { + if (addValue.isBigInt()) { + addValue = unsignedBigIntToBuffer(lexicalGlobalObject, scope, addValue, "options.add"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + auto* addView = jsDynamicCast(addValue); + if (!addView) { + return ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "options.add"_s, "ArrayBuffer, Buffer, TypedArray, DataView, or bigint"_s, addValue); + } + add.reset(reinterpret_cast(addView->vector()), addView->byteLength()); + if (!add) { + return ERR::CRYPTO_OPERATION_FAILED(scope, lexicalGlobalObject, "could not generate prime"_s); + } + } + + ncrypto::BignumPointer rem; + if (!remValue.isUndefined()) { + if (remValue.isBigInt()) { + remValue = unsignedBigIntToBuffer(lexicalGlobalObject, scope, remValue, "options.rem"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + auto* remView = jsDynamicCast(remValue); + if (!remView) { + return ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "options.rem"_s, "ArrayBuffer, Buffer, TypedArray, DataView, or bigint"_s, remValue); + } + rem.reset(reinterpret_cast(remView->vector()), remView->byteLength()); + if (!rem) { + return ERR::CRYPTO_OPERATION_FAILED(scope, lexicalGlobalObject, "could not generate prime"_s); + } + } + + if (add) { + if (UNLIKELY(ncrypto::BignumPointer::GetBitCount(add.get()) > size)) { + throwError(lexicalGlobalObject, scope, ErrorCode::ERR_OUT_OF_RANGE, "invalid options.add"_s); + return JSValue::encode({}); + } + + if (UNLIKELY(rem && add <= rem)) { + throwError(lexicalGlobalObject, scope, ErrorCode::ERR_OUT_OF_RANGE, "invalid options.rem"_s); + return JSValue::encode({}); + } + } + + ncrypto::BignumPointer prime = ncrypto::BignumPointer::NewSecure(); + if (!prime) { + return ERR::CRYPTO_OPERATION_FAILED(scope, lexicalGlobalObject, "could not generate prime"_s); + } + + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + prime.generate({ .bits = size, .safe = safe, .add = add, .rem = rem }, [globalObject](int32_t a, int32_t b) -> bool { + return !globalObject->isShuttingDown(); + }); + + if (bigint) { + ncrypto::DataPointer primeHex = prime.toHex(); + if (!primeHex) { + throwOutOfMemoryError(lexicalGlobalObject, scope, "could not generate prime"_s); + return JSValue::encode({}); + } + + return JSValue::encode(JSBigInt::parseInt(lexicalGlobalObject, vm, primeHex.span(), 16, JSBigInt::ErrorParseMode::ThrowExceptions, + JSBigInt::ParseIntSign::Unsigned)); + } + + JSC::JSUint8Array* result = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), prime.byteLength()); + if (!result) { + throwOutOfMemoryError(lexicalGlobalObject, scope, "could not generate prime"_s); + return JSValue::encode({}); + } + + ncrypto::BignumPointer::EncodePaddedInto(prime.get(), reinterpret_cast(result->vector()), result->byteLength()); + + return JSValue::encode(result); +} diff --git a/src/bun.js/bindings/node/crypto/CryptoPrimes.h b/src/bun.js/bindings/node/crypto/CryptoPrimes.h new file mode 100644 index 0000000000..8f910cfc00 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoPrimes.h @@ -0,0 +1,73 @@ +#pragma once + +#include "root.h" +#include "helpers.h" +#include "ncrypto.h" + +using namespace JSC; +using namespace Bun; + +struct CheckPrimeJobCtx { + CheckPrimeJobCtx(ncrypto::BignumPointer candidate, int32_t checks, JSValue callback); + ~CheckPrimeJobCtx(); + + void runTask(JSGlobalObject* lexicalGlobalObject); + void runFromJS(JSGlobalObject* lexicalGlobalObject); + void deinit(); + + int32_t m_checks; + JSValue m_callback; + ncrypto::BignumPointer m_candidate; + + bool m_result { false }; + + WTF_MAKE_TZONE_ALLOCATED(CheckPrimeJobCtx); +}; + +extern "C" void Bun__CheckPrimeJobCtx__runTask(CheckPrimeJobCtx*, JSGlobalObject*); +extern "C" void Bun__CheckPrimeJobCtx__runFromJS(CheckPrimeJobCtx*, JSGlobalObject*); +extern "C" void Bun__CheckPrimeJobCtx__deinit(CheckPrimeJobCtx*); + +// Opaque struct created zig land +struct CheckPrimeJob { + static CheckPrimeJob* create(JSGlobalObject*, ncrypto::BignumPointer candidate, int32_t checks, JSValue callback); + static void createAndSchedule(JSGlobalObject* globalObject, ncrypto::BignumPointer candidate, int32_t checks, JSValue callback); + + void schedule(); +}; + +struct GeneratePrimeJobCtx { + GeneratePrimeJobCtx(int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback); + ~GeneratePrimeJobCtx(); + + void runTask(JSGlobalObject* lexicalGlobalObject); + void runFromJS(JSGlobalObject* lexicalGlobalObject); + void deinit(); + + int32_t m_size; + bool m_safe; + bool m_bigint; + JSValue m_callback; + ncrypto::BignumPointer m_add; + ncrypto::BignumPointer m_rem; + ncrypto::BignumPointer m_prime; + + WTF_MAKE_TZONE_ALLOCATED(GeneratePrimeJobCtx); +}; + +extern "C" void Bun__GeneratePrimeJobCtx__runTask(GeneratePrimeJobCtx*, JSGlobalObject*); +extern "C" void Bun__GeneratePrimeJobCtx__runFromJS(GeneratePrimeJobCtx*, JSGlobalObject*); +extern "C" void Bun__GeneratePrimeJobCtx__deinit(GeneratePrimeJobCtx*); + +// Opaque struct created zig land +struct GeneratePrimeJob { + static GeneratePrimeJob* create(JSGlobalObject*, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback); + static void createAndSchedule(JSGlobalObject*, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback); + + void schedule(); +}; + +JSC_DECLARE_HOST_FUNCTION(jsCheckPrime); +JSC_DECLARE_HOST_FUNCTION(jsCheckPrimeSync); +JSC_DECLARE_HOST_FUNCTION(jsGeneratePrime); +JSC_DECLARE_HOST_FUNCTION(jsGeneratePrimeSync); diff --git a/src/bun.js/bindings/node/crypto/CryptoUtil.cpp b/src/bun.js/bindings/node/crypto/CryptoUtil.cpp index 0c50cf8d9d..70e3d04c89 100644 --- a/src/bun.js/bindings/node/crypto/CryptoUtil.cpp +++ b/src/bun.js/bindings/node/crypto/CryptoUtil.cpp @@ -92,6 +92,36 @@ EncodedJSValue encode(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, st } +JSValue unsignedBigIntToBuffer(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, JSValue bigIntValue, ASCIILiteral name) +{ + ASSERT(bigIntValue.isBigInt()); + auto& vm = lexicalGlobalObject->vm(); + + JSBigInt* bigInt = bigIntValue.asHeapBigInt(); + + if (bigInt->sign()) { + ERR::OUT_OF_RANGE(scope, lexicalGlobalObject, name, ">= 0"_s, bigIntValue); + return {}; + } + + WTF::String hex = bigInt->toString(lexicalGlobalObject, 16); + RETURN_IF_EXCEPTION(scope, {}); + + JSString* paddedHex = hex.length() % 2 + ? jsString(vm, tryMakeString('0', hex)) + : jsString(vm, hex); + if (UNLIKELY(!paddedHex)) { + throwOutOfMemoryError(lexicalGlobalObject, scope); + return {}; + } + + GCOwnedDataScope paddedView = paddedHex->view(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue buffer = JSValue::decode(WebCore::constructFromEncoding(lexicalGlobalObject, paddedView, BufferEncodingType::hex)); + RELEASE_AND_RETURN(scope, buffer); +} + WebCore::BufferEncodingType getEncodingDefaultBuffer(JSGlobalObject* globalObject, ThrowScope& scope, JSValue encodingValue) { BufferEncodingType res = BufferEncodingType::buffer; @@ -252,8 +282,7 @@ std::optional passphraseFromBufferSource(JSC::JSGlobalObje return std::nullopt; } -// Throws a crypto error with optional OpenSSL error details -void throwCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, unsigned long err, const char* message) +JSValue createCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, uint32_t err, const char* message) { JSC::VM& vm = globalObject->vm(); @@ -265,15 +294,15 @@ void throwCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, unsi } WTF::String errorMessage = WTF::String::fromUTF8(message); - RETURN_IF_EXCEPTION(scope, void()); + RETURN_IF_EXCEPTION(scope, {}); // Create error object with the message JSC::JSObject* errorObject = createError(globalObject, errorMessage); - RETURN_IF_EXCEPTION(scope, void()); + RETURN_IF_EXCEPTION(scope, {}); PutPropertySlot messageSlot(errorObject, false); errorObject->put(errorObject, globalObject, Identifier::fromString(vm, "message"_s), jsString(vm, errorMessage), messageSlot); - RETURN_IF_EXCEPTION(scope, void()); + RETURN_IF_EXCEPTION(scope, {}); ncrypto::CryptoErrorList errorStack; errorStack.capture(); @@ -290,7 +319,7 @@ void throwCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, unsi WTF::String libString = WTF::String::fromUTF8(lib); PutPropertySlot slot(errorObject, false); errorObject->put(errorObject, globalObject, Identifier::fromString(vm, "library"_s), jsString(vm, libString), slot); - RETURN_IF_EXCEPTION(scope, void()); + RETURN_IF_EXCEPTION(scope, {}); } // Add function info if available @@ -299,7 +328,7 @@ void throwCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, unsi PutPropertySlot slot(errorObject, false); errorObject->put(errorObject, globalObject, Identifier::fromString(vm, "function"_s), jsString(vm, funcString), slot); - RETURN_IF_EXCEPTION(scope, void()); + RETURN_IF_EXCEPTION(scope, {}); } // Add reason info if available @@ -308,7 +337,7 @@ void throwCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, unsi PutPropertySlot reasonSlot(errorObject, false); errorObject->put(errorObject, globalObject, Identifier::fromString(vm, "reason"_s), jsString(vm, reasonString), reasonSlot); - RETURN_IF_EXCEPTION(scope, void()); + RETURN_IF_EXCEPTION(scope, {}); // Convert reason to error code (e.g. "this error" -> "ERR_OSSL_THIS_ERROR") String upperReason = reasonString.convertToASCIIUppercase(); @@ -316,7 +345,7 @@ void throwCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, unsi PutPropertySlot codeSlot(errorObject, false); errorObject->put(errorObject, globalObject, Identifier::fromString(vm, "code"_s), jsString(vm, code), codeSlot); - RETURN_IF_EXCEPTION(scope, void()); + RETURN_IF_EXCEPTION(scope, {}); } } @@ -324,16 +353,28 @@ void throwCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, unsi if (errorStack.size() > 0) { PutPropertySlot stackSlot(errorObject, false); auto arr = JSC::constructEmptyArray(globalObject, nullptr, errorStack.size()); - RETURN_IF_EXCEPTION(scope, void()); + RETURN_IF_EXCEPTION(scope, {}); for (int32_t i = 0; i < errorStack.size(); i++) { WTF::String error = errorStack.pop_back().value(); arr->putDirectIndex(globalObject, i, jsString(vm, error)); } errorObject->put(errorObject, globalObject, Identifier::fromString(vm, "opensslErrorStack"_s), arr, stackSlot); - RETURN_IF_EXCEPTION(scope, void()); + RETURN_IF_EXCEPTION(scope, {}); } - // Throw the decorated error + return errorObject; +} + +extern "C" EncodedJSValue Bun__NodeCrypto__createCryptoError(JSC::JSGlobalObject* globalObject, uint32_t err, const char* message) +{ + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + return JSValue::encode(createCryptoError(globalObject, scope, err, message)); +} + +void throwCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, uint32_t err, const char* message) +{ + JSValue errorObject = createCryptoError(globalObject, scope, err, message); + RETURN_IF_EXCEPTION(scope, void()); throwException(globalObject, scope, errorObject); } diff --git a/src/bun.js/bindings/node/crypto/CryptoUtil.h b/src/bun.js/bindings/node/crypto/CryptoUtil.h index 4ed97eb1a2..b770e2e130 100644 --- a/src/bun.js/bindings/node/crypto/CryptoUtil.h +++ b/src/bun.js/bindings/node/crypto/CryptoUtil.h @@ -37,13 +37,14 @@ EncodedJSValue encode(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, st }; // void CheckThrow(JSC::JSGlobalObject* globalObject, SignBase::Error error); +JSC::JSValue unsignedBigIntToBuffer(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue bigIntValue, ASCIILiteral name); WebCore::BufferEncodingType getEncodingDefaultBuffer(JSGlobalObject* globalObject, ThrowScope& scope, JSValue encodingValue); std::optional keyFromString(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, const WTF::StringView& keyView, JSValue passphraseValue); ncrypto::EVPKeyPointer::PKFormatType parseKeyFormat(JSC::JSGlobalObject* globalObject, JSValue formatValue, WTF::ASCIILiteral optionName, std::optional defaultFormat = std::nullopt); std::optional parseKeyType(JSC::JSGlobalObject* globalObject, JSValue typeValue, bool required, WTF::StringView keyType, std::optional isPublic, WTF::ASCIILiteral optionName); bool isArrayBufferOrView(JSValue value); std::optional passphraseFromBufferSource(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSValue input); -void throwCryptoError(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, unsigned long err, const char* message = nullptr); +void throwCryptoError(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, uint32_t err, const char* message = nullptr); void throwCryptoOperationFailed(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope); std::optional getIntOption(JSC::JSGlobalObject* globalObject, JSValue options, WTF::ASCIILiteral name); int32_t getPadding(JSC::JSGlobalObject* globalObject, JSValue options, const ncrypto::EVPKeyPointer& pkey); diff --git a/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp b/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp index b5cdd36de0..f97deccfe0 100644 --- a/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp +++ b/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp @@ -44,6 +44,7 @@ #include "JSVerify.h" #include "JSHmac.h" #include "JSHash.h" +#include "CryptoPrimes.h" using namespace JSC; using namespace Bun; @@ -414,6 +415,15 @@ JSValue createNodeCryptoBinding(Zig::GlobalObject* globalObject) obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "DiffieHellmanGroup"_s)), globalObject->m_JSDiffieHellmanGroupClassStructure.constructor(globalObject)); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "generatePrime"_s)), + JSFunction::create(vm, globalObject, 3, "generatePrime"_s, jsGeneratePrime, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "generatePrimeSync"_s)), + JSFunction::create(vm, globalObject, 2, "generatePrimeSync"_s, jsGeneratePrimeSync, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "checkPrime"_s)), + JSFunction::create(vm, globalObject, 3, "checkPrime"_s, jsCheckPrime, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "checkPrimeSync"_s)), + JSFunction::create(vm, globalObject, 2, "checkPrimeSync"_s, jsCheckPrimeSync, ImplementationVisibility::Public, NoIntrinsic), 0); + return obj; } diff --git a/src/bun.js/bindings/node/crypto/node_crypto_binding.h b/src/bun.js/bindings/node/crypto/node_crypto_binding.h index 924ba9223f..6aac413975 100644 --- a/src/bun.js/bindings/node/crypto/node_crypto_binding.h +++ b/src/bun.js/bindings/node/crypto/node_crypto_binding.h @@ -3,6 +3,10 @@ #include "root.h" #include "helpers.h" +#include "ncrypto.h" + +using namespace Bun; +using namespace JSC; namespace WebCore { diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index f54a763841..b418729795 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -963,6 +963,23 @@ pub const EventLoop = struct { globalObject.reportActiveExceptionAsUnhandled(err); } + fn externRunCallback1(global: *JSC.JSGlobalObject, callback: JSC.JSValue, thisValue: JSC.JSValue, arg0: JSC.JSValue) callconv(.c) void { + const vm = global.bunVM(); + var loop = vm.eventLoop(); + loop.runCallback(callback, global, thisValue, &.{arg0}); + } + + fn externRunCallback2(global: *JSC.JSGlobalObject, callback: JSC.JSValue, thisValue: JSC.JSValue, arg0: JSC.JSValue, arg1: JSC.JSValue) callconv(.c) void { + const vm = global.bunVM(); + var loop = vm.eventLoop(); + loop.runCallback(callback, global, thisValue, &.{ arg0, arg1 }); + } + + comptime { + @export(&externRunCallback1, .{ .name = "Bun__EventLoop__runCallback1" }); + @export(&externRunCallback2, .{ .name = "Bun__EventLoop__runCallback2" }); + } + pub fn runCallbackWithResult(this: *EventLoop, callback: JSC.JSValue, globalObject: *JSC.JSGlobalObject, thisValue: JSC.JSValue, arguments: []const JSC.JSValue) JSC.JSValue { this.enter(); defer this.exit(); diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 4b046ee5f0..d704a5a00a 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -946,6 +946,10 @@ pub const VirtualMachine = struct { return this.debugger != null; } + pub export fn Bun__VirtualMachine__isShuttingDown(this: *const VirtualMachine) callconv(.C) bool { + return this.isShuttingDown(); + } + pub fn isShuttingDown(this: *const VirtualMachine) bool { return this.is_shutting_down; } diff --git a/src/bun.js/node/node_crypto_binding.zig b/src/bun.js/node/node_crypto_binding.zig index 37acb601a9..296edf8d3f 100644 --- a/src/bun.js/node/node_crypto_binding.zig +++ b/src/bun.js/node/node_crypto_binding.zig @@ -17,6 +17,101 @@ const JSError = bun.JSError; const String = bun.String; const UUID = bun.UUID; +fn ExternCryptoJob( + comptime name: []const u8, + comptime externRunTask: fn (*anyopaque, *JSGlobalObject) callconv(.c) void, + comptime externRunFromJS: fn (*anyopaque, *JSGlobalObject) callconv(.c) void, + comptime externDeinit: fn (*anyopaque) callconv(.c) void, +) type { + return struct { + vm: *JSC.VirtualMachine, + task: JSC.WorkPoolTask, + any_task: JSC.AnyTask, + + ctx: *anyopaque, + + pub fn create(global: *JSGlobalObject, ctx: *anyopaque) callconv(.c) *@This() { + const vm = global.bunVM(); + const job = bun.new(@This(), .{ + .vm = vm, + .task = .{ + .callback = &runTask, + }, + .any_task = undefined, + .ctx = ctx, + }); + job.any_task = JSC.AnyTask.New(@This(), &runFromJS).init(job); + return job; + } + + pub fn createAndSchedule(global: *JSGlobalObject, ctx: *anyopaque) callconv(.c) void { + var job = create(global, ctx); + job.schedule(); + } + + pub fn runTask(task: *JSC.WorkPoolTask) void { + const job: *@This() = @fieldParentPtr("task", task); + var vm = job.vm; + defer vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(job.any_task.task())); + + externRunTask(job.ctx, vm.global); + } + + pub fn runFromJS(this: *@This()) void { + defer this.deinit(); + const vm = this.vm; + + if (vm.isShuttingDown()) { + return; + } + + externRunFromJS(this.ctx, vm.global); + } + + fn deinit(this: *@This()) void { + externDeinit(this.ctx); + bun.destroy(this); + } + + pub fn schedule(this: *@This()) callconv(.c) void { + JSC.WorkPool.schedule(&this.task); + } + + comptime { + @export(&create, .{ .name = "Bun__" ++ name ++ "__create" }); + @export(&schedule, .{ .name = "Bun__" ++ name ++ "__schedule" }); + @export(&createAndSchedule, .{ .name = "Bun__" ++ name ++ "__createAndSchedule" }); + } + }; +} + +extern fn Bun__CheckPrimeJobCtx__runTask(ctx: *anyopaque, global: *JSGlobalObject) void; +extern fn Bun__CheckPrimeJobCtx__runFromJS(ctx: *anyopaque, global: *JSGlobalObject) void; +extern fn Bun__CheckPrimeJobCtx__deinit(ctx: *anyopaque) void; + +const CheckPrimeJob = ExternCryptoJob( + "CheckPrimeJob", + Bun__CheckPrimeJobCtx__runTask, + Bun__CheckPrimeJobCtx__runFromJS, + Bun__CheckPrimeJobCtx__deinit, +); + +extern fn Bun__GeneratePrimeJobCtx__runTask(ctx: *anyopaque, global: *JSGlobalObject) void; +extern fn Bun__GeneratePrimeJobCtx__runFromJS(ctx: *anyopaque, global: *JSGlobalObject) void; +extern fn Bun__GeneratePrimeJobCtx__deinit(ctx: *anyopaque) void; + +const GeneratePrimeJob = ExternCryptoJob( + "GeneratePrimeJob", + Bun__GeneratePrimeJobCtx__runTask, + Bun__GeneratePrimeJobCtx__runFromJS, + Bun__GeneratePrimeJobCtx__deinit, +); + +comptime { + _ = CheckPrimeJob; + _ = GeneratePrimeJob; +} + const random = struct { const max_possible_length = @min(JSC.ArrayBuffer.max_size, std.math.maxInt(i32)); const max_range = 0xffff_ffff_ffff; @@ -25,7 +120,7 @@ const random = struct { var min_value, var max_value, var callback = callFrame.argumentsAsArray(3); var min_specified = true; - if (max_value.isUndefined() or max_value.isFunction()) { + if (max_value.isUndefined() or max_value.isCallable()) { callback = max_value; max_value = min_value; min_value = JSValue.jsNumber(0); @@ -128,10 +223,8 @@ const random = struct { task: JSC.WorkPoolTask, any_task: JSC.AnyTask, - promise: JSC.JSPromise.Strong, - poll: bun.Async.KeepAlive = .{}, - - value: JSC.Strong, + callback: JSValue, + value: JSValue, bytes: [*]u8, offset: u32, length: usize, @@ -151,14 +244,12 @@ const random = struct { return; } - const global = this.vm.global; - const promise = this.promise.swap(); - - promise.resolve(global, this.value.swap()); + vm.eventLoop().runCallback(this.callback, vm.global, .undefined, &.{ .null, this.value }); } - pub fn create(global: *JSGlobalObject, value: JSValue, bytes: [*]u8, offset: u32, length: usize) *Job { + pub fn create(global: *JSGlobalObject, value: JSValue, bytes: [*]u8, offset: u32, length: usize, callback: JSValue) *Job { const vm = global.bunVM(); + const job = bun.new(Job, .{ .vm = vm, .task = .{ @@ -166,22 +257,25 @@ const random = struct { }, .any_task = undefined, - .promise = JSC.JSPromise.Strong.init(global), - - .value = JSC.Strong.create(value, global), + .callback = callback, + .value = value, .bytes = bytes, .offset = offset, .length = length, }); - + job.callback.protect(); + job.value.protect(); job.any_task = JSC.AnyTask.New(Job, &Job.runFromJS).init(job); - job.poll.ref(vm); - JSC.WorkPool.schedule(&job.task); return job; } + fn schedule(this: *Job) void { + JSC.WorkPool.schedule(&this.task); + } + fn deinit(this: *Job) void { - this.poll.unref(this.vm); + this.value.unprotect(); + this.callback.unprotect(); bun.destroy(this); } }; @@ -202,9 +296,9 @@ const random = struct { return result; } - const job = Job.create(global, result, bytes.ptr, 0, size); - - return job.promise.value(); + const job = Job.create(global, result, bytes.ptr, 0, size, callback); + job.schedule(); + return .undefined; } fn randomFillSync(global: *JSGlobalObject, callFrame: *JSC.CallFrame) JSError!JSValue { @@ -238,7 +332,7 @@ const random = struct { } fn randomFill(global: *JSGlobalObject, callFrame: *JSC.CallFrame) JSError!JSValue { - const buf_value, const offset_value, const size_value, const callback = + const buf_value, var offset_value, var size_value, var callback = callFrame.argumentsAsArray(4); const buf = buf_value.asArrayBuffer(global) orelse { @@ -247,9 +341,19 @@ const random = struct { const element_size = buf.bytesPerElement() orelse 1; - _ = try validators.validateFunction(global, "callback", callback); - - const offset = try assertOffset(global, offset_value, element_size, buf.byte_len); + var offset: u32 = 0; + if (offset_value.isCallable()) { + callback = offset_value; + offset = try assertOffset(global, JSValue.jsNumber(0), element_size, buf.byte_len); + size_value = JSValue.jsNumber(buf.len); + } else if (size_value.isCallable()) { + callback = size_value; + offset = try assertOffset(global, offset_value, element_size, buf.byte_len); + size_value = JSValue.jsNumber(buf.len - offset); + } else { + _ = try validators.validateFunction(global, "callback", callback); + offset = try assertOffset(global, offset_value, element_size, buf.byte_len); + } const size = if (size_value.isUndefined()) buf.byte_len - offset @@ -257,12 +361,13 @@ const random = struct { try assertSize(global, size_value, element_size, offset, buf.byte_len); if (size == 0) { - return JSC.JSPromise.resolvedPromiseValue(global, callback); + _ = try callback.call(global, .undefined, &.{ .null, JSValue.jsNumber(0) }); + return .undefined; } - const job = Job.create(global, buf_value, buf.slice().ptr, offset, size); - - return job.promise.value(); + const job = Job.create(global, buf_value, buf.slice().ptr, offset, size, callback); + job.schedule(); + return .undefined; } }; diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index c3363fc49d..a895f931bd 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -1734,7 +1734,7 @@ pub const Arguments = struct { const big_int = brk: { if (arguments.next()) |next_val| { if (next_val.isObject()) { - if (next_val.isCallable(ctx.vm())) break :brk false; + if (next_val.isCallable()) break :brk false; arguments.eat(); if (try next_val.getBooleanStrict(ctx, "bigint")) |big_int| { @@ -1777,7 +1777,7 @@ pub const Arguments = struct { const big_int = brk: { if (arguments.next()) |next_val| { if (next_val.isObject()) { - if (next_val.isCallable(ctx.vm())) break :brk false; + if (next_val.isCallable()) break :brk false; arguments.eat(); if (try next_val.getBooleanStrict(ctx, "throwIfNoEntry")) |throw_if_no_entry_val| { @@ -1813,7 +1813,7 @@ pub const Arguments = struct { const big_int = brk: { if (arguments.next()) |next_val| { if (next_val.isObject()) { - if (next_val.isCallable(ctx.vm())) break :brk false; + if (next_val.isCallable()) break :brk false; arguments.eat(); if (try next_val.getBooleanStrict(ctx, "bigint")) |big_int| { diff --git a/src/bun.js/node/node_fs_stat_watcher.zig b/src/bun.js/node/node_fs_stat_watcher.zig index 9d49f0496a..b059898636 100644 --- a/src/bun.js/node/node_fs_stat_watcher.zig +++ b/src/bun.js/node/node_fs_stat_watcher.zig @@ -232,10 +232,9 @@ pub const StatWatcher = struct { global_this: JSC.C.JSContextRef, - pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice) bun.JSError!Arguments { - const vm = ctx.vm(); - const path = try PathLike.fromJSWithAllocator(ctx, arguments, bun.default_allocator) orelse { - return ctx.throwInvalidArguments("filename must be a string or TypedArray", .{}); + pub fn fromJS(global: *JSC.JSGlobalObject, arguments: *ArgumentsSlice) bun.JSError!Arguments { + const path = try PathLike.fromJSWithAllocator(global, arguments, bun.default_allocator) orelse { + return global.throwInvalidArguments("filename must be a string or TypedArray", .{}); }; var listener: JSC.JSValue = .zero; @@ -247,28 +246,28 @@ pub const StatWatcher = struct { // options if (options_or_callable.isObject()) { // default true - persistent = (try options_or_callable.getBooleanStrict(ctx, "persistent")) orelse true; + persistent = (try options_or_callable.getBooleanStrict(global, "persistent")) orelse true; // default false - bigint = (try options_or_callable.getBooleanStrict(ctx, "bigint")) orelse false; + bigint = (try options_or_callable.getBooleanStrict(global, "bigint")) orelse false; - if (try options_or_callable.get(ctx, "interval")) |interval_| { + if (try options_or_callable.get(global, "interval")) |interval_| { if (!interval_.isNumber() and !interval_.isAnyInt()) { - return ctx.throwInvalidArguments("interval must be a number", .{}); + return global.throwInvalidArguments("interval must be a number", .{}); } - interval = interval_.coerce(i32, ctx); + interval = interval_.coerce(i32, global); } } } if (arguments.nextEat()) |listener_| { - if (listener_.isCallable(vm)) { - listener = listener_.withAsyncContextIfNeeded(ctx); + if (listener_.isCallable()) { + listener = listener_.withAsyncContextIfNeeded(global); } } if (listener == .zero) { - return ctx.throwInvalidArguments("Expected \"listener\" callback", .{}); + return global.throwInvalidArguments("Expected \"listener\" callback", .{}); } return Arguments{ @@ -277,7 +276,7 @@ pub const StatWatcher = struct { .persistent = persistent, .bigint = bigint, .interval = interval, - .global_this = ctx, + .global_this = global, }; } diff --git a/src/bun.js/node/node_fs_watcher.zig b/src/bun.js/node/node_fs_watcher.zig index 55c2af8c89..425b932909 100644 --- a/src/bun.js/node/node_fs_watcher.zig +++ b/src/bun.js/node/node_fs_watcher.zig @@ -342,7 +342,6 @@ pub const FSWatcher = struct { verbose: bool, pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice) bun.JSError!Arguments { - const vm = ctx.vm(); const path = try PathLike.fromJS(ctx, arguments) orelse { return ctx.throwInvalidArguments("filename must be a string or TypedArray", .{}); }; @@ -397,13 +396,13 @@ pub const FSWatcher = struct { // listener if (arguments.nextEat()) |callable| { - if (!callable.isCell() or !callable.isCallable(vm)) { + if (!callable.isCell() or !callable.isCallable()) { return ctx.throwInvalidArguments("Expected \"listener\" callback to be a function", .{}); } listener = callable; } } else { - if (!options_or_callable.isCell() or !options_or_callable.isCallable(vm)) { + if (!options_or_callable.isCell() or !options_or_callable.isCallable()) { return ctx.throwInvalidArguments("Expected \"listener\" callback to be a function", .{}); } listener = options_or_callable; diff --git a/src/bun.js/node/node_zlib_binding.zig b/src/bun.js/node/node_zlib_binding.zig index 9163f3cc88..80d348b05c 100644 --- a/src/bun.js/node/node_zlib_binding.zig +++ b/src/bun.js/node/node_zlib_binding.zig @@ -134,8 +134,8 @@ pub fn CompressionStream(comptime T: type) type { }; pub fn runFromJSThread(this: *T) void { - const globalThis: *JSC.JSGlobalObject = this.globalThis; - const vm = globalThis.bunVM(); + const global: *JSC.JSGlobalObject = this.globalThis; + const vm = global.bunVM(); this.poll_ref.unref(vm); defer this.deref(); @@ -149,7 +149,7 @@ pub fn CompressionStream(comptime T: type) type { this_value.ensureStillAlive(); - if (!(this.checkError(globalThis, this_value) catch return globalThis.reportActiveExceptionAsUnhandled(error.JSError))) { + if (!(this.checkError(global, this_value) catch return global.reportActiveExceptionAsUnhandled(error.JSError))) { return; } @@ -157,7 +157,8 @@ pub fn CompressionStream(comptime T: type) type { this_value.ensureStillAlive(); const write_callback: JSC.JSValue = T.writeCallbackGetCached(this_value).?; - _ = write_callback.call(globalThis, this_value, &.{}) catch |err| globalThis.reportActiveExceptionAsUnhandled(err); + + vm.eventLoop().runCallback(write_callback, global, this_value, &.{}); if (this.pending_close) _ = this._close(); } diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index 185d83c442..ed88720abf 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -3236,7 +3236,7 @@ pub const Expect = struct { var whatIsTheType: []const u8 = ""; // Checking for function/class should be done before everything else, or it will fail. - if (value.isCallable(globalThis.vm())) { + if (value.isCallable()) { whatIsTheType = "function"; } else if (value.isObject() or value.jsType().isArray() or value.isNull()) { whatIsTheType = "object"; @@ -3674,7 +3674,7 @@ pub const Expect = struct { incrementExpectCallCounter(); const not = this.flags.not; - const pass = value.isCallable(globalThis.vm()) != not; + const pass = value.isCallable() != not; if (pass) return .undefined; @@ -3942,7 +3942,7 @@ pub const Expect = struct { const predicate = arguments[0]; predicate.ensureStillAlive(); - if (!predicate.isCallable(globalThis.vm())) { + if (!predicate.isCallable()) { return globalThis.throw("toSatisfy() argument must be a function", .{}); } @@ -4825,7 +4825,7 @@ pub const Expect = struct { pass = pass_value.toBoolean(); if (result.fastGet(globalThis, .message)) |message_value| { - if (!message_value.isString() and !message_value.isCallable(globalThis.vm())) { + if (!message_value.isString() and !message_value.isCallable()) { break :valid false; } message = message_value; @@ -4854,7 +4854,7 @@ pub const Expect = struct { message_text = try message.toBunString(globalThis); } else { if (comptime Environment.allow_assert) - assert(message.isCallable(globalThis.vm())); // checked above + assert(message.isCallable()); // checked above const message_result = try message.callWithGlobalThis(globalThis, &.{}); message_text = try bun.String.fromJS2(message_result, globalThis); diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index ced19e4a6c..ba0f1df9c2 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -295,7 +295,7 @@ pub const Jest = struct { } const function = arguments.ptr[0]; - if (function.isEmptyOrUndefinedOrNull() or !function.isCallable(globalThis.vm())) { + if (function.isEmptyOrUndefinedOrNull() or !function.isCallable()) { return globalThis.throwInvalidArgumentType(name, "callback", "function"); } @@ -883,7 +883,7 @@ pub const DescribeScope = struct { } const cb = arguments.ptr[0]; - if (!cb.isObject() or !cb.isCallable(globalThis.vm())) { + if (!cb.isObject() or !cb.isCallable()) { return globalThis.throwInvalidArgumentType(@tagName(hook), "callback", "function"); } @@ -933,7 +933,7 @@ pub const DescribeScope = struct { for (hooks.items) |cb| { if (comptime Environment.allow_assert) { assert(cb.isObject()); - assert(cb.isCallable(globalObject.vm())); + assert(cb.isCallable()); } defer { if (comptime hook == .beforeAll or hook == .afterAll) { @@ -990,7 +990,7 @@ pub const DescribeScope = struct { for (hooks.items) |cb| { if (comptime Environment.allow_assert) { assert(cb.isObject()); - assert(cb.isCallable(globalThis.vm())); + assert(cb.isCallable()); } defer { if (comptime hook == .beforeAll or hook == .afterAll) { @@ -2023,7 +2023,7 @@ fn eachBind(globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSVa var function = args[1]; var options = if (args.len > 2) args[2] else .zero; - if (function.isEmptyOrUndefinedOrNull() or !function.isCell() or !function.isCallable(globalThis.vm())) { + if (function.isEmptyOrUndefinedOrNull() or !function.isCell() or !function.isCallable()) { return globalThis.throwPretty("{s} expects a function", .{signature}); } diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig index 176c54f04c..33e5ebd7df 100644 --- a/src/bun.js/test/pretty_format.zig +++ b/src/bun.js/test/pretty_format.zig @@ -414,7 +414,7 @@ pub const JestPrettyFormat = struct { // If we check an Object has a method table and it does not // it will crash - if (js_type != .Object and value.isCallable(globalThis.vm())) { + if (js_type != .Object and value.isCallable()) { if (value.isClass(globalThis)) { return .{ .tag = .Class, @@ -1305,7 +1305,7 @@ pub const JestPrettyFormat = struct { } else if (printAsymmetricMatcher(this, Format, &writer, writer_, name_buf, value, enable_ansi_colors)) { return; } else if (jsType != .DOMWrapper) { - if (value.isCallable(this.globalThis.vm())) { + if (value.isCallable()) { return try this.printAs(.Function, Writer, writer_, value, jsType, enable_ansi_colors); } @@ -1529,7 +1529,7 @@ pub const JestPrettyFormat = struct { if (_tag.cell == .Symbol) {} else if (_tag.cell.isStringLike()) { try type_value.toZigString(&tag_name_str, this.globalThis); is_tag_kind_primitive = true; - } else if (_tag.cell.isObject() or type_value.isCallable(this.globalThis.vm())) { + } else if (_tag.cell.isObject() or type_value.isCallable()) { type_value.getNameProperty(this.globalThis, &tag_name_str); if (tag_name_str.len == 0) { tag_name_str = ZigString.init("NoName"); diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 5fcb0bf74f..0b27ce768e 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -2627,7 +2627,7 @@ pub const Fetch = struct { } if (try tls.get(ctx, "checkServerIdentity")) |checkServerIdentity| { - if (checkServerIdentity.isCell() and checkServerIdentity.isCallable(globalThis.vm())) { + if (checkServerIdentity.isCell() and checkServerIdentity.isCallable()) { check_server_identity = checkServerIdentity; } } diff --git a/src/bun.js/webcore/streams.zig b/src/bun.js/webcore/streams.zig index 533738ed25..08aeac61e9 100644 --- a/src/bun.js/webcore/streams.zig +++ b/src/bun.js/webcore/streams.zig @@ -3277,7 +3277,7 @@ pub fn ReadableStreamSource( return true; } - if (!value.isCallable(globalObject.vm())) { + if (!value.isCallable()) { globalObject.throwInvalidArgumentType("ReadableStreamSource", "onclose", "function") catch {}; return false; } @@ -3295,7 +3295,7 @@ pub fn ReadableStreamSource( return true; } - if (!value.isCallable(globalObject.vm())) { + if (!value.isCallable()) { globalObject.throwInvalidArgumentType("ReadableStreamSource", "onDrain", "function") catch {}; return false; } diff --git a/src/js/node/crypto.ts b/src/js/node/crypto.ts index 482fd3da6d..521cc8ef1b 100644 --- a/src/js/node/crypto.ts +++ b/src/js/node/crypto.ts @@ -42,6 +42,10 @@ const { ECDH, DiffieHellman: _DiffieHellman, DiffieHellmanGroup: _DiffieHellmanGroup, + checkPrime, + checkPrimeSync, + generatePrime, + generatePrimeSync, } = $cpp("node_crypto_binding.cpp", "createNodeCryptoBinding"); const { @@ -49,13 +53,13 @@ const { pbkdf2Sync: _pbkdf2Sync, timingSafeEqual: _timingSafeEqual, randomInt, - randomUUID: _randomUUID, - randomBytes: _randomBytes, + randomUUID, + randomBytes, randomFillSync, - randomFill: _randomFill, + randomFill, } = $zig("node_crypto_binding.zig", "createNodeCryptoBindingZig"); -const { validateObject, validateString, validateInt32 } = require("internal/validators"); +const { validateObject, validateString } = require("internal/validators"); const kHandle = Symbol("kHandle"); @@ -2361,7 +2365,6 @@ crypto_exports.getFips = function getFips() { return 0; }; -crypto_exports.randomUUID = _randomUUID; crypto_exports.getCurves = getCurves; crypto_exports.getCipherInfo = getCipherInfo; crypto_exports.scrypt = scrypt; @@ -2513,18 +2516,16 @@ crypto_exports.createVerify = createVerify; }; } -function randomBytes(size, callback) { - if (callback === undefined) { - return _randomBytes(size); - } - - // Crypto random promise job is guaranteed to resolve. - _randomBytes(size, callback).then(buf => { - callback(null, buf); - }); -} - +crypto_exports.randomInt = randomInt; +crypto_exports.randomFill = randomFill; +crypto_exports.randomFillSync = randomFillSync; crypto_exports.randomBytes = randomBytes; +crypto_exports.randomUUID = randomUUID; + +crypto_exports.checkPrime = checkPrime; +crypto_exports.checkPrimeSync = checkPrimeSync; +crypto_exports.generatePrime = generatePrime; +crypto_exports.generatePrimeSync = generatePrimeSync; for (const rng of ["pseudoRandomBytes", "prng", "rng"]) { Object.defineProperty(crypto_exports, rng, { @@ -2534,31 +2535,6 @@ for (const rng of ["pseudoRandomBytes", "prng", "rng"]) { }); } -crypto_exports.randomInt = randomInt; - -function randomFill(buf, offset, size, callback) { - if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) { - throw $ERR_INVALID_ARG_TYPE("buf", ["ArrayBuffer", "ArrayBufferView"], buf); - } - - if (typeof offset === "function") { - callback = offset; - offset = 0; - size = buf.length; - } else if (typeof size === "function") { - callback = size; - size = buf.length - offset; - } - - // Crypto random promise job is guaranteed to resolve. - _randomFill(buf, offset, size, callback).then(() => { - callback(null, buf); - }); -} - -crypto_exports.randomFill = randomFill; -crypto_exports.randomFillSync = randomFillSync; - export default crypto_exports; /*! safe-buffer. MIT License. Feross Aboukhadijeh */ diff --git a/src/napi/napi.zig b/src/napi/napi.zig index 681af567d5..b3ec1091a6 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -670,7 +670,7 @@ pub export fn napi_make_callback(env_: napi_env, _: *anyopaque, recv_: napi_valu return envIsNull(); }; const recv, const func = .{ recv_.get(), func_.get() }; - if (func.isEmptyOrUndefinedOrNull() or !func.isCallable(env.toJS().vm())) { + if (func.isEmptyOrUndefinedOrNull() or !func.isCallable()) { return env.setLastError(.function_expected); } @@ -1749,7 +1749,7 @@ pub export fn napi_create_threadsafe_function( }; const func = func_.get(); - if (call_js_cb == null and (func.isEmptyOrUndefinedOrNull() or !func.isCallable(env.toJS().vm()))) { + if (call_js_cb == null and (func.isEmptyOrUndefinedOrNull() or !func.isCallable())) { return env.setLastError(.function_expected); } diff --git a/test/js/node/test/parallel/test-crypto-prime.js b/test/js/node/test/parallel/test-crypto-prime.js new file mode 100644 index 0000000000..91c2d42e52 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-prime.js @@ -0,0 +1,325 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); + +const { + generatePrime, + generatePrimeSync, + checkPrime, + checkPrimeSync, +} = require('crypto'); + +const { Worker } = require('worker_threads'); + +const { promisify } = require('util'); +const pgeneratePrime = promisify(generatePrime); +const pCheckPrime = promisify(checkPrime); + +['hello', false, {}, []].forEach((i) => { + assert.throws(() => generatePrime(i), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrimeSync(i), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +['hello', false, 123].forEach((i) => { + assert.throws(() => generatePrime(80, i, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrimeSync(80, i), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +['hello', false, 123].forEach((i) => { + assert.throws(() => generatePrime(80, {}), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +[-1, 0, 2 ** 31, 2 ** 31 + 1, 2 ** 32 - 1, 2 ** 32].forEach((size) => { + assert.throws(() => generatePrime(size, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + message: />= 1 and <= 2147483647/ + }); + assert.throws(() => generatePrimeSync(size), { + code: 'ERR_OUT_OF_RANGE', + message: />= 1 and <= 2147483647/ + }); +}); + +['test', -1, {}, []].forEach((i) => { + assert.throws(() => generatePrime(8, { safe: i }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrime(8, { rem: i }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrime(8, { add: i }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrimeSync(8, { safe: i }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrimeSync(8, { rem: i }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrimeSync(8, { add: i }), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +{ + // Negative BigInts should not be converted to 0 silently. + + assert.throws(() => generatePrime(20, { add: -1n }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "options.add" is out of range. It must be >= 0. ' + + 'Received -1n' + }); + + assert.throws(() => generatePrime(20, { rem: -1n }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "options.rem" is out of range. It must be >= 0. ' + + 'Received -1n' + }); + + assert.throws(() => checkPrime(-1n, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "candidate" is out of range. It must be >= 0. ' + + 'Received -1n' + }); +} + +generatePrime(80, common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); + checkPrime(prime, common.mustSucceed((result) => { + assert(result); + })); +})); + +assert(checkPrimeSync(generatePrimeSync(80))); + +generatePrime(80, {}, common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); +})); + +assert(checkPrimeSync(generatePrimeSync(80, {}))); + +generatePrime(32, { safe: true }, common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + const check = (val - 1) / 2; + buf.writeUInt32BE(check); + assert(checkPrimeSync(buf)); +})); + +{ + const prime = generatePrimeSync(32, { safe: true }); + assert(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + const check = (val - 1) / 2; + buf.writeUInt32BE(check); + assert(checkPrimeSync(buf)); +} + +const add = 12; +const rem = 11; +const add_buf = Buffer.from([add]); +const rem_buf = Buffer.from([rem]); +generatePrime( + 32, + { add: add_buf, rem: rem_buf }, + common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + assert.strictEqual(val % add, rem); + })); + +{ + const prime = generatePrimeSync(32, { add: add_buf, rem: rem_buf }); + assert(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + assert.strictEqual(val % add, rem); +} + +{ + const prime = generatePrimeSync(32, { add: BigInt(add), rem: BigInt(rem) }); + assert(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + assert.strictEqual(val % add, rem); +} + +{ + // The behavior when specifying only add without rem should depend on the + // safe option. + + if (process.versions.openssl >= '1.1.1f') { + generatePrime(128, { + bigint: true, + add: 5n + }, common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); + assert.strictEqual(prime % 5n, 1n); + })); + + generatePrime(128, { + bigint: true, + safe: true, + add: 5n + }, common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); + assert.strictEqual(prime % 5n, 3n); + })); + } +} + +{ + // This is impossible because it implies (prime % 2**64) == 1 and + // prime < 2**64, meaning prime = 1, but 1 is not prime. + for (const add of [2n ** 64n, 2n ** 65n]) { + assert.throws(() => { + generatePrimeSync(64, { add }); + }, { + code: 'ERR_OUT_OF_RANGE', + message: 'invalid options.add' + }); + } + + // Any parameters with rem >= add lead to an impossible condition. + for (const rem of [7n, 8n, 3000n]) { + assert.throws(() => { + generatePrimeSync(64, { add: 7n, rem }); + }, { + code: 'ERR_OUT_OF_RANGE', + message: 'invalid options.rem' + }); + } + + // This is possible, but not allowed. It implies prime == 7, which means that + // we did not actually generate a random prime. + assert.throws(() => { + generatePrimeSync(3, { add: 8n, rem: 7n }); + }, { + code: 'ERR_OUT_OF_RANGE' + }); + + if (process.versions.openssl >= '1.1.1f') { + // This is possible and allowed (but makes little sense). + assert.strictEqual(generatePrimeSync(4, { + add: 15n, + rem: 13n, + bigint: true + }), 13n); + } +} + +[1, 'hello', {}, []].forEach((i) => { + assert.throws(() => checkPrime(i), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +for (const checks of ['hello', {}, []]) { + assert.throws(() => checkPrime(2n, { checks }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + message: /checks/ + }); + assert.throws(() => checkPrimeSync(2n, { checks }), { + code: 'ERR_INVALID_ARG_TYPE', + message: /checks/ + }); +} + +for (const checks of [-(2 ** 31), -1, 2 ** 31, 2 ** 32 - 1, 2 ** 32, 2 ** 50]) { + assert.throws(() => checkPrime(2n, { checks }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + message: /<= 2147483647/ + }); + assert.throws(() => checkPrimeSync(2n, { checks }), { + code: 'ERR_OUT_OF_RANGE', + message: /<= 2147483647/ + }); +} + +{ + const bytes = Buffer.alloc(67108864); + bytes[0] = 0x1; + assert.throws(() => checkPrime(bytes, common.mustNotCall()), { + code: /ERR_OSSL_(BN_)?BIGNUM_TOO_LONG/, + message: /bignum[_ ]too[_ ]long/i + }); + assert.throws(() => checkPrimeSync(bytes), { + code: /ERR_OSSL_(BN_)?BIGNUM_TOO_LONG/, + message: /bignum[_ ]too[_ ]long/i + }); +} + +assert(!checkPrimeSync(Buffer.from([0x1]))); +assert(checkPrimeSync(Buffer.from([0x2]))); +assert(checkPrimeSync(Buffer.from([0x3]))); +assert(!checkPrimeSync(Buffer.from([0x4]))); + +assert( + !checkPrimeSync( + Buffer.from([0x1]), + { + fast: true, + trialDivision: true, + checks: 10 + })); + +(async function() { + const prime = await pgeneratePrime(36); + assert(await pCheckPrime(prime)); +})().then(common.mustCall()); + +assert.throws(() => { + generatePrimeSync(32, { bigint: '' }); +}, { code: 'ERR_INVALID_ARG_TYPE' }); + +assert.throws(() => { + generatePrime(32, { bigint: '' }, common.mustNotCall()); +}, { code: 'ERR_INVALID_ARG_TYPE' }); + +{ + const prime = generatePrimeSync(3, { bigint: true }); + assert.strictEqual(typeof prime, 'bigint'); + assert.strictEqual(prime, 7n); + assert(checkPrimeSync(prime)); + checkPrime(prime, common.mustSucceed(assert)); +} + +{ + generatePrime(3, { bigint: true }, common.mustSucceed((prime) => { + assert.strictEqual(typeof prime, 'bigint'); + assert.strictEqual(prime, 7n); + assert(checkPrimeSync(prime)); + checkPrime(prime, common.mustSucceed(assert)); + })); +} + +{ + // Verify that generatePrime can be reasonably interrupted. + const worker = new Worker(` + const { generatePrime } = require('crypto'); + generatePrime(2048, () => { + throw new Error('should not be called'); + }); + process.exit(42); + `, { eval: true }); + + worker.on('error', common.mustNotCall()); + worker.on('exit', common.mustCall((exitCode) => assert.strictEqual(exitCode, 42))); +}