diff --git a/index.bs b/index.bs
index 03774cb4..22fe1e9d 100644
--- a/index.bs
+++ b/index.bs
@@ -3348,7 +3348,12 @@ The following abstract operations support the implementation of the
1. Otherwise, if ! [$ReadableStreamHasBYOBReader$](|stream|) is true,
1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|,
|transferredBuffer|, |byteOffset|, |byteLength|).
- 1. Perform ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
+ 1. Let |filledPullIntos| be the result of performing
+ ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
+ 1. [=list/For each=] |filledPullInto| of |filledPullIntos|,
+ 1. Perform !
+ [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.[=ReadableByteStreamController/[[stream]]=],
+ |filledPullInto|).
1. Otherwise,
1. Assert: ! [$IsReadableStreamLocked$](|stream|) is false.
1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|,
@@ -3433,6 +3438,7 @@ The following abstract operations support the implementation of the
|maxBytesToCopy|.
1. Let |totalBytesToCopyRemaining| be |maxBytesToCopy|.
1. Let |ready| be false.
+ 1. Assert: ! [$IsDetachedBuffer$](|pullIntoDescriptor|'s [=pull-into descriptor/buffer=]) is false.
1. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] < |pullIntoDescriptor|'s
[=pull-into descriptor/minimum fill=].
1. Let |remainderBytes| be the remainder after dividing |maxBytesFilled| by |pullIntoDescriptor|'s
@@ -3452,6 +3458,14 @@ The following abstract operations support the implementation of the
queue entry/byte length=]).
1. Let |destStart| be |pullIntoDescriptor|'s [=pull-into descriptor/byte offset=] +
|pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=].
+ 1. Assert: ! [$CanCopyDataBlockBytes$](|pullIntoDescriptor|'s [=pull-into descriptor/buffer=],
+ |destStart|, |headOfQueue|'s [=readable byte stream queue entry/buffer=],
+ |headOfQueue|'s [=readable byte stream queue entry/byte offset=], |bytesToCopy|) is true.
+
If this assertion were to fail (due to a bug in this specification or
+ its implementation), then the next step may read from or write to potentially invalid memory.
+ The user agent should always check this assertion, and stop in an [=implementation-defined=]
+ manner if it fails (e.g. by crashing the process, or by
+ erroring the stream).
1. Perform ! [$CopyDataBlockBytes$](|pullIntoDescriptor|'s [=pull-into
descriptor/buffer=].\[[ArrayBufferData]], |destStart|,
|headOfQueue|'s [=readable byte stream queue entry/buffer=].\[[ArrayBufferData]],
@@ -3561,17 +3575,17 @@ The following abstract operations support the implementation of the
performs the following steps:
1. Assert: |controller|.[=ReadableByteStreamController/[[closeRequested]]=] is false.
+ 1. Let |filledPullIntos| be a new empty [=list=].
1. [=While=] |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not
[=list/is empty|empty=],
- 1. If |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] is 0, return.
+ 1. If |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] is 0, then [=break=].
1. Let |pullIntoDescriptor| be
|controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0].
1. If ! [$ReadableByteStreamControllerFillPullIntoDescriptorFromQueue$](|controller|,
|pullIntoDescriptor|) is true,
1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|).
- 1. Perform !
- [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.[=ReadableByteStreamController/[[stream]]=],
- |pullIntoDescriptor|).
+ 1. [=list/Append=] |pullIntoDescriptor| to |filledPullIntos|.
+ 1. Return |filledPullIntos|.
@@ -3701,11 +3715,16 @@ The following abstract operations support the implementation of the
perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|).
1. Let |stream| be |controller|.[=ReadableByteStreamController/[[stream]]=].
1. If ! [$ReadableStreamHasBYOBReader$](|stream|) is true,
- 1. [=While=] ! [$ReadableStreamGetNumReadIntoRequests$](|stream|) > 0,
+ 1. Let |filledPullIntos| be a new empty [=list=].
+ 1. Let |i| be 0.
+ 1. [=While=] |i| < ! [$ReadableStreamGetNumReadIntoRequests$](|stream|),
1. Let |pullIntoDescriptor| be !
[$ReadableByteStreamControllerShiftPendingPullInto$](|controller|).
+ 1. [=list/Append=] |pullIntoDescriptor| to |filledPullIntos|.
+ 1. Set |i| to |i| + 1.
+ 1. [=list/For each=] |filledPullInto| of |filledPullIntos|,
1. Perform ! [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|stream|,
- |pullIntoDescriptor|).
+ |filledPullInto|).
@@ -3720,7 +3739,12 @@ The following abstract operations support the implementation of the
1. If |pullIntoDescriptor|'s [=pull-into descriptor/reader type=] is "`none`",
1. Perform ? [$ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue$](|controller|,
|pullIntoDescriptor|).
- 1. Perform ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
+ 1. Let |filledPullIntos| be the result of performing
+ ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
+ 1. [=list/For each=] |filledPullInto| of |filledPullIntos|,
+ 1. Perform !
+ [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.[=ReadableByteStreamController/[[stream]]=],
+ |filledPullInto|).
1. Return.
1. If |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] < |pullIntoDescriptor|'s
[=pull-into descriptor/minimum fill=], return.
@@ -3738,10 +3762,15 @@ The following abstract operations support the implementation of the
|remainderSize|).
1. Set |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] to |pullIntoDescriptor|'s
[=pull-into descriptor/bytes filled=] − |remainderSize|.
+ 1. Let |filledPullIntos| be the result of performing
+ ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
1. Perform !
[$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.[=ReadableByteStreamController/[[stream]]=],
|pullIntoDescriptor|).
- 1. Perform ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
+ 1. [=list/For each=] |filledPullInto| of |filledPullIntos|,
+ 1. Perform !
+ [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.[=ReadableByteStreamController/[[stream]]=],
+ |filledPullInto|).
@@ -6848,6 +6877,22 @@ The following abstract operations are a grab-bag of utilities.
1. Return ? [$StructuredDeserialize$](|serialized|, [=the current Realm=]).
+
+ CanCopyDataBlockBytes(|toBuffer|, |toIndex|,
+ |fromBuffer|, |fromIndex|, |count|) performs the following steps:
+
+ 1. Assert: |toBuffer| [=is an Object=].
+ 1. Assert: |toBuffer| has an \[[ArrayBufferData]] internal slot.
+ 1. Assert: |fromBuffer| [=is an Object=].
+ 1. Assert: |fromBuffer| has an \[[ArrayBufferData]] internal slot.
+ 1. If |toBuffer| is |fromBuffer|, return false.
+ 1. If ! [$IsDetachedBuffer$](|toBuffer|) is true, return false.
+ 1. If ! [$IsDetachedBuffer$](|fromBuffer|) is true, return false.
+ 1. If |toIndex| + |count| > |toBuffer|.\[[ArrayBufferByteLength]], return false.
+ 1. If |fromIndex| + |count| > |fromBuffer|.\[[ArrayBufferByteLength]], return false.
+ 1. Return true.
+
+
Using streams in other specifications
Much of this standard concerns itself with the internal machinery of streams. Other specifications
diff --git a/reference-implementation/lib/abstract-ops/miscellaneous.js b/reference-implementation/lib/abstract-ops/miscellaneous.js
index 08589a74..dfcb582a 100644
--- a/reference-implementation/lib/abstract-ops/miscellaneous.js
+++ b/reference-implementation/lib/abstract-ops/miscellaneous.js
@@ -1,4 +1,5 @@
'use strict';
+const { IsDetachedBuffer } = require('./ecmascript');
exports.IsNonNegativeNumber = v => {
if (typeof v !== 'number') {
@@ -20,3 +21,22 @@ exports.CloneAsUint8Array = O => {
const buffer = O.buffer.slice(O.byteOffset, O.byteOffset + O.byteLength);
return new Uint8Array(buffer);
};
+
+exports.CanCopyDataBlockBytes = (toBuffer, toIndex, fromBuffer, fromIndex, count) => {
+ if (toBuffer === fromBuffer) {
+ return false;
+ }
+ if (IsDetachedBuffer(toBuffer) === true) {
+ return false;
+ }
+ if (IsDetachedBuffer(fromBuffer) === true) {
+ return false;
+ }
+ if (toIndex + count > toBuffer.byteLength) {
+ return false;
+ }
+ if (fromIndex + count > fromBuffer.byteLength) {
+ return false;
+ }
+ return true;
+};
diff --git a/reference-implementation/lib/abstract-ops/readable-streams.js b/reference-implementation/lib/abstract-ops/readable-streams.js
index 5cd4814b..6b8ba803 100644
--- a/reference-implementation/lib/abstract-ops/readable-streams.js
+++ b/reference-implementation/lib/abstract-ops/readable-streams.js
@@ -6,7 +6,7 @@ const { promiseResolvedWith, promiseRejectedWith, newPromise, resolvePromise, re
require('../helpers/webidl.js');
const { CanTransferArrayBuffer, Call, CopyDataBlockBytes, CreateArrayFromList, GetIterator, GetMethod, IsDetachedBuffer,
IteratorComplete, IteratorNext, IteratorValue, TransferArrayBuffer, typeIsObject } = require('./ecmascript.js');
-const { CloneAsUint8Array, IsNonNegativeNumber } = require('./miscellaneous.js');
+const { CanCopyDataBlockBytes, CloneAsUint8Array, IsNonNegativeNumber } = require('./miscellaneous.js');
const { EnqueueValueWithSize, ResetQueue } = require('./queue-with-sizes.js');
const { AcquireWritableStreamDefaultWriter, IsWritableStreamLocked, WritableStreamAbort,
WritableStreamDefaultWriterCloseWithErrorPropagation, WritableStreamDefaultWriterRelease,
@@ -1360,7 +1360,10 @@ function ReadableByteStreamControllerEnqueue(controller, chunk) {
} else if (ReadableStreamHasBYOBReader(stream) === true) {
// TODO: Ideally in this branch detaching should happen only if the buffer is not consumed fully.
ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength);
- ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller);
+ const filledPullIntos = ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller);
+ for (const filledPullInto of filledPullIntos) {
+ ReadableByteStreamControllerCommitPullIntoDescriptor(controller._stream, filledPullInto);
+ }
} else {
assert(IsReadableStreamLocked(stream) === false);
ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength);
@@ -1425,6 +1428,7 @@ function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
let totalBytesToCopyRemaining = maxBytesToCopy;
let ready = false;
+ assert(!IsDetachedBuffer(pullIntoDescriptor.buffer));
assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.minimumFill);
const remainderBytes = maxBytesFilled % pullIntoDescriptor.elementSize;
const maxAlignedBytes = maxBytesFilled - remainderBytes;
@@ -1443,6 +1447,9 @@ function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
const bytesToCopy = Math.min(totalBytesToCopyRemaining, headOfQueue.byteLength);
const destStart = pullIntoDescriptor.byteOffset + pullIntoDescriptor.bytesFilled;
+
+ assert(CanCopyDataBlockBytes(pullIntoDescriptor.buffer, destStart, headOfQueue.buffer, headOfQueue.byteOffset,
+ bytesToCopy));
CopyDataBlockBytes(pullIntoDescriptor.buffer, destStart, headOfQueue.buffer, headOfQueue.byteOffset, bytesToCopy);
if (headOfQueue.byteLength === bytesToCopy) {
@@ -1532,9 +1539,10 @@ function ReadableByteStreamControllerInvalidateBYOBRequest(controller) {
function ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller) {
assert(controller._closeRequested === false);
+ const filledPullIntos = [];
while (controller._pendingPullIntos.length > 0) {
if (controller._queueTotalSize === 0) {
- return;
+ break;
}
const pullIntoDescriptor = controller._pendingPullIntos[0];
@@ -1542,13 +1550,11 @@ function ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(contro
if (ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) === true) {
ReadableByteStreamControllerShiftPendingPullInto(controller);
-
- ReadableByteStreamControllerCommitPullIntoDescriptor(
- controller._stream,
- pullIntoDescriptor
- );
+ filledPullIntos.push(pullIntoDescriptor);
}
}
+
+ return filledPullIntos;
}
function ReadableByteStreamControllerProcessReadRequestsUsingQueue(controller) {
@@ -1673,9 +1679,15 @@ function ReadableByteStreamControllerRespondInClosedState(controller, firstDescr
const stream = controller._stream;
if (ReadableStreamHasBYOBReader(stream) === true) {
- while (ReadableStreamGetNumReadIntoRequests(stream) > 0) {
+ const filledPullIntos = [];
+ let i = 0;
+ while (i < ReadableStreamGetNumReadIntoRequests(stream)) {
const pullIntoDescriptor = ReadableByteStreamControllerShiftPendingPullInto(controller);
- ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDescriptor);
+ filledPullIntos.push(pullIntoDescriptor);
+ ++i;
+ }
+ for (const filledPullInto of filledPullIntos) {
+ ReadableByteStreamControllerCommitPullIntoDescriptor(stream, filledPullInto);
}
}
}
@@ -1687,7 +1699,10 @@ function ReadableByteStreamControllerRespondInReadableState(controller, bytesWri
if (pullIntoDescriptor.readerType === 'none') {
ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller, pullIntoDescriptor);
- ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller);
+ const filledPullIntos = ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller);
+ for (const filledPullInto of filledPullIntos) {
+ ReadableByteStreamControllerCommitPullIntoDescriptor(controller._stream, filledPullInto);
+ }
return;
}
@@ -1711,9 +1726,12 @@ function ReadableByteStreamControllerRespondInReadableState(controller, bytesWri
}
pullIntoDescriptor.bytesFilled -= remainderSize;
- ReadableByteStreamControllerCommitPullIntoDescriptor(controller._stream, pullIntoDescriptor);
+ const filledPullIntos = ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller);
- ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller);
+ ReadableByteStreamControllerCommitPullIntoDescriptor(controller._stream, pullIntoDescriptor);
+ for (const filledPullInto of filledPullIntos) {
+ ReadableByteStreamControllerCommitPullIntoDescriptor(controller._stream, filledPullInto);
+ }
}
function ReadableByteStreamControllerRespondInternal(controller, bytesWritten) {
diff --git a/reference-implementation/web-platform-tests b/reference-implementation/web-platform-tests
index 9b03282a..bc9dcbbf 160000
--- a/reference-implementation/web-platform-tests
+++ b/reference-implementation/web-platform-tests
@@ -1 +1 @@
-Subproject commit 9b03282a99ef2314c1c2d5050a105a74a2940019
+Subproject commit bc9dcbbf1a4c2c741ef47f47d6ede6458f40c4a4