From 530cc1a7c8ae6daf499bd88e7fd4b6518dd5e6eb Mon Sep 17 00:00:00 2001 From: Lang Hames Date: Mon, 27 Feb 2017 21:09:47 +0000 Subject: [PATCH] [Support][Error] Add a 'cantFail' utility function for known-safe calls to fallible functions. Some fallible functions (those returning Error or Expected) may only fail for a subset of their inputs. For example, a "safe" square root function will succeed for all finite positive inputs: Expected 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 --- docs/ProgrammersManual.rst | 51 ++++++++++++++++++++++++++++++--- include/llvm/Support/Error.h | 39 +++++++++++++++++++++++++ unittests/Support/ErrorTest.cpp | 28 ++++++++++++++++++ 3 files changed, 114 insertions(+), 4 deletions(-) diff --git a/docs/ProgrammersManual.rst b/docs/ProgrammersManual.rst index f6480d0f8b4..d07f7c58f03 100644 --- a/docs/ProgrammersManual.rst +++ b/docs/ProgrammersManual.rst @@ -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 ` 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 ` 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, unwrapping the +T value from the Expected argument: + +.. code-block:: c++ + + Error mayFail(int X); + Expected 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 """"""""""""""""""""" diff --git a/include/llvm/Support/Error.h b/include/llvm/Support/Error.h index f13c9484b5f..21664d4b715 100644 --- a/include/llvm/Support/Error.h +++ b/include/llvm/Support/Error.h @@ -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 foo(bool DoFallibleOperation); +/// +/// int X = cantFail(foo(false)); +/// @endcode +template +T cantFail(Expected 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 diff --git a/unittests/Support/ErrorTest.cpp b/unittests/Support/ErrorTest.cpp index 29a173a058b..382346cd231 100644 --- a/unittests/Support/ErrorTest.cpp +++ b/unittests/Support/ErrorTest.cpp @@ -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(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("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(make_error("foo", IEC))); + (void)X; + }, + "Failure value returned from cantFail wrapped call") + << "cantFail(Expected) did not cause an abort for failure value"; +} +#endif + + // Test Checked Expected in success mode. TEST(Error, CheckedExpectedInSuccessMode) { Expected A = 7;