diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index d07713f5..2a0165e5 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -164,8 +164,9 @@ is defined. That policy needs no header - see above. ### link:{headers-url}/boost/openmethod/policies/static_rtti.hpp[] -Provides a minimal implementation of the `rtti` policy that does not depend on -standard RTTI. +Provides cpp:static_rtti[], a minimal implementation of the `rtti` policy that +does not depend on standard RTTI, and cpp:named_static_rtti[], a variant that +works across shared libraries. ### link:{headers-url}/boost/openmethod/policies/throw_error_handler.hpp[] diff --git a/doc/modules/ROOT/pages/shared_libraries.adoc b/doc/modules/ROOT/pages/shared_libraries.adoc index bd9b26ab..96647512 100644 --- a/doc/modules/ROOT/pages/shared_libraries.adoc +++ b/doc/modules/ROOT/pages/shared_libraries.adoc @@ -272,6 +272,15 @@ in each module; cpp:initialize[] copes - it treats them as several ids for the same class - but they are extra ids for the hash to separate. This is why the library's own shared-library tests mark their classes `BOOST_SYMBOL_VISIBLE`. +NOTE: Under cpp:static_rtti[], a type id is the address of a static variable +in a template of the library, which has hidden visibility under +`-fvisibility=hidden`, and is never shared by a Windows DLL. Each module then +has its own id for every class, and nothing tells cpp:initialize[] that they +designate the same class: it takes them for unrelated classes, and a call made +in one module on an object created in another lands in the wrong overrider. +Use cpp:named_static_rtti[] instead: it maps the ids to the names of the +classes, and cpp:initialize[] merges them. + ### What it costs cpp:fast_perfect_hash[], the default, searches for a multiplier `M` and a shift diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index e363db1a..e9f13bb4 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -2256,34 +2256,109 @@ using overrider_virtual_types = boost::mp11::mp_remove< MethodParameters, OverriderParameters>, void>; -template +// Whether a registration of a class says that it is `declared`, or derives from +// it. Only the bases listed in the same `use_classes` are known. +template +auto is_or_derives_from(const class_info& ci, type_id declared) -> bool { + using rtti = typename Registry::rtti; + + auto key = rtti::type_index(declared); + + if (rtti::type_index(ci.type) == key) { + return true; + } + + for (auto base = ci.first_base; base != ci.last_base; ++base) { + if (rtti::type_index(*base) == key) { + return true; + } + } + + return false; +} + +// The class of an object, found from its v-table pointer, for an rtti policy +// that cannot tell the dynamic type. Used only to report an error. +// +// The object's own class always matches, because the v-table pointer came from +// its `static_vptr`. Another class may match as well: a v-table whose leading +// slots are unused is pointed to before its start, possibly at the start of +// another class' v-table. Then only a class that derives from `declared` will +// do. If that still does not settle it, `declared` is the answer: it is less +// precise, but true. +template +auto class_of_vptr(vptr_type vptr, type_id declared) -> type_id { + using rtti = typename Registry::rtti; + + if (!vptr) { + return declared; + } + + for (bool derived_only : {false, true}) { + type_id found = nullptr; + bool unique = true; + + for (auto& ci : Registry::state().classes) { + if (*ci.static_vptr != vptr || + (derived_only && !is_or_derives_from(ci, declared))) { + continue; + } + + if (!found) { + found = ci.type; + } else if (rtti::type_index(ci.type) != rtti::type_index(found)) { + unique = false; + break; + } + } + + if (found && unique) { + return found; + } + } + + return declared; +} + +template struct init_bad_call { template static auto fn(bad_call& error, const Arg& arg, const Args&... args) { + using rtti = typename Registry::rtti; + if constexpr (Index == 0u) { - error.method = Rtti::template static_type(); + error.method = rtti::template static_type(); error.arity = sizeof...(args) + 1; } type_id arg_type_id; if constexpr (is_virtual_ptr) { - arg_type_id = Rtti::dynamic_type(*arg); + using Class = std::remove_cv_t; + + if constexpr (rtti::template is_polymorphic) { + arg_type_id = rtti::dynamic_type(*arg); + } else { + arg_type_id = class_of_vptr( + arg.vptr(), rtti::template static_type()); + } + } else if constexpr (rtti::template is_polymorphic) { + arg_type_id = rtti::dynamic_type(arg); } else { - arg_type_id = Rtti::dynamic_type(arg); + arg_type_id = rtti::template static_type(); } error.types[Index] = arg_type_id; - init_bad_call::fn(error, args...); + init_bad_call::fn(error, args...); } static auto fn(bad_call&) { } }; -template -struct init_bad_call { +template +struct init_bad_call { static auto fn(bad_call&) { } }; @@ -3051,7 +3126,7 @@ method::fn_not_implemented( if constexpr (Registry::has_error_handler) { no_overrider error; - detail::init_bad_call::fn( + detail::init_bad_call::fn( error, detail::parameter_traits::peek(args)...); Registry::error_handler::error(error); @@ -3069,7 +3144,7 @@ method::fn_ambiguous( if constexpr (Registry::has_error_handler) { ambiguous_call error; - detail::init_bad_call::fn( + detail::init_bad_call::fn( error, detail::parameter_traits::peek(args)...); Registry::error_handler::error(error); diff --git a/include/boost/openmethod/detail/type_key.hpp b/include/boost/openmethod/detail/type_key.hpp new file mode 100644 index 00000000..4627accd --- /dev/null +++ b/include/boost/openmethod/detail/type_key.hpp @@ -0,0 +1,51 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_DETAIL_TYPE_KEY_HPP +#define BOOST_OPENMETHOD_DETAIL_TYPE_KEY_HPP + +#include +#include +#include + +namespace boost::openmethod::detail { + +// A key that identifies a class across modules, for an rtti policy that +// identifies classes by name: the name, and for a class that cannot be shared +// across translation units - one with internal linkage, or none - the type_id +// as well, which keeps it apart from other classes with the same name. +struct type_key { + std::string_view name; + const void* local; + + friend auto operator==(const type_key& a, const type_key& b) -> bool { + return a.local == b.local && a.name == b.name; + } + + friend auto operator!=(const type_key& a, const type_key& b) -> bool { + return !(a == b); + } + + friend auto operator<(const type_key& a, const type_key& b) -> bool { + if (a.name != b.name) { + return a.name < b.name; + } + + return std::less()(a.local, b.local); + } +}; + +} // namespace boost::openmethod::detail + +template<> +struct std::hash { + auto operator()(const boost::openmethod::detail::type_key& key) const + -> std::size_t { + return std::hash()(key.name) ^ + std::hash()(key.local); + } +}; + +#endif diff --git a/include/boost/openmethod/policies/static_rtti.hpp b/include/boost/openmethod/policies/static_rtti.hpp index 248bca6a..01456f60 100644 --- a/include/boost/openmethod/policies/static_rtti.hpp +++ b/include/boost/openmethod/policies/static_rtti.hpp @@ -7,6 +7,13 @@ #define BOOST_OPENMETHOD_POLICY_MINIMAL_RTTI_HPP #include +#include + +#include + +#include +#include +#include namespace boost::openmethod::policies { @@ -19,6 +26,13 @@ namespace boost::openmethod::policies { //! disabled. `virtual_ptr` must be constructed using @ref final_virtual_ptr (or //! its equivalents for smart pointers). //! +//! Each module of a program has its own copy of those variables, unless the +//! dynamic linker merges them, which it does not do on Windows, nor for symbols +//! with hidden visibility. A class used by several modules then has one +//! `type_id` per module, and the registry takes the copies for unrelated +//! classes. Use @ref named_static_rtti in a program that shares a registry +//! across modules. +//! //! @par Example //! //! Selecting the policy. The registry is declared before @@ -61,4 +75,179 @@ struct static_rtti : rtti { } // namespace boost::openmethod::policies +namespace boost::openmethod::detail { + +// The signature of this function, as the compiler prints it, contains the name +// of `T`. Its prefix and suffix are the same for every `T`; they are measured +// on `void`. +template +constexpr auto static_rtti_signature() -> const char* { + return BOOST_CURRENT_FUNCTION; +} + +inline constexpr std::string_view static_rtti_probe = + static_rtti_signature(); +inline constexpr std::size_t static_rtti_prefix = + static_rtti_probe.find("void"); + +template +constexpr auto static_rtti_type_name() -> std::string_view { + // Without __PRETTY_FUNCTION__ or __FUNCSIG__, BOOST_CURRENT_FUNCTION falls + // back on a name that does not contain `T`, and every class would get the + // same name. + static_assert( + static_rtti_prefix != std::string_view::npos, + "named_static_rtti requires __PRETTY_FUNCTION__ or __FUNCSIG__"); + + constexpr std::size_t suffix = + static_rtti_probe.size() - static_rtti_prefix - 4; + constexpr std::string_view signature = static_rtti_signature(); + + return signature.substr( + static_rtti_prefix, signature.size() - static_rtti_prefix - suffix); +} + +constexpr auto static_rtti_is_qualifier(char c) -> bool { + return (c >= 'a' && c <= 'z') || c == ' ' || c == '&'; +} + +// A class with internal linkage, or none, may print the same name as a +// different class. Only the compilers know which classes they are; this guesses +// it from the marks they leave in the name. GCC prints a local class as +// `f()::L`, or `S::f() const::L`: a scope closed by a parenthesis, maybe +// followed by qualifiers. +constexpr auto static_rtti_marked_local(std::string_view name) -> bool { + constexpr std::string_view marks[] = { + "{anonymous}", // GCC + "(anonymous namespace)", // Clang + "`", // MSVC: `anonymous-namespace', `anonymous namespace' + " 0 && static_rtti_is_qualifier(name[i - 1])) { + --i; + } + + if (i > 0 && name[i - 1] == ')') { + return true; + } + } + + return false; +} + +// Whether the copies of `T`'s type_id in different modules designate the same +// type, and can be merged by name. +template +constexpr auto static_rtti_shareable() -> bool { +#if BOOST_OPENMETHOD_HAS_REFLECTION + if constexpr ( + std::is_class_v || std::is_union_v || std::is_enum_v) { + return std::meta::has_external_linkage(^^T); + } else { + return true; + } +#else + return !static_rtti_marked_local(static_rtti_type_name()); +#endif +} + +// What a named_static_rtti type_id points to. +struct static_rtti_name { + std::string_view name; + bool shareable; +}; + +} // namespace boost::openmethod::detail + +namespace boost::openmethod::policies { + +//! Implementation of the `rtti` policy that identifies classes by name. +//! +//! `named_static_rtti` is a @ref static_rtti that works across modules. It +//! categorizes all types as non-polymorphic, and its type ids are addresses +//! that differ from one module to the next, like those of @ref static_rtti. In +//! addition, `type_index` maps them to the name of the class, as the compiler +//! prints it, so the registry merges the copies of a class coming from +//! different modules. +//! +//! All the modules must be built with the same compiler, because the names +//! must be spelled the same way. The cost is paid once, by @ref initialize. +//! Dispatch does not use names. +//! +//! A class that has internal linkage, or none, is not shared: it is identified +//! by its `type_id`, as with @ref static_rtti. This covers the classes defined +//! in an anonymous namespace, local classes, lambdas, unnamed classes, and the +//! templates instantiated with any of these. With C++26 reflection, the policy +//! asks the compiler which classes they are. Otherwise, it recognizes them from +//! the marks that the compiler leaves in their names. +//! +//! @warning Clang and MSVC leave no mark in the name of a local class. Clang +//! prints it unqualified, and MSVC prints it as if the function were a +//! namespace (`main::Local`). Without reflection, two local classes that print +//! the same name are taken for the same class, and so is a local class that +//! prints the same name as a class at namespace scope. So is a class template +//! instantiated with an entity that has internal linkage, such as the address +//! of a `static` variable, with all compilers. +//! +//! @see [Custom RTTI](xref:ROOT:custom_rtti.adoc) +struct named_static_rtti : rtti { + //! A RttiFn metafunction. + //! + //! @tparam Registry The registry containing this policy. + template + struct fn : static_rtti::fn { + //! Returns the @ref type_id of `Class`. + //! + //! @tparam Class A class. + template + static auto static_type() -> type_id { + // Not const: two local classes can have the same name, and a + // linker may fold identical read-only objects into one. + static detail::static_rtti_name id{ + detail::static_rtti_type_name(), + detail::static_rtti_shareable() + }; + + return &id; + } + + //! Returns a key that identifies a class across modules. + //! + //! @param type A `type_id`. + //! @return The name of the class, and `type` itself if the class + //! cannot be shared. + static auto type_index(type_id type) -> detail::type_key { + auto id = static_cast(type); + + return {id->name, id->shareable ? nullptr : type}; + } + + //! Writes the name of a class to a stream. + //! + //! @param type A `type_id`. + //! @param stream A stream to write to. + template + static void type_name(type_id type, Stream& stream) { + stream << static_cast(type)->name; + } + }; +}; + +} // namespace boost::openmethod::policies + #endif diff --git a/include/boost/openmethod/policies/std_rtti.hpp b/include/boost/openmethod/policies/std_rtti.hpp index 09ca684c..245b535b 100644 --- a/include/boost/openmethod/policies/std_rtti.hpp +++ b/include/boost/openmethod/policies/std_rtti.hpp @@ -7,13 +7,175 @@ #define BOOST_OPENMETHOD_POLICY_STD_RTTI_HPP #include +#include -#ifndef BOOST_NO_RTTI +#include #include + +#ifndef BOOST_NO_RTTI +#include #include #include #endif +// Whether std::type_index identifies classes the way initialize() needs: equal +// for the copies of one class in different modules, and different for two +// classes with internal linkage that have the same name. See +// std_rtti::type_index. +#ifndef BOOST_OPENMETHOD_DETAIL_STD_TYPE_INDEX +#if defined(_MSC_VER) || \ + (defined(__GLIBCXX__) && defined(__GNUC__) && !defined(__clang__) && \ + !defined(__INTEL_COMPILER)) +#define BOOST_OPENMETHOD_DETAIL_STD_TYPE_INDEX 1 +#else +#define BOOST_OPENMETHOD_DETAIL_STD_TYPE_INDEX 0 +#endif +#endif + +namespace boost::openmethod::detail { + +inline auto mangled_is_digit(char c) -> bool { + return c >= '0' && c <= '9'; +} + +// Whether a type name mangled per the Itanium C++ ABI designates a type that a +// different type, in another translation unit, may share the name of: one with +// internal linkage, or none. The mangling says so: +// +// _GLOBAL__N... begins the name of an anonymous namespace +// L at the start of a name, or in the encoding of a function +// or variable (after `Z`, or `_Z` in an expression), names a +// static one: a type local to a static function, a lambda +// in the initializer of a static variable, or a template +// argument that is the address of a static variable +// Ut an unnamed type +// $ or . in a name, Clang's or GCC's name for an unnamed type, or a +// lambda +// +// Source names are skipped by their length, so an identifier like `ZLib` is +// not taken for a mark, and so are the numbers that are not lengths. A mark +// read where there is none only keeps the copies of a class apart, as if it +// had internal linkage. +inline auto itanium_internal_linkage(std::string_view name) -> bool { + const std::size_t n = name.size(); + std::size_t i = 0; + bool encoding = false; + bool literal_value = false; + + auto skip_number = [&]() { + if (i < n && name[i] == 'n') { + ++i; + } + + while (i < n && mangled_is_digit(name[i])) { + ++i; + } + }; + + while (i < n) { + char c = name[i]; + char next = i + 1 < n ? name[i + 1] : '\0'; + + if (mangled_is_digit(c)) { + std::size_t length = 0; + + while (i < n && mangled_is_digit(name[i])) { + length = 10 * length + std::size_t(name[i++] - '0'); + } + + auto identifier = name.substr(i, length); + + // An anonymous namespace, or a name that is not an identifier: + // `$_0` (Clang) or `._anon_0` (GCC) for an unnamed type. + if (identifier.substr(0, 10) == "_GLOBAL__N" || + identifier.find_first_of("$.") != std::string_view::npos) { + return true; + } + + i += length; + + // The value of an enum literal follows its type. + if (literal_value) { + literal_value = false; + skip_number(); + } + + continue; + } + + switch (c) { + case '$': + return true; + + case 'U': + if (next == 't') { + return true; + } + + break; + + case 'Z': + encoding = true; + break; + + case 'L': + if (mangled_is_digit(next)) { + // A static function or variable, in an encoding or at the + // start of a name; an enum literal never comes there. + if (encoding || i == 0 || name[i - 1] == 'N') { + return true; + } + + // An enum literal: L E. + literal_value = true; + } else if (next >= 'a' && next <= 'z') { + // A literal of a builtin type: L E. + i += 2; + skip_number(); + continue; + } + + break; + + case 'S': + // A substitution, S _, or an abbreviation like St. + ++i; + + while ( + i < n && + (mangled_is_digit(name[i]) || + (name[i] >= 'A' && name[i] <= 'Z'))) { + ++i; + } + + continue; + + case 'D': + // A vector type: Dv _. + if (next == 'v') { + i += 2; + skip_number(); + continue; + } + + break; + + case 'T': // a template parameter: T _ + case 'A': // an array type: A _ + case '_': // a discriminator: _ , or __ _ + ++i; + skip_number(); + continue; + } + + ++i; + } + + return false; +} + +} // namespace boost::openmethod::detail + namespace boost::openmethod::policies { //! Implements the @ref rtti policy using standard RTTI. @@ -79,33 +241,44 @@ struct std_rtti : rtti { reinterpret_cast(type)->name()); } - //! Returns a key that uniquely identifies a class. + //! Returns a key that identifies a class across modules. //! //! C++ does *not* guarantee that there is a single instance of //! `std::type_info` per type: a class used by several modules of a //! program typically has one per module. `type_index` maps a `type_id` - //! to a key that compares equal for all the `type_id`s of one class, + //! to a key that compares equal for all the type ids of one class, //! which is what @ref initialize uses to group the registrations coming - //! from different modules. + //! from different modules. The key must also tell apart two classes + //! that have the same name, which classes with internal linkage in + //! different translation units can. + //! + //! With MSVC, and with GCC and libstdc++, the key is a + //! `std::type_index`. Their `std::type_info` equality compares names, + //! except for classes with internal linkage, which MSVC names + //! uniquely, and which GCC marks for libstdc++ to compare by address. //! - //! The key is the *name*, not the address and not a `std::type_index`. - //! `std::type_index` would delegate to `std::type_info::operator==`, - //! and that is only as good as the platform's RTTI uniqueness: - //! libstdc++ falls back to comparing names, but libc++ on Darwin - //! compares uniquely-named RTTI by address. There, two modules' - //! `type_info` objects for the same type compare *unequal* unless the - //! symbol happens to be exported and coalesced by dyld - which, for a - //! template like `method`, requires every - //! template argument to have default visibility as well. Under - //! `-fvisibility=hidden` that is not the case, the copies are not - //! grouped, each module's method keeps its own overrider list, and - //! calls report @ref no_overrider. Comparing names sidesteps the - //! platform's uniqueness rules entirely. + //! Elsewhere, `std::type_index` does not do. libc++ compares the + //! `std::type_info` objects of a class with hidden visibility by + //! address, so the copies in different modules are unequal, and Clang + //! does not mark the classes with internal linkage for libstdc++. The + //! key is then the mangled name of the class, plus its `type_id` if + //! the mangled name shows that the class has internal linkage, or + //! none. //! //! @param type A `type_id`. - //! @return The mangled name of the class identified by `type`. - static auto type_index(type_id type) -> std::string_view { - return reinterpret_cast(type)->name(); + //! @return A key identifying the class of `type`. + static auto type_index(type_id type) { + const auto& info = *reinterpret_cast(type); + +#if BOOST_OPENMETHOD_DETAIL_STD_TYPE_INDEX + return std::type_index(info); +#else + const char* name = info.name(); + + return detail::type_key{ + name, detail::itanium_internal_linkage(name) ? type : nullptr + }; +#endif } //! Casts an object to a type. diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 8528ee82..b234000a 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -274,6 +274,11 @@ struct bad_call : openmethod_error { //! The maximum size of `types` static constexpr std::size_t max_types = 16; //! The type_ids of the arguments. + //! + //! For a class that the `rtti` policy deems polymorphic, the dynamic type + //! of the argument, as reported by the policy. Otherwise, for a + //! `virtual_ptr`, the class whose v-table it points to, or, if that is not + //! conclusive, the class of the parameter. type_id types[max_types]; }; diff --git a/test/implicit_shared_libraries/CMakeLists.txt b/test/implicit_shared_libraries/CMakeLists.txt index e6877e56..b338919f 100644 --- a/test/implicit_shared_libraries/CMakeLists.txt +++ b/test/implicit_shared_libraries/CMakeLists.txt @@ -21,3 +21,4 @@ message(STATUS "Boost.OpenMethod: building implicit shared library tests") add_subdirectory(default_registry) add_subdirectory(custom_registry) +add_subdirectory(named_static_rtti) diff --git a/test/implicit_shared_libraries/Jamfile b/test/implicit_shared_libraries/Jamfile index 0f11d53d..4b378971 100644 --- a/test/implicit_shared_libraries/Jamfile +++ b/test/implicit_shared_libraries/Jamfile @@ -42,3 +42,4 @@ project build-project default_registry ; build-project custom_registry ; +build-project named_static_rtti ; diff --git a/test/implicit_shared_libraries/default_registry/lib.cpp b/test/implicit_shared_libraries/default_registry/lib.cpp index dc331510..f584ad7b 100644 --- a/test/implicit_shared_libraries/default_registry/lib.cpp +++ b/test/implicit_shared_libraries/default_registry/lib.cpp @@ -22,6 +22,16 @@ BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(boost::openmethod::default_registry); BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); +namespace { + +// main.cpp has a Stray of its own, with the same name. They are different +// classes, and must not be merged. +struct Stray : Animal {}; + +} // namespace + +BOOST_OPENMETHOD_CLASSES(Animal, Stray); + BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "?"; } @@ -30,6 +40,10 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "woof"; } +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "library stray"; +} + auto lib_speak_method_type_id() -> const void* { return &typeid( BOOST_OPENMETHOD_TYPE(speak, (virtual_ptr), const char*)); @@ -47,4 +61,8 @@ auto lib_make_dog() -> unique_virtual_ptr { return make_unique_virtual(); } +auto lib_make_stray() -> unique_virtual_ptr { + return make_unique_virtual(); +} + BOOST_OPENMETHOD_TEST_REGISTER_CLASSES(); diff --git a/test/implicit_shared_libraries/default_registry/lib.hpp b/test/implicit_shared_libraries/default_registry/lib.hpp index d746a9d0..d131820b 100644 --- a/test/implicit_shared_libraries/default_registry/lib.hpp +++ b/test/implicit_shared_libraries/default_registry/lib.hpp @@ -59,4 +59,7 @@ LIB_API auto lib_speak(boost::openmethod::virtual_ptr animal) -> const LIB_API auto lib_make_dog() -> boost::openmethod::unique_virtual_ptr; +// An instance of the library's own Stray, a class in an anonymous namespace. +LIB_API auto lib_make_stray() -> boost::openmethod::unique_virtual_ptr; + #endif diff --git a/test/implicit_shared_libraries/default_registry/main.cpp b/test/implicit_shared_libraries/default_registry/main.cpp index c7a8d2e8..38b34cbb 100644 --- a/test/implicit_shared_libraries/default_registry/main.cpp +++ b/test/implicit_shared_libraries/default_registry/main.cpp @@ -20,11 +20,24 @@ using namespace boost::openmethod; BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); +namespace { + +// Not the library's Stray, although both have the same name. +struct Stray : Animal {}; + +} // namespace + +BOOST_OPENMETHOD_CLASSES(Animal, Stray); + // Registered by the executable; the library knows nothing about it. BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "meow"; } +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "executable stray"; +} + // The library is linked implicitly, so its static constructors have already run // by the time we get here: a single initialize() sets up dispatch tables that // include both modules' contributions. @@ -52,8 +65,9 @@ BOOST_AUTO_TEST_CASE(shared_registry_state) { << (std::type_index(exe_ti) == std::type_index(lib_ti))); // The invariant that matters is the *policy* key: that is what // augment_methods() groups by. std::type_index deliberately is NOT - // asserted - it is false on Darwin, which is the whole reason - // std_rtti::type_index compares names instead. + // asserted - libc++ compares the type_info of a class with hidden + // visibility by address, which is why std_rtti::type_index uses + // std::type_index only with MSVC, and with GCC and libstdc++. bool method_identity_agrees = default_registry::rtti::type_index(exe_id) == default_registry::rtti::type_index(lib_id); @@ -82,4 +96,18 @@ BOOST_AUTO_TEST_CASE(shared_registry_state) { BOOST_TEST(std::string(lib_speak(animal)) == "?"); } +// Two classes in anonymous namespaces, one per module, with the same name. +BOOST_AUTO_TEST_CASE(classes_in_anonymous_namespaces_stay_apart) { + initialize(); + + auto lib_stray = lib_make_stray(); + auto exe_stray = make_unique_virtual(); + + BOOST_TEST(lib_stray.vptr() != exe_stray.vptr()); + BOOST_TEST(std::string(speak(lib_stray)) == "library stray"); + BOOST_TEST(std::string(lib_speak(lib_stray)) == "library stray"); + BOOST_TEST(std::string(speak(exe_stray)) == "executable stray"); + BOOST_TEST(std::string(lib_speak(exe_stray)) == "executable stray"); +} + BOOST_OPENMETHOD_TEST_REGISTER_CLASSES(); diff --git a/test/implicit_shared_libraries/named_static_rtti/CMakeLists.txt b/test/implicit_shared_libraries/named_static_rtti/CMakeLists.txt new file mode 100644 index 00000000..380dd6aa --- /dev/null +++ b/test/implicit_shared_libraries/named_static_rtti/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (c) 2017-2026 Jean-Louis Leroy +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt +# or copy at http://www.boost.org/LICENSE_1_0.txt) + +# named_static_rtti shared across an implicitly linked library: the library and +# the executable have different type_ids for the same class, and the registry +# merges them by name. See main.cpp. + +set(lib boost_openmethod-isl_named_static_rtti_lib) +set(exe boost_openmethod-test_implicit_shared_libraries_named_static_rtti) + +add_library(${lib} SHARED lib.cpp) +target_link_libraries(${lib} PRIVATE Boost::openmethod) + +add_executable(${exe} main.cpp) +target_link_libraries(${exe} + PRIVATE Boost::openmethod Boost::unit_test_framework ${lib}) + +# Hidden visibility is the configuration under test - it is b2's default, and +# what BoostRoot.cmake sets. At default visibility, ELF unifies the static +# variables behind the type_ids across modules, and there is nothing to merge. +# Windows DLLs never share them, whatever the setting. +set_target_properties(${lib} ${exe} PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON) + +# See custom_registry/CMakeLists.txt. +set_target_properties(${lib} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY $ + RUNTIME_OUTPUT_DIRECTORY $) + +boost_openmethod_add_test(${exe}) + +if (TARGET tests) + add_dependencies(tests ${exe} ${lib}) +endif() diff --git a/test/implicit_shared_libraries/named_static_rtti/Jamfile b/test/implicit_shared_libraries/named_static_rtti/Jamfile new file mode 100644 index 00000000..91de5f77 --- /dev/null +++ b/test/implicit_shared_libraries/named_static_rtti/Jamfile @@ -0,0 +1,33 @@ +# Boost.OpenMethod Library - implicit_shared_libraries/named_static_rtti Jamfile +# +# Copyright 2017-2026 Jean-Louis Leroy +# +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt + +# named_static_rtti shared across an implicitly linked library: the library and +# the executable have different type_ids for the same class, and the registry +# merges them by name. See main.cpp. +# +# test executable ──linked──▶ lib (owns and exports the registry state) +# +# global is deliberately NOT set: hidden visibility, b2's +# default, is the configuration under test. At global visibility, ELF unifies +# the type_ids across modules, and there is nothing to merge. + +import testing ; + +lib boost_openmethod-isl_named_static_rtti_lib + : lib.cpp + : shared + ; + +run main.cpp + boost_openmethod-isl_named_static_rtti_lib + /boost/test//boost_unit_test_framework/off/static + : + : + : shared + : implicit_shared_libraries_named_static_rtti + ; diff --git a/test/implicit_shared_libraries/named_static_rtti/lib.cpp b/test/implicit_shared_libraries/named_static_rtti/lib.cpp new file mode 100644 index 00000000..6f6648cb --- /dev/null +++ b/test/implicit_shared_libraries/named_static_rtti/lib.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// This library owns and exports the registry state; main.cpp imports it, as in +// the custom_registry variant. +#define OWNS_REGISTRY_STATE +#define LIB_SOURCE + +#include "lib.hpp" +#include "../../test_classes.hpp" + +BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(static_registry); + +using namespace boost::openmethod; + +BOOST_OPENMETHOD_TEST_CLASSES(Object, Animal, Dog, Cat); + +namespace { + +// main.cpp has a Stray of its own, and the compiler prints the same name for +// both. They are different classes, and must not be merged. +struct Stray : Animal {}; + +} // namespace + +BOOST_OPENMETHOD_CLASSES(Animal, Stray); + +// A method the executable knows nothing about. Its virtual parameter is a base +// of Animal, so it takes slot 0 in the v-tables of the library's classes, and +// speak takes slot 1. +BOOST_OPENMETHOD(name, (virtual_ptr), const char*); + +BOOST_OPENMETHOD_OVERRIDE(name, (virtual_ptr), const char*) { + return "Object"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (virtual_ptr), const char*) { + return "Dog"; +} + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "?"; +} + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "woof"; +} + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "library stray"; +} + +auto lib_registry_state_id() -> const void* { + return static_registry::id(); +} + +auto lib_dog_type_id() -> type_id { + return static_registry::rtti::static_type(); +} + +auto lib_speak(virtual_ptr animal) -> const char* { + return speak(animal); +} + +auto lib_make_dog() -> unique_virtual_ptr { + return make_unique_virtual(); +} + +auto lib_make_stray() -> unique_virtual_ptr { + return make_unique_virtual(); +} + +BOOST_OPENMETHOD_TEST_REGISTER_CLASSES(); diff --git a/test/implicit_shared_libraries/named_static_rtti/lib.hpp b/test/implicit_shared_libraries/named_static_rtti/lib.hpp new file mode 100644 index 00000000..9d8e9875 --- /dev/null +++ b/test/implicit_shared_libraries/named_static_rtti/lib.hpp @@ -0,0 +1,52 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_TEST_IMPLICIT_SHARED_LIBRARIES_NAMED_STATIC_RTTI_LIB_HPP +#define BOOST_OPENMETHOD_TEST_IMPLICIT_SHARED_LIBRARIES_NAMED_STATIC_RTTI_LIB_HPP + +// Defines static_registry, sets it as the default registry, and exports or +// imports its state depending on OWNS_REGISTRY_STATE. Must come before any +// other openmethod header. +#include "registry.hpp" + +#include + +// named_static_rtti treats every class as non-polymorphic, so the classes need +// not be polymorphic. They are visible, as they would have to be under +// std_rtti, but that does not merge their type_ids: those are static variables +// of the library's own templates, which have hidden visibility. +struct BOOST_SYMBOL_VISIBLE Object {}; + +struct BOOST_SYMBOL_VISIBLE Animal : Object {}; + +struct BOOST_SYMBOL_VISIBLE Dog : Animal {}; + +struct BOOST_SYMBOL_VISIBLE Cat : Animal {}; + +BOOST_OPENMETHOD(speak, (boost::openmethod::virtual_ptr), const char*); + +#if defined(LIB_SOURCE) +#define LIB_API BOOST_SYMBOL_EXPORT +#else +#define LIB_API BOOST_SYMBOL_IMPORT +#endif + +// The address identifying the registry's shared state, as seen from inside the +// library. +LIB_API auto lib_registry_state_id() -> const void*; + +// static_type(), as the library computes it. +LIB_API auto lib_dog_type_id() -> boost::openmethod::type_id; + +// Dispatch performed inside the library, on an object created in either module. +LIB_API auto lib_speak(boost::openmethod::virtual_ptr animal) -> const + char*; + +LIB_API auto lib_make_dog() -> boost::openmethod::unique_virtual_ptr; + +// An instance of the library's own Stray, a class in an anonymous namespace. +LIB_API auto lib_make_stray() -> boost::openmethod::unique_virtual_ptr; + +#endif diff --git a/test/implicit_shared_libraries/named_static_rtti/main.cpp b/test/implicit_shared_libraries/named_static_rtti/main.cpp new file mode 100644 index 00000000..9a6c96dd --- /dev/null +++ b/test/implicit_shared_libraries/named_static_rtti/main.cpp @@ -0,0 +1,104 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// named_static_rtti across an implicitly linked shared library built with +// hidden visibility - b2's default, and what BoostRoot.cmake sets - or across a +// Windows DLL. +// +// Like static_rtti, named_static_rtti's type_ids are addresses of static +// variables, and each module has its own copy: the library and the executable +// have different type_ids for the same class. With static_rtti, initialize() +// takes them for unrelated classes, and lays out each module's v-tables on its +// own: a call made in one module, on an object created in the other, lands in +// the wrong overrider, or reads past the end of a v-table. named_static_rtti +// maps the type_ids to the names of the classes, and initialize() merges them. + +#define BOOST_TEST_MODULE implicit_shared_libraries_named_static_rtti + +#include + +#include "lib.hpp" +#include "../../test_classes.hpp" + +#include + +#include + +using namespace boost::openmethod; + +BOOST_OPENMETHOD_TEST_CLASSES(Object, Animal, Dog, Cat); + +namespace { + +// Not the library's Stray, although the compiler prints the same name for both. +// This is the collision that every compiler leaves a mark for; local classes +// are marked only by GCC. +struct Stray : Animal {}; + +} // namespace + +BOOST_OPENMETHOD_CLASSES(Animal, Stray); + +// Registered by the executable; the library knows nothing about it. +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "meow"; +} + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "executable stray"; +} + +struct initialize_fixture { + initialize_fixture() { + initialize(); + } +}; + +BOOST_TEST_GLOBAL_FIXTURE(initialize_fixture); + +BOOST_AUTO_TEST_CASE(one_class_across_modules) { + BOOST_TEST(lib_registry_state_id() == static_registry::id()); + + // The type_ids differ, which is what named_static_rtti copes with... + auto exe_dog_type_id = static_registry::rtti::static_type(); + BOOST_TEST(lib_dog_type_id() != exe_dog_type_id); + + // ...by mapping them to the same key. + BOOST_TEST( + (static_registry::rtti::type_index(lib_dog_type_id()) == + static_registry::rtti::type_index(exe_dog_type_id))); + + // One class, one v-table, whichever module created the object. + auto lib_dog = lib_make_dog(); + auto exe_dog = make_unique_virtual(); + BOOST_TEST(lib_dog.vptr() == exe_dog.vptr()); +} + +BOOST_AUTO_TEST_CASE(executable_dispatches_on_library_object) { + // The library's speak takes slot 1, behind name, which is declared on a + // base class, and the library lays out the v-tables. If the executable + // had a speak of its own, it would take slot 0, and land in name. + BOOST_TEST(std::string(speak(lib_make_dog())) == "woof"); +} + +BOOST_AUTO_TEST_CASE(library_dispatches_on_executable_object) { + BOOST_TEST(std::string(lib_speak(make_unique_virtual())) == "woof"); + BOOST_TEST(std::string(lib_speak(make_unique_virtual())) == "meow"); + BOOST_TEST(std::string(speak(make_unique_virtual())) == "meow"); + BOOST_TEST(std::string(lib_speak(make_unique_virtual())) == "?"); +} + +BOOST_AUTO_TEST_CASE(classes_in_anonymous_namespaces_stay_apart) { + auto lib_stray = lib_make_stray(); + auto exe_stray = make_unique_virtual(); + + BOOST_TEST(lib_stray.vptr() != exe_stray.vptr()); + BOOST_TEST(std::string(speak(lib_stray)) == "library stray"); + BOOST_TEST(std::string(lib_speak(lib_stray)) == "library stray"); + BOOST_TEST(std::string(speak(exe_stray)) == "executable stray"); + BOOST_TEST(std::string(lib_speak(exe_stray)) == "executable stray"); +} + +BOOST_OPENMETHOD_TEST_REGISTER_CLASSES(); diff --git a/test/implicit_shared_libraries/named_static_rtti/registry.hpp b/test/implicit_shared_libraries/named_static_rtti/registry.hpp new file mode 100644 index 00000000..673b3ac5 --- /dev/null +++ b/test/implicit_shared_libraries/named_static_rtti/registry.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_TEST_IMPLICIT_SHARED_LIBRARIES_NAMED_STATIC_RTTI_REGISTRY_HPP +#define BOOST_OPENMETHOD_TEST_IMPLICIT_SHARED_LIBRARIES_NAMED_STATIC_RTTI_REGISTRY_HPP + +#include + +struct static_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY static_registry + +#include +#include + +struct BOOST_SYMBOL_VISIBLE static_registry : + boost::openmethod::registry< + boost::openmethod::policies::named_static_rtti> {}; + +// The registry state is shared exactly as in the custom_registry variant. +#if defined(OWNS_REGISTRY_STATE) +BOOST_OPENMETHOD_EXPORT_REGISTRY(static_registry); +#else +BOOST_OPENMETHOD_IMPORT_REGISTRY(static_registry); +#endif + +#endif diff --git a/test/test_named_static_rtti.cpp b/test/test_named_static_rtti.cpp new file mode 100644 index 00000000..0ff3888a --- /dev/null +++ b/test/test_named_static_rtti.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +struct named_registry; +#define BOOST_OPENMETHOD_DEFAULT_REGISTRY named_registry + +#include +#include +#include +#include + +struct named_registry : + boost::openmethod::registry< + boost::openmethod::policies::named_static_rtti> {}; + +#define BOOST_TEST_MODULE openmethod +#include + +#include "test_classes.hpp" + +#include +#include +#include + +struct Animal {}; + +struct Dog : Animal {}; + +struct Cat : Animal {}; + +namespace { + +struct Stray : Animal {}; + +} // namespace + +using namespace boost::openmethod::aliases; + +BOOST_OPENMETHOD_TEST_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD_CLASSES(Animal, Stray); + +BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr, std::ostream& os), void) { + os << "bark"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr, std::ostream& os), void) { + os << "hiss"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr, std::ostream& os), void) { + os << "growl"; +} + +using rtti = named_registry::rtti; + +template +auto key() { + return rtti::type_index(rtti::static_type()); +} + +template +auto name() -> std::string { + std::ostringstream os; + rtti::type_name(rtti::static_type(), os); + + return os.str(); +} + +auto ends_with(std::string_view s, std::string_view suffix) -> bool { + return s.size() >= suffix.size() && + s.substr(s.size() - suffix.size()) == suffix; +} + +BOOST_AUTO_TEST_CASE(names) { + // MSVC prints `struct Dog`. + BOOST_TEST(ends_with(name(), "Dog")); + BOOST_TEST(ends_with(name(), "Cat")); +} + +BOOST_AUTO_TEST_CASE(keys) { + BOOST_TEST((key() == key())); + BOOST_TEST((key() != key())); + BOOST_TEST((key() < key() || key() < key())); + + // A class at namespace scope is identified by its name alone... + BOOST_TEST(key().local == nullptr); + + // ...one in an anonymous namespace by its type_id as well, on every + // compiler. + BOOST_TEST(key().local == rtti::static_type()); +} + +BOOST_AUTO_TEST_CASE(local_classes) { + struct Local {}; + auto first = key(); + + { + struct Local {}; + auto second = key(); + +#if BOOST_OPENMETHOD_HAS_REFLECTION || \ + (defined(__GNUC__) && !defined(__clang__)) + // GCC prints both as `local_classes::test_method()::Local`, and marks + // them as local; reflection does not need marks. + BOOST_TEST(first.local != nullptr); + BOOST_TEST((first != second)); +#else + // Clang and MSVC leave no mark; see the warning in the documentation + // of named_static_rtti. + (void)first; + (void)second; +#endif + } +} + +BOOST_AUTO_TEST_CASE(dispatch) { + boost::openmethod::initialize(); + + { + std::stringstream os; + poke(make_unique_virtual(), os); + BOOST_TEST(os.str() == "bark"); + } + { + std::stringstream os; + poke(make_unique_virtual(), os); + BOOST_TEST(os.str() == "hiss"); + } + { + std::stringstream os; + poke(make_unique_virtual(), os); + BOOST_TEST(os.str() == "growl"); + } +} + +BOOST_OPENMETHOD_TEST_REGISTER_CLASSES(); diff --git a/test/test_static_rtti_errors.cpp b/test/test_static_rtti_errors.cpp new file mode 100644 index 00000000..089f9f05 --- /dev/null +++ b/test/test_static_rtti_errors.cpp @@ -0,0 +1,137 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// static_rtti and named_static_rtti have no dynamic_type. A bad_call still +// reports the class of each argument: the one whose v-table the virtual_ptr +// points to. + +#include +#include +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +#include +#include + +using namespace boost::openmethod; + +struct static_registry : + registry {}; + +struct named_registry : + registry {}; + +struct Animal {}; + +struct Dog : Animal {}; + +struct Cat : Animal {}; + +namespace with_static_rtti { + +using R = static_registry; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, R); + +BOOST_OPENMETHOD(speak, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), void) { +} + +BOOST_OPENMETHOD(meet, (virtual_ptr, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr, virtual_ptr), void) { +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr, virtual_ptr), void) { +} + +} // namespace with_static_rtti + +namespace with_named_static_rtti { + +using R = named_registry; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, R); + +BOOST_OPENMETHOD(speak, (virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), void) { +} + +BOOST_OPENMETHOD(meet, (virtual_ptr, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr, virtual_ptr), void) { +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr, virtual_ptr), void) { +} + +} // namespace with_named_static_rtti + +// Checks that `call` reports `Error`, and that every argument is reported as a +// Dog - the class of the object, not Animal, the class of the parameter. +template +void check_error(Call call, std::size_t arity) { + using rtti = typename Registry::rtti; + + try { + call(); + BOOST_FAIL("should have thrown"); + } catch (const Error& error) { + BOOST_TEST(error.arity == arity); + + for (std::size_t i = 0; i < arity; ++i) { + BOOST_TEST( + (rtti::type_index(error.types[i]) == + rtti::type_index(rtti::template static_type()))); + } + } catch (...) { + BOOST_FAIL("wrong exception"); + } +} + +BOOST_AUTO_TEST_CASE(static_rtti_errors) { + using namespace with_static_rtti; + + initialize(); + Dog dog; + + check_error([&] { speak(final_virtual_ptr(dog)); }, 1); + check_error( + [&] { speak(make_unique_virtual()); }, 1); + check_error( + [&] { meet(final_virtual_ptr(dog), final_virtual_ptr(dog)); }, 2); +} + +BOOST_AUTO_TEST_CASE(named_static_rtti_errors) { + using namespace with_named_static_rtti; + + initialize(); + Dog dog; + + check_error([&] { speak(final_virtual_ptr(dog)); }, 1); + check_error( + [&] { speak(make_unique_virtual()); }, 1); + check_error( + [&] { meet(final_virtual_ptr(dog), final_virtual_ptr(dog)); }, 2); + + try { + speak(final_virtual_ptr(dog)); + } catch (const no_overrider& error) { + std::ostringstream os; + R::rtti::type_name(error.types[0], os); + auto name = os.str(); + BOOST_TEST(name.substr(name.size() - 3) == "Dog"); + } +} diff --git a/test/test_std_rtti_type_index.cpp b/test/test_std_rtti_type_index.cpp new file mode 100644 index 00000000..8b993518 --- /dev/null +++ b/test/test_std_rtti_type_index.cpp @@ -0,0 +1,94 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// std_rtti::type_index must be equal for the copies of a class in different +// modules, and different for two classes with internal linkage that have the +// same name. Where std::type_index does not do that, it scans the mangled name +// for internal linkage. + +#include + +#define BOOST_TEST_MODULE openmethod +#include + +#include + +using boost::openmethod::default_registry; +using boost::openmethod::detail::itanium_internal_linkage; + +// Names produced by GCC, Clang, and Clang with libc++. +BOOST_AUTO_TEST_CASE(mangled_names) { + const char* shared[] = { + "6Global", + "N2ns5InnerE", + "10ZLibStream", // not `ZL`, the mark of a static entity + "12UtilityClass", // not `Ut`, the mark of an unnamed type + "3BoxISt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt6" + "vectorIiSaIiEESt4lessIS6_ESaISt4pairIKS6_S9_EEEE", + "3BoxINSt3__13mapINS0_12basic_stringIcNS0_11char_traitsIcEENS0_" + "9allocatorIcEEEENS0_6vectorIiNS5_IiEEEENS0_4lessIS7_EENS5_INS0_" + "4pairIKS7_SA_EEEEEEE", + "4VBoxIL5Color12EE", // an enum literal, not a static entity + "4VBoxILN2ns5ShadeE12EE", // the same, of a nested enum + "4VBoxILin3EE", // a negative integer literal + "4VBoxIXadL_Z10extern_varEEE", // the address of an extern variable + "3BoxIA10_iE", + "Z15local_in_externvE5Local", // local to a function defined once + "N5boost10openmethod6methodI22speak_boost_openmethodFvNS0_11virtual_" + "ptrI6GlobalNS0_16default_registryEvEESt3mapINSt7__cxx1112basic_" + "stringIcSt11char_traitsIcESaIcEEESt6vectorIiSaIiEESt4lessISD_ESaISt4" + "pairIKSD_SG_EEEES5_EE", + }; + + for (auto name : shared) { + BOOST_TEST_INFO(name); + BOOST_TEST(!itanium_internal_linkage(name)); + } + + const char* internal[] = { + "N12_GLOBAL__N_14AnonE", + "3BoxIN12_GLOBAL__N_14AnonEE", + "4VBoxIXadL_ZL10static_varEEE", + "ZL15local_in_staticvE5Local", + "ZN2nsL15local_in_staticEvE5Local", + "NL6lambdaMUlvE_E", // GCC: a lambda in a static variable + "3$_0", // Clang: an unnamed type, or a lambda + "10._anon_100", // GCC: an unnamed type + }; + + for (auto name : internal) { + BOOST_TEST_INFO(name); + BOOST_TEST(itanium_internal_linkage(name)); + } +} + +struct Global {}; + +struct Other {}; + +namespace { +struct Anon {}; +} // namespace + +template +auto key() { + return default_registry::rtti::type_index( + default_registry::rtti::static_type()); +} + +BOOST_AUTO_TEST_CASE(keys) { + BOOST_TEST((key() == key())); + BOOST_TEST((key() != key())); + BOOST_TEST((key() < key() || key() < key())); + +#if !BOOST_OPENMETHOD_DETAIL_STD_TYPE_INDEX + // The scanner is in use: check it on the names of this compiler. + BOOST_TEST(key().local == nullptr); + BOOST_TEST( + key().local == default_registry::rtti::static_type()); + BOOST_TEST(!itanium_internal_linkage(typeid(Global).name())); + BOOST_TEST(itanium_internal_linkage(typeid(Anon).name())); +#endif +}