From 779c4247f76828fd2ce07cbb064003910709be30 Mon Sep 17 00:00:00 2001 From: "Michael J. Spencer" Date: Sun, 20 Jan 2013 20:32:30 +0000 Subject: [PATCH] [Support] Port ErrorOr from lld to C++03. llvm-svn: 172991 --- llvm/include/llvm/Support/Compiler.h | 27 ++ llvm/include/llvm/Support/ErrorOr.h | 342 ++++++++++++++++++++++++ llvm/include/llvm/Support/type_traits.h | 4 + llvm/unittests/Support/CMakeLists.txt | 1 + llvm/unittests/Support/ErrorOrTest.cpp | 78 ++++++ 5 files changed, 452 insertions(+) create mode 100644 llvm/include/llvm/Support/ErrorOr.h create mode 100644 llvm/unittests/Support/ErrorOrTest.cpp diff --git a/llvm/include/llvm/Support/Compiler.h b/llvm/include/llvm/Support/Compiler.h index ebc1fadc1496..2d49d4caf499 100644 --- a/llvm/include/llvm/Support/Compiler.h +++ b/llvm/include/llvm/Support/Compiler.h @@ -42,6 +42,33 @@ #define LLVM_HAS_RVALUE_REFERENCE_THIS 0 #endif +/// \macro LLVM_HAS_CXX11_TYPETRAITS +/// \brief Does the compiler have the C++11 type traits. +/// +/// #include +/// +/// * enable_if +/// * {true,false}_type +/// * is_constructible +/// * etc... +#if defined(__GXX_EXPERIMENTAL_CXX0X__) \ + || (defined(_MSC_VER) && _MSC_VER >= 1600) +#define LLVM_HAS_CXX11_TYPETRAITS 1 +#else +#define LLVM_HAS_CXX11_TYPETRAITS 0 +#endif + +/// \macro LLVM_HAS_CXX11_STDLIB +/// \brief Does the compiler have the C++11 standard library. +/// +/// Implies LLVM_HAS_RVALUE_REFERENCES, LLVM_HAS_CXX11_TYPETRAITS +#if defined(__GXX_EXPERIMENTAL_CXX0X__) \ + || (defined(_MSC_VER) && _MSC_VER >= 1600) +#define LLVM_HAS_CXX11_STDLIB 1 +#else +#define LLVM_HAS_CXX11_STDLIB 0 +#endif + /// llvm_move - Expands to ::std::move if the compiler supports /// r-value references; otherwise, expands to the argument. #if LLVM_HAS_RVALUE_REFERENCES diff --git a/llvm/include/llvm/Support/ErrorOr.h b/llvm/include/llvm/Support/ErrorOr.h new file mode 100644 index 000000000000..f74ff2166cb8 --- /dev/null +++ b/llvm/include/llvm/Support/ErrorOr.h @@ -0,0 +1,342 @@ +//===- llvm/Support/ErrorOr.h - Error Smart Pointer -----------------------===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// +/// Provides ErrorOr smart pointer. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_ERROR_OR_H +#define LLVM_SUPPORT_ERROR_OR_H + +#include "llvm/Support/AlignOf.h" +#include "llvm/Support/system_error.h" +#include "llvm/Support/type_traits.h" + +#include +#if LLVM_HAS_CXX11_TYPETRAITS +#include +#endif + +namespace llvm { +struct ErrorHolderBase { + error_code Error; + uint16_t RefCount; + bool HasUserData; + + ErrorHolderBase() : RefCount(1) {} + + void aquire() { + ++RefCount; + } + + void release() { + if (--RefCount == 0) + delete this; + } + +protected: + virtual ~ErrorHolderBase() {} +}; + +template +struct ErrorHolder : ErrorHolderBase { +#if LLVM_HAS_RVALUE_REFERENCES + ErrorHolder(T &&UD) : UserData(llvm_move(UD)) {} +#else + ErrorHolder(T &UD) : UserData(UD) {} +#endif + T UserData; +}; + +template struct ErrorOrUserDataTraits : llvm::false_type {}; + +#if LLVM_HAS_CXX11_TYPETRAITS && LLVM_HAS_RVALUE_REFERENCES +template +typename std::enable_if< std::is_constructible::value + , typename std::remove_reference::type>::type && + moveIfMoveConstructible(V &Val) { + return std::move(Val); +} + +template +typename std::enable_if< !std::is_constructible::value + , typename std::remove_reference::type>::type & +moveIfMoveConstructible(V &Val) { + return Val; +} +#else +template +V &moveIfMoveConstructible(V &Val) { + return Val; +} +#endif + +/// \brief Stores a reference that can be changed. +template +class ReferenceStorage { + T *Storage; + +public: + ReferenceStorage(T &Ref) : Storage(&Ref) {} + + operator T &() const { return *Storage; } + T &get() const { return *Storage; } +}; + +/// \brief Represents either an error or a value T. +/// +/// ErrorOr is a pointer-like class that represents the result of an +/// operation. The result is either an error, or a value of type T. This is +/// designed to emulate the usage of returning a pointer where nullptr indicates +/// failure. However instead of just knowing that the operation failed, we also +/// have an error_code and optional user data that describes why it failed. +/// +/// It is used like the following. +/// \code +/// ErrorOr getBuffer(); +/// void handleError(error_code ec); +/// +/// auto buffer = getBuffer(); +/// if (!buffer) +/// handleError(buffer); +/// buffer->write("adena"); +/// \endcode +/// +/// ErrorOr also supports user defined data for specific error_codes. To use +/// this feature you must first add a template specialization of +/// ErrorOrUserDataTraits derived from std::true_type for your type in the lld +/// namespace. This specialization must have a static error_code error() +/// function that returns the error_code this data is used with. +/// +/// getError() may be called to get either the stored user data, or +/// a default constructed UserData if none was stored. +/// +/// Example: +/// \code +/// struct InvalidArgError { +/// InvalidArgError() {} +/// InvalidArgError(std::string S) : ArgName(S) {} +/// std::string ArgName; +/// }; +/// +/// namespace llvm { +/// template<> +/// struct ErrorOrUserDataTraits : std::true_type { +/// static error_code error() { +/// return make_error_code(errc::invalid_argument); +/// } +/// }; +/// } // end namespace llvm +/// +/// using namespace llvm; +/// +/// ErrorOr foo() { +/// return InvalidArgError("adena"); +/// } +/// +/// int main() { +/// auto a = foo(); +/// if (!a && error_code(a) == errc::invalid_argument) +/// llvm::errs() << a.getError().ArgName << "\n"; +/// } +/// \endcode +/// +/// An implicit conversion to bool provides a way to check if there was an +/// error. The unary * and -> operators provide pointer like access to the +/// value. Accessing the value when there is an error has undefined behavior. +/// +/// When T is a reference type the behaivor is slightly different. The reference +/// is held in a std::reference_wrapper::type>, and +/// there is special handling to make operator -> work as if T was not a +/// reference. +/// +/// T cannot be a rvalue reference. +template +class ErrorOr { + static const bool isRef = is_reference::value; + typedef ReferenceStorage::type> wrap; + +public: + typedef typename + conditional< isRef + , wrap + , T + >::type storage_type; + +private: + typedef T &reference; + typedef typename remove_reference::type *pointer; + +public: + ErrorOr() : IsValid(false) {} + + ErrorOr(llvm::error_code EC) : HasError(true), IsValid(true) { + Error = new ErrorHolderBase; + Error->Error = EC; + Error->HasUserData = false; + } + + template + ErrorOr(UserDataT UD, typename + enable_if_c::value>::type* = 0) + : HasError(true), IsValid(true) { + Error = new ErrorHolder(llvm_move(UD)); + Error->Error = ErrorOrUserDataTraits::error(); + Error->HasUserData = true; + } + + ErrorOr(T Val) : HasError(false), IsValid(true) { + new (get()) storage_type(moveIfMoveConstructible(Val)); + } + + ErrorOr(const ErrorOr &Other) : IsValid(false) { + // Construct an invalid ErrorOr if other is invalid. + if (!Other.IsValid) + return; + if (!Other.HasError) { + // Get the other value. + new (get()) storage_type(*Other.get()); + HasError = false; + } else { + // Get other's error. + Error = Other.Error; + HasError = true; + Error->aquire(); + } + + IsValid = true; + } + + ErrorOr &operator =(const ErrorOr &Other) { + if (this == &Other) + return *this; + + this->~ErrorOr(); + new (this) ErrorOr(Other); + + return *this; + } + +#if LLVM_HAS_RVALUE_REFERENCES + ErrorOr(ErrorOr &&Other) : IsValid(false) { + // Construct an invalid ErrorOr if other is invalid. + if (!Other.IsValid) + return; + if (!Other.HasError) { + // Get the other value. + IsValid = true; + new (get()) storage_type(std::move(*Other.get())); + HasError = false; + // Tell other not to do any destruction. + Other.IsValid = false; + } else { + // Get other's error. + Error = Other.Error; + HasError = true; + // Tell other not to do any destruction. + Other.IsValid = false; + } + + IsValid = true; + } + + ErrorOr &operator =(ErrorOr &&Other) { + if (this == &Other) + return *this; + + this->~ErrorOr(); + new (this) ErrorOr(std::move(Other)); + + return *this; + } + + ~ErrorOr() { + if (!IsValid) + return; + if (HasError) + Error->release(); + else + get()->~storage_type(); + } +#endif + + template + ET getError() const { + assert(IsValid && "Cannot get the error of a default constructed ErrorOr!"); + assert(HasError && "Cannot get an error if none exists!"); + assert(ErrorOrUserDataTraits::error() == Error->Error && + "Incorrect user error data type for error!"); + if (!Error->HasUserData) + return ET(); + return reinterpret_cast*>(Error)->UserData; + } + + typedef void (*unspecified_bool_type)(); + static void unspecified_bool_true() {} + + /// \brief Return false if there is an error. + operator unspecified_bool_type() const { + assert(IsValid && "Can't do anything on a default constructed ErrorOr!"); + return HasError ? 0 : unspecified_bool_true; + } + + operator llvm::error_code() const { + assert(IsValid && "Can't do anything on a default constructed ErrorOr!"); + return HasError ? Error->Error : llvm::error_code::success(); + } + + pointer operator ->() { + return toPointer(get()); + } + + reference operator *() { + return *get(); + } + +private: + pointer toPointer(pointer Val) { + return Val; + } + + pointer toPointer(wrap *Val) { + return &Val->get(); + } + +protected: + storage_type *get() { + assert(IsValid && "Can't do anything on a default constructed ErrorOr!"); + assert(!HasError && "Cannot get value when an error exists!"); + return reinterpret_cast(TStorage.buffer); + } + + const storage_type *get() const { + assert(IsValid && "Can't do anything on a default constructed ErrorOr!"); + assert(!HasError && "Cannot get value when an error exists!"); + return reinterpret_cast(TStorage.buffer); + } + + union { + AlignedCharArrayUnion TStorage; + ErrorHolderBase *Error; + }; + bool HasError : 1; + bool IsValid : 1; +}; + +template +typename enable_if_c::value || + is_error_condition_enum::value, bool>::type +operator ==(ErrorOr &Err, E Code) { + return error_code(Err) == Code; +} +} // end namespace llvm + +#endif diff --git a/llvm/include/llvm/Support/type_traits.h b/llvm/include/llvm/Support/type_traits.h index f9306395fce3..db43ccfece14 100644 --- a/llvm/include/llvm/Support/type_traits.h +++ b/llvm/include/llvm/Support/type_traits.h @@ -145,6 +145,10 @@ template struct is_pointer : true_type {}; template struct is_pointer : true_type {}; template struct is_pointer : true_type {}; +/// \brief Metafunction that determines wheather the given type is a reference. +template struct is_reference : false_type {}; +template struct is_reference : true_type {}; + /// \brief Metafunction that determines whether the given type is either an /// integral type or an enumeration type. /// diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt index dd42585b0604..b4b982f2ef23 100644 --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -13,6 +13,7 @@ add_llvm_unittest(SupportTests ConstantRangeTest.cpp DataExtractorTest.cpp EndianTest.cpp + ErrorOrTest.cpp FileOutputBufferTest.cpp IntegersSubsetTest.cpp LeakDetectorTest.cpp diff --git a/llvm/unittests/Support/ErrorOrTest.cpp b/llvm/unittests/Support/ErrorOrTest.cpp new file mode 100644 index 000000000000..1f80aa0cdf8b --- /dev/null +++ b/llvm/unittests/Support/ErrorOrTest.cpp @@ -0,0 +1,78 @@ +//===- unittests/ErrorOrTest.cpp - ErrorOr.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/ErrorOr.h" + +#include "gtest/gtest.h" + +#include + +using namespace llvm; + +namespace { + +ErrorOr t1() {return 1;} +ErrorOr t2() {return make_error_code(errc::invalid_argument);} + +TEST(ErrorOr, SimpleValue) { + ErrorOr a = t1(); + EXPECT_TRUE(a); + EXPECT_EQ(1, *a); + + a = t2(); + EXPECT_FALSE(a); + EXPECT_EQ(errc::invalid_argument, a); + EXPECT_DEBUG_DEATH(*a, "Cannot get value when an error exists"); +} + +#if LLVM_HAS_CXX11_STDLIB +ErrorOr > t3() { + return std::unique_ptr(new int(3)); +} +#endif + +TEST(ErrorOr, Types) { + int x; + ErrorOr a(x); + *a = 42; + EXPECT_EQ(42, x); + +#if LLVM_HAS_CXX11_STDLIB + // Move only types. + EXPECT_EQ(3, **t3()); +#endif +} +} // end anon namespace + +struct InvalidArgError { + InvalidArgError() {} + InvalidArgError(std::string S) : ArgName(S) {} + std::string ArgName; +}; + +namespace llvm { +template<> +struct ErrorOrUserDataTraits : std::true_type { + static error_code error() { + return make_error_code(errc::invalid_argument); + } +}; +} // end namespace lld + +ErrorOr t4() { + return InvalidArgError("adena"); +} + +namespace { +TEST(ErrorOr, UserErrorData) { + ErrorOr a = t4(); + EXPECT_EQ(errc::invalid_argument, a); + EXPECT_EQ("adena", t4().getError().ArgName); +} +} // end anon namespace