diff --git a/folly/lang/SafeAssert.cpp b/folly/lang/SafeAssert.cpp index 742bfd25036eb21a26c9b40d7bda21a8cb9f5375..4339cac7e81357f10cf7ece95834c1d6cba5777e 100644 --- a/folly/lang/SafeAssert.cpp +++ b/folly/lang/SafeAssert.cpp @@ -17,12 +17,15 @@ #include <folly/lang/SafeAssert.h> #include <algorithm> +#include <cerrno> +#include <cstdarg> #include <folly/detail/FileUtilDetail.h> #include <folly/lang/ToAscii.h> #include <folly/portability/Unistd.h> namespace folly { +namespace detail { namespace { @@ -441,48 +444,53 @@ constexpr std::pair<int, const char*> errors[] = { }; #undef FOLLY_DETAIL_ERROR -} // namespace - -namespace signal_safe { - void writeStderr(const char* s, size_t len) { fileutil_detail::wrapFull(write, STDERR_FILENO, const_cast<char*>(s), len); } - void writeStderr(const char* s) { writeStderr(s, strlen(s)); } - void flushStderr() { fileutil_detail::wrapNoInt(fsync, STDERR_FILENO); } -void writeStderr(unsigned long x) { +[[noreturn]] FOLLY_COLD void safe_assert_terminate_v( + safe_assert_arg const* arg_, int const error, va_list msg) noexcept { + auto const& arg = *arg_; char buf[to_ascii_size_max_decimal<uint64_t>()]; - writeStderr(buf, to_ascii_decimal(buf, x)); -} -void writeErrBegin(const char* expr, const char* msg) { writeStderr("\n\nAssertion failure: "); - writeStderr(expr + 1, strlen(expr) - 2); - writeStderr("\nMessage: "); - writeStderr(msg); -} - -[[noreturn]] void writeErrEnd( - const char* file, unsigned int line, const char* function, int error) { + writeStderr(arg.expr + 1, strlen(arg.expr) - 2); + if (*arg.msg_types != safe_assert_msg_type::term) { + writeStderr("\nMessage: "); + auto msg_types = arg.msg_types; + bool stop = false; + while (!stop) { + switch (*msg_types++) { + case safe_assert_msg_type::term: + stop = true; + break; + case safe_assert_msg_type::cstr: + writeStderr(va_arg(msg, char const*)); + break; + case safe_assert_msg_type::ui64: + writeStderr(buf, to_ascii_decimal(buf, va_arg(msg, uint64_t))); + break; + } + } + } writeStderr("\nFile: "); - writeStderr(file); + writeStderr(arg.file); writeStderr("\nLine: "); - writeStderr(line); + writeStderr(buf, to_ascii_decimal(buf, arg.line)); writeStderr("\nFunction: "); - writeStderr(function); + writeStderr(arg.function); if (error) { // if errno is set, print the number and the symbolic constant // the symbolic constant is necessary since actual numbers may vary // for simplicity, do not attempt to mimic strerror printing descriptions writeStderr("\nError: "); - writeStderr(error); + writeStderr(buf, to_ascii_decimal(buf, error)); writeStderr(" ("); // the list is not required to be sorted; but the program is about to die auto const pred = [=](auto const e) { return e.first == error; }; @@ -495,5 +503,23 @@ void writeErrBegin(const char* expr, const char* msg) { abort(); } -} // namespace signal_safe +} // namespace + +template <> +void safe_assert_terminate<0>(safe_assert_arg const* arg, ...) noexcept { + va_list msg; + va_start(msg, arg); + safe_assert_terminate_v(arg, 0, msg); + va_end(msg); +} + +template <> +void safe_assert_terminate<1>(safe_assert_arg const* arg, ...) noexcept { + va_list msg; + va_start(msg, arg); + safe_assert_terminate_v(arg, errno, msg); + va_end(msg); +} + +} // namespace detail } // namespace folly diff --git a/folly/lang/SafeAssert.h b/folly/lang/SafeAssert.h index 18dd02446661bf59ddead2ec983326425d765eab..e976e757b3c3e377eb8d4754647510601f1046af 100644 --- a/folly/lang/SafeAssert.h +++ b/folly/lang/SafeAssert.h @@ -16,58 +16,130 @@ #pragma once -#include <cerrno> +#include <cstdint> +#include <utility> +#include <folly/CppAttributes.h> #include <folly/Portability.h> #include <folly/Preprocessor.h> +#include <folly/lang/CArray.h> -#define FOLLY_SAFE_CHECK_IMPL(expr, expr_s, msg, error, ...) \ - ((expr) ? static_cast<void>(0) \ - : (::folly::signal_safe::writeErrBegin( \ - FOLLY_PP_STRINGIZE(expr_s), (msg)), \ - ::folly::signal_safe::writeStderrPack(__VA_ARGS__), \ - ::folly::signal_safe::writeErrEnd( \ - __FILE__, __LINE__, __PRETTY_FUNCTION__, error))) - -/** - * Verify that the expression is true. If not, prints an error message - * (containing msg) to stderr and abort()s. Just like CHECK(), but only - * logs to stderr and only does async-signal-safe calls. - */ -#define FOLLY_SAFE_CHECK(expr, msg, ...) \ - FOLLY_SAFE_CHECK_IMPL((expr), (expr), (msg), 0, __VA_ARGS__); +#define FOLLY_DETAIL_SAFE_CHECK_IMPL(d, p, expr, expr_s, ...) \ + if ((!d || ::folly::kIsDebug || ::folly::kIsSanitize) && \ + !static_cast<bool>(expr)) { \ + static constexpr auto __folly_detail_safe_assert_arg = \ + ::folly::detail::safe_assert_arg{ \ + FOLLY_PP_STRINGIZE(expr_s), \ + __FILE__, \ + __LINE__, \ + __PRETTY_FUNCTION__, \ + ::folly::detail::safe_assert_msg_types_ptr<decltype( \ + ::folly::detail::safe_assert_msg_types_seq_of(__VA_ARGS__))>}; \ + ::folly::detail::safe_assert_terminate<p>( \ + __folly_detail_safe_assert_arg, __VA_ARGS__); \ + } \ + [] {}() -/** - * In debug mode, verify that the expression is true. Otherwise, do nothing - * (do not even evaluate expr). Just like DCHECK(), but only logs to stderr and - * only does async-signal-safe calls. - */ -#define FOLLY_SAFE_DCHECK(expr, msg, ...) \ - FOLLY_SAFE_CHECK_IMPL( \ - !::folly::kIsDebug || (expr), (expr), (msg), 0, __VA_ARGS__) +// FOLLY_SAFE_CHECK +// +// If expr evaluates to false after explicit conversion to bool, prints context +// information to stderr and aborts. Context information includes the remaining +// variadic arguments. +// +// When the remaining variadic arguments are printed to stderr, there are two +// supported types after implicit conversions: char const* and uint64_t. This +// facility is intentionally not extensible to custom types. +// +// multi-thread-safe +// async-signal-safe +#define FOLLY_SAFE_CHECK(expr, ...) \ + FOLLY_DETAIL_SAFE_CHECK_IMPL( \ + 0, 0, (expr), FOLLY_PP_STRINGIZE(expr), __VA_ARGS__) -/** - * Like FOLLY_SAFE_CHECK, but also prints errno. - */ -#define FOLLY_SAFE_PCHECK(expr, msg, ...) \ - FOLLY_SAFE_CHECK_IMPL((expr), (expr), (msg), errno, __VA_ARGS__) +// FOLLY_SAFE_DCHECK +// +// Equivalent to FOLLY_SAFE_CHECK when in debug or instrumented builds, where +// debug builds are signalled by NDEBUG being undefined and instrumented builds +// include sanitizer builds. +// +// multi-thread-safe +// async-signal-safe +#define FOLLY_SAFE_DCHECK(expr, ...) \ + FOLLY_DETAIL_SAFE_CHECK_IMPL( \ + 1, 0, (expr), FOLLY_PP_STRINGIZE(expr), __VA_ARGS__) + +// FOLLY_SAFE_PCHECK +// +// Equivalent to FOLLY_SAFE_CHECK but includes errno in the context information +// printed to stderr. +// +// multi-thread-safe +// async-signal-safe +#define FOLLY_SAFE_PCHECK(expr, ...) \ + FOLLY_DETAIL_SAFE_CHECK_IMPL( \ + 0, 1, (expr), FOLLY_PP_STRINGIZE(expr), __VA_ARGS__) namespace folly { -namespace signal_safe { +namespace detail { + +enum class safe_assert_msg_type : char { term, cstr, ui64 }; + +template <safe_assert_msg_type... A> +struct safe_assert_msg_type_s {}; -void writeStderr(const char*); -void writeStderr(unsigned long int); +struct safe_assert_msg_types_one_fn { + template <safe_assert_msg_type A> + using c = std::integral_constant<safe_assert_msg_type, A>; + // only used in unevaluated contexts: + c<safe_assert_msg_type::cstr> operator()(char const*) const; + c<safe_assert_msg_type::ui64> operator()(uint64_t) const; +}; +FOLLY_INLINE_VARIABLE constexpr safe_assert_msg_types_one_fn + safe_assert_msg_types_one{}; // a function object to prevent extensions template <typename... A> -void writeStderrPack(A... a) { - // NOTE: C++14-compatible version of comma fold expression. - using _ = int[]; - void(_{0, (writeStderr(a), 0)...}); -} +safe_assert_msg_type_s<decltype(safe_assert_msg_types_one(A{}))::value...> +safe_assert_msg_types_seq_of(A...); // only used in unevaluated contexts + +template <typename> +struct safe_assert_msg_types; +template <safe_assert_msg_type... A> +struct safe_assert_msg_types<safe_assert_msg_type_s<A...>> { + using value_type = c_array<safe_assert_msg_type, sizeof...(A) + 1>; + static constexpr value_type value = {{A..., safe_assert_msg_type::term}}; +}; +template <safe_assert_msg_type... A> +constexpr + typename safe_assert_msg_types<safe_assert_msg_type_s<A...>>::value_type + safe_assert_msg_types<safe_assert_msg_type_s<A...>>::value; +template <typename S> +static constexpr safe_assert_msg_type const* safe_assert_msg_types_ptr = + safe_assert_msg_types<S>::value.data; + +struct safe_assert_arg { + char const* expr; + char const* file; + unsigned int line; + char const* function; + safe_assert_msg_type const* msg_types; +}; -void writeErrBegin(const char* expr, const char* msg); -[[noreturn]] void writeErrEnd( - const char* file, unsigned int line, const char* function, int error); +struct safe_assert_msg_cast_one_fn { + FOLLY_ERASE auto operator()(char const* const a) const { return a; } + FOLLY_ERASE auto operator()(uint64_t const a) const { return a; } +}; +FOLLY_INLINE_VARIABLE constexpr safe_assert_msg_cast_one_fn + safe_assert_msg_cast_one{}; // a function object to prevent extensions + +template <bool P> +[[noreturn]] FOLLY_COLD FOLLY_NOINLINE void safe_assert_terminate( + safe_assert_arg const* arg, ...) noexcept; // the true backing function + +template <bool P, typename... A> +[[noreturn]] FOLLY_ERASE void safe_assert_terminate( + safe_assert_arg const& arg, A... a) noexcept { + safe_assert_terminate<P>(&arg, safe_assert_msg_cast_one(a)...); +} -} // namespace signal_safe +} // namespace detail } // namespace folly diff --git a/folly/lang/test/SafeAssertTest.cpp b/folly/lang/test/SafeAssertTest.cpp index 664bbe9b7658d75244038b7dcfb81cd9a8879f03..ff2b6ce9a548f07bdd2f2bcd9e5f658badea936a 100644 --- a/folly/lang/test/SafeAssertTest.cpp +++ b/folly/lang/test/SafeAssertTest.cpp @@ -53,9 +53,9 @@ TEST(SafeAssert, AssertionFailure) { TEST(SafeAssert, AssertionFailureErrno) { EXPECT_DEATH( - FOLLY_SAFE_PCHECK((errno = EINVAL) && false, "hello"), + ([] { FOLLY_SAFE_PCHECK((errno = EINVAL) && false, "hello"); }()), folly::to<std::string>("Error: ", EINVAL, " \\(EINVAL\\)")); EXPECT_DEATH( - FOLLY_SAFE_PCHECK((errno = 999) && false, "hello"), + ([] { FOLLY_SAFE_PCHECK((errno = 999) && false, "hello"); }()), folly::to<std::string>("Error: 999 \\(<unknown>\\)")); }