diff --git a/js/src/builtin/streams/PipeToState.cpp b/js/src/builtin/streams/PipeToState.cpp index 779470f56938..239ed79abbdf 100644 --- a/js/src/builtin/streams/PipeToState.cpp +++ b/js/src/builtin/streams/PipeToState.cpp @@ -65,18 +65,6 @@ using js::WritableStream; using js::WritableStreamDefaultWriter; using js::WritableStreamDefaultWriterWrite; -// This typedef is undoubtedly not the right one for the long run, but it's -// enough to be placeholder for now. -using Action = bool (*)(JSContext*, Handle state); - -static MOZ_MUST_USE bool DummyAction(JSContext* cx, - Handle state) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, - "pipeTo dummy action"); - return false; -} - static ReadableStream* GetUnwrappedSource(JSContext* cx, Handle state) { cx->check(state); @@ -134,8 +122,8 @@ static MOZ_MUST_USE bool Finalize(JSContext* cx, unsigned argc, Value* vp) { // Shutdown with an action: if any of the above requirements ask to shutdown // with an action action, optionally with an error originalError, then: static MOZ_MUST_USE bool ShutdownWithAction( - JSContext* cx, Handle state, Action action, - Handle> originalError) { + JSContext* cx, Handle state, + PipeToState::ShutdownAction action, Handle> originalError) { cx->check(state); cx->check(originalError); @@ -147,6 +135,9 @@ static MOZ_MUST_USE bool ShutdownWithAction( // Step b: Set shuttingDown to true. state->setShuttingDown(); + // Save the action away for later -- potentially asynchronous -- use. + state->setShutdownAction(action); + // Step c: If dest.[[state]] is "writable" and // ! WritableStreamCloseQueuedOrInFlight(dest) is false, WritableStream* unwrappedDest = GetUnwrappedDest(cx, state); @@ -285,7 +276,9 @@ static MOZ_MUST_USE bool OnSourceErrored( // ! WritableStreamAbort(dest, source.[[storedError]]) and with // source.[[storedError]]. else { - if (!ShutdownWithAction(cx, state, DummyAction, storedError)) { + if (!ShutdownWithAction(cx, state, + PipeToState::ShutdownAction::AbortDestStream, + storedError)) { return false; } } @@ -327,7 +320,9 @@ static MOZ_MUST_USE bool OnDestErrored(JSContext* cx, // ! ReadableStreamCancel(source, dest.[[storedError]]) and with // dest.[[storedError]]. else { - if (!ShutdownWithAction(cx, state, DummyAction, storedError)) { + if (!ShutdownWithAction(cx, state, + PipeToState::ShutdownAction::CancelSource, + storedError)) { return false; } } @@ -362,7 +357,10 @@ static MOZ_MUST_USE bool OnSourceClosed(JSContext* cx, // i. If preventClose is false, shutdown with an action of // ! WritableStreamDefaultWriterCloseWithErrorPropagation(writer). else { - if (!ShutdownWithAction(cx, state, DummyAction, noError)) { + if (!ShutdownWithAction( + cx, state, + PipeToState::ShutdownAction::CloseWriterWithErrorPropagation, + noError)) { return false; } } @@ -426,7 +424,8 @@ static MOZ_MUST_USE bool OnDestClosed(JSContext* cx, // iii. If preventCancel is false, shutdown with an action of // ! ReadableStreamCancel(source, destClosed) and with destClosed. else { - if (!ShutdownWithAction(cx, state, DummyAction, destClosed)) { + if (!ShutdownWithAction( + cx, state, PipeToState::ShutdownAction::CancelSource, destClosed)) { return false; } } diff --git a/js/src/builtin/streams/PipeToState.h b/js/src/builtin/streams/PipeToState.h index a05706f4c048..069624c9e89d 100644 --- a/js/src/builtin/streams/PipeToState.h +++ b/js/src/builtin/streams/PipeToState.h @@ -10,6 +10,7 @@ #define builtin_streams_PipeToState_h #include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/WrappingOperations.h" // mozilla::WrapToSigned #include // uint32_t @@ -86,25 +87,87 @@ class PipeToState : public NativeObject { SlotCount, }; + // The set of possible actions to be passed to the "shutdown with an action" + // algorithm. + // + // We store actions as numbers because 1) handler functions already devote + // their extra slots to target and extra value; and 2) storing a full function + // pointer would require an extra slot, while storing as number packs into + // existing flag storage. + enum class ShutdownAction { + /** The action used during |abortAlgorithm|.*/ + AbortAlgorithm, + + /** + * The action taken when |source| errors and aborting is not prevented, to + * abort |dest| with |source|'s error. + */ + AbortDestStream, + + /** + * The action taken when |dest| becomes errored or closed and canceling is + * not prevented, to cancel |source| with |dest|'s error. + */ + CancelSource, + + /** + * The action taken when |source| closes and closing is not prevented, to + * close the writer while propagating any error in it. + */ + CloseWriterWithErrorPropagation, + + }; + private: enum Flags : uint32_t { - Flag_ShuttingDown = 0b0001, + /** + * The action passed to the "shutdown with an action" algorithm. + * + * Note that because only the first "shutdown" and "shutdown with an action" + * operation has any effect, we can store this action in |PipeToState| in + * the first invocation of either operation without worrying about it being + * overwritten. + * + * Purely for convenience, we encode this in the lowest bits so that the + * result of a mask is the underlying value of the correct |ShutdownAction|. + */ + Flag_ShutdownActionBits = 0b0000'0011, - Flag_PreventClose = 0b0010, - Flag_PreventAbort = 0b0100, - Flag_PreventCancel = 0b1000, + Flag_ShuttingDown = 0b0000'0100, - Flag_PendingRead = 0b1'0000, + Flag_PendingRead = 0b0000'1000, #ifdef DEBUG - Flag_PendingReadWouldBeRejected = 0b10'0000, + Flag_PendingReadWouldBeRejected = 0b0001'0000, #endif + + Flag_PreventClose = 0b0010'0000, + Flag_PreventAbort = 0b0100'0000, + Flag_PreventCancel = 0b1000'0000, }; uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); } void setFlags(uint32_t flags) { - setFixedSlot(Slot_Flags, JS::Int32Value(flags)); + setFixedSlot(Slot_Flags, JS::Int32Value(mozilla::WrapToSigned(flags))); } + // Flags start out zeroed, so the initially-stored shutdown action value will + // be this value. (This is also the value of an *initialized* shutdown + // action, but it doesn't seem worth the trouble to store an extra bit to + // detect this specific action being recorded multiple times, purely for + // assertions.) + static constexpr ShutdownAction UninitializedAction = + ShutdownAction::AbortAlgorithm; + + static_assert(Flag_ShutdownActionBits & 1, + "shutdown action bits must be low-order bits so that we can " + "cast ShutdownAction values directly to bits to store"); + + static constexpr uint32_t MaxAction = + static_cast(ShutdownAction::CloseWriterWithErrorPropagation); + + static_assert(MaxAction <= Flag_ShutdownActionBits, + "max action shouldn't overflow available bits to store it"); + public: static const JSClass class_; @@ -144,6 +207,30 @@ class PipeToState : public NativeObject { setFlags(flags() | Flag_ShuttingDown); } + ShutdownAction shutdownAction() const { + MOZ_ASSERT(shuttingDown(), + "must be shutting down to have a shutdown action"); + + uint32_t bits = flags() & Flag_ShutdownActionBits; + static_assert(Flag_ShutdownActionBits & 1, + "shutdown action bits are assumed to be low-order bits that " + "don't have to be shifted down to ShutdownAction's range"); + + MOZ_ASSERT(bits <= MaxAction, "bits must encode a valid action"); + + return static_cast(bits); + } + + void setShutdownAction(ShutdownAction action) { + MOZ_ASSERT(shuttingDown(), + "must be protected by the |shuttingDown| boolean to save the " + "shutdown action"); + MOZ_ASSERT(shutdownAction() == UninitializedAction, + "should only set shutdown action once"); + + setFlags(flags() | static_cast(action)); + } + bool preventClose() const { return flags() & Flag_PreventClose; } bool preventAbort() const { return flags() & Flag_PreventAbort; } bool preventCancel() const { return flags() & Flag_PreventCancel; }