Skip to content

Commit e11f221

Browse files
committed
[Error] Add FileError helper; upgrade StringError behavior
FileError is meant to encapsulate both an Error and a file name/path. It should be used in cases where an Error occurs deep down the call chain, and we want to return it to the caller along with the file name. StringError was updated to display the error messages in different ways. These can be: 1. display the error_code message, and convert to the same error_code (ECError behavior) 2. display an arbitrary string, and convert to a provided error_code (current StringError behavior) 3. display both an error_code message and a string, in this order; and convert to the same error_code These behaviors can be triggered depending on the constructor. The goal is to use StringError as a base class, when a library needs to provide a explicit Error type. Differential Revision: https://reviews.llvm.org/D50807 llvm-svn: 341064
1 parent 2fab235 commit e11f221

File tree

3 files changed

+217
-5
lines changed

3 files changed

+217
-5
lines changed

llvm/include/llvm/Support/Error.h

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,10 @@ class ErrorInfoBase {
156156
/// they're moved-assigned or constructed from Success values that have already
157157
/// been checked. This enforces checking through all levels of the call stack.
158158
class LLVM_NODISCARD Error {
159-
// ErrorList needs to be able to yank ErrorInfoBase pointers out of this
160-
// class to add to the error list.
159+
// Both ErrorList and FileError need to be able to yank ErrorInfoBase
160+
// pointers out of this class to add to the error list.
161161
friend class ErrorList;
162+
friend class FileError;
162163

163164
// handleErrors needs to be able to set the Checked flag.
164165
template <typename... HandlerTs>
@@ -343,6 +344,8 @@ template <typename ErrT, typename... ArgTs> Error make_error(ArgTs &&... Args) {
343344
template <typename ThisErrT, typename ParentErrT = ErrorInfoBase>
344345
class ErrorInfo : public ParentErrT {
345346
public:
347+
using ParentErrT::ParentErrT; // inherit constructors
348+
346349
static const void *classID() { return &ThisErrT::ID; }
347350

348351
const void *dynamicClassID() const override { return &ThisErrT::ID; }
@@ -1110,10 +1113,33 @@ template <typename T> ErrorOr<T> expectedToErrorOr(Expected<T> &&E) {
11101113
/// StringError is useful in cases where the client is not expected to be able
11111114
/// to consume the specific error message programmatically (for example, if the
11121115
/// error message is to be presented to the user).
1116+
///
1117+
/// StringError can also be used when additional information is to be printed
1118+
/// along with a error_code message. Depending on the constructor called, this
1119+
/// class can either display:
1120+
/// 1. the error_code message (ECError behavior)
1121+
/// 2. a string
1122+
/// 3. the error_code message and a string
1123+
///
1124+
/// These behaviors are useful when subtyping is required; for example, when a
1125+
/// specific library needs an explicit error type. In the example below,
1126+
/// PDBError is derived from StringError:
1127+
///
1128+
/// @code{.cpp}
1129+
/// Expected<int> foo() {
1130+
/// return llvm::make_error<PDBError>(pdb_error_code::dia_failed_loading,
1131+
/// "Additional information");
1132+
/// }
1133+
/// @endcode
1134+
///
11131135
class StringError : public ErrorInfo<StringError> {
11141136
public:
11151137
static char ID;
11161138

1139+
// Prints EC + S and converts to EC
1140+
StringError(std::error_code EC, const Twine &S = Twine());
1141+
1142+
// Prints S and converts to EC
11171143
StringError(const Twine &S, std::error_code EC);
11181144

11191145
void log(raw_ostream &OS) const override;
@@ -1124,6 +1150,7 @@ class StringError : public ErrorInfo<StringError> {
11241150
private:
11251151
std::string Msg;
11261152
std::error_code EC;
1153+
const bool PrintMsgOnly = false;
11271154
};
11281155

11291156
/// Create formatted StringError object.
@@ -1138,6 +1165,61 @@ Error createStringError(std::error_code EC, char const *Fmt,
11381165

11391166
Error createStringError(std::error_code EC, char const *Msg);
11401167

1168+
/// This class wraps a filename and another Error.
1169+
///
1170+
/// In some cases, an error needs to live along a 'source' name, in order to
1171+
/// show more detailed information to the user.
1172+
class FileError final : public ErrorInfo<FileError> {
1173+
1174+
template <class Err>
1175+
friend Error createFileError(
1176+
std::string, Err,
1177+
typename std::enable_if<std::is_base_of<Error, Err>::value &&
1178+
!std::is_base_of<ErrorSuccess, Err>::value>::type
1179+
*);
1180+
1181+
public:
1182+
void log(raw_ostream &OS) const override {
1183+
assert(Err && !FileName.empty() && "Trying to log after takeError().");
1184+
OS << "'" << FileName << "': ";
1185+
Err->log(OS);
1186+
}
1187+
1188+
Error takeError() { return Error(std::move(Err)); }
1189+
1190+
std::error_code convertToErrorCode() const override;
1191+
1192+
// Used by ErrorInfo::classID.
1193+
static char ID;
1194+
1195+
private:
1196+
FileError(std::string F, std::unique_ptr<ErrorInfoBase> E) {
1197+
assert(E && "Cannot create FileError from Error success value.");
1198+
assert(!F.empty() &&
1199+
"The file name provided to FileError must not be empty.");
1200+
FileName = F;
1201+
Err = std::move(E);
1202+
}
1203+
1204+
static Error build(std::string F, Error E) {
1205+
return Error(std::unique_ptr<FileError>(new FileError(F, E.takePayload())));
1206+
}
1207+
1208+
std::string FileName;
1209+
std::unique_ptr<ErrorInfoBase> Err;
1210+
};
1211+
1212+
/// Concatenate a source file path and/or name with an Error. The resulting
1213+
/// Error is unchecked.
1214+
template <class Err>
1215+
inline Error createFileError(
1216+
std::string F, Err E,
1217+
typename std::enable_if<std::is_base_of<Error, Err>::value &&
1218+
!std::is_base_of<ErrorSuccess, Err>::value>::type
1219+
* = nullptr) {
1220+
return FileError::build(F, std::move(E));
1221+
}
1222+
11411223
/// Helper for check-and-exit error handling.
11421224
///
11431225
/// For tool use only. NOT FOR USE IN LIBRARY CODE.

llvm/lib/Support/Error.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace {
1919

2020
enum class ErrorErrorCode : int {
2121
MultipleErrors = 1,
22+
FileError,
2223
InconvertibleError
2324
};
2425

@@ -37,6 +38,8 @@ namespace {
3738
return "Inconvertible error value. An error has occurred that could "
3839
"not be converted to a known std::error_code. Please file a "
3940
"bug.";
41+
case ErrorErrorCode::FileError:
42+
return "A file error occurred.";
4043
}
4144
llvm_unreachable("Unhandled error code");
4245
}
@@ -53,6 +56,7 @@ char ErrorInfoBase::ID = 0;
5356
char ErrorList::ID = 0;
5457
char ECError::ID = 0;
5558
char StringError::ID = 0;
59+
char FileError::ID = 0;
5660

5761
void logAllUnhandledErrors(Error E, raw_ostream &OS, Twine ErrorBanner) {
5862
if (!E)
@@ -75,6 +79,11 @@ std::error_code inconvertibleErrorCode() {
7579
*ErrorErrorCat);
7680
}
7781

82+
std::error_code FileError::convertToErrorCode() const {
83+
return std::error_code(static_cast<int>(ErrorErrorCode::FileError),
84+
*ErrorErrorCat);
85+
}
86+
7887
Error errorCodeToError(std::error_code EC) {
7988
if (!EC)
8089
return Error::success();
@@ -103,10 +112,21 @@ void Error::fatalUncheckedError() const {
103112
}
104113
#endif
105114

106-
StringError::StringError(const Twine &S, std::error_code EC)
115+
StringError::StringError(std::error_code EC, const Twine &S)
107116
: Msg(S.str()), EC(EC) {}
108117

109-
void StringError::log(raw_ostream &OS) const { OS << Msg; }
118+
StringError::StringError(const Twine &S, std::error_code EC)
119+
: Msg(S.str()), EC(EC), PrintMsgOnly(true) {}
120+
121+
void StringError::log(raw_ostream &OS) const {
122+
if (PrintMsgOnly) {
123+
OS << Msg;
124+
} else {
125+
OS << EC.message();
126+
if (!Msg.empty())
127+
OS << (" " + Msg);
128+
}
129+
}
110130

111131
std::error_code StringError::convertToErrorCode() const {
112132
return EC;

llvm/unittests/Support/ErrorTest.cpp

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "llvm/ADT/Twine.h"
1414
#include "llvm/Support/Errc.h"
1515
#include "llvm/Support/ErrorHandling.h"
16+
#include "llvm/Support/ManagedStatic.h"
1617
#include "llvm/Testing/Support/Error.h"
1718
#include "gtest/gtest-spi.h"
1819
#include "gtest/gtest.h"
@@ -104,7 +105,7 @@ TEST(Error, CheckedSuccess) {
104105
EXPECT_FALSE(E) << "Unexpected error while testing Error 'Success'";
105106
}
106107

107-
// Test that unchecked succes values cause an abort.
108+
// Test that unchecked success values cause an abort.
108109
#if LLVM_ENABLE_ABI_BREAKING_CHECKS
109110
TEST(Error, UncheckedSuccess) {
110111
EXPECT_DEATH({ Error E = Error::success(); },
@@ -864,4 +865,113 @@ TEST(Error, C_API) {
864865
EXPECT_TRUE(GotCE) << "Failed to round-trip ErrorList via C API";
865866
}
866867

868+
TEST(Error, FileErrorTest) {
869+
#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
870+
EXPECT_DEATH(
871+
{
872+
Error S = Error::success();
873+
createFileError("file.bin", std::move(S));
874+
},
875+
"");
876+
#endif
877+
Error E1 = make_error<CustomError>(1);
878+
Error FE1 = createFileError("file.bin", std::move(E1));
879+
EXPECT_EQ(toString(std::move(FE1)).compare("'file.bin': CustomError {1}"), 0);
880+
881+
Error E2 = make_error<CustomError>(2);
882+
Error FE2 = createFileError("file.bin", std::move(E2));
883+
handleAllErrors(std::move(FE2), [](const FileError &F) {
884+
EXPECT_EQ(F.message().compare("'file.bin': CustomError {2}"), 0);
885+
});
886+
887+
Error E3 = make_error<CustomError>(3);
888+
Error FE3 = createFileError("file.bin", std::move(E3));
889+
auto E31 = handleErrors(std::move(FE3), [](std::unique_ptr<FileError> F) {
890+
return F->takeError();
891+
});
892+
handleAllErrors(std::move(E31), [](const CustomError &C) {
893+
EXPECT_EQ(C.message().compare("CustomError {3}"), 0);
894+
});
895+
896+
Error FE4 =
897+
joinErrors(createFileError("file.bin", make_error<CustomError>(41)),
898+
createFileError("file2.bin", make_error<CustomError>(42)));
899+
EXPECT_EQ(toString(std::move(FE4))
900+
.compare("'file.bin': CustomError {41}\n"
901+
"'file2.bin': CustomError {42}"),
902+
0);
903+
}
904+
905+
enum class test_error_code {
906+
unspecified = 1,
907+
error_1,
908+
error_2,
909+
};
910+
867911
} // end anon namespace
912+
913+
namespace std {
914+
template <>
915+
struct is_error_code_enum<test_error_code> : std::true_type {};
916+
} // namespace std
917+
918+
namespace {
919+
920+
const std::error_category &TErrorCategory();
921+
922+
inline std::error_code make_error_code(test_error_code E) {
923+
return std::error_code(static_cast<int>(E), TErrorCategory());
924+
}
925+
926+
class TestDebugError : public ErrorInfo<TestDebugError, StringError> {
927+
public:
928+
using ErrorInfo<TestDebugError, StringError >::ErrorInfo; // inherit constructors
929+
TestDebugError(const Twine &S) : ErrorInfo(S, test_error_code::unspecified) {}
930+
static char ID;
931+
};
932+
933+
class TestErrorCategory : public std::error_category {
934+
public:
935+
const char *name() const noexcept override { return "error"; }
936+
std::string message(int Condition) const override {
937+
switch (static_cast<test_error_code>(Condition)) {
938+
case test_error_code::unspecified:
939+
return "An unknown error has occurred.";
940+
case test_error_code::error_1:
941+
return "Error 1.";
942+
case test_error_code::error_2:
943+
return "Error 2.";
944+
}
945+
llvm_unreachable("Unrecognized test_error_code");
946+
}
947+
};
948+
949+
static llvm::ManagedStatic<TestErrorCategory> TestErrCategory;
950+
const std::error_category &TErrorCategory() { return *TestErrCategory; }
951+
952+
char TestDebugError::ID;
953+
954+
TEST(Error, SubtypeStringErrorTest) {
955+
auto E1 = make_error<TestDebugError>(test_error_code::error_1);
956+
EXPECT_EQ(toString(std::move(E1)).compare("Error 1."), 0);
957+
958+
auto E2 = make_error<TestDebugError>(test_error_code::error_1,
959+
"Detailed information");
960+
EXPECT_EQ(toString(std::move(E2)).compare("Error 1. Detailed information"),
961+
0);
962+
963+
auto E3 = make_error<TestDebugError>(test_error_code::error_2);
964+
handleAllErrors(std::move(E3), [](const TestDebugError &F) {
965+
EXPECT_EQ(F.message().compare("Error 2."), 0);
966+
});
967+
968+
auto E4 = joinErrors(make_error<TestDebugError>(test_error_code::error_1,
969+
"Detailed information"),
970+
make_error<TestDebugError>(test_error_code::error_2));
971+
EXPECT_EQ(toString(std::move(E4))
972+
.compare("Error 1. Detailed information\n"
973+
"Error 2."),
974+
0);
975+
}
976+
977+
} // namespace

0 commit comments

Comments
 (0)