Skip to content

Commit 87673ba

Browse files
committed
feat: add support for SharedArrayBuffer in DataViews
1 parent af7d42e commit 87673ba

File tree

5 files changed

+211
-21
lines changed

5 files changed

+211
-21
lines changed

doc/dataview.md

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ The `Napi::DataView` class corresponds to the
66
[JavaScript `DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView)
77
class.
88

9+
**NOTE**: The support for `Napi::DataView::New()` overloads accepting an
10+
`Napi::SharedArrayBuffer` parameter is only available when using
11+
`NAPI_EXPERIMENTAL` and building against Node.js headers that support this
12+
feature.
13+
914
## Methods
1015

1116
### New
@@ -50,6 +55,48 @@ static Napi::DataView Napi::DataView::New(napi_env env, Napi::ArrayBuffer arrayB
5055
5156
Returns a new `Napi::DataView` instance.
5257
58+
### New
59+
60+
Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`.
61+
62+
```cpp
63+
static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer);
64+
```
65+
66+
- `[in] env`: The environment in which to create the `Napi::DataView` instance.
67+
- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`.
68+
69+
Returns a new `Napi::DataView` instance.
70+
71+
### New
72+
73+
Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`.
74+
75+
```cpp
76+
static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer, size_t byteOffset);
77+
```
78+
79+
- `[in] env`: The environment in which to create the `Napi::DataView` instance.
80+
- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`.
81+
- `[in] byteOffset` : The byte offset within the `Napi::SharedArrayBuffer` from which to start projecting the `Napi::DataView`.
82+
83+
Returns a new `Napi::DataView` instance.
84+
85+
### New
86+
87+
Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`.
88+
89+
```cpp
90+
static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer, size_t byteOffset, size_t byteLength);
91+
```
92+
93+
- `[in] env`: The environment in which to create the `Napi::DataView` instance.
94+
- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`.
95+
- `[in] byteOffset` : The byte offset within the `Napi::SharedArrayBuffer` from which to start projecting the `Napi::DataView`.
96+
- `[in] byteLength` : Number of elements in the `Napi::DataView`.
97+
98+
Returns a new `Napi::DataView` instance.
99+
53100
### Constructor
54101

55102
Initializes an empty instance of the `Napi::DataView` class.
@@ -75,7 +122,21 @@ Napi::DataView(napi_env env, napi_value value);
75122
Napi::ArrayBuffer Napi::DataView::ArrayBuffer() const;
76123
```
77124

78-
Returns the backing array buffer.
125+
Returns the backing array buffer as an `Napi::ArrayBuffer`.
126+
127+
**NOTE**: If the `Napi::DataView` is not backed by an `Napi::ArrayBuffer`, this
128+
method will terminate the process with a fatal error when using
129+
`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior
130+
otherwise.
131+
132+
### Buffer
133+
134+
```cpp
135+
Napi::Value Napi::DataView::Buffer() const;
136+
```
137+
138+
Returns the backing array buffer as a generic `Napi::Value`, allowing optional
139+
type-checking with `Is*()` and type-casting with `As<>()` methods.
79140

80141
### ByteOffset
81142

napi-inl.h

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2289,6 +2289,39 @@ inline DataView DataView::New(napi_env env,
22892289
return DataView(env, value);
22902290
}
22912291

2292+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
2293+
inline DataView DataView::New(napi_env env,
2294+
Napi::SharedArrayBuffer arrayBuffer) {
2295+
return New(env, arrayBuffer, 0, arrayBuffer.ByteLength());
2296+
}
2297+
2298+
inline DataView DataView::New(napi_env env,
2299+
Napi::SharedArrayBuffer arrayBuffer,
2300+
size_t byteOffset) {
2301+
if (byteOffset > arrayBuffer.ByteLength()) {
2302+
NAPI_THROW(RangeError::New(
2303+
env, "Start offset is outside the bounds of the buffer"),
2304+
DataView());
2305+
}
2306+
return New(
2307+
env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset);
2308+
}
2309+
2310+
inline DataView DataView::New(napi_env env,
2311+
Napi::SharedArrayBuffer arrayBuffer,
2312+
size_t byteOffset,
2313+
size_t byteLength) {
2314+
if (byteOffset + byteLength > arrayBuffer.ByteLength()) {
2315+
NAPI_THROW(RangeError::New(env, "Invalid DataView length"), DataView());
2316+
}
2317+
napi_value value;
2318+
napi_status status =
2319+
napi_create_dataview(env, byteLength, arrayBuffer, byteOffset, &value);
2320+
NAPI_THROW_IF_FAILED(env, status, DataView());
2321+
return DataView(env, value);
2322+
}
2323+
#endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
2324+
22922325
inline void DataView::CheckCast(napi_env env, napi_value value) {
22932326
NAPI_CHECK(value != nullptr, "DataView::CheckCast", "empty value");
22942327

@@ -2312,15 +2345,19 @@ inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) {
23122345
}
23132346

23142347
inline Napi::ArrayBuffer DataView::ArrayBuffer() const {
2348+
return Buffer().As<Napi::ArrayBuffer>();
2349+
}
2350+
2351+
inline Napi::Value DataView::Buffer() const {
23152352
napi_value arrayBuffer;
23162353
napi_status status = napi_get_dataview_info(_env,
23172354
_value /* dataView */,
23182355
nullptr /* byteLength */,
23192356
nullptr /* data */,
23202357
&arrayBuffer /* arrayBuffer */,
23212358
nullptr /* byteOffset */);
2322-
NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer());
2323-
return Napi::ArrayBuffer(_env, arrayBuffer);
2359+
NAPI_THROW_IF_FAILED(_env, status, Napi::Value());
2360+
return Napi::Value(_env, arrayBuffer);
23242361
}
23252362

23262363
inline size_t DataView::ByteOffset() const {

napi.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,13 +1450,25 @@ class DataView : public Object {
14501450
size_t byteOffset,
14511451
size_t byteLength);
14521452

1453+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
1454+
static DataView New(napi_env env, Napi::SharedArrayBuffer arrayBuffer);
1455+
static DataView New(napi_env env,
1456+
Napi::SharedArrayBuffer arrayBuffer,
1457+
size_t byteOffset);
1458+
static DataView New(napi_env env,
1459+
Napi::SharedArrayBuffer arrayBuffer,
1460+
size_t byteOffset,
1461+
size_t byteLength);
1462+
#endif
1463+
14531464
static void CheckCast(napi_env env, napi_value value);
14541465

14551466
DataView(); ///< Creates a new _empty_ DataView instance.
14561467
DataView(napi_env env,
14571468
napi_value value); ///< Wraps a Node-API value primitive.
14581469

14591470
Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer.
1471+
Napi::Value Buffer() const;
14601472
size_t ByteOffset()
14611473
const; ///< Gets the offset into the buffer where the array starts.
14621474
size_t ByteLength() const; ///< Gets the length of the array in bytes.

test/dataview/dataview.cc

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,51 @@
22

33
using namespace Napi;
44

5-
static Value CreateDataView1(const CallbackInfo& info) {
5+
static Value CreateDataView(const CallbackInfo& info) {
66
ArrayBuffer arrayBuffer = info[0].As<ArrayBuffer>();
77
return DataView::New(info.Env(), arrayBuffer);
88
}
99

10-
static Value CreateDataView2(const CallbackInfo& info) {
10+
static Value CreateDataViewWithByteOffset(const CallbackInfo& info) {
1111
ArrayBuffer arrayBuffer = info[0].As<ArrayBuffer>();
1212
size_t byteOffset = info[1].As<Number>().Uint32Value();
1313
return DataView::New(info.Env(), arrayBuffer, byteOffset);
1414
}
1515

16-
static Value CreateDataView3(const CallbackInfo& info) {
16+
static Value CreateDataViewWithByteOffsetAndByteLength(
17+
const CallbackInfo& info) {
1718
ArrayBuffer arrayBuffer = info[0].As<ArrayBuffer>();
1819
size_t byteOffset = info[1].As<Number>().Uint32Value();
1920
size_t byteLength = info[2].As<Number>().Uint32Value();
2021
return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength);
2122
}
2223

24+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
25+
static Value CreateDataViewOnSharedArrayBuffer(const CallbackInfo& info) {
26+
SharedArrayBuffer arrayBuffer = info[0].As<SharedArrayBuffer>();
27+
return DataView::New(info.Env(), arrayBuffer);
28+
}
29+
30+
static Value CreateDataViewOnSharedArrayBufferWithByteOffset(
31+
const CallbackInfo& info) {
32+
SharedArrayBuffer arrayBuffer = info[0].As<SharedArrayBuffer>();
33+
size_t byteOffset = info[1].As<Number>().Uint32Value();
34+
return DataView::New(info.Env(), arrayBuffer, byteOffset);
35+
}
36+
37+
static Value CreateDataViewOnSharedArrayBufferWithByteOffsetAndByteLength(
38+
const CallbackInfo& info) {
39+
SharedArrayBuffer arrayBuffer = info[0].As<SharedArrayBuffer>();
40+
size_t byteOffset = info[1].As<Number>().Uint32Value();
41+
size_t byteLength = info[2].As<Number>().Uint32Value();
42+
return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength);
43+
}
44+
#endif
45+
46+
static Value GetBuffer(const CallbackInfo& info) {
47+
return info[0].As<DataView>().Buffer();
48+
}
49+
2350
static Value GetArrayBuffer(const CallbackInfo& info) {
2451
return info[0].As<DataView>().ArrayBuffer();
2552
}
@@ -37,10 +64,24 @@ static Value GetByteLength(const CallbackInfo& info) {
3764
Object InitDataView(Env env) {
3865
Object exports = Object::New(env);
3966

40-
exports["createDataView1"] = Function::New(env, CreateDataView1);
41-
exports["createDataView2"] = Function::New(env, CreateDataView2);
42-
exports["createDataView3"] = Function::New(env, CreateDataView3);
67+
exports["createDataView"] = Function::New(env, CreateDataView);
68+
exports["createDataViewWithByteOffset"] =
69+
Function::New(env, CreateDataViewWithByteOffset);
70+
exports["createDataViewWithByteOffsetAndByteLength"] =
71+
Function::New(env, CreateDataViewWithByteOffsetAndByteLength);
72+
73+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
74+
exports["createDataViewOnSharedArrayBuffer"] =
75+
Function::New(env, CreateDataViewOnSharedArrayBuffer);
76+
exports["createDataViewOnSharedArrayBufferWithByteOffset"] =
77+
Function::New(env, CreateDataViewOnSharedArrayBufferWithByteOffset);
78+
exports["createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength"] =
79+
Function::New(
80+
env, CreateDataViewOnSharedArrayBufferWithByteOffsetAndByteLength);
81+
#endif
82+
4383
exports["getArrayBuffer"] = Function::New(env, GetArrayBuffer);
84+
exports["getBuffer"] = Function::New(env, GetBuffer);
4485
exports["getByteOffset"] = Function::New(env, GetByteOffset);
4586
exports["getByteLength"] = Function::New(env, GetByteLength);
4687

test/dataview/dataview.js

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@
33
const assert = require('assert');
44
module.exports = require('../common').runTest(test);
55

6+
let runSharedArrayBufferTests = true;
7+
68
function test (binding) {
79
function testDataViewCreation (factory, arrayBuffer, offset, length) {
810
const view = factory(arrayBuffer, offset, length);
911
offset = offset || 0;
10-
assert.ok(dataview.getArrayBuffer(view) instanceof ArrayBuffer);
11-
assert.strictEqual(dataview.getArrayBuffer(view), arrayBuffer);
12+
if (arrayBuffer instanceof ArrayBuffer) {
13+
assert.ok(dataview.getArrayBuffer(view) instanceof ArrayBuffer);
14+
assert.strictEqual(dataview.getArrayBuffer(view), arrayBuffer);
15+
} else {
16+
assert.ok(dataview.getBuffer(view) instanceof SharedArrayBuffer);
17+
assert.strictEqual(dataview.getBuffer(view), arrayBuffer);
18+
}
1219
assert.strictEqual(dataview.getByteOffset(view), offset);
1320
assert.strictEqual(dataview.getByteLength(view),
1421
length || arrayBuffer.byteLength - offset);
@@ -20,16 +27,48 @@ function test (binding) {
2027
}, RangeError);
2128
}
2229

23-
const dataview = binding.dataview;
24-
const arrayBuffer = new ArrayBuffer(10);
30+
const { hasSharedArrayBuffer, dataview } = binding;
31+
32+
{
33+
const arrayBuffer = new ArrayBuffer(10);
34+
35+
testDataViewCreation(dataview.createDataView, arrayBuffer);
36+
testDataViewCreation(dataview.createDataViewWithByteOffset, arrayBuffer, 2);
37+
testDataViewCreation(dataview.createDataViewWithByteOffset, arrayBuffer, 10);
38+
testDataViewCreation(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 2, 4);
39+
testDataViewCreation(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 10, 0);
40+
41+
testInvalidRange(dataview.createDataViewWithByteOffset, arrayBuffer, 11);
42+
testInvalidRange(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 11, 0);
43+
testInvalidRange(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 6, 5);
44+
}
45+
46+
if (hasSharedArrayBuffer && runSharedArrayBufferTests) {
47+
const sab = new SharedArrayBuffer(10);
2548

26-
testDataViewCreation(dataview.createDataView1, arrayBuffer);
27-
testDataViewCreation(dataview.createDataView2, arrayBuffer, 2);
28-
testDataViewCreation(dataview.createDataView2, arrayBuffer, 10);
29-
testDataViewCreation(dataview.createDataView3, arrayBuffer, 2, 4);
30-
testDataViewCreation(dataview.createDataView3, arrayBuffer, 10, 0);
49+
try {
50+
testDataViewCreation(dataview.createDataViewOnSharedArrayBuffer, sab);
51+
} catch (ex) {
52+
// The `napi_create_dataview` API does not have a valid `#define`
53+
// preprocessor guard for SharedArrayBuffer support, so it is
54+
// possible that the API is present but creating a DataView on
55+
// SharedArrayBuffer is not supported in the current version of Node.js.
56+
// In that case, we should skip the test instead of throwing.
57+
if (ex.message === 'Invalid argument') {
58+
console.warn(`The current version of Node.js (${process.version}) does not support creating DataViews on SharedArrayBuffers; skipping tests.`);
59+
runSharedArrayBufferTests = false;
60+
return;
61+
}
3162

32-
testInvalidRange(dataview.createDataView2, arrayBuffer, 11);
33-
testInvalidRange(dataview.createDataView3, arrayBuffer, 11, 0);
34-
testInvalidRange(dataview.createDataView3, arrayBuffer, 6, 5);
63+
throw ex;
64+
}
65+
testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 2);
66+
testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 10);
67+
testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 2, 4);
68+
testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 10, 0);
69+
70+
testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 11);
71+
testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 11, 0);
72+
testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 6, 5);
73+
}
3574
}

0 commit comments

Comments
 (0)