Skip to content

interop: check the registry spelled on an any virtual parameter - #130

Merged
jll63 merged 1 commit into
boostorg:developfrom
jll63:fix/interop-param-registry
Oct 17, 2026
Merged

jll63 merged 1 commit into
boostorg:developfrom
jll63:fix/interop-param-registry

Conversation

@jll63

@jll63 jll63 commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

(Written by Claude Code, on behalf of @jll63.)

Closes #123.

detail::validate_method_parameter for the any and Boost.TypeErasure interop headers took a ParamRegistry and never compared it with the method's. Every other virtual parameter shape rejects a mismatch; this one accepted it silently — and the parameter is not inert, because it decides the registry of a method that names none. Overriders registered against another registry then land in a different dispatch table, and the call reports no_overrider after initialize().

One missing route, not twelve missing checks

The specializations were unconditional std::true_type, which waved the registry check through as a side effect. They existed only because the generic rule did not count a virtual_traits::vptr as a way to reach a v-table: it knew two of the three routes method::vptr tries — the ADL boost_openmethod_vptr hook, and a class the rtti policy deems polymorphic — and a type-erased value has neither.

detail::has_vptr_source now names all three, in the order they are tried. The generic rule then covers the interop shapes and applies its registry check and its affinity recheck to them. The eleven blanket specializations are gone, from interop/std_any.hpp, interop/boost_any.hpp and interop/boost_type_erasure.hpp; the last keeps the one specialization that is a rejection rather than a blanket accept, an owning any<C, _self> passed by value.

The spelling matters. has_vptr<virtual_traits<T, R>, decltype(peek(declval<T>()))> is what method::vptr itself uses; the shorter const virtual_type& holds for ten of the eleven shapes and fails for any<C, _self&>, whose vptr takes the reference-bound any. A comment says so, because the shorter form is the obvious later "simplification".

virtual_any is unaffected: its specializations match a different shape, virtual_any<Any, Registry>&, and already compared.

Behaviour

parameter spelling R decides an unannotated method's registry checked against a method naming another
virtual_<Animal&, R> — ordinary carrier yes yes
virtual_<const std::any&, R> — interop yes yes, now
virtual_any<A, R>& no, abstains yes

An interop parameter is an ordinary carrier, so deciding the registry is the designed behaviour and stays. Only the missing check was a bug.

Tests

  • compile_fail_std_any_registry_mismatch.cpp — the shape none of the five existing registry-mismatch tests used. The diagnostic is the ordinary "registry mismatch: the parameter belongs to another registry", confirmed on GCC and Clang.
  • test_dispatch_std_any.cpp gains static_asserts for the three routes, and for the parameter still deciding the registry of a method that names none.
  • The static_assert that names the routes gains the third; it keeps the substring compile_fail_non_polymorphic_virtual_parameter.cpp matches.
  • CLAUDE.md's affinity table recorded this as an open bug, and is corrected.

Control: git stash push -- include/ brings the issue's reproducer back. It compiles on develop and is rejected here.

Verified locally:

Build Result
CMake, clang-22 198/198
CMake, g++-16 198/198
b2, gcc (-Wextra -Werror) 128 passed, no warnings

Net 19 insertions against 56 deletions in include/.

🤖 Generated with Claude Code

The validate_method_parameter specializations in interop/std_any.hpp,
interop/boost_any.hpp and interop/boost_type_erasure.hpp accepted every
parameter of their shape unconditionally, so they never compared the registry
spelled on one against the method's. Every other virtual parameter shape
rejects a mismatch; this one took it silently, while still deciding the
registry of a method that named none - so overriders registered against
another registry landed in a different dispatch table, and the call reported
no_overrider after initialize().

Those specializations were there only because the generic rule did not count a
virtual_traits::vptr as a way to reach a v-table. It knew two of the three
routes method::vptr tries, the ADL hook and a class the rtti policy deems
polymorphic, and a type-erased value has neither. detail::has_vptr_source now
names all three, in the order they are tried, so the generic rule covers the
interop shapes and applies its registry check and affinity recheck to them.
The eleven blanket specializations are gone; boost_type_erasure.hpp keeps the
one that is a rejection rather than a blanket accept, an owning
any<C, _self> passed by value.

The spelling of the new route has to be the one method::vptr uses,
`has_vptr<virtual_traits<T, R>, decltype(peek(declval<T>()))>`: the shorter
`const virtual_type&` holds for ten of the eleven shapes and fails for
`any<C, _self&>`.

Stashing include/ brings the issue's reproducer back: it compiles there, and
is now rejected with "registry mismatch: the parameter belongs to another
registry", the message an ordinary carrier gets. The static_assert naming the
routes gains the third; it keeps the substring the compile-fail test matches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cppalliance-bot

Copy link
Copy Markdown

An automated preview of the documentation is available at https://130.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:51:13 UTC

@jll63
jll63 merged commit 1137590 into boostorg:develop Oct 17, 2026
53 of 54 checks passed
jll63 added a commit to jll63/openmethod that referenced this pull request Oct 17, 2026
Brings develop up to the interop fix (boostorg#130) and the has_vptr_fn rename
(boostorg#131), so this branch builds on its own with the new name, and the two PRs
can land in either order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

interop: a registry spelled on an any virtual parameter is never validated against the method's

2 participants