Skip to content

Commit

Permalink
throw_exception_fmt_format
Browse files Browse the repository at this point in the history
Summary: A variation of the `throw_exception` and `terminate_with` functions which wraps throwing an exception constructed with a single argument returned from `fmt::format`.

Reviewed By: vitaut

Differential Revision: D64835338

fbshipit-source-id: 64830616cc3c9076f5df7deb0327d94527c3cd8b
  • Loading branch information
yfeldblum authored and facebook-github-bot committed Nov 8, 2024
1 parent 3ccdcba commit cf549a4
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
1 change: 1 addition & 0 deletions folly/lang/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ cpp_library(
":new",
],
exported_deps = [
"fbsource//third-party/fmt:fmt",
":assume",
":safe_assert",
":thunk",
Expand Down
58 changes: 58 additions & 0 deletions folly/lang/Exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@

#include <atomic>
#include <exception>
#include <functional>
#include <string>
#include <type_traits>
#include <utility>

#if __has_include(<fmt/format.h>)
#include <fmt/format.h>
#endif

#include <folly/CPortability.h>
#include <folly/CppAttributes.h>
#include <folly/Likely.h>
Expand Down Expand Up @@ -86,6 +91,9 @@ using throw_exception_arg_ = //
template <typename R>
using throw_exception_arg_t =
typename throw_exception_arg_<R>::template apply<R>;
template <typename R>
using throw_exception_arg_fmt_t =
remove_cvref_t<typename throw_exception_arg_<R>::template apply<R>>;

template <typename Ex, typename... Args>
[[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void throw_exception_(
Expand Down Expand Up @@ -129,6 +137,56 @@ template <typename Ex, typename... Args>
static_cast<Args&&>(args)...);
}

#if __has_include(<fmt/format.h>)

namespace detail {

template <typename Ex, typename... Args, typename Str>
[[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&&>(args)...); };
if constexpr (std::is_constructible_v<Ex, std::string&&>) {
throw_exception<Ex>(what());
} else {
throw_exception<Ex>(what().c_str());
}
}

template <typename Ex, typename... Args, typename Str>
[[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&&>(args)...); };
if constexpr (std::is_constructible_v<Ex, std::string&&>) {
throw_exception<Ex>(what());
} else {
throw_exception<Ex>(what().c_str());
}
}

} // namespace detail

template <typename Ex, typename... Args>
[[noreturn]] FOLLY_ERASE void throw_exception_fmt_format(
fmt::format_string<detail::throw_exception_arg_fmt_t<Args&&>...> str,
Args&&... args) {
detail::throw_exception_fmt_format_< //
Ex,
detail::throw_exception_arg_t<Args&&>...>(
str, static_cast<Args&&>(args)...);
}

template <typename Ex, typename... Args>
[[noreturn]] FOLLY_ERASE void terminate_with_fmt_format(
fmt::format_string<detail::throw_exception_arg_fmt_t<Args&&>...> str,
Args&&... args) {
detail::terminate_with_fmt_format_< //
Ex,
detail::throw_exception_arg_t<Args&&>...>(
str, static_cast<Args&&>(args)...);
}

#endif

/// invoke_cold
///
/// Invoke the provided function with the provided arguments.
Expand Down
50 changes: 50 additions & 0 deletions folly/lang/test/ExceptionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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<MyFmtFormatCStrException>(
"{} {}", "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<MyFmtFormatStringException>(
"{} {}", "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<MyException>("hello world"),
Expand All @@ -168,6 +204,20 @@ TEST_F(ExceptionTest, terminate_with_variadic) {
message_for_terminate_with<MyException>("world"));
}

TEST_F(ExceptionTest, terminate_with_fmt_format_c_str) {
EXPECT_DEATH(
folly::terminate_with_fmt_format<MyFmtFormatCStrException>(
"{} {}", "hello", "world"),
message_for_terminate_with<MyFmtFormatCStrException>("hello world"));
}

TEST_F(ExceptionTest, terminate_with_fmt_format_string_view) {
EXPECT_DEATH(
folly::terminate_with_fmt_format<MyFmtFormatStringException>(
"{} {}", "hello", "world"),
message_for_terminate_with<MyFmtFormatStringException>("hello world"));
}

TEST_F(ExceptionTest, invoke_cold) {
EXPECT_THROW(
folly::invoke_cold([] { throw std::runtime_error("bad"); }),
Expand Down

0 comments on commit cf549a4

Please sign in to comment.