/* * Copyright (C) 2022-present ScyllaDB */ /* * SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0 */ // Common definitions of test cases used in // handle_exception_optimized_test.cc // handle_exception_fallback_test.cc #include #include #include #include #include #include #include #include "seastarx.hh" #include #include #include #include "utils/assert.hh" #include "utils/exceptions.hh" class base_exception : public std::exception {}; class derived_exception : public base_exception {}; static void dummy_fn(void*) { // } template static std::exception_ptr maybe_wrap_eptr(T&& t) { if constexpr (std::is_same_v) { return std::move(t); } else { return std::make_exception_ptr(std::move(t)); } } static const std::type_info& eptr_typeid(std::exception_ptr eptr) { try { std::rethrow_exception(eptr); } catch (...) { return *abi::__cxa_current_exception_type(); } } template static void check_catch(Throw&& ex) { auto eptr = maybe_wrap_eptr(std::move(ex)); BOOST_TEST_MESSAGE("Checking if " << seastar::pretty_type_name(eptr_typeid(eptr)) << " is caught as " << seastar::pretty_type_name(typeid(Capture))); auto typed_eptr = try_catch(eptr); BOOST_CHECK_NE(typed_eptr, nullptr); // Verify that it's the same as what the usual throwing gives // TODO: Does this check make sense? Does the standard guarantee // that this will give the same pointer? try { std::rethrow_exception(eptr); } catch (Capture& t) { BOOST_CHECK_EQUAL(typed_eptr, &t); } catch (...) { // Can happen if the first check fails, just skip SCYLLA_ASSERT(typed_eptr == nullptr); } } template static void check_no_catch(Throw&& ex) { auto eptr = maybe_wrap_eptr(std::move(ex)); BOOST_TEST_MESSAGE("Checking if " << seastar::pretty_type_name(eptr_typeid(eptr)) << " is NOT caught as " << seastar::pretty_type_name(typeid(Capture))); auto typed_eptr = try_catch(eptr); BOOST_CHECK_EQUAL(typed_eptr, nullptr); } template static std::exception_ptr make_nested_eptr(A&& a, B&& b) { try { throw std::move(b); } catch (...) { try { std::throw_with_nested(std::move(a)); } catch (...) { return std::current_exception(); } } } SEASTAR_TEST_CASE(test_try_catch) { // Some standard examples, throwing exceptions derived from std::exception // and catching them through their base types check_catch(derived_exception()); check_catch(derived_exception()); check_catch(derived_exception()); check_no_catch(derived_exception()); check_no_catch(base_exception()); check_catch(base_exception()); check_catch(base_exception()); check_no_catch(base_exception()); // Catching nested exceptions check_catch(make_nested_eptr(base_exception(), derived_exception())); check_catch(make_nested_eptr(base_exception(), derived_exception())); check_no_catch(make_nested_eptr(base_exception(), derived_exception())); // Check that everything works if we throw some crazy stuff check_catch(int(1)); check_no_catch(int(1)); check_no_catch(nullptr); check_no_catch(nullptr); // Catching pointers is not supported, but nothing should break if they are // being thrown derived_exception exc; check_no_catch(&exc); check_no_catch(&exc); check_no_catch(&dummy_fn); check_no_catch(&dummy_fn); check_no_catch(&std::exception::what); check_no_catch(&std::exception::what); return make_ready_future<>(); } SEASTAR_TEST_CASE(test_try_catch_nested) { using nested_type = std::logic_error; using nesting_type = utils::internal::default_nested_exception_type; // Not nested exception { auto inner = std::make_exception_ptr(nested_type("inner")); auto* inner_exception = try_catch_nested(inner); BOOST_REQUIRE_NE(inner_exception, nullptr); BOOST_REQUIRE_EQUAL(inner_exception->what(), "inner"); } // Not nested exception, wrong type { auto inner = std::make_exception_ptr(std::string("inner")); auto* inner_exception = try_catch_nested(inner); BOOST_REQUIRE_EQUAL(inner_exception, nullptr); } // 1 level { auto inner = std::make_exception_ptr(nested_type("inner")); auto outer = make_nested_exception_ptr(nesting_type("outer"), std::move(inner)); auto* exception = try_catch_nested(outer); BOOST_REQUIRE_NE(exception, nullptr); BOOST_REQUIRE_EQUAL(exception->what(), "inner"); } // 2 levels { auto inner = std::make_exception_ptr(nested_type("inner")); auto outer1 = make_nested_exception_ptr(nesting_type("outer"), std::move(inner)); auto outer2 = make_nested_exception_ptr(utils::internal::default_nested_exception_type("outer2"), std::move(outer1)); auto* exception = try_catch_nested(outer2); BOOST_REQUIRE_NE(exception, nullptr); BOOST_REQUIRE_EQUAL(exception->what(), "inner"); } // 3 levels { auto inner = std::make_exception_ptr(nested_type("inner")); auto outer1 = make_nested_exception_ptr(nesting_type("outer"), std::move(inner)); auto outer2 = make_nested_exception_ptr(utils::internal::default_nested_exception_type("outer2"), std::move(outer1)); auto outer3 = make_nested_exception_ptr(utils::internal::default_nested_exception_type("outer3"), std::move(outer2)); auto* exception = try_catch_nested(outer3); BOOST_REQUIRE_EQUAL(exception->what(), "inner"); } // Negative case, wrong type { auto inner = std::make_exception_ptr(std::string("inner")); auto outer = make_nested_exception_ptr(nesting_type("outer"), std::move(inner)); auto* exception = try_catch_nested(outer); BOOST_REQUIRE_EQUAL(exception, nullptr); } // Negative case - null nested exception { std::exception_ptr inner; auto outer = std::make_exception_ptr(utils::internal::nested_exception(nesting_type("outer"), std::move(inner))); auto* exception = try_catch_nested(outer); BOOST_REQUIRE_EQUAL(exception, nullptr); } return make_ready_future<>(); } SEASTAR_TEST_CASE(test_make_nested_exception_ptr) { auto inner = std::make_exception_ptr(std::runtime_error("inner")); auto outer = make_nested_exception_ptr(std::runtime_error("outer"), inner); try { std::rethrow_exception(outer); } catch (const std::runtime_error& ex) { BOOST_REQUIRE_EQUAL(std::string(ex.what()), "outer"); auto* nested = dynamic_cast(&ex); BOOST_REQUIRE_NE(nested, nullptr); BOOST_REQUIRE_EQUAL(nested->nested_ptr(), inner); } try { std::rethrow_exception(outer); } catch (const std::nested_exception& ex) { BOOST_REQUIRE_EQUAL(ex.nested_ptr(), inner); } // Not a class BOOST_REQUIRE_THROW(std::rethrow_exception(make_nested_exception_ptr(int(123), inner)), int); // Final, so cannot add the std::nested_exception mixin to it struct ultimate_exception final : public std::exception {}; BOOST_REQUIRE_THROW(std::rethrow_exception(make_nested_exception_ptr(ultimate_exception(), inner)), ultimate_exception); // Already derived from nested exception, so cannot put more to it struct already_nested_exception : public std::exception, public std::nested_exception {}; auto inner2 = std::make_exception_ptr(std::runtime_error("inner2")); try { std::rethrow_exception(inner2); } catch (const std::runtime_error&) { try { std::rethrow_exception(make_nested_exception_ptr(already_nested_exception(), inner)); } catch (const already_nested_exception& ex) { BOOST_REQUIRE_EQUAL(ex.nested_ptr(), inner2); } } return make_ready_future<>(); }