Skip to content
File

Blob: src/workerd/jsg/exception.h

cpp180 lines
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 
10namespace 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.
113kj::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().
117bool 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.
121bool 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 
130struct 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 
151TunneledErrorType tunneledErrorType(kj::StringPtr internalMessage);
152 
153// Annotate an internal message with the corresponding brokenness reason.
154kj::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.
158bool isExceptionFromInputGateBroken(kj::StringPtr description);
159 
160constexpr kj::Exception::DetailTypeId EXCEPTION_IS_USER_ERROR = 0x82aff7d637c30e47ull;
161 
162struct 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