Bug 1546603 - Don't finalize stream sources from JS if creating the stream object failed, r=arai.
authorBrian Hackett <bhackett1024@gmail.com>
Thu, 25 Apr 2019 12:13:30 -1000
changeset 530509 2dcd3edb3c5d13410f9ae2943c103b9b9cda3afb
parent 530508 2d41d7ccab748178a7d601730c57431d9cdeb59e
child 530510 5257cabf18a9f322ff48973f2e02cc830eba5aa1
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1546603
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1546603 - Don't finalize stream sources from JS if creating the stream object failed, r=arai. Differential Revision: https://phabricator.services.mozilla.com/D28931
js/src/builtin/Stream.cpp
js/src/builtin/Stream.h
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -3462,30 +3462,54 @@ bool ReadableByteStreamController::const
                                                Value* vp) {
   // Step 1: Throw a TypeError exception.
   JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                             JSMSG_BOGUS_CONSTRUCTOR,
                             "ReadableByteStreamController");
   return false;
 }
 
+// Disconnect the source from a controller without calling finalize() on it,
+// unless this class is reset(). This ensures that finalize() will not be called
+// on the source if setting up the controller fails.
+class MOZ_RAII AutoClearUnderlyingSource {
+  Rooted<ReadableStreamController*> controller_;
+
+ public:
+  AutoClearUnderlyingSource(JSContext* cx, ReadableStreamController* controller)
+      : controller_(cx, controller) {}
+
+  ~AutoClearUnderlyingSource() {
+    if (controller_) {
+      ReadableStreamController::clearUnderlyingSource(
+          controller_, /* finalizeSource */ false);
+    }
+  }
+
+  void reset() {
+    controller_ = nullptr;
+  }
+};
+
 /**
  * Version of SetUpReadableByteStreamController that's specialized for handling
  * external, embedding-provided, underlying sources.
  */
 static MOZ_MUST_USE bool SetUpExternalReadableByteStreamController(
     JSContext* cx, Handle<ReadableStream*> stream,
     JS::ReadableStreamUnderlyingSource* source) {
   // Done elsewhere in the standard: Create the controller object.
   Rooted<ReadableByteStreamController*> controller(
       cx, NewBuiltinClassInstance<ReadableByteStreamController>(cx));
   if (!controller) {
     return false;
   }
 
+  AutoClearUnderlyingSource autoClear(cx, controller);
+
   // Step 1: Assert: stream.[[readableStreamController]] is undefined.
   MOZ_ASSERT(!stream->hasController());
 
   // Step 2: If autoAllocateChunkSize is not undefined, [...]
   // (It's treated as undefined.)
 
   // Step 3: Set controller.[[controlledReadableByteStream]] to stream.
   controller->setStream(stream);
@@ -3551,16 +3575,17 @@ static MOZ_MUST_USE bool SetUpExternalRe
   if (!onStartRejected) {
     return false;
   }
   if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled,
                                onStartRejected)) {
     return false;
   }
 
+  autoClear.reset();
   return true;
 }
 
 static const JSPropertySpec ReadableByteStreamController_properties[] = {
     JS_PS_END};
 
 static const JSFunctionSpec ReadableByteStreamController_methods[] = {
     JS_FS_END};
--- a/js/src/builtin/Stream.h
+++ b/js/src/builtin/Stream.h
@@ -312,19 +312,22 @@ class ReadableStreamController : public 
     return static_cast<JS::ReadableStreamUnderlyingSource*>(
         underlyingSource().toPrivate());
   }
   void setExternalSource(JS::ReadableStreamUnderlyingSource* underlyingSource) {
     setUnderlyingSource(JS::PrivateValue(underlyingSource));
     addFlags(Flag_ExternalSource);
   }
   static void clearUnderlyingSource(
-      JS::Handle<ReadableStreamController*> controller) {
+      JS::Handle<ReadableStreamController*> controller,
+      bool finalizeSource = true) {
     if (controller->hasExternalSource()) {
-      controller->externalSource()->finalize();
+      if (finalizeSource) {
+        controller->externalSource()->finalize();
+      }
       controller->setFlags(controller->flags() & ~Flag_ExternalSource);
     }
     controller->setUnderlyingSource(JS::UndefinedHandleValue);
   }
   double strategyHWM() const {
     return getFixedSlot(Slot_StrategyHWM).toNumber();
   }
   void setStrategyHWM(double highWaterMark) {