Commit af6bb032 authored by Kenton Varda's avatar Kenton Varda

Add mechanism for catching exceptions that has limited use even with…

Add mechanism for catching exceptions that has limited use even with -fno-exceptions, as well as a mechanism to detect when a destructor is called during unwind and to avoid throwing in these situations.
parent c75d4bda
......@@ -509,11 +509,15 @@ TEST(Encoding, ListUpgrade) {
EXPECT_EQ(56u, l[2].getF());
}
try {
reader.getObjectField<List<uint32_t>>();
ADD_FAILURE() << "Expected exception.";
} catch (const kj::Exception& e) {
// expected
{
kj::Maybe<kj::Exception> e = kj::runCatchingExceptions([&]() {
reader.getObjectField<List<uint32_t>>();
#if !KJ_NO_EXCEPTIONS
ADD_FAILURE() << "Should have thrown an exception.";
#endif
});
EXPECT_TRUE(e != nullptr) << "Should have thrown an exception.";
}
{
......@@ -581,11 +585,15 @@ TEST(Encoding, BitListUpgrade) {
auto reader = root.asReader();
try {
reader.getObjectField<List<uint8_t>>();
ADD_FAILURE() << "Expected exception.";
} catch (const kj::Exception& e) {
// expected
{
kj::Maybe<kj::Exception> e = kj::runCatchingExceptions([&]() {
reader.getObjectField<List<uint8_t>>();
#if !KJ_NO_EXCEPTIONS
ADD_FAILURE() << "Should have thrown an exception.";
#endif
});
EXPECT_TRUE(e != nullptr) << "Should have thrown an exception.";
}
{
......
......@@ -34,7 +34,7 @@
namespace capnp {
MessageReader::MessageReader(ReaderOptions options): options(options), allocatedArena(false) {}
MessageReader::~MessageReader() {
MessageReader::~MessageReader() noexcept(false) {
if (allocatedArena) {
arena()->~ReaderArena();
}
......
......@@ -84,7 +84,7 @@ public:
// default value of "ReaderOptions()". The base class constructor doesn't have a default value
// in order to remind subclasses that they really need to give the user a way to provide this.
virtual ~MessageReader();
virtual ~MessageReader() noexcept(false);
virtual kj::ArrayPtr<const word> getSegment(uint id) = 0;
// Gets the segment with the given ID, or returns null if no such segment exists.
......
......@@ -138,17 +138,11 @@ SnappyOutputStream::SnappyOutputStream(
this->compressedBuffer = compressedBuffer;
}
SnappyOutputStream::~SnappyOutputStream() {
SnappyOutputStream::~SnappyOutputStream() noexcept(false) {
if (bufferPos > buffer.begin()) {
if (std::uncaught_exception()) {
try {
flush();
} catch (...) {
// TODO(someday): report secondary faults
}
} else {
unwindDetector.catchExceptionsIfUnwinding([&]() {
flush();
}
});
}
}
......
......@@ -60,7 +60,7 @@ public:
kj::ArrayPtr<byte> buffer = nullptr,
kj::ArrayPtr<byte> compressedBuffer = nullptr);
KJ_DISALLOW_COPY(SnappyOutputStream);
~SnappyOutputStream();
~SnappyOutputStream() noexcept(false);
void flush();
// Force the stream to write any remaining bytes in its buffer to the inner stream. This will
......@@ -79,6 +79,8 @@ private:
kj::Array<byte> ownedCompressedBuffer;
kj::ArrayPtr<byte> compressedBuffer;
kj::UnwindDetector unwindDetector;
};
class SnappyPackedMessageReader: private SnappyInputStream, public PackedMessageReader {
......
......@@ -297,12 +297,14 @@ TEST(Serialize, RejectTooManySegments) {
}
TestInputStream input(data.asPtr(), false);
try {
kj::Maybe<kj::Exception> e = kj::runCatchingExceptions([&]() {
InputStreamMessageReader reader(input);
#if !KJ_NO_EXCEPTIONS
ADD_FAILURE() << "Should have thrown an exception.";
} catch (...) {
// expected
}
#endif
});
EXPECT_TRUE(e != nullptr) << "Should have thrown an exception.";
}
TEST(Serialize, RejectHugeMessage) {
......@@ -315,12 +317,14 @@ TEST(Serialize, RejectHugeMessage) {
ReaderOptions options;
options.traversalLimitInWords = 2;
try {
kj::Maybe<kj::Exception> e = kj::runCatchingExceptions([&]() {
InputStreamMessageReader reader(input, options);
#if !KJ_NO_EXCEPTIONS
ADD_FAILURE() << "Should have thrown an exception.";
} catch (...) {
// expected
}
#endif
});
EXPECT_TRUE(e != nullptr) << "Should have thrown an exception.";
}
// TODO(test): Test error cases.
......
......@@ -197,21 +197,14 @@ InputStreamMessageReader::InputStreamMessageReader(
}
}
InputStreamMessageReader::~InputStreamMessageReader() {
InputStreamMessageReader::~InputStreamMessageReader() noexcept(false) {
if (readPos != nullptr) {
// Note that lazy reads only happen when we have multiple segments, so moreSegments.back() is
// valid.
const byte* allEnd = reinterpret_cast<const byte*>(moreSegments.back().end());
if (std::uncaught_exception()) {
try {
inputStream.skip(allEnd - readPos);
} catch (...) {
// TODO(someday): Devise some way to report secondary errors during unwind.
}
} else {
unwindDetector.catchExceptionsIfUnwinding([&]() {
// Note that lazy reads only happen when we have multiple segments, so moreSegments.back() is
// valid.
const byte* allEnd = reinterpret_cast<const byte*>(moreSegments.back().end());
inputStream.skip(allEnd - readPos);
}
});
}
}
......@@ -267,7 +260,7 @@ void writeMessage(kj::OutputStream& output, kj::ArrayPtr<const kj::ArrayPtr<cons
}
// =======================================================================================
StreamFdMessageReader::~StreamFdMessageReader() {}
StreamFdMessageReader::~StreamFdMessageReader() noexcept(false) {}
void writeMessageToFd(int fd, kj::ArrayPtr<const kj::ArrayPtr<const word>> segments) {
kj::FdOutputStream stream(fd);
......
......@@ -77,7 +77,7 @@ public:
InputStreamMessageReader(kj::InputStream& inputStream,
ReaderOptions options = ReaderOptions(),
kj::ArrayPtr<word> scratchSpace = nullptr);
~InputStreamMessageReader();
~InputStreamMessageReader() noexcept(false);
// implements MessageReader ----------------------------------------
kj::ArrayPtr<const word> getSegment(uint id) override;
......@@ -92,6 +92,8 @@ private:
kj::Array<word> ownedSpace;
// Only if scratchSpace wasn't big enough.
kj::UnwindDetector unwindDetector;
};
void writeMessage(kj::OutputStream& output, MessageBuilder& builder);
......@@ -118,7 +120,7 @@ public:
: FdInputStream(kj::mv(fd)), InputStreamMessageReader(*this, options, scratchSpace) {}
// Read a message from a file descriptor, taking ownership of the descriptor.
~StreamFdMessageReader();
~StreamFdMessageReader() noexcept(false);
};
void writeMessageToFd(int fd, MessageBuilder& builder);
......
......@@ -85,7 +85,7 @@ std::string fileLine(std::string file, int line) {
return file;
}
TEST(Logging, Log) {
TEST(Debug, Log) {
MockExceptionCallback mockCallback;
int line;
......@@ -156,19 +156,23 @@ TEST(Logging, Log) {
mockCallback.text.clear();
}
TEST(Logging, Catch) {
TEST(Debug, Catch) {
int line;
// Catch as kj::Exception.
try {
Maybe<Exception> exception = kj::runCatchingExceptions([&](){
line = __LINE__; KJ_FAIL_ASSERT("foo");
ADD_FAILURE() << "Expected exception.";
} catch (const Exception& e) {
String what = str(e);
});
KJ_IF_MAYBE(e, exception) {
String what = str(*e);
std::string text(what.cStr(), strchr(what.cStr(), '\n') - what.cStr());
EXPECT_EQ(fileLine(__FILE__, line) + ": bug in code: foo", text);
} else {
ADD_FAILURE() << "Expected exception.";
}
#if !KJ_NO_EXCEPTIONS
// Catch as std::exception.
try {
line = __LINE__; KJ_FAIL_ASSERT("foo");
......@@ -178,9 +182,10 @@ TEST(Logging, Catch) {
std::string text(what, strchr(what, '\n') - what);
EXPECT_EQ(fileLine(__FILE__, line) + ": bug in code: foo", text);
}
#endif
}
TEST(Logging, Syscall) {
TEST(Debug, Syscall) {
MockExceptionCallback mockCallback;
int line;
......@@ -204,7 +209,7 @@ TEST(Logging, Syscall) {
EXPECT_TRUE(recovered);
}
TEST(Logging, Context) {
TEST(Debug, Context) {
MockExceptionCallback mockCallback;
{
......@@ -248,11 +253,6 @@ TEST(Logging, Context) {
}
}
TEST(Logging, ExceptionCallbackMustBeOnStack) {
// TODO(cleanup): Put in exception-test.c++, when it exists.
EXPECT_ANY_THROW(new ExceptionCallback);
}
} // namespace
} // namespace _ (private)
} // namespace kj
......@@ -29,6 +29,10 @@
#include <stdlib.h>
#include <exception>
#if __GNUC__
#include <cxxabi.h>
#endif
#if defined(__linux__) && !defined(NDEBUG)
#include <stdio.h>
#endif
......@@ -293,9 +297,7 @@ public:
}
void logMessage(const char* file, int line, int contextDepth, String&& text) override {
if (contextDepth > 0) {
text = str(RepeatChar('_', contextDepth), mv(text));
}
text = str(RepeatChar('_', contextDepth), file, ":", line, ": ", mv(text));
StringPtr textPtr = text;
......@@ -329,4 +331,84 @@ ExceptionCallback& getExceptionCallback() {
return scoped != nullptr ? *scoped : defaultCallback;
}
// =======================================================================================
namespace {
#if __GNUC__
// __cxa_eh_globals does not appear to be defined in a visible header. This is what it looks like.
struct DummyEhGlobals {
abi::__cxa_exception* caughtExceptions;
uint uncaughtExceptions;
};
uint uncaughtExceptionCount() {
// TODO(perf): Use __cxa_get_globals_fast()? Requires that __cxa_get_globals() has been called
// from somewhere.
return reinterpret_cast<DummyEhGlobals*>(abi::__cxa_get_globals())->uncaughtExceptions;
}
#else
#error "This needs to be ported to your compiler / C++ ABI."
#endif
} // namespace
UnwindDetector::UnwindDetector(): uncaughtCount(uncaughtExceptionCount()) {}
bool UnwindDetector::isUnwinding() const {
return uncaughtExceptionCount() > uncaughtCount;
}
void UnwindDetector::catchExceptionsAsSecondaryFaults(_::Runnable& runnable) 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.
runCatchingExceptions(runnable);
}
namespace _ { // private
class RecoverableExceptionCatcher: public ExceptionCallback {
// Catches a recoverable exception without using try/catch. Used when compiled with
// -fno-exceptions.
public:
virtual ~RecoverableExceptionCatcher() {}
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) {
#if KJ_NO_EXCEPTIONS
RecoverableExceptionCatcher catcher;
runnable.run();
return mv(catcher.caught);
#else
try {
runnable.run();
return nullptr;
} catch (Exception& e) {
return kj::mv(e);
} catch (std::exception& e) {
return Exception(Exception::Nature::OTHER, Exception::Durability::PERMANENT,
"(unknown)", -1, str("std::exception: ", e.what()));
} catch (...) {
return Exception(Exception::Nature::OTHER, Exception::Durability::PERMANENT,
"(unknown)", -1, str("Unknown non-KJ exception."));
}
#endif
}
} // namespace _ (private)
} // namespace kj
......@@ -77,7 +77,7 @@ public:
int getLine() const { return line; }
Nature getNature() const { return nature; }
Durability getDurability() const { return durability; }
ArrayPtr<const char> getDescription() const { return description; }
StringPtr getDescription() const { return description; }
ArrayPtr<void* const> getStackTrace() const { return arrayPtr(trace, traceCount); }
struct Context {
......@@ -123,6 +123,8 @@ ArrayPtr<const char> KJ_STRINGIFY(Exception::Nature nature);
ArrayPtr<const char> KJ_STRINGIFY(Exception::Durability durability);
String KJ_STRINGIFY(const Exception& e);
// =======================================================================================
class ExceptionCallback {
// If you don't like C++ exceptions, you may implement and register an ExceptionCallback in order
// to perform your own exception handling. For example, a reasonable thing to do is to have
......@@ -177,6 +179,86 @@ private:
ExceptionCallback& getExceptionCallback();
// Returns the current exception callback.
// =======================================================================================
namespace _ { class Runnable; }
template <typename Func>
Maybe<Exception> runCatchingExceptions(Func&& func);
// Executes the given function (usually, a lambda returning nothing) catching any exceptions that
// are thrown. Returns the Exception if there was one, or null if the operation completed normally.
// Non-KJ exceptions will be wrapped.
//
// If exception are disabled (e.g. with -fno-exceptions), this will still detect whether any
// recoverable exceptions occurred while running the function and will return those.
class UnwindDetector {
// Utility for detecting when a destructor is called due to unwind. Useful for:
// - Avoiding throwing exceptions in this case, which would terminate the program.
// - Detecting whether to commit or roll back a transaction.
//
// To use this class, either inherit privately from it or declare it as a member. The detector
// works by comparing the exception state against that when the constructor was called, so for
// an object that was actually constructed during exception unwind, it will behave as if no
// unwind is taking place. This is usually the desired behavior.
public:
UnwindDetector();
bool isUnwinding() const;
// Returns true if the current thread is in a stack unwind that it wasn't in at the time the
// object was constructed.
template <typename Func>
void catchExceptionsIfUnwinding(Func&& func) const;
// Runs the given function (e.g., a lambda). If isUnwinding() is true, any exceptions are
// caught and treated as secondary faults, meaning they are considered to be side-effects of the
// exception that is unwinding the stack. Otherwise, exceptions are passed through normally.
private:
uint uncaughtCount;
void catchExceptionsAsSecondaryFaults(_::Runnable& runnable) const;
};
namespace _ { // private
class Runnable {
public:
virtual void run() = 0;
};
template <typename Func>
class RunnableImpl: public Runnable {
public:
RunnableImpl(Func&& func): func(kj::mv(func)) {}
void run() override {
func();
}
private:
Func func;
};
Maybe<Exception> runCatchingExceptions(Runnable& runnable);
} // namespace _ (private)
template <typename Func>
Maybe<Exception> runCatchingExceptions(Func&& func) {
_::RunnableImpl<Decay<Func>> runnable(kj::fwd<Func>(func));
return _::runCatchingExceptions(runnable);
}
template <typename Func>
void UnwindDetector::catchExceptionsIfUnwinding(Func&& func) const {
if (isUnwinding()) {
_::RunnableImpl<Decay<Func>> runnable(kj::fwd<Func>(func));
catchExceptionsAsSecondaryFaults(runnable);
} else {
func();
}
}
} // namespace kj
#endif // KJ_EXCEPTION_H_
......@@ -30,10 +30,10 @@
namespace kj {
InputStream::~InputStream() {}
OutputStream::~OutputStream() {}
BufferedInputStream::~BufferedInputStream() {}
BufferedOutputStream::~BufferedOutputStream() {}
InputStream::~InputStream() noexcept(false) {}
OutputStream::~OutputStream() noexcept(false) {}
BufferedInputStream::~BufferedInputStream() noexcept(false) {}
BufferedOutputStream::~BufferedOutputStream() noexcept(false) {}
void InputStream::skip(size_t bytes) {
char scratch[8192];
......@@ -56,7 +56,7 @@ BufferedInputStreamWrapper::BufferedInputStreamWrapper(InputStream& inner, Array
: inner(inner), ownedBuffer(buffer == nullptr ? heapArray<byte>(8192) : nullptr),
buffer(buffer == nullptr ? ownedBuffer : buffer) {}
BufferedInputStreamWrapper::~BufferedInputStreamWrapper() {}
BufferedInputStreamWrapper::~BufferedInputStreamWrapper() noexcept(false) {}
ArrayPtr<const byte> BufferedInputStreamWrapper::getReadBuffer() {
if (bufferAvailable.size() == 0) {
......@@ -123,18 +123,10 @@ BufferedOutputStreamWrapper::BufferedOutputStreamWrapper(OutputStream& inner, Ar
buffer(buffer == nullptr ? ownedBuffer : buffer),
bufferPos(this->buffer.begin()) {}
BufferedOutputStreamWrapper::~BufferedOutputStreamWrapper() {
if (bufferPos > buffer.begin()) {
if (std::uncaught_exception()) {
try {
inner.write(buffer.begin(), bufferPos - buffer.begin());
} catch (...) {
// TODO(someday): Report secondary faults.
}
} else {
flush();
}
}
BufferedOutputStreamWrapper::~BufferedOutputStreamWrapper() noexcept(false) {
unwindDetector.catchExceptionsIfUnwinding([&]() {
flush();
});
}
void BufferedOutputStreamWrapper::flush() {
......@@ -229,15 +221,18 @@ void ArrayOutputStream::write(const void* src, size_t size) {
// =======================================================================================
AutoCloseFd::~AutoCloseFd() {
if (fd >= 0 && close(fd) < 0) {
FAIL_SYSCALL("close", errno, fd) {
break;
AutoCloseFd::~AutoCloseFd() noexcept(false) {
unwindDetector.catchExceptionsIfUnwinding([&]() {
// Don't use SYSCALL() here because close() should not be repeated on EINTR.
if (fd >= 0 && close(fd) < 0) {
FAIL_SYSCALL("close", errno, fd) {
break;
}
}
}
});
}
FdInputStream::~FdInputStream() {}
FdInputStream::~FdInputStream() noexcept(false) {}
size_t FdInputStream::read(void* buffer, size_t minBytes, size_t maxBytes) {
byte* pos = reinterpret_cast<byte*>(buffer);
......@@ -256,7 +251,7 @@ size_t FdInputStream::read(void* buffer, size_t minBytes, size_t maxBytes) {
return pos - reinterpret_cast<byte*>(buffer);
}
FdOutputStream::~FdOutputStream() {}
FdOutputStream::~FdOutputStream() noexcept(false) {}
void FdOutputStream::write(const void* buffer, size_t size) {
const char* pos = reinterpret_cast<const char*>(buffer);
......
......@@ -27,6 +27,7 @@
#include <stddef.h>
#include "common.h"
#include "array.h"
#include "exception.h"
namespace kj {
......@@ -35,7 +36,7 @@ namespace kj {
class InputStream {
public:
virtual ~InputStream();
virtual ~InputStream() noexcept(false);
virtual size_t read(void* buffer, size_t minBytes, size_t maxBytes) = 0;
// Reads at least minBytes and at most maxBytes, copying them into the given buffer. Returns
......@@ -63,7 +64,7 @@ public:
class OutputStream {
public:
virtual ~OutputStream();
virtual ~OutputStream() noexcept(false);
virtual void write(const void* buffer, size_t size) = 0;
// Always writes the full size. Throws exception on error.
......@@ -81,7 +82,7 @@ class BufferedInputStream: public InputStream {
// caller a direct pointer to that memory to potentially avoid a copy.
public:
virtual ~BufferedInputStream();
virtual ~BufferedInputStream() noexcept(false);
virtual ArrayPtr<const byte> getReadBuffer() = 0;
// Get a direct pointer into the read buffer, which contains the next bytes in the input. If the
......@@ -96,7 +97,7 @@ class BufferedOutputStream: public OutputStream {
// caller a direct pointer to that memory to potentially avoid a copy.
public:
virtual ~BufferedOutputStream();
virtual ~BufferedOutputStream() noexcept(false);
virtual ArrayPtr<byte> getWriteBuffer() = 0;
// Get a direct pointer into the write buffer. The caller may choose to fill in some prefix of
......@@ -126,7 +127,7 @@ public:
// its own. This may improve performance if the buffer can be reused.
KJ_DISALLOW_COPY(BufferedInputStreamWrapper);
~BufferedInputStreamWrapper();
~BufferedInputStreamWrapper() noexcept(false);
// implements BufferedInputStream ----------------------------------
ArrayPtr<const byte> getReadBuffer() override;
......@@ -152,7 +153,7 @@ public:
// its own. This may improve performance if the buffer can be reused.
KJ_DISALLOW_COPY(BufferedOutputStreamWrapper);
~BufferedOutputStreamWrapper();
~BufferedOutputStreamWrapper() noexcept(false);
void flush();
// Force the wrapper to write any remaining bytes in its buffer to the inner stream. Note that
......@@ -168,6 +169,7 @@ private:
Array<byte> ownedBuffer;
ArrayPtr<byte> buffer;
byte* bufferPos;
UnwindDetector unwindDetector;
};
// =======================================================================================
......@@ -226,7 +228,7 @@ public:
inline explicit AutoCloseFd(int fd): fd(fd) {}
inline AutoCloseFd(AutoCloseFd&& other): fd(other.fd) { other.fd = -1; }
KJ_DISALLOW_COPY(AutoCloseFd);
~AutoCloseFd();
~AutoCloseFd() noexcept(false);
inline operator int() { return fd; }
inline int get() { return fd; }
......@@ -236,6 +238,7 @@ public:
private:
int fd;
UnwindDetector unwindDetector;
};
class FdInputStream: public InputStream {
......@@ -245,7 +248,7 @@ public:
explicit FdInputStream(int fd): fd(fd) {};
explicit FdInputStream(AutoCloseFd fd): fd(fd), autoclose(mv(fd)) {}
KJ_DISALLOW_COPY(FdInputStream);
~FdInputStream();
~FdInputStream() noexcept(false);
size_t read(void* buffer, size_t minBytes, size_t maxBytes) override;
......@@ -261,7 +264,7 @@ public:
explicit FdOutputStream(int fd): fd(fd) {};
explicit FdOutputStream(AutoCloseFd fd): fd(fd), autoclose(mv(fd)) {}
KJ_DISALLOW_COPY(FdOutputStream);
~FdOutputStream();
~FdOutputStream() noexcept(false);
void write(const void* buffer, size_t size) override;
void write(ArrayPtr<const ArrayPtr<const byte>> pieces) override;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment