ErrorResult.h (36150B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /** 8 * A set of structs for tracking exceptions that need to be thrown to JS: 9 * ErrorResult and IgnoredErrorResult. 10 * 11 * Conceptually, these structs represent either success or an exception in the 12 * process of being thrown. This means that a failing ErrorResult _must_ be 13 * handled in one of the following ways before coming off the stack: 14 * 15 * 1) Suppressed via SuppressException(). 16 * 2) Converted to a pure nsresult return value via StealNSResult(). 17 * 3) Converted to an actual pending exception on a JSContext via 18 * MaybeSetPendingException. 19 * 4) Converted to an exception JS::Value (probably to then reject a Promise 20 * with) via dom::ToJSValue. 21 * 22 * An IgnoredErrorResult will automatically do the first of those four things. 23 */ 24 25 #ifndef mozilla_ErrorResult_h 26 #define mozilla_ErrorResult_h 27 28 #include <new> 29 #include <utility> 30 31 #include "js/ErrorReport.h" 32 #include "js/GCAnnotations.h" 33 #include "js/Value.h" 34 #include "mozilla/Assertions.h" 35 #include "mozilla/Attributes.h" 36 #include "mozilla/Utf8.h" 37 #include "nsISupportsImpl.h" 38 #include "nsString.h" 39 #include "nsTArray.h" 40 #include "nscore.h" 41 42 namespace IPC { 43 class Message; 44 class MessageReader; 45 class MessageWriter; 46 template <typename> 47 struct ParamTraits; 48 } // namespace IPC 49 class PickleIterator; 50 51 namespace mozilla { 52 53 namespace dom { 54 55 class Promise; 56 57 enum ErrNum : uint16_t { 58 #define MSG_DEF(_name, _argc, _has_context, _exn, _str) _name, 59 #include "mozilla/dom/Errors.msg" 60 #undef MSG_DEF 61 Err_Limit 62 }; 63 64 // Debug-only compile-time table of the number of arguments of each error, for 65 // use in static_assert. 66 #if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__)) 67 uint16_t constexpr ErrorFormatNumArgs[] = { 68 # define MSG_DEF(_name, _argc, _has_context, _exn, _str) _argc, 69 # include "mozilla/dom/Errors.msg" 70 # undef MSG_DEF 71 }; 72 #endif 73 74 // Table of whether various error messages want a context arg. 75 bool constexpr ErrorFormatHasContext[] = { 76 #define MSG_DEF(_name, _argc, _has_context, _exn, _str) _has_context, 77 #include "mozilla/dom/Errors.msg" 78 #undef MSG_DEF 79 }; 80 81 // Table of the kinds of exceptions error messages will produce. 82 JSExnType constexpr ErrorExceptionType[] = { 83 #define MSG_DEF(_name, _argc, _has_context, _exn, _str) _exn, 84 #include "mozilla/dom/Errors.msg" 85 #undef MSG_DEF 86 }; 87 88 uint16_t GetErrorArgCount(const ErrNum aErrorNumber); 89 90 namespace binding_detail { 91 void ThrowErrorMessage(JSContext* aCx, const unsigned aErrorNumber, ...); 92 } // namespace binding_detail 93 94 template <ErrNum errorNumber, typename... Ts> 95 inline bool ThrowErrorMessage(JSContext* aCx, Ts&&... aArgs) { 96 #if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__)) 97 static_assert(ErrorFormatNumArgs[errorNumber] == sizeof...(aArgs), 98 "Pass in the right number of arguments"); 99 #endif 100 binding_detail::ThrowErrorMessage(aCx, static_cast<unsigned>(errorNumber), 101 std::forward<Ts>(aArgs)...); 102 return false; 103 } 104 105 template <typename CharT> 106 struct TStringArrayAppender { 107 static void Append(nsTArray<nsTString<CharT>>& aArgs, uint16_t aCount) { 108 MOZ_RELEASE_ASSERT(aCount == 0, 109 "Must give at least as many string arguments as are " 110 "required by the ErrNum."); 111 } 112 113 // Allow passing nsAString/nsACString instances for our args. 114 template <typename... Ts> 115 static void Append(nsTArray<nsTString<CharT>>& aArgs, uint16_t aCount, 116 const nsTSubstring<CharT>& aFirst, Ts&&... aOtherArgs) { 117 if (aCount == 0) { 118 MOZ_ASSERT(false, 119 "There should not be more string arguments provided than are " 120 "required by the ErrNum."); 121 return; 122 } 123 aArgs.AppendElement(aFirst); 124 Append(aArgs, aCount - 1, std::forward<Ts>(aOtherArgs)...); 125 } 126 127 // Also allow passing literal instances for our args. 128 template <int N, typename... Ts> 129 static void Append(nsTArray<nsTString<CharT>>& aArgs, uint16_t aCount, 130 const CharT (&aFirst)[N], Ts&&... aOtherArgs) { 131 if (aCount == 0) { 132 MOZ_ASSERT(false, 133 "There should not be more string arguments provided than are " 134 "required by the ErrNum."); 135 return; 136 } 137 aArgs.AppendElement(nsTLiteralString<CharT>(aFirst)); 138 Append(aArgs, aCount - 1, std::forward<Ts>(aOtherArgs)...); 139 } 140 }; 141 142 using StringArrayAppender = TStringArrayAppender<char16_t>; 143 using CStringArrayAppender = TStringArrayAppender<char>; 144 145 } // namespace dom 146 147 class ErrorResult; 148 class OOMReporter; 149 class CopyableErrorResult; 150 151 namespace binding_danger { 152 153 /** 154 * Templated implementation class for various ErrorResult-like things. The 155 * instantiations differ only in terms of their cleanup policies (used in the 156 * destructor), which they can specify via the template argument. Note that 157 * this means it's safe to reinterpret_cast between the instantiations unless 158 * you plan to invoke the destructor through such a cast pointer. 159 * 160 * A cleanup policy consists of two booleans: whether to assert that we've been 161 * reported or suppressed, and whether to then go ahead and suppress the 162 * exception. 163 */ 164 template <typename CleanupPolicy> 165 class TErrorResult { 166 public: 167 TErrorResult() 168 : mResult(NS_OK) 169 #ifdef DEBUG 170 , 171 mMightHaveUnreportedJSException(false), 172 mUnionState(HasNothing) 173 #endif 174 { 175 } 176 177 ~TErrorResult() { 178 AssertInOwningThread(); 179 180 if (CleanupPolicy::assertHandled) { 181 // Consumers should have called one of MaybeSetPendingException 182 // (possibly via ToJSValue), StealNSResult, and SuppressException 183 AssertReportedOrSuppressed(); 184 } 185 186 if (CleanupPolicy::suppress) { 187 SuppressException(); 188 } 189 190 // And now assert that we're in a good final state. 191 AssertReportedOrSuppressed(); 192 } 193 194 TErrorResult(TErrorResult&& aRHS) 195 // Initialize mResult and whatever else we need to default-initialize, so 196 // the ClearUnionData call in our operator= will do the right thing 197 // (nothing). 198 : TErrorResult() { 199 *this = std::move(aRHS); 200 } 201 TErrorResult& operator=(TErrorResult&& aRHS); 202 203 explicit TErrorResult(nsresult aRv) : TErrorResult() { AssignErrorCode(aRv); } 204 205 operator ErrorResult&(); 206 operator const ErrorResult&() const; 207 operator OOMReporter&(); 208 209 // This method is deprecated. Consumers should Throw*Error with the 210 // appropriate DOMException name if they are throwing a DOMException. If they 211 // have a random nsresult which may or may not correspond to a DOMException 212 // type, they should consider using an appropriate DOMException with an 213 // informative message and calling the relevant Throw*Error. 214 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG Throw(nsresult rv) { 215 MOZ_ASSERT(NS_FAILED(rv), "Please don't try throwing success"); 216 AssignErrorCode(rv); 217 } 218 219 // Duplicate our current state on the given TErrorResult object. Any 220 // existing errors or messages on the target will be suppressed before 221 // cloning. Our own error state remains unchanged. 222 void CloneTo(TErrorResult& aRv) const; 223 224 // Use SuppressException when you want to suppress any exception that might be 225 // on the TErrorResult. After this call, the TErrorResult will be back a "no 226 // exception thrown" state. 227 void SuppressException(); 228 229 // Use StealNSResult() when you want to safely convert the TErrorResult to 230 // an nsresult that you will then return to a caller. This will 231 // SuppressException(), since there will no longer be a way to report it. 232 nsresult StealNSResult() { 233 nsresult rv = ErrorCode(); 234 SuppressException(); 235 // Don't propagate out our internal error codes that have special meaning. 236 if (rv == NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR || 237 rv == NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR || 238 rv == NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION || 239 rv == NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION) { 240 // What to pick here? 241 return NS_ERROR_DOM_INVALID_STATE_ERR; 242 } 243 244 return rv; 245 } 246 247 // Use MaybeSetPendingException to convert a TErrorResult to a pending 248 // exception on the given JSContext. This is the normal "throw an exception" 249 // codepath. 250 // 251 // The return value is false if the TErrorResult represents success, true 252 // otherwise. This does mean that in JSAPI method implementations you can't 253 // just use this as |return rv.MaybeSetPendingException(cx)| (though you could 254 // |return !rv.MaybeSetPendingException(cx)|), but in practice pretty much any 255 // consumer would want to do some more work on the success codepath. So 256 // instead the way you use this is: 257 // 258 // if (rv.MaybeSetPendingException(cx)) { 259 // bail out here 260 // } 261 // go on to do something useful 262 // 263 // The success path is inline, since it should be the common case and we don't 264 // want to pay the price of a function call in some of the consumers of this 265 // method in the common case. 266 // 267 // Note that a true return value does NOT mean there is now a pending 268 // exception on aCx, due to uncatchable exceptions. It should still be 269 // considered equivalent to a JSAPI failure in terms of what callers should do 270 // after true is returned. 271 // 272 // After this call, the TErrorResult will no longer return true from Failed(), 273 // since the exception will have moved to the JSContext. 274 // 275 // If "context" is not null and our exception has a useful message string, the 276 // string "%s: ", with the value of "context" replacing %s, will be prepended 277 // to the message string. The passed-in string must be ASCII. 278 [[nodiscard]] bool MaybeSetPendingException( 279 JSContext* cx, const char* description = nullptr) { 280 WouldReportJSException(); 281 if (!Failed()) { 282 return false; 283 } 284 285 SetPendingException(cx, description); 286 return true; 287 } 288 289 // Use StealExceptionFromJSContext to convert a pending exception on a 290 // JSContext to a TErrorResult. This function must be called only when a 291 // JSAPI operation failed. It assumes that lack of pending exception on the 292 // JSContext means an uncatchable exception was thrown. 293 // 294 // Codepaths that might call this method must call MightThrowJSException even 295 // if the relevant JSAPI calls do not fail. 296 // 297 // When this function returns, JS_IsExceptionPending(cx) will definitely be 298 // false. 299 void StealExceptionFromJSContext(JSContext* cx); 300 301 template <dom::ErrNum errorNumber, typename... Ts> 302 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG 303 ThrowTypeError(Ts&&... messageArgs) { 304 static_assert(dom::ErrorExceptionType[errorNumber] == JSEXN_TYPEERR, 305 "Throwing a non-TypeError via ThrowTypeError"); 306 ThrowErrorWithMessage<errorNumber>(NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR, 307 std::forward<Ts>(messageArgs)...); 308 } 309 310 // To be used when throwing a TypeError with a completely custom 311 // message string that's only used in one spot. 312 inline void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG 313 ThrowTypeError(const nsACString& aMessage) { 314 this->template ThrowTypeError<dom::MSG_ONE_OFF_TYPEERR>(aMessage); 315 } 316 317 // To be used when throwing a TypeError with a completely custom 318 // message string that's a string literal that's only used in one spot. 319 template <int N> 320 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG 321 ThrowTypeError(const char (&aMessage)[N]) { 322 ThrowTypeError(nsLiteralCString(aMessage)); 323 } 324 325 template <dom::ErrNum errorNumber, typename... Ts> 326 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG 327 ThrowRangeError(Ts&&... messageArgs) { 328 static_assert(dom::ErrorExceptionType[errorNumber] == JSEXN_RANGEERR, 329 "Throwing a non-RangeError via ThrowRangeError"); 330 ThrowErrorWithMessage<errorNumber>(NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR, 331 std::forward<Ts>(messageArgs)...); 332 } 333 334 // To be used when throwing a RangeError with a completely custom 335 // message string that's only used in one spot. 336 inline void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG 337 ThrowRangeError(const nsACString& aMessage) { 338 this->template ThrowRangeError<dom::MSG_ONE_OFF_RANGEERR>(aMessage); 339 } 340 341 // To be used when throwing a RangeError with a completely custom 342 // message string that's a string literal that's only used in one spot. 343 template <int N> 344 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG 345 ThrowRangeError(const char (&aMessage)[N]) { 346 ThrowRangeError(nsLiteralCString(aMessage)); 347 } 348 349 bool IsErrorWithMessage() const { 350 return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR || 351 ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR; 352 } 353 354 // Facilities for throwing a preexisting JS exception value via this 355 // TErrorResult. The contract is that any code which might end up calling 356 // ThrowJSException() or StealExceptionFromJSContext() must call 357 // MightThrowJSException() even if no exception is being thrown. Code that 358 // conditionally calls ToJSValue on this TErrorResult only if Failed() must 359 // first call WouldReportJSException even if this TErrorResult has not failed. 360 // 361 // The exn argument to ThrowJSException can be in any compartment. It does 362 // not have to be in the compartment of cx. If someone later uses it, they 363 // will wrap it into whatever compartment they're working in, as needed. 364 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG 365 ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn); 366 bool IsJSException() const { 367 return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION; 368 } 369 370 // Facilities for throwing DOMExceptions of whatever type a spec calls for. 371 // If an empty message string is passed to one of these Throw*Error functions, 372 // the default message string for the relevant type of DOMException will be 373 // used. The passed-in string must be UTF-8. 374 #define DOMEXCEPTION(name, err) \ 375 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG Throw##name( \ 376 const nsACString& aMessage) { \ 377 ThrowDOMException(err, aMessage); \ 378 } \ 379 \ 380 template <int N> \ 381 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG Throw##name( \ 382 const char (&aMessage)[N]) { \ 383 ThrowDOMException(err, aMessage); \ 384 } 385 386 #include "mozilla/dom/DOMExceptionNames.h" 387 388 #undef DOMEXCEPTION 389 390 bool IsDOMException() const { 391 return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION; 392 } 393 394 // Flag on the TErrorResult that whatever needs throwing has been 395 // thrown on the JSContext already and we should not mess with it. 396 // If nothing was thrown, this becomes an uncatchable exception. 397 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG 398 NoteJSContextException(JSContext* aCx); 399 400 // Check whether the TErrorResult says to just throw whatever is on 401 // the JSContext already. 402 bool IsJSContextException() { 403 return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT; 404 } 405 406 // Support for uncatchable exceptions. 407 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG ThrowUncatchableException() { 408 Throw(NS_ERROR_UNCATCHABLE_EXCEPTION); 409 } 410 bool IsUncatchableException() const { 411 return ErrorCode() == NS_ERROR_UNCATCHABLE_EXCEPTION; 412 } 413 414 void MOZ_ALWAYS_INLINE MightThrowJSException() { 415 #ifdef DEBUG 416 mMightHaveUnreportedJSException = true; 417 #endif 418 } 419 void MOZ_ALWAYS_INLINE WouldReportJSException() { 420 #ifdef DEBUG 421 mMightHaveUnreportedJSException = false; 422 #endif 423 } 424 425 // In the future, we can add overloads of Throw that take more 426 // interesting things, like strings or DOM exception types or 427 // something if desired. 428 429 // Backwards-compat to make conversion simpler. We don't call 430 // Throw() here because people can easily pass success codes to 431 // this. This operator is deprecated and ideally shouldn't be used. 432 void operator=(nsresult rv) { AssignErrorCode(rv); } 433 434 bool Failed() const { return NS_FAILED(mResult); } 435 436 bool ErrorCodeIs(nsresult rv) const { return mResult == rv; } 437 438 // For use in logging ONLY. 439 uint32_t ErrorCodeAsInt() const { return static_cast<uint32_t>(ErrorCode()); } 440 441 bool operator==(const ErrorResult& aRight) const; 442 443 protected: 444 nsresult ErrorCode() const { return mResult; } 445 446 // Helper methods for throwing DOMExceptions, for now. We can try to get rid 447 // of these once EME code is fixed to not use them and we decouple 448 // DOMExceptions from nsresult. 449 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG 450 ThrowDOMException(nsresult rv, const nsACString& message); 451 452 // Same thing, but using a string literal. 453 template <int N> 454 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG 455 ThrowDOMException(nsresult rv, const char (&aMessage)[N]) { 456 ThrowDOMException(rv, nsLiteralCString(aMessage)); 457 } 458 459 // Allow Promise to call the above methods when it really needs to. 460 // Unfortunately, we can't have the definition of Promise here, so can't mark 461 // just it's MaybeRejectWithDOMException method as a friend. In any case, 462 // hopefully it's all temporary until we sort out the EME bits. 463 friend class dom::Promise; 464 465 // Implementation of MaybeSetPendingException for the case when we're a 466 // failure result. See documentation of MaybeSetPendingException for the 467 // "context" argument. 468 void SetPendingException(JSContext* cx, const char* context); 469 470 private: 471 #ifdef DEBUG 472 enum UnionState { 473 HasMessage, 474 HasDOMExceptionInfo, 475 HasJSException, 476 HasNothing 477 }; 478 #endif // DEBUG 479 480 friend struct IPC::ParamTraits<TErrorResult>; 481 friend struct IPC::ParamTraits<ErrorResult>; 482 void SerializeMessage(IPC::MessageWriter* aWriter) const; 483 bool DeserializeMessage(IPC::MessageReader* aReader); 484 485 void SerializeDOMExceptionInfo(IPC::MessageWriter* aWriter) const; 486 bool DeserializeDOMExceptionInfo(IPC::MessageReader* aReader); 487 488 // Helper method that creates a new Message for this TErrorResult, 489 // and returns the arguments array from that Message. 490 nsTArray<nsCString>& CreateErrorMessageHelper(const dom::ErrNum errorNumber, 491 nsresult errorType); 492 493 // Helper method to replace invalid UTF-8 characters with the replacement 494 // character. aValidUpTo is the number of characters that are known to be 495 // valid. The string might be truncated if we encounter an OOM error. 496 static void EnsureUTF8Validity(nsCString& aValue, size_t aValidUpTo); 497 498 template <dom::ErrNum errorNumber, typename... Ts> 499 void ThrowErrorWithMessage(nsresult errorType, Ts&&... messageArgs) { 500 #if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__)) 501 static_assert(dom::ErrorFormatNumArgs[errorNumber] == 502 sizeof...(messageArgs) + 503 int(dom::ErrorFormatHasContext[errorNumber]), 504 "Pass in the right number of arguments"); 505 #endif 506 507 ClearUnionData(); 508 509 nsTArray<nsCString>& messageArgsArray = 510 CreateErrorMessageHelper(errorNumber, errorType); 511 uint16_t argCount = dom::GetErrorArgCount(errorNumber); 512 if (dom::ErrorFormatHasContext[errorNumber]) { 513 // Insert an empty string arg at the beginning and reduce our arg count to 514 // still be appended accordingly. 515 MOZ_ASSERT(argCount > 0, 516 "Must have at least one arg if we have a context!"); 517 MOZ_ASSERT(messageArgsArray.Length() == 0, 518 "Why do we already have entries in the array?"); 519 --argCount; 520 messageArgsArray.AppendElement(); 521 } 522 dom::CStringArrayAppender::Append(messageArgsArray, argCount, 523 std::forward<Ts>(messageArgs)...); 524 for (nsCString& arg : messageArgsArray) { 525 size_t validUpTo = Utf8ValidUpTo(arg); 526 if (validUpTo != arg.Length()) { 527 EnsureUTF8Validity(arg, validUpTo); 528 } 529 } 530 #ifdef DEBUG 531 mUnionState = HasMessage; 532 #endif // DEBUG 533 } 534 535 MOZ_ALWAYS_INLINE void AssertInOwningThread() const { 536 #ifdef DEBUG 537 if (CleanupPolicy::assertSameThread) { 538 NS_ASSERT_OWNINGTHREAD(TErrorResult); 539 } 540 #endif 541 } 542 543 void AssignErrorCode(nsresult aRv) { 544 MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR, 545 "Use ThrowTypeError()"); 546 MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR, 547 "Use ThrowRangeError()"); 548 MOZ_ASSERT(!IsErrorWithMessage(), "Don't overwrite errors with message"); 549 MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION, 550 "Use ThrowJSException()"); 551 MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions"); 552 MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION, 553 "Use Throw*Error for the appropriate DOMException name"); 554 MOZ_ASSERT(!IsDOMException(), "Don't overwrite DOM exceptions"); 555 MOZ_ASSERT(aRv != NS_ERROR_XPC_NOT_ENOUGH_ARGS, 556 "May need to bring back ThrowNotEnoughArgsError"); 557 MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT, 558 "Use NoteJSContextException"); 559 mResult = aRv; 560 } 561 562 void ClearMessage(); 563 void ClearDOMExceptionInfo(); 564 565 // ClearUnionData will try to clear the data in our mExtra union. After this 566 // the union may be in an uninitialized state (e.g. mMessage or 567 // mDOMExceptionInfo may point to deleted memory, or mJSException may be a 568 // JS::Value containing an invalid gcthing) and the caller must either 569 // reinitialize it or change mResult to something that will not involve us 570 // touching the union anymore. 571 void ClearUnionData(); 572 573 // Methods for setting various specific kinds of pending exceptions. See 574 // documentation of MaybeSetPendingException for the "context" argument. 575 void SetPendingExceptionWithMessage(JSContext* cx, const char* context); 576 void SetPendingJSException(JSContext* cx); 577 void SetPendingDOMException(JSContext* cx, const char* context); 578 void SetPendingGenericErrorException(JSContext* cx); 579 580 MOZ_ALWAYS_INLINE void AssertReportedOrSuppressed() { 581 MOZ_ASSERT(!Failed()); 582 MOZ_ASSERT(!mMightHaveUnreportedJSException); 583 MOZ_ASSERT(mUnionState == HasNothing); 584 } 585 586 // Special values of mResult: 587 // NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR -- ThrowTypeError() called on us. 588 // NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR -- ThrowRangeError() called on us. 589 // NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION -- ThrowJSException() called 590 // on us. 591 // NS_ERROR_UNCATCHABLE_EXCEPTION -- ThrowUncatchableException called on us. 592 // NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION -- ThrowDOMException() called 593 // on us. 594 nsresult mResult; 595 596 struct Message; 597 struct DOMExceptionInfo; 598 union Extra { 599 // mMessage is set by ThrowErrorWithMessage and reported (and deallocated) 600 // by SetPendingExceptionWithMessage. 601 MOZ_INIT_OUTSIDE_CTOR 602 Message* mMessage; // valid when IsErrorWithMessage() 603 604 // mJSException is set (and rooted) by ThrowJSException and reported (and 605 // unrooted) by SetPendingJSException. 606 MOZ_INIT_OUTSIDE_CTOR 607 JS::Value mJSException; // valid when IsJSException() 608 609 // mDOMExceptionInfo is set by ThrowDOMException and reported (and 610 // deallocated) by SetPendingDOMException. 611 MOZ_INIT_OUTSIDE_CTOR 612 DOMExceptionInfo* mDOMExceptionInfo; // valid when IsDOMException() 613 614 // |mJSException| has a non-trivial constructor and therefore MUST be 615 // placement-new'd into existence. 616 MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS 617 Extra() {} // NOLINT 618 MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS 619 } mExtra; 620 621 Message* InitMessage(Message* aMessage) { 622 // The |new| here switches the active arm of |mExtra|, from the compiler's 623 // point of view. Mere assignment *won't* necessarily do the right thing! 624 new (&mExtra.mMessage) Message*(aMessage); 625 return mExtra.mMessage; 626 } 627 628 JS::Value& InitJSException() { 629 // The |new| here switches the active arm of |mExtra|, from the compiler's 630 // point of view. Mere assignment *won't* necessarily do the right thing! 631 new (&mExtra.mJSException) JS::Value(); // sets to undefined 632 return mExtra.mJSException; 633 } 634 635 DOMExceptionInfo* InitDOMExceptionInfo(DOMExceptionInfo* aDOMExceptionInfo) { 636 // The |new| here switches the active arm of |mExtra|, from the compiler's 637 // point of view. Mere assignment *won't* necessarily do the right thing! 638 new (&mExtra.mDOMExceptionInfo) DOMExceptionInfo*(aDOMExceptionInfo); 639 return mExtra.mDOMExceptionInfo; 640 } 641 642 #ifdef DEBUG 643 // Used to keep track of codepaths that might throw JS exceptions, 644 // for assertion purposes. 645 bool mMightHaveUnreportedJSException; 646 647 // Used to keep track of what's stored in our union right now. Note 648 // that this may be set to HasNothing even if our mResult suggests 649 // we should have something, if we have already cleaned up the 650 // something. 651 UnionState mUnionState; 652 653 // The thread that created this TErrorResult 654 NS_DECL_OWNINGTHREAD; 655 #endif 656 657 // Not to be implemented, to make sure people always pass this by 658 // reference, not by value. 659 TErrorResult(const TErrorResult&) = delete; 660 void operator=(const TErrorResult&) = delete; 661 } JS_HAZ_ROOTED; 662 663 struct JustAssertCleanupPolicy { 664 static const bool assertHandled = true; 665 static const bool suppress = false; 666 static const bool assertSameThread = true; 667 }; 668 669 struct AssertAndSuppressCleanupPolicy { 670 static const bool assertHandled = true; 671 static const bool suppress = true; 672 static const bool assertSameThread = true; 673 }; 674 675 struct JustSuppressCleanupPolicy { 676 static const bool assertHandled = false; 677 static const bool suppress = true; 678 static const bool assertSameThread = true; 679 }; 680 681 struct ThreadSafeJustSuppressCleanupPolicy { 682 static const bool assertHandled = false; 683 static const bool suppress = true; 684 static const bool assertSameThread = false; 685 }; 686 687 } // namespace binding_danger 688 689 // A class people should normally use on the stack when they plan to actually 690 // do something with the exception. 691 class ErrorResult : public binding_danger::TErrorResult< 692 binding_danger::AssertAndSuppressCleanupPolicy> { 693 typedef binding_danger::TErrorResult< 694 binding_danger::AssertAndSuppressCleanupPolicy> 695 BaseErrorResult; 696 697 public: 698 ErrorResult() = default; 699 700 ErrorResult(ErrorResult&& aRHS) = default; 701 // Explicitly allow moving out of a CopyableErrorResult into an ErrorResult. 702 // This is implemented below so it can see the definition of 703 // CopyableErrorResult. 704 inline explicit ErrorResult(CopyableErrorResult&& aRHS); 705 706 explicit ErrorResult(nsresult aRv) : BaseErrorResult(aRv) {} 707 708 // This operator is deprecated and ideally shouldn't be used. 709 void operator=(nsresult rv) { BaseErrorResult::operator=(rv); } 710 711 ErrorResult& operator=(ErrorResult&& aRHS) = default; 712 713 // Not to be implemented, to make sure people always pass this by 714 // reference, not by value. 715 ErrorResult(const ErrorResult&) = delete; 716 ErrorResult& operator=(const ErrorResult&) = delete; 717 }; 718 719 template <typename CleanupPolicy> 720 binding_danger::TErrorResult<CleanupPolicy>::operator ErrorResult&() { 721 return *static_cast<ErrorResult*>( 722 reinterpret_cast<TErrorResult<AssertAndSuppressCleanupPolicy>*>(this)); 723 } 724 725 template <typename CleanupPolicy> 726 binding_danger::TErrorResult<CleanupPolicy>::operator const ErrorResult&() 727 const { 728 return *static_cast<const ErrorResult*>( 729 reinterpret_cast<const TErrorResult<AssertAndSuppressCleanupPolicy>*>( 730 this)); 731 } 732 733 // A class for use when an ErrorResult should just automatically be ignored. 734 // This doesn't inherit from ErrorResult so we don't make two separate calls to 735 // SuppressException. 736 class IgnoredErrorResult : public binding_danger::TErrorResult< 737 binding_danger::JustSuppressCleanupPolicy> {}; 738 739 // A class for use when an ErrorResult needs to be copied to a lambda, into 740 // an IPDL structure, etc. Since this will often involve crossing thread 741 // boundaries this class will assert if you try to copy a JS exception. Only 742 // use this if you are propagating internal errors. In general its best 743 // to use ErrorResult by default and only convert to a CopyableErrorResult when 744 // you need it. 745 class CopyableErrorResult 746 : public binding_danger::TErrorResult< 747 binding_danger::ThreadSafeJustSuppressCleanupPolicy> { 748 typedef binding_danger::TErrorResult< 749 binding_danger::ThreadSafeJustSuppressCleanupPolicy> 750 BaseErrorResult; 751 752 public: 753 CopyableErrorResult() = default; 754 755 explicit CopyableErrorResult(const ErrorResult& aRight) : BaseErrorResult() { 756 auto val = reinterpret_cast<const CopyableErrorResult&>(aRight); 757 operator=(val); 758 } 759 760 CopyableErrorResult(CopyableErrorResult&& aRHS) = default; 761 762 explicit CopyableErrorResult(ErrorResult&& aRHS) : BaseErrorResult() { 763 // We must not copy JS exceptions since it can too easily lead to 764 // off-thread use. Assert this and fall back to a generic error 765 // in release builds. 766 MOZ_DIAGNOSTIC_ASSERT( 767 !aRHS.IsJSException(), 768 "Attempt to copy from ErrorResult with a JS exception value."); 769 if (aRHS.IsJSException()) { 770 aRHS.SuppressException(); 771 Throw(NS_ERROR_FAILURE); 772 } else { 773 // We could avoid the cast here if we had a move constructor on 774 // TErrorResult templated on the cleanup policy type, but then we'd have 775 // to either inline the impl or force all possible instantiations or 776 // something. This is a bit simpler, and not that different from our copy 777 // constructor. 778 auto val = reinterpret_cast<CopyableErrorResult&&>(aRHS); 779 operator=(val); 780 } 781 } 782 783 explicit CopyableErrorResult(nsresult aRv) : BaseErrorResult(aRv) {} 784 785 // This operator is deprecated and ideally shouldn't be used. 786 void operator=(nsresult rv) { BaseErrorResult::operator=(rv); } 787 788 CopyableErrorResult& operator=(CopyableErrorResult&& aRHS) = default; 789 790 CopyableErrorResult(const CopyableErrorResult& aRight) : BaseErrorResult() { 791 operator=(aRight); 792 } 793 794 CopyableErrorResult& operator=(const CopyableErrorResult& aRight) { 795 // We must not copy JS exceptions since it can too easily lead to 796 // off-thread use. Assert this and fall back to a generic error 797 // in release builds. 798 MOZ_DIAGNOSTIC_ASSERT( 799 !IsJSException(), 800 "Attempt to copy to ErrorResult with a JS exception value."); 801 MOZ_DIAGNOSTIC_ASSERT( 802 !aRight.IsJSException(), 803 "Attempt to copy from ErrorResult with a JS exception value."); 804 if (aRight.IsJSException()) { 805 SuppressException(); 806 Throw(NS_ERROR_FAILURE); 807 } else { 808 aRight.CloneTo(*this); 809 } 810 return *this; 811 } 812 813 // Disallow implicit converstion to non-const ErrorResult&, because that would 814 // allow people to throw exceptions on us while bypassing our checks for JS 815 // exceptions. 816 operator ErrorResult&() = delete; 817 818 // Allow conversion to ErrorResult&& so we can move out of ourselves into 819 // an ErrorResult. 820 operator ErrorResult&&() && { 821 auto* val = reinterpret_cast<ErrorResult*>(this); 822 return std::move(*val); 823 } 824 }; 825 826 inline ErrorResult::ErrorResult(CopyableErrorResult&& aRHS) 827 : ErrorResult(reinterpret_cast<ErrorResult&&>(aRHS)) {} 828 829 namespace dom::binding_detail { 830 831 enum class ErrorFor { 832 getter, 833 setter, 834 }; 835 836 template <ErrorFor ErrorType> 837 struct ErrorDescriptionFor { 838 const char* mInterface; 839 const char* mMember; 840 }; 841 842 class FastErrorResult : public mozilla::binding_danger::TErrorResult< 843 mozilla::binding_danger::JustAssertCleanupPolicy> { 844 public: 845 using TErrorResult::MaybeSetPendingException; 846 847 template <ErrorFor ErrorType> 848 [[nodiscard]] bool MaybeSetPendingException( 849 JSContext* aCx, const ErrorDescriptionFor<ErrorType>& aDescription) { 850 WouldReportJSException(); 851 if (!Failed()) { 852 return false; 853 } 854 855 nsAutoCString description(aDescription.mInterface); 856 description.Append('.'); 857 description.Append(aDescription.mMember); 858 if constexpr (ErrorType == ErrorFor::getter) { 859 description.AppendLiteral(" getter"); 860 } else { 861 static_assert(ErrorType == ErrorFor::setter); 862 description.AppendLiteral(" setter"); 863 } 864 SetPendingException(aCx, description.get()); 865 return true; 866 } 867 }; 868 869 } // namespace dom::binding_detail 870 871 // We want an OOMReporter class that has the following properties: 872 // 873 // 1) Can be cast to from any ErrorResult-like type. 874 // 2) Has a fast destructor (because we want to use it from bindings). 875 // 3) Won't be randomly instantiated by non-binding code (because the fast 876 // destructor is not so safe). 877 // 4) Doesn't look ugly on the callee side (e.g. isn't in the binding_detail or 878 // binding_danger namespace). 879 // 880 // We do this by creating a class that can't actually be constructed directly 881 // but can be cast to from ErrorResult-like types, both implicitly and 882 // explicitly. 883 class OOMReporter : private dom::binding_detail::FastErrorResult { 884 public: 885 void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG ReportOOM() { 886 Throw(NS_ERROR_OUT_OF_MEMORY); 887 } 888 889 // A method that turns a FastErrorResult into an OOMReporter, which we use in 890 // codegen to ensure that callees don't take an ErrorResult when they should 891 // only be taking an OOMReporter. The idea is that we can then just have a 892 // FastErrorResult on the stack and call this to produce the thing to pass to 893 // callees. 894 static OOMReporter& From(FastErrorResult& aRv) { return aRv; } 895 896 private: 897 // TErrorResult is a friend so its |operator OOMReporter&()| can work. 898 template <typename CleanupPolicy> 899 friend class binding_danger::TErrorResult; 900 901 OOMReporter() : dom::binding_detail::FastErrorResult() {} 902 }; 903 904 template <typename CleanupPolicy> 905 binding_danger::TErrorResult<CleanupPolicy>::operator OOMReporter&() { 906 return *static_cast<OOMReporter*>( 907 reinterpret_cast<TErrorResult<JustAssertCleanupPolicy>*>(this)); 908 } 909 910 // A class for use when an ErrorResult should just automatically be 911 // ignored. This is designed to be passed as a temporary only, like 912 // so: 913 // 914 // foo->Bar(IgnoreErrors()); 915 class MOZ_TEMPORARY_CLASS IgnoreErrors { 916 public: 917 operator ErrorResult&() && { return mInner; } 918 operator OOMReporter&() && { return mInner; } 919 920 private: 921 // We don't use an ErrorResult member here so we don't make two separate calls 922 // to SuppressException (one from us, one from the ErrorResult destructor 923 // after asserting). 924 binding_danger::TErrorResult<binding_danger::JustSuppressCleanupPolicy> 925 mInner; 926 } JS_HAZ_ROOTED; 927 928 /****************************************************************************** 929 ** Macros for checking results 930 ******************************************************************************/ 931 932 #define RETURN_NSRESULT_ON_FAILURE(res) \ 933 do { \ 934 (res).WouldReportJSException(); \ 935 if ((res).Failed()) { \ 936 NS_WARNING(nsPrintfCString( \ 937 "RETURN_NSRESULT_ON_FAILURE(%s) failed with result 0x%X", \ 938 #res, (res).ErrorCodeAsInt()) \ 939 .get()); \ 940 return (res).StealNSResult(); \ 941 } \ 942 } while (0) 943 944 } // namespace mozilla 945 946 #endif /* mozilla_ErrorResult_h */