#include "dxxsconf.h" #include "compiler-poison.h" /* If DXX_HAVE_POISON_UNDEFINED is not zero, * array_managed_type::array_managed_type() is declared and must be * defined out of line. These tests do not need to run any code at * array construction time, so force DXX_HAVE_POISON_UNDEFINED to be zero. */ #undef DXX_HAVE_POISON_UNDEFINED #define DXX_HAVE_POISON_UNDEFINED 0 #include "valptridx.h" #include "valptridx.tcc" #define BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE Rebirth valptridx #include /* Convenience macro to prevent the compiler optimizer from performing * constant propagation of the input. This ensures that build-time * tests are suppressed, allowing the runtime test to be emitted and * executed. */ #define OPTIMIZER_HIDE_VARIABLE(V) asm("" : "=rm" (V) : "0" (V) : "memory") /* Convenience function to pass an anonymous value (such as an integer * constant) through OPTIMIZER_HIDE_VARIABLE, then return it. This is * needed for contexts where a bare asm() statement would not be legal. * * It also has the useful secondary effect of casting the input to a * type, if the caller explicitly sets the typename T. */ template static inline T optimizer_hidden_variable(T v) { OPTIMIZER_HIDE_VARIABLE(v); return v; } /* Convenience macro to ignore the result of an expression in a way that * __attribute__((warn_unused_result)) does not warn that the result was * ignored. */ #define DXX_TEST_VALPTRIDX_IGNORE_RETURN(EXPR) ({ auto &&r = EXPR; static_cast(r); }) struct object { /* No state needed - this module is testing valptridx, not the type * managed by valptridx. */ }; using objnum_t = uint16_t; constexpr std::integral_constant MAX_OBJECTS{}; DXX_VALPTRIDX_DECLARE_SUBTYPE(, object, objnum_t, MAX_OBJECTS); DXX_VALPTRIDX_DEFINE_SUBTYPE_TYPEDEFS(object, obj); DXX_VALPTRIDX_DEFINE_GLOBAL_FACTORIES(object, obj, Objects); valptridx::array_managed_type Objects; /* Some valptridx names are normally protected. The tests need access * to them. Define a subclass that relaxes the permissions. */ struct valptridx_access_override : valptridx { using base_type = valptridx; using typename base_type::allow_end_construction; using typename base_type::index_range_exception; using typename base_type::null_pointer_exception; using typename base_type::report_error_uses_exception; }; /* Test that using the factory does not throw an exception on the * highest valid index. */ BOOST_AUTO_TEST_CASE(idx_no_exception_near_end) { BOOST_CHECK_NO_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN( Objects.vmptr(optimizer_hidden_variable(MAX_OBJECTS - 1))) ); } /* Test that using the factory does throw an exception on the lowest * invalid index and on the highest representable integer. */ BOOST_AUTO_TEST_CASE(idx_exception_at_end) { if (!valptridx_access_override::report_error_uses_exception::value) /* If valptridx is configured not to use exceptions to report * errors, these tests will fail even when the logic is correct. * Skip them in that case. */ return; BOOST_CHECK_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN( Objects.vmptr( optimizer_hidden_variable(MAX_OBJECTS))), valptridx_access_override::index_range_exception); BOOST_CHECK_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN( Objects.vmptr( optimizer_hidden_variable(UINT16_MAX))), valptridx_access_override::index_range_exception); } BOOST_AUTO_TEST_CASE(iter_no_exception_at_allowed_end) { /* Test the allowed side of end() handling for off-by-one bugs. */ BOOST_CHECK_NO_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN( (Objects.set_count(optimizer_hidden_variable(MAX_OBJECTS - 1)), Objects.vmptr.end()) )); BOOST_CHECK_NO_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN( (Objects.set_count(optimizer_hidden_variable(MAX_OBJECTS)), Objects.vmptr.end()) )); } BOOST_AUTO_TEST_CASE(iter_exception_if_beyond_end) { /* Test that end() throws an exception if the range's count exceeds * the valid storage size. */ if (!valptridx_access_override::report_error_uses_exception::value) return; BOOST_CHECK_THROW((DXX_TEST_VALPTRIDX_IGNORE_RETURN( (Objects.set_count(optimizer_hidden_variable(MAX_OBJECTS + 1)), Objects.vmptr.end()) )), valptridx_access_override::index_range_exception); } BOOST_AUTO_TEST_CASE(guarded_in_range) { auto &&g = Objects.vmptr.check_untrusted(optimizer_hidden_variable(0)); optimizer_hidden_variable(g); /* Test that a `guarded` returned by `check_untrusted` on a valid * index throws if accessed without testing its validity. A runtime * check on validity is mandatory. * * These exceptions are not configurable, so no test on * `report_error_uses_exception` is required. */ BOOST_CHECK_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN(*g), std::logic_error); /* Test its validity */ BOOST_TEST(static_cast(g)); /* Test that it can now be read successfully. */ BOOST_CHECK_NO_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN(*g)); } BOOST_AUTO_TEST_CASE(guarded_out_of_range) { auto &&g = Objects.vmptr.check_untrusted(optimizer_hidden_variable(MAX_OBJECTS)); optimizer_hidden_variable(g); /* Test that a `guarded` returned by `check_untrusted` on an invalid * index throws if accessed without testing its validity. * * Test its validity. * * Test that it still throws afterward. */ BOOST_CHECK_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN(*g), std::logic_error); BOOST_TEST(!static_cast(g)); BOOST_CHECK_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN(*g), std::logic_error); } BOOST_AUTO_TEST_CASE(idx_convert_check) { using vo = valptridx; BOOST_CHECK_NO_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN(({ typename vo::imidx i(static_cast(0)); typename vo::vmidx{i}; })) ); typename vo::imidx i_none(static_cast(~0)); if (!valptridx_access_override::report_error_uses_exception::value) return; BOOST_CHECK_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN(({ typename vo::vmidx{i_none}; })), valptridx_access_override::index_range_exception ); BOOST_CHECK_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN(({ typename vo::vmidx{optimizer_hidden_variable(~0)}; })), valptridx_access_override::index_range_exception ); } BOOST_AUTO_TEST_CASE(ptr_convert_check) { using vo = valptridx; typename vo::imptr i_none(nullptr); auto &&i_zero = Objects.imptr(optimizer_hidden_variable(0)); BOOST_CHECK_NO_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN(({ typename vo::vmptr v_zero{i_zero}; v_zero.get_nonnull_pointer(); })) ); if (!valptridx_access_override::report_error_uses_exception::value) return; BOOST_CHECK_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN(({ typename vo::vmptr{i_none}; })), valptridx_access_override::null_pointer_exception ); BOOST_CHECK_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN(({ Objects.vmptr(optimizer_hidden_variable(nullptr)); })), valptridx_access_override::null_pointer_exception ); BOOST_CHECK_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN(({ i_none.get_nonnull_pointer(); })), valptridx_access_override::null_pointer_exception ); auto &&pi_zero = Objects.vmptridx(optimizer_hidden_variable(0)); BOOST_CHECK_THROW( DXX_TEST_VALPTRIDX_IGNORE_RETURN(({ pi_zero.absolute_sibling(optimizer_hidden_variable(Objects.size())); })), valptridx_access_override::index_range_exception ); } /* For each style selector, set a type. Test that the type is used. * Reset to a different type. Test that the second type is used. Clear * the selector, so that any further use is an error. * * This combination verifies that the requested style is set, and that * the match is not an accident. If the first test succeeded because * the style happened to match for the wrong reason, then redefining the * style would have no effect, and the second test would fail. */ /* Test DXX_VALPTRIDX_REPORT_ERROR_STYLE_default - the final fallback, * used only when everything else is unset. */ #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default exception static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::exception); static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::exception); #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default undefined static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::undefined); static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::undefined); #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default /* invalid mapping - force an error on use */ /* Test the const/mutable defaults. These are the second lowest * priority, above only DXX_VALPTRIDX_REPORT_ERROR_STYLE_default. */ #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default trap_terse #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default trap_verbose static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::trap_terse); static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::trap_verbose); #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default exception #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default trap_terse static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::exception); static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::trap_terse); #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default /* Test the type-specific default. This is the third lowest priority. */ #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_error_report_test undefined static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::undefined); static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::undefined); #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_error_report_test #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_error_report_test exception static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::exception); static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::exception); #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_error_report_test #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_error_report_test /* Test the const/mutable type-specific. These are the highest * priority. */ #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_error_report_test trap_terse #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_error_report_test trap_verbose static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::trap_terse); static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::trap_verbose); #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_error_report_test #undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_error_report_test #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_error_report_test exception #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_error_report_test undefined static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::exception); static_assert(valptridx_detail::untyped_utilities::error_style_dispatch::value == valptridx_detail::untyped_utilities::report_error_style::undefined);