Skip to content

Commit 18844d7

Browse files
committed
stream: use primordials for prototype method access in stream/iter
Replace direct prototype property access and method calls with primordial-safe equivalents throughout the stream/iter implementation. - `.byteLength`/`.byteOffset`/`.buffer` on typed arrays replaced with `TypedArrayPrototypeGetByteLength`/`GetByteOffset`/`GetBuffer` - `.buffer.byteLength` replaced with `ArrayBufferPrototypeGetByteLength` - `.buffer.slice()` replaced with `ArrayBufferPrototypeSlice` - `.call()` replaced with `FunctionPrototypeCall` - `.toString()` replaced with `FunctionPrototypeCall` - Protocol method calls replaced with `FunctionPrototypeCall` - `.map()` replaced with `ArrayPrototypeMap` - `.startsWith()` replaced with `StringPrototypeStartsWith` - `.slice()` on `Buffer` replaced with `TypedArrayPrototypeSlice` - `.shift()` on plain array replaced with `ArrayPrototypeShift` - `.fill()` on typed arrays replaced with `TypedArrayPrototypeFill` - `.then()` replaced with `PromisePrototypeThen` - `.catch()` replaced with `PromisePrototypeThen(…, undefined, handler)` - Added `__proto__`: null to all iterator result objects resolved through promises (`push.js`, `broadcast.js`, `share.js`, `consumers.js`)
1 parent 5bfafbc commit 18844d7

8 files changed

Lines changed: 111 additions & 69 deletions

File tree

lib/internal/streams/iter/broadcast.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const {
1919
Symbol,
2020
SymbolAsyncIterator,
2121
SymbolDispose,
22+
TypedArrayPrototypeGetByteLength,
2223
} = primordials;
2324

2425
const { TextEncoder } = require('internal/encoding');
@@ -236,7 +237,7 @@ class BroadcastImpl {
236237
if (reason) {
237238
consumer.reject?.(reason);
238239
} else {
239-
consumer.resolve({ done: true, value: undefined });
240+
consumer.resolve({ __proto__: null, done: true, value: undefined });
240241
}
241242
consumer.resolve = null;
242243
consumer.reject = null;
@@ -289,9 +290,9 @@ class BroadcastImpl {
289290
if (bufferIndex < this.#buffer.length) {
290291
const chunk = this.#buffer.get(bufferIndex);
291292
consumer.cursor++;
292-
consumer.resolve({ done: false, value: chunk });
293+
consumer.resolve({ __proto__: null, done: false, value: chunk });
293294
} else {
294-
consumer.resolve({ done: true, value: undefined });
295+
consumer.resolve({ __proto__: null, done: true, value: undefined });
295296
}
296297
consumer.resolve = null;
297298
consumer.reject = null;
@@ -368,7 +369,7 @@ class BroadcastImpl {
368369
const resolve = consumer.resolve;
369370
consumer.resolve = null;
370371
consumer.reject = null;
371-
resolve({ done: false, value: chunk });
372+
resolve({ __proto__: null, done: false, value: chunk });
372373
}
373374
}
374375
}
@@ -417,7 +418,7 @@ class BroadcastWriter {
417418
const converted =
418419
typeof chunk === 'string' ? encoder.encode(chunk) : chunk;
419420
this.#broadcast[kWrite]([converted]);
420-
this.#totalBytes += converted.byteLength;
421+
this.#totalBytes += TypedArrayPrototypeGetByteLength(converted);
421422
return kResolvedPromise;
422423
}
423424
return this.writev([chunk], options);
@@ -433,7 +434,7 @@ class BroadcastWriter {
433434
(typeof c === 'string' ? encoder.encode(c) : c));
434435
this.#broadcast[kWrite](converted);
435436
for (let i = 0; i < converted.length; i++) {
436-
this.#totalBytes += converted[i].byteLength;
437+
this.#totalBytes += TypedArrayPrototypeGetByteLength(converted[i]);
437438
}
438439
return kResolvedPromise;
439440
}
@@ -459,7 +460,7 @@ class BroadcastWriter {
459460

460461
if (this.#broadcast[kWrite](converted)) {
461462
for (let i = 0; i < converted.length; i++) {
462-
this.#totalBytes += converted[i].byteLength;
463+
this.#totalBytes += TypedArrayPrototypeGetByteLength(converted[i]);
463464
}
464465
return;
465466
}
@@ -486,7 +487,7 @@ class BroadcastWriter {
486487
const converted =
487488
typeof chunk === 'string' ? encoder.encode(chunk) : chunk;
488489
if (this.#broadcast[kWrite]([converted])) {
489-
this.#totalBytes += converted.byteLength;
490+
this.#totalBytes += TypedArrayPrototypeGetByteLength(converted);
490491
return true;
491492
}
492493
return false;
@@ -501,7 +502,7 @@ class BroadcastWriter {
501502
(typeof c === 'string' ? encoder.encode(c) : c));
502503
if (this.#broadcast[kWrite](converted)) {
503504
for (let i = 0; i < converted.length; i++) {
504-
this.#totalBytes += converted[i].byteLength;
505+
this.#totalBytes += TypedArrayPrototypeGetByteLength(converted[i]);
505506
}
506507
return true;
507508
}
@@ -596,7 +597,7 @@ class BroadcastWriter {
596597
const pending = this.#pendingWrites.shift();
597598
if (this.#broadcast[kWrite](pending.chunk)) {
598599
for (let i = 0; i < pending.chunk.length; i++) {
599-
this.#totalBytes += pending.chunk[i].byteLength;
600+
this.#totalBytes += TypedArrayPrototypeGetByteLength(pending.chunk[i]);
600601
}
601602
pending.resolve();
602603
} else {

lib/internal/streams/iter/consumers.js

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@
88
// ondrain() - backpressure drain utility
99

1010
const {
11+
ArrayBufferPrototypeGetByteLength,
12+
ArrayBufferPrototypeSlice,
1113
ArrayPrototypeFilter,
1214
ArrayPrototypeMap,
1315
ArrayPrototypePush,
1416
ArrayPrototypeSlice,
17+
PromisePrototypeThen,
1518
SafePromiseAllReturnVoid,
1619
SafePromiseRace,
1720
SymbolAsyncIterator,
21+
TypedArrayPrototypeGetBuffer,
22+
TypedArrayPrototypeGetByteLength,
23+
TypedArrayPrototypeGetByteOffset,
1824
} = primordials;
1925

2026
const {
@@ -71,7 +77,7 @@ function bytesSync(source, options) {
7177
for (let i = 0; i < batch.length; i++) {
7278
const chunk = batch[i];
7379
if (limit !== undefined) {
74-
totalBytes += chunk.byteLength;
80+
totalBytes += TypedArrayPrototypeGetByteLength(chunk);
7581
if (totalBytes > limit) {
7682
throw new ERR_OUT_OF_RANGE('totalBytes', `<= ${limit}`, totalBytes);
7783
}
@@ -106,11 +112,15 @@ function textSync(source, options) {
106112
*/
107113
function arrayBufferSync(source, options) {
108114
const data = bytesSync(source, options);
109-
if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) {
110-
return data.buffer;
115+
const byteOffset = TypedArrayPrototypeGetByteOffset(data);
116+
const byteLength = TypedArrayPrototypeGetByteLength(data);
117+
const buffer = TypedArrayPrototypeGetBuffer(data);
118+
if (byteOffset === 0 &&
119+
byteLength === ArrayBufferPrototypeGetByteLength(buffer)) {
120+
return buffer;
111121
}
112-
return data.buffer.slice(data.byteOffset,
113-
data.byteOffset + data.byteLength);
122+
return ArrayBufferPrototypeSlice(buffer, byteOffset,
123+
byteOffset + byteLength);
114124
}
115125

116126
/**
@@ -128,7 +138,7 @@ function arraySync(source, options) {
128138
for (let i = 0; i < batch.length; i++) {
129139
const chunk = batch[i];
130140
if (limit !== undefined) {
131-
totalBytes += chunk.byteLength;
141+
totalBytes += TypedArrayPrototypeGetByteLength(chunk);
132142
if (totalBytes > limit) {
133143
throw new ERR_OUT_OF_RANGE('totalBytes', `<= ${limit}`, totalBytes);
134144
}
@@ -191,7 +201,7 @@ async function bytes(source, options) {
191201
for (let i = 0; i < batch.length; i++) {
192202
const chunk = batch[i];
193203
if (limit !== undefined) {
194-
totalBytes += chunk.byteLength;
204+
totalBytes += TypedArrayPrototypeGetByteLength(chunk);
195205
if (totalBytes > limit) {
196206
throw new ERR_OUT_OF_RANGE('totalBytes', `<= ${limit}`, totalBytes);
197207
}
@@ -207,7 +217,7 @@ async function bytes(source, options) {
207217
for (let i = 0; i < batch.length; i++) {
208218
const chunk = batch[i];
209219
if (limit !== undefined) {
210-
totalBytes += chunk.byteLength;
220+
totalBytes += TypedArrayPrototypeGetByteLength(chunk);
211221
if (totalBytes > limit) {
212222
throw new ERR_OUT_OF_RANGE('totalBytes', `<= ${limit}`, totalBytes);
213223
}
@@ -245,11 +255,15 @@ async function text(source, options) {
245255
*/
246256
async function arrayBuffer(source, options) {
247257
const data = await bytes(source, options);
248-
if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) {
249-
return data.buffer;
258+
const byteOffset = TypedArrayPrototypeGetByteOffset(data);
259+
const byteLength = TypedArrayPrototypeGetByteLength(data);
260+
const buffer = TypedArrayPrototypeGetBuffer(data);
261+
if (byteOffset === 0 &&
262+
byteLength === ArrayBufferPrototypeGetByteLength(buffer)) {
263+
return buffer;
250264
}
251-
return data.buffer.slice(data.byteOffset,
252-
data.byteOffset + data.byteLength);
265+
return ArrayBufferPrototypeSlice(buffer, byteOffset,
266+
byteOffset + byteLength);
253267
}
254268

255269
/**
@@ -299,7 +313,7 @@ async function array(source, options) {
299313
for (let i = 0; i < batch.length; i++) {
300314
const chunk = batch[i];
301315
if (limit !== undefined) {
302-
totalBytes += chunk.byteLength;
316+
totalBytes += TypedArrayPrototypeGetByteLength(chunk);
303317
if (totalBytes > limit) {
304318
throw new ERR_OUT_OF_RANGE('totalBytes', `<= ${limit}`, totalBytes);
305319
}
@@ -315,7 +329,7 @@ async function array(source, options) {
315329
for (let i = 0; i < batch.length; i++) {
316330
const chunk = batch[i];
317331
if (limit !== undefined) {
318-
totalBytes += chunk.byteLength;
332+
totalBytes += TypedArrayPrototypeGetByteLength(chunk);
319333
if (totalBytes > limit) {
320334
throw new ERR_OUT_OF_RANGE('totalBytes', `<= ${limit}`, totalBytes);
321335
}
@@ -439,8 +453,9 @@ function merge(...args) {
439453

440454
const startIterator = (state, index) => {
441455
if (!state.done && !state.pending) {
442-
state.pending = state.iterator.next().then(
443-
(result) => ({ index, result }));
456+
state.pending = PromisePrototypeThen(
457+
state.iterator.next(),
458+
(result) => ({ __proto__: null, index, result }));
444459
}
445460
};
446461

lib/internal/streams/iter/from.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ const {
1111
ArrayPrototypeEvery,
1212
ArrayPrototypePush,
1313
ArrayPrototypeSlice,
14+
FunctionPrototypeCall,
1415
ObjectPrototypeToString,
1516
SymbolAsyncIterator,
1617
SymbolIterator,
1718
SymbolToPrimitive,
19+
TypedArrayPrototypeGetBuffer,
20+
TypedArrayPrototypeGetByteLength,
21+
TypedArrayPrototypeGetByteOffset,
1822
Uint8Array,
1923
} = primordials;
2024

@@ -154,7 +158,11 @@ function primitiveToUint8Array(chunk) {
154158
return chunk;
155159
}
156160
// Other ArrayBufferView types (Int8Array, DataView, etc.)
157-
return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
161+
return new Uint8Array(
162+
TypedArrayPrototypeGetBuffer(chunk),
163+
TypedArrayPrototypeGetByteOffset(chunk),
164+
TypedArrayPrototypeGetByteLength(chunk),
165+
);
158166
}
159167

160168
/**
@@ -166,7 +174,7 @@ function tryStringCoercion(obj) {
166174
// Check for Symbol.toPrimitive first
167175
if (hasToPrimitive(obj)) {
168176
const toPrimitive = obj[SymbolToPrimitive];
169-
const result = toPrimitive.call(obj, 'string');
177+
const result = FunctionPrototypeCall(toPrimitive, obj, 'string');
170178
if (typeof result === 'string') {
171179
return result;
172180
}
@@ -175,7 +183,7 @@ function tryStringCoercion(obj) {
175183

176184
// Check for custom toString
177185
if (hasCustomToString(obj)) {
178-
const result = obj.toString();
186+
const result = FunctionPrototypeCall(obj.toString, obj);
179187
return result;
180188
}
181189

@@ -200,7 +208,7 @@ function* normalizeSyncValue(value) {
200208

201209
// Handle ToStreamable protocol
202210
if (isToStreamable(value)) {
203-
const result = value[toStreamable]();
211+
const result = FunctionPrototypeCall(value[toStreamable], value);
204212
yield* normalizeSyncValue(result);
205213
return;
206214
}
@@ -305,7 +313,7 @@ async function* normalizeAsyncValue(value) {
305313

306314
// Handle ToAsyncStreamable protocol (check before ToStreamable)
307315
if (isToAsyncStreamable(value)) {
308-
const result = value[toAsyncStreamable]();
316+
const result = FunctionPrototypeCall(value[toAsyncStreamable], value);
309317
if (isPromise(result)) {
310318
yield* normalizeAsyncValue(await result);
311319
} else {
@@ -316,7 +324,7 @@ async function* normalizeAsyncValue(value) {
316324

317325
// Handle ToStreamable protocol
318326
if (isToStreamable(value)) {
319-
const result = value[toStreamable]();
327+
const result = FunctionPrototypeCall(value[toStreamable], value);
320328
yield* normalizeAsyncValue(result);
321329
return;
322330
}

lib/internal/streams/iter/pull.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
String,
1515
SymbolAsyncIterator,
1616
SymbolIterator,
17+
TypedArrayPrototypeGetByteLength,
1718
} = primordials;
1819

1920
const {
@@ -615,7 +616,7 @@ function pipeToSync(source, ...args) {
615616
for (let i = 0; i < batch.length; i++) {
616617
const chunk = batch[i];
617618
writer.write(chunk);
618-
totalBytes += chunk.byteLength;
619+
totalBytes += TypedArrayPrototypeGetByteLength(chunk);
619620
}
620621
}
621622

@@ -662,7 +663,7 @@ async function pipeTo(source, ...args) {
662663
if (hasWritev && batch.length > 1) {
663664
await writer.writev(batch, signal ? { signal } : undefined);
664665
for (let i = 0; i < batch.length; i++) {
665-
totalBytes += batch[i].byteLength;
666+
totalBytes += TypedArrayPrototypeGetByteLength(batch[i]);
666667
}
667668
} else {
668669
const promises = [];
@@ -672,7 +673,7 @@ async function pipeTo(source, ...args) {
672673
if (result !== undefined) {
673674
ArrayPrototypePush(promises, result);
674675
}
675-
totalBytes += chunk.byteLength;
676+
totalBytes += TypedArrayPrototypeGetByteLength(chunk);
676677
}
677678
if (promises.length > 0) {
678679
await SafePromiseAllReturnVoid(promises);

lib/internal/streams/iter/push.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const {
1212
Promise,
1313
PromiseResolve,
1414
SymbolAsyncIterator,
15+
TypedArrayPrototypeGetByteLength,
1516
} = primordials;
1617

1718
const {
@@ -145,15 +146,15 @@ class PushQueue {
145146
case 'drop-newest':
146147
// Discard this write, but return true
147148
for (let i = 0; i < chunks.length; i++) {
148-
this.#bytesWritten += chunks[i].byteLength;
149+
this.#bytesWritten += TypedArrayPrototypeGetByteLength(chunks[i]);
149150
}
150151
return true;
151152
}
152153
}
153154

154155
this.#slots.push(chunks);
155156
for (let i = 0; i < chunks.length; i++) {
156-
this.#bytesWritten += chunks[i].byteLength;
157+
this.#bytesWritten += TypedArrayPrototypeGetByteLength(chunks[i]);
157158
}
158159

159160
this.#resolvePendingReads();
@@ -352,10 +353,10 @@ class PushQueue {
352353
const pending = this.#pendingReads.shift();
353354
const result = this.#drain();
354355
this.#resolvePendingWrites();
355-
pending.resolve({ value: result, done: false });
356+
pending.resolve({ __proto__: null, value: result, done: false });
356357
} else if (this.#writerState === 'closed') {
357358
const pending = this.#pendingReads.shift();
358-
pending.resolve({ value: undefined, done: true });
359+
pending.resolve({ __proto__: null, value: undefined, done: true });
359360
} else if (this.#writerState === 'errored' && this.#error) {
360361
const pending = this.#pendingReads.shift();
361362
pending.reject(this.#error);
@@ -371,7 +372,7 @@ class PushQueue {
371372
const pending = this.#pendingWrites.shift();
372373
this.#slots.push(pending.chunks);
373374
for (let i = 0; i < pending.chunks.length; i++) {
374-
this.#bytesWritten += pending.chunks[i].byteLength;
375+
this.#bytesWritten += TypedArrayPrototypeGetByteLength(pending.chunks[i]);
375376
}
376377
pending.resolve();
377378
}

0 commit comments

Comments
 (0)