From 800de0381343dc1da8834e81dee40db907a581bc Mon Sep 17 00:00:00 2001 From: "Michael J. Spencer" Date: Wed, 19 Dec 2012 00:51:07 +0000 Subject: [PATCH] [Core][ErrorOr] Add support for user error data. llvm-svn: 170483 --- lld/CMakeLists.txt | 4 + lld/include/lld/Core/ErrorOr.h | 351 ++++++++++++++++++++++++++--------------- lld/unittests/CMakeLists.txt | 16 ++ lld/unittests/ErrorOrTest.cpp | 75 +++++++++ 4 files changed, 322 insertions(+), 124 deletions(-) create mode 100644 lld/unittests/CMakeLists.txt create mode 100644 lld/unittests/ErrorOrTest.cpp diff --git a/lld/CMakeLists.txt b/lld/CMakeLists.txt index 6211f63..ead9a6b 100644 --- a/lld/CMakeLists.txt +++ b/lld/CMakeLists.txt @@ -144,3 +144,7 @@ add_subdirectory(lib) add_subdirectory(tools) add_subdirectory(test) + +if (LLVM_INCLUDE_TESTS AND NOT LLD_BUILT_STANDALONE) + add_subdirectory(unittests) +endif() diff --git a/lld/include/lld/Core/ErrorOr.h b/lld/include/lld/Core/ErrorOr.h index 403b4a5..0fd7d96 100644 --- a/lld/include/lld/Core/ErrorOr.h +++ b/lld/include/lld/Core/ErrorOr.h @@ -21,88 +21,52 @@ #include "llvm/Support/AlignOf.h" #include "llvm/Support/system_error.h" +#include #include #include namespace lld { -template -class ErrorOrBase { - static const bool isRef = std::is_reference::value; - typedef std::reference_wrapper::type> wrap; - -public: - typedef typename - std::conditional< isRef - , wrap - , T - >::type storage_type; - -private: - typedef T &reference; - typedef typename std::remove_reference::type *pointer; +struct ErrorHolderBase { + llvm::error_code Error; + std::atomic RefCount; + bool HasUserData; - ErrorOrBase(const ErrorOrBase&) LLVM_DELETED_FUNCTION; - ErrorOrBase &operator =(const ErrorOrBase&) LLVM_DELETED_FUNCTION; - ErrorOrBase(ErrorOrBase &&other) LLVM_DELETED_FUNCTION; - ErrorOrBase &operator =(ErrorOrBase &&other) LLVM_DELETED_FUNCTION; - -public: - ErrorOrBase() : _error(llvm::make_error_code(llvm::errc::invalid_argument)) {} + ErrorHolderBase() : RefCount(1) {} - ErrorOrBase(llvm::error_code ec) { - if (!_error) - get()->~storage_type(); - _error = ec; - } - - ErrorOrBase(T t) : _error(llvm::error_code::success()) { - new (get()) storage_type(t); - } - - ~ErrorOrBase() { - if (!_error) - get()->~storage_type(); + void aquire() { + ++RefCount; } - /// \brief Return false if there is an error. - operator bool() { - return !_error; + void release() { + if (RefCount.fetch_sub(1) == 1) + delete this; } - operator llvm::error_code() { - return _error; - } - - operator reference() { - return *get(); - } - - pointer operator ->() { - return toPointer(get()); - } - - reference operator *() { - return *get(); - } +protected: + virtual ~ErrorHolderBase() {} +}; -private: - pointer toPointer(pointer t) { - return t; - } +template +struct ErrorHolder : ErrorHolderBase { + ErrorHolder(T &&UD) : UserData(UD) {} + T UserData; +}; - pointer toPointer(wrap *t) { - return &t->get(); - } +template struct ErrorOrUserDataTraits : std::false_type {}; -protected: - storage_type *get() { - assert(!_error && "T not valid!"); - return reinterpret_cast(_t.buffer); - } +template +typename std::enable_if< std::is_constructible::value + , typename std::remove_reference::type>::type && + moveIfMoveConstructible(V &t) { + return std::move(t); +} - llvm::error_code _error; - llvm::AlignedCharArrayUnion _t; -}; +template +typename std::enable_if< !std::is_constructible::value + , typename std::remove_reference::type>::type & +moveIfMoveConstructible(V &t) { + return t; +} /// \brief Represents either an error or a value T. /// @@ -110,7 +74,7 @@ protected: /// 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 that describes why it failed. +/// have an error_code and optional user data that describes why it failed. /// /// It is used like the following. /// \code @@ -123,6 +87,45 @@ protected: /// 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 lld { +/// template<> +/// struct ErrorOrUserDataTraits : std::true_type { +/// static error_code error() { +/// return make_error_code(errc::invalid_argument); +/// } +/// }; +/// } // end namespace lld +/// +/// using namespace lld; +/// +/// 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. @@ -133,81 +136,181 @@ protected: /// reference. /// /// T cannot be a rvalue reference. -template::storage_type>::value> -class ErrorOr; - template -class ErrorOr : public ErrorOrBase { - ErrorOr(const ErrorOr &other) LLVM_DELETED_FUNCTION; - ErrorOr &operator =(const ErrorOr &other) LLVM_DELETED_FUNCTION; +class ErrorOr { + static const bool isRef = std::is_reference::value; + typedef std::reference_wrapper::type> wrap; + +public: + typedef typename + std::conditional< isRef + , wrap + , T + >::type storage_type; + +private: + typedef T &reference; + typedef typename std::remove_reference::type *pointer; + public: - ErrorOr(llvm::error_code ec) : ErrorOrBase(ec) {} - ErrorOr(T t) : ErrorOrBase(t) {} - ErrorOr(ErrorOr &&other) : ErrorOrBase() { - // Get the other value. - if (!other._error) - new (this->get()) - typename ErrorOrBase::storage_type(std::move(*other.get())); + 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 + std::enable_if::value>::type* = 0) + : HasError(true), IsValid(true) { + Error = new ErrorHolder(std::move(UD)); + Error->Error = ErrorOrUserDataTraits::error(); + Error->HasUserData = true; + } - // Get the other error. - this->_error = other._error; + ErrorOr(T t) : HasError(false), IsValid(true) { + new (get()) storage_type(moveIfMoveConstructible(t)); + } - // Make sure other doesn't try to delete its storage. - other._error = llvm::make_error_code(llvm::errc::invalid_argument); + 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->~ErrorOr(); + new (this) ErrorOr(other); + + return *this; + } + + ErrorOr(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(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) { - // Delete any existing value. - if (!this->_error) - this->get()->~storage_type(); + if (this == &other) + return *this; + + this->~ErrorOr(); + new (this) ErrorOr(other); + + return *this; + } + + ~ErrorOr() { + if (!IsValid) + return; + if (HasError) + Error->release(); + else + get()->~storage_type(); + } - // Get the other value. - if (!other._error) - new (this->get()) - typename ErrorOrBase::storage_type(std::move(*other.get())); + 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; + } - // Get the other error. - this->_error = other._error; + typedef void (*unspecified_bool_type)(); + static void unspecified_bool_true() {} - // Make sure other doesn't try to delete its storage. - other._error = llvm::make_error_code(llvm::errc::invalid_argument); + /// \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; } -}; -template -class ErrorOr : public ErrorOrBase { - static_assert(std::is_copy_constructible::value, - "T must be copy or move constructible!"); + operator llvm::error_code() const { + assert(IsValid && "Can't do anything on a default constructed ErrorOr!"); + return HasError ? Error->Error : llvm::error_code::success(); + } - ErrorOr(ErrorOr &&other) LLVM_DELETED_FUNCTION; - ErrorOr &operator =(ErrorOr &&other) LLVM_DELETED_FUNCTION; -public: - ErrorOr(llvm::error_code ec) : ErrorOrBase(ec) {} - ErrorOr(T t) : ErrorOrBase(t) {} - ErrorOr(const ErrorOr &other) : ErrorOrBase() { - // Get the other value. - if (!other._error) - new (this->get()) typename ErrorOrBase::storage_type(*other.get()); + pointer operator ->() { + return toPointer(get()); + } + + reference operator *() { + return *get(); + } - // Get the other error. - this->_error = other._error; +private: + pointer toPointer(pointer t) { + return t; } - ErrorOr &operator =(const ErrorOr &other) { - // Delete any existing value. - if (!this->_error) - this->get()->~storage_type(); + pointer toPointer(wrap *t) { + return &t->get(); + } - // Get the other value. - if (!other._error) - new (this->get()) typename ErrorOrBase::storage_type(*other.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(_t.buffer); + } - // Get the other error. - this->_error = other._error; + 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(_t.buffer); } + + union { + llvm::AlignedCharArrayUnion _t; + ErrorHolderBase *Error; + }; + bool HasError : 1; + bool IsValid : 1; }; + +template +typename std::enable_if::value || + llvm::is_error_condition_enum::value, bool>::type +operator ==(ErrorOr &Err, E Code) { + return error_code(Err) == Code; } +} // end namespace lld #endif diff --git a/lld/unittests/CMakeLists.txt b/lld/unittests/CMakeLists.txt new file mode 100644 index 0000000..f995f03 --- /dev/null +++ b/lld/unittests/CMakeLists.txt @@ -0,0 +1,16 @@ +add_custom_target(LLDUnitTests) +set_target_properties(LLDUnitTests PROPERTIES FOLDER "lld tests") + +# add_lld_unittest(test_dirname file1.cpp file2.cpp) +# +# Will compile the list of files together and link against lld +# Produces a binary named 'basename(test_dirname)'. +function(add_lld_unittest test_dirname) + add_unittest(LLDUnitTests ${test_dirname} ${ARGN}) +endfunction() + +set(LLVM_LINK_COMPONENTS + support + ) + +add_lld_unittest(CoreTests ErrorOrTest.cpp) diff --git a/lld/unittests/ErrorOrTest.cpp b/lld/unittests/ErrorOrTest.cpp new file mode 100644 index 0000000..d1d7bd9 --- /dev/null +++ b/lld/unittests/ErrorOrTest.cpp @@ -0,0 +1,75 @@ +//===- 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 "lld/Core/ErrorOr.h" + +#include "gtest/gtest.h" + +#include + +using namespace lld; +using namespace llvm; + +namespace { + +ErrorOr t1() {return 1;} +ErrorOr t2() {return make_error_code(errc::invalid_argument);} + +TEST(ErrorOr, SimpleValue) { + auto 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"); +} + +ErrorOr> t3() { + return std::unique_ptr(new int(3)); +} + +TEST(ErrorOr, Types) { + int x; + ErrorOr a(x); + *a = 42; + EXPECT_EQ(42, *a); + + // Move only types. + EXPECT_EQ(3, **t3()); +} +} // end anon namespace + +struct InvalidArgError { + InvalidArgError() {} + InvalidArgError(std::string S) : ArgName(S) {} + std::string ArgName; +}; + +namespace lld { +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) { + auto a = t4(); + EXPECT_EQ(errc::invalid_argument, a); + EXPECT_EQ("adena", t4().getError().ArgName); +} +} // end anon namespace -- 2.7.4