From b87cf4f247b8286530eab156dc5eaa2b775821f6 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Wed, 25 Jun 2025 19:44:13 -0800 Subject: [PATCH] zig: handle when coerceToInt32 and coerceToInt64 throw (#20655) --- src/bun.js/VirtualMachine.zig | 2 +- src/bun.js/api/JSTranspiler.zig | 2 +- src/bun.js/api/bun/dns_resolver.zig | 4 +-- src/bun.js/api/bun/socket/Handlers.zig | 6 +--- .../api/bun/socket/tls_socket_functions.zig | 4 +-- src/bun.js/api/bun/udp_socket.zig | 36 ++++++++----------- src/bun.js/bindings/JSPromise.zig | 4 +-- src/bun.js/bindings/JSValue.zig | 18 +++++----- src/bun.js/jsc.zig | 2 +- src/bun.js/jsc/host_fn.zig | 7 ++-- src/bun.js/node/node_http_binding.zig | 2 +- src/bun.js/virtual_machine_exports.zig | 2 +- src/bun.js/webcore/Response.zig | 2 +- src/sql/postgres.zig | 4 +-- test/internal/ban-words.test.ts | 2 +- 15 files changed, 43 insertions(+), 54 deletions(-) diff --git a/src/bun.js/VirtualMachine.zig b/src/bun.js/VirtualMachine.zig index 09de65cc5b..daf49f889a 100644 --- a/src/bun.js/VirtualMachine.zig +++ b/src/bun.js/VirtualMachine.zig @@ -3602,7 +3602,7 @@ pub const ExitHandler = struct { pub fn dispatchOnBeforeExit(this: *ExitHandler) void { JSC.markBinding(@src()); const vm: *VirtualMachine = @alignCast(@fieldParentPtr("exit_handler", this)); - bun.jsc.fromJSHostCallVoid(vm.global, @src(), Process__dispatchOnBeforeExit, .{ vm.global, this.exit_code }) catch return; + bun.jsc.fromJSHostCallGeneric(vm.global, @src(), Process__dispatchOnBeforeExit, .{ vm.global, this.exit_code }) catch return; } }; diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index 88486f25a9..06169d9f8c 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -408,7 +408,7 @@ fn transformOptionsFromJSC(globalObject: *JSC.JSGlobalObject, temp_allocator: st if (!kind.isStringLike()) { tsconfig.jsonStringify(globalThis, 0, &out); } else { - out = tsconfig.toBunString(globalThis) catch @panic("unexpected exception"); + out = try tsconfig.toBunString(globalThis); } if (out.isEmpty()) break :tsconfig; diff --git a/src/bun.js/api/bun/dns_resolver.zig b/src/bun.js/api/bun/dns_resolver.zig index 4fe7437c67..ae5d2cb214 100644 --- a/src/bun.js/api/bun/dns_resolver.zig +++ b/src/bun.js/api/bun/dns_resolver.zig @@ -3387,11 +3387,11 @@ pub const DNSResolver = struct { const options = callframe.argument(0); if (options.isObject()) { if (try options.getTruthy(globalThis, "timeout")) |timeout| { - resolver.options.timeout = timeout.coerceToInt32(globalThis); + resolver.options.timeout = try timeout.coerceToInt32(globalThis); } if (try options.getTruthy(globalThis, "tries")) |tries| { - resolver.options.tries = tries.coerceToInt32(globalThis); + resolver.options.tries = try tries.coerceToInt32(globalThis); } } diff --git a/src/bun.js/api/bun/socket/Handlers.zig b/src/bun.js/api/bun/socket/Handlers.zig index 383a2529c6..b856b5dd75 100644 --- a/src/bun.js/api/bun/socket/Handlers.zig +++ b/src/bun.js/api/bun/socket/Handlers.zig @@ -314,11 +314,7 @@ pub const SocketConfig = struct { return globalObject.throwInvalidArguments("Expected \"port\" to be a number between 0 and 65535", .{}); } - const porti32 = port_value.coerceToInt32(globalObject); - if (globalObject.hasException()) { - return error.JSError; - } - + const porti32 = try port_value.coerceToInt32(globalObject); if (porti32 < 0 or porti32 > 65535) { return globalObject.throwInvalidArguments("Expected \"port\" to be a number between 0 and 65535", .{}); } diff --git a/src/bun.js/api/bun/socket/tls_socket_functions.zig b/src/bun.js/api/bun/socket/tls_socket_functions.zig index 233bfaa40f..2b6768911b 100644 --- a/src/bun.js/api/bun/socket/tls_socket_functions.zig +++ b/src/bun.js/api/bun/socket/tls_socket_functions.zig @@ -92,7 +92,7 @@ pub fn setMaxSendFragment(this: *This, globalObject: *JSC.JSGlobalObject, callfr if (!arg.isNumber()) { return globalObject.throw("Expected size to be a number", .{}); } - const size = args.ptr[0].coerceToInt64(globalObject); + const size = try args.ptr[0].coerceToInt64(globalObject); if (size < 1) { return globalObject.throw("Expected size to be greater than 1", .{}); } @@ -328,7 +328,7 @@ pub fn exportKeyingMaterial(this: *This, globalObject: *JSC.JSGlobalObject, call return globalObject.throw("Expected length to be a number", .{}); } - const length = length_arg.coerceToInt64(globalObject); + const length = try length_arg.coerceToInt64(globalObject); if (length < 0) { return globalObject.throw("Expected length to be a positive number", .{}); } diff --git a/src/bun.js/api/bun/udp_socket.zig b/src/bun.js/api/bun/udp_socket.zig index 80294f58de..0a94d13ac1 100644 --- a/src/bun.js/api/bun/udp_socket.zig +++ b/src/bun.js/api/bun/udp_socket.zig @@ -158,7 +158,7 @@ pub const UDPSocketConfig = struct { const port: u16 = brk: { if (try options.getTruthy(globalThis, "port")) |value| { - const number = value.coerceToInt32(globalThis); + const number = try value.coerceToInt32(globalThis); if (number < 0 or number > 0xffff) { return globalThis.throwInvalidArguments("Expected \"port\" to be an integer between 0 and 65535", .{}); } @@ -228,7 +228,7 @@ pub const UDPSocketConfig = struct { const connect_port_js = try connect.getTruthy(globalThis, "port") orelse { return globalThis.throwInvalidArguments("Expected \"connect.port\" to be an integer", .{}); }; - const connect_port = connect_port_js.coerceToInt32(globalThis); + const connect_port = try connect_port_js.coerceToInt32(globalThis); const str = try connect_host_js.toBunString(globalThis); defer str.deref(); @@ -442,13 +442,13 @@ pub const UDPSocket = struct { } var addr = std.mem.zeroes(std.posix.sockaddr.storage); - if (!parseAddr(this, globalThis, JSC.jsNumber(0), arguments[0], &addr)) { + if (!try parseAddr(this, globalThis, JSC.jsNumber(0), arguments[0], &addr)) { return globalThis.throwValue(try bun.JSC.Maybe(void).errnoSys(@as(i32, @intCast(@intFromEnum(std.posix.E.INVAL))), .setsockopt).?.toJS(globalThis)); } var interface = std.mem.zeroes(std.posix.sockaddr.storage); - const res = if (arguments.len > 1 and parseAddr(this, globalThis, JSC.jsNumber(0), arguments[1], &interface)) blk: { + const res = if (arguments.len > 1 and try parseAddr(this, globalThis, JSC.jsNumber(0), arguments[1], &interface)) blk: { if (addr.family != interface.family) { return globalThis.throwInvalidArguments("Family mismatch between address and interface", .{}); } @@ -481,12 +481,12 @@ pub const UDPSocket = struct { } var source_addr: std.posix.sockaddr.storage = undefined; - if (!parseAddr(this, globalThis, JSC.jsNumber(0), arguments[0], &source_addr)) { + if (!try parseAddr(this, globalThis, JSC.jsNumber(0), arguments[0], &source_addr)) { return globalThis.throwValue(try bun.JSC.Maybe(void).errnoSys(@as(i32, @intCast(@intFromEnum(std.posix.E.INVAL))), .setsockopt).?.toJS(globalThis)); } var group_addr: std.posix.sockaddr.storage = undefined; - if (!parseAddr(this, globalThis, JSC.jsNumber(0), arguments[1], &group_addr)) { + if (!try parseAddr(this, globalThis, JSC.jsNumber(0), arguments[1], &group_addr)) { return globalThis.throwValue(try bun.JSC.Maybe(void).errnoSys(@as(i32, @intCast(@intFromEnum(std.posix.E.INVAL))), .setsockopt).?.toJS(globalThis)); } @@ -496,7 +496,7 @@ pub const UDPSocket = struct { var interface: std.posix.sockaddr.storage = undefined; - const res = if (arguments.len > 2 and parseAddr(this, globalThis, JSC.jsNumber(0), arguments[2], &interface)) blk: { + const res = if (arguments.len > 2 and try parseAddr(this, globalThis, JSC.jsNumber(0), arguments[2], &interface)) blk: { if (source_addr.family != interface.family) { return globalThis.throwInvalidArguments("Family mismatch among source, group and interface addresses", .{}); } @@ -530,7 +530,7 @@ pub const UDPSocket = struct { var addr: std.posix.sockaddr.storage = undefined; - if (!parseAddr(this, globalThis, JSC.jsNumber(0), arguments[0], &addr)) { + if (!try parseAddr(this, globalThis, JSC.jsNumber(0), arguments[0], &addr)) { return .false; } @@ -584,7 +584,7 @@ pub const UDPSocket = struct { return globalThis.throwInvalidArguments("Expected 1 argument, got {}", .{arguments.len}); } - const ttl = arguments[0].coerceToInt32(globalThis); + const ttl = try arguments[0].coerceToInt32(globalThis); const res = function(this.socket, ttl); if (getUSError(res, .setsockopt, true)) |err| { @@ -655,7 +655,7 @@ pub const UDPSocket = struct { continue; } if (i % 3 == 2) { - if (!this.parseAddr(globalThis, port, val, &addrs[slice_idx])) { + if (!try this.parseAddr(globalThis, port, val, &addrs[slice_idx])) { return globalThis.throwInvalidArguments("Invalid address", .{}); } addr_ptrs[slice_idx] = &addrs[slice_idx]; @@ -713,7 +713,7 @@ pub const UDPSocket = struct { var addr: std.posix.sockaddr.storage = std.mem.zeroes(std.posix.sockaddr.storage); const addr_ptr = brk: { if (dst) |dest| { - if (!this.parseAddr(globalThis, dest.port, dest.address, &addr)) { + if (!try this.parseAddr(globalThis, dest.port, dest.address, &addr)) { return globalThis.throwInvalidArguments("Invalid address", .{}); } break :brk &addr; @@ -729,20 +729,14 @@ pub const UDPSocket = struct { return JSValue.jsBoolean(res > 0); } - fn parseAddr( - this: *This, - globalThis: *JSGlobalObject, - port_val: JSValue, - address_val: JSValue, - storage: *std.posix.sockaddr.storage, - ) bool { + fn parseAddr(this: *This, globalThis: *JSGlobalObject, port_val: JSValue, address_val: JSValue, storage: *std.posix.sockaddr.storage) bun.JSError!bool { _ = this; - const number = port_val.coerceToInt32(globalThis); + const number = try port_val.coerceToInt32(globalThis); const port: u16 = if (number < 1 or number > 0xffff) 0 else @intCast(number); - const str = address_val.toBunString(globalThis) catch @panic("unexpected exception"); + const str = try address_val.toBunString(globalThis); defer str.deref(); - const address_slice = str.toOwnedSliceZ(default_allocator) catch bun.outOfMemory(); + const address_slice = try str.toOwnedSliceZ(default_allocator); defer default_allocator.free(address_slice); var addr4: *std.posix.sockaddr.in = @ptrCast(storage); diff --git a/src/bun.js/bindings/JSPromise.zig b/src/bun.js/bindings/JSPromise.zig index a67fb9c87d..2fbc6e2084 100644 --- a/src/bun.js/bindings/JSPromise.zig +++ b/src/bun.js/bindings/JSPromise.zig @@ -270,7 +270,7 @@ pub const JSPromise = opaque { } } - return bun.jsc.fromJSHostCallVoid(globalThis, @src(), JSC__JSPromise__resolve, .{ this, globalThis, value }) catch return bun.debugAssert(false); // TODO: properly propagate exception upwards + return bun.jsc.fromJSHostCallGeneric(globalThis, @src(), JSC__JSPromise__resolve, .{ this, globalThis, value }) catch return bun.debugAssert(false); // TODO: properly propagate exception upwards } pub fn reject(this: *JSPromise, globalThis: *JSGlobalObject, value: JSError!JSValue) void { @@ -284,7 +284,7 @@ pub const JSPromise = opaque { const err = value catch |err| globalThis.takeException(err); - return bun.jsc.fromJSHostCallVoid(globalThis, @src(), JSC__JSPromise__reject, .{ this, globalThis, err }) catch return bun.debugAssert(false); // TODO: properly propagate exception upwards + return bun.jsc.fromJSHostCallGeneric(globalThis, @src(), JSC__JSPromise__reject, .{ this, globalThis, err }) catch return bun.debugAssert(false); // TODO: properly propagate exception upwards } pub fn rejectAsHandled(this: *JSPromise, globalThis: *JSGlobalObject, value: JSValue) void { diff --git a/src/bun.js/bindings/JSValue.zig b/src/bun.js/bindings/JSValue.zig index 853ba1b3f0..bbb5037a6b 100644 --- a/src/bun.js/bindings/JSValue.zig +++ b/src/bun.js/bindings/JSValue.zig @@ -46,16 +46,14 @@ pub const JSValue = enum(i64) { return @as(JSValue, @enumFromInt(@as(i64, @bitCast(@intFromPtr(ptr))))); } - // TODO: use JSError! `toInt32` can throw extern fn JSC__JSValue__coerceToInt32(this: JSValue, globalThis: *JSC.JSGlobalObject) i32; - pub fn coerceToInt32(this: JSValue, globalThis: *JSC.JSGlobalObject) i32 { - return JSC__JSValue__coerceToInt32(this, globalThis); + pub fn coerceToInt32(this: JSValue, globalThis: *JSC.JSGlobalObject) bun.JSError!i32 { + return bun.jsc.fromJSHostCallGeneric(globalThis, @src(), JSC__JSValue__coerceToInt32, .{ this, globalThis }); } - // TODO: use JSError! `toInt32` can throw extern fn JSC__JSValue__coerceToInt64(this: JSValue, globalThis: *JSC.JSGlobalObject) i64; - pub fn coerceToInt64(this: JSValue, globalThis: *JSC.JSGlobalObject) i64 { - return JSC__JSValue__coerceToInt64(this, globalThis); + pub fn coerceToInt64(this: JSValue, globalThis: *JSC.JSGlobalObject) bun.JSError!i64 { + return bun.jsc.fromJSHostCallGeneric(globalThis, @src(), JSC__JSValue__coerceToInt64, .{ this, globalThis }); } pub fn getIndex(this: JSValue, globalThis: *JSGlobalObject, i: u32) JSError!JSValue { @@ -195,7 +193,7 @@ pub const JSValue = enum(i64) { if (this.getNumber()) |num| { return @bitCast(coerceJSValueDoubleTruncatingT(i32, num)); } - return @bitCast(this.coerceToInt32(globalThis)); + return @bitCast(try this.coerceToInt32(globalThis)); }, else => @compileError("Unsupported coercion type"), }; @@ -389,7 +387,7 @@ pub const JSValue = enum(i64) { extern fn JSC__JSValue__putToPropertyKey(target: JSValue, globalObject: *JSGlobalObject, key: JSC.JSValue, value: JSC.JSValue) void; pub fn putToPropertyKey(target: JSValue, globalObject: *JSGlobalObject, key: JSC.JSValue, value: JSC.JSValue) bun.JSError!void { - return bun.jsc.host_fn.fromJSHostCallVoid(globalObject, @src(), JSC__JSValue__putToPropertyKey, .{ target, globalObject, key, value }); + return bun.jsc.host_fn.fromJSHostCallGeneric(globalObject, @src(), JSC__JSValue__putToPropertyKey, .{ target, globalObject, key, value }); } extern fn JSC__JSValue__putIndex(value: JSValue, globalObject: *JSGlobalObject, i: u32, out: JSValue) void; @@ -1147,12 +1145,12 @@ pub const JSValue = enum(i64) { extern fn JSC__JSValue__toZigException(this: JSValue, global: *JSGlobalObject, exception: *ZigException) void; pub fn toZigException(this: JSValue, global: *JSGlobalObject, exception: *ZigException) void { - return bun.jsc.fromJSHostCallVoid(global, @src(), JSC__JSValue__toZigException, .{ this, global, exception }) catch return; // TODO: properly propagate termination + return bun.jsc.fromJSHostCallGeneric(global, @src(), JSC__JSValue__toZigException, .{ this, global, exception }) catch return; // TODO: properly propagate termination } extern fn JSC__JSValue__toZigString(this: JSValue, out: *ZigString, global: *JSGlobalObject) void; pub fn toZigString(this: JSValue, out: *ZigString, global: *JSGlobalObject) JSError!void { - return bun.jsc.fromJSHostCallVoid(global, @src(), JSC__JSValue__toZigString, .{ this, out, global }); + return bun.jsc.fromJSHostCallGeneric(global, @src(), JSC__JSValue__toZigString, .{ this, out, global }); } /// Increments the reference count, you must call `.deref()` or it will leak memory. diff --git a/src/bun.js/jsc.zig b/src/bun.js/jsc.zig index 9cc215aeb4..2442416cf6 100644 --- a/src/bun.js/jsc.zig +++ b/src/bun.js/jsc.zig @@ -35,7 +35,7 @@ pub const toJSHostFn = host_fn.toJSHostFn; pub const toJSHostFnWithContext = host_fn.toJSHostFnWithContext; pub const toJSHostCall = host_fn.toJSHostCall; pub const fromJSHostCall = host_fn.fromJSHostCall; -pub const fromJSHostCallVoid = host_fn.fromJSHostCallVoid; +pub const fromJSHostCallGeneric = host_fn.fromJSHostCallGeneric; pub const createCallback = host_fn.createCallback; // JSC Classes Bindings diff --git a/src/bun.js/jsc/host_fn.zig b/src/bun.js/jsc/host_fn.zig index da0f830cfd..9ac2f008a4 100644 --- a/src/bun.js/jsc/host_fn.zig +++ b/src/bun.js/jsc/host_fn.zig @@ -121,19 +121,20 @@ pub fn fromJSHostCall( return if (value == .zero) error.JSError else value; } -pub fn fromJSHostCallVoid( +pub fn fromJSHostCallGeneric( globalThis: *JSGlobalObject, /// For attributing thrown exceptions src: std.builtin.SourceLocation, comptime function: anytype, args: std.meta.ArgsTuple(@TypeOf(function)), -) bun.JSError!void { +) bun.JSError!@typeInfo(@TypeOf(function)).@"fn".return_type.? { var scope: jsc.CatchScope = undefined; scope.init(globalThis, src, .assertions_only); defer scope.deinit(); - @call(.auto, function, args); + const result = @call(.auto, function, args); try scope.returnIfException(); + return result; } const ParsedHostFunctionErrorSet = struct { diff --git a/src/bun.js/node/node_http_binding.zig b/src/bun.js/node/node_http_binding.zig index 713201d0ea..71731ca19d 100644 --- a/src/bun.js/node/node_http_binding.zig +++ b/src/bun.js/node/node_http_binding.zig @@ -35,7 +35,7 @@ pub fn setMaxHTTPHeaderSize(globalThis: *JSC.JSGlobalObject, callframe: *JSC.Cal return globalThis.throwNotEnoughArguments("setMaxHTTPHeaderSize", 1, arguments.len); } const value = arguments[0]; - const num = value.coerceToInt64(globalThis); + const num = try value.coerceToInt64(globalThis); if (num <= 0) { return globalThis.throwInvalidArgumentTypeValue("maxHeaderSize", "non-negative integer", value); } diff --git a/src/bun.js/virtual_machine_exports.zig b/src/bun.js/virtual_machine_exports.zig index b86d08c4e7..7d23011496 100644 --- a/src/bun.js/virtual_machine_exports.zig +++ b/src/bun.js/virtual_machine_exports.zig @@ -198,7 +198,7 @@ pub fn Bun__setSyntheticAllocationLimitForTesting(globalObject: *JSGlobalObject, return globalObject.throwInvalidArguments("setSyntheticAllocationLimitForTesting expects a number", .{}); } - const limit: usize = @intCast(@max(args[0].coerceToInt64(globalObject), 1024 * 1024)); + const limit: usize = @intCast(@max(try args[0].coerceToInt64(globalObject), 1024 * 1024)); const prev = VirtualMachine.synthetic_allocation_limit; VirtualMachine.synthetic_allocation_limit = limit; VirtualMachine.string_allocation_limit = limit; diff --git a/src/bun.js/webcore/Response.zig b/src/bun.js/webcore/Response.zig index 6767cc8e10..bdd0552cd6 100644 --- a/src/bun.js/webcore/Response.zig +++ b/src/bun.js/webcore/Response.zig @@ -658,7 +658,7 @@ pub const Init = struct { } if (try response_init.fastGet(globalThis, .status)) |status_value| { - const number = status_value.coerceToInt64(globalThis); + const number = try status_value.coerceToInt64(globalThis); if ((200 <= number and number < 600) or number == 101) { result.status_code = @as(u16, @truncate(@as(u32, @intCast(number)))); } else { diff --git a/src/sql/postgres.zig b/src/sql/postgres.zig index 2e36888fe7..53159e2d69 100644 --- a/src/sql/postgres.zig +++ b/src/sql/postgres.zig @@ -972,12 +972,12 @@ pub const PostgresRequest = struct { }, .int4 => { const l = try writer.length(); - try writer.int4(@bitCast(value.coerceToInt32(globalObject))); + try writer.int4(@bitCast(try value.coerceToInt32(globalObject))); try l.writeExcludingSelf(); }, .int4_array => { const l = try writer.length(); - try writer.int4(@bitCast(value.coerceToInt32(globalObject))); + try writer.int4(@bitCast(try value.coerceToInt32(globalObject))); try l.writeExcludingSelf(); }, .float8 => { diff --git a/test/internal/ban-words.test.ts b/test/internal/ban-words.test.ts index 27f2e9ac46..536428015b 100644 --- a/test/internal/ban-words.test.ts +++ b/test/internal/ban-words.test.ts @@ -45,7 +45,7 @@ const words: Record "// autofix": { reason: "Evaluate if this variable should be deleted entirely or explicitly discarded.", limit: 174 }, "global.hasException": { reason: "Incompatible with strict exception checks. Use a CatchScope instead.", limit: 28 }, - "globalObject.hasException": { reason: "Incompatible with strict exception checks. Use a CatchScope instead.", limit: 48 }, + "globalObject.hasException": { reason: "Incompatible with strict exception checks. Use a CatchScope instead.", limit: 47 }, "globalThis.hasException": { reason: "Incompatible with strict exception checks. Use a CatchScope instead.", limit: 136 }, }; const words_keys = [...Object.keys(words)];