<div dir="ltr">Hi Craig,<div><br></div><div>> <span style="font-size:13px">TypedError Err = foo();</span></div><span style="font-size:13px">> // no checking in between</span><br style="font-size:13px"><span style="font-size:13px">> Err = foo();</span><br style="font-size:13px"><div><span style="font-size:13px"><br></span></div><div>This will cause an abort - the assignment operator for TypedError checks that you're not overwriting an unhanded error.</div><div><br></div><div><span style="font-size:13px">> TypedError Err = foo();</span><br style="font-size:13px"><span style="font-size:13px">> functionWithHorribleSideEffect</span><span style="font-size:13px">s();</span><br style="font-size:13px"><span style="font-size:13px">> if (Err) return;</span><br></div><div><span style="font-size:13px"><br></span></div><div>This is potentially reasonable code - it's impossible to distinguish in general from:</div><div><br></div><div>TypedError Err = foo();</div><div>functionWithPerfectlyReasonableSideEffects();</div><div>if (Err) return;</div><div><br></div><div>That said, to avoid problems related to this style we can offer style guidelines. Idiomatic usage of the system looks like:<br></div><div><br></div><div>if (auto Err = foo())</div><div>  return Err;</div><div>functionWithHorribleSideEffects();</div><div><br></div><div>This is how people tend to write error checks in most of the LLVM code I've seen to date.</div><div><br></div><div><span style="font-size:13px">> Do you anticipate giving these kinds of errors to out of tree projects?  If so, are there any kind of binary compatibility guarantee?</span><br style="font-size:13px"></div><div><br></div><div>Out of tree projects can use the TypedError.h header and derive their own error classes. This is all pure C++, I don't think there are binary compatibility issues.</div><div><br></div><div>> <span style="font-size:13px">What about errors that should come out of constructors?  Or <shudder> destructors?</span></div><div><br></div><div>TypedError can't be "thrown" in the same way that C++ exceptions can. It's an ordinary C++ value. You can't return an error from a constructor, but you can pass a reference to an error in and set that. In general the style guideline for a "may-fail" constructors would be to write something like this:</div><div><br></div><div><font face="monospace, monospace">class Foo {</font></div><div><font face="monospace, monospace">public:</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">  static TypedErrorOr<Foo> create(int X, int Y) {</font></div><div><font face="monospace, monospace">    TypedError Err;</font></div><div><font face="monospace, monospace">    Foo F(X, Y, Err);</font></div><div><font face="monospace, monospace">    if (Err)</font></div><div><font face="monospace, monospace">      return std::move(Err);</font></div><div><font face="monospace, monospace">    return std::move(F);</font></div><div><font face="monospace, monospace">  }</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">private:</font></div><div><font face="monospace, monospace">  Foo(int x, int y, TypedError &Err) {</font></div><div><font face="monospace, monospace">    if (x == y) {</font></div><div><font face="monospace, monospace">      Err = make_typed_error<BadFoo>();</font></div><div><font face="monospace, monospace">      return;</font></div><div><font face="monospace, monospace">    }</font></div><div><font face="monospace, monospace">  }</font></div><div><font face="monospace, monospace">};</font></div><div><br></div><div>Then you have:</div><div><br></div><div><font face="monospace, monospace">TypedErrorOr<Foo> F = Foo::create(X, Y);</font></div><div><br></div><div><br></div><div>The only way to catch failure of a destructor is for the class to hold a reference to a TypedError, and set that. This is extremely difficult to do correctly, but as far is I know all error schemes suffer from poor interaction with destructors. In LLVM failing destructors are very rare, so I don't anticipate this being a problem in general.</div><div><br></div><div>> <span style="font-size:13px"> </span><span style="font-size:13px">If a constructor fails and doesn't establish it's invariant, what will prevent the use of that invalid object?</span></div><div><span style="font-size:13px"><br></span></div><div><span style="font-size:13px">If the style guideline above is followed the invalid object will never be returned to the user. Care must be taken to ensure that the destructor can destruct the partially constructed object, but that's always the case.</span></div><div><br></div><div>> <span style="font-size:13px"> </span><span style="font-size:13px">How many subclasses do you expect to make of TypedError? Less than 10? More than 100?</span></div><div><span style="font-size:13px"><br></span></div><div>This is a support library, so it's not possible to reason about how many external clients will want to use it in their projects, or how many errors they would define. In LLVM I'd like to see us adopt a 'less-is-more' approach: New error types should be introduced sparingly, and each new error type should require a rationale for its existence. In particular, distinct error types should only be introduced when it's reasonable for some client to make a meaningful distinction between them. If an error is only being returned in order to produce a string diagnostic, a generic StringDiagnosticError should suffice.</div><div><br></div><div>Answering your question more directly: In the LLVM code I'm familiar with I can see room for more than 10 error types, but fewer than 100.</div><div><br></div><div>> <span style="font-size:13px">How common is it to want to handle a specific error code in a non-local way?  In my experience, I either want a specific error handled locally, or a fail / not-failed from farther away.  The answer to this question may influence the number of subclasses you want to make.</span></div><div><span style="font-size:13px"><br></span></div><div>Errors usually get handled locally, or just produce a diagnostic and failure, however there are some cases where we want non-local recovery from specific errors. The archive-walking example I gave earlier is one such case. You're right on the point about subclasses too - that's what I was hoping to capture with my comment above: only introduce an error type if it's meaningful for a client to distinguish it from other errors.</div><div><br></div><div> > <span style="font-size:13px">Are file, line number, and / or call stack information captured?  I've found file and line number information to be incredibly useful from a productivity standpoint.</span></div><div><span style="font-size:13px"><br></span></div><div>I think that information is helpful for programmatic errors, but those are better represented by asserts or "report_fatal_error". This system is intended to support modelling of non-programmatic errors - bad input, resource failures and the like. For those, the specific point in the code where the error was triggered is less useful. If such information is needed, this system makes it easy to break on the failure point in a debugger.</div><div><br></div><div>Cheers,</div><div>Lang.</div><div><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Feb 3, 2016 at 6:15 AM, Craig, Ben via llvm-dev <span dir="ltr"><<a href="mailto:llvm-dev@lists.llvm.org" target="_blank">llvm-dev@lists.llvm.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
  
    
  
  <div bgcolor="#FFFFFF" text="#000000">
    I've had some experience dealing with rich error descriptions
    without exceptions before.  The scheme I used was somewhat similar
    to what you have.  Here are some items to consider.<br>
    <br>
    * How will the following code be avoided?  The answer may be compile
    time error, runtime error, style recommendations, or maybe something
    else.<br>
    <br>
    TypedError Err = foo();<br>
    // no checking in between<br>
    Err = foo();<br>
    <br>
    * How about this?<br>
    <br>
    TypedError Err = foo();<br>
    functionWithHorribleSideEffects();<br>
    if(Err) return;<br>
    <br>
    * Do you anticipate giving these kinds of errors to out of tree
    projects?  If so, are there any kind of binary compatibility
    guarantee?<br>
    <br>
    * What about errors that should come out of constructors?  Or
    <shudder> destructors?<br>
    <br>
    * If a constructor fails and doesn't establish it's invariant, what
    will prevent the use of that invalid object?<br>
    <br>
    * How many subclasses do you expect to make of TypedError? Less than
    10? More than 100?<br>
    <br>
    * How common is it to want to handle a specific error code in a
    non-local way?  In my experience, I either want a specific error
    handled locally, or a fail / not-failed from farther away.  The
    answer to this question may influence the number of subclasses you
    want to make.<br>
    <br>
    * Are file, line number, and / or call stack information captured? 
    I've found file and line number information to be incredibly useful
    from a productivity standpoint.<div><div class="h5"><br>
    <br>
    <div>On 2/2/2016 7:29 PM, Lang Hames via
      llvm-dev wrote:<br>
    </div>
    </div></div><blockquote type="cite"><div><div class="h5">
      <div dir="ltr"><span style="font-size:13px">Hi All,</span>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">I've been thinking lately about how
          to improve LLVM's error model and error reporting. A lack of
          good error reporting in Orc and MCJIT has forced me to spend a
          lot of time investigating hard-to-debug errors that could
          easily have been identified if we provided richer error
          information to the client, rather than just aborting. Kevin
          Enderby has made similar observations about the state of
          libObject and the difficulty of producing good error messages
          for damaged object files. I expect to encounter more issues
          like this as I continue work on the MachO side of LLD. I see
          tackling the error modeling problem as a first step towards
          improving error handling in general: if we make it easy to
          model errors, it may pave the way for better error handling in
          many parts of our libraries.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">At present in LLVM we model errors
          with std::error_code (and its helper, ErrorOr) and use
          diagnostic streams for error reporting. Neither of these seem
          entirely up to the job of providing a solid error-handling
          mechanism for library code. Diagnostic streams are great if
          all you want to do is report failure to the user and then
          terminate, but they can't be used to distinguish between
          different kinds of errors, and so are unsuited to many
          use-cases (especially error recovery). On the other hand,
          std::error_code allows error kinds to be distinguished, but
          suffers a number of drawbacks:</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">1. It carries no context: It tells
          you what went wrong, but not where or why, making it difficult
          to produce good diagnostics.</div>
        <div style="font-size:13px">2. It's extremely easy to ignore or
          forget: instances can be silently dropped.</div>
        <div style="font-size:13px">3. It's not especially debugger
          friendly: Most people call the error_code constructors
          directly for both success and failure values. Breakpoints have
          to be set carefully to avoid stopping when success values are
          constructed.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">In fairness to std::error_code, it
          has some nice properties too:</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">1. It's extremely lightweight.</div>
        <div style="font-size:13px">2. It's explicit in the API (unlike
          exceptions).</div>
        <div style="font-size:13px">3. It doesn't require C++ RTTI (a
          requirement for use in LLVM).</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">To address these shortcomings I have
          prototyped a new error-handling scheme partially inspired by
          C++ exceptions. The aim was to preserve the performance and
          API visibility of std::error_code, while allowing users to
          define custom error classes and inheritance relationships
          between them. My hope is that library code could use this
          scheme to model errors in a meaningful way, allowing clients
          to inspect the error information and recover where possible,
          or provide a rich diagnostic when aborting.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">The scheme has three major "moving
          parts":</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">1. A new 'TypedError' class that can
          be used as a replacement for std::error_code. E.g.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px"><font face="monospace, monospace">std::error_code
            foo();</font></div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">becomes</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px"><font face="monospace, monospace">TypedError
            foo();</font></div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">The TypedError class serves as a
          lightweight wrapper for the real error information (see (2)).
          It also contains a 'Checked' flag, initially set to false,
          that tracks whether the error has been handled or not. If a
          TypedError is ever destructed without being checked (or passed
          on to someone else) it will call std::terminate(). TypedError
          cannot be silently dropped.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">2. A utility class, TypedErrorInfo,
          for building error class hierarchies rooted at
          'TypedErrorInfoBase' with custom RTTI. E.g.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px"><font face="monospace, monospace">//
            Define a new error type implicitly inheriting from
            TypedErrorInfoBase.</font></div>
        <div style="font-size:13px"><font face="monospace, monospace">class
            MyCustomError : public TypedErrorInfo<MyCustomError> {</font></div>
        <div style="font-size:13px"><font face="monospace, monospace">public:</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
            // Custom error info.</font></div>
        <div style="font-size:13px"><font face="monospace, monospace">};</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"><br>
          </font></div>
        <div style="font-size:13px"><font face="monospace, monospace">//
            Define a subclass of MyCustomError.</font></div>
        <div style="font-size:13px"><font face="monospace, monospace">class
            MyCustomSubError : public
            TypedErrorInfo<MyCustomSubError, MyCustomError> {</font></div>
        <div style="font-size:13px"><font face="monospace, monospace">public:</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
            // Extends MyCustomError, adds new members.</font></div>
        <div style="font-size:13px"><font face="monospace, monospace">};</font></div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">3.  A set of utility functions that
          use the custom RTTI system to inspect and handle typed errors.
          For example 'catchAllTypedErrors' and 'handleTypedError'
          cooperate to handle error instances in a type-safe way:</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px"><font face="monospace, monospace">TypedError
            foo() {</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
            if (SomeFailureCondition)</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
              return make_typed_error<MyCustomError>();</font></div>
        <div style="font-size:13px"><font face="monospace, monospace">}</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"><br>
          </font></div>
        <div style="font-size:13px"><font face="monospace, monospace">TypedError
            Err = foo();</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"><br>
          </font></div>
        <div style="font-size:13px"><font face="monospace, monospace">catchAllTypedErrors(std::move(Err),</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
            handleTypedError<MyCustomError>(</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
              [](std::unique_ptr<MyCustomError> E) {</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
                // Handle the error.</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
                return TypedError(); // <- Indicate success from
            handler.</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
              }</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
            )</font></div>
        <div style="font-size:13px"><font face="monospace, monospace">);</font></div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">If your initial reaction is "Too
          much boilerplate!" I understand, but take comfort: (1) In the
          overwhelmingly common case of simply returning errors, the
          usage is identical to std::error_code:</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px"><font face="monospace, monospace">if
            (TypedError Err = foo())</font></div>
        <div style="font-size:13px"><font face="monospace, monospace"> 
            return Err;</font></div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">and (2) the boilerplate for catching
          errors is usually easily contained in a handful of utility
          functions, and tends not to crowd the rest of your source
          code. My initial experiments with this scheme involved
          updating many source lines, but did not add much code at all
          beyond the new error classes that were introduced.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">I believe that this scheme addresses
          many of the shortcomings of std::error_code while maintaining
          the strengths: </div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">1. Context - Custom error classes
          enable the user to attach as much contextual information as
          desired.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">2. Difficult to drop - The 'checked'
          flag in TypedError ensures that it can't be dropped, it must
          be explicitly "handled", even if that only involves catching
          the error and doing nothing.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">3. Debugger friendly - You can set a
          breakpoint on any custom error class's constructor to catch
          that error being created. Since the error class hierarchy is
          rooted you can break on TypedErrorInfoBase::TypedErrorInfoBase
          to catch any error being raised.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">4. Lightweight - Because TypedError
          instances are just a pointer and a checked-bit,
          move-constructing it is very cheap. We may also want to
          consider ignoring the 'checked' bit in release mode, at which
          point TypedError should be as cheap as std::error_code.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">5. Explicit - TypedError is
          represented explicitly in the APIs, the same as
          std::error_code.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">6. Does not require C++ RTTI - The
          custom RTTI system does not rely on any standard C++ RTTI
          features.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">This scheme also has one attribute
          that I haven't seen in previous error handling systems (though
          my experience in this area is limited): Errors are not
          copyable, due to ownership semantics of TypedError. I think
          this actually neatly captures the idea that there is a chain
          of responsibility for dealing with any given error.
          Responsibility may be transferred (e.g. by returning it to a
          caller), but it cannot be duplicated as it doesn't generally
          make sense for multiple people to report or attempt to recover
          from the same error.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">I've tested this prototype out by
          threading it through the object-creation APIs of libObject and
          using custom error classes to report errors in MachO headers.
          My initial experience is that this has enabled much richer
          error messages than are possible with std::error_code.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">To enable interaction with APIs that
          still use std::error_code I have added a custom ECError class
          that wraps a std::error_code, and can be converted back to a
          std::error_code using the typedErrorToErrorCode function. For
          now, all custom error code classes should (and do, in the
          prototype) derive from this utility class. In my experiments,
          this has made it easy to thread TypedError selectively through
          parts of the API. Eventually my hope is that TypedError could
          replace std::error_code for user-facing APIs, at which point
          custom errors would no longer need to derive from ECError, and
          ECError could be relegated to a utility for interacting with
          other codebases that still use std::error_code.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">So - I look forward to hearing your
          thoughts. :)</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">Cheers,</div>
        <div style="font-size:13px">Lang.</div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">
          <div>Attached files:<br>
          </div>
          <div><br>
          </div>
          <div>typed_error.patch - Adds
            include/llvm/Support/TypedError.h (also adds anchor() method
            to lib/Support/ErrorHandling.cpp).</div>
          <div><br>
          </div>
          <div>error_demo.tgz - Stand-alone program demo'ing basic use
            of the TypedError API.</div>
        </div>
        <div style="font-size:13px"><br>
        </div>
        <div style="font-size:13px">libobject_typed_error_demo.patch -
          Threads TypedError through the binary-file creation methods
          (createBinary, createObjectFile, etc). Proof-of-concept for
          how TypedError can be integrated into an existing system.</div>
        <div style="font-size:13px"><br>
        </div>
      </div>
      <br>
      <fieldset></fieldset>
      <br>
      </div></div><span class=""><pre>_______________________________________________
LLVM Developers mailing list
<a href="mailto:llvm-dev@lists.llvm.org" target="_blank">llvm-dev@lists.llvm.org</a>
<a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev</a>
</pre>
    </span></blockquote><span class="HOEnZb"><font color="#888888">
    <br>
    <pre cols="72">-- 
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project
</pre>
  </font></span></div>

<br>_______________________________________________<br>
LLVM Developers mailing list<br>
<a href="mailto:llvm-dev@lists.llvm.org">llvm-dev@lists.llvm.org</a><br>
<a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev" rel="noreferrer" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev</a><br>
<br></blockquote></div><br></div>