Skip to content

Commit c20a8cc

Browse files
committed
stream: move byte-stream helpers to util binding
Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #63570
1 parent e2c7b65 commit c20a8cc

9 files changed

Lines changed: 207 additions & 180 deletions

File tree

lib/internal/webstreams/readablestream.js

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

689-
const {
690-
buffer: viewBuffer,
691-
byteLength: viewByteLength,
692-
} = getArrayBufferView(view);
689+
const arrayBufferView = getArrayBufferView(view);
690+
const viewBuffer = arrayBufferView[0];
691+
const viewByteLength = arrayBufferView[2];
693692
const viewBufferByteLength = ArrayBufferPrototypeGetByteLength(viewBuffer);
694693

695694
if (ArrayBufferPrototypeGetDetached(viewBuffer)) {
@@ -980,10 +979,9 @@ class ReadableStreamBYOBReader {
980979
}
981980
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
982981

983-
const {
984-
buffer: viewBuffer,
985-
byteLength: viewByteLength,
986-
} = getArrayBufferView(view);
982+
const arrayBufferView = getArrayBufferView(view);
983+
const viewBuffer = arrayBufferView[0];
984+
const viewByteLength = arrayBufferView[2];
987985

988986
if (isSharedArrayBuffer(viewBuffer)) {
989987
throw new ERR_INVALID_ARG_VALUE(
@@ -1200,10 +1198,9 @@ class ReadableByteStreamController {
12001198
if (!isReadableByteStreamController(this))
12011199
throw new ERR_INVALID_THIS('ReadableByteStreamController');
12021200
validateBuffer(chunk);
1203-
const {
1204-
buffer: chunkBuffer,
1205-
byteLength: chunkByteLength,
1206-
} = getArrayBufferView(chunk);
1201+
const arrayBufferView = getArrayBufferView(chunk);
1202+
const chunkBuffer = arrayBufferView[0];
1203+
const chunkByteLength = arrayBufferView[2];
12071204

12081205
if (isSharedArrayBuffer(chunkBuffer)) {
12091206
throw new ERR_INVALID_ARG_VALUE(
@@ -2749,7 +2746,10 @@ function readableByteStreamControllerPullInto(
27492746
assert(minimumFill >= elementSize && minimumFill <= view.byteLength);
27502747
assert(minimumFill % elementSize === 0);
27512748

2752-
const { buffer, byteOffset, byteLength } = getArrayBufferView(view);
2749+
const arrayBufferView = getArrayBufferView(view);
2750+
const buffer = arrayBufferView[0];
2751+
const byteOffset = arrayBufferView[1];
2752+
const byteLength = arrayBufferView[2];
27532753
const bufferByteLength = ArrayBufferPrototypeGetByteLength(buffer);
27542754

27552755
let transferredBuffer;
@@ -2890,7 +2890,10 @@ function readableByteStreamControllerEnqueue(controller, chunk) {
28902890
stream,
28912891
} = controller[kState];
28922892

2893-
const { buffer, byteOffset, byteLength } = getArrayBufferView(chunk);
2893+
const arrayBufferView = getArrayBufferView(chunk);
2894+
const buffer = arrayBufferView[0];
2895+
const byteOffset = arrayBufferView[1];
2896+
const byteLength = arrayBufferView[2];
28942897

28952898
if (closeRequested || stream[kState].state !== 'readable')
28962899
return;
@@ -3183,11 +3186,10 @@ function readableByteStreamControllerRespondWithNewView(controller, view) {
31833186
const desc = pendingPullIntos[0];
31843187
assert(stream[kState].state !== 'errored');
31853188

3186-
const {
3187-
buffer: viewBuffer,
3188-
byteOffset: viewByteOffset,
3189-
byteLength: viewByteLength,
3190-
} = getArrayBufferView(view);
3189+
const arrayBufferView = getArrayBufferView(view);
3190+
const viewBuffer = arrayBufferView[0];
3191+
const viewByteOffset = arrayBufferView[1];
3192+
const viewByteLength = arrayBufferView[2];
31913193
const viewBufferByteLength = ArrayBufferPrototypeGetByteLength(viewBuffer);
31923194

31933195
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
@@ -165,7 +165,6 @@
165165
'src/node_types.cc',
166166
'src/node_url.cc',
167167
'src/node_url_pattern.cc',
168-
'src/node_webstreams.cc',
169168
'src/node_util.cc',
170169
'src/node_v8.cc',
171170
'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: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,11 +1480,12 @@ void CopyArrayBuffer(const FunctionCallbackInfo<Value>& args) {
14801480
// args[3] == Source ArrayBuffer Offset
14811481
// args[4] == bytesToCopy
14821482

1483+
Environment* env = Environment::GetCurrent(args);
14831484
CHECK(args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer());
1484-
CHECK(args[1]->IsUint32());
1485+
CHECK(args[1]->IsNumber());
14851486
CHECK(args[2]->IsArrayBuffer() || args[2]->IsSharedArrayBuffer());
1486-
CHECK(args[3]->IsUint32());
1487-
CHECK(args[4]->IsUint32());
1487+
CHECK(args[3]->IsNumber());
1488+
CHECK(args[4]->IsNumber());
14881489

14891490
void* destination;
14901491
size_t destination_byte_length;
@@ -1495,16 +1496,36 @@ void CopyArrayBuffer(const FunctionCallbackInfo<Value>& args) {
14951496
size_t source_byte_length;
14961497
std::tie(source, source_byte_length) = DecomposeBufferToParts(args[2]);
14971498

1498-
uint32_t destination_offset = args[1].As<Uint32>()->Value();
1499-
uint32_t source_offset = args[3].As<Uint32>()->Value();
1500-
size_t bytes_to_copy = args[4].As<Uint32>()->Value();
1501-
1502-
CHECK_GE(destination_byte_length - destination_offset, bytes_to_copy);
1503-
CHECK_GE(source_byte_length - source_offset, bytes_to_copy);
1499+
int64_t destination_offset;
1500+
int64_t source_offset;
1501+
int64_t bytes_to_copy;
1502+
if (!args[1]->IntegerValue(env->context()).To(&destination_offset) ||
1503+
!args[3]->IntegerValue(env->context()).To(&source_offset) ||
1504+
!args[4]->IntegerValue(env->context()).To(&bytes_to_copy)) {
1505+
return;
1506+
}
15041507

1505-
uint8_t* dest = static_cast<uint8_t*>(destination) + destination_offset;
1506-
uint8_t* src = static_cast<uint8_t*>(source) + source_offset;
1507-
memcpy(dest, src, bytes_to_copy);
1508+
CHECK_GE(destination_offset, 0);
1509+
CHECK_GE(source_offset, 0);
1510+
CHECK_GE(bytes_to_copy, 0);
1511+
1512+
const uint64_t destination_offset_u =
1513+
static_cast<uint64_t>(destination_offset);
1514+
const uint64_t source_offset_u = static_cast<uint64_t>(source_offset);
1515+
const uint64_t bytes_to_copy_u = static_cast<uint64_t>(bytes_to_copy);
1516+
const uint64_t destination_byte_length_u = destination_byte_length;
1517+
const uint64_t source_byte_length_u = source_byte_length;
1518+
CHECK_LE(destination_offset_u, destination_byte_length_u);
1519+
CHECK_LE(source_offset_u, source_byte_length_u);
1520+
CHECK_LE(bytes_to_copy_u, destination_byte_length_u - destination_offset_u);
1521+
CHECK_LE(bytes_to_copy_u, source_byte_length_u - source_offset_u);
1522+
1523+
const size_t destination_offset_s = static_cast<size_t>(destination_offset_u);
1524+
const size_t source_offset_s = static_cast<size_t>(source_offset_u);
1525+
const size_t bytes_to_copy_s = static_cast<size_t>(bytes_to_copy_u);
1526+
uint8_t* dest = static_cast<uint8_t*>(destination) + destination_offset_s;
1527+
uint8_t* src = static_cast<uint8_t*>(source) + source_offset_s;
1528+
memcpy(dest, src, bytes_to_copy_s);
15081529
}
15091530

15101531
// 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
@@ -116,7 +116,6 @@ class ExternalReferenceRegistry {
116116
V(v8) \
117117
V(zlib) \
118118
V(wasm_web_api) \
119-
V(webstreams) \
120119
V(worker)
121120

122121
#if NODE_HAVE_I18N_SUPPORT

src/node_util.cc

Lines changed: 116 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,112 @@ 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(Environment* env,
217+
Local<Value> value,
218+
uint64_t* result) {
219+
int64_t integer;
220+
if (!value->IntegerValue(env->context()).To(&integer)) {
221+
return false;
222+
}
223+
if (integer < 0) {
224+
return false;
225+
}
226+
*result = static_cast<uint64_t>(integer);
227+
return true;
228+
}
229+
230+
// Returns true iff bytes can be safely copied between the buffers given the
231+
// requested offsets and count. Matches lib/internal/webstreams/util.js:
232+
// toBuffer !== fromBuffer &&
233+
// !toBuffer.detached &&
234+
// !fromBuffer.detached &&
235+
// toIndex + count <= toBuffer.byteLength &&
236+
// fromIndex + count <= fromBuffer.byteLength
237+
void CanCopyArrayBuffer(const FunctionCallbackInfo<Value>& args) {
238+
Environment* env = Environment::GetCurrent(args);
239+
CHECK(args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer());
240+
CHECK(args[1]->IsNumber());
241+
CHECK(args[2]->IsArrayBuffer() || args[2]->IsSharedArrayBuffer());
242+
CHECK(args[3]->IsNumber());
243+
CHECK(args[4]->IsNumber());
244+
245+
// SharedArrayBuffer handles are interoperable with ArrayBuffer handles in
246+
// V8, so we can use the ArrayBuffer accessors uniformly. WasDetached()
247+
// always returns false on a SAB.
248+
Local<ArrayBuffer> to_buffer = args[0].As<ArrayBuffer>();
249+
Local<ArrayBuffer> from_buffer = args[2].As<ArrayBuffer>();
250+
251+
if (to_buffer->StrictEquals(from_buffer)) {
252+
args.GetReturnValue().Set(false);
253+
return;
254+
}
255+
if (to_buffer->WasDetached() || from_buffer->WasDetached()) {
256+
args.GetReturnValue().Set(false);
257+
return;
258+
}
259+
260+
uint64_t to_index;
261+
uint64_t from_index;
262+
uint64_t count;
263+
if (!ReadNonNegativeInteger(env, args[1], &to_index) ||
264+
!ReadNonNegativeInteger(env, args[3], &from_index) ||
265+
!ReadNonNegativeInteger(env, args[4], &count)) {
266+
args.GetReturnValue().Set(false);
267+
return;
268+
}
269+
270+
const uint64_t to_byte_length = to_buffer->ByteLength();
271+
const uint64_t from_byte_length = from_buffer->ByteLength();
272+
273+
bool ok = to_index <= to_byte_length &&
274+
count <= to_byte_length - to_index &&
275+
from_index <= from_byte_length &&
276+
count <= from_byte_length - from_index;
277+
args.GetReturnValue().Set(ok);
278+
}
279+
280+
// Equivalent to:
281+
// new Uint8Array(view.buffer.slice(view.byteOffset,
282+
// view.byteOffset + view.byteLength))
283+
// Allocates a fresh ArrayBuffer with the view's bytes copied into it, then
284+
// returns a Uint8Array over the full new buffer. Avoids the JS-side
285+
// Reflect.get + slice round-trip.
286+
void CloneAsUint8Array(const FunctionCallbackInfo<Value>& args) {
287+
Environment* env = Environment::GetCurrent(args);
288+
Isolate* isolate = env->isolate();
289+
CHECK(args[0]->IsArrayBufferView());
290+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
291+
size_t byte_length = view->ByteLength();
292+
Local<ArrayBuffer> new_buffer;
293+
if (!ArrayBuffer::MaybeNew(isolate, byte_length).ToLocal(&new_buffer)) {
294+
// MaybeNew does not schedule an exception on allocation failure.
295+
THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate);
296+
return;
297+
}
298+
if (byte_length > 0) {
299+
size_t copied = view->CopyContents(new_buffer->Data(), byte_length);
300+
CHECK_EQ(copied, byte_length);
301+
}
302+
args.GetReturnValue().Set(Uint8Array::New(new_buffer, 0, byte_length));
303+
}
304+
197305
static uint32_t GetUVHandleTypeCode(const uv_handle_type type) {
198306
// TODO(anonrig): We can use an enum here and then create the array in the
199307
// binding, which will remove the hard-coding in C++ and JS land.
@@ -480,6 +588,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
480588
registry->Register(GetExternalValue);
481589
registry->Register(Sleep);
482590
registry->Register(ArrayBufferViewHasBuffer);
591+
registry->Register(GetArrayBufferView);
592+
registry->Register(CanCopyArrayBuffer);
593+
registry->Register(CloneAsUint8Array);
483594
registry->Register(GuessHandleType);
484595
registry->Register(fast_guess_handle_type_);
485596
registry->Register(ParseEnv);
@@ -589,6 +700,11 @@ void Initialize(Local<Object> target,
589700
SetMethod(context, target, "parseEnv", ParseEnv);
590701
SetMethod(
591702
context, target, "arrayBufferViewHasBuffer", ArrayBufferViewHasBuffer);
703+
SetMethodNoSideEffect(
704+
context, target, "getArrayBufferView", GetArrayBufferView);
705+
SetMethodNoSideEffect(
706+
context, target, "canCopyArrayBuffer", CanCopyArrayBuffer);
707+
SetMethod(context, target, "cloneAsUint8Array", CloneAsUint8Array);
592708
SetMethod(context,
593709
target,
594710
"constructSharedArrayBuffer",

0 commit comments

Comments
 (0)