/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/lang/Exception.h>
#include <algorithm>
#include <cstring>
#include <functional>
#include <string>
#include <fmt/format.h>
#include <folly/Portability.h>
#include <folly/lang/Align.h>
#include <folly/lang/Keep.h>
#include <folly/lang/Pretty.h>
#include <folly/portability/GTest.h>
using namespace std::literals;
extern "C" FOLLY_KEEP void check_cond_std_terminate(bool c) {
if (c) {
std::terminate();
}
folly::detail::keep_sink();
}
extern "C" FOLLY_KEEP void check_cond_folly_terminate_with(bool c) {
if (c) {
folly::terminate_with<std::runtime_error>("bad error");
}
folly::detail::keep_sink();
}
extern "C" FOLLY_KEEP std::exception const* check_get_object_exception(
std::exception_ptr const& ptr) {
return folly::exception_ptr_get_object<std::exception>(ptr);
}
extern "C" FOLLY_KEEP void check_cond_catch_exception(bool c) {
auto try_ = [=] { c ? folly::throw_exception(0) : void(); };
auto catch_ = []() { folly::detail::keep_sink(); };
folly::catch_exception(try_, catch_);
}
extern "C" FOLLY_KEEP void check_cond_catch_exception_nx(bool c) {
auto try_ = [=] { c ? folly::throw_exception(0) : void(); };
auto catch_ = []() noexcept { folly::detail::keep_sink_nx(); };
folly::catch_exception(try_, catch_);
}
extern "C" FOLLY_KEEP void check_cond_catch_exception_ptr(bool c) {
auto try_ = [=] { c ? folly::throw_exception(0) : void(); };
auto catch_ = folly::detail::keep_sink<>;
folly::catch_exception(try_, catch_);
}
extern "C" FOLLY_KEEP void check_cond_catch_exception_ptr_nx(bool c) {
auto try_ = [=] { c ? folly::throw_exception(0) : void(); };
auto catch_ = folly::detail::keep_sink_nx<>;
folly::catch_exception(try_, catch_);
}
extern "C" FOLLY_KEEP void check_std_make_exception_ptr(
std::exception_ptr* ptr) {
::new (ptr) std::exception_ptr( //
std::make_exception_ptr(std::runtime_error("foo")));
}
extern "C" FOLLY_KEEP void check_folly_make_exception_ptr_with_in_place(
std::exception_ptr* ptr) {
::new (ptr) std::exception_ptr(
folly::make_exception_ptr_with(std::in_place, std::runtime_error("foo")));
}
extern "C" FOLLY_KEEP void check_folly_make_exception_ptr_with_in_place_type(
std::exception_ptr* ptr) {
constexpr auto tag = std::in_place_type<std::runtime_error>;
::new (ptr) std::exception_ptr( //
folly::make_exception_ptr_with(tag, "foo"));
}
extern "C" FOLLY_KEEP void check_folly_make_exception_ptr_with_invocable(
std::exception_ptr* ptr) {
::new (ptr) std::exception_ptr(
folly::make_exception_ptr_with([] { return std::runtime_error("foo"); }));
}
template <typename Ex>
static std::string message_for_terminate_with(std::string const& what) {
auto const name = folly::pretty_name<Ex>();
std::string const p0 = "terminate called after throwing an instance of";
std::string const p1 = "terminating (due to|with) uncaught exception of type";
// clang-format off
return
folly::kIsGlibcxx ? p0 + " '" + name + "'\\s+what\\(\\):\\s+" + what :
folly::kIsLibcpp ? p1 + " " + name + ": " + what :
"" /* empty regex matches anything */;
// clang-format on
}
static std::string message_for_terminate() {
// clang-format off
return
folly::kIsGlibcxx ? "terminate called without an active exception" :
folly::kIsLibcpp ? "terminating" :
"" /* empty regex matches anything */;
// clang-format on
}
namespace {
template <int I>
struct Virt {
virtual ~Virt() {}
int value = I;
operator int() const { return value; }
};
} // namespace
class MyException : public std::exception {
private:
char const* what_;
public:
explicit MyException(char const* const what) : MyException(what, 0) {}
MyException(char const* const what, std::size_t const strip)
: what_(what + strip) {}
char const* what() const noexcept override { return what_; }
};
class ExceptionTest : public testing::Test {};
TEST_F(ExceptionTest, throw_exception_direct) {
try {
folly::throw_exception<MyException>("hello world");
ADD_FAILURE();
} catch (MyException const& ex) {
EXPECT_STREQ("hello world", ex.what());
}
}
TEST_F(ExceptionTest, throw_exception_variadic) {
try {
folly::throw_exception<MyException>("hello world", 6);
ADD_FAILURE();
} catch (MyException const& ex) {
EXPECT_STREQ("world", ex.what());
}
}
TEST_F(ExceptionTest, terminate_with_direct) {
EXPECT_DEATH(
folly::terminate_with<MyException>("hello world"),
message_for_terminate_with<MyException>("hello world"));
}
TEST_F(ExceptionTest, terminate_with_variadic) {
EXPECT_DEATH(
folly::terminate_with<MyException>("hello world", 6),
message_for_terminate_with<MyException>("world"));
}
TEST_F(ExceptionTest, invoke_cold) {
EXPECT_THROW(
folly::invoke_cold([] { throw std::runtime_error("bad"); }),
std::runtime_error);
EXPECT_EQ(7, folly::invoke_cold([] { return 7; }));
}
TEST_F(ExceptionTest, invoke_noreturn_cold) {
EXPECT_THROW(
folly::invoke_noreturn_cold([] { throw std::runtime_error("bad"); }),
std::runtime_error);
EXPECT_DEATH(folly::invoke_noreturn_cold([] {}), message_for_terminate());
}
TEST_F(ExceptionTest, catch_exception) {
auto identity = [](int i) { return i; };
auto returner = [](int i) { return [=] { return i; }; };
auto thrower = [](int i) { return [=]() -> int { throw i; }; };
EXPECT_EQ(3, folly::catch_exception(returner(3), returner(4)));
EXPECT_EQ(3, folly::catch_exception<int>(returner(3), identity));
EXPECT_EQ(3, folly::catch_exception<int>(returner(3), +identity));
EXPECT_EQ(3, folly::catch_exception<int>(returner(3), *+identity));
EXPECT_EQ(4, folly::catch_exception(thrower(3), returner(4)));
EXPECT_EQ(3, folly::catch_exception<int>(thrower(3), identity));
EXPECT_EQ(3, folly::catch_exception<int>(thrower(3), +identity));
EXPECT_EQ(3, folly::catch_exception<int>(thrower(3), *+identity));
}
TEST_F(ExceptionTest, rethrow_current_exception) {
EXPECT_THROW(
folly::invoke_noreturn_cold([] {
try {
throw std::runtime_error("bad");
} catch (...) {
folly::rethrow_current_exception();
}
}),
std::runtime_error);
}
TEST_F(ExceptionTest, uncaught_exception) {
struct dtor {
unsigned expected;
explicit dtor(unsigned e) noexcept : expected{e} {}
~dtor() {
EXPECT_EQ(expected, std::uncaught_exceptions());
EXPECT_EQ(expected, folly::uncaught_exceptions());
}
};
try {
dtor obj{0};
} catch (...) {
}
try {
dtor obj{1};
throw std::exception();
} catch (...) {
}
}
TEST_F(ExceptionTest, current_exception) {
EXPECT_EQ(nullptr, std::current_exception());
EXPECT_EQ(nullptr, folly::current_exception());
try {
throw std::exception();
} catch (...) {
// primary exception?
#if defined(_CPPLIB_VER)
// As per
// https://learn.microsoft.com/en-us/cpp/standard-library/exception-functions?view=msvc-170
// current_exception() returns a new value each time, so its not directly
// comparable on MSVC
EXPECT_NE(nullptr, std::current_exception());
EXPECT_NE(nullptr, folly::current_exception());
#else
EXPECT_EQ(std::current_exception(), folly::current_exception());
#endif
}
try {
throw std::exception();
} catch (...) {
try {
throw;
} catch (...) {
// dependent exception?
#if defined(_CPPLIB_VER)
EXPECT_NE(nullptr, std::current_exception());
EXPECT_NE(nullptr, folly::current_exception());
#else
EXPECT_EQ(std::current_exception(), folly::current_exception());
#endif
}
}
}
TEST_F(ExceptionTest, exception_ptr_empty) {
auto ptr = std::exception_ptr();
EXPECT_EQ(nullptr, folly::exception_ptr_get_type(ptr));
EXPECT_EQ(nullptr, folly::exception_ptr_get_object(ptr, nullptr));
EXPECT_EQ(nullptr, folly::exception_ptr_get_object(ptr, &typeid(long)));
EXPECT_EQ(nullptr, folly::exception_ptr_get_object(ptr, &typeid(int)));
EXPECT_EQ(nullptr, folly::exception_ptr_get_object<int>(ptr));
EXPECT_EQ(nullptr, folly::exception_ptr_get_object(ptr));
}
TEST_F(ExceptionTest, exception_ptr_int) {
auto ptr = std::make_exception_ptr(17);
EXPECT_EQ(&typeid(int), folly::exception_ptr_get_type(ptr));
EXPECT_EQ(17, *(int*)(folly::exception_ptr_get_object(ptr, nullptr)));
EXPECT_EQ(nullptr, folly::exception_ptr_get_object(ptr, &typeid(long)));
EXPECT_EQ(17, *(int*)(folly::exception_ptr_get_object(ptr, &typeid(int))));
EXPECT_EQ(17, *folly::exception_ptr_get_object<int>(ptr));
EXPECT_EQ(17, *(int*)(folly::exception_ptr_get_object(ptr)));
}
TEST_F(ExceptionTest, exception_ptr_vmi) {
using A0 = Virt<0>;
using A1 = Virt<1>;
using A2 = Virt<2>;
struct B0 : virtual A1, virtual A2 {};
struct B1 : virtual A2, virtual A0 {};
struct B2 : virtual A0, virtual A1 {};
struct C : B0, B1, B2 {
int value = 44;
operator int() const { return value; }
};
auto ptr = std::make_exception_ptr(C());
EXPECT_EQ(&typeid(C), folly::exception_ptr_get_type(ptr));
EXPECT_EQ(44, *(C*)(folly::exception_ptr_get_object(ptr, nullptr)));
EXPECT_EQ(nullptr, folly::exception_ptr_get_object(ptr, &typeid(long)));
EXPECT_EQ(44, *(C*)(folly::exception_ptr_get_object(ptr, &typeid(C))));
EXPECT_EQ(1, *(A1*)(folly::exception_ptr_get_object(ptr, &typeid(A1))));
EXPECT_EQ(1, folly::exception_ptr_get_object<A1>(ptr)->value);
EXPECT_EQ(44, *(C*)(folly::exception_ptr_get_object(ptr)));
EXPECT_EQ(
nullptr,
folly::exception_ptr_try_get_object_exact_fast<A0>(
nullptr, folly::tag<B1, B2>));
EXPECT_EQ(
nullptr,
folly::exception_ptr_try_get_object_exact_fast<A0>(
std::make_exception_ptr(17), folly::tag<B1, B2>));
EXPECT_EQ(
nullptr,
folly::exception_ptr_try_get_object_exact_fast<A0>(
ptr, folly::tag<B1, B2>));
EXPECT_EQ(
folly::exception_ptr_get_object<C>(ptr),
folly::exception_ptr_try_get_object_exact_fast<A0>(
ptr, folly::tag<B1, C, B2>));
EXPECT_EQ(
nullptr,
folly::exception_ptr_get_object_hint<A0>(nullptr, folly::tag<B1, B2>));
EXPECT_EQ(
nullptr,
folly::exception_ptr_get_object_hint<A0>(
std::make_exception_ptr(17), folly::tag<B1, B2>));
EXPECT_EQ(
folly::exception_ptr_get_object<C>(ptr),
folly::exception_ptr_get_object_hint<A0>(ptr, folly::tag<B1, B2>));
EXPECT_EQ(
folly::exception_ptr_get_object<C>(ptr),
folly::exception_ptr_get_object_hint<A0>(ptr, folly::tag<B1, C, B2>));
}
TEST_F(ExceptionTest, make_exception_ptr_with_invocable_fail) {
auto ptr = folly::make_exception_ptr_with( //
[]() -> std::string { throw 17; });
EXPECT_EQ(&typeid(int), folly::exception_ptr_get_type(ptr));
EXPECT_EQ(17, *folly::exception_ptr_get_object<int>(ptr));
}
TEST_F(ExceptionTest, make_exception_ptr_with_invocable) {
auto ptr = folly::make_exception_ptr_with( //
[] { return std::string("hello world"); });
EXPECT_EQ(&typeid(std::string), folly::exception_ptr_get_type(ptr));
EXPECT_EQ("hello world", *folly::exception_ptr_get_object<std::string>(ptr));
}
TEST_F(ExceptionTest, make_exception_ptr_with_in_place_type) {
auto ptr = folly::make_exception_ptr_with(
std::in_place_type<std::string>, "hello world");
EXPECT_EQ(&typeid(std::string), folly::exception_ptr_get_type(ptr));
EXPECT_EQ("hello world", *folly::exception_ptr_get_object<std::string>(ptr));
}
TEST_F(ExceptionTest, make_exception_ptr_with_in_place) {
auto ptr = folly::make_exception_ptr_with(std::in_place, 17);
EXPECT_EQ(&typeid(int), folly::exception_ptr_get_type(ptr));
EXPECT_EQ(17, *folly::exception_ptr_get_object<int>(ptr));
}
TEST_F(ExceptionTest, exception_shared_string) {
constexpr auto c = "hello, world!";
auto s0 = folly::exception_shared_string(c);
auto s1 = s0;
auto s2 = s1;
EXPECT_STREQ(c, s2.what());
EXPECT_STREQ(c, folly::exception_shared_string(std::string_view(c)).what());
EXPECT_STREQ(c, folly::exception_shared_string(std::string(c)).what());
}
#if FOLLY_CPLUSPLUS >= 202002
TEST_F(ExceptionTest, exception_shared_string_literal) {
using namespace folly::string_literals;
auto s0 = folly::exception_shared_string("hello, world!"_litv);
auto s1 = s0;
auto s2 = s1;
EXPECT_STREQ("hello, world!", s2.what());
}
#endif
// example of how to do the in-place formatting efficiently
struct format_param_fn {
template <typename A>
using arg_t = folly::conditional_t<folly::is_register_pass_v<A>, A, A const&>;
template <typename... A>
auto operator()(
fmt::format_string<arg_t<A>...> const& fmt, A const&... arg) const {
return std::pair{
fmt::formatted_size(fmt, static_cast<arg_t<A>>(arg)...),
[&](auto buf, auto len) {
auto res =
fmt::format_to_n(buf, len, fmt, static_cast<arg_t<A>>(arg)...);
FOLLY_SAFE_DCHECK(len == res.size);
}};
}
};
inline constexpr format_param_fn format_param{};
TEST_F(ExceptionTest, exception_shared_string_format) {
auto s = std::invoke(
[](auto p) { return folly::exception_shared_string(p.first, p.second); },
format_param("a number {} and a string {}", 217, "flobber"s));
EXPECT_STREQ("a number 217 and a string flobber", s.what());
}