mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
The iterators of push(), share(), shareSync(), broadcast() consumers
and from() normalization created a `{ __proto__: null, done, value }`
literal for every result. V8 creates such literals in dictionary mode,
which makes them several times more expensive to create and read than
ordinary objects.
Create them with an IterResult constructor whose prototype is a single
frozen, null-prototype object instead. Results have fast properties
and a single shape, and still have no %Object.prototype% in their
prototype chain, so a polluted Object.prototype.then still cannot turn
a result into a thenable. Creating and reading a result is about 5x
faster in a microbenchmark.
benchmark/streams/iter-throughput-share-sync.js improves by 7% to 42%
(more with more consumers) and iter-throughput-share.js by 4-6%;
pipeTo() with small chunks is about 2.5% faster.
This is observable: results are no longer null-prototype objects, so
deepStrictEqual() comparisons against `{ __proto__: null, ... }` no
longer match, and util.inspect() prints them as
`IterResult { done, value }` (the prototype has a non-enumerable
`constructor` for that purpose). The five tests that compared results
that way now compare their own properties, and a new test covers the
result contract and the prototype pollution case.
Assisted-by: OpenCode
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66648
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
80 lines
2.4 KiB
JavaScript
80 lines
2.4 KiB
JavaScript
// Flags: --experimental-stream-iter
|
|
'use strict';
|
|
|
|
// Iterator results created by the stream/iter iterators themselves do not
|
|
// inherit from Object.prototype, so prototype pollution cannot affect them.
|
|
// (Iterators implemented as async generators, such as the one returned by
|
|
// pull(), return ordinary iterator results created by the engine.)
|
|
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const { inspect } = require('util');
|
|
const {
|
|
broadcast,
|
|
from,
|
|
push,
|
|
share,
|
|
shareSync,
|
|
} = require('stream/iter');
|
|
|
|
function assertResult(result, done) {
|
|
assert.strictEqual(result instanceof Object, false);
|
|
assert.deepStrictEqual(Object.keys(result), ['done', 'value']);
|
|
assert.strictEqual(result.done, done);
|
|
assert.match(inspect(result), /^IterResult \{ done: (true|false), value: /);
|
|
}
|
|
|
|
async function testAsyncIterators() {
|
|
const sources = {
|
|
'push()': () => {
|
|
const { writer, readable } = push();
|
|
writer.writeSync('a');
|
|
writer.endSync();
|
|
return readable;
|
|
},
|
|
'share()': () => share(from('a')).pull(),
|
|
'broadcast()': () => {
|
|
const { writer, broadcast: bc } = broadcast();
|
|
const consumer = bc.push();
|
|
writer.writeSync('a');
|
|
writer.endSync();
|
|
return consumer;
|
|
},
|
|
};
|
|
for (const create of Object.values(sources)) {
|
|
const iterator = create()[Symbol.asyncIterator]();
|
|
assertResult(await iterator.next(), false);
|
|
assertResult(await iterator.next(), true);
|
|
}
|
|
}
|
|
|
|
function testShareSync() {
|
|
const iterator = shareSync(['a']).pull()[Symbol.iterator]();
|
|
assertResult(iterator.next(), false);
|
|
assertResult(iterator.next(), true);
|
|
}
|
|
|
|
async function testPollutedThen() {
|
|
// Resolving an async next() with an object looks up `then`. Results must
|
|
// not pick it up from a polluted Object.prototype.
|
|
const { writer, readable } = push();
|
|
writer.writeSync('ab');
|
|
writer.endSync();
|
|
const iterator = readable[Symbol.asyncIterator]();
|
|
Object.prototype.then = common.mustNotCall('Object.prototype.then');
|
|
try {
|
|
const first = await iterator.next();
|
|
assert.strictEqual(first.done, false);
|
|
assert.strictEqual(new TextDecoder().decode(first.value[0]), 'ab');
|
|
assert.strictEqual((await iterator.next()).done, true);
|
|
} finally {
|
|
delete Object.prototype.then;
|
|
}
|
|
}
|
|
|
|
(async () => {
|
|
await testAsyncIterators();
|
|
testShareSync();
|
|
await testPollutedThen();
|
|
})().then(common.mustCall());
|