feat: add SharedArrayBuffer (#1688)
diff --git a/doc/README.md b/doc/README.md
index bad1a5c..e5e24df 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -60,6 +60,7 @@
- [ClassPropertyDescriptor](class_property_descriptor.md)
- [Buffer](buffer.md)
- [ArrayBuffer](array_buffer.md)
+ - [SharedArrayBuffer](shared_array_buffer.md)
- [TypedArray](typed_array.md)
- [TypedArrayOf](typed_array_of.md)
- [DataView](dataview.md)
diff --git a/doc/shared_array_buffer.md b/doc/shared_array_buffer.md
new file mode 100644
index 0000000..872dbb4
--- /dev/null
+++ b/doc/shared_array_buffer.md
@@ -0,0 +1,65 @@
+# SharedArrayBuffer
+
+Class `Napi::SharedArrayBuffer` inherits from class [`Napi::Object`][].
+
+The `Napi::SharedArrayBuffer` class corresponds to the
+[JavaScript `SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)
+class.
+
+**NOTE**: The support for `Napi::SharedArrayBuffer` is only available when using
+`NAPI_EXPERIMENTAL` and building against Node.js headers that support this
+feature.
+
+## Methods
+
+### New
+
+Allocates a new `Napi::SharedArrayBuffer` instance with a given length.
+
+```cpp
+static Napi::SharedArrayBuffer Napi::SharedArrayBuffer::New(napi_env env, size_t byteLength);
+```
+
+- `[in] env`: The environment in which to create the `Napi::SharedArrayBuffer`
+ instance.
+- `[in] byteLength`: The length to be allocated, in bytes.
+
+Returns a new `Napi::SharedArrayBuffer` instance.
+
+### Constructor
+
+Initializes an empty instance of the `Napi::SharedArrayBuffer` class.
+
+```cpp
+Napi::SharedArrayBuffer::SharedArrayBuffer();
+```
+
+### Constructor
+
+Initializes a wrapper instance of an existing `Napi::SharedArrayBuffer` object.
+
+```cpp
+Napi::SharedArrayBuffer::SharedArrayBuffer(napi_env env, napi_value value);
+```
+
+- `[in] env`: The environment in which to create the `Napi::SharedArrayBuffer`
+ instance.
+- `[in] value`: The `Napi::SharedArrayBuffer` reference to wrap.
+
+### ByteLength
+
+```cpp
+size_t Napi::SharedArrayBuffer::ByteLength() const;
+```
+
+Returns the length of the wrapped data, in bytes.
+
+### Data
+
+```cpp
+void* Napi::SharedArrayBuffer::Data() const;
+```
+
+Returns a pointer the wrapped data.
+
+[`Napi::Object`]: ./object.md
diff --git a/doc/value.md b/doc/value.md
index f19532f..f61a36e 100644
--- a/doc/value.md
+++ b/doc/value.md
@@ -268,6 +268,19 @@
Returns `true` if the underlying value is a JavaScript `Napi::Promise` or
`false` otherwise.
+### IsSharedArrayBuffer
+
+```cpp
+bool Napi::Value::IsSharedArrayBuffer() const;
+```
+
+Returns `true` if the underlying value is a JavaScript
+`Napi::IsSharedArrayBuffer` or `false` otherwise.
+
+**NOTE**: The support for `Napi::SharedArrayBuffer` is only available when using
+`NAPI_EXPERIMENTAL` and building against Node.js headers that support this
+feature.
+
### IsString
```cpp
diff --git a/napi-inl.h b/napi-inl.h
index 54651c1..94574fc 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -934,6 +934,19 @@
return Type() == napi_external;
}
+#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
+inline bool Value::IsSharedArrayBuffer() const {
+ if (IsEmpty()) {
+ return false;
+ }
+
+ bool result;
+ napi_status status = node_api_is_sharedarraybuffer(_env, _value, &result);
+ NAPI_THROW_IF_FAILED(_env, status, false);
+ return result;
+}
+#endif
+
template <typename T>
inline T Value::As() const {
#ifdef NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS
@@ -2068,6 +2081,55 @@
return result;
}
+#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
+////////////////////////////////////////////////////////////////////////////////
+// SharedArrayBuffer class
+////////////////////////////////////////////////////////////////////////////////
+
+inline SharedArrayBuffer::SharedArrayBuffer() : Object() {}
+
+inline SharedArrayBuffer::SharedArrayBuffer(napi_env env, napi_value value)
+ : Object(env, value) {}
+
+inline void SharedArrayBuffer::CheckCast(napi_env env, napi_value value) {
+ NAPI_CHECK(value != nullptr, "SharedArrayBuffer::CheckCast", "empty value");
+
+ bool result;
+ napi_status status = node_api_is_sharedarraybuffer(env, value, &result);
+ NAPI_CHECK(status == napi_ok,
+ "SharedArrayBuffer::CheckCast",
+ "node_api_is_sharedarraybuffer failed");
+ NAPI_CHECK(
+ result, "SharedArrayBuffer::CheckCast", "value is not sharedarraybuffer");
+}
+
+inline SharedArrayBuffer SharedArrayBuffer::New(napi_env env,
+ size_t byteLength) {
+ napi_value value;
+ void* data;
+ napi_status status =
+ node_api_create_sharedarraybuffer(env, byteLength, &data, &value);
+ NAPI_THROW_IF_FAILED(env, status, SharedArrayBuffer());
+
+ return SharedArrayBuffer(env, value);
+}
+
+inline void* SharedArrayBuffer::Data() {
+ void* data;
+ napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr);
+ NAPI_THROW_IF_FAILED(_env, status, nullptr);
+ return data;
+}
+
+inline size_t SharedArrayBuffer::ByteLength() {
+ size_t length;
+ napi_status status =
+ napi_get_arraybuffer_info(_env, _value, nullptr, &length);
+ NAPI_THROW_IF_FAILED(_env, status, 0);
+ return length;
+}
+#endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
+
////////////////////////////////////////////////////////////////////////////////
// ArrayBuffer class
////////////////////////////////////////////////////////////////////////////////
diff --git a/napi.h b/napi.h
index ba0e134..013a911 100644
--- a/napi.h
+++ b/napi.h
@@ -543,6 +543,9 @@
bool IsDataView() const; ///< Tests if a value is a JavaScript data view.
bool IsBuffer() const; ///< Tests if a value is a Node buffer.
bool IsExternal() const; ///< Tests if a value is a pointer to external data.
+#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
+ bool IsSharedArrayBuffer() const;
+#endif
/// Casts to another type of `Napi::Value`, when the actual type is known or
/// assumed.
@@ -1202,6 +1205,21 @@
};
#endif // NODE_ADDON_API_CPP_EXCEPTIONS
+#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
+class SharedArrayBuffer : public Object {
+ public:
+ SharedArrayBuffer();
+ SharedArrayBuffer(napi_env env, napi_value value);
+
+ static SharedArrayBuffer New(napi_env env, size_t byteLength);
+
+ static void CheckCast(napi_env env, napi_value value);
+
+ void* Data();
+ size_t ByteLength();
+};
+#endif
+
/// A JavaScript array buffer value.
class ArrayBuffer : public Object {
public:
diff --git a/test/binding.cc b/test/binding.cc
index 9e5aaaa..fa651cc 100644
--- a/test/binding.cc
+++ b/test/binding.cc
@@ -64,6 +64,7 @@
Object InitTypedThreadSafeFunctionUnref(Env env);
Object InitTypedThreadSafeFunction(Env env);
#endif
+Object InitSharedArrayBuffer(Env env);
Object InitSymbol(Env env);
Object InitTypedArray(Env env);
Object InitGlobalObject(Env env);
@@ -140,6 +141,7 @@
exports.Set("promise", InitPromise(env));
exports.Set("run_script", InitRunScript(env));
exports.Set("symbol", InitSymbol(env));
+ exports.Set("sharedarraybuffer", InitSharedArrayBuffer(env));
#if (NAPI_VERSION > 3)
exports.Set("threadsafe_function_ctx", InitThreadSafeFunctionCtx(env));
exports.Set("threadsafe_function_exception",
@@ -194,6 +196,12 @@
"isExperimental",
Napi::Boolean::New(env, NAPI_VERSION == NAPI_VERSION_EXPERIMENTAL));
+#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
+ exports.Set("hasSharedArrayBuffer", Napi::Boolean::New(env, true));
+#else
+ exports.Set("hasSharedArrayBuffer", Napi::Boolean::New(env, false));
+#endif
+
return exports;
}
diff --git a/test/binding.gyp b/test/binding.gyp
index 8ee391f..9ff334b 100644
--- a/test/binding.gyp
+++ b/test/binding.gyp
@@ -54,6 +54,7 @@
'object/subscript_operator.cc',
'promise.cc',
'run_script.cc',
+ 'shared_array_buffer.cc',
'symbol.cc',
'threadsafe_function/threadsafe_function_ctx.cc',
'threadsafe_function/threadsafe_function_exception.cc',
diff --git a/test/shared_array_buffer.cc b/test/shared_array_buffer.cc
new file mode 100644
index 0000000..57f6649
--- /dev/null
+++ b/test/shared_array_buffer.cc
@@ -0,0 +1,104 @@
+#include "napi.h"
+
+using namespace Napi;
+
+namespace {
+
+#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
+Value TestIsSharedArrayBuffer(const CallbackInfo& info) {
+ if (info.Length() < 1) {
+ Error::New(info.Env(), "Wrong number of arguments")
+ .ThrowAsJavaScriptException();
+ return Value();
+ }
+
+ return Boolean::New(info.Env(), info[0].IsSharedArrayBuffer());
+}
+
+Value TestCreateSharedArrayBuffer(const CallbackInfo& info) {
+ if (info.Length() < 1) {
+ Error::New(info.Env(), "Wrong number of arguments")
+ .ThrowAsJavaScriptException();
+ return Value();
+ } else if (!info[0].IsNumber()) {
+ Error::New(info.Env(),
+ "Wrong type of arguments. Expects a number as first argument.")
+ .ThrowAsJavaScriptException();
+ return Value();
+ }
+
+ auto byte_length = info[0].As<Number>().Uint32Value();
+ if (byte_length == 0) {
+ Error::New(info.Env(),
+ "Invalid byte length. Expects a non-negative integer.")
+ .ThrowAsJavaScriptException();
+ return Value();
+ }
+
+ return SharedArrayBuffer::New(info.Env(), byte_length);
+}
+
+Value TestGetSharedArrayBufferInfo(const CallbackInfo& info) {
+ if (info.Length() < 1) {
+ Error::New(info.Env(), "Wrong number of arguments")
+ .ThrowAsJavaScriptException();
+ return Value();
+ } else if (!info[0].IsSharedArrayBuffer()) {
+ Error::New(info.Env(),
+ "Wrong type of arguments. Expects a SharedArrayBuffer as first "
+ "argument.")
+ .ThrowAsJavaScriptException();
+ return Value();
+ }
+
+ auto byte_length = info[0].As<SharedArrayBuffer>().ByteLength();
+
+ return Number::New(info.Env(), byte_length);
+}
+
+Value TestSharedArrayBufferData(const CallbackInfo& info) {
+ if (info.Length() < 1) {
+ Error::New(info.Env(), "Wrong number of arguments")
+ .ThrowAsJavaScriptException();
+ return Value();
+ } else if (!info[0].IsSharedArrayBuffer()) {
+ Error::New(info.Env(),
+ "Wrong type of arguments. Expects a SharedArrayBuffer as first "
+ "argument.")
+ .ThrowAsJavaScriptException();
+ return Value();
+ }
+
+ auto byte_length = info[0].As<SharedArrayBuffer>().ByteLength();
+ void* data = info[0].As<SharedArrayBuffer>().Data();
+
+ if (byte_length > 0 && data != nullptr) {
+ uint8_t* bytes = static_cast<uint8_t*>(data);
+ for (size_t i = 0; i < byte_length; i++) {
+ bytes[i] = i % 256;
+ }
+
+ return Boolean::New(info.Env(), true);
+ }
+
+ return Boolean::New(info.Env(), false);
+}
+#endif
+} // end anonymous namespace
+
+Object InitSharedArrayBuffer(Env env) {
+ Object exports = Object::New(env);
+
+#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
+ exports["testIsSharedArrayBuffer"] =
+ Function::New(env, TestIsSharedArrayBuffer);
+ exports["testCreateSharedArrayBuffer"] =
+ Function::New(env, TestCreateSharedArrayBuffer);
+ exports["testGetSharedArrayBufferInfo"] =
+ Function::New(env, TestGetSharedArrayBufferInfo);
+ exports["testSharedArrayBufferData"] =
+ Function::New(env, TestSharedArrayBufferData);
+#endif
+
+ return exports;
+}
diff --git a/test/shared_array_buffer.js b/test/shared_array_buffer.js
new file mode 100644
index 0000000..018021a
--- /dev/null
+++ b/test/shared_array_buffer.js
@@ -0,0 +1,55 @@
+'use strict';
+
+const assert = require('assert');
+
+module.exports = require('./common').runTest(test);
+
+let skippedMessageShown = false;
+
+function test ({ hasSharedArrayBuffer, sharedarraybuffer }) {
+ if (!hasSharedArrayBuffer) {
+ if (!skippedMessageShown) {
+ console.log(' >Skipped (no SharedArrayBuffer support)');
+ skippedMessageShown = true;
+ }
+ return;
+ }
+
+ {
+ const sab = new SharedArrayBuffer(16);
+ const ab = new ArrayBuffer(16);
+ const obj = {};
+ const arr = [];
+
+ assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(sab), true);
+ assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(ab), false);
+ assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(obj), false);
+ assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(arr), false);
+ assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(null), false);
+ assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(undefined), false);
+ }
+
+ {
+ const sab = sharedarraybuffer.testCreateSharedArrayBuffer(16);
+ assert(sab instanceof SharedArrayBuffer);
+ assert.strictEqual(sab.byteLength, 16);
+ }
+
+ {
+ const sab = new SharedArrayBuffer(32);
+ const byteLength = sharedarraybuffer.testGetSharedArrayBufferInfo(sab);
+ assert.strictEqual(byteLength, 32);
+ }
+
+ {
+ const sab = new SharedArrayBuffer(8);
+ const result = sharedarraybuffer.testSharedArrayBufferData(sab);
+ assert.strictEqual(result, true);
+
+ // Check if data was written correctly
+ const view = new Uint8Array(sab);
+ for (let i = 0; i < 8; i++) {
+ assert.strictEqual(view[i], i % 256);
+ }
+ }
+}
diff --git a/test/value_type_cast.cc b/test/value_type_cast.cc
index 9a140d2..dfc03b3 100644
--- a/test/value_type_cast.cc
+++ b/test/value_type_cast.cc
@@ -27,6 +27,11 @@
#define V(Type) \
void TypeCast##Type(const CallbackInfo& info) { USE(info[0].As<Type>()); }
TYPE_CAST_TYPES(V)
+
+#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
+V(SharedArrayBuffer)
+#endif
+
#undef V
void TypeCastBuffer(const CallbackInfo& info) {
@@ -47,6 +52,11 @@
#define V(Type) exports["typeCast" #Type] = Function::New(env, TypeCast##Type);
TYPE_CAST_TYPES(V)
+
+#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
+ V(SharedArrayBuffer)
+#endif
+
#undef V
exports["typeCastBuffer"] = Function::New(env, TypeCastBuffer);
diff --git a/test/value_type_cast.js b/test/value_type_cast.js
index cdebd4e..274dd27 100644
--- a/test/value_type_cast.js
+++ b/test/value_type_cast.js
@@ -42,7 +42,7 @@
},
typeCastArrayBuffer: {
positiveValues: [new ArrayBuffer(0)],
- negativeValues: [new Uint8Array(1), {}, [], null, undefined]
+ negativeValues: [new Uint8Array(1), new SharedArrayBuffer(0), {}, [], null, undefined]
},
typeCastTypedArray: {
positiveValues: [new Uint8Array(0)],
@@ -77,6 +77,13 @@
}
};
+ if ('typeCastSharedArrayBuffer' in binding) {
+ testTable.typeCastSharedArrayBuffer = {
+ positiveValues: [new SharedArrayBuffer(0)],
+ negativeValues: [new Uint8Array(1), new ArrayBuffer(0), {}, [], null, undefined]
+ };
+ }
+
if (process.argv[2] === 'child') {
child(binding, testTable, process.argv[3], process.argv[4], parseInt(process.argv[5]));
return;