blob: c0c21f071729787c1e1b6c92f2faae5421351eb3 [file] [log] [blame]
// Copyright (c) 2013, Kenton Varda <[email protected]>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef KJ_EXCEPTION_H_
#define KJ_EXCEPTION_H_
#include "memory.h"
#include "array.h"
#include "string.h"
namespace kj {
class ExceptionImpl;
class Exception {
// Exception thrown in case of fatal errors.
//
// Actually, a subclass of this which also implements std::exception will be thrown, but we hide
// that fact from the interface to avoid #including <exception>.
#ifdef __CDT_PARSER__
// For some reason Eclipse gets confused by the definition of Nature if it's the first thing
// in the class.
typedef void WorkAroundCdtBug;
#endif
public:
enum class Nature {
// What kind of failure? This is informational, not intended for programmatic use.
// Note that the difference between some of these failure types is not always clear. For
// example, a precondition failure may be due to a "local bug" in the calling code, or it
// may be due to invalid input.
PRECONDITION,
LOCAL_BUG,
OS_ERROR,
NETWORK_FAILURE,
OTHER
// Make sure to update the stringifier if you add a new nature.
};
enum class Durability {
TEMPORARY, // Retrying the exact same operation might succeed.
PERMANENT // Retrying the exact same operation will fail in exactly the same way.
// Make sure to update the stringifier if you add a new durability.
};
Exception(Nature nature, Durability durability, const char* file, int line,
String description = nullptr) noexcept;
Exception(Nature nature, Durability durability, String file, int line,
String description = nullptr) noexcept;
Exception(const Exception& other) noexcept;
Exception(Exception&& other) = default;
~Exception() noexcept;
const char* getFile() const { return file; }
int getLine() const { return line; }
Nature getNature() const { return nature; }
Durability getDurability() const { return durability; }
StringPtr getDescription() const { return description; }
ArrayPtr<void* const> getStackTrace() const { return arrayPtr(trace, traceCount); }
struct Context {
// Describes a bit about what was going on when the exception was thrown.
const char* file;
int line;
String description;
Maybe<Own<Context>> next;
Context(const char* file, int line, String&& description, Maybe<Own<Context>>&& next)
: file(file), line(line), description(mv(description)), next(mv(next)) {}
Context(const Context& other) noexcept;
};
inline Maybe<const Context&> getContext() const {
KJ_IF_MAYBE(c, context) {
return *c;
} else {
return nullptr;
}
}
void wrapContext(const char* file, int line, String&& description);
// Wraps the context in a new node. This becomes the head node returned by getContext() -- it
// is expected that contexts will be added in reverse order as the exception passes up the
// callback stack.
private:
String ownFile;
const char* file;
int line;
Nature nature;
Durability durability;
String description;
Maybe<Own<Context>> context;
void* trace[16];
uint traceCount;
friend class ExceptionImpl;
};
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
// onRecoverableException() set a flag indicating that an error occurred, and then check for that
// flag just before writing to storage and/or returning results to the user. If the flag is set,
// discard whatever you have and return an error instead.
//
// ExceptionCallbacks must always be allocated on the stack. When an exception is thrown, the
// newest ExceptionCallback on the calling thread's stack is called. The default implementation
// of each method calls the next-oldest ExceptionCallback for that thread. Thus the callbacks
// behave a lot like try/catch blocks, except that they are called before any stack unwinding
// occurs.
public:
ExceptionCallback();
KJ_DISALLOW_COPY(ExceptionCallback);
virtual ~ExceptionCallback() noexcept(false);
virtual void onRecoverableException(Exception&& exception);
// Called when an exception has been raised, but the calling code has the ability to continue by
// producing garbage output. This method _should_ throw the exception, but is allowed to simply
// return if garbage output is acceptable.
//
// The global default implementation throws an exception unless the library was compiled with
// -fno-exceptions, in which case it logs an error and returns.
virtual void onFatalException(Exception&& exception);
// Called when an exception has been raised and the calling code cannot continue. If this method
// returns normally, abort() will be called. The method must throw the exception to avoid
// aborting.
//
// The global default implementation throws an exception unless the library was compiled with
// -fno-exceptions, in which case it logs an error and returns.
virtual void logMessage(const char* file, int line, int contextDepth, String&& text);
// Called when something wants to log some debug text. The text always ends in a newline if
// it is non-empty. `contextDepth` indicates how many levels of context the message passed
// through; it may make sense to indent the message accordingly.
//
// The global default implementation writes the text to stderr.
protected:
ExceptionCallback& next;
private:
ExceptionCallback(ExceptionCallback& next);
class RootExceptionCallback;
friend ExceptionCallback& getExceptionCallback();
};
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();
}
}
#define KJ_ON_SCOPE_SUCCESS(code) \
::kj::UnwindDetector KJ_UNIQUE_NAME(_kjUnwindDetector); \
KJ_DEFER(if (!KJ_UNIQUE_NAME(_kjUnwindDetector).isUnwinding()) { code; })
// Runs `code` if the current scope is exited normally (not due to an exception).
#define KJ_ON_SCOPE_FAILURE(code) \
::kj::UnwindDetector KJ_UNIQUE_NAME(_kjUnwindDetector); \
KJ_DEFER(if (KJ_UNIQUE_NAME(_kjUnwindDetector).isUnwinding()) { code; })
// Runs `code` if the current scope is exited due to an exception.
} // namespace kj
#endif // KJ_EXCEPTION_H_