diff options
Diffstat (limited to 'dom/bindings/ErrorResult.h')
-rw-r--r-- | dom/bindings/ErrorResult.h | 962 |
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 */ |