Commit b4f7d822 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook GitHub Bot

revise FOLLY_SAFE_CHECK

Summary:
Principally, this change shrink the code generated by `FOLLY_SAFE_CHECK` by:
* Moving the literal arguments which would be passed to the backing functions into a constexpr data structure and passing only a pointer to that data structure.
* Passing all the arguments through via C variadic arguments rather than inlining the sequence of calls to handle them into the caller.

Reviewed By: swolchok, luciang

Differential Revision: D26996101

fbshipit-source-id: 61c39c08758a9de28ba0cfd342b174185e80ad9f
parent 55dfcb03
...@@ -17,12 +17,15 @@ ...@@ -17,12 +17,15 @@
#include <folly/lang/SafeAssert.h> #include <folly/lang/SafeAssert.h>
#include <algorithm> #include <algorithm>
#include <cerrno>
#include <cstdarg>
#include <folly/detail/FileUtilDetail.h> #include <folly/detail/FileUtilDetail.h>
#include <folly/lang/ToAscii.h> #include <folly/lang/ToAscii.h>
#include <folly/portability/Unistd.h> #include <folly/portability/Unistd.h>
namespace folly { namespace folly {
namespace detail {
namespace { namespace {
...@@ -441,48 +444,53 @@ constexpr std::pair<int, const char*> errors[] = { ...@@ -441,48 +444,53 @@ constexpr std::pair<int, const char*> errors[] = {
}; };
#undef FOLLY_DETAIL_ERROR #undef FOLLY_DETAIL_ERROR
} // namespace
namespace signal_safe {
void writeStderr(const char* s, size_t len) { void writeStderr(const char* s, size_t len) {
fileutil_detail::wrapFull(write, STDERR_FILENO, const_cast<char*>(s), len); fileutil_detail::wrapFull(write, STDERR_FILENO, const_cast<char*>(s), len);
} }
void writeStderr(const char* s) { void writeStderr(const char* s) {
writeStderr(s, strlen(s)); writeStderr(s, strlen(s));
} }
void flushStderr() { void flushStderr() {
fileutil_detail::wrapNoInt(fsync, STDERR_FILENO); 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>()]; 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("\n\nAssertion failure: ");
writeStderr(expr + 1, strlen(expr) - 2); writeStderr(arg.expr + 1, strlen(arg.expr) - 2);
if (*arg.msg_types != safe_assert_msg_type::term) {
writeStderr("\nMessage: "); writeStderr("\nMessage: ");
writeStderr(msg); auto msg_types = arg.msg_types;
} bool stop = false;
while (!stop) {
[[noreturn]] void writeErrEnd( switch (*msg_types++) {
const char* file, unsigned int line, const char* function, int error) { 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("\nFile: ");
writeStderr(file); writeStderr(arg.file);
writeStderr("\nLine: "); writeStderr("\nLine: ");
writeStderr(line); writeStderr(buf, to_ascii_decimal(buf, arg.line));
writeStderr("\nFunction: "); writeStderr("\nFunction: ");
writeStderr(function); writeStderr(arg.function);
if (error) { if (error) {
// if errno is set, print the number and the symbolic constant // if errno is set, print the number and the symbolic constant
// the symbolic constant is necessary since actual numbers may vary // the symbolic constant is necessary since actual numbers may vary
// for simplicity, do not attempt to mimic strerror printing descriptions // for simplicity, do not attempt to mimic strerror printing descriptions
writeStderr("\nError: "); writeStderr("\nError: ");
writeStderr(error); writeStderr(buf, to_ascii_decimal(buf, error));
writeStderr(" ("); writeStderr(" (");
// the list is not required to be sorted; but the program is about to die // 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; }; auto const pred = [=](auto const e) { return e.first == error; };
...@@ -495,5 +503,23 @@ void writeErrBegin(const char* expr, const char* msg) { ...@@ -495,5 +503,23 @@ void writeErrBegin(const char* expr, const char* msg) {
abort(); 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 } // namespace folly
...@@ -16,58 +16,130 @@ ...@@ -16,58 +16,130 @@
#pragma once #pragma once
#include <cerrno> #include <cstdint>
#include <utility>
#include <folly/CppAttributes.h>
#include <folly/Portability.h> #include <folly/Portability.h>
#include <folly/Preprocessor.h> #include <folly/Preprocessor.h>
#include <folly/lang/CArray.h>
#define FOLLY_SAFE_CHECK_IMPL(expr, expr_s, msg, error, ...) \ #define FOLLY_DETAIL_SAFE_CHECK_IMPL(d, p, expr, expr_s, ...) \
((expr) ? static_cast<void>(0) \ if ((!d || ::folly::kIsDebug || ::folly::kIsSanitize) && \
: (::folly::signal_safe::writeErrBegin( \ !static_cast<bool>(expr)) { \
FOLLY_PP_STRINGIZE(expr_s), (msg)), \ static constexpr auto __folly_detail_safe_assert_arg = \
::folly::signal_safe::writeStderrPack(__VA_ARGS__), \ ::folly::detail::safe_assert_arg{ \
::folly::signal_safe::writeErrEnd( \ FOLLY_PP_STRINGIZE(expr_s), \
__FILE__, __LINE__, __PRETTY_FUNCTION__, error))) __FILE__, \
__LINE__, \
/** __PRETTY_FUNCTION__, \
* Verify that the expression is true. If not, prints an error message ::folly::detail::safe_assert_msg_types_ptr<decltype( \
* (containing msg) to stderr and abort()s. Just like CHECK(), but only ::folly::detail::safe_assert_msg_types_seq_of(__VA_ARGS__))>}; \
* logs to stderr and only does async-signal-safe calls. ::folly::detail::safe_assert_terminate<p>( \
*/ __folly_detail_safe_assert_arg, __VA_ARGS__); \
#define FOLLY_SAFE_CHECK(expr, msg, ...) \ } \
FOLLY_SAFE_CHECK_IMPL((expr), (expr), (msg), 0, __VA_ARGS__); [] {}()
/** // FOLLY_SAFE_CHECK
* 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 // If expr evaluates to false after explicit conversion to bool, prints context
* only does async-signal-safe calls. // information to stderr and aborts. Context information includes the remaining
*/ // variadic arguments.
#define FOLLY_SAFE_DCHECK(expr, msg, ...) \ //
FOLLY_SAFE_CHECK_IMPL( \ // When the remaining variadic arguments are printed to stderr, there are two
!::folly::kIsDebug || (expr), (expr), (msg), 0, __VA_ARGS__) // 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__)
/** // FOLLY_SAFE_DCHECK
* Like FOLLY_SAFE_CHECK, but also prints errno. //
*/ // Equivalent to FOLLY_SAFE_CHECK when in debug or instrumented builds, where
#define FOLLY_SAFE_PCHECK(expr, msg, ...) \ // debug builds are signalled by NDEBUG being undefined and instrumented builds
FOLLY_SAFE_CHECK_IMPL((expr), (expr), (msg), errno, __VA_ARGS__) // 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 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*); struct safe_assert_msg_types_one_fn {
void writeStderr(unsigned long int); 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> template <typename... A>
void writeStderrPack(A... a) { safe_assert_msg_type_s<decltype(safe_assert_msg_types_one(A{}))::value...>
// NOTE: C++14-compatible version of comma fold expression. safe_assert_msg_types_seq_of(A...); // only used in unevaluated contexts
using _ = int[];
void(_{0, (writeStderr(a), 0)...}); 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); struct safe_assert_msg_cast_one_fn {
[[noreturn]] void writeErrEnd( FOLLY_ERASE auto operator()(char const* const a) const { return a; }
const char* file, unsigned int line, const char* function, int error); 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 } // namespace folly
...@@ -53,9 +53,9 @@ TEST(SafeAssert, AssertionFailure) { ...@@ -53,9 +53,9 @@ TEST(SafeAssert, AssertionFailure) {
TEST(SafeAssert, AssertionFailureErrno) { TEST(SafeAssert, AssertionFailureErrno) {
EXPECT_DEATH( EXPECT_DEATH(
FOLLY_SAFE_PCHECK((errno = EINVAL) && false, "hello"), ([] { FOLLY_SAFE_PCHECK((errno = EINVAL) && false, "hello"); }()),
folly::to<std::string>("Error: ", EINVAL, " \\(EINVAL\\)")); folly::to<std::string>("Error: ", EINVAL, " \\(EINVAL\\)"));
EXPECT_DEATH( EXPECT_DEATH(
FOLLY_SAFE_PCHECK((errno = 999) && false, "hello"), ([] { FOLLY_SAFE_PCHECK((errno = 999) && false, "hello"); }()),
folly::to<std::string>("Error: 999 \\(<unknown>\\)")); folly::to<std::string>("Error: 999 \\(<unknown>\\)"));
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment