[Support] Add ExitOnError utility to support tools that use the exit-on-error
authorLang Hames <lhames@gmail.com>
Thu, 17 Mar 2016 21:28:49 +0000 (21:28 +0000)
committerLang Hames <lhames@gmail.com>
Thu, 17 Mar 2016 21:28:49 +0000 (21:28 +0000)
idiom.

Most LLVM tool code exits immediately when an error is encountered and prints an
error message to stderr. The ExitOnError class supports this by providing two
call operators - one for Errors, and one for Expected<T>s. Calls to code that
can return Errors (or Expected<T>s) can use these calls to bail out on error,
and otherwise continue as if the operation had succeeded. E.g.

Error foo();
Expected<int> bar();

int main(int argc, char *argv[]) {
  ExitOnError ExitOnErr;

  ExitOnErr.setBanner(std::string("Error in ") + argv[0] + ":");

  // Exit if foo returns an error. No need to manually check error return.
  ExitOnErr(foo());

  // Exit if bar returns an error, otherwise unwrap the contained int and
  // continue.
  int X = ExitOnErr(bar());

  // ...

  return 0;
}

llvm-svn: 263749

llvm/include/llvm/Support/Error.h
llvm/unittests/Support/ErrorTest.cpp

index 33a3bc9..2e1699e 100644 (file)
@@ -519,7 +519,8 @@ inline void handleAllErrors(Error E) {
 /// (allowing clean deallocation of resources, etc.), while reporting error
 /// information to the user.
 template <typename... HandlerTs>
-void logAllUnhandledErrors(Error E, raw_ostream &OS, std::string ErrorBanner) {
+void logAllUnhandledErrors(Error E, raw_ostream &OS,
+                           const std::string &ErrorBanner) {
   if (!E)
     return;
   OS << ErrorBanner;
@@ -742,6 +743,55 @@ inline std::error_code errorToErrorCode(Error Err) {
   return EC;
 }
 
+/// Helper for check-and-exit error handling.
+///
+/// For tool use only. NOT FOR USE IN LIBRARY CODE.
+///
+class ExitOnError {
+public:
+
+  /// Create an error on exit helper.
+  ExitOnError(std::string Banner = "", int DefaultErrorExitCode = 1)
+    : Banner(Banner),
+      GetExitCode([=](const Error&) { return DefaultErrorExitCode; }) {}
+
+  /// Set the banner string for any errors caught by operator().
+  void setBanner(std::string Banner) {
+    this->Banner = Banner;
+  }
+
+  /// Set the exit-code mapper function.
+  void setExitCodeMapper(std::function<int(const Error&)> GetExitCode) {
+    this->GetExitCode = GetExitCode;
+  }
+
+  /// Check Err. If it's in a failure state log the error(s) and exit.
+  void operator()(Error Err) const {
+    checkError(std::move(Err));
+  }
+
+  /// Check E. If it's in a success state return the contained value. If it's
+  /// in a failure state log the error(s) and exit.
+  template <typename T>
+  T operator()(Expected<T> E) const {
+    checkError(E.takeError());
+    return std::move(*E);
+  }
+
+private:
+
+  void checkError(Error Err) const {
+    if (Err) {
+      int ExitCode = GetExitCode(Err);
+      logAllUnhandledErrors(std::move(Err), errs(), Banner);
+      exit(ExitCode);
+    }
+  }
+
+  std::string Banner;
+  std::function<int(const Error&)> GetExitCode;
+};
+
 } // namespace llvm
 
 #endif // LLVM_SUPPORT_ERROR_H
index d5af2ca..343a2d2 100644 (file)
@@ -38,6 +38,7 @@ namespace {
 //   - consume_error to consume a "safe" error without any output.
 //   - handleAllUnhandledErrors to assert that all errors are handled.
 //   - logAllUnhandledErrors to log errors to a stream.
+//   - ExitOnError tests.
 //
 // Expected tests:
 //   - Expected<T> with T.
@@ -50,6 +51,7 @@ namespace {
 //   - std::error_code to Error (ECError) in failure mode.
 //   - Error to std::error_code in success mode.
 //   - Error (ECError) to std::error_code in failure mode.
+//
 
 // Custom error class with a default base class and some random 'info' attached.
 class CustomError : public ErrorInfo<CustomError> {
@@ -376,6 +378,32 @@ TEST(Error, CheckErrorUtilities) {
     EXPECT_EQ(ErrorInfo, 7)
         << "Failed to handle Error returned from handleErrors.";
   }
+
+  // Test ExitOnError
+  {
+    ExitOnError ExitOnErr;
+    ExitOnErr.setBanner("Error in tool:");
+    ExitOnErr.setExitCodeMapper(
+      [](const Error &E) {
+        if (E.isA<CustomSubError>())
+          return 2;
+        return 1;
+      });
+
+    // Make sure we don't bail on success.
+    ExitOnErr(Error::success());
+    EXPECT_EQ(ExitOnErr(Expected<int>(7)), 7)
+      << "exitOnError returned an invalid value for Expected";
+
+    // Exit tests.
+    EXPECT_EXIT(ExitOnErr(make_error<CustomError>(7)),
+                ::testing::ExitedWithCode(1), "Error in tool:")
+      << "exitOnError returned an unexpected error result";
+
+    EXPECT_EXIT(ExitOnErr(Expected<int>(make_error<CustomSubError>(0, 0))),
+                ::testing::ExitedWithCode(2), "Error in tool:")
+      << "exitOnError returned an unexpected error result";
+  }
 }
 
 // Test Expected behavior.