/* -*- 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 #include #include #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 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 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(errorNumber), std::forward(aArgs)...); return false; } template struct TStringArrayAppender { static void Append(nsTArray>& 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 static void Append(nsTArray>& aArgs, uint16_t aCount, const nsTSubstring& 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(aOtherArgs)...); } // Also allow passing literal instances for our args. template static void Append(nsTArray>& 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(aFirst)); Append(aArgs, aCount - 1, std::forward(aOtherArgs)...); } }; using StringArrayAppender = TStringArrayAppender; using CStringArrayAppender = TStringArrayAppender; } // 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 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 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(NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR, std::forward(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(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 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG ThrowTypeError(const char (&aMessage)[N]) { ThrowTypeError(nsLiteralCString(aMessage)); } template 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(NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR, std::forward(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(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 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 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 \ 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(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 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; friend struct IPC::ParamTraits; 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& 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 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& 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(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 binding_danger::TErrorResult::operator ErrorResult&() { return *static_cast( reinterpret_cast*>(this)); } template binding_danger::TErrorResult::operator const ErrorResult&() const { return *static_cast( reinterpret_cast*>( 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(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(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(this); return std::move(*val); } }; inline ErrorResult::ErrorResult(CopyableErrorResult&& aRHS) : ErrorResult(reinterpret_cast(aRHS)) {} namespace dom::binding_detail { enum class ErrorFor { getter, setter, }; template struct ErrorDescriptionFor { const char* mInterface; const char* mMember; }; class FastErrorResult : public mozilla::binding_danger::TErrorResult< mozilla::binding_danger::JustAssertCleanupPolicy> { public: using TErrorResult::MaybeSetPendingException; template [[nodiscard]] bool MaybeSetPendingException( JSContext* aCx, const ErrorDescriptionFor& 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 friend class binding_danger::TErrorResult; OOMReporter() : dom::binding_detail::FastErrorResult() {} }; template binding_danger::TErrorResult::operator OOMReporter&() { return *static_cast( reinterpret_cast*>(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 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 */