Repository navigation
Conversation
|
An automated preview of the documentation is available at https://127.openmethod.prtest3.cppalliance.org/libs/openmethod/doc/html/index.html If more commits are pushed to the pull request, the docs will rebuild at the same URL. 2026-10-13 21:37:11 UTC |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #127 +/- ##
===========================================
+ Coverage 92.34% 92.36% +0.02%
===========================================
Files 27 29 +2
Lines 2234 2293 +59
Branches 679 697 +18
===========================================
+ Hits 2063 2118 +55
- Misses 112 114 +2
- Partials 59 61 +2
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
…ibraries static_rtti's type_id is the address of a local static in a template of the library. Each module has its own copy: always on Windows, and under -fvisibility=hidden on ELF and Mach-O. A class used by several modules then has one type_id per module, initialize() takes the copies for unrelated classes and methods, and a call made in one module on an object created in another lands in the wrong overrider or reads past the end of a v-table. named_static_rtti keeps per-module addresses as type_ids, but points them at the name of the class, cut out of __PRETTY_FUNCTION__ / __FUNCSIG__, and type_index returns that name, so initialize() merges the copies, as std_rtti does with type_info::name(). All modules must be built with the same compiler. A class with internal linkage, or none, may print the same name as a different class, so type_index keys it on its type_id as well, and it is never merged. With C++26 reflection, has_external_linkage decides exactly. Without it, the marks the compilers leave in the name decide: anonymous namespaces, lambdas and unnamed classes are marked by every compiler, local classes only by GCC. Clang prints a local class unqualified and MSVC as if its function were a namespace, so two same-named local classes are merged there; the documentation says so. static_rtti itself is unchanged: it stays correct within one module, local classes included, and its documentation now points to named_static_rtti for programs that share a registry across modules. Tests: test/implicit_shared_libraries/named_static_rtti (hidden visibility, classes in anonymous namespaces on both sides, a v-table layout that makes a mis-merged call land in the wrong overrider) and test_named_static_rtti.cpp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
init_bad_call called Rtti::dynamic_type on every argument, unguarded. The method constructor takes the address of fn_not_implemented and fn_ambiguous, so a registry combining static_rtti or named_static_rtti - which have no dynamic_type - with an error handler did not compile. For a class the rtti policy deems polymorphic, nothing changes. Otherwise, for a virtual_ptr argument, the class is found from the v-table pointer, by scanning the registry's classes on the error path only. The object's own class always matches; if another one does too - v-tables with leading unused slots are pointed to before their start, and may coincide with another's - only the classes deriving from the parameter's class count. If that is still not conclusive, or for any other argument, the parameter's class is reported: less precise, but true. Classes are compared through type_index, so named_static_rtti's per-module copies count as one. Under std_rtti, a non-polymorphic class reached through a virtual_ptr is now reported as the class of the object rather than that of the parameter. Test: test_static_rtti_errors.cpp, with static_rtti and named_static_rtti, each with throw_error_handler. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
std_rtti::type_index keyed on type_info::name(). Two classes in anonymous namespaces in different translation units, both named Helper, have the same name - _GLOBAL__N_1 is the same in every translation unit - so initialize() merged them into one class, and a call aborted with "ambiguous". This happened with GCC, Clang, libc++ and MSVC alike, within a single module. type_index now returns std::type_index again where that is exact: with MSVC, whose type_info equality compares decorated names that carry a hash per translation unit for anonymous namespaces, and with GCC and libstdc++, which compare names except for the classes that GCC marks as having internal linkage. It cannot be used with libc++, which compares the type_info objects of classes with hidden visibility by address - the reason for the switch to names, in boostorg#75 - nor with Clang and libstdc++, since Clang does not emit GCC's mark. There, the key is the mangled name, plus the type_id when the mangling shows internal linkage: an anonymous namespace, a static function or variable in an encoding, or an unnamed type or lambda. A small scanner reads the marks, skipping source names by their length so that identifiers like ZLibStream are not mistaken for them, and skipping the numbers that are not lengths. The key struct of named_static_rtti moves to detail/type_key.hpp, shared by both policies. Tests: test_std_rtti_type_index.cpp checks the scanner on names produced by GCC, Clang and libc++; the default_registry shared-library test gains a class in an anonymous namespace in each module, which must keep its own overrider. Forcing std::type_index on libc++ with hidden visibility reproduces the macOS failure; disabling the scanner on Clang and libstdc++ reproduces the collision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ontract std_rtti: the mangled-name scanner lost track of source-name lengths after a substitution (S_), a template parameter, an array or a vector type, read the value of a nested enum literal as a length, and treated every L<digit> after a first Z as a static entity. Identifiers were then read letter by letter: an `Utility` after `NS_` was taken for the unnamed-type mark, and a `_GLOBAL__N` after a nested enum literal was skipped, merging distinct anonymous-namespace classes. Replace it with substring marks that cannot occur in an identifier: `_GLOBAL__N`, `$` (Clang's unnamed types and lambdas) and `.` (GCC's). Classes local to static functions, and templates on the address of a static variable, are no longer recognized - MSVC's std::type_index does not tell them apart either - and the documentation of std_rtti::type_index now says so, describes the key type as unspecified, and states the narrower guarantee. named_static_rtti: under reflection, non-class types (overrider function types, for example) fall back to the name marks instead of counting as shareable. class_of_vptr: the tie-breaking pass relied on the bases listed in one use_classes, and could report an unrelated class whose v-table pointer coincides. A single match is reported; anything else reports the class of the parameter, which is always true. Docs: the RttiFn blueprint and custom_rtti.adoc state the real contract of type_index - equal across modules, distinct for same-named classes in anonymous namespaces. @tparam Class now matches the template parameter of static_type in both static_rtti policies. Bodies that name detail:: twice open with `using namespace detail;`. Tests: the four misparsed names from the review are regression cases in test_std_rtti_type_index.cpp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One "Sharing a Registry" section now holds the macro table, the header idiom, and a single warning about a translation unit that uses neither macro. The "exactly one INSTANTIATE" rule was stated four times, and the forgotten-macro warning twice. The platform rationale for the macros moves into Platform Details, next to the rest of it. The example commentary and the type-id, alternatives, indirect-vptr, custom-registry and implementation sections are tightened. Factual fixes: * The dynamic-loading program instantiates the state with INSTANTIATE, not EXPORT. * Its header is like, not the same as, the implicit-linking one. * Only ELF merges a class's type_info across modules; Mach-O does not, as the repository's own Jamfile notes. * Implicit linking: the library owns the state because Windows and macOS resolve imported symbols at link time, and an executable owner would need a circular link - not merely because it is "natural". * The tests mark their classes BOOST_SYMBOL_VISIBLE to unify their type ids while keeping per-module static vptrs, not to help the hash; the note now gives the remedy instead: on ELF, BOOST_SYMBOL_VISIBLE restores a single type_info. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
named_static_rtti: the object a type_id points to is now the key itself - the name, and the key's own address if the class cannot be shared - so static_rtti_name goes, type_index returns the stored key, and its return type is deduced, keeping detail::type_key out of the reference. The local-class check of the name runs once per type at run time instead of at compile time, where it cost 0.5-2 ms per type per translation unit. The name extraction no longer computes a quantity that cancels out, and the reflection fallback is written once. init_bad_call: use dynamic_type only when the rtti policy deems the class polymorphic *and* provides it (has_dynamic_type, from BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN). Otherwise recover the class from the v-table pointer, now also for classes that carry their own (boost_openmethod_vptr). A custom rtti without dynamic_type, used with inplace_vptr and an error handler, no longer fails to compile. std_rtti: measure the mangled name once; BOOST_GCC instead of raw compiler checks; drop the unused override hook of the platform macro. Tests: test_static_rtti_errors.cpp is one templated test case over both registries, with the name check folded into the helper (the separate try block did not fail if nothing was thrown), plus a case for the inplace_vptr policy above. test_named_static_rtti.cpp: local_classes entirely under its #if, and the dispatch checks through a lambda. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
<boost/openmethod/policies/named_static_rtti.hpp> holds named_static_rtti and its name-extraction and linkage helpers. static_rtti.hpp goes back to what it was, except for the documentation pointing to named_static_rtti. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rrors inplace_vptr_base's constructor casts `this` to the derived class before the C++ v-table pointer of a polymorphic class is set, which UBSan rejects. Make the rtti policy deem every class polymorphic instead, so the case still covers a policy that has no dynamic_type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The blueprint showed `std::is_polymorphic_v<Class>` as if it were the required value. Declare it without an initializer, and say what the library does with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An rtti policy that deems a class polymorphic owes a dynamic_type for it, and the library reads the class of an object of no other class. The contract was implicit, and the branch contradicted itself over it: init_bad_call probed for dynamic_type and fell back when it was missing, while final_virtual_ptr called it unguarded under runtime checks, so a policy that took the probe at its word failed to compile there. A test policy claimed polymorphism without providing dynamic_type, which only that gap made possible. detail::dynamic_type_of is now the one route to rtti::dynamic_type, and asserts both halves of the contract. final_virtual_ptr, init_bad_call and the dynamic_vptr of both vptr policies go through it; std_rtti::dynamic_type asserts for itself. The dead `else return static_type<T>()` branch in the custom-RTTI examples and tests existed only for the old unguarded call, and becomes that assert, which is the better demonstration. The test policy is gone: plain static_rtti covers the case, since the inplace classes reach class_of_vptr through their v-table hook. named_static_rtti drops the class-keys MSVC prints, at the head of a name and inside template arguments, so two modules that disagree on the key of a class - `class Dog;` against `struct Dog`, only a warning - still agree on its name, and a class prints the same name on every compiler. A compile-time probe of the compiler's own output gates it, so only MSVC pays for the storage a stripped name needs; elsewhere the name stays a view into the signature. std_rtti no longer reads a bare `$` as the mark of an unnamed type: GCC and Clang accept it in an identifier, so a class named Foo$Impl was taken for one and never merged across modules. Only `$_` followed by a digit counts, which is Clang's own spelling. class_of_vptr no longer uses a null type_id as its "not found" sentinel, since a policy may use a null one as a class's id. Verified: MSVC v18 (371 targets, 201/201, including the named_static_rtti DLL test), clang-22 with libstdc++ (206/206) and with libc++ under hidden visibility (201/201), g++-16 with reflection (202/202), b2 gcc with no warnings, check-flat.sh, Antora. Forcing the class-key strip off under MSVC fails the name tests with `struct Dog != Dog`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
(Written by Claude Code, on behalf of @jll63.)
Three fixes around class identity across shared libraries, and within one program, plus one settled contract between the
rttipolicy and the library, and a rework of the shared-libraries page.named_static_rtti: astatic_rttithat works across shared librariesstatic_rtti'stype_idis the address of a local static in a template of the library. Each module has its own copy: always on Windows, and under-fvisibility=hiddenon ELF and Mach-O.initialize()takes the copies for unrelated classes and methods, and a call made in one module on an object created in another lands in the wrong overrider, or reads past the end of a v-table.named_static_rtti, in its own header<boost/openmethod/policies/named_static_rtti.hpp>, keeps per-module addresses as type ids, but points them at the name of the class, cut out of__PRETTY_FUNCTION__/__FUNCSIG__, and itstype_indexreturns that name, soinitialize()merges the copies. All modules must be built with the same compiler.Classes with internal linkage, or none, may print the same name as a different class, so they are keyed on their
type_idas well and never merged:has_external_linkagedecides, exactly.The class-keys MSVC prints are dropped, at the head of a name and inside template arguments, so
struct Box<struct Dog,enum Color>becomesBox<Dog,Color>. Two modules that disagree on the key of a class -class Dog;in one againststruct Dogin the other, which is only a warning - still agree on its name, and a class prints the same name on every compiler, which is what a diagnostic shows. A compile-time probe of the compiler's own output gates it, so only MSVC pays for the storage a stripped name needs; elsewhere the name stays a view into the signature literal, which the program carries anyway.static_rttiitself is unchanged: it stays correct within one module, and its documentation now points tonamed_static_rtti.bad_callargument types, and whatis_polymorphicmeansinit_bad_callcalledRtti::dynamic_typeunguarded, sostatic_rttiornamed_static_rtticombined with any error handler did not compile. It now reports:rttipolicy deems the class polymorphic;virtual_ptror a pointer stored in the object (inplace_vptr), found by scanning the registry's classes on the error path only. Copies of one class from several modules count as one;Fixing that exposed a contract nothing stated.
is_polymorphic<T>means the policy'sdynamic_typecan read the class of aT, so a policy that saystrueowes adynamic_type, and the library asks for the class of no other class. The first attempt here madedynamic_typeoptional instead, which contradictedfinal_virtual_ptr- it callsdynamic_typeunder runtime checks wheneveris_polymorphicholds - and let a test policy claim polymorphism without providing one.detail::dynamic_type_ofis now the one route tortti::dynamic_typeand asserts both halves of the contract, with a readable message instead of "no member nameddynamic_type".final_virtual_ptr,init_bad_calland thedynamic_vptrof bothvptrpolicies go through it;std_rtti::dynamic_typeasserts for itself. Theelse return static_type<T>()branch in the custom-RTTI examples and tests existed only for the old unguarded call, and becomes that assert, which demonstrates the rule better than dead code did.custom_rtti.adochad the omission rule wrong - it saiddynamic_typemay be omitted wheneverboost_openmethod_vptris provided for every class, whichfinal_virtual_ptralready contradicted.std_rtti: same-named classes with internal linkagestd_rtti::type_indexkeyed ontype_info::name(). Two classes namedHelperin anonymous namespaces in different translation units have the same name:_GLOBAL__N_1is the same everywhere.initialize()merged them, and the call aborted with "ambiguous". This happened with GCC, Clang, libc++ and MSVC alike, within a single module.type_indexnow returnsstd::type_indexagain where that is exact: MSVC, and GCC with libstdc++. It cannot be used with libc++, which compares thetype_infoof hidden-visibility classes by address (the reason for the switch to names in #75), nor with Clang and libstdc++, since Clang does not emit GCC's internal-linkage mark.There, the key is the mangled name, plus the
type_idwhen the mangling shows internal linkage. Only marks that cannot occur in an identifier are looked for, so no parsing is involved:_GLOBAL__N, GCC's., and Clang's$_followed by a digit. A bare$is not one of them - GCC and Clang accept it in an identifier unless-fno-dollars-in-identifiersis given, so a class namedFoo$Implwould have been taken for an unnamed one and never merged across modules. A local class of astaticfunction, and a template argument that is the address of astaticvariable, are not detected; MSVC's decorated names do not tell those apart either.The documentation of
type_index, in the rtti policy blueprint and incustom_rtti.adoc, now states the contract across modules: equal for the same class, different for different classes, in any module.shared_libraries.adocReworked for brevity: sharing a registry, implicit and dynamic linking, type ids across modules (with
named_static_rtti), indirect v-table pointers and custom registries, with the platform specifics gathered in implementation notes at the end. The page states the ownership rule once, at the top: one module owns the registry state and exports it, the others import it; the library owns it in the implicit-linking example, the program in the dynamic-loading one.Tests
implicit_shared_libraries/named_static_rtti: hidden visibility, a v-table layout that makes a mis-merged call land in the wrong overrider, and a class in an anonymous namespace in each module.test_named_static_rtti.cpp: names, keys, local-class detection, and the class-key strip on names spelled the way MSVC spells them, since no other compiler produces one.test_static_rtti_errors.cpp:no_overriderandambiguous_callwith both policies plusthrow_error_handler, checking that the dynamic class is reported, and a case where the class is read from aninplace_vptr. The policy invented to mean "polymorphic withoutdynamic_type" is gone with the contract it contradicted; plainstatic_rtticovers the case.test_std_rtti_type_index.cpp: the marks on names produced by GCC, Clang and libc++, including names that only resemble a mark (Global,ZLibStream,UtilityClass,Utafter a substitution,Foo$Impl).implicit_shared_libraries/default_registry: gains an anonymous-namespace class in each module.Controls run locally:
static_rttiin the named test reproduces the wrong-overrider landing.std::type_indexon libc++ with hidden visibility reproduces the macOS failure.struct Dog != Dog.Verified locally, at the head of the branch:
-Wextra -Werror)named_static_rttiDLL testdev/check-flat.sh, Antora🤖 Generated with Claude Code