Skip to content

Commit 5bfafbc

Browse files
committed
stream: replace instanceof with cross-realm-safe type checks
Replace all instanceof checks in the `stream/iter` implementation with cross-realm-safe alternatives from `internal/util/types` and `internal/util`. Objects created in different VM contexts have different prototypes, causing `instanceof` to return `false`. - `instanceof Uint8Array` -> `isUint8Array` (`internal/util/types`) - `instanceof ArrayBuffer` -> `isArrayBuffer` (`internal/util/types`) - `instanceof Promise` -> `isPromise` (`internal/util/types`) - `error instanceof Error` -> `isError` (`internal/util`) Note: `Error.isError` cannot be used via primordials because V8 flag-gated features (`InitializeGlobal_js_error_iserror` in `bootstrapper.cc`) are installed after `primordials.js` captures `Error`'s static methods during snapshot build. Adds cross-realm tests verifying correct handling of `Uint8Array`, `ArrayBuffer`, `Uint8Array[]`, `Promise`, and typed array views created via `vm.runInNewContext`
1 parent 6af362e commit 5bfafbc

6 files changed

Lines changed: 187 additions & 44 deletions

File tree

lib/internal/streams/iter/broadcast.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const {
1111
ArrayPrototypeMap,
1212
ArrayPrototypePush,
1313
ArrayPrototypeSlice,
14-
Error,
1514
MathMax,
1615
Promise,
1716
PromiseResolve,
@@ -24,7 +23,7 @@ const {
2423

2524
const { TextEncoder } = require('internal/encoding');
2625

27-
const { lazyDOMException } = require('internal/util');
26+
const { isError, lazyDOMException } = require('internal/util');
2827

2928
const {
3029
codes: {
@@ -710,7 +709,7 @@ const Broadcast = {
710709
await result.writer.end(signal ? { signal } : undefined);
711710
} catch (error) {
712711
await result.writer.fail(
713-
error instanceof Error ? error : new ERR_INVALID_ARG_TYPE('error', 'Error', String(error)));
712+
isError(error) ? error : new ERR_INVALID_ARG_TYPE('error', 'Error', String(error)));
714713
}
715714
})();
716715

lib/internal/streams/iter/from.js

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66
// Handles recursive flattening of nested iterables and protocol conversions.
77

88
const {
9-
ArrayBuffer,
109
ArrayBufferIsView,
1110
ArrayIsArray,
1211
ArrayPrototypeEvery,
1312
ArrayPrototypePush,
1413
ArrayPrototypeSlice,
1514
ObjectPrototypeToString,
16-
Promise,
1715
SymbolAsyncIterator,
1816
SymbolIterator,
1917
SymbolToPrimitive,
@@ -26,6 +24,11 @@ const {
2624
},
2725
} = require('internal/errors');
2826
const { TextEncoder } = require('internal/encoding');
27+
const {
28+
isArrayBuffer,
29+
isPromise,
30+
isUint8Array,
31+
} = require('internal/util/types');
2932

3033
const {
3134
toStreamable,
@@ -50,7 +53,7 @@ const FROM_BATCH_SIZE = 128;
5053
*/
5154
function isPrimitiveChunk(value) {
5255
if (typeof value === 'string') return true;
53-
if (value instanceof ArrayBuffer) return true;
56+
if (isArrayBuffer(value)) return true;
5457
if (ArrayBufferIsView(value)) return true;
5558
return false;
5659
}
@@ -144,10 +147,10 @@ function primitiveToUint8Array(chunk) {
144147
if (typeof chunk === 'string') {
145148
return encoder.encode(chunk);
146149
}
147-
if (chunk instanceof ArrayBuffer) {
150+
if (isArrayBuffer(chunk)) {
148151
return new Uint8Array(chunk);
149152
}
150-
if (chunk instanceof Uint8Array) {
153+
if (isUint8Array(chunk)) {
151154
return chunk;
152155
}
153156
// Other ArrayBufferView types (Int8Array, DataView, etc.)
@@ -243,7 +246,7 @@ function isUint8ArrayBatch(value) {
243246
if (!ArrayIsArray(value)) return false;
244247
if (value.length === 0) return true;
245248
// Check first element - if it's a Uint8Array, assume the rest are too
246-
return value[0] instanceof Uint8Array;
249+
return isUint8Array(value[0]);
247250
}
248251

249252
/**
@@ -261,7 +264,7 @@ function* normalizeSyncSource(source) {
261264
continue;
262265
}
263266
// Fast path 2: value is a single Uint8Array (very common)
264-
if (value instanceof Uint8Array) {
267+
if (isUint8Array(value)) {
265268
yield [value];
266269
continue;
267270
}
@@ -288,7 +291,7 @@ function* normalizeSyncSource(source) {
288291
*/
289292
async function* normalizeAsyncValue(value) {
290293
// Handle promises first
291-
if (value instanceof Promise) {
294+
if (isPromise(value)) {
292295
const resolved = await value;
293296
yield* normalizeAsyncValue(resolved);
294297
return;
@@ -303,7 +306,7 @@ async function* normalizeAsyncValue(value) {
303306
// Handle ToAsyncStreamable protocol (check before ToStreamable)
304307
if (isToAsyncStreamable(value)) {
305308
const result = value[toAsyncStreamable]();
306-
if (result instanceof Promise) {
309+
if (isPromise(result)) {
307310
yield* normalizeAsyncValue(await result);
308311
} else {
309312
yield* normalizeAsyncValue(result);
@@ -377,7 +380,7 @@ async function* normalizeAsyncSource(source) {
377380
continue;
378381
}
379382
// Fast path 2: value is a single Uint8Array (very common)
380-
if (value instanceof Uint8Array) {
383+
if (isUint8Array(value)) {
381384
yield [value];
382385
continue;
383386
}
@@ -411,7 +414,7 @@ async function* normalizeAsyncSource(source) {
411414
continue;
412415
}
413416
// Fast path 2: value is a single Uint8Array (very common)
414-
if (value instanceof Uint8Array) {
417+
if (isUint8Array(value)) {
415418
ArrayPrototypePush(batch, value);
416419
continue;
417420
}
@@ -477,9 +480,8 @@ function fromSync(input) {
477480
};
478481
}
479482
// Check if it's an array of Uint8Array (common case)
480-
if (input[0] instanceof Uint8Array) {
481-
const allUint8 = ArrayPrototypeEvery(input,
482-
(item) => item instanceof Uint8Array);
483+
if (isUint8Array(input[0])) {
484+
const allUint8 = ArrayPrototypeEvery(input, isUint8Array);
483485
if (allUint8) {
484486
const batch = input;
485487
return {
@@ -542,9 +544,8 @@ function from(input) {
542544
},
543545
};
544546
}
545-
if (input[0] instanceof Uint8Array) {
546-
const allUint8 = ArrayPrototypeEvery(input,
547-
(item) => item instanceof Uint8Array);
547+
if (isUint8Array(input[0])) {
548+
const allUint8 = ArrayPrototypeEvery(input, isUint8Array);
548549
if (allUint8) {
549550
const batch = input;
550551
return {

lib/internal/streams/iter/pull.js

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@ const {
1010
ArrayIsArray,
1111
ArrayPrototypePush,
1212
ArrayPrototypeSlice,
13-
Error,
14-
Promise,
1513
SafePromiseAllReturnVoid,
1614
String,
1715
SymbolAsyncIterator,
1816
SymbolIterator,
19-
Uint8Array,
2017
} = primordials;
2118

2219
const {
@@ -27,7 +24,11 @@ const {
2724
},
2825
} = require('internal/errors');
2926
const { TextEncoder } = require('internal/encoding');
30-
const { lazyDOMException } = require('internal/util');
27+
const { isError, lazyDOMException } = require('internal/util');
28+
const {
29+
isPromise,
30+
isUint8Array,
31+
} = require('internal/util/types');
3132
const { AbortController } = require('internal/abort_controller');
3233

3334
const {
@@ -121,7 +122,7 @@ function parsePipeToArgs(args) {
121122
* @yields {Uint8Array}
122123
*/
123124
function* flattenTransformYieldSync(value) {
124-
if (value instanceof Uint8Array) {
125+
if (isUint8Array(value)) {
125126
yield value;
126127
return;
127128
}
@@ -144,7 +145,7 @@ function* flattenTransformYieldSync(value) {
144145
* @yields {Uint8Array}
145146
*/
146147
async function* flattenTransformYieldAsync(value) {
147-
if (value instanceof Uint8Array) {
148+
if (isUint8Array(value)) {
148149
yield value;
149150
return;
150151
}
@@ -178,7 +179,7 @@ function* processTransformResultSync(result) {
178179
return;
179180
}
180181
if (ArrayIsArray(result) && result.length > 0 &&
181-
result[0] instanceof Uint8Array) {
182+
isUint8Array(result[0])) {
182183
// Fast path: Uint8Array[]
183184
if (result.length > 0) {
184185
yield result;
@@ -207,7 +208,7 @@ function* processTransformResultSync(result) {
207208
*/
208209
async function* processTransformResultAsync(result) {
209210
// Handle Promise
210-
if (result instanceof Promise) {
211+
if (isPromise(result)) {
211212
const resolved = await result;
212213
yield* processTransformResultAsync(resolved);
213214
return;
@@ -216,7 +217,7 @@ async function* processTransformResultAsync(result) {
216217
return;
217218
}
218219
if (ArrayIsArray(result) &&
219-
(result.length === 0 || result[0] instanceof Uint8Array)) {
220+
(result.length === 0 || isUint8Array(result[0]))) {
220221
// Fast path: Uint8Array[]
221222
if (result.length > 0) {
222223
yield result;
@@ -228,7 +229,7 @@ async function* processTransformResultAsync(result) {
228229
const batch = [];
229230
for await (const item of result) {
230231
// Fast path: item is already Uint8Array
231-
if (item instanceof Uint8Array) {
232+
if (isUint8Array(item)) {
232233
ArrayPrototypePush(batch, item);
233234
continue;
234235
}
@@ -247,7 +248,7 @@ async function* processTransformResultAsync(result) {
247248
const batch = [];
248249
for (const item of result) {
249250
// Fast path: item is already Uint8Array
250-
if (item instanceof Uint8Array) {
251+
if (isUint8Array(item)) {
251252
ArrayPrototypePush(batch, item);
252253
continue;
253254
}
@@ -353,7 +354,7 @@ async function* applyStatelessAsyncTransform(source, transform, options) {
353354
continue;
354355
}
355356
// Handle Promise of Uint8Array[]
356-
if (result instanceof Promise) {
357+
if (isPromise(result)) {
357358
const resolved = await result;
358359
if (resolved === null) continue;
359360
if (isUint8ArrayBatch(resolved)) {
@@ -374,7 +375,7 @@ async function* applyStatelessAsyncTransform(source, transform, options) {
374375
for (let i = 0; i < item.length; i++) {
375376
ArrayPrototypePush(batch, item[i]);
376377
}
377-
} else if (item instanceof Uint8Array) {
378+
} else if (isUint8Array(item)) {
378379
ArrayPrototypePush(batch, item);
379380
} else if (item !== null && item !== undefined) {
380381
for await (const chunk of flattenTransformYieldAsync(item)) {
@@ -407,7 +408,7 @@ async function* applyStatefulAsyncTransform(source, transform, options) {
407408
continue;
408409
}
409410
// Fast path: single Uint8Array
410-
if (item instanceof Uint8Array) {
411+
if (isUint8Array(item)) {
411412
yield [item];
412413
continue;
413414
}
@@ -524,7 +525,7 @@ async function* createAsyncPipeline(source, transforms, signal) {
524525
if (!controller.signal.aborted) {
525526
try {
526527
controller.abort(
527-
error instanceof Error ? error :
528+
isError(error) ? error :
528529
new ERR_OPERATION_FAILED(String(error)));
529530
} catch {
530531
// Transform signal listeners may throw - suppress
@@ -623,7 +624,7 @@ function pipeToSync(source, ...args) {
623624
}
624625
} catch (error) {
625626
if (!options?.preventFail) {
626-
writer.fail(error instanceof Error ? error : new ERR_OPERATION_FAILED(String(error)));
627+
writer.fail(isError(error) ? error : new ERR_OPERATION_FAILED(String(error)));
627628
}
628629
throw error;
629630
}
@@ -722,7 +723,7 @@ async function pipeTo(source, ...args) {
722723
} catch (error) {
723724
if (!options?.preventFail) {
724725
await writer.fail(
725-
error instanceof Error ? error : new ERR_OPERATION_FAILED(String(error)));
726+
isError(error) ? error : new ERR_OPERATION_FAILED(String(error)));
726727
}
727728
throw error;
728729
}

lib/internal/streams/iter/push.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
const {
99
ArrayPrototypePush,
1010
ArrayPrototypeSlice,
11-
Error,
1211
MathMax,
1312
Promise,
1413
PromiseResolve,
@@ -20,7 +19,7 @@ const {
2019
ERR_INVALID_STATE,
2120
},
2221
} = require('internal/errors');
23-
const { lazyDOMException } = require('internal/util');
22+
const { isError, lazyDOMException } = require('internal/util');
2423

2524
const {
2625
drainableProtocol,
@@ -78,12 +77,12 @@ class PushQueue {
7877

7978
if (this.#signal) {
8079
if (this.#signal.aborted) {
81-
this.fail(this.#signal.reason instanceof Error ?
80+
this.fail(isError(this.#signal.reason) ?
8281
this.#signal.reason :
8382
lazyDOMException('Aborted', 'AbortError'));
8483
} else {
8584
this.#abortHandler = () => {
86-
this.fail(this.#signal.reason instanceof Error ?
85+
this.fail(isError(this.#signal.reason) ?
8786
this.#signal.reason :
8887
lazyDOMException('Aborted', 'AbortError'));
8988
};

lib/internal/streams/iter/share.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
const {
99
ArrayPrototypePush,
10-
Error,
1110
MathMax,
1211
Promise,
1312
PromiseResolve,
@@ -18,6 +17,8 @@ const {
1817
SymbolIterator,
1918
} = primordials;
2019

20+
const { isError } = require('internal/util');
21+
2122
const {
2223
shareProtocol,
2324
shareSyncProtocol,
@@ -310,7 +311,7 @@ class ShareImpl {
310311
}
311312
} catch (error) {
312313
this.#sourceError =
313-
error instanceof Error ? error : new ERR_OPERATION_FAILED(String(error));
314+
isError(error) ? error : new ERR_OPERATION_FAILED(String(error));
314315
this.#sourceExhausted = true;
315316
} finally {
316317
this.#pulling = false;
@@ -533,7 +534,7 @@ class SyncShareImpl {
533534
}
534535
} catch (error) {
535536
this.#sourceError =
536-
error instanceof Error ? error : new ERR_OPERATION_FAILED(String(error));
537+
isError(error) ? error : new ERR_OPERATION_FAILED(String(error));
537538
this.#sourceExhausted = true;
538539
}
539540
}

0 commit comments

Comments
 (0)