[Support][Error] Add a 'cantFail' utility function for known-safe calls to

fallible functions.

Some fallible functions (those returning Error or Expected<T>) may only fail
for a subset of their inputs. For example, a "safe" square root function will
succeed for all finite positive inputs:

  Expected<double> safeSqrt(double d) {
    if (d < 0 && !isnan(d) && !isinf(d))
      return make_error<...>("Cannot sqrt -ve values, nans or infs");
    return sqrt(d);
  }

At a safe callsite for such a function, checking the error return value is
redundant:

  if (auto ValOrErr = safeSqrt(42.0)) {
    // use *ValOrErr.
  } else
    llvm_unreachable("safeSqrt should always succeed for +ve values");

The cantFail function wraps this check and extracts the contained value,
simplifying control flow:

  double Result = cantFail(safeSqrt(42.0));

This function should be used with care: it is a programmatic error to wrap a
call with cantFail if it can in fact fail. For debug builds this will
result in llvm_unreachable being called. For release builds the behavior is
undefined.

Use of this function is likely to be rare in library code, but more common
for tool and unit-test code where inputs and mock functions may be known to be
safe.


git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@296384 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Lang Hames 2017-02-27 21:09:47 +00:00
parent 563a987b91
commit 530cc1a7c8
3 changed files with 114 additions and 4 deletions

View File

@ -564,18 +564,18 @@ the boolean conversion operator):
.. code-block:: c++
if (auto Err = canFail(...))
if (auto Err = mayFail(...))
return Err; // Failure value - move error to caller.
// Safe to continue: Err was checked.
In contrast, the following code will always cause an abort, even if ``canFail``
In contrast, the following code will always cause an abort, even if ``mayFail``
returns a success value:
.. code-block:: c++
canFail();
// Program will always abort here, even if canFail() returns Success, since
mayFail();
// Program will always abort here, even if mayFail() returns Success, since
// the value is not checked.
Failure values are considered checked once a handler for the error type has
@ -633,6 +633,12 @@ exiting with an error code, the :ref:`ExitOnError <err_exitonerr>` utility
may be a better choice than handleErrors, as it simplifies control flow when
calling fallible functions.
In situations where it is known that a particular call to a fallible function
will always succeed (for example, a call to a function that can only fail on a
subset of inputs with an input that is known to be safe) the
:ref:`cantFail <err_cantfail>` functions can be used to remove the error type,
simplifying control flow.
StringError
"""""""""""
@ -765,6 +771,43 @@ mapping can also be supplied from ``Error`` values to exit codes using the
Use ``ExitOnError`` in your tool code where possible as it can greatly improve
readability.
.. _err_cantfail:
Using cantFail to simplify safe callsites
"""""""""""""""""""""""""""""""""""""""""
Some functions may only fail for a subset of their inputs. For such functions
call-sites using known-safe inputs can assume that the result will be a success
value.
The cantFail functions encapsulate this by wrapping an assertion that their
argument is a success value and, in the case of Expected<T>, unwrapping the
T value from the Expected<T> argument:
.. code-block:: c++
Error mayFail(int X);
Expected<int> mayFail2(int X);
void foo() {
cantFail(mayFail(KnownSafeValue));
int Y = cantFail(mayFail2(KnownSafeValue));
...
}
Like the ExitOnError utility, cantFail simplifies control flow. Their treatment
of error cases is very different however: Where ExitOnError is guaranteed to
terminate the program on an error input, cantFile simply asserts that the result
is success. In debug builds this will result in an assertion failure if an error
is encountered. In release builds the behavior of cantFail for failure values is
undefined. As such, care must be taken in the use of cantFail: clients must be
certain that a cantFail wrapped call really can not fail under any
circumstances.
Use of the cantFail functions should be rare in library code, but they are
likely to be of more use in tool and unit-test code where inputs and/or
mocked-up classes or functions may be known to be safe.
Fallible constructors
"""""""""""""""""""""

View File

@ -985,6 +985,45 @@ private:
LLVM_ATTRIBUTE_NORETURN void report_fatal_error(Error Err,
bool gen_crash_diag = true);
/// Report a fatal error if Err is a failure value.
///
/// This function can be used to wrap calls to fallible functions ONLY when it
/// is known that the Error will always be a success value. E.g.
///
/// @code{.cpp}
/// // foo only attempts the fallible operation if DoFallibleOperation is
/// // true. If DoFallibleOperation is false then foo always returns
/// // Error::success().
/// Error foo(bool DoFallibleOperation);
///
/// cantFail(foo(false));
/// @endcode
inline void cantFail(Error Err) {
if (Err)
llvm_unreachable("Failure value returned from cantFail wrapped call");
}
/// Report a fatal error if ValOrErr is a failure value, otherwise unwraps and
/// returns the contained value.
///
/// This function can be used to wrap calls to fallible functions ONLY when it
/// is known that the Error will always be a success value. E.g.
///
/// @code{.cpp}
/// // foo only attempts the fallible operation if DoFallibleOperation is
/// // true. If DoFallibleOperation is false then foo always returns an int.
/// Expected<int> foo(bool DoFallibleOperation);
///
/// int X = cantFail(foo(false));
/// @endcode
template <typename T>
T cantFail(Expected<T> ValOrErr) {
if (ValOrErr)
return std::move(*ValOrErr);
else
llvm_unreachable("Failure value returned from cantFail wrapped call");
}
} // end namespace llvm
#endif // LLVM_SUPPORT_ERROR_H

View File

@ -469,6 +469,34 @@ TEST(Error, ExitOnError) {
<< "exitOnError returned an unexpected error result";
}
// Test that the ExitOnError utility works as expected.
TEST(Error, CantFailSuccess) {
cantFail(Error::success());
int X = cantFail(Expected<int>(42));
EXPECT_EQ(X, 42) << "Expected value modified by cantFail";
}
// Test that cantFail results in a crash if you pass it a failure value.
#if LLVM_ENABLE_ABI_BREAKING_CHECKS
TEST(Error, CantFailDeath) {
EXPECT_DEATH(
cantFail(make_error<StringError>("foo", inconvertibleErrorCode())),
"Failure value returned from cantFail wrapped call")
<< "cantFail(Error) did not cause an abort for failure value";
EXPECT_DEATH(
{
auto IEC = inconvertibleErrorCode();
int X = cantFail(Expected<int>(make_error<StringError>("foo", IEC)));
(void)X;
},
"Failure value returned from cantFail wrapped call")
<< "cantFail(Expected<int>) did not cause an abort for failure value";
}
#endif
// Test Checked Expected<T> in success mode.
TEST(Error, CheckedExpectedInSuccessMode) {
Expected<int> A = 7;