diff --git a/folly/lang/BUCK b/folly/lang/BUCK index 1f9fe87b260..bbbfa68540e 100644 --- a/folly/lang/BUCK +++ b/folly/lang/BUCK @@ -138,6 +138,7 @@ cpp_library( ":new", ], exported_deps = [ + "fbsource//third-party/fmt:fmt", ":assume", ":safe_assert", ":thunk", diff --git a/folly/lang/Exception.h b/folly/lang/Exception.h index 9b3f42c8b49..ea2c6afc9cc 100644 --- a/folly/lang/Exception.h +++ b/folly/lang/Exception.h @@ -18,10 +18,15 @@ #include #include +#include #include #include #include +#if __has_include() +#include +#endif + #include #include #include @@ -86,6 +91,9 @@ using throw_exception_arg_ = // template using throw_exception_arg_t = typename throw_exception_arg_::template apply; +template +using throw_exception_arg_fmt_t = + remove_cvref_t::template apply>; template [[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void throw_exception_( @@ -129,6 +137,56 @@ template static_cast(args)...); } +#if __has_include() + +namespace detail { + +template +[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void +throw_exception_fmt_format_(Str str, Args&&... args) { + auto what = [&] { return fmt::format(str, static_cast(args)...); }; + if constexpr (std::is_constructible_v) { + throw_exception(what()); + } else { + throw_exception(what().c_str()); + } +} + +template +[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void +terminate_with_fmt_format_(Str str, Args&&... args) noexcept { + auto what = [&] { return fmt::format(str, static_cast(args)...); }; + if constexpr (std::is_constructible_v) { + throw_exception(what()); + } else { + throw_exception(what().c_str()); + } +} + +} // namespace detail + +template +[[noreturn]] FOLLY_ERASE void throw_exception_fmt_format( + fmt::format_string...> str, + Args&&... args) { + detail::throw_exception_fmt_format_< // + Ex, + detail::throw_exception_arg_t...>( + str, static_cast(args)...); +} + +template +[[noreturn]] FOLLY_ERASE void terminate_with_fmt_format( + fmt::format_string...> str, + Args&&... args) { + detail::terminate_with_fmt_format_< // + Ex, + detail::throw_exception_arg_t...>( + str, static_cast(args)...); +} + +#endif + /// invoke_cold /// /// Invoke the provided function with the provided arguments. diff --git a/folly/lang/test/ExceptionTest.cpp b/folly/lang/test/ExceptionTest.cpp index 2cc67c984b7..29f4937e99e 100644 --- a/folly/lang/test/ExceptionTest.cpp +++ b/folly/lang/test/ExceptionTest.cpp @@ -136,6 +136,22 @@ class MyException : public std::exception { char const* what() const noexcept override { return what_; } }; +struct MyFmtFormatCStrException : std::exception { + std::runtime_error inner; + explicit MyFmtFormatCStrException(char const* const what) noexcept + : inner{what} {} + char const* what() const noexcept override { return inner.what(); } +}; + +struct MyFmtFormatStringException : std::exception { + std::runtime_error inner; + explicit MyFmtFormatStringException(std::string&& what) : inner{what} {} + explicit MyFmtFormatStringException(char const*) : inner{""} { + ADD_FAILURE(); + } + char const* what() const noexcept override { return inner.what(); } +}; + class ExceptionTest : public testing::Test {}; TEST_F(ExceptionTest, throw_exception_direct) { @@ -156,6 +172,26 @@ TEST_F(ExceptionTest, throw_exception_variadic) { } } +TEST_F(ExceptionTest, throw_exception_fmt_format_c_str) { + try { + folly::throw_exception_fmt_format( + "{} {}", "hello", "world"); + ADD_FAILURE(); + } catch (MyFmtFormatCStrException const& ex) { + EXPECT_STREQ("hello world", ex.what()); + } +} + +TEST_F(ExceptionTest, throw_exception_fmt_format_string_view) { + try { + folly::throw_exception_fmt_format( + "{} {}", "hello", "world"); + ADD_FAILURE(); + } catch (MyFmtFormatStringException const& ex) { + EXPECT_STREQ("hello world", ex.what()); + } +} + TEST_F(ExceptionTest, terminate_with_direct) { EXPECT_DEATH( folly::terminate_with("hello world"), @@ -168,6 +204,20 @@ TEST_F(ExceptionTest, terminate_with_variadic) { message_for_terminate_with("world")); } +TEST_F(ExceptionTest, terminate_with_fmt_format_c_str) { + EXPECT_DEATH( + folly::terminate_with_fmt_format( + "{} {}", "hello", "world"), + message_for_terminate_with("hello world")); +} + +TEST_F(ExceptionTest, terminate_with_fmt_format_string_view) { + EXPECT_DEATH( + folly::terminate_with_fmt_format( + "{} {}", "hello", "world"), + message_for_terminate_with("hello world")); +} + TEST_F(ExceptionTest, invoke_cold) { EXPECT_THROW( folly::invoke_cold([] { throw std::runtime_error("bad"); }),