Index: include/llvm/Support/Error.h =================================================================== --- include/llvm/Support/Error.h +++ include/llvm/Support/Error.h @@ -27,6 +27,7 @@ #include "llvm/Support/ErrorOr.h" #include "llvm/Support/Format.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/ManagedStatic.h" #include #include #include @@ -156,9 +157,10 @@ /// 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 LLVM_NODISCARD Error { - // ErrorList needs to be able to yank ErrorInfoBase pointers out of this - // class to add to the error list. + // Both ErrorList and FileError needs to be able to yank ErrorInfoBase + // pointers out of this class to add to the error list. friend class ErrorList; + friend class FileError; // handleErrors needs to be able to set the Checked flag. template @@ -546,7 +548,7 @@ /// 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. + /// be made on the Expected value. Error takeError() { #if LLVM_ENABLE_ABI_BREAKING_CHECKS Unchecked = false; @@ -928,7 +930,7 @@ /// foo(Aggressive), /// []() { return foo(Conservative); }, /// [](AggressiveStrategyError&) { -/// // Implicitly conusme this - we'll recover by using a conservative +/// // Implicitly consume this - we'll recover by using a conservative /// // strategy. /// }); /// @@ -1138,6 +1140,118 @@ Error createStringError(std::error_code EC, char const *Msg); +/// This class wraps a filename and another Error. +/// +/// In some cases, an error needs to live along a 'source' name, in order to +/// show more detailed information to the user. +class FileError final : public ErrorInfo { + + template + friend Error createFileError( + std::string, Err, + typename std::enable_if::value && + !std::is_base_of::value>::type + *); + +public: + void log(raw_ostream &OS) const override { + assert(Err && !FileName.empty() && "Trying to log after takeError()."); + OS << "'" << FileName << "': "; + Err->log(OS); + } + + Error takeError() { return Error(std::move(Err)); } + + std::error_code convertToErrorCode() const override; + + // Used by ErrorInfo::classID. + static char ID; + +private: + + FileError(std::string F, std::unique_ptr E) { + assert(E && "Cannot create FileError from Error success value."); + assert(!F.empty() && "The file name provided to FileError must not be empty."); + FileName = F; + Err = std::move(E); + } + + static Error build(std::string F, Error E) { + return Error(std::unique_ptr(new FileError(F, E.takePayload()))); + } + + std::string FileName; + std::unique_ptr Err; +}; + +/// Concatenate a source file path and/or name with an Error. The resulting +/// Error is unchecked. +template +inline Error createFileError( + std::string F, Err E, + typename std::enable_if::value && + !std::is_base_of::value>::type + * = nullptr) { + return FileError::build(F, std::move(E)); +} + +/// This class provides the combined features of ECError and StringError, while +/// allowing for sub typing. +/// +/// DebugErrorInfo<> is useful when an Error sub type is required, for later +/// inspection, along with an std::error_code and an optional String, such as: +/// +/// @code{.cpp} +/// Expected foo() { +/// return llvm::make_error(pdb_error_code::dia_failed_loading, +/// "Additional information"); +/// } +/// @endcode +/// +/// When implementing the class, the following shall be provided: +/// - a enumeration type, starting at 1, where a 'unspecified' +/// element should be present +/// - a custom std::error_category type +/// - a Error subtype of DebugErrorInfo<> +/// +template +class DebugErrorInfo : public ErrorInfo { +public: + DebugErrorInfo(EC C) : DebugErrorInfo(C, "") {} + DebugErrorInfo(Twine Context) + : DebugErrorInfo(EC::unspecified, Context.str()) {} + DebugErrorInfo(EC C, StringRef Context) : Code(C) { + if (Code == EC::unspecified) + ErrMsg += Context; + else { + ErrMsg += convertToErrorCode().message(); + if (!Context.empty()) + ErrMsg += (" " + Context).str(); + } + } + + void log(raw_ostream &OS) const override { OS << ErrMsg; } + StringRef getErrorMessage() const { return ErrMsg; } + EC getError() const { return Code; } + + static ManagedStatic Category; + + std::error_code convertToErrorCode() const override { + return std::error_code(static_cast(Code), *Category); + } + + // Used by ErrorInfo::classID. + static char ID; + +protected: + std::string ErrMsg; + EC Code; +}; + +#define DEFINE_DEBUGERRORINFO(C, EC, Cat) \ + template <> char DebugErrorInfo::ID; \ + template <> ManagedStatic DebugErrorInfo::Category; + /// Helper for check-and-exit error handling. /// /// For tool use only. NOT FOR USE IN LIBRARY CODE. Index: unittests/Support/ErrorTest.cpp =================================================================== --- unittests/Support/ErrorTest.cpp +++ unittests/Support/ErrorTest.cpp @@ -104,7 +104,7 @@ EXPECT_FALSE(E) << "Unexpected error while testing Error 'Success'"; } -// Test that unchecked succes values cause an abort. +// Test that unchecked success values cause an abort. #if LLVM_ENABLE_ABI_BREAKING_CHECKS TEST(Error, UncheckedSuccess) { EXPECT_DEATH({ Error E = Error::success(); }, @@ -864,4 +864,96 @@ EXPECT_TRUE(GotCE) << "Failed to round-trip ErrorList via C API"; } +TEST(Error, FileErrorTest) { + EXPECT_DEATH( + { + Error S = Error::success(); + createFileError("file.bin", std::move(S)); + }, + ""); + + Error E1 = make_error(1); + Error FE1 = createFileError("file.bin", std::move(E1)); + EXPECT_EQ(toString(std::move(FE1)).compare("'file.bin': CustomError {1}"), 0); + + Error E2 = make_error(2); + Error FE2 = createFileError("file.bin", std::move(E2)); + handleAllErrors(std::move(FE2), [](const FileError &F) { + EXPECT_EQ(F.message().compare("'file.bin': CustomError {2}"), 0); + }); + + Error E3 = make_error(3); + Error FE3 = createFileError("file.bin", std::move(E3)); + auto E31 = handleErrors(std::move(FE3), [](std::unique_ptr F) { + return F->takeError(); + }); + handleAllErrors(std::move(E31), [](const CustomError &C) { + EXPECT_EQ(C.message().compare("CustomError {3}"), 0); + }); + + Error FE4 = + joinErrors(createFileError("file.bin", make_error(41)), + createFileError("file2.bin", make_error(42))); + EXPECT_EQ(toString(std::move(FE4)) + .compare("'file.bin': CustomError {41}\n" + "'file2.bin': CustomError {42}"), + 0); +} + +enum class test_error_code { + unspecified = 1, + error_1, + error_2, +}; + +class TestErrorCategory : public std::error_category { +public: + const char *name() const noexcept override { return "error"; } + std::string message(int Condition) const override { + switch (static_cast(Condition)) { + case test_error_code::unspecified: + return "An unknown error has occurred."; + case test_error_code::error_1: + return "Error 1."; + case test_error_code::error_2: + return "Error 2."; + } + llvm_unreachable("Unrecognized test_error_code"); + } +}; + +class TestDebugError : public DebugErrorInfo { +public: + using DebugErrorInfo::DebugErrorInfo; +}; + +} // end anon namespace + +DEFINE_DEBUGERRORINFO(TestDebugError, test_error_code, TestErrorCategory) + +namespace { + +TEST(Error, DebugInfoErrorTest) { + auto E1 = make_error(test_error_code::error_1); + EXPECT_EQ(toString(std::move(E1)).compare("Error 1."), 0); + + auto E2 = make_error(test_error_code::error_1, + "Detailed information"); + EXPECT_EQ(toString(std::move(E2)).compare("Error 1. Detailed information"), + 0); + + auto E3 = make_error(test_error_code::error_2); + handleAllErrors(std::move(E3), [](const TestDebugError &F) { + EXPECT_EQ(F.message().compare("Error 2."), 0); + }); + + auto E4 = joinErrors(make_error(test_error_code::error_1, + "Detailed information"), + make_error(test_error_code::error_2)); + EXPECT_EQ(toString(std::move(E4)) + .compare("Error 1. Detailed information\n" + "Error 2."), + 0); +} + } // end anon namespace