From 8d3a04235d70186fd7c2952e38a644a4d2a39419 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 25 Jun 2025 18:03:26 +0800 Subject: [PATCH] Crypto.getRandomValues consistency Crypto.getRandomValues should mutate the given parameter as well as return the value. This return value must be the same (JsObject) as the input parameter. There might be more magical ways to solve this, but I opted for both the simplest and most flexible: adding a `toZig` function to JsObject which does what js.zig does internally when mapping js values to Zig (and, of course, it uses the same code). This allows a caller to receive a JsObject (not too common, but we already do that in a few places) and return that same JsObject (again, not too common, but we do have support for returning JsObject directly already). With the main addition that the JsObjet can now be turned into a Zig type by the caller. --- src/browser/crypto/crypto.zig | 36 +++++++++++++++++++++++------------ src/runtime/js.zig | 5 +++++ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/browser/crypto/crypto.zig b/src/browser/crypto/crypto.zig index bd9eac52..20cfb4e2 100644 --- a/src/browser/crypto/crypto.zig +++ b/src/browser/crypto/crypto.zig @@ -17,17 +17,21 @@ // along with this program. If not, see . const std = @import("std"); +const Env = @import("../env.zig").Env; const uuidv4 = @import("../../id.zig").uuidv4; // https://w3c.github.io/webcrypto/#crypto-interface pub const Crypto = struct { - pub fn _getRandomValues(_: *const Crypto, into: RandomValues) !RandomValues { + _not_empty: bool = true, + + pub fn _getRandomValues(_: *const Crypto, js_obj: Env.JsObject) !Env.JsObject { + var into = try js_obj.toZig(Crypto, "getRandomValues", RandomValues); const buf = into.asBuffer(); if (buf.len > 65_536) { return error.QuotaExceededError; } std.crypto.random.bytes(buf); - return into; + return js_obj; } pub fn _randomUUID(_: *const Crypto) [36]u8 { @@ -48,16 +52,16 @@ const RandomValues = union(enum) { uint64: []u64, fn asBuffer(self: RandomValues) []u8 { - switch (self) { - .int8 => |b| return (@as([]u8, @ptrCast(b)))[0..b.len], - .uint8 => |b| return (@as([]u8, @ptrCast(b)))[0..b.len], - .int16 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 2], - .uint16 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 2], - .int32 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 4], - .uint32 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 4], - .int64 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 8], - .uint64 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 8], - } + return switch (self) { + .int8 => |b| (@as([]u8, @ptrCast(b)))[0..b.len], + .uint8 => |b| (@as([]u8, @ptrCast(b)))[0..b.len], + .int16 => |b| (@as([]u8, @ptrCast(b)))[0 .. b.len * 2], + .uint16 => |b| (@as([]u8, @ptrCast(b)))[0 .. b.len * 2], + .int32 => |b| (@as([]u8, @ptrCast(b)))[0 .. b.len * 4], + .uint32 => |b| (@as([]u8, @ptrCast(b)))[0 .. b.len * 4], + .int64 => |b| (@as([]u8, @ptrCast(b)))[0 .. b.len * 8], + .uint64 => |b| (@as([]u8, @ptrCast(b)))[0 .. b.len * 8], + }; } }; @@ -82,4 +86,12 @@ test "Browser.Crypto" { .{ "new Set(r2).size", "5" }, .{ "r1.every((v, i) => v === r2[i])", "true" }, }, .{}); + + try runner.testCases(&.{ + .{ "var r3 = new Uint8Array(16)", null }, + .{ "let r4 = crypto.getRandomValues(r3)", "undefined" }, + .{ "r4[6] = 10", null }, + .{ "r4[6]", "10" }, + .{ "r3[6]", "10" }, + }, .{}); } diff --git a/src/runtime/js.zig b/src/runtime/js.zig index cc48175b..792bb6a9 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -1601,6 +1601,11 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { const str = try self.js_obj.getConstructorName(); return jsStringToZig(allocator, str, self.js_context.isolate); } + + pub fn toZig(self: JsObject, comptime Struct: type, comptime name: []const u8, comptime T: type) !T { + const named_function = comptime NamedFunction.init(Struct, name); + return self.js_context.jsValueToZig(named_function, T, self.js_obj.toValue()); + } }; // This only exists so that we know whether a function wants the opaque