[llvm] r285122 - [docs] Add more Error documentation to the Programmer's Manual.

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Tue Oct 25 14:19:30 PDT 2016


Author: lhames
Date: Tue Oct 25 16:19:30 2016
New Revision: 285122

URL: http://llvm.org/viewvc/llvm-project?rev=285122&view=rev
Log:
[docs] Add more Error documentation to the Programmer's Manual.

This patch updates some of the existing Error examples, expands on the
documentation for handleErrors, and includes new sections that cover
a number of helpful utilities and common error usage idioms.


Modified:
    llvm/trunk/docs/ProgrammersManual.rst

Modified: llvm/trunk/docs/ProgrammersManual.rst
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/docs/ProgrammersManual.rst?rev=285122&r1=285121&r2=285122&view=diff
==============================================================================
--- llvm/trunk/docs/ProgrammersManual.rst (original)
+++ llvm/trunk/docs/ProgrammersManual.rst Tue Oct 25 16:19:30 2016
@@ -337,25 +337,28 @@ Failure values are constructed using ``m
 that inherits from the ErrorInfo utility, E.g.:
 
 .. code-block:: c++
-
-  class MyError : public ErrorInfo<MyError> {
+  class BadFileFormat : public ErrorInfo<BadFileFormat> {
   public:
-    MyError(std::string Msg) : Msg(Msg) {}
-    void log(OStream &OS) const override { OS << "MyError - " << Msg; }
     static char ID;
-  private:
-    std::string Msg;
-  };
+    std::string Path;
 
-  char MyError::ID = 0; // In MyError.cpp
+    BadFileFormat(StringRef Path) : Path(Path.str()) {}
 
-  Error bar() {
-    if (checkErrorCondition)
-      return make_error<MyError>("Error condition detected");
+    void log(raw_ostream &OS) const override {
+      OS << Path << " is malformed";
+    }
+
+    std::error_code convertToErrorCode() const override {
+      return make_error_code(object_error::parse_failed);
+    }
+  };
 
-    // No error - proceed with bar.
+  char FileExists::ID; // This should be declared in the C++ file.
 
-    // Return success value.
+  Error printFormattedFile(StringRef Path) {
+    if (<check for valid format>)
+      return make_error<InvalidObjectFile>(Path);
+    // print file contents.
     return Error::success();
   }
 
@@ -375,34 +378,56 @@ success, enabling the following idiom:
 For functions that can fail but need to return a value the ``Expected<T>``
 utility can be used. Values of this type can be constructed with either a
 ``T``, or an ``Error``. Expected<T> values are also implicitly convertible to
-boolean, but with the opposite convention to Error: true for success, false for
-error. If success, the ``T`` value can be accessed via the dereference operator.
-If failure, the ``Error`` value can be extracted using the ``takeError()``
-method. Idiomatic usage looks like:
+boolean, but with the opposite convention to ``Error``: true for success, false
+for error. If success, the ``T`` value can be accessed via the dereference
+operator. If failure, the ``Error`` value can be extracted using the
+``takeError()`` method. Idiomatic usage looks like:
 
 .. code-block:: c++
 
-  Expected<float> parseAndSquareRoot(IStream &IS) {
-    float f;
-    IS >> f;
-    if (f < 0)
-      return make_error<FloatingPointError>(...);
-    return sqrt(f);
+  Expected<FormattedFile> openFormattedFile(StringRef Path) {
+    // If badly formatted, return an error.
+    if (auto Err = checkFormat(Path))
+      return std::move(Err);
+    // Otherwise return a FormattedFile instance.
+    return FormattedFile(Path);
   }
 
-  Error foo(IStream &IS) {
-    if (auto SqrtOrErr = parseAndSquartRoot(IS)) {
-      float Sqrt = *SqrtOrErr;
+  Error processFormattedFile(StringRef Path) {
+    // Try to open a formatted file
+    if (auto FileOrErr = openFormattedFile(Path)) {
+      // On success, grab a reference to the file and continue.
+      auto &File = *FileOrErr;
       // ...
-    } else
-      return SqrtOrErr.takeError();
+    } else
    // On error, extract the Error value and return it.
+      return FileOrErr.takeError();
   }
 
-All Error instances, whether success or failure, must be either checked or
-moved from (via std::move or a return) before they are destructed. Accidentally
-discarding an unchecked error will cause a program abort at the point where the
-unchecked value's destructor is run, making it easy to identify and fix
-violations of this rule.
+If an ``Expected<T>`` value is in success mode then the ``takeError()`` method
+will return a success value. Using this fact, the above function can be
+rewritten as:
+
+.. code-block:: c++
+
+  Error processFormattedFile(StringRef Path) {
+    // Try to open a formatted file
+    auto FileOrErr = openFormattedFile(Path);
+    if (auto Err = FileOrErr.takeError())
+      // On error, extract the Error value and return it.
+      return Err;
+    // On success, grab a reference to the file and continue.
+    auto &File = *FileOrErr;
+    // ...
+  }
+
+This second form is often more readable for functions that involve multiple
+``Expected<T>`` values as it limits the indentation required.
+
+All ``Error`` instances, whether success or failure, must be either checked or
+moved from (via ``std::move`` or a return) before they are destructed.
+Accidentally discarding an unchecked error will cause a program abort at the
+point where the unchecked value's destructor is run, making it easy to identify
+and fix violations of this rule.
 
 Success values are considered checked once they have been tested (by invoking
 the boolean conversion operator):
@@ -428,22 +453,305 @@ been activated:
 
 .. code-block:: c++
 
-  auto Err = canFail(...);
-  if (auto Err2 =
-       handleErrors(std::move(Err),
-         [](std::unique_ptr<MyError> M) {
-           // Try to handle 'M'. If successful, return a success value from
-           // the handler.
-           if (tryToHandle(M))
-             return Error::success();
-
-           // We failed to handle 'M' - return it from the handler.
-           // This value will be passed back from catchErrors and
-           // wind up in Err2, where it will be returned from this function.
-           return Error(std::move(M));
-         })))
-    return Err2;
+  handleErrors(
+    processFormattedFile(…),
+    [](const BadFileFormat &BFF) {
+      report(“Unable to process “ + BFF.Path + “: bad format”);
+    },
+    [](const FileNotFound &FNF) {
+      report("File not found " + FNF.Path);
+    });
+
+The ``handleErrors`` function takes an error as its first argument, followed by
+a variadic list of "handlers", each of which must be a callable type (a
+function, lambda, or class with a call operator) with one argument. The
+``handleErrors`` function will visit each handler in the sequence and check its
+argument type against the dynamic type of the error, running the first handler
+that matches. This is the same process that is used for catch clauses in C++
+exceptions.
+
+Since the list of handlers passed to ``handleErrors`` may not cover every error
+type that can occur, the ``handleErrors`` function also returns an Error value
+that must be checked or propagated. If the error value that is passed to
+``handleErrors`` does not match any of the handlers it will be returned from
+handleErrors. Idiomatic use of ``handleErrors`` thus looks like:
+
+.. code-block:: c++
+
+  if (auto Err =
+        handleErrors(
+          processFormattedFile(...),
+          [](const BadFileFormat &BFF) {
+            report(“Unable to process “ + BFF.Path + “: bad format”);
+          },
+          [](const FileNotFound &FNF) {
+            report("File not found " + FNF.Path);
+          }))
+    return Err;
+
+In cases where you truly know that the handler list is exhaustive the
+``handleAllErrors`` function can be used instead. This is identical to
+``handleErrors`` except that it will terminate the program if an unhandled
+error is passed in, and can therefore return void. The ``handleAllErrors``
+function should generally be avoided: the introduction of a new error type
+elsewhere in the program can easily turn a formerly exhaustive list of errors
+into a non-exhaustive list, risking unexpected program termination. Where
+possible, use handleErrors and propagate unknown errors up the stack instead.
+
+StringError
+"""""""""""
+
+Many kinds of errors have no recovery strategy, the only action that can be
+taken is to report them to the user so that the user can attempt to fix the
+environment. In this case representing the error as a string makes perfect
+sense. LLVM provides the ``StringError class for this purpose. It takes two
+arguments: A string error message, and an equivalent ``std::error_code`` for
+interoperability:
+
+.. code-block:: c++
+
+  make_error<StringError>("Bad executable",
+                          make_error_code(errc::executable_format_error"));
+
+If you're certain that the error you're building will never need to be converted
+to a ``std::error_code`` you can use the ``inconvertibleErrorCode()`` function:
+
+.. code-block:: c++
+
+  make_error<StringError>("Bad executable", inconvertibleErrorCode());
+
+This should be done only after careful consideration. If any attempt is made to
+convert this error to a ``std::error_code`` it will trigger immediate program
+termination. Unless you are certain that your errors will not need
+interoperability you should look for an existing ``std::error_code`` that you
+can convert to, and even (as painful as it is) consider introducing a new one as
+a stopgap measure.
+
+Interoperability with std::error_code and ErrorOr
+"""""""""""""""""""""""""""""""""""""""""""""""""
+
+Many existing LLVM APIs use ``std::error_code`` and its partner ``ErrorOr<T>``
+(which plays the same role as ``Expected<T>``, but wraps a ``std::error_code``
+rather than an ``Error``). The infectious nature of error types means that an
+attempt to change one of these functions to return ``Error`` or ``Expected<T>``
+instead often results in an avalanche of changes to callers, callers of callers,
+and so on. (The first such attempt, returning an ``Error`` from
+MachOObjectFile’s constructor, was abandoned after the diff reached 3000 lines,
+impacted half a dozen libraries, and was still growing).
+
+To solve this problem, the ``Error``/``std::error_code`` interoperability requirement was
+introduced. Two pairs of functions allow any ``Error`` value to be converted to a
+``std::error_code``, any ``Expected<T>`` to be converted to an ``ErrorOr<T>``, and vice
+versa:
+
+.. code-block:: c++
+
+  std::error_code errorToErrorCode(Error Err);
+  Error errorCodeToError(std::error_code EC);
+
+  template <typename T> ErrorOr<T> expectedToErrorOr(Expected<T> TOrErr);
+  template <typename T> Expected<T> errorOrToExpected(ErrorOr<T> TOrEC);
+
+
+Using these APIs it is easy to make surgical patches that update individual
+functions from ``std::error_code`` to ``Error``, and from ``ErrorOr<T>`` to
+``Expected<T>``.
+
+Returning Errors from error handlers
+""""""""""""""""""""""""""""""""""""
+
+Error recovery attempts may themselves fail. For that reason, ``handleErrors``
+actually recognises three different forms of handler signature:
+
+.. code-block:: c++
+
+  // Error must be handled, no new errors produced:
+  void(UserDefinedError &E);
+
+  // Error must be handled, new errors can be produced:
+  Error(UserDefinedError &E);
+
+  // Original error can be inspected, then re-wrapped and returned (or a new
+  // error can be produced):
+  Error(std::unique_ptr<UserDefinedError> E);
+
+Any error returned from a handler will be returned from the ``handleErrors``
+function so that it can be handled itself, or propagated up the stack.
+
+Using ExitOnError to simplify tool code
+"""""""""""""""""""""""""""""""""""""""
+
+Library code should never call ``exit`` for a recoverable error, however in tool
+code (especially comamnd line tools) this can be a reasonable approach. Calling
+``exit`` upon encountering an error dramatically simplifies control flow as the
+error no longer needs to be propagated up the stack. This allows code to be
+written in straight-line style, as long as each fallible call is wrapped in a
+check and call to exit. The ``ExitOnError``` class supports this pattern by
+providing call operators that inspect ``Error`` values, stripping the error away
+in the success case and logging to ``stderr`` then exiting in the failure case.
+
+To use this class, declare a global ``ExitOnError`` variable in your program:
+
+.. code-block:: c++
+
+  ExitOnError ExitOnErr;
+
+Calls to fallible functions can then be wrapped with a call to ``ExitOnErr``,
+turning them into non-failing calls:
+
+.. code-block:: c++
+
+  Error mayFail();
+  Expected<int> mayFail2();
+
+  void foo() {
+    ExitOnErr(mayFail());
+    int X = ExitOnErr(mayFail2());
+  }
 
+On failure, the error’s log message will be written to ``stderr``, optionally
+preceded by a string “banner” that can be set by calling the setBanner method. A
+mapping can also be supplied from ``Error`` values to exit codes using the
+``setExitCodeMapper`` method:
+
+int main(int argc, char *argv[]) {
+  ExitOnErr.setBanner(std::string(argv[0]) + “ error:”);
+  ExitOnErr.setExitCodeMapper(
+    [](const Error &Err) {
+      if (Err.isA<BadFileFormat>())
+        return 2;
+      return 1;
+    });
+
+Use ``ExitOnError`` in your tool code where possible as it can greatly improve
+readability.
+
+Fallible constructors
+"""""""""""""""""""""
+
+Some classes require resource acquisition or other complex initialization that
+can fail during construction. Unfortunately constructors can’t return errors,
+and having clients test objects after they’re constructed to ensure that they’re
+valid is error prone as it’s all too easy to forget the test. To work around
+this, use the named constructor idiom and return an ``Expected<T>``:
+
+.. code-block:: c++
+
+  class Foo {
+  public:
+
+
   static Expected<Foo> Create(Resource R1, Resource R2) {
+      Error Err;
+      Foo F(R1, R2, Err);
+      if (Err)
+        return std::move(Err);
+      return std::move(F);
+    }
+
+  private:
+
+    Foo(Resource R1, Resource R2, Error &Err) {
+      ErrorAsOutParameter EAO(&Err);
+      if (auto Err2 = R1.acquire()) {
+        Err = std::move(Err2);
+        return;
+      }
+      Err = R2.acquire();
+    }
+  };
+
+
+Here, the named constructor passes an ``Error`` by reference into the actual
+constructor, which the constructor can then use to return errors. The
+``ErrorAsOutParameter`` utility sets the ``Error`` value's checked flag on entry
+to the constructor so that the error can be assigned to, then resets it on exit
+to force the client (the named constructor) to check the error.
+
+By using this idiom, clients attempting to construct a Foo receive either a
+well-formed Foo or an Error, never an object in an invalid state.
+
+Propagating and consuming errors based on types
+"""""""""""""""""""""""""""""""""""""""""""""""
+
+In some contexts, certain types of error are known to be benign. For example,
+when walking an archive, some clients may be happy to skip over badly formatted
+object files rather than terminating the walk immediately. Skipping badly
+formatted objects could be achieved using an elaborate handler method, But the
+Error.h header provides two utilities that make this idiom much cleaner: the
+type inspection method, ``isA``, and the ``consumeError`` function:
+
+.. code-block:: c++
+
+  Error walkArchive(Archive A) {
+    for (unsigned I = 0; I != A.numMembers(); ++I) {
+      auto ChildOrErr = A.getMember(I);
+      if (auto Err = ChildOrErr.takeError())
+        if (Err.isA<BadFileFormat>())
+          consumeError(std::move(Err))
+        else
+          return Err;
+        auto &Child = *ChildOrErr;
+      // do work
+    }
+    return Error::success();
+  }
+
+Concatenating Errors with joinErrors
+""""""""""""""""""""""""""""""""""""
+
+In the archive walking example above ``BadFileFormat`` errors are simply
+consumed and ignored. If the client had wanted report these errors after
+completing the walk over the archive they could use the ``joinErrors`` utility:
+
+.. code-block:: c++
+
+  Error walkArchive(Archive A) {
+    Error DeferredErrs = Error::success();
+    for (unsigned I = 0; I != A.numMembers(); ++I) {
+      auto ChildOrErr = A.getMember(I);
+      if (auto Err = ChildOrErr.takeError())
+        if (Err.isA<BadFileFormat>())
+          DeferredErrs = joinErrors(std::move(DeferredErrs), std::move(Err));
+        else
+          return Err;
+      auto &Child = *ChildOrErr;
+      // do work
+    }
+    return DeferredErrs;
+  }
+
+The ``joinErrors`` routine builds a special error type called ``ErrorList``,
+which holds a list of user defined errors. The ``handleErrors`` routine
+recognizes this type and will attempt to handle each of the contained erorrs in
+order. If all contained errors can be handled, ``handleErrors`` will return
+``Error::success()``, otherwise ``handleErrors`` will concatenate the remaining
+errors and return the resulting ``ErrorList``.
+
+Building fallible iterators and iterator ranges
+"""""""""""""""""""""""""""""""""""""""""""""""
+
+The archive walking examples above retrieve archive members by index, however
+this requires considerable boiler-plate for iteration and error checking. We can
+clean this up considerably by using ``Error`` with the "fallible iterator"
+pattern. The usual C++ iterator patterns do not allow for failure on increment,
+but we can incorporate support for it by having iterators hold an Error
+reference through which they can report failure. In this pattern, if an
+increment operation fails the failure is recorded via the Error reference and
+the iterator value is set to the end of the range in order to terminate the
+loop. This ensures that the dereference operation is safe anywhere that an
+ordinary iterator dereference would be safe (i.e. when the iterator is not equal
+to end). Where this pattern is followed (as in the ``llvm::object::Archive``
+class) the result is much cleaner iteration idiom:
+
+.. code-block:: c++
+
+  Error Err;
+  for (auto &Child : Ar->children(Err)) {
+    // Use Child - we only enter the loop when it’s valid.
+  }
+  // Check Err after the loop to ensure it didn’t break due to an error.
+  if (Err)
+    return Err;
 
 .. _function_apis:
 




More information about the llvm-commits mailing list