diff --git a/src/workerd/jsg/BUILD.bazel b/src/workerd/jsg/BUILD.bazel index 89879f4c8a6..706ea5b9db7 100644 --- a/src/workerd/jsg/BUILD.bazel +++ b/src/workerd/jsg/BUILD.bazel @@ -8,23 +8,40 @@ wd_cc_library( name = "jsg", srcs = glob( ["*.c++"], - exclude = ["*-test.c++"], + exclude = [ + "exception.c++", + "*-test.c++", + ], ), hdrs = glob( ["*.h"], - exclude = ["rtti.h"], + exclude = [ + "exception.h", + "rtti.h", + ], ), visibility = ["//visibility:public"], deps = [ + ":exception", ":modules_capnp", + "//src/workerd/util", "//src/workerd/util:sentry", "//src/workerd/util:thread-scopes", - "//src/workerd/util", "@capnp-cpp//src/kj", "@workerd-v8//:v8", ], ) +wd_cc_library( + name = "exception", + srcs = ["exception.c++"], + hdrs = ["exception.h"], + visibility = ["//visibility:public"], + deps = [ + "@capnp-cpp//src/kj", + ], +) + wd_cc_capnp_library( name = "rtti_capnp", srcs = ["rtti.capnp"], @@ -34,11 +51,11 @@ wd_cc_capnp_library( js_capnp_library( name = "rtti_capnp_js", srcs = ["rtti.capnp"], - visibility = ["//visibility:public"], target_compatible_with = select({ "@platforms//os:windows": ["@platforms//:incompatible"], "//conditions:default": [], }), + visibility = ["//visibility:public"], ) npm_package( diff --git a/src/workerd/jsg/exception.c++ b/src/workerd/jsg/exception.c++ new file mode 100644 index 00000000000..2f166ddad89 --- /dev/null +++ b/src/workerd/jsg/exception.c++ @@ -0,0 +1,181 @@ +#include "exception.h" + +#include + +namespace workerd::jsg { + +kj::StringPtr stripRemoteExceptionPrefix(kj::StringPtr internalMessage) { + while (internalMessage.startsWith("remote exception: "_kj)) { + // Exception was passed over RPC. + internalMessage = internalMessage.slice("remote exception: "_kj.size()); + } + return internalMessage; +} + +namespace { + constexpr auto ERROR_PREFIX_DELIM = "; "_kj; + constexpr auto ERROR_REMOTE_PREFIX = "remote."_kj; + constexpr auto ERROR_TUNNELED_PREFIX_CFJS = "cfjs."_kj; + constexpr auto ERROR_TUNNELED_PREFIX_JSG = "jsg."_kj; + constexpr auto ERROR_INTERNAL_SOURCE_PREFIX_CFJS = "cfjs-internal."_kj; + constexpr auto ERROR_INTERNAL_SOURCE_PREFIX_JSG = "jsg-internal."_kj; +} + +TunneledErrorType tunneledErrorType(kj::StringPtr internalMessage) { + // A tunneled error in an internal message is prefixed by one of the following patterns, + // anchored at the beginning of the message: + // jsg. + // expected <...>; jsg. + // broken.<...>; jsg. + // where <...> is some failed expectation from e.g. a KJ_REQUIRE. + // + // A tunneled error might have a prefix "remote.". This indicates it was tunneled from an actor or + // from one worker to another. If this prefix is present, we set `isFromRemote` to true, remove + // the "remote." prefix, and continue processing the rest of the error. + // + // Additionally, a prefix of `jsg-internal.` instead of `jsg.` means "throw a specific + // JavaScript error type, but still hide the message text from the app". + + internalMessage = stripRemoteExceptionPrefix(internalMessage); + + struct Properties { + bool isFromRemote = false; + bool isDurableObjectReset = false; + }; + Properties properties; + + // Remove `remote.` (if present). Note that there are cases where we return a tunneled error + // through multiple workers, so let's be paranoid and allow for multiple "remote." prefxies. + while (internalMessage.startsWith(ERROR_REMOTE_PREFIX)) { + properties.isFromRemote = true; + internalMessage = internalMessage.slice(ERROR_REMOTE_PREFIX.size()); + } + + auto findDelim = [](kj::StringPtr msg) -> size_t { + // Either return 0 if no matches or the index past the first delim if there are. + auto match = strstr(msg.cStr(), ERROR_PREFIX_DELIM.cStr()); + if (!match) { + return 0; + } else { + return (match - msg.cStr()) + ERROR_PREFIX_DELIM.size(); + } + }; + + auto tryExtractError = [](kj::StringPtr msg, Properties properties) + -> kj::Maybe { + if (msg.startsWith(ERROR_TUNNELED_PREFIX_CFJS)) { + return TunneledErrorType{ + .message = msg.slice(ERROR_TUNNELED_PREFIX_CFJS.size()), + .isJsgError = true, + .isInternal = false, + .isFromRemote = properties.isFromRemote, + .isDurableObjectReset = properties.isDurableObjectReset, + }; + } + if (msg.startsWith(ERROR_TUNNELED_PREFIX_JSG)) { + return TunneledErrorType{ + .message = msg.slice(ERROR_TUNNELED_PREFIX_JSG.size()), + .isJsgError = true, + .isInternal = false, + .isFromRemote = properties.isFromRemote, + .isDurableObjectReset = properties.isDurableObjectReset, + }; + } + if (msg.startsWith(ERROR_INTERNAL_SOURCE_PREFIX_CFJS)) { + return TunneledErrorType{ + .message = msg.slice(ERROR_INTERNAL_SOURCE_PREFIX_CFJS.size()), + .isJsgError = true, + .isInternal = true, + .isFromRemote = properties.isFromRemote, + .isDurableObjectReset = properties.isDurableObjectReset, + }; + } + if (msg.startsWith(ERROR_INTERNAL_SOURCE_PREFIX_JSG)) { + return TunneledErrorType{ + .message = msg.slice(ERROR_INTERNAL_SOURCE_PREFIX_JSG.size()), + .isJsgError = true, + .isInternal = true, + .isFromRemote = properties.isFromRemote, + .isDurableObjectReset = properties.isDurableObjectReset, + }; + } + + return nullptr; + }; + + auto makeDefaultError = [](kj::StringPtr msg, Properties properties) { + return TunneledErrorType{ + .message = msg, + .isJsgError = false, + .isInternal = true, + .isFromRemote = properties.isFromRemote, + .isDurableObjectReset = properties.isDurableObjectReset, + }; + }; + + if (internalMessage.startsWith("expected ")) { + // This was a test assertion, peel away delimiters until either we find an error or there are + // none left. + auto idx = findDelim(internalMessage); + while(idx) { + internalMessage = internalMessage.slice(idx); + KJ_IF_MAYBE(e, tryExtractError(internalMessage, properties)) { + return kj::mv(*e); + } + idx = findDelim(internalMessage); + } + + // We failed to extract an expected error, make a default one. + return makeDefaultError(internalMessage, properties); + } + + while (internalMessage.startsWith("broken.")) { + properties.isDurableObjectReset = true; + + // Trim away all broken prefixes, they are not allowed to have internal delimiters. + internalMessage = internalMessage.slice(findDelim(internalMessage)); + } + + // There are no prefixes left, just try to extract the error. + KJ_IF_MAYBE(e, tryExtractError(internalMessage, properties)) { + return kj::mv(*e); + } else { + return makeDefaultError(internalMessage, properties); + } +} + +bool isTunneledException(kj::StringPtr internalMessage) { + return !tunneledErrorType(internalMessage).isInternal; +} + +bool isDoNotLogException(kj::StringPtr internalMessage) { + return strstr(internalMessage.cStr(), "worker_do_not_log") != nullptr; +} + +kj::String annotateBroken(kj::StringPtr internalMessage, kj::StringPtr brokenessReason) { + // TODO(soon) Once we support multiple brokenness reasons, we can make this much simpler. + + KJ_LOG(INFO, "Annotating with brokenness", internalMessage, brokenessReason); + auto tunneledInfo = tunneledErrorType(internalMessage); + + kj::StringPtr remotePrefix; + if (tunneledInfo.isFromRemote) { + remotePrefix = ERROR_REMOTE_PREFIX; + } + + kj::StringPtr prefixType = ERROR_TUNNELED_PREFIX_JSG; + kj::StringPtr internalErrorType; + if (tunneledInfo.isInternal) { + prefixType = ERROR_INTERNAL_SOURCE_PREFIX_JSG; + if (!tunneledInfo.isJsgError) { + // This is not a JSG error, so we need to give it a type. + internalErrorType = "Error: "_kj; + } + } + + return kj::str( + remotePrefix, brokenessReason, ERROR_PREFIX_DELIM, prefixType, internalErrorType, + tunneledInfo.message); +} + +} // namespace workerd::jsg diff --git a/src/workerd/jsg/exception.h b/src/workerd/jsg/exception.h new file mode 100644 index 00000000000..44f3085d5e9 --- /dev/null +++ b/src/workerd/jsg/exception.h @@ -0,0 +1,145 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +#pragma once + +#include + +namespace workerd::jsg { + +#define JSG_EXCEPTION(jsErrorType) JSG_ERROR_ ## jsErrorType +#define JSG_DOM_EXCEPTION(name) "jsg.DOMException(" name ")" +#define JSG_INTERNAL_DOM_EXCEPTION(name) "jsg-internal.DOMException(" name ")" + +#define JSG_ERROR_DOMOperationError JSG_DOM_EXCEPTION("OperationError") +#define JSG_ERROR_DOMDataError JSG_DOM_EXCEPTION("DataError") +#define JSG_ERROR_DOMDataCloneError JSG_DOM_EXCEPTION("DataCloneError") +#define JSG_ERROR_DOMInvalidAccessError JSG_DOM_EXCEPTION("InvalidAccessError") +#define JSG_ERROR_DOMInvalidStateError JSG_DOM_EXCEPTION("InvalidStateError") +#define JSG_ERROR_DOMInvalidCharacterError JSG_DOM_EXCEPTION("InvalidCharacterError") +#define JSG_ERROR_DOMNotSupportedError JSG_DOM_EXCEPTION("NotSupportedError") +#define JSG_ERROR_DOMSyntaxError JSG_DOM_EXCEPTION("SyntaxError") +#define JSG_ERROR_DOMTimeoutError JSG_DOM_EXCEPTION("TimeoutError") +#define JSG_ERROR_DOMTypeMismatchError JSG_DOM_EXCEPTION("TypeMismatchError") +#define JSG_ERROR_DOMQuotaExceededError JSG_DOM_EXCEPTION("QuotaExceededError") +#define JSG_ERROR_DOMAbortError JSG_DOM_EXCEPTION("AbortError") + +#define JSG_ERROR_TypeError "jsg.TypeError" +#define JSG_ERROR_Error "jsg.Error" +#define JSG_ERROR_RangeError "jsg.RangeError" + +#define JSG_ERROR_InternalDOMOperationError JSG_INTERNAL_DOM_EXCEPTION("OperationError") + +#define JSG_KJ_EXCEPTION(type, jsErrorType, ...) \ + kj::Exception(kj::Exception::Type::type, __FILE__, __LINE__, \ + kj::str(JSG_EXCEPTION(jsErrorType) ": ", __VA_ARGS__)) + +#define JSG_ASSERT(cond, jsErrorType, ...) \ + KJ_ASSERT(cond, kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) + +#define JSG_REQUIRE(cond, jsErrorType, ...) \ + KJ_REQUIRE(cond, kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) +// Unlike KJ_REQUIRE, JSG_REQUIRE passes all message arguments through kj::str which makes it +// "prettier". This does have some implications like if there's only string literal arguments then +// there's an unnecessary heap copy. More importantly none of the expressions you pass in end up in +// the resultant string AND you are responsible for formatting the resultant string. For example, +// KJ_REQUIRE(false, "some message", x) formats it like "some message; x = 5". The "equivalent" via +// this macro would be JSG_REQUIRE(false, "some message ", x); which would yield a string like +// "some message 5" (or JSG_REQUIRE(false, "some message; x = ", x) if you wanted identical output, +// but then why not use KJ_REQUIRE). + +#define JSG_REQUIRE_NONNULL(value, jsErrorType, ...) \ + KJ_REQUIRE_NONNULL(value, kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) +// JSG_REQUIRE + KJ_REQUIRE_NONNULL. + +#define JSG_FAIL_REQUIRE(jsErrorType, ...) \ + KJ_FAIL_REQUIRE(kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) +// JSG_REQUIRE + KJ_FAIL_REQUIRE + +#define JSG_WARN_ONCE(msg, ...) \ + static bool logOnce KJ_UNUSED = ([&] { \ + KJ_LOG(WARNING, msg, ##__VA_ARGS__); \ + return true; \ + })() \ + +// Conditionally log a warning, at most once. Useful for determining if code changes would break +// any existing scripts. +#define JSG_WARN_ONCE_IF(cond, msg, ...) \ + if (cond) { \ + JSG_WARN_ONCE(msg, ##__VA_ARGS__); \ + } + +// These are passthrough functions to KJ. We expect the error string to be +// surfaced to the application. + +#define _JSG_INTERNAL_REQUIRE(cond, jsErrorType, ...) \ + do { \ + try { \ + KJ_REQUIRE(cond, jsErrorType ": Cloudflare internal error."); \ + } catch (const kj::Exception& e) { \ + KJ_LOG(ERROR, e, ##__VA_ARGS__); \ + throw e; \ + } \ + } while (0) + +#define _JSG_INTERNAL_REQUIRE_NONNULL(value, jsErrorType, ...) \ + ([&]() -> decltype(auto) { \ + try { \ + return KJ_REQUIRE_NONNULL(value, jsErrorType ": Cloudflare internal error."); \ + } catch (const kj::Exception& e) { \ + KJ_LOG(ERROR, e, ##__VA_ARGS__); \ + throw e; \ + } \ + }()) + +#define _JSG_INTERNAL_FAIL_REQUIRE(jsErrorType, ...) \ + do { \ + try { \ + KJ_FAIL_REQUIRE(jsErrorType ": Cloudflare internal error."); \ + } catch (const kj::Exception& e) { \ + KJ_LOG(ERROR, e, ##__VA_ARGS__); \ + throw e; \ + } \ + } while (0) + +bool isTunneledException(kj::StringPtr internalMessage); +// Given a KJ exception's description, returns whether it contains a tunneled exception that could +// be converted back to JavaScript via makeInternalError(). + +bool isDoNotLogException(kj::StringPtr internalMessage); +// Given a KJ exception's description, returns whether it contains the magic constant that indicates +// the exception is the script's fault and isn't worth logging. + +// Log an exception ala LOG_EXCEPTION, but only if it is worth logging and not a tunneled exception. +#define LOG_EXCEPTION_IF_INTERNAL(context, exception) \ + if (!jsg::isTunneledException(exception.getDescription()) && \ + !jsg::isDoNotLogException(exception.getDescription())) { \ + LOG_EXCEPTION(context, exception); \ + } + + +struct TunneledErrorType { + kj::StringPtr message; + // The original error message stripped of prefixes. + + bool isJsgError; + // Was this error prefixed by JSG already? + + bool isInternal; + // Is this error internal? If so, the error message should be logged to syslog and hidden from + // the app. + + bool isFromRemote; + // Was the error tunneled from either a worker or an actor? + + bool isDurableObjectReset; + // Was the error created because a durable object is broken? +}; + +TunneledErrorType tunneledErrorType(kj::StringPtr internalMessage); + +kj::String annotateBroken(kj::StringPtr internalMessage, kj::StringPtr brokenessReason); +// Annotate an internal message with the corresponding brokeness reason. + +} // namespace workerd::jsg diff --git a/src/workerd/jsg/jsg.h b/src/workerd/jsg/jsg.h index a6d7a4577f1..3d2a990592f 100644 --- a/src/workerd/jsg/jsg.h +++ b/src/workerd/jsg/jsg.h @@ -17,100 +17,8 @@ #include "macro-meta.h" #include "wrappable.h" -#define JSG_ASSERT(cond, jsErrorType, ...) \ - KJ_ASSERT(cond, kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) - -#define JSG_REQUIRE(cond, jsErrorType, ...) \ - KJ_REQUIRE(cond, kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) -// Unlike KJ_REQUIRE, JSG_REQUIRE passes all message arguments through kj::str which makes it -// "prettier". This does have some implications like if there's only string literal arguments then -// there's an unnecessary heap copy. More importantly none of the expressions you pass in end up in -// the resultant string AND you are responsible for formatting the resultant string. For example, -// KJ_REQUIRE(false, "some message", x) formats it like "some message; x = 5". The "equivalent" via -// this macro would be JSG_REQUIRE(false, "some message ", x); which would yield a string like -// "some message 5" (or JSG_REQUIRE(false, "some message; x = ", x) if you wanted identical output, -// but then why not use KJ_REQUIRE). - -#define JSG_REQUIRE_NONNULL(value, jsErrorType, ...) \ - KJ_REQUIRE_NONNULL(value, kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) -// JSG_REQUIRE + KJ_REQUIRE_NONNULL. - -#define JSG_FAIL_REQUIRE(jsErrorType, ...) \ - KJ_FAIL_REQUIRE(kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__)) -// JSG_REQUIRE + KJ_FAIL_REQUIRE - -#define JSG_WARN_ONCE(msg, ...) \ - static bool logOnce KJ_UNUSED = ([&] { \ - KJ_LOG(WARNING, msg, ##__VA_ARGS__); \ - return true; \ - })() \ - -// Conditionally log a warning, at most once. Useful for determining if code changes would break -// any existing scripts. -#define JSG_WARN_ONCE_IF(cond, msg, ...) \ - if (cond) { \ - JSG_WARN_ONCE(msg, ##__VA_ARGS__); \ - } +#include -// These are passthrough functions to KJ. We expect the error string to be -// surfaced to the application. - -#define _JSG_INTERNAL_REQUIRE(cond, jsErrorType, ...) \ - do { \ - try { \ - KJ_REQUIRE(cond, jsErrorType ": Cloudflare internal error."); \ - } catch (const kj::Exception& e) { \ - KJ_LOG(ERROR, e, ##__VA_ARGS__); \ - throw e; \ - } \ - } while (0) - -#define _JSG_INTERNAL_REQUIRE_NONNULL(value, jsErrorType, ...) \ - ([&]() -> decltype(auto) { \ - try { \ - return KJ_REQUIRE_NONNULL(value, jsErrorType ": Cloudflare internal error."); \ - } catch (const kj::Exception& e) { \ - KJ_LOG(ERROR, e, ##__VA_ARGS__); \ - throw e; \ - } \ - }()) - -#define _JSG_INTERNAL_FAIL_REQUIRE(jsErrorType, ...) \ - do { \ - try { \ - KJ_FAIL_REQUIRE(jsErrorType ": Cloudflare internal error."); \ - } catch (const kj::Exception& e) { \ - KJ_LOG(ERROR, e, ##__VA_ARGS__); \ - throw e; \ - } \ - } while (0) - -#define JSG_EXCEPTION(jsErrorType) JSG_ERROR_ ## jsErrorType -#define JSG_DOM_EXCEPTION(name) "jsg.DOMException(" name ")" -#define JSG_INTERNAL_DOM_EXCEPTION(name) "jsg-internal.DOMException(" name ")" - -#define JSG_ERROR_DOMOperationError JSG_DOM_EXCEPTION("OperationError") -#define JSG_ERROR_DOMDataError JSG_DOM_EXCEPTION("DataError") -#define JSG_ERROR_DOMDataCloneError JSG_DOM_EXCEPTION("DataCloneError") -#define JSG_ERROR_DOMInvalidAccessError JSG_DOM_EXCEPTION("InvalidAccessError") -#define JSG_ERROR_DOMInvalidStateError JSG_DOM_EXCEPTION("InvalidStateError") -#define JSG_ERROR_DOMInvalidCharacterError JSG_DOM_EXCEPTION("InvalidCharacterError") -#define JSG_ERROR_DOMNotSupportedError JSG_DOM_EXCEPTION("NotSupportedError") -#define JSG_ERROR_DOMSyntaxError JSG_DOM_EXCEPTION("SyntaxError") -#define JSG_ERROR_DOMTimeoutError JSG_DOM_EXCEPTION("TimeoutError") -#define JSG_ERROR_DOMTypeMismatchError JSG_DOM_EXCEPTION("TypeMismatchError") -#define JSG_ERROR_DOMQuotaExceededError JSG_DOM_EXCEPTION("QuotaExceededError") -#define JSG_ERROR_DOMAbortError JSG_DOM_EXCEPTION("AbortError") - -#define JSG_ERROR_TypeError "jsg.TypeError" -#define JSG_ERROR_Error "jsg.Error" -#define JSG_ERROR_RangeError "jsg.RangeError" - -#define JSG_ERROR_InternalDOMOperationError JSG_INTERNAL_DOM_EXCEPTION("OperationError") - -#define JSG_KJ_EXCEPTION(type, jsErrorType, ...) \ - kj::Exception(kj::Exception::Type::type, __FILE__, __LINE__, \ - kj::str(JSG_EXCEPTION(jsErrorType) ": ", __VA_ARGS__)) namespace workerd::jsg { kj::String stringifyHandle(v8::Local value); diff --git a/src/workerd/jsg/util.c++ b/src/workerd/jsg/util.c++ index c6c842f08ad..29c4194dbe3 100644 --- a/src/workerd/jsg/util.c++ +++ b/src/workerd/jsg/util.c++ @@ -162,155 +162,6 @@ bool setDurableObjectResetError(v8::Isolate* isolate, v8::Local& exce jsg::v8StrIntern(isolate, "durableObjectReset"_kj), v8::True(isolate))); } - -struct TunneledErrorType { - kj::StringPtr message; - // The original error message stripped of prefixes. - - bool isJsgError; - // Was this error prefixed by JSG already? - - bool isInternal; - // Is this error internal? If so, the error message should be logged to syslog and hidden from - // the app. - - bool isFromRemote; - // Was the error tunneled from either a worker or an actor? - - bool isDurableObjectReset; - // Was the error created because a durable object is broken? -}; - -namespace { - constexpr auto ERROR_PREFIX_DELIM = "; "_kj; - constexpr auto ERROR_REMOTE_PREFIX = "remote."_kj; - constexpr auto ERROR_TUNNELED_PREFIX_CFJS = "cfjs."_kj; - constexpr auto ERROR_TUNNELED_PREFIX_JSG = "jsg."_kj; - constexpr auto ERROR_INTERNAL_SOURCE_PREFIX_CFJS = "cfjs-internal."_kj; - constexpr auto ERROR_INTERNAL_SOURCE_PREFIX_JSG = "jsg-internal."_kj; -} -TunneledErrorType tunneledErrorType(kj::StringPtr internalMessage) { - // A tunneled error in an internal message is prefixed by one of the following patterns, - // anchored at the beginning of the message: - // jsg. - // expected <...>; jsg. - // broken.<...>; jsg. - // where <...> is some failed expectation from e.g. a KJ_REQUIRE. - // - // A tunneled error might have a prefix "remote.". This indicates it was tunneled from an actor or - // from one worker to another. If this prefix is present, we set `isFromRemote` to true, remove - // the "remote." prefix, and continue processing the rest of the error. - // - // Additionally, a prefix of `jsg-internal.` instead of `jsg.` means "throw a specific - // JavaScript error type, but still hide the message text from the app". - - internalMessage = stripRemoteExceptionPrefix(internalMessage); - - struct Properties { - bool isFromRemote = false; - bool isDurableObjectReset = false; - }; - Properties properties; - - // Remove `remote.` (if present). Note that there are cases where we return a tunneled error - // through multiple workers, so let's be paranoid and allow for multiple "remote." prefxies. - while (internalMessage.startsWith(ERROR_REMOTE_PREFIX)) { - properties.isFromRemote = true; - internalMessage = internalMessage.slice(ERROR_REMOTE_PREFIX.size()); - } - - auto findDelim = [](kj::StringPtr msg) -> size_t { - // Either return 0 if no matches or the index past the first delim if there are. - auto match = strstr(msg.cStr(), ERROR_PREFIX_DELIM.cStr()); - if (!match) { - return 0; - } else { - return (match - msg.cStr()) + ERROR_PREFIX_DELIM.size(); - } - }; - - auto tryExtractError = [](kj::StringPtr msg, Properties properties) - -> kj::Maybe { - if (msg.startsWith(ERROR_TUNNELED_PREFIX_CFJS)) { - return TunneledErrorType{ - .message = msg.slice(ERROR_TUNNELED_PREFIX_CFJS.size()), - .isJsgError = true, - .isInternal = false, - .isFromRemote = properties.isFromRemote, - .isDurableObjectReset = properties.isDurableObjectReset, - }; - } - if (msg.startsWith(ERROR_TUNNELED_PREFIX_JSG)) { - return TunneledErrorType{ - .message = msg.slice(ERROR_TUNNELED_PREFIX_JSG.size()), - .isJsgError = true, - .isInternal = false, - .isFromRemote = properties.isFromRemote, - .isDurableObjectReset = properties.isDurableObjectReset, - }; - } - if (msg.startsWith(ERROR_INTERNAL_SOURCE_PREFIX_CFJS)) { - return TunneledErrorType{ - .message = msg.slice(ERROR_INTERNAL_SOURCE_PREFIX_CFJS.size()), - .isJsgError = true, - .isInternal = true, - .isFromRemote = properties.isFromRemote, - .isDurableObjectReset = properties.isDurableObjectReset, - }; - } - if (msg.startsWith(ERROR_INTERNAL_SOURCE_PREFIX_JSG)) { - return TunneledErrorType{ - .message = msg.slice(ERROR_INTERNAL_SOURCE_PREFIX_JSG.size()), - .isJsgError = true, - .isInternal = true, - .isFromRemote = properties.isFromRemote, - .isDurableObjectReset = properties.isDurableObjectReset, - }; - } - - return nullptr; - }; - - auto makeDefaultError = [](kj::StringPtr msg, Properties properties) { - return TunneledErrorType{ - .message = msg, - .isJsgError = false, - .isInternal = true, - .isFromRemote = properties.isFromRemote, - .isDurableObjectReset = properties.isDurableObjectReset, - }; - }; - - if (internalMessage.startsWith("expected ")) { - // This was a test assertion, peel away delimiters until either we find an error or there are - // none left. - auto idx = findDelim(internalMessage); - while(idx) { - internalMessage = internalMessage.slice(idx); - KJ_IF_MAYBE(e, tryExtractError(internalMessage, properties)) { - return kj::mv(*e); - } - idx = findDelim(internalMessage); - } - - // We failed to extract an expected error, make a default one. - return makeDefaultError(internalMessage, properties); - } - - while (internalMessage.startsWith("broken.")) { - properties.isDurableObjectReset = true; - - // Trim away all broken prefixes, they are not allowed to have internal delimiters. - internalMessage = internalMessage.slice(findDelim(internalMessage)); - } - - // There are no prefixes left, just try to extract the error. - KJ_IF_MAYBE(e, tryExtractError(internalMessage, properties)) { - return kj::mv(*e); - } else { - return makeDefaultError(internalMessage, properties); - } -} struct DecodedException { v8::Local handle; bool isInternal; @@ -411,31 +262,7 @@ kj::StringPtr extractTunneledExceptionDescription(kj::StringPtr message) { } } -kj::String annotateBroken(kj::StringPtr internalMessage, kj::StringPtr brokenessReason) { - // TODO(soon) Once we support multiple brokenness reasons, we can make this much simpler. - KJ_LOG(INFO, "Annotating with brokenness", internalMessage, brokenessReason); - auto tunneledInfo = tunneledErrorType(internalMessage); - - kj::StringPtr remotePrefix; - if (tunneledInfo.isFromRemote) { - remotePrefix = ERROR_REMOTE_PREFIX; - } - - kj::StringPtr prefixType = ERROR_TUNNELED_PREFIX_JSG; - kj::StringPtr internalErrorType; - if (tunneledInfo.isInternal) { - prefixType = ERROR_INTERNAL_SOURCE_PREFIX_JSG; - if (!tunneledInfo.isJsgError) { - // This is not a JSG error, so we need to give it a type. - internalErrorType = "Error: "_kj; - } - } - - return kj::str( - remotePrefix, brokenessReason, ERROR_PREFIX_DELIM, prefixType, internalErrorType, - tunneledInfo.message); -} v8::Local makeInternalError(v8::Isolate* isolate, kj::Exception&& exception) { auto desc = exception.getDescription(); @@ -637,22 +464,6 @@ kj::Exception Lock::exceptionToKj(Value&& exception) { return createTunneledException(v8Isolate, exception.getHandle(v8Isolate)); } -kj::StringPtr stripRemoteExceptionPrefix(kj::StringPtr internalMessage) { - while (internalMessage.startsWith("remote exception: "_kj)) { - // Exception was passed over RPC. - internalMessage = internalMessage.slice("remote exception: "_kj.size()); - } - return internalMessage; -} - -bool isTunneledException(kj::StringPtr internalMessage) { - return !tunneledErrorType(internalMessage).isInternal; -} - -bool isDoNotLogException(kj::StringPtr internalMessage) { - return strstr(internalMessage.cStr(), "worker_do_not_log") != nullptr; -} - static kj::byte DUMMY = 0; static kj::Array getEmptyArray() { // An older version of asBytes(), when given an empty ArrayBuffer, would often return an array diff --git a/src/workerd/jsg/util.h b/src/workerd/jsg/util.h index 419fde30138..80517d1af83 100644 --- a/src/workerd/jsg/util.h +++ b/src/workerd/jsg/util.h @@ -26,9 +26,6 @@ bool getCommonJsExportDefault(v8::Isolate* isolate); kj::String fullyQualifiedTypeName(const std::type_info& type); kj::String typeName(const std::type_info& type); -kj::String annotateBroken(kj::StringPtr internalMessage, kj::StringPtr brokenessReason); -// Annotate an internal message with the corresponding brokeness reason. - v8::Local makeInternalError(v8::Isolate* isolate, kj::StringPtr internalMessage); v8::Local makeInternalError(v8::Isolate* isolate, kj::Exception&& exception); // Creates a JavaScript error that obfuscates the exception details, while logging the full details @@ -132,21 +129,6 @@ kj::Exception createTunneledException(v8::Isolate* isolate, v8::Local kj::StringPtr stripRemoteExceptionPrefix(kj::StringPtr internalMessage); // Given a KJ exception's description, strips any leading "remote exception: " prefixes. -bool isTunneledException(kj::StringPtr internalMessage); -// Given a KJ exception's description, returns whether it contains a tunneled exception that could -// be converted back to JavaScript via makeInternalError(). - -bool isDoNotLogException(kj::StringPtr internalMessage); -// Given a KJ exception's description, returns whether it contains the magic constant that indicates -// the exception is the script's fault and isn't worth logging. - -// Log an exception ala LOG_EXCEPTION, but only if it is worth logging and not a tunneled exception. -#define LOG_EXCEPTION_IF_INTERNAL(context, exception) \ - if (!jsg::isTunneledException(exception.getDescription()) && \ - !jsg::isDoNotLogException(exception.getDescription())) { \ - LOG_EXCEPTION(context, exception); \ - } - template v8::Local check(v8::MaybeLocal maybe) { // V8 usually returns a MaybeLocal to mean that the function can throw a JavaScript exception.