[llvm] [orc-rt] Add error.h: structured error support. (PR #154869)
Lang Hames via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 21 18:48:16 PDT 2025
https://github.com/lhames created https://github.com/llvm/llvm-project/pull/154869
Adds support for the Error class, Expected class template, and related APIs that will be used for error propagation and handling in the new ORC runtime.
The implementations of these types are cut-down versions of similar APIs in llvm/Support/Error.h. Most advice on llvm::Error and llvm::Expected (e.g. from the LLVM Programmer's manual) applies equally to orc_rt::Error and orc_rt::Expected.
Ported from the old ORC runtime at compiler-rt/lib/orc.
>From b71f2af8eb13db17c5ad0aad8413860d0dac1cbe Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Fri, 22 Aug 2025 11:29:59 +1000
Subject: [PATCH] [orc-rt] Add error.h: structured error support.
Adds support for the Error class, Expected class template, and related APIs
that will be used for error propagation and handling in the new ORC runtime.
The implementations of these types are cut-down versions of similar APIs in
llvm/Support/Error.h. Most advice on llvm::Error and llvm::Expected (e.g. from
the LLVM Programmer's manual) applies equally to orc_rt::Error and
orc_rt::Expected.
Ported from the old ORC runtime at compiler-rt/lib/orc.
---
orc-rt/include/CMakeLists.txt | 2 +
orc-rt/include/orc-rt/compiler.h | 74 +++++
orc-rt/include/orc-rt/error.h | 539 +++++++++++++++++++++++++++++++
orc-rt/unittests/CMakeLists.txt | 1 +
orc-rt/unittests/error-test.cpp | 408 +++++++++++++++++++++++
5 files changed, 1024 insertions(+)
create mode 100644 orc-rt/include/orc-rt/compiler.h
create mode 100644 orc-rt/include/orc-rt/error.h
create mode 100644 orc-rt/unittests/error-test.cpp
diff --git a/orc-rt/include/CMakeLists.txt b/orc-rt/include/CMakeLists.txt
index 717b75290e052..38d0b2daa201e 100644
--- a/orc-rt/include/CMakeLists.txt
+++ b/orc-rt/include/CMakeLists.txt
@@ -1,6 +1,8 @@
set(ORC_RT_HEADERS
orc-rt-c/orc-rt.h
orc-rt/bitmask-enum.h
+ orc-rt/error.h
+ orc-rt/compiler.h
orc-rt/math.h
orc-rt/rtti.h
orc-rt/span.h
diff --git a/orc-rt/include/orc-rt/compiler.h b/orc-rt/include/orc-rt/compiler.h
new file mode 100644
index 0000000000000..1a176726d26f7
--- /dev/null
+++ b/orc-rt/include/orc-rt/compiler.h
@@ -0,0 +1,74 @@
+//===--------- compiler.h - Compiler abstraction support --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of the ORC runtime support library.
+//
+// Most functionality in this file was swiped from llvm/Support/Compiler.h.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_COMPILER_H
+#define ORC_RT_COMPILER_H
+
+#if defined(_WIN32)
+#define ORC_RT_INTERFACE extern "C"
+#define ORC_RT_HIDDEN
+#define ORC_RT_IMPORT extern "C" __declspec(dllimport)
+#else
+#define ORC_RT_INTERFACE extern "C" __attribute__((visibility("default")))
+#define ORC_RT_HIDDEN __attribute__((visibility("hidden")))
+#define ORC_RT_IMPORT extern "C"
+#endif
+
+#ifndef __has_builtin
+#define __has_builtin(x) 0
+#endif
+
+// Only use __has_cpp_attribute in C++ mode. GCC defines __has_cpp_attribute in
+// C mode, but the :: in __has_cpp_attribute(scoped::attribute) is invalid.
+#ifndef ORC_RT_HAS_CPP_ATTRIBUTE
+#if defined(__cplusplus) && defined(__has_cpp_attribute)
+#define ORC_RT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
+#else
+#define ORC_RT_HAS_CPP_ATTRIBUTE(x) 0
+#endif
+#endif
+
+// Use the 'nodiscard' attribute in C++17 or newer mode.
+#if defined(__cplusplus) && __cplusplus > 201402L && \
+ ORC_RT_HAS_CPP_ATTRIBUTE(nodiscard)
+#define ORC_RT_NODISCARD [[nodiscard]]
+#elif ORC_RT_HAS_CPP_ATTRIBUTE(clang::warn_unused_result)
+#define ORC_RT_NODISCARD [[clang::warn_unused_result]]
+// Clang in C++14 mode claims that it has the 'nodiscard' attribute, but also
+// warns in the pedantic mode that 'nodiscard' is a C++17 extension (PR33518).
+// Use the 'nodiscard' attribute in C++14 mode only with GCC.
+// TODO: remove this workaround when PR33518 is resolved.
+#elif defined(__GNUC__) && ORC_RT_HAS_CPP_ATTRIBUTE(nodiscard)
+#define ORC_RT_NODISCARD [[nodiscard]]
+#else
+#define ORC_RT_NODISCARD
+#endif
+
+#if __has_builtin(__builtin_expect)
+#define ORC_RT_LIKELY(EXPR) __builtin_expect((bool)(EXPR), true)
+#define ORC_RT_UNLIKELY(EXPR) __builtin_expect((bool)(EXPR), false)
+#else
+#define ORC_RT_LIKELY(EXPR) (EXPR)
+#define ORC_RT_UNLIKELY(EXPR) (EXPR)
+#endif
+
+#if defined(__APPLE__)
+#define ORC_RT_WEAK_IMPORT __attribute__((weak_import))
+#elif defined(_WIN32)
+#define ORC_RT_WEAK_IMPORT
+#else
+#define ORC_RT_WEAK_IMPORT __attribute__((weak))
+#endif
+
+#endif // ORC_RT_COMPILER_H
diff --git a/orc-rt/include/orc-rt/error.h b/orc-rt/include/orc-rt/error.h
new file mode 100644
index 0000000000000..478ba5b865e12
--- /dev/null
+++ b/orc-rt/include/orc-rt/error.h
@@ -0,0 +1,539 @@
+//===-------- error.h - Enforced error checking for ORC RT ------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_ERROR_H
+#define ORC_RT_ERROR_H
+
+#include "orc-rt/compiler.h"
+#include "orc-rt/rtti.h"
+
+#include <cassert>
+#include <cstdlib>
+#include <memory>
+#include <string>
+#include <type_traits>
+
+namespace orc_rt {
+
+/// Base class for all errors.
+class ErrorInfoBase : public RTTIExtends<ErrorInfoBase, RTTIRoot> {
+public:
+ virtual std::string toString() const = 0;
+};
+
+/// Represents an environmental error.
+class ORC_RT_NODISCARD Error {
+
+ template <typename T> friend class Expected;
+
+ friend Error make_error(std::unique_ptr<ErrorInfoBase> Payload);
+
+ template <typename... HandlerTs>
+ friend Error handleErrors(Error E, HandlerTs &&...Hs);
+
+public:
+ /// Destroy this error. Aborts if error was not checked, or was checked but
+ /// not handled.
+ ~Error() { assertIsChecked(); }
+
+ Error(const Error &) = delete;
+ Error &operator=(const Error &) = delete;
+
+ /// Move-construct an error. The newly constructed error is considered
+ /// unchecked, even if the source error had been checked. The original error
+ /// becomes a checked success value.
+ Error(Error &&Other) {
+ setChecked(true);
+ *this = std::move(Other);
+ }
+
+ /// 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) {
+ // Don't allow overwriting of unchecked values.
+ 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;
+ }
+
+ /// Create a success value.
+ static Error success() { return Error(); }
+
+ /// Error values convert to true for failure values, false otherwise.
+ explicit operator bool() {
+ setChecked(getPtr() == nullptr);
+ return getPtr() != nullptr;
+ }
+
+ /// Return true if this Error contains a failure value of the given type.
+ template <typename ErrT> bool isA() const {
+ return getPtr() && getPtr()->isA<ErrT>();
+ }
+
+private:
+ Error() = default;
+
+ Error(std::unique_ptr<ErrorInfoBase> ErrInfo) {
+ auto RawErrPtr = reinterpret_cast<uintptr_t>(ErrInfo.release());
+ assert((RawErrPtr & 0x1) == 0 && "ErrorInfo is insufficiently aligned");
+ ErrPtr = RawErrPtr | 0x1;
+ }
+
+ void assertIsChecked() {
+ if (ORC_RT_UNLIKELY(!isChecked() || getPtr())) {
+ fprintf(stderr, "Error must be checked prior to destruction.\n");
+ abort(); // Some sort of JIT program abort?
+ }
+ }
+
+ template <typename ErrT = ErrorInfoBase> ErrT *getPtr() const {
+ return reinterpret_cast<ErrT *>(ErrPtr & ~uintptr_t(1));
+ }
+
+ void setPtr(ErrorInfoBase *Ptr) {
+ ErrPtr = (reinterpret_cast<uintptr_t>(Ptr) & ~uintptr_t(1)) | (ErrPtr & 1);
+ }
+
+ bool isChecked() const { return ErrPtr & 0x1; }
+
+ void setChecked(bool Checked) { ErrPtr = (ErrPtr & ~uintptr_t(1)) | Checked; }
+
+ template <typename ErrT = ErrorInfoBase> std::unique_ptr<ErrT> takePayload() {
+ static_assert(std::is_base_of<ErrorInfoBase, ErrT>::value,
+ "ErrT is not an ErrorInfoBase subclass");
+ std::unique_ptr<ErrT> Tmp(getPtr<ErrT>());
+ setPtr(nullptr);
+ setChecked(true);
+ return Tmp;
+ }
+
+ uintptr_t ErrPtr = 0;
+};
+
+/// Create an Error from an ErrorInfoBase.
+Error make_error(std::unique_ptr<ErrorInfoBase> Payload) {
+ return Error(std::move(Payload));
+}
+
+/// Construct an error of ErrT with the given arguments.
+template <typename ErrT, typename... ArgTs> Error make_error(ArgTs &&...Args) {
+ static_assert(std::is_base_of<ErrorInfoBase, ErrT>::value,
+ "ErrT is not an ErrorInfoBase subclass");
+ return make_error(std::make_unique<ErrT>(std::forward<ArgTs>(Args)...));
+}
+
+/// Traits class for selecting and applying error handlers.
+template <typename HandlerT>
+struct ErrorHandlerTraits
+ : public ErrorHandlerTraits<
+ decltype(&std::remove_reference_t<HandlerT>::operator())> {};
+
+// Specialization functions of the form 'Error (const ErrT&)'.
+template <typename ErrT> struct ErrorHandlerTraits<Error (&)(ErrT &)> {
+ static bool appliesTo(const ErrorInfoBase &E) {
+ return E.template isA<ErrT>();
+ }
+
+ template <typename HandlerT>
+ static Error apply(HandlerT &&H, std::unique_ptr<ErrorInfoBase> E) {
+ assert(appliesTo(*E) && "Applying incorrect handler");
+ return H(static_cast<ErrT &>(*E));
+ }
+};
+
+// Specialization functions of the form 'void (const ErrT&)'.
+template <typename ErrT> struct ErrorHandlerTraits<void (&)(ErrT &)> {
+ static bool appliesTo(const ErrorInfoBase &E) {
+ return E.template isA<ErrT>();
+ }
+
+ template <typename HandlerT>
+ static Error apply(HandlerT &&H, std::unique_ptr<ErrorInfoBase> E) {
+ assert(appliesTo(*E) && "Applying incorrect handler");
+ H(static_cast<ErrT &>(*E));
+ return Error::success();
+ }
+};
+
+/// Specialization for functions of the form 'Error (std::unique_ptr<ErrT>)'.
+template <typename ErrT>
+struct ErrorHandlerTraits<Error (&)(std::unique_ptr<ErrT>)> {
+ static bool appliesTo(const ErrorInfoBase &E) {
+ return E.template isA<ErrT>();
+ }
+
+ template <typename HandlerT>
+ static Error apply(HandlerT &&H, std::unique_ptr<ErrorInfoBase> E) {
+ assert(appliesTo(*E) && "Applying incorrect handler");
+ std::unique_ptr<ErrT> SubE(static_cast<ErrT *>(E.release()));
+ return H(std::move(SubE));
+ }
+};
+
+/// Specialization for functions of the form 'void (std::unique_ptr<ErrT>)'.
+template <typename ErrT>
+struct ErrorHandlerTraits<void (&)(std::unique_ptr<ErrT>)> {
+ static bool appliesTo(const ErrorInfoBase &E) {
+ return E.template isA<ErrT>();
+ }
+
+ template <typename HandlerT>
+ static Error apply(HandlerT &&H, std::unique_ptr<ErrorInfoBase> E) {
+ assert(appliesTo(*E) && "Applying incorrect handler");
+ std::unique_ptr<ErrT> SubE(static_cast<ErrT *>(E.release()));
+ H(std::move(SubE));
+ return Error::success();
+ }
+};
+
+// Specialization for member functions of the form 'RetT (const ErrT&)'.
+template <typename C, typename RetT, typename ErrT>
+class ErrorHandlerTraits<RetT (C::*)(ErrT &)>
+ : public ErrorHandlerTraits<RetT (&)(ErrT &)> {};
+
+// Specialization for member functions of the form 'RetT (const ErrT&) const'.
+template <typename C, typename RetT, typename ErrT>
+class ErrorHandlerTraits<RetT (C::*)(ErrT &) const>
+ : public ErrorHandlerTraits<RetT (&)(ErrT &)> {};
+
+// Specialization for member functions of the form 'RetT (const ErrT&)'.
+template <typename C, typename RetT, typename ErrT>
+class ErrorHandlerTraits<RetT (C::*)(const ErrT &)>
+ : public ErrorHandlerTraits<RetT (&)(ErrT &)> {};
+
+// Specialization for member functions of the form 'RetT (const ErrT&) const'.
+template <typename C, typename RetT, typename ErrT>
+class ErrorHandlerTraits<RetT (C::*)(const ErrT &) const>
+ : public ErrorHandlerTraits<RetT (&)(ErrT &)> {};
+
+/// Specialization for member functions of the form
+/// 'RetT (std::unique_ptr<ErrT>)'.
+template <typename C, typename RetT, typename ErrT>
+class ErrorHandlerTraits<RetT (C::*)(std::unique_ptr<ErrT>)>
+ : public ErrorHandlerTraits<RetT (&)(std::unique_ptr<ErrT>)> {};
+
+/// Specialization for member functions of the form
+/// 'RetT (std::unique_ptr<ErrT>) const'.
+template <typename C, typename RetT, typename ErrT>
+class ErrorHandlerTraits<RetT (C::*)(std::unique_ptr<ErrT>) const>
+ : public ErrorHandlerTraits<RetT (&)(std::unique_ptr<ErrT>)> {};
+
+inline Error handleErrorsImpl(std::unique_ptr<ErrorInfoBase> Payload) {
+ return make_error(std::move(Payload));
+}
+
+template <typename HandlerT, typename... HandlerTs>
+Error handleErrorsImpl(std::unique_ptr<ErrorInfoBase> Payload,
+ HandlerT &&Handler, HandlerTs &&...Handlers) {
+ if (ErrorHandlerTraits<HandlerT>::appliesTo(*Payload))
+ return ErrorHandlerTraits<HandlerT>::apply(std::forward<HandlerT>(Handler),
+ std::move(Payload));
+ return handleErrorsImpl(std::move(Payload),
+ std::forward<HandlerTs>(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 <typename... HandlerTs>
+Error handleErrors(Error E, HandlerTs &&...Hs) {
+ if (!E)
+ return Error::success();
+ return handleErrorsImpl(E.takePayload(), std::forward<HandlerTs>(Hs)...);
+}
+
+/// Behaves the same as handleErrors, except that by contract all errors
+/// *must* be handled by the given handlers (i.e. there must be no remaining
+/// errors after running the handlers, or llvm_unreachable is called).
+template <typename... HandlerTs>
+void handleAllErrors(Error E, HandlerTs &&...Handlers) {
+ cantFail(handleErrors(std::move(E), std::forward<HandlerTs>(Handlers)...));
+}
+
+/// Helper for Errors used as out-parameters.
+/// Sets the 'checked' flag on construction, resets it on destruction.
+class ErrorAsOutParameter {
+public:
+ ErrorAsOutParameter(Error *Err) : Err(Err) {
+ // Raise the checked bit if Err is success.
+ if (Err)
+ (void)!!*Err;
+ }
+
+ ~ErrorAsOutParameter() {
+ // Clear the checked bit.
+ if (Err && !*Err)
+ *Err = Error::success();
+ }
+
+private:
+ Error *Err;
+};
+
+template <typename T> class ORC_RT_NODISCARD Expected {
+
+ template <class OtherT> friend class Expected;
+
+ static constexpr bool IsRef = std::is_reference<T>::value;
+ using wrap = std::reference_wrapper<std::remove_reference_t<T>>;
+ using error_type = std::unique_ptr<ErrorInfoBase>;
+ using storage_type = std::conditional_t<IsRef, wrap, T>;
+ using value_type = T;
+
+ using reference = std::remove_reference_t<T> &;
+ using const_reference = const std::remove_reference_t<T> &;
+ using pointer = std::remove_reference_t<T> *;
+ using const_pointer = const std::remove_reference_t<T> *;
+
+public:
+ /// Create an Expected from a failure value.
+ Expected(Error Err) : HasError(true), Unchecked(true) {
+ assert(Err && "Cannot create Expected<T> from Error success value");
+ new (getErrorStorage()) error_type(Err.takePayload());
+ }
+
+ /// Create an Expected from a T value.
+ template <typename OtherT>
+ Expected(OtherT &&Val,
+ std::enable_if_t<std::is_convertible<OtherT, T>::value> * = nullptr)
+ : HasError(false), Unchecked(true) {
+ new (getStorage()) storage_type(std::forward<OtherT>(Val));
+ }
+
+ /// Move-construct an Expected<T> from an Expected<OtherT>.
+ Expected(Expected &&Other) { moveConstruct(std::move(Other)); }
+
+ /// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT
+ /// must be convertible to T.
+ template <class OtherT>
+ Expected(
+ Expected<OtherT> &&Other,
+ std::enable_if_t<std::is_convertible<OtherT, T>::value> * = nullptr) {
+ moveConstruct(std::move(Other));
+ }
+
+ /// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT
+ /// isn't convertible to T.
+ template <class OtherT>
+ explicit Expected(
+ Expected<OtherT> &&Other,
+ std::enable_if_t<!std::is_convertible<OtherT, T>::value> * = nullptr) {
+ moveConstruct(std::move(Other));
+ }
+
+ /// Move-assign from another Expected<T>.
+ Expected &operator=(Expected &&Other) {
+ moveAssign(std::move(Other));
+ return *this;
+ }
+
+ /// Destroy an Expected<T>.
+ ~Expected() {
+ assertIsChecked();
+ if (!HasError)
+ getStorage()->~storage_type();
+ else
+ getErrorStorage()->~error_type();
+ }
+
+ /// Returns true if this Expected value is in a success state (holding a T),
+ /// and false if this Expected value is in a failure state.
+ explicit operator bool() {
+ Unchecked = HasError;
+ return !HasError;
+ }
+
+ /// Returns true if this Expected value holds an Error of type error_type.
+ template <typename ErrT> bool isFailureOfType() const {
+ return HasError && (*getErrorStorage())->template isFailureOfType<ErrT>();
+ }
+
+ /// Take ownership of the stored error.
+ ///
+ /// If this Expected value is in a success state (holding a T) then this
+ /// method is a no-op and returns Error::success.
+ ///
+ /// If thsi Expected value is in a failure state (holding an Error) then this
+ /// method returns the contained error and leaves this Expected in an
+ /// 'empty' state from which it may be safely destructed but not otherwise
+ /// accessed.
+ Error takeError() {
+ Unchecked = false;
+ return HasError ? Error(std::move(*getErrorStorage())) : Error::success();
+ }
+
+ /// Returns a pointer to the stored T value.
+ pointer operator->() {
+ assertIsChecked();
+ return toPointer(getStorage());
+ }
+
+ /// Returns a pointer to the stored T value.
+ const_pointer operator->() const {
+ assertIsChecked();
+ return toPointer(getStorage());
+ }
+
+ /// Returns a reference to the stored T value.
+ reference operator*() {
+ assertIsChecked();
+ return *getStorage();
+ }
+
+ /// Returns a reference to the stored T value.
+ const_reference operator*() const {
+ assertIsChecked();
+ return *getStorage();
+ }
+
+private:
+ template <class T1>
+ static bool compareThisIfSameType(const T1 &a, const T1 &b) {
+ return &a == &b;
+ }
+
+ template <class T1, class T2>
+ static bool compareThisIfSameType(const T1 &a, const T2 &b) {
+ return false;
+ }
+
+ template <class OtherT> void moveConstruct(Expected<OtherT> &&Other) {
+ HasError = Other.HasError;
+ Unchecked = true;
+ Other.Unchecked = false;
+
+ if (!HasError)
+ new (getStorage()) storage_type(std::move(*Other.getStorage()));
+ else
+ new (getErrorStorage()) error_type(std::move(*Other.getErrorStorage()));
+ }
+
+ template <class OtherT> void moveAssign(Expected<OtherT> &&Other) {
+ assertIsChecked();
+
+ 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<storage_type *>(&TStorage);
+ }
+
+ const storage_type *getStorage() const {
+ assert(!HasError && "Cannot get value when an error exists!");
+ return reinterpret_cast<const storage_type *>(&TStorage);
+ }
+
+ error_type *getErrorStorage() {
+ assert(HasError && "Cannot get error when a value exists!");
+ return reinterpret_cast<error_type *>(&ErrorStorage);
+ }
+
+ const error_type *getErrorStorage() const {
+ assert(HasError && "Cannot get error when a value exists!");
+ return reinterpret_cast<const error_type *>(&ErrorStorage);
+ }
+
+ void assertIsChecked() {
+ if (ORC_RT_UNLIKELY(Unchecked)) {
+ fprintf(stderr,
+ "Expected<T> must be checked before access or destruction.\n");
+ abort();
+ }
+ }
+
+ union {
+ alignas(storage_type) char TStorage[sizeof(storage_type)];
+ alignas(error_type) char ErrorStorage[sizeof(error_type)];
+ };
+
+ bool HasError : 1;
+ bool Unchecked : 1;
+};
+
+/// Consume an error without doing anything.
+inline void consumeError(Error Err) {
+ handleAllErrors(std::move(Err), [](const ErrorInfoBase &) {});
+}
+
+/// Consumes success values. It is a programmatic error to call this function
+/// on a failure value.
+inline void cantFail(Error Err) {
+#ifndef NDEBUG
+ // TODO: Log unhandled error.
+ if (Err)
+ abort();
+#else
+ Err.operator bool(); // Reset checked flag.
+#endif
+}
+
+/// Auto-unwrap an Expected<T> value in the success state. It is a programmatic
+/// error to call this function on a failure value.
+template <typename T> T cantFail(Expected<T> E) {
+ assert(E && "cantFail called on failure value");
+ consumeError(E.takeError());
+ return std::move(*E);
+}
+
+/// Auto-unwrap an Expected<T> value in the success state. It is a programmatic
+/// error to call this function on a failure value.
+template <typename T> T &cantFail(Expected<T &> E) {
+ assert(E && "cantFail called on failure value");
+ consumeError(E.takeError());
+ return *E;
+}
+
+/// Convert the given error to a string. The error value is consumed in the
+/// process.
+inline std::string toString(Error Err) {
+ assert(Err && "Cannot convert success value to string");
+ std::string ErrMsg;
+ handleAllErrors(std::move(Err),
+ [&](const ErrorInfoBase &EIB) { ErrMsg = EIB.toString(); });
+ return ErrMsg;
+}
+
+/// Simple string error type.
+class StringError : public RTTIExtends<StringError, ErrorInfoBase> {
+public:
+ StringError(std::string ErrMsg) : ErrMsg(std::move(ErrMsg)) {}
+ std::string toString() const override { return ErrMsg; }
+
+private:
+ std::string ErrMsg;
+};
+
+} // namespace orc_rt
+
+#endif // ORC_RT_ERROR_H
diff --git a/orc-rt/unittests/CMakeLists.txt b/orc-rt/unittests/CMakeLists.txt
index 00751e07742c6..d92409ed2f6a1 100644
--- a/orc-rt/unittests/CMakeLists.txt
+++ b/orc-rt/unittests/CMakeLists.txt
@@ -13,6 +13,7 @@ endfunction()
add_orc_rt_unittest(CoreTests
bitmask-enum-test.cpp
+ error-test.cpp
math-test.cpp
rtti-test.cpp
span-test.cpp
diff --git a/orc-rt/unittests/error-test.cpp b/orc-rt/unittests/error-test.cpp
new file mode 100644
index 0000000000000..32651c1431ea0
--- /dev/null
+++ b/orc-rt/unittests/error-test.cpp
@@ -0,0 +1,408 @@
+//===-- error_test.cpp ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of the ORC runtime.
+//
+// Note:
+// This unit test was adapted from
+// llvm/unittests/Support/ErrorTest.cpp
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/error.h"
+#include "gtest/gtest.h"
+
+using namespace orc_rt;
+
+namespace {
+
+class CustomError : public RTTIExtends<CustomError, ErrorInfoBase> {
+public:
+ CustomError(int Info) : Info(Info) {}
+ std::string toString() const override {
+ return "CustomError (" + std::to_string(Info) + ")";
+ }
+ int getInfo() const { return Info; }
+
+protected:
+ int Info;
+};
+
+class CustomSubError : public RTTIExtends<CustomSubError, CustomError> {
+public:
+ CustomSubError(int Info, std::string ExtraInfo)
+ : RTTIExtends<CustomSubError, CustomError>(Info),
+ ExtraInfo(std::move(ExtraInfo)) {}
+
+ std::string toString() const override {
+ return "CustomSubError (" + std::to_string(Info) + ", " + ExtraInfo + ")";
+ }
+ const std::string &getExtraInfo() const { return ExtraInfo; }
+
+protected:
+ std::string ExtraInfo;
+};
+
+static Error handleCustomError(const CustomError &CE) {
+ return Error::success();
+}
+
+static void handleCustomErrorVoid(const CustomError &CE) {}
+
+static Error handleCustomErrorUP(std::unique_ptr<CustomError> CE) {
+ return Error::success();
+}
+
+static void handleCustomErrorUPVoid(std::unique_ptr<CustomError> CE) {}
+
+} // end anonymous namespace
+
+// Test that a checked success value doesn't cause any issues.
+TEST(Error, CheckedSuccess) {
+ Error E = Error::success();
+ EXPECT_FALSE(E) << "Unexpected error while testing Error 'Success'";
+}
+
+// Check that a consumed success value doesn't cause any issues.
+TEST(Error, ConsumeSuccess) { consumeError(Error::success()); }
+
+TEST(Error, ConsumeError) {
+ Error E = make_error<CustomError>(42);
+ if (E) {
+ consumeError(std::move(E));
+ } else
+ ADD_FAILURE() << "Error failure value should convert to true";
+}
+
+// Test that unchecked success values cause an abort.
+TEST(Error, UncheckedSuccess) {
+ EXPECT_DEATH(
+ { Error E = Error::success(); },
+ "Error must be checked prior to destruction")
+ << "Unchecked Error Succes value did not cause abort()";
+}
+
+// Test that a checked but unhandled error causes an abort.
+TEST(Error, CheckedButUnhandledError) {
+ auto DropUnhandledError = []() {
+ Error E = make_error<CustomError>(42);
+ (void)!E;
+ };
+ EXPECT_DEATH(DropUnhandledError(),
+ "Error must be checked prior to destruction")
+ << "Unhandled Error failure value did not cause an abort()";
+}
+
+// Check that we can handle a custom error.
+TEST(Error, HandleCustomError) {
+ int CaughtErrorInfo = 0;
+ handleAllErrors(make_error<CustomError>(42), [&](const CustomError &CE) {
+ CaughtErrorInfo = CE.getInfo();
+ });
+
+ EXPECT_EQ(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<Err>)
+// void (unique_ptr<Err>)
+// Error (unique_ptr<Err>) mutable
+// void (unique_ptr<Err>) mutable
+TEST(Error, HandlerTypeDeduction) {
+
+ handleAllErrors(make_error<CustomError>(42), [](const CustomError &CE) {});
+
+ handleAllErrors(
+ make_error<CustomError>(42),
+ [](const CustomError &CE) mutable -> Error { return Error::success(); });
+
+ handleAllErrors(make_error<CustomError>(42),
+ [](const CustomError &CE) mutable {});
+
+ handleAllErrors(make_error<CustomError>(42),
+ [](CustomError &CE) -> Error { return Error::success(); });
+
+ handleAllErrors(make_error<CustomError>(42), [](CustomError &CE) {});
+
+ handleAllErrors(
+ make_error<CustomError>(42),
+ [](CustomError &CE) mutable -> Error { return Error::success(); });
+
+ handleAllErrors(make_error<CustomError>(42), [](CustomError &CE) mutable {});
+
+ handleAllErrors(make_error<CustomError>(42),
+ [](std::unique_ptr<CustomError> CE) -> Error {
+ return Error::success();
+ });
+
+ handleAllErrors(make_error<CustomError>(42),
+ [](std::unique_ptr<CustomError> CE) {});
+
+ handleAllErrors(make_error<CustomError>(42),
+ [](std::unique_ptr<CustomError> CE) mutable -> Error {
+ return Error::success();
+ });
+
+ handleAllErrors(make_error<CustomError>(42),
+ [](std::unique_ptr<CustomError> CE) mutable {});
+
+ // Check that named handlers of type 'Error (const Err&)' work.
+ handleAllErrors(make_error<CustomError>(42), handleCustomError);
+
+ // Check that named handlers of type 'void (const Err&)' work.
+ handleAllErrors(make_error<CustomError>(42), handleCustomErrorVoid);
+
+ // Check that named handlers of type 'Error (std::unique_ptr<Err>)' work.
+ handleAllErrors(make_error<CustomError>(42), handleCustomErrorUP);
+
+ // Check that named handlers of type 'Error (std::unique_ptr<Err>)' work.
+ handleAllErrors(make_error<CustomError>(42), handleCustomErrorUPVoid);
+}
+
+// Test that we can handle errors with custom base classes.
+TEST(Error, HandleCustomErrorWithCustomBaseClass) {
+ int CaughtErrorInfo = 0;
+ std::string CaughtErrorExtraInfo;
+ handleAllErrors(make_error<CustomSubError>(42, "foo"),
+ [&](const CustomSubError &SE) {
+ CaughtErrorInfo = SE.getInfo();
+ CaughtErrorExtraInfo = SE.getExtraInfo();
+ });
+
+ EXPECT_EQ(CaughtErrorInfo, 42) << "Wrong result from CustomSubError handler";
+ EXPECT_EQ(CaughtErrorExtraInfo, "foo")
+ << "Wrong result from CustomSubError handler";
+}
+
+// Check that we trigger only the first handler that applies.
+TEST(Error, FirstHandlerOnly) {
+ int DummyInfo = 0;
+ int CaughtErrorInfo = 0;
+ std::string CaughtErrorExtraInfo;
+
+ handleAllErrors(
+ make_error<CustomSubError>(42, "foo"),
+ [&](const CustomSubError &SE) {
+ CaughtErrorInfo = SE.getInfo();
+ CaughtErrorExtraInfo = SE.getExtraInfo();
+ },
+ [&](const CustomError &CE) { DummyInfo = CE.getInfo(); });
+
+ EXPECT_EQ(CaughtErrorInfo, 42) << "Activated the wrong Error handler(s)";
+ EXPECT_EQ(CaughtErrorExtraInfo, "foo")
+ << "Activated the wrong Error handler(s)";
+ EXPECT_EQ(DummyInfo, 0) << "Activated the wrong Error handler(s)";
+}
+
+// Check that general handlers shadow specific ones.
+TEST(Error, HandlerShadowing) {
+ int CaughtErrorInfo = 0;
+ int DummyInfo = 0;
+ std::string DummyExtraInfo;
+
+ handleAllErrors(
+ make_error<CustomSubError>(42, "foo"),
+ [&](const CustomError &CE) { CaughtErrorInfo = CE.getInfo(); },
+ [&](const CustomSubError &SE) {
+ DummyInfo = SE.getInfo();
+ DummyExtraInfo = SE.getExtraInfo();
+ });
+
+ EXPECT_EQ(CaughtErrorInfo, 42)
+ << "General Error handler did not shadow specific handler";
+ EXPECT_EQ(DummyInfo, 0)
+ << "General Error handler did not shadow specific handler";
+ EXPECT_EQ(DummyExtraInfo, "")
+ << "General Error handler did not shadow specific handler";
+}
+
+// ErrorAsOutParameter tester.
+static void errAsOutParamHelper(Error &Err) {
+ ErrorAsOutParameter ErrAsOutParam(&Err);
+ // Verify that checked flag is raised - assignment should not crash.
+ Err = Error::success();
+ // Raise the checked bit manually - caller should still have to test the
+ // error.
+ (void)!!Err;
+}
+
+// Test that ErrorAsOutParameter sets the checked flag on construction.
+TEST(Error, ErrorAsOutParameterChecked) {
+ Error E = Error::success();
+ errAsOutParamHelper(E);
+ (void)!!E;
+}
+
+// Test that ErrorAsOutParameter clears the checked flag on destruction.
+TEST(Error, ErrorAsOutParameterUnchecked) {
+ EXPECT_DEATH(
+ {
+ Error E = Error::success();
+ errAsOutParamHelper(E);
+ },
+ "Error must be checked prior to destruction")
+ << "ErrorAsOutParameter did not clear the checked flag on destruction.";
+}
+
+// Check 'Error::isA<T>' method handling.
+TEST(Error, IsAHandling) {
+ // Check 'isA' handling.
+ Error E = make_error<CustomError>(42);
+ Error F = make_error<CustomSubError>(42, "foo");
+ Error G = Error::success();
+
+ EXPECT_TRUE(E.isA<CustomError>());
+ EXPECT_FALSE(E.isA<CustomSubError>());
+ EXPECT_TRUE(F.isA<CustomError>());
+ EXPECT_TRUE(F.isA<CustomSubError>());
+ EXPECT_FALSE(G.isA<CustomError>());
+
+ consumeError(std::move(E));
+ consumeError(std::move(F));
+ consumeError(std::move(G));
+}
+
+TEST(Error, StringError) {
+ auto E = make_error<StringError>("foo");
+ if (E.isA<StringError>())
+ EXPECT_EQ(toString(std::move(E)), "foo") << "Unexpected StringError value";
+ else
+ ADD_FAILURE() << "Expected StringError value";
+}
+
+// Test Checked Expected<T> in success mode.
+TEST(Error, CheckedExpectedInSuccessMode) {
+ Expected<int> A = 7;
+ EXPECT_TRUE(!!A) << "Expected with non-error value doesn't convert to 'true'";
+ // Access is safe in second test, since we checked the error in the first.
+ EXPECT_EQ(*A, 7) << "Incorrect Expected non-error value";
+}
+
+// Test Expected with reference type.
+TEST(Error, ExpectedWithReferenceType) {
+ int A = 7;
+ Expected<int &> B = A;
+ // 'Check' B.
+ (void)!!B;
+ int &C = *B;
+ EXPECT_EQ(&A, &C) << "Expected failed to propagate reference";
+}
+
+// Test Unchecked Expected<T> in success mode.
+// We expect this to blow up the same way Error would.
+// Test runs in debug mode only.
+TEST(Error, UncheckedExpectedInSuccessModeDestruction) {
+ EXPECT_DEATH(
+ { Expected<int> A = 7; },
+ "Expected<T> must be checked before access or destruction.")
+ << "Unchecekd Expected<T> success value did not cause an abort().";
+}
+
+// Test Unchecked Expected<T> in success mode.
+// We expect this to blow up the same way Error would.
+// Test runs in debug mode only.
+TEST(Error, UncheckedExpectedInSuccessModeAccess) {
+ EXPECT_DEATH(
+ {
+ Expected<int> A = 7;
+ *A;
+ },
+ "Expected<T> must be checked before access or destruction.")
+ << "Unchecekd Expected<T> success value did not cause an abort().";
+}
+
+// Test Unchecked Expected<T> in success mode.
+// We expect this to blow up the same way Error would.
+// Test runs in debug mode only.
+TEST(Error, UncheckedExpectedInSuccessModeAssignment) {
+ EXPECT_DEATH(
+ {
+ Expected<int> A = 7;
+ A = 7;
+ },
+ "Expected<T> must be checked before access or destruction.")
+ << "Unchecekd Expected<T> success value did not cause an abort().";
+}
+
+// Test Expected<T> in failure mode.
+TEST(Error, ExpectedInFailureMode) {
+ Expected<int> A = make_error<CustomError>(42);
+ EXPECT_FALSE(!!A) << "Expected with error value doesn't convert to 'false'";
+ Error E = A.takeError();
+ EXPECT_TRUE(E.isA<CustomError>()) << "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.
+TEST(Error, AccessExpectedInFailureMode) {
+ Expected<int> A = make_error<CustomError>(42);
+ EXPECT_DEATH(*A, "Expected<T> must be checked before access or destruction.")
+ << "Incorrect Expected error value";
+ consumeError(A.takeError());
+}
+
+// Check that an Expected instance with an error triggers an abort if
+// unhandled.
+// Test runs in debug mode only.
+TEST(Error, UnhandledExpectedInFailureMode) {
+ EXPECT_DEATH(
+ { Expected<int> A = make_error<CustomError>(42); },
+ "Expected<T> must be checked before access or destruction.")
+ << "Unchecked Expected<T> failure value did not cause an abort()";
+}
+
+// Test covariance of Expected.
+TEST(Error, ExpectedCovariance) {
+ class B {};
+ class D : public B {};
+
+ Expected<B *> A1(Expected<D *>(nullptr));
+ // Check A1 by converting to bool before assigning to it.
+ (void)!!A1;
+ A1 = Expected<D *>(nullptr);
+ // Check A1 again before destruction.
+ (void)!!A1;
+
+ Expected<std::unique_ptr<B>> A2(Expected<std::unique_ptr<D>>(nullptr));
+ // Check A2 by converting to bool before assigning to it.
+ (void)!!A2;
+ A2 = Expected<std::unique_ptr<D>>(nullptr);
+ // Check A2 again before destruction.
+ (void)!!A2;
+}
+
+// Test that the ExitOnError utility works as expected.
+TEST(Error, CantFailSuccess) {
+ cantFail(Error::success());
+
+ int X = cantFail(Expected<int>(42));
+ EXPECT_EQ(X, 42) << "Expected value modified by cantFail";
+
+ int Dummy = 42;
+ int &Y = cantFail(Expected<int &>(Dummy));
+ EXPECT_EQ(&Dummy, &Y) << "Reference mangled by cantFail";
+}
+
+// Test that cantFail results in a crash if you pass it a failure value.
+TEST(Error, CantFailDeath) {
+ EXPECT_DEATH(cantFail(make_error<StringError>("foo")), "")
+ << "cantFail(Error) did not cause an abort for failure value";
+
+ EXPECT_DEATH(cantFail(Expected<int>(make_error<StringError>("foo"))), "")
+ << "cantFail(Expected<int>) did not cause an abort for failure value";
+}
More information about the llvm-commits
mailing list