823 lines
28 KiB
C++
823 lines
28 KiB
C++
// 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 <stdlib.h>
|
|
#include <exception>
|
|
#include <new>
|
|
#include <stdint.h>
|
|
|
|
#if !KJ_NO_RTTI
|
|
#include <typeinfo>
|
|
#endif
|
|
#if __GNUC__
|
|
#include <cxxabi.h>
|
|
#endif
|
|
|
|
#ifndef KJ_USE_BACKTRACE
|
|
#if (__linux__ && __GLIBC__ && !__UCLIBC__) || __APPLE__
|
|
#define KJ_USE_BACKTRACE 1
|
|
#endif
|
|
#endif
|
|
|
|
#if KJ_USE_BACKTRACE
|
|
#include <execinfo.h>
|
|
#endif
|
|
|
|
|
|
#if (__linux__ || __APPLE__)
|
|
#include <stdio.h>
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
|
|
#if KJ_HAS_LIBDL
|
|
#include "dlfcn.h"
|
|
#endif
|
|
|
|
|
|
#if KJ_HAS_COMPILER_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
|
|
#include <sanitizer/lsan_interface.h>
|
|
#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 <typename T>
|
|
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<uint>(severity)];
|
|
}
|
|
|
|
|
|
ArrayPtr<void* const> getStackTrace(ArrayPtr<void*> 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<void*>(reinterpret_cast<uintptr_t>(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<void* const> 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<void* const> 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<uintptr_t>(addr) >= 0x400000000000ull && dladdr(addr, &info)) {
|
|
uintptr_t offset = reinterpret_cast<uintptr_t>(addr) -
|
|
reinterpret_cast<uintptr_t>(info.dli_fbase);
|
|
return kj::str(info.dli_fname, '@', reinterpret_cast<void*>(offset));
|
|
} else {
|
|
return kj::str(addr);
|
|
}
|
|
}, " ");
|
|
#else
|
|
// TODO(someday): Support other platforms.
|
|
return kj::strArray(trace, " ");
|
|
#endif
|
|
}
|
|
|
|
StringPtr stringifyStackTraceAddresses(ArrayPtr<void* const> trace, ArrayPtr<char> 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<uintptr_t>(addr) >= 0x400000000000ull && dladdr(addr, &info)) {
|
|
uintptr_t offset = reinterpret_cast<uintptr_t>(addr) -
|
|
reinterpret_cast<uintptr_t>(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<uint>(type)];
|
|
}
|
|
|
|
String KJ_STRINGIFY(const Exception& e) {
|
|
uint contextDepth = 0;
|
|
|
|
Maybe<const Exception::Context&> contextPtr = e.getContext();
|
|
for (;;) {
|
|
KJ_IF_MAYBE(c, contextPtr) {
|
|
++contextDepth;
|
|
contextPtr = c->next;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Array<String> contextText = heapArray<String>(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<Context>(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<char*>(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<FakeEhGlobals*>(__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<Exception> caught;
|
|
};
|
|
|
|
Maybe<Exception> 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
|