From 601857dbc0d072fa1d5972e8e364ff40de4d5ccb Mon Sep 17 00:00:00 2001 From: Mingxin Wang Date: Sat, 15 Aug 2026 16:04:22 +0800 Subject: [PATCH] Reach metadata by conversion instead of by named lookup meta_storage looked a meta up with get(), which resolved to a static_cast over a flat set of base classes. That works only while every meta is a direct base of the storage, and cannot reach a meta held inside an aggregate. Replace it with proxy_meta, whose contained metas are reached through a conversion operator. A meta is contained if the metadata converts to it without throwing, so an aggregate that itself converts to a meta makes that meta reachable too. unique_types_t reduces the meta list so that each entry is contained by exactly one entry, namely itself, which is the property that keeps the conversion unambiguous: a duplicate collapses onto its leftmost occurrence, and a meta subsumed by an aggregate is replaced by that aggregate in place. Both storages now expose the metadata through operator*, so choosing between them is independent of how a meta is looked up. static_meta_storage holds a single meta and is chosen by size rather than by shape, which separates the metadata layout from the decision to hold it out of line. Under pointer authentication, assigning one storage from another signs a raw pointer, which meta_ptr now supports. No functional change. --- include/proxy/v4/detail/core.h | 154 ++++++++++++++++--- include/proxy/v4/detail/facade_meta_traits.h | 124 +++++---------- tests/proxy_detail_tests.cpp | 76 +++++++++ tests/proxy_invocation_tests.cpp | 4 +- tests/proxy_pac_tests.cpp | 15 +- 5 files changed, 263 insertions(+), 110 deletions(-) diff --git a/include/proxy/v4/detail/core.h b/include/proxy/v4/detail/core.h index 6ea3d737..4262904b 100644 --- a/include/proxy/v4/detail/core.h +++ b/include/proxy/v4/detail/core.h @@ -41,7 +41,7 @@ template struct basic_facade_traits; template -struct meta_storage; +struct proxy_meta; } // namespace detail @@ -246,15 +246,14 @@ struct proxy_helper { proxy& p_; }; - template - static const meta_storage& get_meta(const proxy& p) noexcept { - assert(p.has_value()); - return p.meta_; + template + static const M& get_meta(const proxy& p) noexcept { + assert(p.meta_.has_value()); + return *p.meta_; } - template - static const meta_storage& - get_meta(const proxy_indirect_accessor& p) noexcept { - return get_meta(as_proxy(p)); + template + static const M& get_meta(const proxy_indirect_accessor& p) noexcept { + return get_meta(as_proxy(p)); } template static void* get_ptr(proxy& p) noexcept { @@ -659,6 +658,122 @@ struct specialization_traits, TT> : applicable_traits {}; template class TT> concept specialization_of = specialization_traits::applicable; +template +struct first_containing_reduction : std::type_identity {}; +template +struct first_containing_reduction + : std::conditional, I, + void> {}; + +template +struct most_containing_reduction + : std::conditional, I, + O> {}; +template +struct sfinae_unique_types_traits : std::type_identity {}; +template +struct sfinae_unique_types_traits< + std::enable_if_t<(std::is_nothrow_convertible_v || + ...)>, + std::tuple, std::tuple> + : sfinae_unique_types_traits, std::tuple> {}; +template +struct sfinae_unique_types_traits< + std::enable_if_t || + ...)>, + std::tuple, std::tuple> + : sfinae_unique_types_traits< + void, + std::tuple, U, Us...>>, + std::tuple> {}; +template +using unique_types_t = sfinae_unique_types_traits, T>::type; + +template +concept nullable = requires(T v, const T cv) { + { v.reset() } noexcept; + { cv.has_value() } noexcept -> std::same_as; +}; + +struct sentinel_meta { + sentinel_meta() = default; + template + constexpr explicit sentinel_meta(std::in_place_type_t

) noexcept : v_(1) {} + void reset() noexcept { v_ = 0; } + bool has_value() const noexcept { return v_; } + +private: + std::ptrdiff_t v_; +}; + +template +struct PRO4D_ENFORCE_EBO composite_meta : Ms... { + composite_meta() = default; + template + constexpr explicit composite_meta(std::in_place_type_t

) + : Ms(std::in_place_type

)... {} +}; + +template +struct proxy_meta_base_impl { + constexpr proxy_meta_base_impl() noexcept {} + template + constexpr explicit proxy_meta_base_impl(std::in_place_type_t

) + : value_(std::in_place_type

) {} + proxy_meta_base_impl(const proxy_meta_base_impl& rhs) noexcept + : proxy_meta_base_impl() { + assign(rhs); + } + proxy_meta_base_impl& operator=(const proxy_meta_base_impl& rhs) noexcept { + assign(rhs); + return *this; + } + + template + requires(std::is_nothrow_convertible_v || + (std::is_nothrow_convertible_v || ...)) + constexpr operator const T&() const noexcept { + return static_cast, void, First, Rest...>&>( + value_); + } + + bool has_value() const noexcept { + return static_cast(value_).has_value(); + } + void reset() noexcept { static_cast(value_).reset(); } + +private: + void assign(const proxy_meta_base_impl& rhs) noexcept { + if (rhs.has_value()) { + value_ = rhs.value_; + } else { + reset(); + } + } + + composite_meta value_; +}; +template + requires(std::is_trivially_copyable_v) +struct proxy_meta_base_impl : First { + using First::First; +}; + +template +struct proxy_meta_base_traits + : specialization_type_traits>, + sentinel_meta> {}; +template +struct proxy_meta_base_traits + : specialization_type_traits>> {}; +template +using proxy_meta_base_t = typename proxy_meta_base_traits::type; + template consteval void diagnose_proxiable_size_too_large() { static_assert(ActualSize <= MaxSize, "not proxiable due to size too large"); @@ -803,8 +918,8 @@ struct facade_traits : specialization_t, specialization_t { - using meta_storage_base = specialization_t< - compact_facade_meta_traits::storage, + using meta_base = specialization_t< + proxy_meta_base_t, composite_t, lifetime_meta_t, @@ -849,8 +964,8 @@ struct facade_traits : specialization_t -struct meta_storage : facade_traits::meta_storage_base { - using base = facade_traits::meta_storage_base; +struct proxy_meta : facade_traits::meta_base { + using base = facade_traits::meta_base; using base::base; }; @@ -889,7 +1004,7 @@ template ret_t invoke_impl(P&& p, Args&&... args) { using Ctx = erased_context; Ctx ctx{proxy_helper::get_ptr(p)}; - const auto& inv = proxy_helper::get_meta(p).template get>(); + const auto& inv = proxy_helper::get_meta>(p); if constexpr (overload_traits::this_qualifier == qualifier_type::rv) { proxy_helper::meta_resetting_guard guard{p}; return inv(ctx, std::forward(args)...); @@ -935,8 +1050,7 @@ class proxy_indirect_accessor } template friend const R& reflect(const proxy_indirect_accessor& p) noexcept { - return detail::proxy_helper::get_meta(p) - .template get>() + return detail::proxy_helper::get_meta>(p) .reflector; } }; @@ -1153,8 +1267,7 @@ class proxy : public detail::facade_traits::direct_accessor, } template friend const R& reflect(const proxy& p) noexcept { - return detail::proxy_helper::get_meta(p) - .template get>() + return detail::proxy_helper::get_meta>(p) .reflector; } @@ -1206,7 +1319,7 @@ class proxy : public detail::facade_traits::direct_accessor, P& result = *std::construct_at(reinterpret_cast(ptr_), std::forward(args)...); if constexpr (proxiable) { - meta_ = detail::meta_storage{std::in_place_type

}; + meta_ = decltype(meta_){std::in_place_type

}; } else { detail::facade_traits::template diagnose_proxiable_noreturn

(); } @@ -1234,7 +1347,8 @@ class proxy : public detail::facade_traits::direct_accessor, }) alignas(F::max_align) std::byte ptr_[F::max_size]; - detail::meta_storage meta_; + typename compact_facade_meta_traits::template storage> + meta_; }; template diff --git a/include/proxy/v4/detail/facade_meta_traits.h b/include/proxy/v4/detail/facade_meta_traits.h index 56d71b68..8a6298e3 100644 --- a/include/proxy/v4/detail/facade_meta_traits.h +++ b/include/proxy/v4/detail/facade_meta_traits.h @@ -78,6 +78,11 @@ class meta_ptr { schema()); return *this; } + meta_ptr& operator=(const T* p) noexcept { + p_ = ptrauth_sign_unauthenticated(p, ptrauth_key_cxx_vtable_pointer, + schema()); + return *this; + } meta_ptr& operator=(std::nullptr_t) noexcept { p_ = nullptr; return *this; @@ -102,12 +107,6 @@ template using meta_ptr = const T*; #endif // PRO4D_HAS_PAC -template -concept nullable = requires(T v, const T cv) { - { v.reset() } noexcept; - { cv.has_value() } noexcept -> std::same_as; -}; - template struct invoker_base { invoker_base() = default; @@ -141,112 +140,73 @@ struct invoker; PRO4D_DEF_OVERLOAD_SPECIALIZATIONS(PRO4D_DEF_INVOKER) #undef PRO4D_DEF_INVOKER -struct sentinel_meta { - sentinel_meta() = default; - template - explicit sentinel_meta(std::in_place_type_t

) noexcept : v_(1) {} - void reset() noexcept { v_ = 0; } - bool has_value() const noexcept { return v_; } - -private: - std::ptrdiff_t v_; -}; - -template -struct PRO4D_ENFORCE_EBO inline_meta_storage : First, Rest... { - using First::has_value; - using First::reset; - - constexpr inline_meta_storage() noexcept {} - template - constexpr explicit inline_meta_storage(std::in_place_type_t

) - : First(std::in_place_type

), Rest(std::in_place_type

)... {} - inline_meta_storage(const inline_meta_storage& rhs) noexcept - : inline_meta_storage() { - if (static_cast(rhs).has_value()) { - static_cast(*this) = static_cast(rhs); - ((static_cast(*this) = static_cast(rhs)), ...); - } else { - static_cast(*this).reset(); - } - } - inline_meta_storage& operator=(const inline_meta_storage& rhs) noexcept { - if (static_cast(rhs).has_value()) { - static_cast(*this) = static_cast(rhs); - ((static_cast(*this) = static_cast(rhs)), ...); - } else { - static_cast(*this).reset(); - } +template +struct PRO4D_ENFORCE_EBO inplace_meta_storage : M { + using M::M; + + inplace_meta_storage() = default; + inplace_meta_storage(const inplace_meta_storage&) = default; + template + requires(std::is_nothrow_convertible_v) + inplace_meta_storage(const inplace_meta_storage& rhs) noexcept + : M(static_cast(*rhs)) {} + inplace_meta_storage& operator=(const inplace_meta_storage&) = default; + template + requires(std::is_nothrow_convertible_v) + inplace_meta_storage& + operator=(const inplace_meta_storage& rhs) noexcept { + static_cast(*this) = static_cast(*rhs); return *this; } - template - const M& get() const noexcept { - return static_cast(*this); - } -}; -template -struct inline_meta_storage : First { - using First::First; - template - const M& get() const noexcept { - return static_cast(*this); - } + const M& operator*() const noexcept { return *this; } }; -template +template struct static_meta_storage { static_meta_storage() = default; + template + requires(std::is_nothrow_convertible_v) + static_meta_storage(const static_meta_storage& rhs) noexcept + : ptr_(std::addressof(static_cast(*rhs))) {} + template + requires(std::is_nothrow_convertible_v) + static_meta_storage& operator=(const static_meta_storage& rhs) noexcept { + ptr_ = std::addressof(static_cast(*rhs)); + return *this; + } template explicit static_meta_storage(std::in_place_type_t

) : ptr_(std::addressof(storage

)) {} bool has_value() const noexcept { return ptr_ != nullptr; } void reset() noexcept { ptr_ = nullptr; } - template - const M& get() const noexcept { - return (*ptr_).template get(); - } + const M& operator*() const noexcept { return *ptr_; } private: - meta_ptr, void (*)(Ms...)> ptr_; + meta_ptr ptr_; template - static inline const inline_meta_storage storage{std::in_place_type

}; + static inline const M storage{std::in_place_type

}; }; -template -struct compact_meta_storage_traits - : std::type_identity> {}; -template -struct compact_meta_storage_traits - : std::type_identity> {}; -template <> -struct compact_meta_storage_traits<> - : std::type_identity> {}; - -template -struct flat_meta_storage_traits - : std::type_identity> {}; -template -struct flat_meta_storage_traits - : std::type_identity> {}; - } // namespace detail struct compact_facade_meta_traits { template using invoker = detail::invoker; - template - using storage = detail::compact_meta_storage_traits::type; + template + using storage = std::conditional_t, + detail::static_meta_storage>; }; struct flat_facade_meta_traits { template using invoker = detail::invoker; - template - using storage = detail::flat_meta_storage_traits::type; + template + using storage = detail::inplace_meta_storage; }; } // namespace pro::inline v4 diff --git a/tests/proxy_detail_tests.cpp b/tests/proxy_detail_tests.cpp index 760f9ce7..893f988a 100644 --- a/tests/proxy_detail_tests.cpp +++ b/tests/proxy_detail_tests.cpp @@ -27,4 +27,80 @@ static_assert(pro::detail::explicitly_convertible); static_assert(!pro::detail::explicitly_convertible); static_assert(!pro::detail::explicitly_convertible); +template +struct NullableMeta { + NullableMeta() = default; + template + constexpr explicit NullableMeta(std::in_place_type_t

) noexcept + : v(I + 1) {} + void reset() noexcept { v = 0; } + bool has_value() const noexcept { return v != 0; } + + int v = 0; +}; +template +struct PlainMeta { + PlainMeta() = default; + template + constexpr explicit PlainMeta(std::in_place_type_t

) noexcept {} +}; + +static_assert(pro::detail::nullable>); +static_assert(!pro::detail::nullable>); + +using M0 = NullableMeta<0>; +using M1 = NullableMeta<1>; +using M2 = NullableMeta<2>; +using M01 = pro::detail::proxy_meta_base_t; +using M02 = pro::detail::proxy_meta_base_t; + +static_assert(std::is_same_v< + pro::detail::proxy_meta_base_t<>, + pro::detail::proxy_meta_base_impl>); +static_assert(std::is_same_v>, + pro::detail::proxy_meta_base_impl< + pro::detail::sentinel_meta, PlainMeta<0>>>); +static_assert(std::is_same_v, + pro::detail::proxy_meta_base_impl>); + +static_assert(std::is_base_of_v>); +static_assert(!std::is_base_of_v); + +static_assert(std::is_same_v, + pro::detail::proxy_meta_base_impl>); +static_assert(std::is_same_v, + pro::detail::proxy_meta_base_impl>); +static_assert(std::is_same_v, + pro::detail::proxy_meta_base_impl>); +static_assert(std::is_same_v, + pro::detail::proxy_meta_base_impl>); +static_assert(std::is_same_v, + pro::detail::proxy_meta_base_impl>); +static_assert(std::is_same_v, + pro::detail::proxy_meta_base_impl>); +static_assert( + std::is_same_v>, + pro::detail::proxy_meta_base_impl< + pro::detail::proxy_meta_base_t>>); + +static_assert(std::is_nothrow_convertible_v); +static_assert(std::is_nothrow_convertible_v); +static_assert(!std::is_nothrow_convertible_v); +static_assert(!std::is_nothrow_convertible_v); + +inline constexpr pro::detail::proxy_meta_base_t kReordered{ + std::in_place_type}; +inline constexpr pro::detail::proxy_meta_base_t kDiamond{ + std::in_place_type}; + +static_assert(static_cast(kReordered).v == 1); +static_assert(static_cast(kReordered).v == 2); +static_assert( + std::addressof(static_cast(kDiamond)) == + std::addressof(static_cast(static_cast(kDiamond)))); +static_assert( + std::addressof(static_cast(kDiamond)) == + std::addressof(static_cast(static_cast(kDiamond)))); + } // namespace proxy_detail_tests_detail diff --git a/tests/proxy_invocation_tests.cpp b/tests/proxy_invocation_tests.cpp index 16521bd7..d4670a3c 100644 --- a/tests/proxy_invocation_tests.cpp +++ b/tests/proxy_invocation_tests.cpp @@ -218,8 +218,8 @@ TEST(ProxyInvocationTests, TestMultipleDispatches_Duplicated) { ::add_convention)> // ::build {}; - static_assert(sizeof(pro::detail::meta_storage) == - sizeof(pro::detail::meta_storage>)); + static_assert(sizeof(pro::detail::proxy_meta) == + sizeof(pro::detail::proxy_meta>)); std::list l = {1, 2, 3}; pro::proxy p = &l; ASSERT_EQ(Size(*p), std::size_t{3}); diff --git a/tests/proxy_pac_tests.cpp b/tests/proxy_pac_tests.cpp index 53bf2058..e3c56f5f 100644 --- a/tests/proxy_pac_tests.cpp +++ b/tests/proxy_pac_tests.cpp @@ -18,8 +18,8 @@ namespace proxy_pac_tests_detail { template constexpr bool IsInlineMetaPreferred = pro::detail::specialization_of< - typename pro::detail::facade_traits::meta_storage_base, - pro::detail::inline_meta_storage>; + pro::compact_facade_meta_traits::storage>, + pro::detail::inplace_meta_storage>; template auto GetRawBytes(const T& v) noexcept { @@ -30,10 +30,13 @@ auto GetRawBytes(const T& v) noexcept { template void CorruptMeta(pro::proxy& p) noexcept { - const pro::detail::meta_storage& meta = - pro::detail::proxy_helper::get_meta(p); - std::byte* target = reinterpret_cast( - const_cast*>(std::addressof(meta))); + // meta_ is the second of the two slots of proxy, behind the storage of + // the contained value. + using Storage = + pro::compact_facade_meta_traits::storage>; + static_assert(sizeof(pro::proxy) == sizeof(Storage) + F::max_size); + std::byte* target = + reinterpret_cast(std::addressof(p)) + F::max_size; std::uintptr_t word; std::memcpy(&word, target, sizeof(word)); word ^= std::uintptr_t{1} << 54u; // Within the PAC bits for any VA size