Bug 839088 part 1. Add a way to throw a JS::Value on an ErrorResult. r=peterv
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 19 Feb 2013 11:54:40 -0500
changeset 128766 313c30fbb1faa88a69f572320ee1b9121359006a
parent 128765 1b1b5e2368174347255aa1ad248bfe4e787b1ac8
child 128767 8832678a13dbd5cbb67b85e0f152b8b89fc0f914
push id3582
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 20:50:56 +0000
treeherdermozilla-aurora@400370bbc9fa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs839088
milestone21.0a1
Bug 839088 part 1. Add a way to throw a JS::Value on an ErrorResult. r=peterv
dom/base/domerr.msg
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/ErrorResult.h
xpcom/base/ErrorList.h
--- a/dom/base/domerr.msg
+++ b/dom/base/domerr.msg
@@ -114,8 +114,10 @@ DOM_MSG_DEF(NS_ERROR_FACTORY_NO_SIGNATUR
 DOM_MSG_DEF(NS_ERROR_FACTORY_EXISTS                , "Factory already exists")
 
 DOM4_MSG_DEF(UnknownError, "The operation failed for reasons unrelated to the file storage itself and not covered by any other error code.", NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR)
 DOM4_MSG_DEF(LockedFileInactiveError, "A request was placed against a locked file which is currently not active, or which is finished.", NS_ERROR_DOM_FILEHANDLE_LOCKEDFILE_INACTIVE_ERR)
 DOM4_MSG_DEF(ReadOnlyError, "A mutation operation was attempted in a READ_ONLY locked file.", NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR)
 
 DOM4_MSG_DEF(InvalidStateError, "A mutation operation was attempted on a file storage that did not allow mutations.", NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR)
 DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to LockedFile.abort.", NS_ERROR_DOM_FILEHANDLE_ABORT_ERR)
+
+DOM_MSG_DEF(NS_ERROR_DOM_JS_EXCEPTION, "A callback threw an exception")
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -56,16 +56,24 @@ struct ErrorResult::Message {
   dom::ErrNum mErrorNumber;
 };
 
 void
 ErrorResult::ThrowTypeError(const dom::ErrNum errorNumber, ...)
 {
   va_list ap;
   va_start(ap, errorNumber);
+  if (IsJSException()) {
+    // We have rooted our mJSException, and we don't have the info
+    // needed to unroot here, so just bail.
+    va_end(ap);
+    MOZ_ASSERT(false,
+               "Ignoring ThrowTypeError call because we have a JS exception");
+    return;
+  }
   if (IsTypeError()) {
     delete mMessage;
   }
   mResult = NS_ERROR_TYPE_ERR;
   Message* message = new Message();
   message->mErrorNumber = errorNumber;
   uint16_t argCount =
     dom::GetErrorMessage(nullptr, nullptr, errorNumber)->argCount;
@@ -102,16 +110,49 @@ void
 ErrorResult::ClearMessage()
 {
   if (IsTypeError()) {
     delete mMessage;
     mMessage = nullptr;
   }
 }
 
+void
+ErrorResult::ThrowJSException(JSContext* cx, JS::Value exn)
+{
+  MOZ_ASSERT(mMightHaveUnreportedJSException,
+             "Why didn't you tell us you planned to throw a JS exception?");
+
+  if (IsTypeError()) {
+    delete mMessage;
+  }
+
+  if (!JS_AddNamedValueRoot(cx, &mJSException, "ErrorResult::mJSException")) {
+    // Don't use NS_ERROR_DOM_JS_EXCEPTION, because that indicates we have
+    // in fact rooted mJSException.
+    mResult = NS_ERROR_OUT_OF_MEMORY;
+  } else {
+    mJSException = exn;
+    mResult = NS_ERROR_DOM_JS_EXCEPTION;
+  }
+}
+
+void
+ErrorResult::ReportJSException(JSContext* cx)
+{
+  MOZ_ASSERT(!mMightHaveUnreportedJSException,
+             "Why didn't you tell us you planned to handle JS exceptions?");
+  if (JS_WrapValue(cx, &mJSException)) {
+    JS_SetPendingException(cx, mJSException);
+  }
+  // If JS_WrapValue failed, not much we can do about it...  No matter
+  // what, go ahead and unroot mJSException.
+  JS_RemoveValueRoot(cx, &mJSException);
+}
+
 namespace dom {
 
 bool
 DefineConstants(JSContext* cx, JSObject* obj, ConstantSpec* cs)
 {
   for (; cs->name; ++cs) {
     JSBool ok =
       JS_DefineProperty(cx, obj, cs->name, cs->value, NULL, NULL,
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -57,16 +57,20 @@ inline bool
 ThrowMethodFailedWithDetails(JSContext* cx, ErrorResult& rv,
                              const char* /* ifaceName */,
                              const char* /* memberName */)
 {
   if (rv.IsTypeError()) {
     rv.ReportTypeError(cx);
     return false;
   }
+  if (rv.IsJSException()) {
+    rv.ReportJSException(cx);
+    return false;
+  }
   return Throw<mainThread>(cx, rv.ErrorCode());
 }
 
 // Returns true if the JSClass is used for DOM objects.
 inline bool
 IsDOMClass(const JSClass* clasp)
 {
   return clasp->flags & JSCLASS_IS_DOMJSCLASS;
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3681,16 +3681,17 @@ class CGCallGenerator(CGThing):
             if not resultOutParam:
                 call = CGWrapper(call, pre="result = ")
 
         call = CGWrapper(call)
         self.cgRoot.append(call)
 
         if isFallible:
             self.cgRoot.prepend(CGGeneric("ErrorResult rv;"))
+            self.cgRoot.append(CGGeneric("rv.WouldReportJSException();"));
             self.cgRoot.append(CGGeneric("if (rv.Failed()) {"))
             self.cgRoot.append(CGIndenter(errorReport))
             self.cgRoot.append(CGGeneric("}"))
 
     def define(self):
         return self.cgRoot.define()
 
 class MethodNotCreatorError(Exception):
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -30,61 +30,103 @@ enum ErrNum {
 };
 
 } // namespace dom
 
 class ErrorResult {
 public:
   ErrorResult() {
     mResult = NS_OK;
+#ifdef DEBUG
+    mMightHaveUnreportedJSException = false;
+#endif
   }
+
 #ifdef DEBUG
   ~ErrorResult() {
     MOZ_ASSERT_IF(IsTypeError(), !mMessage);
+    MOZ_ASSERT(!mMightHaveUnreportedJSException);
   }
 #endif
 
   void Throw(nsresult rv) {
     MOZ_ASSERT(NS_FAILED(rv), "Please don't try throwing success");
     MOZ_ASSERT(rv != NS_ERROR_TYPE_ERR, "Use ThrowTypeError()");
     MOZ_ASSERT(!IsTypeError(), "Don't overwite TypeError");
+    MOZ_ASSERT(rv != NS_ERROR_DOM_JS_EXCEPTION, "Use ThrowJSException()");
+    MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions");
     mResult = rv;
   }
 
   void ThrowTypeError(const dom::ErrNum errorNumber, ...);
   void ReportTypeError(JSContext* cx);
   void ClearMessage();
   bool IsTypeError() const { return ErrorCode() == NS_ERROR_TYPE_ERR; }
 
+  // Facilities for throwing a preexisting JS exception value via this
+  // ErrorResult.  The contract is that any code which might end up calling
+  // ThrowJSException() must call MightThrowJSException() even if no exception
+  // is being thrown.  Code that would call ReportJSException as needed must
+  // first call WouldReportJSException even if this ErrorResult has not failed.
+  void ThrowJSException(JSContext* cx, JS::Value exn);
+  void ReportJSException(JSContext* cx);
+  bool IsJSException() const { return ErrorCode() == NS_ERROR_DOM_JS_EXCEPTION; }
+  void MOZ_ALWAYS_INLINE MightThrowJSException()
+  {
+#ifdef DEBUG
+    mMightHaveUnreportedJSException = true;
+#endif
+  }
+  void MOZ_ALWAYS_INLINE WouldReportJSException()
+  {
+#ifdef DEBUG
+    mMightHaveUnreportedJSException = false;
+#endif
+  }
+
   // In the future, we can add overloads of Throw that take more
   // interesting things, like strings or DOM exception types or
   // something if desired.
 
   // Backwards-compat to make conversion simpler.  We don't call
   // Throw() here because people can easily pass success codes to
   // this.
   void operator=(nsresult rv) {
     MOZ_ASSERT(rv != NS_ERROR_TYPE_ERR, "Use ThrowTypeError()");
     MOZ_ASSERT(!IsTypeError(), "Don't overwite TypeError");
+    MOZ_ASSERT(rv != NS_ERROR_DOM_JS_EXCEPTION, "Use ThrowJSException()");
+    MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions");
     mResult = rv;
   }
 
   bool Failed() const {
     return NS_FAILED(mResult);
   }
 
   nsresult ErrorCode() const {
     return mResult;
   }
 
 private:
   nsresult mResult;
   struct Message;
-  // Do not use nsAutoPtr to avoid extra initalizatoin and check.
-  Message* mMessage;
+  // mMessage is set by ThrowTypeError and cleared (and deallocatd) by
+  // ReportTypeError.
+  // mJSException is set (and rooted) by ThrowJSException and unrooted
+  // by ReportJSException.
+  union {
+    Message* mMessage; // valid when IsTypeError()
+    JS::Value mJSException; // valid when IsJSException()
+  };
+
+#ifdef DEBUG
+  // Used to keep track of codepaths that might throw JS exceptions,
+  // for assertion purposes.
+  bool mMightHaveUnreportedJSException;
+#endif
 
   // Not to be implemented, to make sure people always pass this by
   // reference, not by value.
   ErrorResult(const ErrorResult&) MOZ_DELETE;
 };
 
 } // namespace mozilla
 
--- a/xpcom/base/ErrorList.h
+++ b/xpcom/base/ErrorList.h
@@ -496,17 +496,17 @@
 #undef MODULE
 
 
   /* ======================================================================= */
   /* 14: NS_ERROR_MODULE_DOM */
   /* ======================================================================= */
 #define MODULE NS_ERROR_MODULE_DOM
   /* XXX If you add a new DOM error code, also add an error string to
-   * dom/src/base/domerr.msg */
+   * dom/base/domerr.msg */
 
   /* Standard DOM error codes: http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html */
   ERROR(NS_ERROR_DOM_INDEX_SIZE_ERR,               FAILURE(1)),
   ERROR(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR,        FAILURE(3)),
   ERROR(NS_ERROR_DOM_WRONG_DOCUMENT_ERR,           FAILURE(4)),
   ERROR(NS_ERROR_DOM_INVALID_CHARACTER_ERR,        FAILURE(5)),
   ERROR(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR,  FAILURE(7)),
   ERROR(NS_ERROR_DOM_NOT_FOUND_ERR,                FAILURE(8)),
@@ -543,16 +543,17 @@
   ERROR(NS_ERROR_DOM_NOT_FUNCTION_ERR,             FAILURE(1007)),
   ERROR(NS_ERROR_DOM_TOO_FEW_PARAMETERS_ERR,       FAILURE(1008)),
   ERROR(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN,          FAILURE(1009)),
   ERROR(NS_ERROR_DOM_PROP_ACCESS_DENIED,           FAILURE(1010)),
   ERROR(NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED,      FAILURE(1011)),
   ERROR(NS_ERROR_DOM_BAD_URI,                      FAILURE(1012)),
   ERROR(NS_ERROR_DOM_RETVAL_UNDEFINED,             FAILURE(1013)),
   ERROR(NS_ERROR_DOM_QUOTA_REACHED,                FAILURE(1014)),
+  ERROR(NS_ERROR_DOM_JS_EXCEPTION,                 FAILURE(1015)),
 #undef MODULE
 
 
   /* ======================================================================= */
   /* 15: NS_ERROR_MODULE_IMGLIB */
   /* ======================================================================= */
 #define MODULE NS_ERROR_MODULE_IMGLIB
   ERROR(NS_IMAGELIB_SUCCESS_LOAD_FINISHED,  SUCCESS(0)),