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 @@
const errCannotPipeToALockedStream = 'Cannot pipe to a locked stream';
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 {
constructor() {
// TODO(domenic): when V8 gets default parameters and destructuring, all
......@@ -179,7 +205,7 @@
pipeThrough({writable, readable}, options) {
const promise = this.pipeTo(writable, options);
if (v8.isPromise(promise)) {
v8.markPromiseAsHandled(promise);
markPromiseAsHandled(promise);
}
return readable;
}
......@@ -395,9 +421,9 @@
binding.WritableStreamDefaultWriterRelease(writer);
ReadableStreamReaderGenericRelease(reader);
if (errorGiven) {
v8.rejectPromise(promise, error);
rejectPromise(promise, error);
} else {
v8.resolvePromise(promise, undefined);
resolvePromise(promise, undefined);
}
}
......@@ -660,7 +686,7 @@
const reader = stream[_reader];
const readRequest = stream[_reader][_readRequests].shift();
v8.resolvePromise(readRequest, CreateIterResultObject(chunk, done));
resolvePromise(readRequest, CreateIterResultObject(chunk, done));
}
function ReadableStreamDefaultControllerEnqueue(controller, chunk) {
......@@ -721,12 +747,12 @@
}
if (IsReadableStreamDefaultReader(reader) === true) {
reader[_readRequests].forEach(request => v8.rejectPromise(request, e));
reader[_readRequests].forEach(request => rejectPromise(request, e));
reader[_readRequests] = new binding.SimpleQueue();
}
v8.rejectPromise(reader[_closedPromise], e);
v8.markPromiseAsHandled(reader[_closedPromise]);
rejectPromise(reader[_closedPromise], e);
markPromiseAsHandled(reader[_closedPromise]);
}
function ReadableStreamClose(stream) {
......@@ -739,11 +765,11 @@
if (IsReadableStreamDefaultReader(reader) === true) {
reader[_readRequests].forEach(request =>
v8.resolvePromise(request, CreateIterResultObject(undefined, true)));
resolvePromise(request, CreateIterResultObject(undefined, true)));
reader[_readRequests] = new binding.SimpleQueue();
}
v8.resolvePromise(reader[_closedPromise], undefined);
resolvePromise(reader[_closedPromise], undefined);
}
function ReadableStreamDefaultControllerGetDesiredSize(controller) {
......@@ -806,7 +832,7 @@
break;
case STATE_ERRORED:
reader[_closedPromise] = Promise_reject(stream[_storedError]);
v8.markPromiseAsHandled(reader[_closedPromise]);
markPromiseAsHandled(reader[_closedPromise]);
break;
}
}
......@@ -823,11 +849,11 @@
}
if (ReadableStreamGetState(reader[_ownerReadableStream]) === STATE_READABLE) {
v8.rejectPromise(reader[_closedPromise], new TypeError(errReleasedReaderClosedPromise));
rejectPromise(reader[_closedPromise], new TypeError(errReleasedReaderClosedPromise));
} else {
reader[_closedPromise] = Promise_reject(new TypeError(errReleasedReaderClosedPromise));
}
v8.markPromiseAsHandled(reader[_closedPromise]);
markPromiseAsHandled(reader[_closedPromise]);
reader[_ownerReadableStream][_reader] = undefined;
reader[_ownerReadableStream] = undefined;
......@@ -987,7 +1013,7 @@
if (canceled2 === true) {
const compositeReason = [reason1, reason2];
const cancelResult = ReadableStreamCancel(stream, compositeReason);
v8.resolvePromise(promise, cancelResult);
resolvePromise(promise, cancelResult);
}
return promise;
......@@ -1000,7 +1026,7 @@
if (canceled1 === true) {
const compositeReason = [reason1, reason2];
const cancelResult = ReadableStreamCancel(stream, compositeReason);
v8.resolvePromise(promise, cancelResult);
resolvePromise(promise, cancelResult);
}
return promise;
......
......@@ -113,8 +113,41 @@
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) {
queue.forEach(promise => v8.rejectPromise(promise, e));
queue.forEach(promise => rejectPromise(promise, e));
}
class WritableStream {
......@@ -279,7 +312,7 @@
stream[_pendingAbortRequest] = undefined;
if (abortRequest.wasAlreadyErroring === true) {
v8.rejectPromise(abortRequest.promise, storedError);
rejectPromise(abortRequest.promise, storedError);
WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream);
return;
}
......@@ -290,11 +323,11 @@
thenPromise(
promise,
() => {
v8.resolvePromise(abortRequest.promise, undefined);
resolvePromise(abortRequest.promise, undefined);
WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream);
},
reason => {
v8.rejectPromise(abortRequest.promise, reason);
rejectPromise(abortRequest.promise, reason);
WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream);
});
}
......@@ -302,14 +335,14 @@
function WritableStreamFinishInFlightWrite(stream) {
// assert(stream[_inFlightWriteRequest] !== undefined,
// '_stream_.[[inFlightWriteRequest]] is not *undefined*.');
v8.resolvePromise(stream[_inFlightWriteRequest], undefined);
resolvePromise(stream[_inFlightWriteRequest], undefined);
stream[_inFlightWriteRequest] = undefined;
}
function WritableStreamFinishInFlightWriteWithError(stream, error) {
// assert(stream[_inFlightWriteRequest] !== undefined,
// '_stream_.[[inFlightWriteRequest]] is not *undefined*.');
v8.rejectPromise(stream[_inFlightWriteRequest], error);
rejectPromise(stream[_inFlightWriteRequest], error);
stream[_inFlightWriteRequest] = undefined;
let state = stream[_stateAndFlags] & STATE_MASK;
......@@ -322,7 +355,7 @@
function WritableStreamFinishInFlightClose(stream) {
// assert(stream[_inFlightCloseRequest] !== undefined,
// '_stream_.[[inFlightCloseRequest]] is not *undefined*.');
v8.resolvePromise(stream[_inFlightCloseRequest], undefined);
resolvePromise(stream[_inFlightCloseRequest], undefined);
stream[_inFlightCloseRequest] = undefined;
const state = stream[_stateAndFlags] & STATE_MASK;
......@@ -332,7 +365,7 @@
if (state === ERRORING) {
stream[_storedError] = undefined;
if (stream[_pendingAbortRequest] !== undefined) {
v8.resolvePromise(stream[_pendingAbortRequest].promise, undefined);
resolvePromise(stream[_pendingAbortRequest].promise, undefined);
stream[_pendingAbortRequest] = undefined;
}
}
......@@ -340,7 +373,7 @@
stream[_stateAndFlags] = (stream[_stateAndFlags] & ~STATE_MASK) | CLOSED;
const writer = stream[_writer];
if (writer !== undefined) {
v8.resolvePromise(writer[_closedPromise], undefined);
resolvePromise(writer[_closedPromise], undefined);
}
// assert(stream[_pendingAbortRequest] === undefined,
......@@ -352,7 +385,7 @@
function WritableStreamFinishInFlightCloseWithError(stream, error) {
// assert(stream[_inFlightCloseRequest] !== undefined,
// '_stream_.[[inFlightCloseRequest]] is not *undefined*.');
v8.rejectPromise(stream[_inFlightCloseRequest], error);
rejectPromise(stream[_inFlightCloseRequest], error);
stream[_inFlightCloseRequest] = undefined;
const state = stream[_stateAndFlags] & STATE_MASK;
......@@ -360,7 +393,7 @@
// '_stream_.[[state]] is `"writable"` or `"erroring"`');
if (stream[_pendingAbortRequest] !== undefined) {
v8.rejectPromise(stream[_pendingAbortRequest].promise, error);
rejectPromise(stream[_pendingAbortRequest].promise, error);
stream[_pendingAbortRequest] = undefined;
}
......@@ -402,14 +435,14 @@
if (stream[_closeRequest] !== undefined) {
// assert(stream[_inFlightCloseRequest] === undefined,
// '_stream_.[[inFlightCloseRequest]] is *undefined*');
v8.rejectPromise(stream[_closeRequest], stream[_storedError]);
rejectPromise(stream[_closeRequest], stream[_storedError]);
stream[_closeRequest] = undefined;
}
const writer = stream[_writer];
if (writer !== undefined) {
v8.rejectPromise(writer[_closedPromise], stream[_storedError]);
v8.markPromiseAsHandled(writer[_closedPromise]);
rejectPromise(writer[_closedPromise], stream[_storedError]);
markPromiseAsHandled(writer[_closedPromise]);
}
}
......@@ -425,7 +458,7 @@
writer[_readyPromise] = v8.createPromise();
} else {
// assert(!backpressure, '_backpressure_ is *false*.');
v8.resolvePromise(writer[_readyPromise], undefined);
resolvePromise(writer[_readyPromise], undefined);
}
}
if (backpressure) {
......@@ -483,7 +516,7 @@
case ERRORING:
{
this[_readyPromise] = Promise_reject(stream[_storedError]);
v8.markPromiseAsHandled(this[_readyPromise]);
markPromiseAsHandled(this[_readyPromise]);
this[_closedPromise] = v8.createPromise();
break;
}
......@@ -500,9 +533,9 @@
// assert(state === ERRORED, '_state_ is `"errored"`.');
const storedError = stream[_storedError];
this[_readyPromise] = Promise_reject(storedError);
v8.markPromiseAsHandled(this[_readyPromise]);
markPromiseAsHandled(this[_readyPromise]);
this[_closedPromise] = Promise_reject(storedError);
v8.markPromiseAsHandled(this[_closedPromise]);
markPromiseAsHandled(this[_closedPromise]);
break;
}
}
......@@ -611,7 +644,7 @@
if ((stream[_stateAndFlags] & BACKPRESSURE_FLAG) &&
state === WRITABLE) {
v8.resolvePromise(writer[_readyPromise], undefined);
resolvePromise(writer[_readyPromise], undefined);
}
WritableStreamDefaultControllerClose(stream[_writableStreamController]);
return promise;
......@@ -636,23 +669,23 @@
function WritableStreamDefaultWriterEnsureClosedPromiseRejected(
writer, error) {
if (v8.promiseState(writer[_closedPromise]) === v8.kPROMISE_PENDING) {
v8.rejectPromise(writer[_closedPromise], error);
if (promiseState(writer[_closedPromise]) === v8.kPROMISE_PENDING) {
rejectPromise(writer[_closedPromise], error);
} else {
writer[_closedPromise] = Promise_reject(error);
}
v8.markPromiseAsHandled(writer[_closedPromise]);
markPromiseAsHandled(writer[_closedPromise]);
}
function WritableStreamDefaultWriterEnsureReadyPromiseRejected(
writer, error) {
if (v8.promiseState(writer[_readyPromise]) === v8.kPROMISE_PENDING) {
v8.rejectPromise(writer[_readyPromise], error);
if (promiseState(writer[_readyPromise]) === v8.kPROMISE_PENDING) {
rejectPromise(writer[_readyPromise], error);
} else {
writer[_readyPromise] = Promise_reject(error);
}
v8.markPromiseAsHandled(writer[_readyPromise]);
markPromiseAsHandled(writer[_readyPromise]);
}
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