|
|
@@ -0,0 +1,428 @@ |
|
|
//===-------- 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 "compiler.h" |
|
|
#include "extensible_rtti.h" |
|
|
#include "stl_extras.h" |
|
|
|
|
|
#include <cassert> |
|
|
#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 ErrT, typename... ArgTs> |
|
|
friend Error make_error(ArgTs &&...Args); |
|
|
|
|
|
friend Error repackage_error(std::unique_ptr<ErrorInfoBase>); |
|
|
|
|
|
template <typename ErrT> friend std::unique_ptr<ErrT> error_cast(Error &); |
|
|
|
|
|
template <typename T> friend class Expected; |
|
|
|
|
|
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 = (reinterpret_cast<uintptr_t>(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; |
|
|
}; |
|
|
|
|
|
/// 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 Error(std::make_unique<ErrT>(std::forward<ArgTs>(Args)...)); |
|
|
} |
|
|
|
|
|
/// Construct an error of ErrT using a std::unique_ptr<ErrorInfoBase>. The |
|
|
/// primary use-case for this is 're-packaging' errors after inspecting them |
|
|
/// using error_cast, hence the name. |
|
|
inline Error repackage_error(std::unique_ptr<ErrorInfoBase> EIB) { |
|
|
return Error(std::move(EIB)); |
|
|
} |
|
|
|
|
|
/// If the argument is an error of type ErrT then this function unpacks it |
|
|
/// and returns a std::unique_ptr<ErrT>. Otherwise returns a nullptr and |
|
|
/// leaves the error untouched. Common usage looks like: |
|
|
/// |
|
|
/// \code{.cpp} |
|
|
/// if (Error E = foo()) { |
|
|
/// if (auto EV1 = error_cast<ErrorType1>(E)) { |
|
|
/// // use unwrapped EV1 value. |
|
|
/// } else if (EV2 = error_cast<ErrorType2>(E)) { |
|
|
/// // use unwrapped EV2 value. |
|
|
/// } ... |
|
|
/// } |
|
|
/// \endcode |
|
|
template <typename ErrT> std::unique_ptr<ErrT> error_cast(Error &Err) { |
|
|
static_assert(std::is_base_of<ErrorInfoBase, ErrT>::value, |
|
|
"ErrT is not an ErrorInfoBase subclass"); |
|
|
if (Err.isA<ErrT>()) |
|
|
return Err.takePayload<ErrT>(); |
|
|
return nullptr; |
|
|
} |
|
|
|
|
|
/// 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 { |
|
|
std::aligned_union_t<1, storage_type> TStorage; |
|
|
std::aligned_union_t<1, error_type> ErrorStorage; |
|
|
}; |
|
|
|
|
|
bool HasError : 1; |
|
|
bool Unchecked : 1; |
|
|
}; |
|
|
|
|
|
/// Consume an error without doing anything. |
|
|
inline void consumeError(Error Err) { |
|
|
if (Err) |
|
|
(void)error_cast<ErrorInfoBase>(Err); |
|
|
} |
|
|
|
|
|
/// Consumes success values. It is a programmatic error to call this function |
|
|
/// on a failure value. |
|
|
inline void cantFail(Error Err) { |
|
|
assert(!Err && "cantFail called on failure value"); |
|
|
consumeError(std::move(Err)); |
|
|
} |
|
|
|
|
|
/// 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) { |
|
|
if (auto EIB = error_cast<ErrorInfoBase>(Err)) |
|
|
return EIB->toString(); |
|
|
return {}; |
|
|
} |
|
|
|
|
|
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; |
|
|
}; |
|
|
|
|
|
} // end namespace __orc_rt |
|
|
|
|
|
#endif // ORC_RT_ERROR_H |