Skip to content

Commit 7b8d69e

Browse files
authored
feat: add support for SharedArrayBuffer in DataViews (#1714)
1 parent f8210c6 commit 7b8d69e

File tree

5 files changed

+225
-22
lines changed

5 files changed

+225
-22
lines changed

doc/dataview.md

Lines changed: 63 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,22 @@ 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. Use `Buffer()` instead to get the backing buffer without assuming its
131+
type.
132+
133+
### Buffer
134+
135+
```cpp
136+
Napi::Value Napi::DataView::Buffer() const;
137+
```
138+
139+
Returns the backing array buffer as a generic `Napi::Value`, allowing optional
140+
type-checking with `Is*()` and type-casting with `As<>()` methods.
79141

80142
### ByteOffset
81143

napi-inl.h

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,6 +2302,39 @@ inline DataView DataView::New(napi_env env,
23022302
return DataView(env, value);
23032303
}
23042304

2305+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
2306+
inline DataView DataView::New(napi_env env,
2307+
Napi::SharedArrayBuffer arrayBuffer) {
2308+
return New(env, arrayBuffer, 0, arrayBuffer.ByteLength());
2309+
}
2310+
2311+
inline DataView DataView::New(napi_env env,
2312+
Napi::SharedArrayBuffer arrayBuffer,
2313+
size_t byteOffset) {
2314+
if (byteOffset > arrayBuffer.ByteLength()) {
2315+
NAPI_THROW(RangeError::New(
2316+
env, "Start offset is outside the bounds of the buffer"),
2317+
DataView());
2318+
}
2319+
return New(
2320+
env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset);
2321+
}
2322+
2323+
inline DataView DataView::New(napi_env env,
2324+
Napi::SharedArrayBuffer arrayBuffer,
2325+
size_t byteOffset,
2326+
size_t byteLength) {
2327+
if (byteOffset + byteLength > arrayBuffer.ByteLength()) {
2328+
NAPI_THROW(RangeError::New(env, "Invalid DataView length"), DataView());
2329+
}
2330+
napi_value value;
2331+
napi_status status =
2332+
napi_create_dataview(env, byteLength, arrayBuffer, byteOffset, &value);
2333+
NAPI_THROW_IF_FAILED(env, status, DataView());
2334+
return DataView(env, value);
2335+
}
2336+
#endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
2337+
23052338
inline void DataView::CheckCast(napi_env env, napi_value value) {
23062339
NAPI_CHECK(value != nullptr, "DataView::CheckCast", "empty value");
23072340

@@ -2325,15 +2358,19 @@ inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) {
23252358
}
23262359

23272360
inline Napi::ArrayBuffer DataView::ArrayBuffer() const {
2361+
return Buffer().As<Napi::ArrayBuffer>();
2362+
}
2363+
2364+
inline Napi::Value DataView::Buffer() const {
23282365
napi_value arrayBuffer;
23292366
napi_status status = napi_get_dataview_info(_env,
23302367
_value /* dataView */,
23312368
nullptr /* byteLength */,
23322369
nullptr /* data */,
23332370
&arrayBuffer /* arrayBuffer */,
23342371
nullptr /* byteOffset */);
2335-
NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer());
2336-
return Napi::ArrayBuffer(_env, arrayBuffer);
2372+
NAPI_THROW_IF_FAILED(_env, status, Napi::Value());
2373+
return Napi::Value(_env, arrayBuffer);
23372374
}
23382375

23392376
inline size_t DataView::ByteOffset() const {

napi.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1456,13 +1456,37 @@ class DataView : public Object {
14561456
size_t byteOffset,
14571457
size_t byteLength);
14581458

1459+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
1460+
static DataView New(napi_env env, Napi::SharedArrayBuffer arrayBuffer);
1461+
static DataView New(napi_env env,
1462+
Napi::SharedArrayBuffer arrayBuffer,
1463+
size_t byteOffset);
1464+
static DataView New(napi_env env,
1465+
Napi::SharedArrayBuffer arrayBuffer,
1466+
size_t byteOffset,
1467+
size_t byteLength);
1468+
#endif
1469+
14591470
static void CheckCast(napi_env env, napi_value value);
14601471

14611472
DataView(); ///< Creates a new _empty_ DataView instance.
14621473
DataView(napi_env env,
14631474
napi_value value); ///< Wraps a Node-API value primitive.
14641475

1465-
Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer.
1476+
// Gets the backing `ArrayBuffer`.
1477+
//
1478+
// If this `DataView` is not backed by an `ArrayBuffer`, this method will
1479+
// terminate the process with a fatal error when using
1480+
// `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior
1481+
// otherwise. Use `Buffer()` instead to get the backing buffer without
1482+
// assuming its type.
1483+
Napi::ArrayBuffer ArrayBuffer() const;
1484+
1485+
// Gets the backing buffer (an `ArrayBuffer` or `SharedArrayBuffer`).
1486+
//
1487+
// Use `IsArrayBuffer()` or `IsSharedArrayBuffer()` to check the type of the
1488+
// backing buffer prior to casting with `As<T>()`.
1489+
Napi::Value Buffer() const;
14661490
size_t ByteOffset()
14671491
const; ///< Gets the offset into the buffer where the array starts.
14681492
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)