File
Blob: src/workerd/jsg/exception.h
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | #pragma once |
| 6 | |
| 7 | #include <kj/debug.h> |
| 8 | #include <kj/string.h> |
| 9 | |
| 10 | namespace workerd::jsg { |
| 11 | |
| 12 | #define JSG_EXCEPTION(jsErrorType) JSG_ERROR_##jsErrorType |
| 13 | #define JSG_DOM_EXCEPTION(name) "jsg.DOMException(" name ")" |
| 14 | #define JSG_INTERNAL_DOM_EXCEPTION(name) "jsg-internal.DOMException(" name ")" |
| 15 | |
| 16 | #define JSG_ERROR_DOMOperationError JSG_DOM_EXCEPTION("OperationError") |
| 17 | #define JSG_ERROR_DOMDataError JSG_DOM_EXCEPTION("DataError") |
| 18 | #define JSG_ERROR_DOMDataCloneError JSG_DOM_EXCEPTION("DataCloneError") |
| 19 | #define JSG_ERROR_DOMInvalidAccessError JSG_DOM_EXCEPTION("InvalidAccessError") |
| 20 | #define JSG_ERROR_DOMInvalidStateError JSG_DOM_EXCEPTION("InvalidStateError") |
| 21 | #define JSG_ERROR_DOMInvalidCharacterError JSG_DOM_EXCEPTION("InvalidCharacterError") |
| 22 | #define JSG_ERROR_DOMNotSupportedError JSG_DOM_EXCEPTION("NotSupportedError") |
| 23 | #define JSG_ERROR_DOMSyntaxError JSG_DOM_EXCEPTION("SyntaxError") |
| 24 | #define JSG_ERROR_DOMTimeoutError JSG_DOM_EXCEPTION("TimeoutError") |
| 25 | #define JSG_ERROR_DOMTypeMismatchError JSG_DOM_EXCEPTION("TypeMismatchError") |
| 26 | #define JSG_ERROR_DOMQuotaExceededError JSG_DOM_EXCEPTION("QuotaExceededError") |
| 27 | #define JSG_ERROR_DOMAbortError JSG_DOM_EXCEPTION("AbortError") |
| 28 | #define JSG_ERROR_DOMNotFoundError JSG_DOM_EXCEPTION("NotFoundError") |
| 29 | |
| 30 | #define JSG_ERROR_TypeError "jsg.TypeError" |
| 31 | #define JSG_ERROR_Error "jsg.Error" |
| 32 | #define JSG_ERROR_RangeError "jsg.RangeError" |
| 33 | |
| 34 | #define JSG_ERROR_InternalDOMOperationError JSG_INTERNAL_DOM_EXCEPTION("OperationError") |
| 35 | |
| 36 | #define JSG_KJ_EXCEPTION(type, jsErrorType, ...) \ |
| 37 | kj::Exception(kj::Exception::Type::type, __FILE__, __LINE__, \ |
| 38 | kj::str(JSG_EXCEPTION(jsErrorType) ": ", __VA_ARGS__)) |
| 39 | |
| 40 | #define JSG_ASSERT(cond, jsErrorType, ...) \ |
| 41 | KJ_ASSERT(cond, kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) |
| 42 | |
| 43 | // Asserts if the method is compatible with v8 fast api |
| 44 | #define JSG_ASSERT_FASTAPI(Method) \ |
| 45 | static_assert(isFastApiCompatible<Method>, "Method is not v8 fast api compatible"); |
| 46 | |
| 47 | #define JSG_REQUIRE(cond, jsErrorType, ...) \ |
| 48 | KJ_REQUIRE(cond, kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) |
| 49 | // Unlike KJ_REQUIRE, JSG_REQUIRE passes all message arguments through kj::str which makes it |
| 50 | // "prettier". This does have some implications like if there's only string literal arguments then |
| 51 | // there's an unnecessary heap copy. More importantly none of the expressions you pass in end up in |
| 52 | // the resultant string AND you are responsible for formatting the resultant string. For example, |
| 53 | // KJ_REQUIRE(false, "some message", x) formats it like "some message; x = 5". The "equivalent" via |
| 54 | // this macro would be JSG_REQUIRE(false, "some message ", x); which would yield a string like |
| 55 | // "some message 5" (or JSG_REQUIRE(false, "some message; x = ", x) if you wanted identical output, |
| 56 | // but then why not use KJ_REQUIRE). |
| 57 | |
| 58 | #define JSG_REQUIRE_NONNULL(value, jsErrorType, ...) \ |
| 59 | KJ_REQUIRE_NONNULL(value, kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) |
| 60 | // JSG_REQUIRE + KJ_REQUIRE_NONNULL. |
| 61 | |
| 62 | #define JSG_FAIL_REQUIRE(jsErrorType, ...) \ |
| 63 | KJ_FAIL_REQUIRE(kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) |
| 64 | // JSG_REQUIRE + KJ_FAIL_REQUIRE |
| 65 | |
| 66 | #define JSG_WARN_ONCE(msg, ...) \ |
| 67 | static bool logOnce KJ_UNUSED = ([&] { \ |
| 68 | KJ_LOG(WARNING, msg, ##__VA_ARGS__); \ |
| 69 | return true; \ |
| 70 | })() |
| 71 | |
| 72 | // Conditionally log a warning, at most once. Useful for determining if code changes would break |
| 73 | // any existing scripts. |
| 74 | #define JSG_WARN_ONCE_IF(cond, msg, ...) \ |
| 75 | if (cond) { \ |
| 76 | JSG_WARN_ONCE(msg, ##__VA_ARGS__); \ |
| 77 | } |
| 78 | |
| 79 | // These are passthrough functions to KJ. We expect the error string to be |
| 80 | // surfaced to the application. |
| 81 | |
| 82 | #define _JSG_INTERNAL_REQUIRE(cond, jsErrorType, ...) \ |
| 83 | do { \ |
| 84 | try { \ |
| 85 | KJ_REQUIRE(cond, jsErrorType ": Cloudflare internal error."); \ |
| 86 | } catch (kj::Exception & e) { \ |
| 87 | KJ_LOG(ERROR, e, ##__VA_ARGS__); \ |
| 88 | throw kj::mv(e); \ |
| 89 | } \ |
| 90 | } while (0) |
| 91 | |
| 92 | #define _JSG_INTERNAL_REQUIRE_NONNULL(value, jsErrorType, ...) \ |
| 93 | ([&]() -> decltype(auto) { \ |
| 94 | try { \ |
| 95 | return KJ_REQUIRE_NONNULL(value, jsErrorType ": Cloudflare internal error."); \ |
| 96 | } catch (kj::Exception & e) { \ |
| 97 | KJ_LOG(ERROR, e, ##__VA_ARGS__); \ |
| 98 | throw kj::mv(e); \ |
| 99 | } \ |
| 100 | }()) |
| 101 | |
| 102 | #define _JSG_INTERNAL_FAIL_REQUIRE(jsErrorType, ...) \ |
| 103 | do { \ |
| 104 | try { \ |
| 105 | KJ_FAIL_REQUIRE(jsErrorType ": Cloudflare internal error."); \ |
| 106 | } catch (kj::Exception & e) { \ |
| 107 | KJ_LOG(ERROR, e, ##__VA_ARGS__); \ |
| 108 | throw kj::mv(e); \ |
| 109 | } \ |
| 110 | } while (0) |
| 111 | |
| 112 | // Given a KJ exception's description, strips any leading "remote exception: " prefixes. |
| 113 | kj::StringPtr stripRemoteExceptionPrefix(kj::StringPtr internalMessage); |
| 114 | |
| 115 | // Given a KJ exception's description, returns whether it contains a tunneled exception that could |
| 116 | // be converted back to JavaScript via exceptionToJs(). |
| 117 | bool isTunneledException(kj::StringPtr internalMessage); |
| 118 | |
| 119 | // Given a KJ exception's description, returns whether it contains the magic constant that indicates |
| 120 | // the exception is the script's fault and isn't worth logging. |
| 121 | bool isDoNotLogException(kj::StringPtr internalMessage); |
| 122 | |
| 123 | // Log an exception ala LOG_EXCEPTION, but only if it is worth logging and not a tunneled exception. |
| 124 | #define LOG_EXCEPTION_IF_INTERNAL(context, exception) \ |
| 125 | if (!jsg::isTunneledException(exception.getDescription()) && \ |
| 126 | !jsg::isDoNotLogException(exception.getDescription())) { \ |
| 127 | LOG_EXCEPTION(context, exception); \ |
| 128 | } |
| 129 | |
| 130 | struct TunneledErrorType { |
| 131 | // The original error message stripped of prefixes. |
| 132 | kj::StringPtr message; |
| 133 | |
| 134 | // Was this error prefixed by JSG already? |
| 135 | bool isJsgError; |
| 136 | |
| 137 | // Is this error internal? If so, the error message should be logged to syslog and hidden from |
| 138 | // the app. |
| 139 | bool isInternal; |
| 140 | |
| 141 | // Was the error tunneled from either a worker or an actor? |
| 142 | bool isFromRemote; |
| 143 | |
| 144 | // Was the error created because a durable object is broken? |
| 145 | bool isDurableObjectReset; |
| 146 | |
| 147 | // Does the error contain the "worker_do_not_log" magic constant? |
| 148 | bool isDoNotLogException; |
| 149 | }; |
| 150 | |
| 151 | TunneledErrorType tunneledErrorType(kj::StringPtr internalMessage); |
| 152 | |
| 153 | // Annotate an internal message with the corresponding brokenness reason. |
| 154 | kj::String annotateBroken(kj::StringPtr internalMessage, kj::StringPtr brokennessReason); |
| 155 | |
| 156 | // Returns true if the exception description originated from user code throwing inside |
| 157 | // blockConcurrencyWhile. Handles any leading "remote." prefixes transparently. |
| 158 | bool isExceptionFromInputGateBroken(kj::StringPtr description); |
| 159 | |
| 160 | constexpr kj::Exception::DetailTypeId EXCEPTION_IS_USER_ERROR = 0x82aff7d637c30e47ull; |
| 161 | |
| 162 | struct ExceptionToJsOptions { |
| 163 | // When ignoreDetail is true, tells kjExceptionToJs() to ignore any serialized |
| 164 | // exception detail in the kj::Exception. |
| 165 | bool ignoreDetail = false; |
| 166 | |
| 167 | // When trusted is true and the kj::Exception has a serialized exception detail, the |
| 168 | // stack will be included in the deserialized error if it is available. When false, |
| 169 | // the stack will be omitted. |
| 170 | bool trusted = false; |
| 171 | |
| 172 | // If the deserialized exception detail is not an object, then it will be ignored |
| 173 | // and we will fall back to constructing a new error object. The default is true |
| 174 | // to preserve existing behavior, but setting this to false may be useful in some |
| 175 | // cases. When false, the kjExceptionToJs() might return a non-object value. |
| 176 | bool allowNonObjects = false; |
| 177 | }; |
| 178 | |
| 179 | } // namespace workerd::jsg |