diff --git a/implementors/node/features.js b/implementors/node/features.js index fec2137..f3d4d79 100644 --- a/implementors/node/features.js +++ b/implementors/node/features.js @@ -31,4 +31,13 @@ globalThis.runtimeFeatures = { major > 25 || (major === 25 && minor >= 4) || (major === 24 && (minor > 13 || (minor === 13 && patch >= 1))), + + // napi_create_typedarray accepts a SharedArrayBuffer-backed buffer only + // since Node.js v26.2.0 and v24.18.0 (nodejs/node#62710). The v25.x line + // saw no release after that landed, and it was not backported to v20.x or + // v22.x, where such calls fail with "invalid argument". + typedarraySharedArrayBuffer: + major > 26 || + (major === 26 && minor >= 2) || + (major === 24 && minor >= 18), }; diff --git a/tests/js-native-api/test_typedarray/CMakeLists.txt b/tests/js-native-api/test_typedarray/CMakeLists.txt index e6aa327..94cf12e 100644 --- a/tests/js-native-api/test_typedarray/CMakeLists.txt +++ b/tests/js-native-api/test_typedarray/CMakeLists.txt @@ -1 +1,2 @@ add_node_api_cts_addon(test_typedarray test_typedarray.c) +add_node_api_cts_addon(test_typedarray_sharedarraybuffer test_typedarray_sharedarraybuffer.c) diff --git a/tests/js-native-api/test_typedarray/test_sharedarraybuffer.js b/tests/js-native-api/test_typedarray/test_sharedarraybuffer.js new file mode 100644 index 0000000..9ac6d5e --- /dev/null +++ b/tests/js-native-api/test_typedarray/test_sharedarraybuffer.js @@ -0,0 +1,117 @@ +// napi_create_typedarray accepts a SharedArrayBuffer-backed buffer only on +// newer Node.js releases (see implementors/node/features.js). +if (!runtimeFeatures.typedarraySharedArrayBuffer) { + skipTest(); +} + +// Verify SharedArrayBuffer-backed typed arrays can be created through +// napi_create_typedarray, while preserving the existing ArrayBuffer behavior. +const addon = loadAddon('test_typedarray_sharedarraybuffer'); + +// Float16Array is an ES2025 addition, not exposed by default in older V8 +// versions. See the longer note in test.js. +const hasFloat16Array = typeof Float16Array !== 'undefined'; + +const typedArrayCases = [ + { type: Int8Array, values: [-1, 0, 127] }, + { type: Uint8Array, values: [1, 2, 255] }, + { type: Uint8ClampedArray, values: [0, 128, 255] }, + { type: Int16Array, values: [-1, 0, 32767] }, + { type: Uint16Array, values: [1, 2, 65535] }, + { type: Int32Array, values: [-1, 0, 123456789] }, + { type: Uint32Array, values: [1, 2, 4294967295] }, + ...(hasFloat16Array ? [{ type: Float16Array, values: [0.5, -1.5, 42.25] }] : []), + { type: Float32Array, values: [0.5, -1.5, 42.25] }, + { type: Float64Array, values: [0.5, -1.5, 42.25] }, + { type: BigInt64Array, values: [1n, -2n, 123456789n] }, + { type: BigUint64Array, values: [1n, 2n, 123456789n] }, +]; + +function createBuffer(Type, BufferType, length) { + const byteOffset = Type.BYTES_PER_ELEMENT; + const byteLength = byteOffset + (length * Type.BYTES_PER_ELEMENT); + return { + buffer: new BufferType(byteLength), + byteOffset, + }; +} + +function verifyTypedArray(Type, buffer, byteOffset, values) { + const template = new Type(buffer, byteOffset, values.length); + const theArray = addon.CreateTypedArray(template, buffer); + const theArrayBuffer = addon.GetArrayBuffer(theArray); + + assert.ok(theArray instanceof Type); + assert.strictEqual(theArray.buffer, buffer); + assert.strictEqual(theArrayBuffer, buffer); + assert.strictEqual(theArray.byteOffset, byteOffset); + assert.strictEqual(theArray.length, values.length); + + theArray.set(values); + assert.deepStrictEqual( + Array.from(new Type(buffer, byteOffset, values.length)), + values, + ); +} + +// Keep the existing ArrayBuffer behavior covered while focusing this test +// on SharedArrayBuffer-backed TypedArray creation. +{ + const { buffer, byteOffset } = createBuffer(Uint8Array, ArrayBuffer, 3); + verifyTypedArray(Uint8Array, buffer, byteOffset, [1, 2, 3]); +} + +// Verify all TypedArray variants can be created from a SharedArrayBuffer. +for (const { type, values } of typedArrayCases) { + const { buffer, byteOffset } = createBuffer( + type, + SharedArrayBuffer, + values.length, + ); + verifyTypedArray(type, buffer, byteOffset, values); +} + +// Test for creating TypedArrays with SharedArrayBuffer and invalid range. +for (const { type, values } of typedArrayCases) { + const { buffer, byteOffset } = createBuffer( + type, + SharedArrayBuffer, + values.length, + ); + const template = new type(buffer, byteOffset, values.length); + + assert.throws(() => { + addon.CreateTypedArray(template, buffer, values.length + 1, byteOffset); + }, RangeError); +} + +// Test for creating TypedArrays with SharedArrayBuffer and invalid alignment. +for (const { type, values } of typedArrayCases) { + if (type.BYTES_PER_ELEMENT <= 1) { + continue; + } + + const { buffer, byteOffset } = createBuffer( + type, + SharedArrayBuffer, + values.length, + ); + const template = new type(buffer, byteOffset, values.length); + + assert.throws(() => { + addon.CreateTypedArray(template, buffer, 1, byteOffset + 1); + }, RangeError); +} + +// Test invalid arguments. +{ + const template = new Uint8Array(1); + + assert.throws(() => { + addon.CreateTypedArray(template, {}); + }, { name: 'Error', message: 'Invalid argument' }); + + assert.throws(() => { + addon.CreateTypedArray(template, 1); + }, { name: 'Error', message: 'Invalid argument' }); +} diff --git a/tests/js-native-api/test_typedarray/test_typedarray_sharedarraybuffer.c b/tests/js-native-api/test_typedarray/test_typedarray_sharedarraybuffer.c new file mode 100644 index 0000000..f03ccfc --- /dev/null +++ b/tests/js-native-api/test_typedarray/test_typedarray_sharedarraybuffer.c @@ -0,0 +1,79 @@ +// Verify napi_create_typedarray() accepts SharedArrayBuffer-backed views +// without changing its existing error handling. + +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value CreateTypedArray(napi_env env, napi_callback_info info) { + size_t argc = 4; + napi_value args[4]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 2 || argc == 4, "Wrong number of arguments"); + + bool is_typedarray; + NODE_API_CALL(env, napi_is_typedarray(env, args[0], &is_typedarray)); + NODE_API_ASSERT(env, + is_typedarray, + "Wrong type of arguments. Expects a typed array as first " + "argument."); + + napi_typedarray_type type; + size_t length; + size_t byte_offset; + NODE_API_CALL(env, + napi_get_typedarray_info( + env, args[0], &type, &length, NULL, NULL, &byte_offset)); + + if (argc == 4) { + uint32_t uint32_length; + NODE_API_CALL(env, napi_get_value_uint32(env, args[2], &uint32_length)); + length = uint32_length; + + uint32_t uint32_byte_offset; + NODE_API_CALL(env, + napi_get_value_uint32(env, args[3], &uint32_byte_offset)); + byte_offset = uint32_byte_offset; + } + + napi_value typedarray; + NODE_API_CALL(env, + napi_create_typedarray( + env, type, length, args[1], byte_offset, &typedarray)); + + return typedarray; +} + +static napi_value GetArrayBuffer(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); + + napi_value arraybuffer; + NODE_API_CALL(env, + napi_get_typedarray_info( + env, args[0], NULL, NULL, NULL, &arraybuffer, NULL)); + + return arraybuffer; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("CreateTypedArray", CreateTypedArray), + DECLARE_NODE_API_PROPERTY("GetArrayBuffer", GetArrayBuffer), + }; + + NODE_API_CALL( + env, + napi_define_properties(env, + exports, + sizeof(descriptors) / sizeof(*descriptors), + descriptors)); + + return exports; +} +EXTERN_C_END