Skip to content

Commit 08df548

Browse files
committed
stream: move byte-stream helpers to util binding
Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 0a1d641 commit 08df548

9 files changed

Lines changed: 204 additions & 181 deletions

File tree

lib/internal/webstreams/readablestream.js

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -690,10 +690,9 @@ class ReadableStreamBYOBRequest {
690690
'This BYOB request has been invalidated');
691691
}
692692

693-
const {
694-
buffer: viewBuffer,
695-
byteLength: viewByteLength,
696-
} = getArrayBufferView(view);
693+
const arrayBufferView = getArrayBufferView(view);
694+
const viewBuffer = arrayBufferView[0];
695+
const viewByteLength = arrayBufferView[2];
697696
const viewBufferByteLength = ArrayBufferPrototypeGetByteLength(viewBuffer);
698697

699698
if (ArrayBufferPrototypeGetDetached(viewBuffer)) {
@@ -984,10 +983,9 @@ class ReadableStreamBYOBReader {
984983
}
985984
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
986985

987-
const {
988-
buffer: viewBuffer,
989-
byteLength: viewByteLength,
990-
} = getArrayBufferView(view);
986+
const arrayBufferView = getArrayBufferView(view);
987+
const viewBuffer = arrayBufferView[0];
988+
const viewByteLength = arrayBufferView[2];
991989

992990
if (isSharedArrayBuffer(viewBuffer)) {
993991
throw new ERR_INVALID_ARG_VALUE(
@@ -1204,10 +1202,9 @@ class ReadableByteStreamController {
12041202
if (!isReadableByteStreamController(this))
12051203
throw new ERR_INVALID_THIS('ReadableByteStreamController');
12061204
validateBuffer(chunk);
1207-
const {
1208-
buffer: chunkBuffer,
1209-
byteLength: chunkByteLength,
1210-
} = getArrayBufferView(chunk);
1205+
const arrayBufferView = getArrayBufferView(chunk);
1206+
const chunkBuffer = arrayBufferView[0];
1207+
const chunkByteLength = arrayBufferView[2];
12111208

12121209
if (isSharedArrayBuffer(chunkBuffer)) {
12131210
throw new ERR_INVALID_ARG_VALUE(
@@ -2753,7 +2750,10 @@ function readableByteStreamControllerPullInto(
27532750
assert(minimumFill >= elementSize && minimumFill <= view.byteLength);
27542751
assert(minimumFill % elementSize === 0);
27552752

2756-
const { buffer, byteOffset, byteLength } = getArrayBufferView(view);
2753+
const arrayBufferView = getArrayBufferView(view);
2754+
const buffer = arrayBufferView[0];
2755+
const byteOffset = arrayBufferView[1];
2756+
const byteLength = arrayBufferView[2];
27572757
const bufferByteLength = ArrayBufferPrototypeGetByteLength(buffer);
27582758

27592759
let transferredBuffer;
@@ -2894,7 +2894,10 @@ function readableByteStreamControllerEnqueue(controller, chunk) {
28942894
stream,
28952895
} = controller[kState];
28962896

2897-
const { buffer, byteOffset, byteLength } = getArrayBufferView(chunk);
2897+
const arrayBufferView = getArrayBufferView(chunk);
2898+
const buffer = arrayBufferView[0];
2899+
const byteOffset = arrayBufferView[1];
2900+
const byteLength = arrayBufferView[2];
28982901

28992902
if (closeRequested || stream[kState].state !== 'readable')
29002903
return;
@@ -3187,11 +3190,10 @@ function readableByteStreamControllerRespondWithNewView(controller, view) {
31873190
const desc = pendingPullIntos[0];
31883191
assert(stream[kState].state !== 'errored');
31893192

3190-
const {
3191-
buffer: viewBuffer,
3192-
byteOffset: viewByteOffset,
3193-
byteLength: viewByteLength,
3194-
} = getArrayBufferView(view);
3193+
const arrayBufferView = getArrayBufferView(view);
3194+
const viewBuffer = arrayBufferView[0];
3195+
const viewByteOffset = arrayBufferView[1];
3196+
const viewByteLength = arrayBufferView[2];
31953197
const viewBufferByteLength = ArrayBufferPrototypeGetByteLength(viewBuffer);
31963198

31973199
if (stream[kState].state === 'closed') {

lib/internal/webstreams/util.js

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,17 @@ const {
2424
const {
2525
canCopyArrayBuffer,
2626
cloneAsUint8Array,
27-
getArrayBufferView,
28-
} = internalBinding('webstreams');
29-
30-
const {
31-
inspect,
32-
} = require('util');
33-
34-
const {
3527
constants: {
3628
kPending,
3729
},
30+
getArrayBufferView,
3831
getPromiseDetails,
3932
} = internalBinding('util');
4033

34+
const {
35+
inspect,
36+
} = require('util');
37+
4138
const assert = require('internal/assert');
4239

4340
const {
@@ -89,10 +86,10 @@ function customInspect(depth, options, name, data) {
8986
}
9087

9188
// getArrayBufferView, canCopyArrayBuffer, and cloneAsUint8Array are
92-
// implemented in src/node_webstreams.cc via direct V8 API calls. They are
93-
// immune to user tampering of typed-array prototypes (matching the defensive
94-
// behavior of the previous Reflect.get-based JS implementation) and faster on
95-
// hot byte-stream paths.
89+
// implemented in src/node_util.cc via direct V8 API calls. They are immune to
90+
// user tampering of typed-array prototypes (matching the defensive behavior of
91+
// the previous Reflect.get-based JS implementation) and faster on hot
92+
// byte-stream paths.
9693

9794
function isBrandCheck(brand) {
9895
return (value) => {

node.gyp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@
169169
'src/node_types.cc',
170170
'src/node_url.cc',
171171
'src/node_url_pattern.cc',
172-
'src/node_webstreams.cc',
173172
'src/node_util.cc',
174173
'src/node_v8.cc',
175174
'src/node_wasi.cc',

src/node_binding.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@
9696
V(v8) \
9797
V(wasi) \
9898
V(wasm_web_api) \
99-
V(webstreams) \
10099
V(watchdog) \
101100
V(worker) \
102101
V(zlib)

src/node_buffer.cc

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,16 @@ static void SetDetachKey(const FunctionCallbackInfo<Value>& args) {
15241524

15251525
namespace {
15261526

1527+
bool ReadNonNegativeInteger(Local<Value> value, uint64_t* result) {
1528+
constexpr double kMaxSafeInteger = 9007199254740991.0;
1529+
double number = value.As<Number>()->Value();
1530+
if (number < 0 || number > kMaxSafeInteger) {
1531+
return false;
1532+
}
1533+
*result = static_cast<uint64_t>(number);
1534+
return static_cast<double>(*result) == number;
1535+
}
1536+
15271537
std::pair<void*, size_t> DecomposeBufferToParts(Local<Value> buffer) {
15281538
void* pointer;
15291539
size_t byte_length;
@@ -1551,10 +1561,10 @@ void CopyArrayBuffer(const FunctionCallbackInfo<Value>& args) {
15511561
// args[4] == bytesToCopy
15521562

15531563
CHECK(args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer());
1554-
CHECK(args[1]->IsUint32());
1564+
CHECK(args[1]->IsNumber());
15551565
CHECK(args[2]->IsArrayBuffer() || args[2]->IsSharedArrayBuffer());
1556-
CHECK(args[3]->IsUint32());
1557-
CHECK(args[4]->IsUint32());
1566+
CHECK(args[3]->IsNumber());
1567+
CHECK(args[4]->IsNumber());
15581568

15591569
void* destination;
15601570
size_t destination_byte_length;
@@ -1565,16 +1575,29 @@ void CopyArrayBuffer(const FunctionCallbackInfo<Value>& args) {
15651575
size_t source_byte_length;
15661576
std::tie(source, source_byte_length) = DecomposeBufferToParts(args[2]);
15671577

1568-
uint32_t destination_offset = args[1].As<Uint32>()->Value();
1569-
uint32_t source_offset = args[3].As<Uint32>()->Value();
1570-
size_t bytes_to_copy = args[4].As<Uint32>()->Value();
1571-
1572-
CHECK_GE(destination_byte_length - destination_offset, bytes_to_copy);
1573-
CHECK_GE(source_byte_length - source_offset, bytes_to_copy);
1574-
1575-
uint8_t* dest = static_cast<uint8_t*>(destination) + destination_offset;
1576-
uint8_t* src = static_cast<uint8_t*>(source) + source_offset;
1577-
memcpy(dest, src, bytes_to_copy);
1578+
uint64_t destination_offset;
1579+
uint64_t source_offset;
1580+
uint64_t bytes_to_copy;
1581+
CHECK(ReadNonNegativeInteger(args[1], &destination_offset));
1582+
CHECK(ReadNonNegativeInteger(args[3], &source_offset));
1583+
CHECK(ReadNonNegativeInteger(args[4], &bytes_to_copy));
1584+
1585+
const uint64_t destination_offset_u = destination_offset;
1586+
const uint64_t source_offset_u = source_offset;
1587+
const uint64_t bytes_to_copy_u = bytes_to_copy;
1588+
const uint64_t destination_byte_length_u = destination_byte_length;
1589+
const uint64_t source_byte_length_u = source_byte_length;
1590+
CHECK_LE(destination_offset_u, destination_byte_length_u);
1591+
CHECK_LE(source_offset_u, source_byte_length_u);
1592+
CHECK_LE(bytes_to_copy_u, destination_byte_length_u - destination_offset_u);
1593+
CHECK_LE(bytes_to_copy_u, source_byte_length_u - source_offset_u);
1594+
1595+
const size_t destination_offset_s = static_cast<size_t>(destination_offset_u);
1596+
const size_t source_offset_s = static_cast<size_t>(source_offset_u);
1597+
const size_t bytes_to_copy_s = static_cast<size_t>(bytes_to_copy_u);
1598+
uint8_t* dest = static_cast<uint8_t*>(destination) + destination_offset_s;
1599+
uint8_t* src = static_cast<uint8_t*>(source) + source_offset_s;
1600+
memcpy(dest, src, bytes_to_copy_s);
15781601
}
15791602

15801603
// Converts a number parameter to size_t suitable for ArrayBuffer sizes

src/node_external_reference.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ class ExternalReferenceRegistry {
118118
V(v8) \
119119
V(zlib) \
120120
V(wasm_web_api) \
121-
V(webstreams) \
122121
V(worker)
123122

124123
#if NODE_HAVE_I18N_SUPPORT

src/node_util.cc

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ using v8::Local;
2727
using v8::LocalVector;
2828
using v8::MaybeLocal;
2929
using v8::Name;
30+
using v8::Number;
3031
using v8::Object;
3132
using v8::ObjectTemplate;
3233
using v8::ONLY_CONFIGURABLE;
@@ -42,6 +43,7 @@ using v8::StackFrame;
4243
using v8::StackTrace;
4344
using v8::String;
4445
using v8::Uint32;
46+
using v8::Uint8Array;
4547
using v8::Value;
4648

4749
// If a UTF-16 character is a low/trailing surrogate.
@@ -194,6 +196,106 @@ void ArrayBufferViewHasBuffer(const FunctionCallbackInfo<Value>& args) {
194196
args.GetReturnValue().Set(args[0].As<ArrayBufferView>()->HasBuffer());
195197
}
196198

199+
// Returns [buffer, byteOffset, byteLength] in a single binding crossing,
200+
// equivalent to reading the three properties via
201+
// Reflect.get(view.constructor.prototype, ..., view). Uses the V8 API
202+
// directly so it is immune to prototype tampering and avoids the JS-side
203+
// overhead of the defensive accessors in lib/internal/.
204+
void GetArrayBufferView(const FunctionCallbackInfo<Value>& args) {
205+
Isolate* isolate = args.GetIsolate();
206+
CHECK(args[0]->IsArrayBufferView());
207+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
208+
Local<Value> values[] = {
209+
view->Buffer(),
210+
Number::New(isolate, static_cast<double>(view->ByteOffset())),
211+
Number::New(isolate, static_cast<double>(view->ByteLength())),
212+
};
213+
args.GetReturnValue().Set(Array::New(isolate, values, arraysize(values)));
214+
}
215+
216+
static bool ReadNonNegativeInteger(Local<Value> value, uint64_t* result) {
217+
constexpr double kMaxSafeInteger = 9007199254740991.0;
218+
double number = value.As<Number>()->Value();
219+
if (number < 0 || number > kMaxSafeInteger) {
220+
return false;
221+
}
222+
*result = static_cast<uint64_t>(number);
223+
return static_cast<double>(*result) == number;
224+
}
225+
226+
// Returns true iff bytes can be safely copied between the buffers given the
227+
// requested offsets and count. Matches lib/internal/webstreams/util.js:
228+
// toBuffer !== fromBuffer &&
229+
// !toBuffer.detached &&
230+
// !fromBuffer.detached &&
231+
// toIndex + count <= toBuffer.byteLength &&
232+
// fromIndex + count <= fromBuffer.byteLength
233+
void CanCopyArrayBuffer(const FunctionCallbackInfo<Value>& args) {
234+
CHECK(args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer());
235+
CHECK(args[1]->IsNumber());
236+
CHECK(args[2]->IsArrayBuffer() || args[2]->IsSharedArrayBuffer());
237+
CHECK(args[3]->IsNumber());
238+
CHECK(args[4]->IsNumber());
239+
240+
// SharedArrayBuffer handles are interoperable with ArrayBuffer handles in
241+
// V8, so we can use the ArrayBuffer accessors uniformly. WasDetached()
242+
// always returns false on a SAB.
243+
Local<ArrayBuffer> to_buffer = args[0].As<ArrayBuffer>();
244+
Local<ArrayBuffer> from_buffer = args[2].As<ArrayBuffer>();
245+
246+
if (to_buffer->StrictEquals(from_buffer)) {
247+
args.GetReturnValue().Set(false);
248+
return;
249+
}
250+
if (to_buffer->WasDetached() || from_buffer->WasDetached()) {
251+
args.GetReturnValue().Set(false);
252+
return;
253+
}
254+
255+
uint64_t to_index;
256+
uint64_t from_index;
257+
uint64_t count;
258+
if (!ReadNonNegativeInteger(args[1], &to_index) ||
259+
!ReadNonNegativeInteger(args[3], &from_index) ||
260+
!ReadNonNegativeInteger(args[4], &count)) {
261+
args.GetReturnValue().Set(false);
262+
return;
263+
}
264+
265+
const uint64_t to_byte_length = to_buffer->ByteLength();
266+
const uint64_t from_byte_length = from_buffer->ByteLength();
267+
268+
bool ok = to_index <= to_byte_length && count <= to_byte_length - to_index &&
269+
from_index <= from_byte_length &&
270+
count <= from_byte_length - from_index;
271+
args.GetReturnValue().Set(ok);
272+
}
273+
274+
// Equivalent to:
275+
// new Uint8Array(view.buffer.slice(view.byteOffset,
276+
// view.byteOffset + view.byteLength))
277+
// Allocates a fresh ArrayBuffer with the view's bytes copied into it, then
278+
// returns a Uint8Array over the full new buffer. Avoids the JS-side
279+
// Reflect.get + slice round-trip.
280+
void CloneAsUint8Array(const FunctionCallbackInfo<Value>& args) {
281+
Environment* env = Environment::GetCurrent(args);
282+
Isolate* isolate = env->isolate();
283+
CHECK(args[0]->IsArrayBufferView());
284+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
285+
size_t byte_length = view->ByteLength();
286+
Local<ArrayBuffer> new_buffer;
287+
if (!ArrayBuffer::MaybeNew(isolate, byte_length).ToLocal(&new_buffer)) {
288+
// MaybeNew does not schedule an exception on allocation failure.
289+
THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate);
290+
return;
291+
}
292+
if (byte_length > 0) {
293+
size_t copied = view->CopyContents(new_buffer->Data(), byte_length);
294+
CHECK_EQ(copied, byte_length);
295+
}
296+
args.GetReturnValue().Set(Uint8Array::New(new_buffer, 0, byte_length));
297+
}
298+
197299
static uint32_t GetUVHandleTypeCode(const uv_handle_type type) {
198300
// TODO(anonrig): We can use an enum here and then create the array in the
199301
// binding, which will remove the hard-coding in C++ and JS land.
@@ -480,6 +582,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
480582
registry->Register(GetExternalValue);
481583
registry->Register(Sleep);
482584
registry->Register(ArrayBufferViewHasBuffer);
585+
registry->Register(GetArrayBufferView);
586+
registry->Register(CanCopyArrayBuffer);
587+
registry->Register(CloneAsUint8Array);
483588
registry->Register(GuessHandleType);
484589
registry->Register(fast_guess_handle_type_);
485590
registry->Register(ParseEnv);
@@ -589,6 +694,11 @@ void Initialize(Local<Object> target,
589694
SetMethod(context, target, "parseEnv", ParseEnv);
590695
SetMethod(
591696
context, target, "arrayBufferViewHasBuffer", ArrayBufferViewHasBuffer);
697+
SetMethodNoSideEffect(
698+
context, target, "getArrayBufferView", GetArrayBufferView);
699+
SetMethodNoSideEffect(
700+
context, target, "canCopyArrayBuffer", CanCopyArrayBuffer);
701+
SetMethod(context, target, "cloneAsUint8Array", CloneAsUint8Array);
592702
SetMethod(context,
593703
target,
594704
"constructSharedArrayBuffer",

0 commit comments

Comments
 (0)