Commit 2431d137 authored by Adam Rice's avatar Adam Rice Committed by Commit Bot

Wrap Promise manipulation methods in Stream API

Maximum stack space exceeded exceptions within the WritableStream and
ReadableStream implementations can lead to slots being undefined that
are expected to contain Promises. This in turn leads to CHECK
failures. Protect all calls to V8 Promise intrinsics with checks that
the expected Promise is present, and throw an exception if it isn't.

Also add a test to verify that a CHECK failure does not occur.

Bug: 743082
Change-Id: Iaf89aa2a694600d129021e7e902c6bc02401c8b4
Reviewed-on: https://chromium-review.googlesource.com/579628
Commit-Queue: Takeshi Yoshino <tyoshino@chromium.org>
Reviewed-by: default avatarTakeshi Yoshino <tyoshino@chromium.org>
Cr-Commit-Position: refs/heads/master@{#488605}
parent 86d4e6cc
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
promise_test(t => {
const ws = new WritableStream();
const abortPromise = ws.abort();
// Call getWriter() with restricted stack space in order to cause a "stack
// space exceeded" exception to happen inside the implementation. It is called
// with progressively more stack space until something interesting happens.
function goDeeper() {
try {
goDeeper();
ws.getWriter();
} catch (e) {}
}
goDeeper();
return Promise.resolve();
}, 'this test should not crash');
</script>
...@@ -98,6 +98,32 @@ ...@@ -98,6 +98,32 @@
const errCannotPipeToALockedStream = 'Cannot pipe to a locked stream'; const errCannotPipeToALockedStream = 'Cannot pipe to a locked stream';
const errDestinationStreamClosed = 'Destination stream closed'; const errDestinationStreamClosed = 'Destination stream closed';
// TODO(ricea): Share these with WritableStream.
function internalError() {
throw new RangeError('ReadableStream Internal Error');
}
function rejectPromise(p, reason) {
if (!v8.isPromise(p)) {
internalError();
}
v8.rejectPromise(p, reason);
}
function resolvePromise(p, value) {
if (!v8.isPromise(p)) {
internalError();
}
v8.resolvePromise(p, value);
}
function markPromiseAsHandled(p) {
if (!v8.isPromise(p)) {
internalError();
}
v8.markPromiseAsHandled(p);
}
class ReadableStream { class ReadableStream {
constructor() { constructor() {
// TODO(domenic): when V8 gets default parameters and destructuring, all // TODO(domenic): when V8 gets default parameters and destructuring, all
...@@ -179,7 +205,7 @@ ...@@ -179,7 +205,7 @@
pipeThrough({writable, readable}, options) { pipeThrough({writable, readable}, options) {
const promise = this.pipeTo(writable, options); const promise = this.pipeTo(writable, options);
if (v8.isPromise(promise)) { if (v8.isPromise(promise)) {
v8.markPromiseAsHandled(promise); markPromiseAsHandled(promise);
} }
return readable; return readable;
} }
...@@ -395,9 +421,9 @@ ...@@ -395,9 +421,9 @@
binding.WritableStreamDefaultWriterRelease(writer); binding.WritableStreamDefaultWriterRelease(writer);
ReadableStreamReaderGenericRelease(reader); ReadableStreamReaderGenericRelease(reader);
if (errorGiven) { if (errorGiven) {
v8.rejectPromise(promise, error); rejectPromise(promise, error);
} else { } else {
v8.resolvePromise(promise, undefined); resolvePromise(promise, undefined);
} }
} }
...@@ -660,7 +686,7 @@ ...@@ -660,7 +686,7 @@
const reader = stream[_reader]; const reader = stream[_reader];
const readRequest = stream[_reader][_readRequests].shift(); const readRequest = stream[_reader][_readRequests].shift();
v8.resolvePromise(readRequest, CreateIterResultObject(chunk, done)); resolvePromise(readRequest, CreateIterResultObject(chunk, done));
} }
function ReadableStreamDefaultControllerEnqueue(controller, chunk) { function ReadableStreamDefaultControllerEnqueue(controller, chunk) {
...@@ -721,12 +747,12 @@ ...@@ -721,12 +747,12 @@
} }
if (IsReadableStreamDefaultReader(reader) === true) { if (IsReadableStreamDefaultReader(reader) === true) {
reader[_readRequests].forEach(request => v8.rejectPromise(request, e)); reader[_readRequests].forEach(request => rejectPromise(request, e));
reader[_readRequests] = new binding.SimpleQueue(); reader[_readRequests] = new binding.SimpleQueue();
} }
v8.rejectPromise(reader[_closedPromise], e); rejectPromise(reader[_closedPromise], e);
v8.markPromiseAsHandled(reader[_closedPromise]); markPromiseAsHandled(reader[_closedPromise]);
} }
function ReadableStreamClose(stream) { function ReadableStreamClose(stream) {
...@@ -739,11 +765,11 @@ ...@@ -739,11 +765,11 @@
if (IsReadableStreamDefaultReader(reader) === true) { if (IsReadableStreamDefaultReader(reader) === true) {
reader[_readRequests].forEach(request => reader[_readRequests].forEach(request =>
v8.resolvePromise(request, CreateIterResultObject(undefined, true))); resolvePromise(request, CreateIterResultObject(undefined, true)));
reader[_readRequests] = new binding.SimpleQueue(); reader[_readRequests] = new binding.SimpleQueue();
} }
v8.resolvePromise(reader[_closedPromise], undefined); resolvePromise(reader[_closedPromise], undefined);
} }
function ReadableStreamDefaultControllerGetDesiredSize(controller) { function ReadableStreamDefaultControllerGetDesiredSize(controller) {
...@@ -806,7 +832,7 @@ ...@@ -806,7 +832,7 @@
break; break;
case STATE_ERRORED: case STATE_ERRORED:
reader[_closedPromise] = Promise_reject(stream[_storedError]); reader[_closedPromise] = Promise_reject(stream[_storedError]);
v8.markPromiseAsHandled(reader[_closedPromise]); markPromiseAsHandled(reader[_closedPromise]);
break; break;
} }
} }
...@@ -823,11 +849,11 @@ ...@@ -823,11 +849,11 @@
} }
if (ReadableStreamGetState(reader[_ownerReadableStream]) === STATE_READABLE) { if (ReadableStreamGetState(reader[_ownerReadableStream]) === STATE_READABLE) {
v8.rejectPromise(reader[_closedPromise], new TypeError(errReleasedReaderClosedPromise)); rejectPromise(reader[_closedPromise], new TypeError(errReleasedReaderClosedPromise));
} else { } else {
reader[_closedPromise] = Promise_reject(new TypeError(errReleasedReaderClosedPromise)); reader[_closedPromise] = Promise_reject(new TypeError(errReleasedReaderClosedPromise));
} }
v8.markPromiseAsHandled(reader[_closedPromise]); markPromiseAsHandled(reader[_closedPromise]);
reader[_ownerReadableStream][_reader] = undefined; reader[_ownerReadableStream][_reader] = undefined;
reader[_ownerReadableStream] = undefined; reader[_ownerReadableStream] = undefined;
...@@ -987,7 +1013,7 @@ ...@@ -987,7 +1013,7 @@
if (canceled2 === true) { if (canceled2 === true) {
const compositeReason = [reason1, reason2]; const compositeReason = [reason1, reason2];
const cancelResult = ReadableStreamCancel(stream, compositeReason); const cancelResult = ReadableStreamCancel(stream, compositeReason);
v8.resolvePromise(promise, cancelResult); resolvePromise(promise, cancelResult);
} }
return promise; return promise;
...@@ -1000,7 +1026,7 @@ ...@@ -1000,7 +1026,7 @@
if (canceled1 === true) { if (canceled1 === true) {
const compositeReason = [reason1, reason2]; const compositeReason = [reason1, reason2];
const cancelResult = ReadableStreamCancel(stream, compositeReason); const cancelResult = ReadableStreamCancel(stream, compositeReason);
v8.resolvePromise(promise, cancelResult); resolvePromise(promise, cancelResult);
} }
return promise; return promise;
......
...@@ -113,8 +113,41 @@ ...@@ -113,8 +113,41 @@
templateErrorCannotActionOnStateStream(action, stateNames[state])); templateErrorCannotActionOnStateStream(action, stateNames[state]));
} }
// TODO(ricea): Share these with ReadableStream.
function internalError() {
throw new RangeError('WritableStream Internal Error');
}
function rejectPromise(p, reason) {
if (!v8.isPromise(p)) {
internalError();
}
v8.rejectPromise(p, reason);
}
function resolvePromise(p, value) {
if (!v8.isPromise(p)) {
internalError();
}
v8.resolvePromise(p, value);
}
function markPromiseAsHandled(p) {
if (!v8.isPromise(p)) {
internalError();
}
v8.markPromiseAsHandled(p);
}
function promiseState(p) {
if (!v8.isPromise(p)) {
internalError();
}
return v8.promiseState(p);
}
function rejectPromises(queue, e) { function rejectPromises(queue, e) {
queue.forEach(promise => v8.rejectPromise(promise, e)); queue.forEach(promise => rejectPromise(promise, e));
} }
class WritableStream { class WritableStream {
...@@ -279,7 +312,7 @@ ...@@ -279,7 +312,7 @@
stream[_pendingAbortRequest] = undefined; stream[_pendingAbortRequest] = undefined;
if (abortRequest.wasAlreadyErroring === true) { if (abortRequest.wasAlreadyErroring === true) {
v8.rejectPromise(abortRequest.promise, storedError); rejectPromise(abortRequest.promise, storedError);
WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream);
return; return;
} }
...@@ -290,11 +323,11 @@ ...@@ -290,11 +323,11 @@
thenPromise( thenPromise(
promise, promise,
() => { () => {
v8.resolvePromise(abortRequest.promise, undefined); resolvePromise(abortRequest.promise, undefined);
WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream);
}, },
reason => { reason => {
v8.rejectPromise(abortRequest.promise, reason); rejectPromise(abortRequest.promise, reason);
WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream);
}); });
} }
...@@ -302,14 +335,14 @@ ...@@ -302,14 +335,14 @@
function WritableStreamFinishInFlightWrite(stream) { function WritableStreamFinishInFlightWrite(stream) {
// assert(stream[_inFlightWriteRequest] !== undefined, // assert(stream[_inFlightWriteRequest] !== undefined,
// '_stream_.[[inFlightWriteRequest]] is not *undefined*.'); // '_stream_.[[inFlightWriteRequest]] is not *undefined*.');
v8.resolvePromise(stream[_inFlightWriteRequest], undefined); resolvePromise(stream[_inFlightWriteRequest], undefined);
stream[_inFlightWriteRequest] = undefined; stream[_inFlightWriteRequest] = undefined;
} }
function WritableStreamFinishInFlightWriteWithError(stream, error) { function WritableStreamFinishInFlightWriteWithError(stream, error) {
// assert(stream[_inFlightWriteRequest] !== undefined, // assert(stream[_inFlightWriteRequest] !== undefined,
// '_stream_.[[inFlightWriteRequest]] is not *undefined*.'); // '_stream_.[[inFlightWriteRequest]] is not *undefined*.');
v8.rejectPromise(stream[_inFlightWriteRequest], error); rejectPromise(stream[_inFlightWriteRequest], error);
stream[_inFlightWriteRequest] = undefined; stream[_inFlightWriteRequest] = undefined;
let state = stream[_stateAndFlags] & STATE_MASK; let state = stream[_stateAndFlags] & STATE_MASK;
...@@ -322,7 +355,7 @@ ...@@ -322,7 +355,7 @@
function WritableStreamFinishInFlightClose(stream) { function WritableStreamFinishInFlightClose(stream) {
// assert(stream[_inFlightCloseRequest] !== undefined, // assert(stream[_inFlightCloseRequest] !== undefined,
// '_stream_.[[inFlightCloseRequest]] is not *undefined*.'); // '_stream_.[[inFlightCloseRequest]] is not *undefined*.');
v8.resolvePromise(stream[_inFlightCloseRequest], undefined); resolvePromise(stream[_inFlightCloseRequest], undefined);
stream[_inFlightCloseRequest] = undefined; stream[_inFlightCloseRequest] = undefined;
const state = stream[_stateAndFlags] & STATE_MASK; const state = stream[_stateAndFlags] & STATE_MASK;
...@@ -332,7 +365,7 @@ ...@@ -332,7 +365,7 @@
if (state === ERRORING) { if (state === ERRORING) {
stream[_storedError] = undefined; stream[_storedError] = undefined;
if (stream[_pendingAbortRequest] !== undefined) { if (stream[_pendingAbortRequest] !== undefined) {
v8.resolvePromise(stream[_pendingAbortRequest].promise, undefined); resolvePromise(stream[_pendingAbortRequest].promise, undefined);
stream[_pendingAbortRequest] = undefined; stream[_pendingAbortRequest] = undefined;
} }
} }
...@@ -340,7 +373,7 @@ ...@@ -340,7 +373,7 @@
stream[_stateAndFlags] = (stream[_stateAndFlags] & ~STATE_MASK) | CLOSED; stream[_stateAndFlags] = (stream[_stateAndFlags] & ~STATE_MASK) | CLOSED;
const writer = stream[_writer]; const writer = stream[_writer];
if (writer !== undefined) { if (writer !== undefined) {
v8.resolvePromise(writer[_closedPromise], undefined); resolvePromise(writer[_closedPromise], undefined);
} }
// assert(stream[_pendingAbortRequest] === undefined, // assert(stream[_pendingAbortRequest] === undefined,
...@@ -352,7 +385,7 @@ ...@@ -352,7 +385,7 @@
function WritableStreamFinishInFlightCloseWithError(stream, error) { function WritableStreamFinishInFlightCloseWithError(stream, error) {
// assert(stream[_inFlightCloseRequest] !== undefined, // assert(stream[_inFlightCloseRequest] !== undefined,
// '_stream_.[[inFlightCloseRequest]] is not *undefined*.'); // '_stream_.[[inFlightCloseRequest]] is not *undefined*.');
v8.rejectPromise(stream[_inFlightCloseRequest], error); rejectPromise(stream[_inFlightCloseRequest], error);
stream[_inFlightCloseRequest] = undefined; stream[_inFlightCloseRequest] = undefined;
const state = stream[_stateAndFlags] & STATE_MASK; const state = stream[_stateAndFlags] & STATE_MASK;
...@@ -360,7 +393,7 @@ ...@@ -360,7 +393,7 @@
// '_stream_.[[state]] is `"writable"` or `"erroring"`'); // '_stream_.[[state]] is `"writable"` or `"erroring"`');
if (stream[_pendingAbortRequest] !== undefined) { if (stream[_pendingAbortRequest] !== undefined) {
v8.rejectPromise(stream[_pendingAbortRequest].promise, error); rejectPromise(stream[_pendingAbortRequest].promise, error);
stream[_pendingAbortRequest] = undefined; stream[_pendingAbortRequest] = undefined;
} }
...@@ -402,14 +435,14 @@ ...@@ -402,14 +435,14 @@
if (stream[_closeRequest] !== undefined) { if (stream[_closeRequest] !== undefined) {
// assert(stream[_inFlightCloseRequest] === undefined, // assert(stream[_inFlightCloseRequest] === undefined,
// '_stream_.[[inFlightCloseRequest]] is *undefined*'); // '_stream_.[[inFlightCloseRequest]] is *undefined*');
v8.rejectPromise(stream[_closeRequest], stream[_storedError]); rejectPromise(stream[_closeRequest], stream[_storedError]);
stream[_closeRequest] = undefined; stream[_closeRequest] = undefined;
} }
const writer = stream[_writer]; const writer = stream[_writer];
if (writer !== undefined) { if (writer !== undefined) {
v8.rejectPromise(writer[_closedPromise], stream[_storedError]); rejectPromise(writer[_closedPromise], stream[_storedError]);
v8.markPromiseAsHandled(writer[_closedPromise]); markPromiseAsHandled(writer[_closedPromise]);
} }
} }
...@@ -425,7 +458,7 @@ ...@@ -425,7 +458,7 @@
writer[_readyPromise] = v8.createPromise(); writer[_readyPromise] = v8.createPromise();
} else { } else {
// assert(!backpressure, '_backpressure_ is *false*.'); // assert(!backpressure, '_backpressure_ is *false*.');
v8.resolvePromise(writer[_readyPromise], undefined); resolvePromise(writer[_readyPromise], undefined);
} }
} }
if (backpressure) { if (backpressure) {
...@@ -483,7 +516,7 @@ ...@@ -483,7 +516,7 @@
case ERRORING: case ERRORING:
{ {
this[_readyPromise] = Promise_reject(stream[_storedError]); this[_readyPromise] = Promise_reject(stream[_storedError]);
v8.markPromiseAsHandled(this[_readyPromise]); markPromiseAsHandled(this[_readyPromise]);
this[_closedPromise] = v8.createPromise(); this[_closedPromise] = v8.createPromise();
break; break;
} }
...@@ -500,9 +533,9 @@ ...@@ -500,9 +533,9 @@
// assert(state === ERRORED, '_state_ is `"errored"`.'); // assert(state === ERRORED, '_state_ is `"errored"`.');
const storedError = stream[_storedError]; const storedError = stream[_storedError];
this[_readyPromise] = Promise_reject(storedError); this[_readyPromise] = Promise_reject(storedError);
v8.markPromiseAsHandled(this[_readyPromise]); markPromiseAsHandled(this[_readyPromise]);
this[_closedPromise] = Promise_reject(storedError); this[_closedPromise] = Promise_reject(storedError);
v8.markPromiseAsHandled(this[_closedPromise]); markPromiseAsHandled(this[_closedPromise]);
break; break;
} }
} }
...@@ -611,7 +644,7 @@ ...@@ -611,7 +644,7 @@
if ((stream[_stateAndFlags] & BACKPRESSURE_FLAG) && if ((stream[_stateAndFlags] & BACKPRESSURE_FLAG) &&
state === WRITABLE) { state === WRITABLE) {
v8.resolvePromise(writer[_readyPromise], undefined); resolvePromise(writer[_readyPromise], undefined);
} }
WritableStreamDefaultControllerClose(stream[_writableStreamController]); WritableStreamDefaultControllerClose(stream[_writableStreamController]);
return promise; return promise;
...@@ -636,23 +669,23 @@ ...@@ -636,23 +669,23 @@
function WritableStreamDefaultWriterEnsureClosedPromiseRejected( function WritableStreamDefaultWriterEnsureClosedPromiseRejected(
writer, error) { writer, error) {
if (v8.promiseState(writer[_closedPromise]) === v8.kPROMISE_PENDING) { if (promiseState(writer[_closedPromise]) === v8.kPROMISE_PENDING) {
v8.rejectPromise(writer[_closedPromise], error); rejectPromise(writer[_closedPromise], error);
} else { } else {
writer[_closedPromise] = Promise_reject(error); writer[_closedPromise] = Promise_reject(error);
} }
v8.markPromiseAsHandled(writer[_closedPromise]); markPromiseAsHandled(writer[_closedPromise]);
} }
function WritableStreamDefaultWriterEnsureReadyPromiseRejected( function WritableStreamDefaultWriterEnsureReadyPromiseRejected(
writer, error) { writer, error) {
if (v8.promiseState(writer[_readyPromise]) === v8.kPROMISE_PENDING) { if (promiseState(writer[_readyPromise]) === v8.kPROMISE_PENDING) {
v8.rejectPromise(writer[_readyPromise], error); rejectPromise(writer[_readyPromise], error);
} else { } else {
writer[_readyPromise] = Promise_reject(error); writer[_readyPromise] = Promise_reject(error);
} }
v8.markPromiseAsHandled(writer[_readyPromise]); markPromiseAsHandled(writer[_readyPromise]);
} }
function WritableStreamDefaultWriterGetDesiredSize(writer) { function WritableStreamDefaultWriterGetDesiredSize(writer) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment