mirror of
https://github.com/RPCSX/llvm.git
synced 2024-11-30 15:10:33 +00:00
[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:
parent
563a987b91
commit
530cc1a7c8
@ -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
|
||||
"""""""""""""""""""""
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user