// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors // Licensed under the MIT License: // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "exception.h" #include "string.h" #include "debug.h" #include "threadlocal.h" #include "miniposix.h" #include #include #include #include #if !KJ_NO_RTTI #include #endif #if __GNUC__ #include #endif #ifndef KJ_USE_BACKTRACE #if (__linux__ && __GLIBC__ && !__UCLIBC__) || __APPLE__ #define KJ_USE_BACKTRACE 1 #endif #endif #if KJ_USE_BACKTRACE #include #endif #if (__linux__ || __APPLE__) #include #include #endif #if KJ_HAS_LIBDL #include "dlfcn.h" #endif #if KJ_HAS_COMPILER_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) #include #else static void __lsan_ignore_object(const void* p) {} #endif // TODO(cleanup): Remove the LSAN stuff per https://github.com/capnproto/capnproto/pull/1255 // feedback. namespace { template inline T* lsanIgnoreObjectAndReturn(T* ptr) { // Defensively lsan_ignore_object since the documentation doesn't explicitly specify what happens // if you call this multiple times on the same object. // TODO(cleanup): Remove this per https://github.com/capnproto/capnproto/pull/1255. __lsan_ignore_object(ptr); return ptr; } } namespace kj { StringPtr KJ_STRINGIFY(LogSeverity severity) { static const char* SEVERITY_STRINGS[] = { "info", "warning", "error", "fatal", "debug" }; return SEVERITY_STRINGS[static_cast(severity)]; } ArrayPtr getStackTrace(ArrayPtr space, uint ignoreCount) { if (getExceptionCallback().stackTraceMode() == ExceptionCallback::StackTraceMode::NONE) { return nullptr; } #if KJ_USE_BACKTRACE size_t size = backtrace(space.begin(), space.size()); for (auto& addr: space.slice(0, size)) { // The addresses produced by backtrace() are return addresses, which means they point to the // instruction immediately after the call. Invoking addr2line on these can be confusing because // it often points to the next line. If the next instruction is inlined from another function, // the trace can be extra-confusing, since now it claims to be in a function that was not // actually on the call stack. If we subtract 1 from each address, though, we get a much more // reasonable trace. This may cause the addresses to be invalid instruction pointers if the // instructions were multi-byte, but it appears addr2line is able to cope with this. addr = reinterpret_cast(reinterpret_cast(addr) - 1); } return space.slice(kj::min(ignoreCount + 1, size), size); #else return nullptr; #endif } #if __GNUC__ || __clang__ // Allow dependents to override the implementation of stack symbolication by making it a weak // symbol. We prefer weak symbols over some sort of callback registration mechanism becasue this // allows an alternate symbolication library to be easily linked into tests without changing the // code of the test. __attribute__((weak)) #endif String stringifyStackTrace(ArrayPtr trace) { if (trace.size() == 0) return nullptr; if (getExceptionCallback().stackTraceMode() != ExceptionCallback::StackTraceMode::FULL) { return nullptr; } #if (__linux__ || __APPLE__) && !__ANDROID__ // We want to generate a human-readable stack trace. // TODO(someday): It would be really great if we could avoid farming out to another process // and do this all in-process, but that may involve onerous requirements like large library // dependencies or using -rdynamic. // The environment manipulation is not thread-safe, so lock a mutex. This could still be // problematic if another thread is manipulating the environment in unrelated code, but there's // not much we can do about that. This is debug-only anyway and only an issue when LD_PRELOAD // is in use. static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mutex); KJ_DEFER(pthread_mutex_unlock(&mutex)); // Don't heapcheck / intercept syscalls. const char* preload = getenv("LD_PRELOAD"); String oldPreload; if (preload != nullptr) { oldPreload = heapString(preload); unsetenv("LD_PRELOAD"); } KJ_DEFER(if (oldPreload != nullptr) { setenv("LD_PRELOAD", oldPreload.cStr(), true); }); String lines[32]; FILE* p = nullptr; auto strTrace = strArray(trace, " "); #if __linux__ if (access("/proc/self/exe", R_OK) < 0) { // Apparently /proc is not available? return nullptr; } // Obtain symbolic stack trace using addr2line. // TODO(cleanup): Use fork() and exec() or maybe our own Subprocess API (once it exists), to // avoid depending on a shell. p = popen(str("addr2line -e /proc/", getpid(), "/exe ", strTrace).cStr(), "r"); #elif __APPLE__ // The Mac OS X equivalent of addr2line is atos. // (Internally, it uses the private CoreSymbolication.framework library.) p = popen(str("xcrun atos -p ", getpid(), ' ', strTrace).cStr(), "r"); #endif if (p == nullptr) { return nullptr; } char line[512]; size_t i = 0; while (i < kj::size(lines) && fgets(line, sizeof(line), p) != nullptr) { // Don't include exception-handling infrastructure or promise infrastructure in stack trace. // addr2line output matches file names; atos output matches symbol names. if (strstr(line, "kj/common.c++") != nullptr || strstr(line, "kj/exception.") != nullptr || strstr(line, "kj/debug.") != nullptr || strstr(line, "kj/async.") != nullptr || strstr(line, "kj/async-prelude.h") != nullptr || strstr(line, "kj/async-inl.h") != nullptr || strstr(line, "kj::Exception") != nullptr || strstr(line, "kj::_::Debug") != nullptr) { continue; } size_t len = strlen(line); if (len > 0 && line[len-1] == '\n') line[len-1] = '\0'; lines[i++] = str("\n ", trimSourceFilename(line), ": returning here"); } // Skip remaining input. while (fgets(line, sizeof(line), p) != nullptr) {} pclose(p); return strArray(arrayPtr(lines, i), ""); #else return nullptr; #endif } String stringifyStackTraceAddresses(ArrayPtr trace) { #if KJ_HAS_LIBDL return strArray(KJ_MAP(addr, trace) { Dl_info info; // Shared libraries are mapped near the end of the address space while the executable is mapped // near the beginning. We want to print addresses in the executable as raw addresses, not // offsets, since that's what addr2line expects for executables. For shared libraries it // expects offsets. In any case, most frames are likely to be in the main executable so it // makes the output cleaner if we don't repeatedly write its name. if (reinterpret_cast(addr) >= 0x400000000000ull && dladdr(addr, &info)) { uintptr_t offset = reinterpret_cast(addr) - reinterpret_cast(info.dli_fbase); return kj::str(info.dli_fname, '@', reinterpret_cast(offset)); } else { return kj::str(addr); } }, " "); #else // TODO(someday): Support other platforms. return kj::strArray(trace, " "); #endif } StringPtr stringifyStackTraceAddresses(ArrayPtr trace, ArrayPtr scratch) { // Version which writes into a pre-allocated buffer. This is safe for signal handlers to the // extent that dladdr() is safe. // // TODO(cleanup): We should improve the KJ stringification framework so that there's a way to // write this string directly into a larger message buffer with strPreallocated(). #if KJ_HAS_LIBDL char* ptr = scratch.begin(); char* limit = scratch.end() - 1; for (auto addr: trace) { Dl_info info; // Shared libraries are mapped near the end of the address space while the executable is mapped // near the beginning. We want to print addresses in the executable as raw addresses, not // offsets, since that's what addr2line expects for executables. For shared libraries it // expects offsets. In any case, most frames are likely to be in the main executable so it // makes the output cleaner if we don't repeatedly write its name. if (reinterpret_cast(addr) >= 0x400000000000ull && dladdr(addr, &info)) { uintptr_t offset = reinterpret_cast(addr) - reinterpret_cast(info.dli_fbase); ptr = _::fillLimited(ptr, limit, kj::StringPtr(info.dli_fname), "@0x"_kj, hex(offset)); } else { ptr = _::fillLimited(ptr, limit, toCharSequence(addr)); } ptr = _::fillLimited(ptr, limit, " "_kj); } *ptr = '\0'; return StringPtr(scratch.begin(), ptr); #else // TODO(someday): Support other platforms. return kj::strPreallocated(scratch, kj::delimited(trace, " ")); #endif } String getStackTrace() { void* space[32]; auto trace = getStackTrace(space, 2); return kj::str(stringifyStackTraceAddresses(trace), stringifyStackTrace(trace)); } kj::StringPtr trimSourceFilename(kj::StringPtr filename) { // Removes noisy prefixes from source code file name. // // The goal here is to produce the "canonical" filename given the filename returned by e.g. // addr2line. addr2line gives us the full path of the file as passed on the compiler // command-line, which in turn is affected by build system and by whether and where we're // performing an out-of-tree build. // // To deal with all this, we look for directory names in the path which we recognize to be // locations that represent roots of the source tree. We strip said root and everything before // it. // // On Windows, we often get filenames containing backslashes. Since we aren't allowed to allocate // a new string here, we can't do much about this, so our returned "canonical" name will // unfortunately end up with backslashes. static constexpr const char* ROOTS[] = { "ekam-provider/canonical/", // Ekam source file. "ekam-provider/c++header/", // Ekam include file. "src/", // Non-Ekam source root. "tmp/", // Non-Ekam generated code. }; retry: for (size_t i: kj::indices(filename)) { if (i == 0 || filename[i-1] == '/' ) { // We're at the start of a directory name. Check for valid prefixes. for (kj::StringPtr root: ROOTS) { if (filename.slice(i).startsWith(root)) { filename = filename.slice(i + root.size()); // We should keep searching to find the last instance of a root name. `i` is no longer // a valid index for `filename` so start the loop over. goto retry; } } } } return filename; } StringPtr KJ_STRINGIFY(Exception::Type type) { static const char* TYPE_STRINGS[] = { "failed", "overloaded", "disconnected", "unimplemented" }; return TYPE_STRINGS[static_cast(type)]; } String KJ_STRINGIFY(const Exception& e) { uint contextDepth = 0; Maybe contextPtr = e.getContext(); for (;;) { KJ_IF_MAYBE(c, contextPtr) { ++contextDepth; contextPtr = c->next; } else { break; } } Array contextText = heapArray(contextDepth); contextDepth = 0; contextPtr = e.getContext(); for (;;) { KJ_IF_MAYBE(c, contextPtr) { contextText[contextDepth++] = str(trimSourceFilename(c->file), ":", c->line, ": context: ", c->description, "\n"); contextPtr = c->next; } else { break; } } return str(strArray(contextText, ""), e.getFile(), ":", e.getLine(), ": ", e.getType(), e.getDescription() == nullptr ? "" : ": ", e.getDescription(), e.getStackTrace().size() > 0 ? "\nstack: " : "", stringifyStackTraceAddresses(e.getStackTrace()), stringifyStackTrace(e.getStackTrace())); } Exception::Exception(Type type, const char* file, int line, String description) noexcept : file(trimSourceFilename(file).cStr()), line(line), type(type), description(mv(description)), traceCount(0) {} Exception::Exception(Type type, String file, int line, String description) noexcept : ownFile(kj::mv(file)), file(trimSourceFilename(ownFile).cStr()), line(line), type(type), description(mv(description)), traceCount(0) {} Exception::Exception(const Exception& other) noexcept : file(other.file), line(other.line), type(other.type), description(heapString(other.description)), traceCount(other.traceCount) { if (file == other.ownFile.cStr()) { ownFile = heapString(other.ownFile); file = ownFile.cStr(); } memcpy(trace, other.trace, sizeof(trace[0]) * traceCount); KJ_IF_MAYBE(c, other.context) { context = heap(**c); } } Exception::~Exception() noexcept {} Exception::Context::Context(const Context& other) noexcept : file(other.file), line(other.line), description(str(other.description)) { KJ_IF_MAYBE(n, other.next) { next = heap(**n); } } void Exception::wrapContext(const char* file, int line, String&& description) { context = heap(file, line, mv(description), mv(context)); } void Exception::extendTrace(uint ignoreCount, uint limit) { if (isFullTrace) { // Awkward: extendTrace() was called twice without truncating in between. This should probably // be an error, but historically we didn't check for this so I'm hesitant to make it an error // now. We shouldn't actually extend the trace, though, as our current trace is presumably // rooted in main() and it'd be weird to append frames "above" that. // TODO(cleanup): Abort here and see what breaks? return; } KJ_STACK_ARRAY(void*, newTraceSpace, kj::min(kj::size(trace), limit) + ignoreCount + 1, sizeof(trace)/sizeof(trace[0]) + 8, 128); auto newTrace = kj::getStackTrace(newTraceSpace, ignoreCount + 1); if (newTrace.size() > ignoreCount + 2) { // Remove suffix that won't fit into our static-sized trace. newTrace = newTrace.slice(0, kj::min(kj::size(trace) - traceCount, newTrace.size())); // Copy the rest into our trace. memcpy(trace + traceCount, newTrace.begin(), newTrace.asBytes().size()); traceCount += newTrace.size(); isFullTrace = true; } } void Exception::truncateCommonTrace() { if (isFullTrace) { // We're truncating the common portion of the full trace, turning it back into a limited // trace. isFullTrace = false; } else { // If the trace was never extended in the first place, trying to truncate it is at best a waste // of time and at worst might remove information for no reason. So, don't. // // This comes up in particular in coroutines, when the exception originated from a co_awaited // promise. In that case we manually add the one relevant frame to the trace, rather than // call extendTrace() just to have to truncate most of it again a moment later in the // unhandled_exception() callback. return; } if (traceCount > 0) { // Create a "reference" stack trace that is a little bit deeper than the one in the exception. void* refTraceSpace[sizeof(this->trace) / sizeof(this->trace[0]) + 4]; auto refTrace = kj::getStackTrace(refTraceSpace, 0); // We expect that the deepest frame in the exception's stack trace should be somewhere in our // own trace, since our own trace has a deeper limit. Search for it. for (uint i = refTrace.size(); i > 0; i--) { if (refTrace[i-1] == trace[traceCount-1]) { // See how many frames match. for (uint j = 0; j < i; j++) { if (j >= traceCount) { // We matched the whole trace, apparently? traceCount = 0; return; } else if (refTrace[i-j-1] != trace[traceCount-j-1]) { // Found mismatching entry. // If we matched more than half of the reference trace, guess that this is in fact // the prefix we're looking for. if (j > refTrace.size() / 2) { // Delete the matching suffix. Also delete one non-matched entry on the assumption // that both traces contain that stack frame but are simply at different points in // the function. traceCount -= j + 1; return; } } } } } // No match. Ignore. } } #if !KJ_NO_EXCEPTIONS class ExceptionImpl: public Exception, public std::exception { public: inline ExceptionImpl(Exception&& other): Exception(mv(other)) {} ExceptionImpl(const ExceptionImpl& other): Exception(other) {} const char* what() const noexcept override; private: mutable String whatBuffer; }; const char* ExceptionImpl::what() const noexcept { whatBuffer = str(*this); return whatBuffer.begin(); } #endif // !KJ_NO_EXCEPTIONS // ======================================================================================= namespace { KJ_THREADLOCAL_PTR(ExceptionCallback) threadLocalCallback = nullptr; } // namespace void requireOnStack(void* ptr, kj::StringPtr description) { #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) || \ KJ_HAS_COMPILER_FEATURE(address_sanitizer) || \ KJ_HAS_COMPILER_FEATURE(hwaddress_sanitizer) || \ defined(__SANITIZE_ADDRESS__) // When using libfuzzer or ASAN, this sanity check may spurriously fail, so skip it. #else char stackVar; ptrdiff_t offset = reinterpret_cast(ptr) - &stackVar; KJ_REQUIRE(offset < 65536 && offset > -65536, kj::str(description)); #endif } ExceptionCallback::ExceptionCallback(): next(getExceptionCallback()) { requireOnStack(this, "ExceptionCallback must be allocated on the stack."); threadLocalCallback = this; } ExceptionCallback::ExceptionCallback(ExceptionCallback& next): next(next) {} ExceptionCallback::~ExceptionCallback() noexcept(false) { if (&next != this) { threadLocalCallback = &next; } } void ExceptionCallback::onRecoverableException(Exception&& exception) { next.onRecoverableException(mv(exception)); } void ExceptionCallback::onFatalException(Exception&& exception) { next.onFatalException(mv(exception)); } void ExceptionCallback::logMessage( LogSeverity severity, const char* file, int line, int contextDepth, String&& text) { next.logMessage(severity, file, line, contextDepth, mv(text)); } ExceptionCallback::StackTraceMode ExceptionCallback::stackTraceMode() { return next.stackTraceMode(); } namespace _ { // private uint uncaughtExceptionCount(); // defined later in this file } class ExceptionCallback::RootExceptionCallback: public ExceptionCallback { public: RootExceptionCallback(): ExceptionCallback(*this) {} void onRecoverableException(Exception&& exception) override { #if KJ_NO_EXCEPTIONS logException(LogSeverity::ERROR, mv(exception)); #else if (_::uncaughtExceptionCount() > 0) { // Bad time to throw an exception. Just log instead. // // TODO(someday): We should really compare uncaughtExceptionCount() against the count at // the innermost runCatchingExceptions() frame in this thread to tell if exceptions are // being caught correctly. logException(LogSeverity::ERROR, mv(exception)); } else { throw ExceptionImpl(mv(exception)); } #endif } void onFatalException(Exception&& exception) override { #if KJ_NO_EXCEPTIONS logException(LogSeverity::FATAL, mv(exception)); #else throw ExceptionImpl(mv(exception)); #endif } void logMessage(LogSeverity severity, const char* file, int line, int contextDepth, String&& text) override { text = str(kj::repeat('_', contextDepth), file, ":", line, ": ", severity, ": ", mv(text), '\n'); StringPtr textPtr = text; while (textPtr != nullptr) { miniposix::ssize_t n = miniposix::write(STDERR_FILENO, textPtr.begin(), textPtr.size()); if (n <= 0) { // stderr is broken. Give up. return; } textPtr = textPtr.slice(n); } } StackTraceMode stackTraceMode() override { #ifdef KJ_DEBUG return StackTraceMode::FULL; #else return StackTraceMode::ADDRESS_ONLY; #endif } private: void logException(LogSeverity severity, Exception&& e) { // We intentionally go back to the top exception callback on the stack because we don't want to // bypass whatever log processing is in effect. // // We intentionally don't log the context since it should get re-added by the exception callback // anyway. getExceptionCallback().logMessage(severity, e.getFile(), e.getLine(), 0, str( e.getType(), e.getDescription() == nullptr ? "" : ": ", e.getDescription(), e.getStackTrace().size() > 0 ? "\nstack: " : "", stringifyStackTraceAddresses(e.getStackTrace()), stringifyStackTrace(e.getStackTrace()), "\n")); } }; ExceptionCallback& getExceptionCallback() { static auto defaultCallback = lsanIgnoreObjectAndReturn( new ExceptionCallback::RootExceptionCallback()); // We allocate on the heap because some objects may throw in their destructors. If those objects // had static storage, they might get fully constructed before the root callback. If they however // then throw an exception during destruction, there would be a lifetime issue because their // destructor would end up getting registered after the root callback's destructor. One solution // is to just leak this pointer & allocate on first-use. The cost is that the initialization is // mildly more expensive (+ we need to annotate sanitizers to ignore the problem). A great // compiler annotation that would simply things would be one that allowed static variables to have // their destruction omitted wholesale. That would allow us to avoid the heap but still have the // same robust safety semantics leaking would give us. A practical alternative that could be // implemented without new compilers would be to define another static root callback in // RootExceptionCallback's destructor (+ a separate pointer to share its value with this // function). Since this would end up getting constructed during exit unwind, it would have the // nice property of effectively being guaranteed to be evicted last. // // All this being said, I came back to leaking the object is the easiest tweak here: // * Can't go wrong // * Easy to maintain // * Throwing exceptions is bound to do be expensive and malloc-happy anyway, so the incremental // cost of 1 heap allocation is minimal. // // TODO(cleanup): Harris has an excellent suggestion in // https://github.com/capnproto/capnproto/pull/1255 that should ensure we initialize the root // callback once on first use as a global & never destroy it. ExceptionCallback* scoped = threadLocalCallback; return scoped != nullptr ? *scoped : *defaultCallback; } void throwFatalException(kj::Exception&& exception, uint ignoreCount) { if (ignoreCount != (uint)kj::maxValue) exception.extendTrace(ignoreCount + 1); getExceptionCallback().onFatalException(kj::mv(exception)); abort(); } void throwRecoverableException(kj::Exception&& exception, uint ignoreCount) { if (ignoreCount != (uint)kj::maxValue) exception.extendTrace(ignoreCount + 1); getExceptionCallback().onRecoverableException(kj::mv(exception)); } // ======================================================================================= namespace _ { // private #if KJ_CPP_STD >= 201703L uint uncaughtExceptionCount() { return std::uncaught_exceptions(); } #elif __GNUC__ // Horrible -- but working -- hack: We can dig into __cxa_get_globals() in order to extract the // count of uncaught exceptions. This function is part of the C++ ABI implementation used on Linux, // OSX, and probably other platforms that use GCC. Unfortunately, __cxa_get_globals() is only // actually defined in cxxabi.h on some platforms (e.g. Linux, but not OSX), and even where it is // defined, it returns an incomplete type. Here we use the same hack used by Evgeny Panasyuk: // https://github.com/panaseleus/stack_unwinding/blob/master/boost/exception/uncaught_exception_count.hpp // // Notice that a similar hack is possible on MSVC -- if its C++11 support ever gets to the point of // supporting KJ in the first place. // // It appears likely that a future version of the C++ standard may include an // uncaught_exception_count() function in the standard library, or an equivalent language feature. // Some discussion: // https://groups.google.com/a/isocpp.org/d/msg/std-proposals/HglEslyZFYs/kKdu5jJw5AgJ struct FakeEhGlobals { // Fake void* caughtExceptions; uint uncaughtExceptions; }; // LLVM's libstdc++ doesn't declare __cxa_get_globals in its cxxabi.h. GNU does. Because it is // extern "C", the compiler wills get upset if we re-declare it even in a different namespace. #if _LIBCPPABI_VERSION extern "C" void* __cxa_get_globals(); #else using abi::__cxa_get_globals; #endif uint uncaughtExceptionCount() { return reinterpret_cast(__cxa_get_globals())->uncaughtExceptions; } #else #error "This needs to be ported to your compiler / C++ ABI." #endif } // namespace _ (private) UnwindDetector::UnwindDetector(): uncaughtCount(_::uncaughtExceptionCount()) {} bool UnwindDetector::isUnwinding() const { return _::uncaughtExceptionCount() > uncaughtCount; } #if !KJ_NO_EXCEPTIONS void UnwindDetector::catchThrownExceptionAsSecondaryFault() const { // TODO(someday): Attach the secondary exception to whatever primary exception is causing // the unwind. For now we just drop it on the floor as this is probably fine most of the // time. getCaughtExceptionAsKj(); } #endif #if __GNUC__ && !KJ_NO_RTTI static kj::String demangleTypeName(const char* name) { if (name == nullptr) return kj::heapString("(nil)"); int status; char* buf = abi::__cxa_demangle(name, nullptr, nullptr, &status); kj::String result = kj::heapString(buf == nullptr ? name : buf); free(buf); return kj::mv(result); } kj::String getCaughtExceptionType() { return demangleTypeName(abi::__cxa_current_exception_type()->name()); } #else kj::String getCaughtExceptionType() { return kj::heapString("(unknown)"); } #endif #if KJ_NO_EXCEPTIONS namespace _ { // private class RecoverableExceptionCatcher: public ExceptionCallback { // Catches a recoverable exception without using try/catch. Used when compiled with // -fno-exceptions. public: virtual ~RecoverableExceptionCatcher() noexcept(false) {} void onRecoverableException(Exception&& exception) override { if (caught == nullptr) { caught = mv(exception); } else { // TODO(someday): Consider it a secondary fault? } } Maybe caught; }; Maybe runCatchingExceptions(Runnable& runnable) { RecoverableExceptionCatcher catcher; runnable.run(); KJ_IF_MAYBE(e, catcher.caught) { e->truncateCommonTrace(); } return mv(catcher.caught); } } // namespace _ (private) #else // KJ_NO_EXCEPTIONS kj::Exception getCaughtExceptionAsKj() { try { throw; } catch (Exception& e) { e.truncateCommonTrace(); return kj::mv(e); } catch (CanceledException) { throw; } catch (std::bad_alloc& e) { return Exception(Exception::Type::OVERLOADED, "(unknown)", -1, str("std::bad_alloc: ", e.what())); } catch (std::exception& e) { return Exception(Exception::Type::FAILED, "(unknown)", -1, str("std::exception: ", e.what())); } catch (...) { #if __GNUC__ && !KJ_NO_RTTI return Exception(Exception::Type::FAILED, "(unknown)", -1, str( "unknown non-KJ exception of type: ", getCaughtExceptionType())); #else return Exception(Exception::Type::FAILED, "(unknown)", -1, str("unknown non-KJ exception")); #endif } } #endif // !KJ_NO_EXCEPTIONS } // namespace kj