Bug 1224664 - Assert if an ErrorResult is accessed on a thread different than the one it's created on, r=khuey
authorEmilio Cobos Álvarez <me@emiliocobos.me>
Fri, 15 Jul 2016 22:35:13 -0400
changeset 330279 469d01eebea4e2055553289ce6542fc093460bbd
parent 330278 eabda35065c18c345b1e4d66fca6cd6c2fbae74d
child 330280 a80fdfc128b0c29dc34dd3fc28868252111a5d52
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs1224664
milestone50.0a1
Bug 1224664 - Assert if an ErrorResult is accessed on a thread different than the one it's created on, r=khuey
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/ErrorResult.h
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -154,37 +154,40 @@ struct ErrorResult::Message {
   {
     return GetErrorArgCount(mErrorNumber) == mArgs.Length();
   }
 };
 
 nsTArray<nsString>&
 ErrorResult::CreateErrorMessageHelper(const dom::ErrNum errorNumber, nsresult errorType)
 {
+  AssertInOwningThread();
   mResult = errorType;
 
   mMessage = new Message();
   mMessage->mErrorNumber = errorNumber;
   return mMessage->mArgs;
 }
 
 void
 ErrorResult::SerializeMessage(IPC::Message* aMsg) const
 {
   using namespace IPC;
+  AssertInOwningThread();
   MOZ_ASSERT(mUnionState == HasMessage);
   MOZ_ASSERT(mMessage);
   WriteParam(aMsg, mMessage->mArgs);
   WriteParam(aMsg, mMessage->mErrorNumber);
 }
 
 bool
 ErrorResult::DeserializeMessage(const IPC::Message* aMsg, PickleIterator* aIter)
 {
   using namespace IPC;
+  AssertInOwningThread();
   nsAutoPtr<Message> readMessage(new Message());
   if (!ReadParam(aMsg, aIter, &readMessage->mArgs) ||
       !ReadParam(aMsg, aIter, &readMessage->mErrorNumber)) {
     return false;
   }
   if (!readMessage->HasCorrectNumberOfArguments()) {
     return false;
   }
@@ -195,16 +198,17 @@ ErrorResult::DeserializeMessage(const IP
   mUnionState = HasMessage;
 #endif // DEBUG
   return true;
 }
 
 void
 ErrorResult::SetPendingExceptionWithMessage(JSContext* aCx)
 {
+  AssertInOwningThread();
   MOZ_ASSERT(mMessage, "SetPendingExceptionWithMessage() can be called only once");
   MOZ_ASSERT(mUnionState == HasMessage);
 
   Message* message = mMessage;
   MOZ_RELEASE_ASSERT(message->HasCorrectNumberOfArguments());
   const uint32_t argCount = message->mArgs.Length();
   const char16_t* args[JS::MaxNumErrorArguments + 1];
   for (uint32_t i = 0; i < argCount; ++i) {
@@ -218,27 +222,29 @@ ErrorResult::SetPendingExceptionWithMess
 
   ClearMessage();
   mResult = NS_OK;
 }
 
 void
 ErrorResult::ClearMessage()
 {
+  AssertInOwningThread();
   MOZ_ASSERT(IsErrorWithMessage());
   delete mMessage;
   mMessage = nullptr;
 #ifdef DEBUG
   mUnionState = HasNothing;
 #endif // DEBUG
 }
 
 void
 ErrorResult::ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn)
 {
+  AssertInOwningThread();
   MOZ_ASSERT(mMightHaveUnreportedJSException,
              "Why didn't you tell us you planned to throw a JS exception?");
 
   ClearUnionData();
 
   // Make sure mJSException is initialized _before_ we try to root it.  But
   // don't set it to exn yet, because we don't want to do that until after we
   // root.
@@ -254,16 +260,17 @@ ErrorResult::ThrowJSException(JSContext*
     mUnionState = HasJSException;
 #endif // DEBUG
   }
 }
 
 void
 ErrorResult::SetPendingJSException(JSContext* cx)
 {
+  AssertInOwningThread();
   MOZ_ASSERT(!mMightHaveUnreportedJSException,
              "Why didn't you tell us you planned to handle JS exceptions?");
   MOZ_ASSERT(mUnionState == HasJSException);
 
   JS::Rooted<JS::Value> exception(cx, mJSException);
   if (JS_WrapValue(cx, &exception)) {
     JS_SetPendingException(cx, exception);
   }
@@ -287,26 +294,28 @@ struct ErrorResult::DOMExceptionInfo {
   nsCString mMessage;
   nsresult mRv;
 };
 
 void
 ErrorResult::SerializeDOMExceptionInfo(IPC::Message* aMsg) const
 {
   using namespace IPC;
+  AssertInOwningThread();
   MOZ_ASSERT(mDOMExceptionInfo);
   MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
   WriteParam(aMsg, mDOMExceptionInfo->mMessage);
   WriteParam(aMsg, mDOMExceptionInfo->mRv);
 }
 
 bool
 ErrorResult::DeserializeDOMExceptionInfo(const IPC::Message* aMsg, PickleIterator* aIter)
 {
   using namespace IPC;
+  AssertInOwningThread();
   nsCString message;
   nsresult rv;
   if (!ReadParam(aMsg, aIter, &message) ||
       !ReadParam(aMsg, aIter, &rv)) {
     return false;
   }
 
   MOZ_ASSERT(mUnionState == HasNothing);
@@ -316,53 +325,57 @@ ErrorResult::DeserializeDOMExceptionInfo
   mUnionState = HasDOMExceptionInfo;
 #endif // DEBUG
   return true;
 }
 
 void
 ErrorResult::ThrowDOMException(nsresult rv, const nsACString& message)
 {
+  AssertInOwningThread();
   ClearUnionData();
 
   mResult = NS_ERROR_DOM_DOMEXCEPTION;
   mDOMExceptionInfo = new DOMExceptionInfo(rv, message);
 #ifdef DEBUG
   mUnionState = HasDOMExceptionInfo;
 #endif
 }
 
 void
 ErrorResult::SetPendingDOMException(JSContext* cx)
 {
+  AssertInOwningThread();
   MOZ_ASSERT(mDOMExceptionInfo,
              "SetPendingDOMException() can be called only once");
   MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
 
   dom::Throw(cx, mDOMExceptionInfo->mRv, mDOMExceptionInfo->mMessage);
 
   ClearDOMExceptionInfo();
   mResult = NS_OK;
 }
 
 void
 ErrorResult::ClearDOMExceptionInfo()
 {
+  AssertInOwningThread();
   MOZ_ASSERT(IsDOMException());
   MOZ_ASSERT(mUnionState == HasDOMExceptionInfo || !mDOMExceptionInfo);
   delete mDOMExceptionInfo;
   mDOMExceptionInfo = nullptr;
 #ifdef DEBUG
   mUnionState = HasNothing;
 #endif // DEBUG
 }
 
 void
 ErrorResult::ClearUnionData()
 {
+  AssertInOwningThread();
   if (IsJSException()) {
     JSContext* cx = nsContentUtils::RootingCx();
     MOZ_ASSERT(cx);
     mJSException.setUndefined();
     js::RemoveRawValueRoot(cx, &mJSException);
 #ifdef DEBUG
     mUnionState = HasNothing;
 #endif // DEBUG
@@ -371,26 +384,29 @@ ErrorResult::ClearUnionData()
   } else if (IsDOMException()) {
     ClearDOMExceptionInfo();
   }
 }
 
 void
 ErrorResult::SetPendingGenericErrorException(JSContext* cx)
 {
+  AssertInOwningThread();
   MOZ_ASSERT(!IsErrorWithMessage());
   MOZ_ASSERT(!IsJSException());
   MOZ_ASSERT(!IsDOMException());
   dom::Throw(cx, ErrorCode());
   mResult = NS_OK;
 }
 
 ErrorResult&
 ErrorResult::operator=(ErrorResult&& aRHS)
 {
+  AssertInOwningThread();
+  aRHS.AssertInOwningThread();
   // Clear out any union members we may have right now, before we
   // start writing to it.
   ClearUnionData();
 
 #ifdef DEBUG
   mMightHaveUnreportedJSException = aRHS.mMightHaveUnreportedJSException;
   aRHS.mMightHaveUnreportedJSException = false;
 #endif
@@ -425,16 +441,19 @@ ErrorResult::operator=(ErrorResult&& aRH
   mResult = aRHS.mResult;
   aRHS.mResult = NS_OK;
   return *this;
 }
 
 void
 ErrorResult::CloneTo(ErrorResult& aRv) const
 {
+  AssertInOwningThread();
+  aRv.AssertInOwningThread();
+
   aRv.ClearUnionData();
   aRv.mResult = mResult;
 #ifdef DEBUG
   aRv.mMightHaveUnreportedJSException = mMightHaveUnreportedJSException;
 #endif
 
   if (IsErrorWithMessage()) {
 #ifdef DEBUG
@@ -457,26 +476,28 @@ ErrorResult::CloneTo(ErrorResult& aRv) c
     JS::Rooted<JS::Value> exception(cx, mJSException);
     aRv.ThrowJSException(cx, exception);
   }
 }
 
 void
 ErrorResult::SuppressException()
 {
+  AssertInOwningThread();
   WouldReportJSException();
   ClearUnionData();
   // We don't use AssignErrorCode, because we want to override existing error
   // states, which AssignErrorCode is not allowed to do.
   mResult = NS_OK;
 }
 
 void
 ErrorResult::SetPendingException(JSContext* cx)
 {
+  AssertInOwningThread();
   if (IsUncatchableException()) {
     // Nuke any existing exception on cx, to make sure we're uncatchable.
     JS_ClearPendingException(cx);
     // Don't do any reporting.  Just return, to create an
     // uncatchable exception.
     mResult = NS_OK;
     return;
   }
@@ -499,32 +520,34 @@ ErrorResult::SetPendingException(JSConte
     return;
   }
   SetPendingGenericErrorException(cx);
 }
 
 void
 ErrorResult::StealExceptionFromJSContext(JSContext* cx)
 {
+  AssertInOwningThread();
   MOZ_ASSERT(mMightHaveUnreportedJSException,
              "Why didn't you tell us you planned to throw a JS exception?");
 
   JS::Rooted<JS::Value> exn(cx);
   if (!JS_GetPendingException(cx, &exn)) {
     ThrowUncatchableException();
     return;
   }
 
   ThrowJSException(cx, exn);
   JS_ClearPendingException(cx);
 }
 
 void
 ErrorResult::NoteJSContextException(JSContext* aCx)
 {
+  AssertInOwningThread();
   if (JS_IsExceptionPending(aCx)) {
     mResult = NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT;
   } else {
     mResult = NS_ERROR_UNCATCHABLE_EXCEPTION;
   }
 }
 
 namespace dom {
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -101,17 +101,17 @@ IsNonProxyDOMClass(const js::Class* clas
 }
 
 inline bool
 IsNonProxyDOMClass(const JSClass* clasp)
 {
   return IsNonProxyDOMClass(js::Valueify(clasp));
 }
 
-// Returns true if the JSClass is used for DOM interface and interface 
+// Returns true if the JSClass is used for DOM interface and interface
 // prototype objects.
 inline bool
 IsDOMIfaceAndProtoClass(const JSClass* clasp)
 {
   return clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS;
 }
 
 inline bool
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -26,16 +26,17 @@
 
 #include "js/GCAnnotations.h"
 #include "js/Value.h"
 #include "nscore.h"
 #include "nsStringGlue.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Move.h"
 #include "nsTArray.h"
+#include "nsISupportsImpl.h"
 
 namespace IPC {
 class Message;
 template <typename> struct ParamTraits;
 } // namespace IPC
 class PickleIterator;
 
 namespace mozilla {
@@ -100,16 +101,17 @@ public:
 
 #ifdef DEBUG
   ~ErrorResult() {
     // Consumers should have called one of MaybeSetPendingException
     // (possibly via ToJSValue), StealNSResult, and SuppressException
     MOZ_ASSERT(!Failed());
     MOZ_ASSERT(!mMightHaveUnreportedJSException);
     MOZ_ASSERT(mUnionState == HasNothing);
+    NS_ASSERT_OWNINGTHREAD(ErrorResult);
   }
 #endif // DEBUG
 
   ErrorResult(ErrorResult&& aRHS)
     // Initialize mResult and whatever else we need to default-initialize, so
     // the ClearUnionData call in our operator= will do the right thing
     // (nothing).
     : ErrorResult()
@@ -331,16 +333,22 @@ private:
     uint16_t argCount = dom::GetErrorArgCount(errorNumber);
     dom::StringArrayAppender::Append(messageArgsArray, argCount,
                                      Forward<Ts>(messageArgs)...);
 #ifdef DEBUG
     mUnionState = HasMessage;
 #endif // DEBUG
   }
 
+  MOZ_ALWAYS_INLINE void AssertInOwningThread() const {
+#ifdef DEBUG
+    NS_ASSERT_OWNINGTHREAD(ErrorResult);
+#endif
+  }
+
   void AssignErrorCode(nsresult aRv) {
     MOZ_ASSERT(aRv != NS_ERROR_TYPE_ERR, "Use ThrowTypeError()");
     MOZ_ASSERT(aRv != NS_ERROR_RANGE_ERR, "Use ThrowRangeError()");
     MOZ_ASSERT(!IsErrorWithMessage(), "Don't overwrite errors with message");
     MOZ_ASSERT(aRv != NS_ERROR_DOM_JS_EXCEPTION, "Use ThrowJSException()");
     MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions");
     MOZ_ASSERT(aRv != NS_ERROR_DOM_DOMEXCEPTION, "Use ThrowDOMException()");
     MOZ_ASSERT(!IsDOMException(), "Don't overwrite DOM exceptions");
@@ -399,16 +407,19 @@ private:
   // for assertion purposes.
   bool mMightHaveUnreportedJSException;
 
   // Used to keep track of what's stored in our union right now.  Note
   // that this may be set to HasNothing even if our mResult suggests
   // we should have something, if we have already cleaned up the
   // something.
   UnionState mUnionState;
+
+  // The thread that created this ErrorResult
+  NS_DECL_OWNINGTHREAD;
 #endif
 
   // Not to be implemented, to make sure people always pass this by
   // reference, not by value.
   ErrorResult(const ErrorResult&) = delete;
   void operator=(const ErrorResult&) = delete;
 };