From f4a88cd2a7b81c129ebb2679ccca5c07f39e581a Mon Sep 17 00:00:00 2001 From: Chen Kai <281165273grape@gmail.com> Date: Sun, 19 Jul 2026 10:15:39 -0400 Subject: [PATCH 1/6] fix(napi): receive raw pointer out parameters --- examples/js_dsl/mod.test.ts | 19 ++++++++++++ examples/js_dsl/mod.zig | 46 +++++++++++++++++++++++++++++ src/Env.zig | 6 ++-- src/Value.zig | 58 ++++++++++++++++++++++++++++++------- 4 files changed, 115 insertions(+), 14 deletions(-) diff --git a/examples/js_dsl/mod.test.ts b/examples/js_dsl/mod.test.ts index 42db759..36b6b00 100644 --- a/examples/js_dsl/mod.test.ts +++ b/examples/js_dsl/mod.test.ts @@ -278,6 +278,25 @@ describe("mixed DSL + N-API", () => { expect(obj).toEqual({ x: 10 }); }); + it("reports the active Node version", () => { + expect(mod.nodeVersion()).toEqual(process.versions.node); + expect(mod.nodeRelease()).toEqual(process.release.name); + }); + + it("returns a TypedArray's backing ArrayBuffer and range", () => { + const backing = new ArrayBuffer(16); + const view = new Uint16Array(backing, 4, 3); + + expect(mod.typedArrayInfoMatches(view, backing, 3, 4)).toBe(true); + }); + + it("returns a DataView's backing ArrayBuffer and range", () => { + const backing = new ArrayBuffer(16); + const view = new DataView(backing, 4, 6); + + expect(mod.dataViewInfoMatches(view, backing, 6, 4)).toBe(true); + }); + it("randomBytes16 uses js.io() to produce a Uint8Array", () => { const bytes = mod.randomBytes16(); expect(bytes).toBeInstanceOf(Uint8Array); diff --git a/examples/js_dsl/mod.zig b/examples/js_dsl/mod.zig index 99f5438..ec9a076 100644 --- a/examples/js_dsl/mod.zig +++ b/examples/js_dsl/mod.zig @@ -334,6 +334,52 @@ pub fn makeObject(key: String, value: Number) !Value { return .{ .val = obj }; } +pub fn nodeVersion() !String { + const semantic_version = (try js.env().getNodeVersion()).toSemanticVersion(); + var buffer: [64]u8 = undefined; + const version = std.fmt.bufPrint( + &buffer, + "{d}.{d}.{d}", + .{ semantic_version.major, semantic_version.minor, semantic_version.patch }, + ) catch return error.FormatError; + return String.from(version); +} + +pub fn nodeRelease() !String { + const release = (try js.env().getNodeVersion()).getRelease(); + return String.from(std.mem.span(release)); +} + +pub fn typedArrayInfoMatches( + value: Value, + expected_arraybuffer: Value, + expected_length: Number, + expected_byte_offset: Number, +) !Boolean { + const info = try value.toValue().getTypedarrayInfo(); + const length: usize = @intCast(expected_length.assertU32()); + const byte_offset: usize = @intCast(expected_byte_offset.assertU32()); + if (info.length != length) return Boolean.from(false); + if (info.byte_offset != byte_offset) return Boolean.from(false); + if (info.data.len != length * info.array_type.elementSize()) return Boolean.from(false); + return Boolean.from(try info.arraybuffer.strictEquals(expected_arraybuffer.toValue())); +} + +pub fn dataViewInfoMatches( + value: Value, + expected_arraybuffer: Value, + expected_byte_length: Number, + expected_byte_offset: Number, +) !Boolean { + const info = try value.toValue().getDataviewInfo(); + const byte_length: usize = @intCast(expected_byte_length.assertU32()); + const byte_offset: usize = @intCast(expected_byte_offset.assertU32()); + if (info.byte_length != byte_length) return Boolean.from(false); + if (info.byte_offset != byte_offset) return Boolean.from(false); + if (info.data.len != byte_length) return Boolean.from(false); + return Boolean.from(try info.arraybuffer.strictEquals(expected_arraybuffer.toValue())); +} + /// Generate 16 random bytes using the DSL-managed shared std.Io instance. pub fn randomBytes16() Uint8Array { var bytes: [16]u8 = undefined; diff --git a/src/Env.zig b/src/Env.zig index 4735d80..029f6e7 100644 --- a/src/Env.zig +++ b/src/Env.zig @@ -889,11 +889,11 @@ pub fn asyncInit(self: Env, async_resource: Value, async_resource_name: Value) N /// https://nodejs.org/api/n-api.html#napi_get_node_version pub fn getNodeVersion(self: Env) NapiError!NodeVersion { - var version: c.napi_node_version = undefined; + var version: [*c]const c.napi_node_version = undefined; try status.check( - c.napi_get_node_version(self.env, @ptrCast(&version)), + c.napi_get_node_version(self.env, &version), ); - return NodeVersion{ .version = version }; + return NodeVersion{ .version = version.* }; } /// https://nodejs.org/api/n-api.html#napi_get_version diff --git a/src/Value.zig b/src/Value.zig index bd3b206..a790300 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -144,13 +144,33 @@ pub const TypedarrayInfo = struct { /// https://nodejs.org/api/n-api.html#napi_get_typedarray_info pub fn getTypedarrayInfo(self: Value) NapiError!TypedarrayInfo { - var info: TypedarrayInfo = undefined; + var array_type_raw: c.napi_typedarray_type = undefined; + var length: usize = undefined; var data: [*]u8 = undefined; - try status.check( - c.napi_get_typedarray_info(self.env, self.value, @ptrCast(&info.array_type), &info.length, @ptrCast(&data), @ptrCast(&info.arraybuffer), &info.byte_offset), - ); - info.data = data[0 .. info.length * info.array_type.elementSize()]; - return info; + var arraybuffer: c.napi_value = undefined; + var byte_offset: usize = undefined; + try status.check( + c.napi_get_typedarray_info( + self.env, + self.value, + &array_type_raw, + &length, + @ptrCast(&data), + &arraybuffer, + &byte_offset, + ), + ); + const array_type: TypedarrayType = @enumFromInt(array_type_raw); + return .{ + .array_type = array_type, + .length = length, + .data = data[0 .. length * array_type.elementSize()], + .arraybuffer = .{ + .env = self.env, + .value = arraybuffer, + }, + .byte_offset = byte_offset, + }; } pub const DataViewInfo = struct { @@ -162,13 +182,29 @@ pub const DataViewInfo = struct { /// https://nodejs.org/api/n-api.html#napi_get_dataview_info pub fn getDataviewInfo(self: Value) NapiError!DataViewInfo { - var info: DataViewInfo = undefined; + var byte_length: usize = undefined; var data: [*]u8 = undefined; - try status.check( - c.napi_get_dataview_info(self.env, self.value, &info.byte_length, @ptrCast(&data), @ptrCast(&info.arraybuffer), &info.byte_offset), + var arraybuffer: c.napi_value = undefined; + var byte_offset: usize = undefined; + try status.check( + c.napi_get_dataview_info( + self.env, + self.value, + &byte_length, + @ptrCast(&data), + &arraybuffer, + &byte_offset, + ), ); - info.data = data[0..info.byte_length]; - return info; + return .{ + .byte_length = byte_length, + .data = data[0..byte_length], + .arraybuffer = .{ + .env = self.env, + .value = arraybuffer, + }, + .byte_offset = byte_offset, + }; } /// https://nodejs.org/api/n-api.html#napi_get_date_value From 66ed541bef8006881fa6f6e2ddf1a143073443cb Mon Sep 17 00:00:00 2001 From: Chen Kai <281165273grape@gmail.com> Date: Sun, 19 Jul 2026 13:03:14 -0400 Subject: [PATCH 2/6] fix(napi): report unsupported typed array types --- examples/js_dsl/mod.test.ts | 10 ++++++++++ src/Value.zig | 10 ++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/examples/js_dsl/mod.test.ts b/examples/js_dsl/mod.test.ts index 36b6b00..031fb20 100644 --- a/examples/js_dsl/mod.test.ts +++ b/examples/js_dsl/mod.test.ts @@ -290,6 +290,16 @@ describe("mixed DSL + N-API", () => { expect(mod.typedArrayInfoMatches(view, backing, 3, 4)).toBe(true); }); + it("rejects unsupported TypedArray element types", () => { + const Float16ArrayCtor = Reflect.get(globalThis, "Float16Array"); + if (typeof Float16ArrayCtor !== "function") return; + const view = Reflect.construct(Float16ArrayCtor, [3]) as Uint16Array; + + expect(() => + mod.typedArrayInfoMatches(view, view.buffer, 3, 0), + ).toThrow("UnsupportedTypedarrayType"); + }); + it("returns a DataView's backing ArrayBuffer and range", () => { const backing = new ArrayBuffer(16); const view = new DataView(backing, 4, 6); diff --git a/src/Value.zig b/src/Value.zig index a790300..a6cae2a 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const c = @import("c.zig").c; const status = @import("status.zig"); const NapiError = @import("status.zig").NapiError; @@ -142,8 +143,12 @@ pub const TypedarrayInfo = struct { byte_offset: usize, }; +pub const TypedarrayInfoError = NapiError || error{ + UnsupportedTypedarrayType, +}; + /// https://nodejs.org/api/n-api.html#napi_get_typedarray_info -pub fn getTypedarrayInfo(self: Value) NapiError!TypedarrayInfo { +pub fn getTypedarrayInfo(self: Value) TypedarrayInfoError!TypedarrayInfo { var array_type_raw: c.napi_typedarray_type = undefined; var length: usize = undefined; var data: [*]u8 = undefined; @@ -160,7 +165,8 @@ pub fn getTypedarrayInfo(self: Value) NapiError!TypedarrayInfo { &byte_offset, ), ); - const array_type: TypedarrayType = @enumFromInt(array_type_raw); + const array_type = std.enums.fromInt(TypedarrayType, array_type_raw) orelse + return error.UnsupportedTypedarrayType; return .{ .array_type = array_type, .length = length, From 8ad727a13286f8ea546b52c4910ee398b4101f6e Mon Sep 17 00:00:00 2001 From: Chen Kai <281165273grape@gmail.com> Date: Sun, 19 Jul 2026 13:19:07 -0400 Subject: [PATCH 3/6] test(napi): reuse existing typed array example --- examples/js_dsl/mod.test.ts | 24 ------------------------ examples/js_dsl/mod.zig | 30 ------------------------------ 2 files changed, 54 deletions(-) diff --git a/examples/js_dsl/mod.test.ts b/examples/js_dsl/mod.test.ts index 031fb20..76198e7 100644 --- a/examples/js_dsl/mod.test.ts +++ b/examples/js_dsl/mod.test.ts @@ -283,30 +283,6 @@ describe("mixed DSL + N-API", () => { expect(mod.nodeRelease()).toEqual(process.release.name); }); - it("returns a TypedArray's backing ArrayBuffer and range", () => { - const backing = new ArrayBuffer(16); - const view = new Uint16Array(backing, 4, 3); - - expect(mod.typedArrayInfoMatches(view, backing, 3, 4)).toBe(true); - }); - - it("rejects unsupported TypedArray element types", () => { - const Float16ArrayCtor = Reflect.get(globalThis, "Float16Array"); - if (typeof Float16ArrayCtor !== "function") return; - const view = Reflect.construct(Float16ArrayCtor, [3]) as Uint16Array; - - expect(() => - mod.typedArrayInfoMatches(view, view.buffer, 3, 0), - ).toThrow("UnsupportedTypedarrayType"); - }); - - it("returns a DataView's backing ArrayBuffer and range", () => { - const backing = new ArrayBuffer(16); - const view = new DataView(backing, 4, 6); - - expect(mod.dataViewInfoMatches(view, backing, 6, 4)).toBe(true); - }); - it("randomBytes16 uses js.io() to produce a Uint8Array", () => { const bytes = mod.randomBytes16(); expect(bytes).toBeInstanceOf(Uint8Array); diff --git a/examples/js_dsl/mod.zig b/examples/js_dsl/mod.zig index ec9a076..73c73ce 100644 --- a/examples/js_dsl/mod.zig +++ b/examples/js_dsl/mod.zig @@ -350,36 +350,6 @@ pub fn nodeRelease() !String { return String.from(std.mem.span(release)); } -pub fn typedArrayInfoMatches( - value: Value, - expected_arraybuffer: Value, - expected_length: Number, - expected_byte_offset: Number, -) !Boolean { - const info = try value.toValue().getTypedarrayInfo(); - const length: usize = @intCast(expected_length.assertU32()); - const byte_offset: usize = @intCast(expected_byte_offset.assertU32()); - if (info.length != length) return Boolean.from(false); - if (info.byte_offset != byte_offset) return Boolean.from(false); - if (info.data.len != length * info.array_type.elementSize()) return Boolean.from(false); - return Boolean.from(try info.arraybuffer.strictEquals(expected_arraybuffer.toValue())); -} - -pub fn dataViewInfoMatches( - value: Value, - expected_arraybuffer: Value, - expected_byte_length: Number, - expected_byte_offset: Number, -) !Boolean { - const info = try value.toValue().getDataviewInfo(); - const byte_length: usize = @intCast(expected_byte_length.assertU32()); - const byte_offset: usize = @intCast(expected_byte_offset.assertU32()); - if (info.byte_length != byte_length) return Boolean.from(false); - if (info.byte_offset != byte_offset) return Boolean.from(false); - if (info.data.len != byte_length) return Boolean.from(false); - return Boolean.from(try info.arraybuffer.strictEquals(expected_arraybuffer.toValue())); -} - /// Generate 16 random bytes using the DSL-managed shared std.Io instance. pub fn randomBytes16() Uint8Array { var bytes: [16]u8 = undefined; From b65ac7084ef9831d9efb52197a5bf3154f72da4d Mon Sep 17 00:00:00 2001 From: Chen Kai <281165273grape@gmail.com> Date: Sun, 19 Jul 2026 13:32:32 -0400 Subject: [PATCH 4/6] test(napi): cover view info out parameters --- examples/js_dsl/mod.test.ts | 24 ++++++++++++++++++++++++ examples/js_dsl/mod.zig | 30 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/examples/js_dsl/mod.test.ts b/examples/js_dsl/mod.test.ts index 76198e7..031fb20 100644 --- a/examples/js_dsl/mod.test.ts +++ b/examples/js_dsl/mod.test.ts @@ -283,6 +283,30 @@ describe("mixed DSL + N-API", () => { expect(mod.nodeRelease()).toEqual(process.release.name); }); + it("returns a TypedArray's backing ArrayBuffer and range", () => { + const backing = new ArrayBuffer(16); + const view = new Uint16Array(backing, 4, 3); + + expect(mod.typedArrayInfoMatches(view, backing, 3, 4)).toBe(true); + }); + + it("rejects unsupported TypedArray element types", () => { + const Float16ArrayCtor = Reflect.get(globalThis, "Float16Array"); + if (typeof Float16ArrayCtor !== "function") return; + const view = Reflect.construct(Float16ArrayCtor, [3]) as Uint16Array; + + expect(() => + mod.typedArrayInfoMatches(view, view.buffer, 3, 0), + ).toThrow("UnsupportedTypedarrayType"); + }); + + it("returns a DataView's backing ArrayBuffer and range", () => { + const backing = new ArrayBuffer(16); + const view = new DataView(backing, 4, 6); + + expect(mod.dataViewInfoMatches(view, backing, 6, 4)).toBe(true); + }); + it("randomBytes16 uses js.io() to produce a Uint8Array", () => { const bytes = mod.randomBytes16(); expect(bytes).toBeInstanceOf(Uint8Array); diff --git a/examples/js_dsl/mod.zig b/examples/js_dsl/mod.zig index 73c73ce..ec9a076 100644 --- a/examples/js_dsl/mod.zig +++ b/examples/js_dsl/mod.zig @@ -350,6 +350,36 @@ pub fn nodeRelease() !String { return String.from(std.mem.span(release)); } +pub fn typedArrayInfoMatches( + value: Value, + expected_arraybuffer: Value, + expected_length: Number, + expected_byte_offset: Number, +) !Boolean { + const info = try value.toValue().getTypedarrayInfo(); + const length: usize = @intCast(expected_length.assertU32()); + const byte_offset: usize = @intCast(expected_byte_offset.assertU32()); + if (info.length != length) return Boolean.from(false); + if (info.byte_offset != byte_offset) return Boolean.from(false); + if (info.data.len != length * info.array_type.elementSize()) return Boolean.from(false); + return Boolean.from(try info.arraybuffer.strictEquals(expected_arraybuffer.toValue())); +} + +pub fn dataViewInfoMatches( + value: Value, + expected_arraybuffer: Value, + expected_byte_length: Number, + expected_byte_offset: Number, +) !Boolean { + const info = try value.toValue().getDataviewInfo(); + const byte_length: usize = @intCast(expected_byte_length.assertU32()); + const byte_offset: usize = @intCast(expected_byte_offset.assertU32()); + if (info.byte_length != byte_length) return Boolean.from(false); + if (info.byte_offset != byte_offset) return Boolean.from(false); + if (info.data.len != byte_length) return Boolean.from(false); + return Boolean.from(try info.arraybuffer.strictEquals(expected_arraybuffer.toValue())); +} + /// Generate 16 random bytes using the DSL-managed shared std.Io instance. pub fn randomBytes16() Uint8Array { var bytes: [16]u8 = undefined; From 086ea5a73ddb6c161487f5a928fe3fc6682315c7 Mon Sep 17 00:00:00 2001 From: Chen Kai <281165273grape@gmail.com> Date: Wed, 22 Jul 2026 21:21:26 +0800 Subject: [PATCH 5/6] test(napi): report unsupported Float16Array as skipped --- examples/js_dsl/mod.test.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/js_dsl/mod.test.ts b/examples/js_dsl/mod.test.ts index 031fb20..e6043ac 100644 --- a/examples/js_dsl/mod.test.ts +++ b/examples/js_dsl/mod.test.ts @@ -290,15 +290,17 @@ describe("mixed DSL + N-API", () => { expect(mod.typedArrayInfoMatches(view, backing, 3, 4)).toBe(true); }); - it("rejects unsupported TypedArray element types", () => { - const Float16ArrayCtor = Reflect.get(globalThis, "Float16Array"); - if (typeof Float16ArrayCtor !== "function") return; - const view = Reflect.construct(Float16ArrayCtor, [3]) as Uint16Array; - - expect(() => - mod.typedArrayInfoMatches(view, view.buffer, 3, 0), - ).toThrow("UnsupportedTypedarrayType"); - }); + const Float16ArrayCtor = Reflect.get(globalThis, "Float16Array"); + it.skipIf(typeof Float16ArrayCtor !== "function")( + "rejects unsupported TypedArray element types", + () => { + const view = Reflect.construct(Float16ArrayCtor, [3]) as Uint16Array; + + expect(() => + mod.typedArrayInfoMatches(view, view.buffer, 3, 0), + ).toThrow("UnsupportedTypedarrayType"); + }, + ); it("returns a DataView's backing ArrayBuffer and range", () => { const backing = new ArrayBuffer(16); From aea004339eef47c9c9d649e63878f508ab127170 Mon Sep 17 00:00:00 2001 From: Chen Kai <281165273grape@gmail.com> Date: Thu, 23 Jul 2026 15:34:23 +0800 Subject: [PATCH 6/6] fix(napi): normalize empty view data pointers (#56) ## Motivation Node-API permits ArrayBuffer, Buffer, TypedArray, and DataView data out parameters to be `NULL` or an arbitrary pointer when the reported length is zero. This commonly occurs for empty and detached values. The previous binding received those results into non-null `[*]u8` storage, so a valid Node-API result could create a zero-length Zig slice whose pointer violates the slice type invariant. Normalizing this case at the binding boundary keeps the public slice API safe, while the TypedArray zero-length branch avoids applying element-alignment casts to an empty byte-slice sentinel. ## Summary - represent Node-API `void*` data out parameters as nullable Zig pointers - normalize successful zero-length results to safe empty slices without changing public return types - skip TypedArray alignment casts when the element length is zero - cover shared null-plus-zero normalization in Zig and exercise empty and detached values through the example API --- examples/js_dsl/mod.test.ts | 47 +++++++++++++++++++++++++++++++++++++ examples/js_dsl/mod.zig | 10 ++++++++ src/Value.zig | 37 +++++++++++++++++++---------- src/js/typed_arrays.zig | 1 + 4 files changed, 83 insertions(+), 12 deletions(-) diff --git a/examples/js_dsl/mod.test.ts b/examples/js_dsl/mod.test.ts index e6043ac..06dcd69 100644 --- a/examples/js_dsl/mod.test.ts +++ b/examples/js_dsl/mod.test.ts @@ -167,6 +167,21 @@ describe("typed arrays", () => { expect(Array.from(result)).toEqual([2.5, 5.0, 7.5]); }); + it("accepts empty Uint8Array and Float64Array values", () => { + expect(mod.uint8Sum(new Uint8Array(0))).toEqual(0); + const result = mod.float64Scale(new Float64Array(0), 2.5); + expect(result).toBeInstanceOf(Float64Array); + expect(result).toHaveLength(0); + }); + + it("accepts a detached Uint8Array value", () => { + const backing = new ArrayBuffer(8); + const view = new Uint8Array(backing, 2, 3); + structuredClone(backing, { transfer: [backing] }); + + expect(mod.uint8Sum(view)).toEqual(0); + }); + it("allocUint8 allocates and fills via alloc pattern", () => { const result = mod.allocUint8(5); expect(result).toBeInstanceOf(Uint8Array); @@ -309,6 +324,38 @@ describe("mixed DSL + N-API", () => { expect(mod.dataViewInfoMatches(view, backing, 6, 4)).toBe(true); }); + it("reports zero byte length for empty and detached ArrayBuffers", () => { + expect(mod.arrayBufferByteLength(new ArrayBuffer(0))).toEqual(0); + + const detached = new ArrayBuffer(8); + structuredClone(detached, { transfer: [detached] }); + expect(mod.arrayBufferByteLength(detached)).toEqual(0); + }); + + it("reports zero byte length for an empty Buffer", () => { + expect(mod.bufferByteLength(Buffer.alloc(0))).toEqual(0); + }); + + it("reports zero range for empty and detached TypedArrays", () => { + const empty = new Uint8Array(0); + expect(mod.typedArrayInfoMatches(empty, empty.buffer, 0, 0)).toBe(true); + + const backing = new ArrayBuffer(16); + const view = new Uint16Array(backing, 4, 3); + structuredClone(backing, { transfer: [backing] }); + expect(mod.typedArrayInfoMatches(view, backing, 0, 0)).toBe(true); + }); + + it("reports zero range for empty and detached DataViews", () => { + const empty = new DataView(new ArrayBuffer(0)); + expect(mod.dataViewInfoMatches(empty, empty.buffer, 0, 0)).toBe(true); + + const backing = new ArrayBuffer(16); + const view = new DataView(backing, 4, 6); + structuredClone(backing, { transfer: [backing] }); + expect(mod.dataViewInfoMatches(view, backing, 0, 0)).toBe(true); + }); + it("randomBytes16 uses js.io() to produce a Uint8Array", () => { const bytes = mod.randomBytes16(); expect(bytes).toBeInstanceOf(Uint8Array); diff --git a/examples/js_dsl/mod.zig b/examples/js_dsl/mod.zig index ec9a076..9387dd9 100644 --- a/examples/js_dsl/mod.zig +++ b/examples/js_dsl/mod.zig @@ -350,6 +350,16 @@ pub fn nodeRelease() !String { return String.from(std.mem.span(release)); } +pub fn arrayBufferByteLength(value: Value) !Number { + const bytes = try value.toValue().getArrayBufferInfo(); + return Number.from(bytes.len); +} + +pub fn bufferByteLength(value: Value) !Number { + const bytes = try value.toValue().getBufferInfo(); + return Number.from(bytes.len); +} + pub fn typedArrayInfoMatches( value: Value, expected_arraybuffer: Value, diff --git a/src/Value.zig b/src/Value.zig index a6cae2a..2429bcd 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -13,6 +13,13 @@ value: c.napi_value, const Value = @This(); +fn byteSlice(data: ?*anyopaque, byte_length: usize) []u8 { + if (byte_length == 0) return &.{}; + // The N-API zero-length case, where data may be null or arbitrary, was handled above. + const byte_ptr: [*]u8 = @ptrCast(data.?); + return byte_ptr[0..byte_length]; +} + /// https://nodejs.org/api/n-api.html#napi_is_array pub fn isArray(self: Value) NapiError!bool { var is_array: bool = undefined; @@ -107,22 +114,22 @@ pub fn getArrayLength(self: Value) NapiError!u32 { /// https://nodejs.org/api/n-api.html#napi_get_arraybuffer_info pub fn getArrayBufferInfo(self: Value) NapiError![]u8 { - var data: [*]u8 = undefined; + var data: ?*anyopaque = undefined; var byte_length: usize = undefined; try status.check( - c.napi_get_arraybuffer_info(self.env, self.value, @ptrCast(&data), &byte_length), + c.napi_get_arraybuffer_info(self.env, self.value, &data, &byte_length), ); - return data[0..byte_length]; + return byteSlice(data, byte_length); } /// https://nodejs.org/api/n-api.html#napi_get_buffer_info pub fn getBufferInfo(self: Value) NapiError![]u8 { - var data: [*]u8 = undefined; + var data: ?*anyopaque = undefined; var byte_length: usize = undefined; try status.check( - c.napi_get_buffer_info(self.env, self.value, @ptrCast(&data), &byte_length), + c.napi_get_buffer_info(self.env, self.value, &data, &byte_length), ); - return data[0..byte_length]; + return byteSlice(data, byte_length); } /// https://nodejs.org/api/n-api.html#napi_get_prototype @@ -151,7 +158,7 @@ pub const TypedarrayInfoError = NapiError || error{ pub fn getTypedarrayInfo(self: Value) TypedarrayInfoError!TypedarrayInfo { var array_type_raw: c.napi_typedarray_type = undefined; var length: usize = undefined; - var data: [*]u8 = undefined; + var data: ?*anyopaque = undefined; var arraybuffer: c.napi_value = undefined; var byte_offset: usize = undefined; try status.check( @@ -160,7 +167,7 @@ pub fn getTypedarrayInfo(self: Value) TypedarrayInfoError!TypedarrayInfo { self.value, &array_type_raw, &length, - @ptrCast(&data), + &data, &arraybuffer, &byte_offset, ), @@ -170,7 +177,7 @@ pub fn getTypedarrayInfo(self: Value) TypedarrayInfoError!TypedarrayInfo { return .{ .array_type = array_type, .length = length, - .data = data[0 .. length * array_type.elementSize()], + .data = byteSlice(data, length * array_type.elementSize()), .arraybuffer = .{ .env = self.env, .value = arraybuffer, @@ -189,7 +196,7 @@ pub const DataViewInfo = struct { /// https://nodejs.org/api/n-api.html#napi_get_dataview_info pub fn getDataviewInfo(self: Value) NapiError!DataViewInfo { var byte_length: usize = undefined; - var data: [*]u8 = undefined; + var data: ?*anyopaque = undefined; var arraybuffer: c.napi_value = undefined; var byte_offset: usize = undefined; try status.check( @@ -197,14 +204,14 @@ pub fn getDataviewInfo(self: Value) NapiError!DataViewInfo { self.env, self.value, &byte_length, - @ptrCast(&data), + &data, &arraybuffer, &byte_offset, ), ); return .{ .byte_length = byte_length, - .data = data[0..byte_length], + .data = byteSlice(data, byte_length), .arraybuffer = .{ .env = self.env, .value = arraybuffer, @@ -569,3 +576,9 @@ pub fn objectSeal(self: Value) NapiError!void { c.napi_object_seal(self.env, self.value), ); } + +test "byteSlice normalizes null data for zero byte length" { + const bytes = byteSlice(null, 0); + + try std.testing.expectEqual(@as(usize, 0), bytes.len); +} diff --git a/src/js/typed_arrays.zig b/src/js/typed_arrays.zig index 2f1f365..87eb5cd 100644 --- a/src/js/typed_arrays.zig +++ b/src/js/typed_arrays.zig @@ -44,6 +44,7 @@ pub fn TypedArray(comptime Element: type, comptime array_type: TypedarrayType) t pub fn toSlice(self: Self) ![]Element { const info = try self.val.getTypedarrayInfo(); if (info.array_type != array_type) return error.TypeMismatch; + if (info.length == 0) return &.{}; const byte_ptr: [*]u8 = info.data.ptr; const typed_ptr: [*]Element = @ptrCast(@alignCast(byte_ptr)); return typed_ptr[0..info.length];