summaryrefslogtreecommitdiffstats
path: root/dom/bindings/ErrorResult.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/bindings/ErrorResult.h')
-rw-r--r--dom/bindings/ErrorResult.h962
1 files changed, 962 insertions, 0 deletions
diff --git a/dom/bindings/ErrorResult.h b/dom/bindings/ErrorResult.h
new file mode 100644
index 0000000000..d2003c1d9b
--- /dev/null
+++ b/dom/bindings/ErrorResult.h
@@ -0,0 +1,962 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A set of structs for tracking exceptions that need to be thrown to JS:
+ * ErrorResult and IgnoredErrorResult.
+ *
+ * Conceptually, these structs represent either success or an exception in the
+ * process of being thrown. This means that a failing ErrorResult _must_ be
+ * handled in one of the following ways before coming off the stack:
+ *
+ * 1) Suppressed via SuppressException().
+ * 2) Converted to a pure nsresult return value via StealNSResult().
+ * 3) Converted to an actual pending exception on a JSContext via
+ * MaybeSetPendingException.
+ * 4) Converted to an exception JS::Value (probably to then reject a Promise
+ * with) via dom::ToJSValue.
+ *
+ * An IgnoredErrorResult will automatically do the first of those four things.
+ */
+
+#ifndef mozilla_ErrorResult_h
+#define mozilla_ErrorResult_h
+
+#include <stdarg.h>
+
+#include <new>
+#include <utility>
+
+#include "js/GCAnnotations.h"
+#include "js/ErrorReport.h"
+#include "js/Value.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Utf8.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nscore.h"
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+template <typename>
+struct ParamTraits;
+} // namespace IPC
+class PickleIterator;
+
+namespace mozilla {
+
+namespace dom {
+
+class Promise;
+
+enum ErrNum : uint16_t {
+#define MSG_DEF(_name, _argc, _has_context, _exn, _str) _name,
+#include "mozilla/dom/Errors.msg"
+#undef MSG_DEF
+ Err_Limit
+};
+
+// Debug-only compile-time table of the number of arguments of each error, for
+// use in static_assert.
+#if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__))
+uint16_t constexpr ErrorFormatNumArgs[] = {
+# define MSG_DEF(_name, _argc, _has_context, _exn, _str) _argc,
+# include "mozilla/dom/Errors.msg"
+# undef MSG_DEF
+};
+#endif
+
+// Table of whether various error messages want a context arg.
+bool constexpr ErrorFormatHasContext[] = {
+#define MSG_DEF(_name, _argc, _has_context, _exn, _str) _has_context,
+#include "mozilla/dom/Errors.msg"
+#undef MSG_DEF
+};
+
+// Table of the kinds of exceptions error messages will produce.
+JSExnType constexpr ErrorExceptionType[] = {
+#define MSG_DEF(_name, _argc, _has_context, _exn, _str) _exn,
+#include "mozilla/dom/Errors.msg"
+#undef MSG_DEF
+};
+
+uint16_t GetErrorArgCount(const ErrNum aErrorNumber);
+
+namespace binding_detail {
+void ThrowErrorMessage(JSContext* aCx, const unsigned aErrorNumber, ...);
+} // namespace binding_detail
+
+template <ErrNum errorNumber, typename... Ts>
+inline bool ThrowErrorMessage(JSContext* aCx, Ts&&... aArgs) {
+#if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__))
+ static_assert(ErrorFormatNumArgs[errorNumber] == sizeof...(aArgs),
+ "Pass in the right number of arguments");
+#endif
+ binding_detail::ThrowErrorMessage(aCx, static_cast<unsigned>(errorNumber),
+ std::forward<Ts>(aArgs)...);
+ return false;
+}
+
+template <typename CharT>
+struct TStringArrayAppender {
+ static void Append(nsTArray<nsTString<CharT>>& aArgs, uint16_t aCount) {
+ MOZ_RELEASE_ASSERT(aCount == 0,
+ "Must give at least as many string arguments as are "
+ "required by the ErrNum.");
+ }
+
+ // Allow passing nsAString/nsACString instances for our args.
+ template <typename... Ts>
+ static void Append(nsTArray<nsTString<CharT>>& aArgs, uint16_t aCount,
+ const nsTSubstring<CharT>& aFirst, Ts&&... aOtherArgs) {
+ if (aCount == 0) {
+ MOZ_ASSERT(false,
+ "There should not be more string arguments provided than are "
+ "required by the ErrNum.");
+ return;
+ }
+ aArgs.AppendElement(aFirst);
+ Append(aArgs, aCount - 1, std::forward<Ts>(aOtherArgs)...);
+ }
+
+ // Also allow passing literal instances for our args.
+ template <int N, typename... Ts>
+ static void Append(nsTArray<nsTString<CharT>>& aArgs, uint16_t aCount,
+ const CharT (&aFirst)[N], Ts&&... aOtherArgs) {
+ if (aCount == 0) {
+ MOZ_ASSERT(false,
+ "There should not be more string arguments provided than are "
+ "required by the ErrNum.");
+ return;
+ }
+ aArgs.AppendElement(nsTLiteralString<CharT>(aFirst));
+ Append(aArgs, aCount - 1, std::forward<Ts>(aOtherArgs)...);
+ }
+};
+
+using StringArrayAppender = TStringArrayAppender<char16_t>;
+using CStringArrayAppender = TStringArrayAppender<char>;
+
+} // namespace dom
+
+class ErrorResult;
+class OOMReporter;
+class CopyableErrorResult;
+
+namespace binding_danger {
+
+/**
+ * Templated implementation class for various ErrorResult-like things. The
+ * instantiations differ only in terms of their cleanup policies (used in the
+ * destructor), which they can specify via the template argument. Note that
+ * this means it's safe to reinterpret_cast between the instantiations unless
+ * you plan to invoke the destructor through such a cast pointer.
+ *
+ * A cleanup policy consists of two booleans: whether to assert that we've been
+ * reported or suppressed, and whether to then go ahead and suppress the
+ * exception.
+ */
+template <typename CleanupPolicy>
+class TErrorResult {
+ public:
+ TErrorResult()
+ : mResult(NS_OK)
+#ifdef DEBUG
+ ,
+ mMightHaveUnreportedJSException(false),
+ mUnionState(HasNothing)
+#endif
+ {
+ }
+
+ ~TErrorResult() {
+ AssertInOwningThread();
+
+ if (CleanupPolicy::assertHandled) {
+ // Consumers should have called one of MaybeSetPendingException
+ // (possibly via ToJSValue), StealNSResult, and SuppressException
+ AssertReportedOrSuppressed();
+ }
+
+ if (CleanupPolicy::suppress) {
+ SuppressException();
+ }
+
+ // And now assert that we're in a good final state.
+ AssertReportedOrSuppressed();
+ }
+
+ TErrorResult(TErrorResult&& aRHS)
+ // Initialize mResult and whatever else we need to default-initialize, so
+ // the ClearUnionData call in our operator= will do the right thing
+ // (nothing).
+ : TErrorResult() {
+ *this = std::move(aRHS);
+ }
+ TErrorResult& operator=(TErrorResult&& aRHS);
+
+ explicit TErrorResult(nsresult aRv) : TErrorResult() { AssignErrorCode(aRv); }
+
+ operator ErrorResult&();
+ operator const ErrorResult&() const;
+ operator OOMReporter&();
+
+ // This method is deprecated. Consumers should Throw*Error with the
+ // appropriate DOMException name if they are throwing a DOMException. If they
+ // have a random nsresult which may or may not correspond to a DOMException
+ // type, they should consider using an appropriate DOMException with an
+ // informative message and calling the relevant Throw*Error.
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG Throw(nsresult rv) {
+ MOZ_ASSERT(NS_FAILED(rv), "Please don't try throwing success");
+ AssignErrorCode(rv);
+ }
+
+ // Duplicate our current state on the given TErrorResult object. Any
+ // existing errors or messages on the target will be suppressed before
+ // cloning. Our own error state remains unchanged.
+ void CloneTo(TErrorResult& aRv) const;
+
+ // Use SuppressException when you want to suppress any exception that might be
+ // on the TErrorResult. After this call, the TErrorResult will be back a "no
+ // exception thrown" state.
+ void SuppressException();
+
+ // Use StealNSResult() when you want to safely convert the TErrorResult to
+ // an nsresult that you will then return to a caller. This will
+ // SuppressException(), since there will no longer be a way to report it.
+ nsresult StealNSResult() {
+ nsresult rv = ErrorCode();
+ SuppressException();
+ // Don't propagate out our internal error codes that have special meaning.
+ if (rv == NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR ||
+ rv == NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR ||
+ rv == NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION ||
+ rv == NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION) {
+ // What to pick here?
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ return rv;
+ }
+
+ // Use MaybeSetPendingException to convert a TErrorResult to a pending
+ // exception on the given JSContext. This is the normal "throw an exception"
+ // codepath.
+ //
+ // The return value is false if the TErrorResult represents success, true
+ // otherwise. This does mean that in JSAPI method implementations you can't
+ // just use this as |return rv.MaybeSetPendingException(cx)| (though you could
+ // |return !rv.MaybeSetPendingException(cx)|), but in practice pretty much any
+ // consumer would want to do some more work on the success codepath. So
+ // instead the way you use this is:
+ //
+ // if (rv.MaybeSetPendingException(cx)) {
+ // bail out here
+ // }
+ // go on to do something useful
+ //
+ // The success path is inline, since it should be the common case and we don't
+ // want to pay the price of a function call in some of the consumers of this
+ // method in the common case.
+ //
+ // Note that a true return value does NOT mean there is now a pending
+ // exception on aCx, due to uncatchable exceptions. It should still be
+ // considered equivalent to a JSAPI failure in terms of what callers should do
+ // after true is returned.
+ //
+ // After this call, the TErrorResult will no longer return true from Failed(),
+ // since the exception will have moved to the JSContext.
+ //
+ // If "context" is not null and our exception has a useful message string, the
+ // string "%s: ", with the value of "context" replacing %s, will be prepended
+ // to the message string. The passed-in string must be ASCII.
+ [[nodiscard]] bool MaybeSetPendingException(
+ JSContext* cx, const char* description = nullptr) {
+ WouldReportJSException();
+ if (!Failed()) {
+ return false;
+ }
+
+ SetPendingException(cx, description);
+ return true;
+ }
+
+ // Use StealExceptionFromJSContext to convert a pending exception on a
+ // JSContext to a TErrorResult. This function must be called only when a
+ // JSAPI operation failed. It assumes that lack of pending exception on the
+ // JSContext means an uncatchable exception was thrown.
+ //
+ // Codepaths that might call this method must call MightThrowJSException even
+ // if the relevant JSAPI calls do not fail.
+ //
+ // When this function returns, JS_IsExceptionPending(cx) will definitely be
+ // false.
+ void StealExceptionFromJSContext(JSContext* cx);
+
+ template <dom::ErrNum errorNumber, typename... Ts>
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowTypeError(Ts&&... messageArgs) {
+ static_assert(dom::ErrorExceptionType[errorNumber] == JSEXN_TYPEERR,
+ "Throwing a non-TypeError via ThrowTypeError");
+ ThrowErrorWithMessage<errorNumber>(NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR,
+ std::forward<Ts>(messageArgs)...);
+ }
+
+ // To be used when throwing a TypeError with a completely custom
+ // message string that's only used in one spot.
+ inline void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowTypeError(const nsACString& aMessage) {
+ this->template ThrowTypeError<dom::MSG_ONE_OFF_TYPEERR>(aMessage);
+ }
+
+ // To be used when throwing a TypeError with a completely custom
+ // message string that's a string literal that's only used in one spot.
+ template <int N>
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowTypeError(const char (&aMessage)[N]) {
+ ThrowTypeError(nsLiteralCString(aMessage));
+ }
+
+ template <dom::ErrNum errorNumber, typename... Ts>
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowRangeError(Ts&&... messageArgs) {
+ static_assert(dom::ErrorExceptionType[errorNumber] == JSEXN_RANGEERR,
+ "Throwing a non-RangeError via ThrowRangeError");
+ ThrowErrorWithMessage<errorNumber>(NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR,
+ std::forward<Ts>(messageArgs)...);
+ }
+
+ // To be used when throwing a RangeError with a completely custom
+ // message string that's only used in one spot.
+ inline void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowRangeError(const nsACString& aMessage) {
+ this->template ThrowRangeError<dom::MSG_ONE_OFF_RANGEERR>(aMessage);
+ }
+
+ // To be used when throwing a RangeError with a completely custom
+ // message string that's a string literal that's only used in one spot.
+ template <int N>
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowRangeError(const char (&aMessage)[N]) {
+ ThrowRangeError(nsLiteralCString(aMessage));
+ }
+
+ bool IsErrorWithMessage() const {
+ return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR ||
+ ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR;
+ }
+
+ // Facilities for throwing a preexisting JS exception value via this
+ // TErrorResult. The contract is that any code which might end up calling
+ // ThrowJSException() or StealExceptionFromJSContext() must call
+ // MightThrowJSException() even if no exception is being thrown. Code that
+ // conditionally calls ToJSValue on this TErrorResult only if Failed() must
+ // first call WouldReportJSException even if this TErrorResult has not failed.
+ //
+ // The exn argument to ThrowJSException can be in any compartment. It does
+ // not have to be in the compartment of cx. If someone later uses it, they
+ // will wrap it into whatever compartment they're working in, as needed.
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn);
+ bool IsJSException() const {
+ return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION;
+ }
+
+ // Facilities for throwing DOMExceptions of whatever type a spec calls for.
+ // If an empty message string is passed to one of these Throw*Error functions,
+ // the default message string for the relevant type of DOMException will be
+ // used. The passed-in string must be UTF-8.
+#define DOMEXCEPTION(name, err) \
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG Throw##name( \
+ const nsACString& aMessage) { \
+ ThrowDOMException(err, aMessage); \
+ } \
+ \
+ template <int N> \
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG Throw##name( \
+ const char(&aMessage)[N]) { \
+ ThrowDOMException(err, aMessage); \
+ }
+
+#include "mozilla/dom/DOMExceptionNames.h"
+
+#undef DOMEXCEPTION
+
+ bool IsDOMException() const {
+ return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION;
+ }
+
+ // Flag on the TErrorResult that whatever needs throwing has been
+ // thrown on the JSContext already and we should not mess with it.
+ // If nothing was thrown, this becomes an uncatchable exception.
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ NoteJSContextException(JSContext* aCx);
+
+ // Check whether the TErrorResult says to just throw whatever is on
+ // the JSContext already.
+ bool IsJSContextException() {
+ return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT;
+ }
+
+ // Support for uncatchable exceptions.
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG ThrowUncatchableException() {
+ Throw(NS_ERROR_UNCATCHABLE_EXCEPTION);
+ }
+ bool IsUncatchableException() const {
+ return ErrorCode() == NS_ERROR_UNCATCHABLE_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. This operator is deprecated and ideally shouldn't be used.
+ void operator=(nsresult rv) { AssignErrorCode(rv); }
+
+ bool Failed() const { return NS_FAILED(mResult); }
+
+ bool ErrorCodeIs(nsresult rv) const { return mResult == rv; }
+
+ // For use in logging ONLY.
+ uint32_t ErrorCodeAsInt() const { return static_cast<uint32_t>(ErrorCode()); }
+
+ bool operator==(const ErrorResult& aRight) const;
+
+ protected:
+ nsresult ErrorCode() const { return mResult; }
+
+ // Helper methods for throwing DOMExceptions, for now. We can try to get rid
+ // of these once EME code is fixed to not use them and we decouple
+ // DOMExceptions from nsresult.
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowDOMException(nsresult rv, const nsACString& message);
+
+ // Same thing, but using a string literal.
+ template <int N>
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowDOMException(nsresult rv, const char (&aMessage)[N]) {
+ ThrowDOMException(rv, nsLiteralCString(aMessage));
+ }
+
+ // Allow Promise to call the above methods when it really needs to.
+ // Unfortunately, we can't have the definition of Promise here, so can't mark
+ // just it's MaybeRejectWithDOMException method as a friend. In any case,
+ // hopefully it's all temporary until we sort out the EME bits.
+ friend class dom::Promise;
+
+ // Implementation of MaybeSetPendingException for the case when we're a
+ // failure result. See documentation of MaybeSetPendingException for the
+ // "context" argument.
+ void SetPendingException(JSContext* cx, const char* context);
+
+ private:
+#ifdef DEBUG
+ enum UnionState {
+ HasMessage,
+ HasDOMExceptionInfo,
+ HasJSException,
+ HasNothing
+ };
+#endif // DEBUG
+
+ friend struct IPC::ParamTraits<TErrorResult>;
+ friend struct IPC::ParamTraits<ErrorResult>;
+ void SerializeMessage(IPC::MessageWriter* aWriter) const;
+ bool DeserializeMessage(IPC::MessageReader* aReader);
+
+ void SerializeDOMExceptionInfo(IPC::MessageWriter* aWriter) const;
+ bool DeserializeDOMExceptionInfo(IPC::MessageReader* aReader);
+
+ // Helper method that creates a new Message for this TErrorResult,
+ // and returns the arguments array from that Message.
+ nsTArray<nsCString>& CreateErrorMessageHelper(const dom::ErrNum errorNumber,
+ nsresult errorType);
+
+ // Helper method to replace invalid UTF-8 characters with the replacement
+ // character. aValidUpTo is the number of characters that are known to be
+ // valid. The string might be truncated if we encounter an OOM error.
+ static void EnsureUTF8Validity(nsCString& aValue, size_t aValidUpTo);
+
+ template <dom::ErrNum errorNumber, typename... Ts>
+ void ThrowErrorWithMessage(nsresult errorType, Ts&&... messageArgs) {
+#if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__))
+ static_assert(dom::ErrorFormatNumArgs[errorNumber] ==
+ sizeof...(messageArgs) +
+ int(dom::ErrorFormatHasContext[errorNumber]),
+ "Pass in the right number of arguments");
+#endif
+
+ ClearUnionData();
+
+ nsTArray<nsCString>& messageArgsArray =
+ CreateErrorMessageHelper(errorNumber, errorType);
+ uint16_t argCount = dom::GetErrorArgCount(errorNumber);
+ if (dom::ErrorFormatHasContext[errorNumber]) {
+ // Insert an empty string arg at the beginning and reduce our arg count to
+ // still be appended accordingly.
+ MOZ_ASSERT(argCount > 0,
+ "Must have at least one arg if we have a context!");
+ MOZ_ASSERT(messageArgsArray.Length() == 0,
+ "Why do we already have entries in the array?");
+ --argCount;
+ messageArgsArray.AppendElement();
+ }
+ dom::CStringArrayAppender::Append(messageArgsArray, argCount,
+ std::forward<Ts>(messageArgs)...);
+ for (nsCString& arg : messageArgsArray) {
+ size_t validUpTo = Utf8ValidUpTo(arg);
+ if (validUpTo != arg.Length()) {
+ EnsureUTF8Validity(arg, validUpTo);
+ }
+ }
+#ifdef DEBUG
+ mUnionState = HasMessage;
+#endif // DEBUG
+ }
+
+ MOZ_ALWAYS_INLINE void AssertInOwningThread() const {
+#ifdef DEBUG
+ if (CleanupPolicy::assertSameThread) {
+ NS_ASSERT_OWNINGTHREAD(TErrorResult);
+ }
+#endif
+ }
+
+ void AssignErrorCode(nsresult aRv) {
+ MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR,
+ "Use ThrowTypeError()");
+ MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR,
+ "Use ThrowRangeError()");
+ MOZ_ASSERT(!IsErrorWithMessage(), "Don't overwrite errors with message");
+ MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION,
+ "Use ThrowJSException()");
+ MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions");
+ MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION,
+ "Use Throw*Error for the appropriate DOMException name");
+ MOZ_ASSERT(!IsDOMException(), "Don't overwrite DOM exceptions");
+ MOZ_ASSERT(aRv != NS_ERROR_XPC_NOT_ENOUGH_ARGS,
+ "May need to bring back ThrowNotEnoughArgsError");
+ MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT,
+ "Use NoteJSContextException");
+ mResult = aRv;
+ }
+
+ void ClearMessage();
+ void ClearDOMExceptionInfo();
+
+ // ClearUnionData will try to clear the data in our mExtra union. After this
+ // the union may be in an uninitialized state (e.g. mMessage or
+ // mDOMExceptionInfo may point to deleted memory, or mJSException may be a
+ // JS::Value containing an invalid gcthing) and the caller must either
+ // reinitialize it or change mResult to something that will not involve us
+ // touching the union anymore.
+ void ClearUnionData();
+
+ // Methods for setting various specific kinds of pending exceptions. See
+ // documentation of MaybeSetPendingException for the "context" argument.
+ void SetPendingExceptionWithMessage(JSContext* cx, const char* context);
+ void SetPendingJSException(JSContext* cx);
+ void SetPendingDOMException(JSContext* cx, const char* context);
+ void SetPendingGenericErrorException(JSContext* cx);
+
+ MOZ_ALWAYS_INLINE void AssertReportedOrSuppressed() {
+ MOZ_ASSERT(!Failed());
+ MOZ_ASSERT(!mMightHaveUnreportedJSException);
+ MOZ_ASSERT(mUnionState == HasNothing);
+ }
+
+ // Special values of mResult:
+ // NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR -- ThrowTypeError() called on us.
+ // NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR -- ThrowRangeError() called on us.
+ // NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION -- ThrowJSException() called
+ // on us.
+ // NS_ERROR_UNCATCHABLE_EXCEPTION -- ThrowUncatchableException called on us.
+ // NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION -- ThrowDOMException() called
+ // on us.
+ nsresult mResult;
+
+ struct Message;
+ struct DOMExceptionInfo;
+ union Extra {
+ // mMessage is set by ThrowErrorWithMessage and reported (and deallocated)
+ // by SetPendingExceptionWithMessage.
+ MOZ_INIT_OUTSIDE_CTOR
+ Message* mMessage; // valid when IsErrorWithMessage()
+
+ // mJSException is set (and rooted) by ThrowJSException and reported (and
+ // unrooted) by SetPendingJSException.
+ MOZ_INIT_OUTSIDE_CTOR
+ JS::Value mJSException; // valid when IsJSException()
+
+ // mDOMExceptionInfo is set by ThrowDOMException and reported (and
+ // deallocated) by SetPendingDOMException.
+ MOZ_INIT_OUTSIDE_CTOR
+ DOMExceptionInfo* mDOMExceptionInfo; // valid when IsDOMException()
+
+ // |mJSException| has a non-trivial constructor and therefore MUST be
+ // placement-new'd into existence.
+ MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS
+ Extra() {} // NOLINT
+ MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS
+ } mExtra;
+
+ Message* InitMessage(Message* aMessage) {
+ // The |new| here switches the active arm of |mExtra|, from the compiler's
+ // point of view. Mere assignment *won't* necessarily do the right thing!
+ new (&mExtra.mMessage) Message*(aMessage);
+ return mExtra.mMessage;
+ }
+
+ JS::Value& InitJSException() {
+ // The |new| here switches the active arm of |mExtra|, from the compiler's
+ // point of view. Mere assignment *won't* necessarily do the right thing!
+ new (&mExtra.mJSException) JS::Value(); // sets to undefined
+ return mExtra.mJSException;
+ }
+
+ DOMExceptionInfo* InitDOMExceptionInfo(DOMExceptionInfo* aDOMExceptionInfo) {
+ // The |new| here switches the active arm of |mExtra|, from the compiler's
+ // point of view. Mere assignment *won't* necessarily do the right thing!
+ new (&mExtra.mDOMExceptionInfo) DOMExceptionInfo*(aDOMExceptionInfo);
+ return mExtra.mDOMExceptionInfo;
+ }
+
+#ifdef DEBUG
+ // Used to keep track of codepaths that might throw JS exceptions,
+ // 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 TErrorResult
+ NS_DECL_OWNINGTHREAD;
+#endif
+
+ // Not to be implemented, to make sure people always pass this by
+ // reference, not by value.
+ TErrorResult(const TErrorResult&) = delete;
+ void operator=(const TErrorResult&) = delete;
+} JS_HAZ_ROOTED;
+
+struct JustAssertCleanupPolicy {
+ static const bool assertHandled = true;
+ static const bool suppress = false;
+ static const bool assertSameThread = true;
+};
+
+struct AssertAndSuppressCleanupPolicy {
+ static const bool assertHandled = true;
+ static const bool suppress = true;
+ static const bool assertSameThread = true;
+};
+
+struct JustSuppressCleanupPolicy {
+ static const bool assertHandled = false;
+ static const bool suppress = true;
+ static const bool assertSameThread = true;
+};
+
+struct ThreadSafeJustSuppressCleanupPolicy {
+ static const bool assertHandled = false;
+ static const bool suppress = true;
+ static const bool assertSameThread = false;
+};
+
+} // namespace binding_danger
+
+// A class people should normally use on the stack when they plan to actually
+// do something with the exception.
+class ErrorResult : public binding_danger::TErrorResult<
+ binding_danger::AssertAndSuppressCleanupPolicy> {
+ typedef binding_danger::TErrorResult<
+ binding_danger::AssertAndSuppressCleanupPolicy>
+ BaseErrorResult;
+
+ public:
+ ErrorResult() = default;
+
+ ErrorResult(ErrorResult&& aRHS) = default;
+ // Explicitly allow moving out of a CopyableErrorResult into an ErrorResult.
+ // This is implemented below so it can see the definition of
+ // CopyableErrorResult.
+ inline explicit ErrorResult(CopyableErrorResult&& aRHS);
+
+ explicit ErrorResult(nsresult aRv) : BaseErrorResult(aRv) {}
+
+ // This operator is deprecated and ideally shouldn't be used.
+ void operator=(nsresult rv) { BaseErrorResult::operator=(rv); }
+
+ ErrorResult& operator=(ErrorResult&& aRHS) = default;
+
+ // Not to be implemented, to make sure people always pass this by
+ // reference, not by value.
+ ErrorResult(const ErrorResult&) = delete;
+ ErrorResult& operator=(const ErrorResult&) = delete;
+};
+
+template <typename CleanupPolicy>
+binding_danger::TErrorResult<CleanupPolicy>::operator ErrorResult&() {
+ return *static_cast<ErrorResult*>(
+ reinterpret_cast<TErrorResult<AssertAndSuppressCleanupPolicy>*>(this));
+}
+
+template <typename CleanupPolicy>
+binding_danger::TErrorResult<CleanupPolicy>::operator const ErrorResult&()
+ const {
+ return *static_cast<const ErrorResult*>(
+ reinterpret_cast<const TErrorResult<AssertAndSuppressCleanupPolicy>*>(
+ this));
+}
+
+// A class for use when an ErrorResult should just automatically be ignored.
+// This doesn't inherit from ErrorResult so we don't make two separate calls to
+// SuppressException.
+class IgnoredErrorResult : public binding_danger::TErrorResult<
+ binding_danger::JustSuppressCleanupPolicy> {};
+
+// A class for use when an ErrorResult needs to be copied to a lambda, into
+// an IPDL structure, etc. Since this will often involve crossing thread
+// boundaries this class will assert if you try to copy a JS exception. Only
+// use this if you are propagating internal errors. In general its best
+// to use ErrorResult by default and only convert to a CopyableErrorResult when
+// you need it.
+class CopyableErrorResult
+ : public binding_danger::TErrorResult<
+ binding_danger::ThreadSafeJustSuppressCleanupPolicy> {
+ typedef binding_danger::TErrorResult<
+ binding_danger::ThreadSafeJustSuppressCleanupPolicy>
+ BaseErrorResult;
+
+ public:
+ CopyableErrorResult() = default;
+
+ explicit CopyableErrorResult(const ErrorResult& aRight) : BaseErrorResult() {
+ auto val = reinterpret_cast<const CopyableErrorResult&>(aRight);
+ operator=(val);
+ }
+
+ CopyableErrorResult(CopyableErrorResult&& aRHS) = default;
+
+ explicit CopyableErrorResult(ErrorResult&& aRHS) : BaseErrorResult() {
+ // We must not copy JS exceptions since it can too easily lead to
+ // off-thread use. Assert this and fall back to a generic error
+ // in release builds.
+ MOZ_DIAGNOSTIC_ASSERT(
+ !aRHS.IsJSException(),
+ "Attempt to copy from ErrorResult with a JS exception value.");
+ if (aRHS.IsJSException()) {
+ aRHS.SuppressException();
+ Throw(NS_ERROR_FAILURE);
+ } else {
+ // We could avoid the cast here if we had a move constructor on
+ // TErrorResult templated on the cleanup policy type, but then we'd have
+ // to either inline the impl or force all possible instantiations or
+ // something. This is a bit simpler, and not that different from our copy
+ // constructor.
+ auto val = reinterpret_cast<CopyableErrorResult&&>(aRHS);
+ operator=(val);
+ }
+ }
+
+ explicit CopyableErrorResult(nsresult aRv) : BaseErrorResult(aRv) {}
+
+ // This operator is deprecated and ideally shouldn't be used.
+ void operator=(nsresult rv) { BaseErrorResult::operator=(rv); }
+
+ CopyableErrorResult& operator=(CopyableErrorResult&& aRHS) = default;
+
+ CopyableErrorResult(const CopyableErrorResult& aRight) : BaseErrorResult() {
+ operator=(aRight);
+ }
+
+ CopyableErrorResult& operator=(const CopyableErrorResult& aRight) {
+ // We must not copy JS exceptions since it can too easily lead to
+ // off-thread use. Assert this and fall back to a generic error
+ // in release builds.
+ MOZ_DIAGNOSTIC_ASSERT(
+ !IsJSException(),
+ "Attempt to copy to ErrorResult with a JS exception value.");
+ MOZ_DIAGNOSTIC_ASSERT(
+ !aRight.IsJSException(),
+ "Attempt to copy from ErrorResult with a JS exception value.");
+ if (aRight.IsJSException()) {
+ SuppressException();
+ Throw(NS_ERROR_FAILURE);
+ } else {
+ aRight.CloneTo(*this);
+ }
+ return *this;
+ }
+
+ // Disallow implicit converstion to non-const ErrorResult&, because that would
+ // allow people to throw exceptions on us while bypassing our checks for JS
+ // exceptions.
+ operator ErrorResult&() = delete;
+
+ // Allow conversion to ErrorResult&& so we can move out of ourselves into
+ // an ErrorResult.
+ operator ErrorResult&&() && {
+ auto* val = reinterpret_cast<ErrorResult*>(this);
+ return std::move(*val);
+ }
+};
+
+inline ErrorResult::ErrorResult(CopyableErrorResult&& aRHS)
+ : ErrorResult(reinterpret_cast<ErrorResult&&>(aRHS)) {}
+
+namespace dom::binding_detail {
+
+enum class ErrorFor {
+ getter,
+ setter,
+};
+
+template <ErrorFor ErrorType>
+struct ErrorDescriptionFor {
+ const char* mInterface;
+ const char* mMember;
+};
+
+class FastErrorResult : public mozilla::binding_danger::TErrorResult<
+ mozilla::binding_danger::JustAssertCleanupPolicy> {
+ public:
+ using TErrorResult::MaybeSetPendingException;
+
+ template <ErrorFor ErrorType>
+ [[nodiscard]] bool MaybeSetPendingException(
+ JSContext* aCx, const ErrorDescriptionFor<ErrorType>& aDescription) {
+ WouldReportJSException();
+ if (!Failed()) {
+ return false;
+ }
+
+ nsAutoCString description(aDescription.mInterface);
+ description.Append('.');
+ description.Append(aDescription.mMember);
+ if constexpr (ErrorType == ErrorFor::getter) {
+ description.AppendLiteral(" getter");
+ } else {
+ static_assert(ErrorType == ErrorFor::setter);
+ description.AppendLiteral(" setter");
+ }
+ SetPendingException(aCx, description.get());
+ return true;
+ }
+};
+
+} // namespace dom::binding_detail
+
+// We want an OOMReporter class that has the following properties:
+//
+// 1) Can be cast to from any ErrorResult-like type.
+// 2) Has a fast destructor (because we want to use it from bindings).
+// 3) Won't be randomly instantiated by non-binding code (because the fast
+// destructor is not so safe).
+// 4) Doesn't look ugly on the callee side (e.g. isn't in the binding_detail or
+// binding_danger namespace).
+//
+// We do this by creating a class that can't actually be constructed directly
+// but can be cast to from ErrorResult-like types, both implicitly and
+// explicitly.
+class OOMReporter : private dom::binding_detail::FastErrorResult {
+ public:
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG ReportOOM() {
+ Throw(NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // A method that turns a FastErrorResult into an OOMReporter, which we use in
+ // codegen to ensure that callees don't take an ErrorResult when they should
+ // only be taking an OOMReporter. The idea is that we can then just have a
+ // FastErrorResult on the stack and call this to produce the thing to pass to
+ // callees.
+ static OOMReporter& From(FastErrorResult& aRv) { return aRv; }
+
+ private:
+ // TErrorResult is a friend so its |operator OOMReporter&()| can work.
+ template <typename CleanupPolicy>
+ friend class binding_danger::TErrorResult;
+
+ OOMReporter() : dom::binding_detail::FastErrorResult() {}
+};
+
+template <typename CleanupPolicy>
+binding_danger::TErrorResult<CleanupPolicy>::operator OOMReporter&() {
+ return *static_cast<OOMReporter*>(
+ reinterpret_cast<TErrorResult<JustAssertCleanupPolicy>*>(this));
+}
+
+// A class for use when an ErrorResult should just automatically be
+// ignored. This is designed to be passed as a temporary only, like
+// so:
+//
+// foo->Bar(IgnoreErrors());
+class MOZ_TEMPORARY_CLASS IgnoreErrors {
+ public:
+ operator ErrorResult&() && { return mInner; }
+ operator OOMReporter&() && { return mInner; }
+
+ private:
+ // We don't use an ErrorResult member here so we don't make two separate calls
+ // to SuppressException (one from us, one from the ErrorResult destructor
+ // after asserting).
+ binding_danger::TErrorResult<binding_danger::JustSuppressCleanupPolicy>
+ mInner;
+} JS_HAZ_ROOTED;
+
+/******************************************************************************
+ ** Macros for checking results
+ ******************************************************************************/
+
+#define ENSURE_SUCCESS(res, ret) \
+ do { \
+ if (res.Failed()) { \
+ nsCString msg; \
+ msg.AppendPrintf( \
+ "ENSURE_SUCCESS(%s, %s) failed with " \
+ "result 0x%X", \
+ #res, #ret, res.ErrorCodeAsInt()); \
+ NS_WARNING(msg.get()); \
+ return ret; \
+ } \
+ } while (0)
+
+#define ENSURE_SUCCESS_VOID(res) \
+ do { \
+ if (res.Failed()) { \
+ nsCString msg; \
+ msg.AppendPrintf( \
+ "ENSURE_SUCCESS_VOID(%s) failed with " \
+ "result 0x%X", \
+ #res, res.ErrorCodeAsInt()); \
+ NS_WARNING(msg.get()); \
+ return; \
+ } \
+ } while (0)
+
+} // namespace mozilla
+
+#endif /* mozilla_ErrorResult_h */