Index: docs/ProgrammersManual.rst =================================================================== --- docs/ProgrammersManual.rst +++ docs/ProgrammersManual.rst @@ -263,8 +263,176 @@ when defining a function which should be able to efficiently accept concatenated strings. +.. _error_apis: + +Error handling +-------------- + +Proper error handling helps us identify bugs in our code, and helps end-users +understand errors in their tool usage. Errors fall into two broad categories: +*programmatic* and *recoverable*, with different strategies for handling and +reporting. + +Programmatic Errors +^^^^^^^^^^^^^^^^^^^ + +Programmatic errors are violations of program invariants or API contracts, and +represent bugs within the program itself. Our aim is to document invariants, and +to abort quickly at the point of failure (providing some basic diagnostic) when +invariants are broken at runtime. + +The fundamental tools for handling programmatic errors are assertions and the +llvm_unreachable function. Assertions are used to express invariant conditions, +and should include a message describing the invariant: + +.. code-block:: c++ + + assert(isPhysReg(R) && "All virt regs should have been allocated already."); + +The llvm_unreachable function can be used to document areas of control flow +that should never be entered if the program invariants hold: + +.. code-block:: c++ + + enum { Foo, Bar, Baz } X = foo(); + + switch (X) { + case Foo: /* Handle Foo */; break; + case Bar: /* Handle Bar */; break; + default: + llvm_unreachable("X should be Foo or Bar here"); + } + +Recoverable Errors +^^^^^^^^^^^^^^^^^^ + +Recoverable errors represent an error in the program's environment, for example +a resource failure (a missing file, a dropped network connection, etc.), or +malformed input. These errors should be detected and communicated to a level of +the program where they can be handled appropriately. Handling the error may be +as simple as reporting the issue to the user, or it may involve attempts at +recovery. + +Recoverable errors are modeled using LLVM's ``Error`` scheme. This scheme +represents errors using function return values, similar to classic C integer +error codes, or C++'s ``std::error_code``. However, the ``Error`` class is +actually a lightweight wrapper for user-defined error types, allowing arbitrary +information to be attached to describe the error. This is similar to the way C++ +exceptions allow throwing of user-defined types. + +Success values are created by calling ``Error::success()``: + +.. code-block:: c++ + + Error foo() { + // Do something. + // Return success. + return Error::success(); + } + +Success values are very cheap to construct and return - they have minimal +impact on program performance. + +Failure values are constructed using ``make_error``, where ``T`` is any class +that inherits from the ErrorInfo utility: + +.. code-block:: c++ + + class MyError : public ErrorInfo { + public: + MyError(std::string Msg) : Msg(Msg) {} + void log(OStream &OS) const override { OS << "MyError - " << Msg; } + private: + std::string Msg; + }; + + Error bar() { + if (checkErrorCondition) + return make_error("Error condition detected"); + + // No error - proceed with bar. + + // Return success value. + return Error::success(); + } + +For functions that can fail but need to return a value the ``Expected`` +utility can be used. Values of this type can be constructed with either a +``T``, or a ``Error``. Values are implicitly convertible to boolean: true +for success, false for error. If success, the ``T`` value can be accessed via +the dereference operator. If failure, the ``Error`` value can be extracted +using the ``takeError()`` method: + +.. code-block:: c++ + + Expected parseAndSquareRoot(IStream &IS) { + float f; + OS >> f; + if (f < 0) + return make_error(...); + return sqrt(f); + } + + Error foo(IStream &IS) { + if (auto SqrtOrErr = parseAndSquartRoot(IS)) { + float Sqrt = *SqrtOrErr; + // ... + } else + return SqrtOrErr.takeError(); + } + +All Error instances, whether success or failure, must be either checked or +moved from (via std::move or a return) before they are destructed. Accidentally +discarding an unchecked error will cause a program abort at the point where the +unchecked value's destructor is run, making it easy to identify and fix +violations of this rule. + +Success values are considered checked once they have been tested (by invoking +the boolean conversion operator): + +.. code-block:: c++ + + if (auto Err = canFail(...)) + return Err; // Failure value - move error to caller. + + // Safe to continue: Err was checked. + +In contrast, the following code will always cause an abort, regardless of the +return value of ``foo``: + +.. code-block:: c++ + + canFail(); + // Program will always abort here, even if canFail() returns Success, since + // the value is not checked. + +Failure values are considered checked once a handler for the error type has +been activated: + +.. code-block:: c++ + + auto Err = canFail(...); + if (auto Err2 = + handleErrors(std::move(Err), + [](std::unique_ptr M) { + // Try to handle 'M'. If successful, return a success value from + // the handler. + if (tryToHandle(M)) + return Error::success(); + + // We failed to handle 'M' - return it from the handler. + // This value will be passed back from catchErrors and + // wind up in Err2, where it will be returned from this function. + return Error(std::move(M)); + }))) + return Err2; + + .. _function_apis: +More information on Error and its related utilities can be found in the +Error.h header file. + Passing functions and other callable objects -------------------------------------------- Index: include/llvm/Support/Error.h =================================================================== --- include/llvm/Support/Error.h +++ include/llvm/Support/Error.h @@ -0,0 +1,747 @@ +//===----- llvm/Support/Error.h - Recoverable error handling ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines an API used to report recoverable errors. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_ERROR_H +#define LLVM_SUPPORT_ERROR_H + +#include "llvm/ADT/PointerIntPair.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorOr.h" +#include "llvm/Support/raw_ostream.h" +#include + +namespace llvm { + +class Error; +class ErrorList; + +/// Base class for error info classes. Do not extend this directly: Extend +/// the ErrorInfo template subclass instead. +class ErrorInfoBase { +public: + virtual ~ErrorInfoBase() {} + + /// Print an error message to an output stream. + virtual void log(raw_ostream &OS) const = 0; + + // Check whether this instance is a subclass of the class identified by + // ClassID. + virtual bool isA(const void *const ClassID) const { + return ClassID == classID(); + } + + // Check whether this instance is a subclass of ErrorInfoT. + template bool isA() const { + return isA(ErrorInfoT::classID()); + } + + // Returns the class ID for this type. + static const void *classID() { return &ID; } + +private: + virtual void anchor(); + static char ID; +}; + +/// Lightweight error class with error context and mandatory checking. +/// +/// Instances of this class wrap a ErrorInfoBase pointer. Failure states +/// are represented by setting the pointer to a ErrorInfoBase subclass +/// instance containing information describing the failure. Success is +/// represented by a null pointer value. +/// +/// Instances of Error also contains a 'Checked' flag, which must be set +/// before the destructor is called, otherwise the destructor will trigger a +/// runtime error. This enforces at runtime the requirement that all Error +/// instances be checked or returned to the caller. +/// +/// There are two ways to set the checked flag, depending on what state the +/// Error instance is in. For Error instances indicating success, it +/// is sufficient to invoke the boolean conversion operator. E.g.: +/// +/// Error foo(<...>); +/// +/// if (auto E = foo(<...>)) +/// return E; // <- Return E if it is in the error state. +/// // We have verified that E was in the success state. It can now be safely +/// // destroyed. +/// +/// A success value *can not* be dropped. For example, just calling 'foo(<...>)' +/// without testing the return value will raise a runtime error, even if foo +/// returns success. +/// +/// For Error instances representing failure, you must use the either the +/// handleErrors or handleAllErrors function with a typed handler. E.g.: +/// +/// class MyErrorInfo : public ErrorInfo { +/// // Custom error info. +/// }; +/// +/// Error foo(<...>) { return make_error(...); } +/// +/// auto E = foo(<...>); // <- foo returns failure with MyErrorInfo. +/// auto NewE = +/// checkErrors(E, +/// [](const MyErrorInfo &M) { +/// // Deal with the error. +/// }, +/// [](std::unique_ptr M) -> Error { +/// if (canHandle(*M)) { +/// // handle error. +/// return Error::success(); +/// } +/// // Couldn't handle this error instance. Pass it up the stack. +/// return Error(std::move(M)); +/// ); +/// // Note - we must check or return NewE in case any of the handlers +/// // returned a new error. +/// +/// The handleAllErrors function is identical to handleErrors, except +/// that it has a void return type, and requires all errors to be handled and +/// no new errors be returned. It prevents errors (assuming they can all be +/// handled) from having to be bubbled all the way to the top-level. +/// +/// *All* Error instances must be checked before destruction, even if +/// they're moved-assigned or constructed from Success values that have already +/// been checked. This enforces checking through all levels of the call stack. +class Error { + + // ErrorList needs to be able to yank ErrorInfoBase pointers out of this + // class to add to the error list. + friend class ErrorList; + + // handleErrors needs to be able to set the Checked flag. + template + friend Error handleErrors(Error E, HandlerTs &&... Handlers); + +public: + /// Create a success value. Prefer using 'Error::success()' for readability + /// where possible. + Error() { + setPtr(nullptr); + setChecked(false); + } + + /// Create a success value. This is equivalent to calling the default + /// constructor, but should be preferred for readability where possible. + static Error success() { return Error(); } + + // Errors are not copy-constructable. + Error(const Error &Other) = delete; + + /// Move-construct an error value. The newly constructed error is considered + /// unchecked, even if the source error had been checked. The original error + /// becomes a checked Success value, regardless of its original state. + Error(Error &&Other) { + setChecked(true); + *this = std::move(Other); + } + + /// Create an error value. Prefer using the 'make_error' function, but + /// this constructor can be useful when "re-throwing" errors from handlers. + Error(std::unique_ptr Payload) { + setPtr(Payload.release()); + setChecked(false); + } + + // Errors are not copy-assignable. + Error &operator=(const Error &Other) = delete; + + /// Move-assign an error value. The current error must represent success, you + /// you cannot overwrite an unhandled error. The current error is then + /// considered unchecked. The source error becomes a checked success value, + /// regardless of its original state. + Error &operator=(Error &&Other) { + // Move assignment shouldn't drop an existing error, but we won't complain + // about overwriting success. + assertIsChecked(); + setPtr(Other.getPtr()); + + // This Error is unchecked, even if the source error was checked. + setChecked(false); + + // Null out Other's payload and set its checked bit. + Other.setPtr(nullptr); + Other.setChecked(true); + + return *this; + } + + /// Destroy a Error. Fails with a call to abort() if the error is + /// unchecked. + ~Error() { + assertIsChecked(); + delete getPtr(); + } + + /// Bool conversion. Returns true if this Error is in a failure state, + /// and false if it is in an accept state. If the error is in a Success state + /// it will be considered checked. + explicit operator bool() { + setChecked(getPtr() == nullptr); + return getPtr() != nullptr; + } + + /// Check whether one error is a subclass of another. + template bool isA() const { + return getPtr()->isA(ErrT::classID()); + } + +private: + void assertIsChecked() { +#ifndef NDEBUG + if (!getChecked() || getPtr()) { + dbgs() << "Program aborted due to an unhandled Error:\n"; + if (getPtr()) + getPtr()->log(dbgs()); + else + dbgs() + << "Error value was Success. (Note: Success values must still be " + "checked prior to being destroyed).\n"; + abort(); + } +#endif + } + + ErrorInfoBase *getPtr() const { +#ifndef NDEBUG + return PayloadAndCheckedBit.getPointer(); +#else + return Payload; +#endif + } + + void setPtr(ErrorInfoBase *EI) { +#ifndef NDEBUG + PayloadAndCheckedBit.setPointer(EI); +#else + Payload = EI; +#endif + } + + bool getChecked() const { +#ifndef NDEBUG + return PayloadAndCheckedBit.getInt(); +#else + return true; +#endif + } + + void setChecked(bool V) { +#ifndef NDEBUG + PayloadAndCheckedBit.setInt(V); +#endif + } + + std::unique_ptr takePayload() { + std::unique_ptr Tmp(getPtr()); + setPtr(nullptr); + setChecked(true); + return Tmp; + } + +#ifndef NDEBUG + PointerIntPair PayloadAndCheckedBit; +#else + ErrorInfoBase *Payload; +#endif +}; + +/// Make a Error instance representing failure using the given error info +/// type. +template Error make_error(ArgTs &&... Args) { + return Error(llvm::make_unique(std::forward(Args)...)); +} + +/// Base class for user error types. Users should declare their error types +/// like: +/// +/// class MyError : public ErrorInfo { +/// .... +/// }; +/// +/// This class provides an implementation of the ErrorInfoBase::kind +/// method, which is used by the Error RTTI system. +template +class ErrorInfo : public ParentErrT { +public: + bool isA(const void *const ClassID) const override { + return ClassID == classID() || ParentErrT::isA(ClassID); + } + + static const void *classID() { return &ID; } + +private: + static char ID; +}; + +template +char ErrorInfo::ID = 0; + +/// Special ErrorInfo subclass representing a list of ErrorInfos. +/// Instances of this class are constructed by joinError. +class ErrorList final : public ErrorInfo { + + // handleErrors needs to be able to iterate the payload list of an + // ErrorList. + template + friend Error handleErrors(Error E, HandlerTs &&... Handlers); + + // joinErrors is implemented in terms of join. + friend Error joinErrors(Error, Error); + +public: + void log(raw_ostream &OS) const override { + OS << "Multiple errors:\n"; + for (auto &ErrPayload : Payloads) { + ErrPayload->log(OS); + OS << "\n"; + } + } + +private: + ErrorList(std::unique_ptr Payload1, + std::unique_ptr Payload2) { + assert(!Payload1->isA() && !Payload2->isA() && + "ErrorList constructor payloads should be singleton errors"); + Payloads.push_back(std::move(Payload1)); + Payloads.push_back(std::move(Payload2)); + } + + static Error join(Error E1, Error E2) { + if (!E1) + return E2; + if (!E2) + return E1; + if (E1.isA()) { + auto &E1List = static_cast(*E1.getPtr()); + if (E2.isA()) { + auto &E2List = static_cast(*E2.getPtr()); + for (auto &Payload : E2List.Payloads) + E1List.Payloads.push_back(std::move(Payload)); + } else + E1List.Payloads.push_back(E2.takePayload()); + + return E1; + } + if (E2.isA()) { + auto &E2List = static_cast(*E2.getPtr()); + E2List.Payloads.insert(E2List.Payloads.begin(), E1.takePayload()); + return E2; + } + return Error(std::unique_ptr( + new ErrorList(E1.takePayload(), E2.takePayload()))); + } + + std::vector> Payloads; +}; + +/// Concatenate errors. The resulting Error is unchecked, and contains the +/// ErrorInfo(s), if any, contained in E1, followed by the +/// ErrorInfo(s), if any, contained in E2. +inline Error joinErrors(Error E1, Error E2) { + return ErrorList::join(std::move(E1), std::move(E2)); +} + +/// Helper for testing applicability of, and applying, handlers for +/// ErrorInfo types. +template +class ErrorHandlerTraits + : public ErrorHandlerTraits::type::operator())> {}; + +// Specialization functions of the form 'Error (const ErrT&)'. +template class ErrorHandlerTraits { +public: + static bool appliesTo(const ErrorInfoBase &E) { + return E.template isA(); + } + + template + static Error apply(HandlerT &&H, std::unique_ptr E) { + assert(appliesTo(*E) && "Applying incorrect handler"); + return H(static_cast(*E)); + } +}; + +// Specialization functions of the form 'void (const ErrT&)'. +template class ErrorHandlerTraits { +public: + static bool appliesTo(const ErrorInfoBase &E) { + return E.template isA(); + } + + template + static Error apply(HandlerT &&H, std::unique_ptr E) { + assert(appliesTo(*E) && "Applying incorrect handler"); + H(static_cast(*E)); + return Error::success(); + } +}; + +/// Specialization for functions of the form 'Error (std::unique_ptr)'. +template +class ErrorHandlerTraits)> { +public: + static bool appliesTo(const ErrorInfoBase &E) { + return E.template isA(); + } + + template + static Error apply(HandlerT &&H, std::unique_ptr E) { + assert(appliesTo(*E) && "Applying incorrect handler"); + std::unique_ptr SubE(static_cast(E.release())); + return H(std::move(SubE)); + } +}; + +/// Specialization for functions of the form 'Error (std::unique_ptr)'. +template +class ErrorHandlerTraits)> { +public: + static bool appliesTo(const ErrorInfoBase &E) { + return E.template isA(); + } + + template + static Error apply(HandlerT &&H, std::unique_ptr E) { + assert(appliesTo(*E) && "Applying incorrect handler"); + std::unique_ptr SubE(static_cast(E.release())); + H(std::move(SubE)); + return Error::success(); + } +}; + +// Specialization for member functions of the form 'RetT (const ErrT&)'. +template +class ErrorHandlerTraits + : public ErrorHandlerTraits {}; + +// Specialization for member functions of the form 'RetT (const ErrT&) const'. +template +class ErrorHandlerTraits + : public ErrorHandlerTraits {}; + +// Specialization for member functions of the form 'RetT (const ErrT&)'. +template +class ErrorHandlerTraits + : public ErrorHandlerTraits {}; + +// Specialization for member functions of the form 'RetT (const ErrT&) const'. +template +class ErrorHandlerTraits + : public ErrorHandlerTraits {}; + +/// Specialization for member functions of the form +/// 'RetT (std::unique_ptr) const'. +template +class ErrorHandlerTraits)> + : public ErrorHandlerTraits)> {}; + +/// Specialization for member functions of the form +/// 'RetT (std::unique_ptr) const'. +template +class ErrorHandlerTraits) const> + : public ErrorHandlerTraits)> {}; + +inline Error handleErrorImpl(std::unique_ptr Payload) { + return Error(std::move(Payload)); +} + +template +Error handleErrorImpl(std::unique_ptr Payload, + HandlerT &&Handler, HandlerTs &&... Handlers) { + if (ErrorHandlerTraits::appliesTo(*Payload)) + return ErrorHandlerTraits::apply(std::forward(Handler), + std::move(Payload)); + return handleErrorImpl(std::move(Payload), + std::forward(Handlers)...); +} + +/// Pass the ErrorInfo(s) contained in E to their respective handlers. Any +/// unhandled errors (or Errors returned by handlers) are re-concatenated and +/// returned. +/// Because this function returns an error, its result must also be checked +/// or returned. If you intend to handle all errors use handleAllErrors +/// (which returns void, and will abort() on unhandled errors) instead. +template +Error handleErrors(Error E, HandlerTs &&... Hs) { + if (!E) + return Error::success(); + + std::unique_ptr Payload = E.takePayload(); + + if (Payload->isA()) { + ErrorList &List = static_cast(*Payload); + Error R; + for (auto &P : List.Payloads) + R = ErrorList::join( + std::move(R), + handleErrorImpl(std::move(P), std::forward(Hs)...)); + return R; + } + + return handleErrorImpl(std::move(Payload), std::forward(Hs)...); +} + +/// Behaves the same as handleErrors, except that it requires that all +/// errors be handled by the given handlers. If any unhandled error remains +/// after the handlers have run, abort() will be called. +template +void handleAllErrors(Error E, HandlerTs &&... Handlers) { + auto F = handleErrors(std::move(E), std::forward(Handlers)...); + // Cast 'F' to bool to set the 'Checked' flag if it's a success value: + (void)!F; +} + +/// Check that E is a non-error, then drop it. +inline void handleAllErrors(Error E) { + // Cast 'E' to a bool to set the 'Checked' flag if it's a success value: + (void)!E; +} + +/// Log all errors (if any) in E to OS. If there are any errors, ErrorBanner +/// will be printed before the first one is logged. A newline will be printed +/// after each error. +/// +/// This is useful in the base level of your program to allow clean termination +/// (allowing clean deallocation of resources, etc.), while reporting error +/// information to the user. +template +void logAllUnhandledErrors(Error E, raw_ostream &OS, std::string ErrorBanner) { + if (!E) + return; + OS << ErrorBanner; + handleAllErrors(std::move(E), [&](const ErrorInfoBase &EI) { + EI.log(OS); + OS << "\n"; + }); +} + +/// Consume a Error without doing anything. This method should be used +/// only where an error can be considered a reasonable and expected return +/// value. +/// +/// Uses of this method are potentially indicative of design problems: If it's +/// legitimate to do nothing while processing an "error", the error-producer +/// might be more clearly refactored to return an Optional. +inline void consumeError(Error Err) { + handleAllErrors(std::move(Err), [](const ErrorInfoBase &) {}); +} + +/// Tagged union holding either a T or a Error. +/// +/// This class parallels ErrorOr, but replaces error_code with Error. Since +/// Error cannot be copied, this class replaces getError() with +/// takeError(). It also adds an bool errorIsA() method for testing the +/// error class type. +template class Expected { + template friend class Expected; + static const bool isRef = std::is_reference::value; + typedef ReferenceStorage::type> wrap; + +public: + typedef typename std::conditional::type storage_type; + +private: + typedef typename std::remove_reference::type &reference; + typedef const typename std::remove_reference::type &const_reference; + typedef typename std::remove_reference::type *pointer; + typedef const typename std::remove_reference::type *const_pointer; + +public: + /// Create an Expected error value from the given Error. + Expected(Error Err) : HasError(true) { + assert(Err && "Cannot create Expected from Error success value."); + new (getErrorStorage()) Error(std::move(Err)); + } + + /// Create an Expected success value from the given OtherT value, which + /// must be convertible to T. + template + Expected(OtherT &&Val, + typename std::enable_if::value>::type + * = nullptr) + : HasError(false) { + new (getStorage()) storage_type(std::move(Val)); + } + + /// Move construct an Expected value. + Expected(Expected &&Other) { moveConstruct(std::move(Other)); } + + /// Move construct an Expected value from an Expected, where OtherT + /// must be convertible to T. + template + Expected(Expected &&Other, + typename std::enable_if::value>::type + * = nullptr) { + moveConstruct(std::move(Other)); + } + + /// Move construct an Expected value from an Expected, where OtherT + /// isn't convertible to T. + template + explicit Expected( + Expected &&Other, + typename std::enable_if::value>::type * = + nullptr) { + moveConstruct(std::move(Other)); + } + + /// Move-assign from another Expected. + Expected &operator=(Expected &&Other) { + moveAssign(std::move(Other)); + return *this; + } + + /// Destroy an Expected. + ~Expected() { + if (!HasError) + getStorage()->~storage_type(); + else + getErrorStorage()->~Error(); + } + + /// \brief Return false if there is an error. + explicit operator bool() const { return !HasError; } + + /// \brief Returns a reference to the stored T value. + reference get() { return *getStorage(); } + + /// \brief Returns a const reference to the stored T value. + const_reference get() const { return const_cast *>(this)->get(); } + + /// \brief Check that this Expected is an error of type ErrT. + template bool errorIsA() const { + return HasError && getErrorStorage()->template isA(); + } + + /// \brief Take ownership of the stored error. + /// After calling this the Expected is in an indeterminate state that can + /// only be safely destructed. No further calls (beside the destructor) should + /// be made on the Expected vaule. + Error takeError() { + return HasError ? std::move(*getErrorStorage()) : Error(); + } + + /// \brief Returns a pointer to the stored T value. + pointer operator->() { return toPointer(getStorage()); } + + /// \brief Returns a const pointer to the stored T value. + const_pointer operator->() const { return toPointer(getStorage()); } + + /// \brief Returns a reference to the stored T value. + reference operator*() { return *getStorage(); } + + /// \brief Returns a const reference to the stored T value. + const_reference operator*() const { return *getStorage(); } + +private: + template + static bool compareThisIfSameType(const T1 &a, const T1 &b) { + return &a == &b; + } + + template + static bool compareThisIfSameType(const T1 &a, const T2 &b) { + return false; + } + + template void moveConstruct(Expected &&Other) { + if (!Other.HasError) { + // Get the other value. + HasError = false; + new (getStorage()) storage_type(std::move(*Other.getStorage())); + } else { + // Get other's error. + HasError = true; + new (getErrorStorage()) Error(Other.takeError()); + } + } + + template void moveAssign(Expected &&Other) { + if (compareThisIfSameType(*this, Other)) + return; + + this->~Expected(); + new (this) Expected(std::move(Other)); + } + + pointer toPointer(pointer Val) { return Val; } + + const_pointer toPointer(const_pointer Val) const { return Val; } + + pointer toPointer(wrap *Val) { return &Val->get(); } + + const_pointer toPointer(const wrap *Val) const { return &Val->get(); } + + storage_type *getStorage() { + assert(!HasError && "Cannot get value when an error exists!"); + return reinterpret_cast(TStorage.buffer); + } + + const storage_type *getStorage() const { + assert(!HasError && "Cannot get value when an error exists!"); + return reinterpret_cast(TStorage.buffer); + } + + Error *getErrorStorage() { + assert(HasError && "Cannot get error when a value exists!"); + return reinterpret_cast(ErrorStorage.buffer); + } + + union { + AlignedCharArrayUnion TStorage; + AlignedCharArrayUnion ErrorStorage; + }; + bool HasError : 1; +}; + +/// This class wraps a std::error_code in a Error. +/// +/// This is useful if you're writing an interface that returns a Error +/// (or Expected) and you want to call code that still returns +/// std::error_codes. +class ECError : public ErrorInfo { +public: + ECError() = default; + ECError(std::error_code EC) : EC(EC) {} + std::error_code getErrorCode() const { return EC; } + void log(raw_ostream &OS) const override { OS << EC.message(); } + +protected: + std::error_code EC; +}; + +/// Helper for converting an std::error_code to a Error. +inline Error errorCodeToError(std::error_code EC) { + if (!EC) + return Error::success(); + return make_error(EC); +} + +/// Helper for converting an ECError to a std::error_code. +/// +/// This method requires that Err be Error() or an ECError, otherwise it +/// will trigger a call to abort(). +inline std::error_code errorToErrorCode(Error Err) { + std::error_code EC; + handleAllErrors(std::move(Err), + [&](const ECError &ECE) { EC = ECE.getErrorCode(); }); + return EC; +} + +} // namespace llvm + +#endif // LLVM_SUPPORT_ERROR_H Index: lib/Support/ErrorHandling.cpp =================================================================== --- lib/Support/ErrorHandling.cpp +++ lib/Support/ErrorHandling.cpp @@ -19,6 +19,7 @@ #include "llvm/Config/config.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Mutex.h" #include "llvm/Support/MutexGuard.h" @@ -138,6 +139,9 @@ remove_fatal_error_handler(); } +void ErrorInfoBase::anchor() {} +char ErrorInfoBase::ID = 0; + #ifdef LLVM_ON_WIN32 #include Index: unittests/Support/CMakeLists.txt =================================================================== --- unittests/Support/CMakeLists.txt +++ unittests/Support/CMakeLists.txt @@ -16,6 +16,7 @@ DwarfTest.cpp EndianStreamTest.cpp EndianTest.cpp + ErrorTest.cpp ErrorOrTest.cpp FileOutputBufferTest.cpp IteratorTest.cpp Index: unittests/Support/ErrorTest.cpp =================================================================== --- unittests/Support/ErrorTest.cpp +++ unittests/Support/ErrorTest.cpp @@ -0,0 +1,458 @@ +//===----- unittests/ErrorTest.cpp - Error.h tests ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/Error.h" +#include "llvm/Support/Errc.h" +#include "gtest/gtest.h" +#include + +using namespace llvm; + +namespace { + +// Test: +// +// Constructing success. +// - Silent acceptance of tested success. +// - Programmatic error on untested success. +// +// Custom error class. +// - Creation of a custom error class with a default base class. +// - Handling of a custom error class with a default base class. +// - Handler type deduction. +// - Failure to handle a custom error class. +// - Isa testing of a custom error class. +// +// - Creation of a custom error class with a custom base class. +// - Handling of a custom error class with a default base class. +// - Correct shadowing of handlers. +// +// Utility functions: +// - join_errors to defer errors. +// - consume_error to consume a "safe" error without any output. +// - handleAllUnhandledErrors to assert that all errors are handled. +// - logAllUnhandledErrors to log errors to a stream. +// +// Expected tests: +// - Expected with T. +// - Expected with Error. +// - Failure to handle an Expected in failure mode. +// - Error extraction (Expected -> Error). +// +// std::error_code: +// - std::error_code to Error in success mode. +// - std::error_code to Error (ECError) in failure mode. +// - Error to std::error_code in success mode. +// - Error (ECError) to std::error_code in failure mode. + +// Custom error class with a default base class and some random 'info' attached. +class CustomError : public ErrorInfo { +public: + // Create an error with some info attached. + CustomError(int Info) : Info(Info) {} + + // Get the info attached to this error. + int getInfo() const { return Info; } + + // Log this error to a stream. + void log(raw_ostream &OS) const override { + OS << "CustomError { " << getInfo() << "}"; + } + +protected: + // This error is subclassed below, but we can't use inheriting constructors + // yet, so we can't propagate the constructors through ErrorInfo. Instead + // we have to have a default constructor and have the subclass initialize all + // fields. + CustomError() : Info(0) {} + + int Info; +}; + +// Custom error class with a custom base class and some additional random +// 'info'. +class CustomSubError : public ErrorInfo { +public: + // Create a sub-error with some info attached. + CustomSubError(int Info, int ExtraInfo) : ExtraInfo(ExtraInfo) { + this->Info = Info; + } + + // Get the extra info attached to this error. + int getExtraInfo() const { return ExtraInfo; } + + // Log this error to a stream. + void log(raw_ostream &OS) const override { + OS << "CustomSubError { " << getInfo() << ", " << getExtraInfo() << "}"; + } + +protected: + int ExtraInfo; +}; + +// Verify that success values that are checked (i.e. cast to 'bool') are +// destructed without error, and that unchecked success values cause an +// abort. +TEST(Error, CheckSuccess) { + // Test checked success. + { + Error E; + EXPECT_FALSE(E) << "Unexpected error while testing Error 'Success'"; + } + +// Test unchecked success. +// Test runs in debug mode only. +#ifndef NDEBUG + { + auto DropUncheckedSuccess = []() { Error E; }; + EXPECT_DEATH(DropUncheckedSuccess(), + "Program aborted due to an unhandled Error:") + << "Unchecked Error Succes value did not cause abort()"; + } +#endif +} + +static Error handleCustomError(const CustomError &CE) { return Error(); } + +static void handleCustomErrorVoid(const CustomError &CE) {} + +static Error handleCustomErrorUP(std::unique_ptr CE) { + return Error(); +} + +static void handleCustomErrorUPVoid(std::unique_ptr CE) {} + +// Verify creation and handling of custom error classes. +TEST(Error, CheckCustomErrors) { +// Check that we abort on unhandled failure cases. (Force conversion to bool +// to make sure that we don't accidentally treat checked errors as handled). +// Test runs in debug mode only. +#ifndef NDEBUG + { + auto DropUnhandledError = []() { + Error E = make_error(42); + (void)!E; + }; + EXPECT_DEATH(DropUnhandledError(), + "Program aborted due to an unhandled Error:") + << "Unhandled Error failure value did not cause abort()"; + } +#endif + + // Check 'isA' handling. + { + Error E = make_error(1); + Error F = make_error(1, 2); + + EXPECT_TRUE(E.isA()); + EXPECT_FALSE(E.isA()); + EXPECT_TRUE(F.isA()); + EXPECT_TRUE(F.isA()); + + consumeError(std::move(E)); + consumeError(std::move(F)); + } + + // Check that we can handle a custom error. + { + int CaughtErrorInfo = 0; + handleAllErrors(make_error(42), [&](const CustomError &CE) { + CaughtErrorInfo = CE.getInfo(); + }); + + EXPECT_TRUE(CaughtErrorInfo == 42) + << "Wrong result from CustomError handler"; + } + + // Check that handler type deduction also works for handlers + // of the following types: + // void (const Err&) + // Error (const Err&) mutable + // void (const Err&) mutable + // Error (Err&) + // void (Err&) + // Error (Err&) mutable + // void (Err&) mutable + // Error (unique_ptr) + // void (unique_ptr) + // Error (unique_ptr) mutable + // void (unique_ptr) mutable + + handleAllErrors(make_error(42), [](const CustomError &CE) {}); + + handleAllErrors( + make_error(42), + [](const CustomError &CE) mutable { return Error::success(); }); + + handleAllErrors(make_error(42), + [](const CustomError &CE) mutable {}); + + handleAllErrors(make_error(42), + [](CustomError &CE) { return Error::success(); }); + + handleAllErrors(make_error(42), [](CustomError &CE) {}); + + handleAllErrors(make_error(42), + [](CustomError &CE) mutable { return Error::success(); }); + + handleAllErrors(make_error(42), [](CustomError &CE) mutable {}); + + handleAllErrors( + make_error(42), + [](std::unique_ptr CE) { return Error::success(); }); + + handleAllErrors(make_error(42), + [](std::unique_ptr CE) {}); + + handleAllErrors( + make_error(42), + [](std::unique_ptr CE) mutable { return Error::success(); }); + + handleAllErrors(make_error(42), + [](std::unique_ptr CE) mutable {}); + + // Check that named handlers of type 'Error (const Err&)' work. + handleAllErrors(make_error(42), handleCustomError); + + // Check that named handlers of type 'void (const Err&)' work. + handleAllErrors(make_error(42), handleCustomErrorVoid); + + // Check that named handlers of type 'Error (std::unique_ptr)' work. + handleAllErrors(make_error(42), handleCustomErrorUP); + + // Check that named handlers of type 'Error (std::unique_ptr)' work. + handleAllErrors(make_error(42), handleCustomErrorUPVoid); + + // Check that we can handle a custom error with a custom base class. + { + int CaughtErrorInfo = 0; + int CaughtErrorExtraInfo = 0; + handleAllErrors(make_error(42, 7), + [&](const CustomSubError &SE) { + CaughtErrorInfo = SE.getInfo(); + CaughtErrorExtraInfo = SE.getExtraInfo(); + }); + + EXPECT_TRUE(CaughtErrorInfo == 42 && CaughtErrorExtraInfo == 7) + << "Wrong result from CustomSubError handler"; + } + + // Check that we trigger only the first handler that applies. + { + int DummyInfo = 0; + int CaughtErrorInfo = 0; + int CaughtErrorExtraInfo = 0; + + handleAllErrors(make_error(42, 7), + [&](const CustomSubError &SE) { + CaughtErrorInfo = SE.getInfo(); + CaughtErrorExtraInfo = SE.getExtraInfo(); + }, + [&](const CustomError &CE) { DummyInfo = CE.getInfo(); }); + + EXPECT_TRUE(CaughtErrorInfo == 42 && CaughtErrorExtraInfo == 7 && + DummyInfo == 0) + << "Activated the wrong Error handler(s)"; + } + + // Check that general handlers shadow specific ones. + { + int CaughtErrorInfo = 0; + int DummyInfo = 0; + int DummyExtraInfo = 0; + + handleAllErrors( + make_error(42, 7), + [&](const CustomError &CE) { CaughtErrorInfo = CE.getInfo(); }, + [&](const CustomSubError &SE) { + DummyInfo = SE.getInfo(); + DummyExtraInfo = SE.getExtraInfo(); + }); + + EXPECT_TRUE(CaughtErrorInfo = 42 && DummyInfo == 0 && DummyExtraInfo == 0) + << "General Error handler did not shadow specific handler"; + } +} + +// Test utility functions. +TEST(Error, CheckErrorUtilities) { + + // Test joinErrors + { + int CustomErrorInfo1 = 0; + int CustomErrorInfo2 = 0; + int CustomErrorExtraInfo = 0; + Error E = joinErrors(make_error(7), + make_error(42, 7)); + + handleAllErrors(std::move(E), + [&](const CustomSubError &SE) { + CustomErrorInfo2 = SE.getInfo(); + CustomErrorExtraInfo = SE.getExtraInfo(); + }, + [&](const CustomError &CE) { + // Assert that the CustomError instance above is handled + // before the + // CustomSubError - joinErrors should preserve error + // ordering. + EXPECT_EQ(CustomErrorInfo2, 0) + << "CustomErrorInfo2 should be 0 here. " + "joinErrors failed to preserve ordering.\n"; + CustomErrorInfo1 = CE.getInfo(); + }); + + EXPECT_TRUE(CustomErrorInfo1 == 7 && CustomErrorInfo2 == 42 && + CustomErrorExtraInfo == 7) + << "Failed handling compound Error."; + } + + // Test consumeError for both success and error cases. + { + Error E; + consumeError(std::move(E)); + } + { + Error E = make_error(7); + consumeError(std::move(E)); + } + +// Test that handleAllUnhandledErrors crashes if an error is not caught. +// Test runs in debug mode only. +#ifndef NDEBUG + { + auto FailToHandle = []() { + handleAllErrors(make_error(7), + [&](const CustomSubError &SE) { + errs() << "This should never be called"; + exit(1); + }); + }; + + EXPECT_DEATH(FailToHandle(), "Program aborted due to an unhandled Error:") + << "Unhandled Error in handleAllErrors call did not cause an " + "abort()"; + } +#endif + +// Test that handleAllUnhandledErrors crashes if an error is returned from a +// handler. +// Test runs in debug mode only. +#ifndef NDEBUG + { + auto ReturnErrorFromHandler = []() { + handleAllErrors(make_error(7), + [&](std::unique_ptr SE) { + return Error(std::move(SE)); + }); + }; + + EXPECT_DEATH(ReturnErrorFromHandler(), + "Program aborted due to an unhandled Error:") + << " Error returned from handler in handleAllErrors call did not " + "cause abort()"; + } +#endif + + // Test that we can return values from handleErrors. + { + int ErrorInfo = 0; + + Error E = handleErrors( + make_error(7), + [&](std::unique_ptr CE) { return Error(std::move(CE)); }); + + handleAllErrors(std::move(E), + [&](const CustomError &CE) { ErrorInfo = CE.getInfo(); }); + + EXPECT_EQ(ErrorInfo, 7) + << "Failed to handle Error returned from handleErrors."; + } +} + +// Test Expected behavior. +TEST(Error, CheckExpected) { + + // Check that non-errors convert to 'true'. + { + Expected A = 7; + EXPECT_TRUE(!!A) + << "Expected with non-error value doesn't convert to 'true'"; + } + + // Check that non-error values are accessible via operator*. + { + Expected A = 7; + EXPECT_EQ(*A, 7) << "Incorrect Expected non-error value"; + } + + // Check that errors convert to 'false'. + { + Expected A = make_error(42); + EXPECT_FALSE(!!A) << "Expected with error value doesn't convert to 'false'"; + consumeError(A.takeError()); + } + + // Check that error values are accessible via takeError(). + { + Expected A = make_error(42); + Error E = A.takeError(); + EXPECT_TRUE(E.isA()) << "Incorrect Expected error value"; + consumeError(std::move(E)); + } + +// Check that an Expected instance with an error value doesn't allow access to +// operator*. +// Test runs in debug mode only. +#ifndef NDEBUG + { + Expected A = make_error(42); + EXPECT_DEATH(*A, "\\(!HasError && \"Cannot get value " + "when an error exists!\"\\)") + << "Incorrect Expected error value"; + consumeError(A.takeError()); + } +#endif + +// Check that an Expected instance with an error triggers an abort if +// unhandled. +// Test runs in debug mode only. +#ifndef NDEBUG + EXPECT_DEATH({ Expected A = make_error(42); }, + "Program aborted due to an unhandled Error:") + << "Unchecked Expected failure value did not cause an abort()"; +#endif + + // Test covariance of Expected. + { + class B {}; + class D : public B {}; + + Expected A1(Expected(nullptr)); + A1 = Expected(nullptr); + + Expected> A2(Expected>(nullptr)); + A2 = Expected>(nullptr); + } +} + +TEST(Error, ECError) { + + // Round-trip a success value to check that it converts correctly. + EXPECT_EQ(errorToErrorCode(errorCodeToError(std::error_code())), + std::error_code()) + << "std::error_code() should round-trip via Error conversions"; + + // Round-trip an error value to check that it converts correctly. + EXPECT_EQ(errorToErrorCode(errorCodeToError(errc::invalid_argument)), + errc::invalid_argument) + << "std::error_code error value should round-trip via Error " + "conversions"; +} + +} // end anon namespace