diff --git a/.github/workflows/bvt-appleclang-arm64e.yml b/.github/workflows/bvt-appleclang-arm64e.yml new file mode 100644 index 00000000..bb611a79 --- /dev/null +++ b/.github/workflows/bvt-appleclang-arm64e.yml @@ -0,0 +1,39 @@ +on: + workflow_call: + workflow_dispatch: + +jobs: + bvt-appleclang-arm64e: + runs-on: macos-26 + steps: + - uses: actions/checkout@v7 + + - name: Check toolchain versions + run: | + cc --version + xcodebuild -version + cmake --version + ninja --version + + - name: Build and run test with AppleClang (arm64e with PAC) on cmake + run: | + cmake --preset default -DCMAKE_CXX_STANDARD=23 -DPROXY_BUILD_MODULES=FALSE -DCMAKE_OSX_ARCHITECTURES=arm64e + cmake --build --preset default -j + ctest --preset default -j + mkdir build/cmake/drop + bash ./tools/dump_build_env.sh c++ build/cmake/drop/env-info.json + + - name: Build and run freestanding test with AppleClang (arm64e with PAC) on cmake + run: | + cmake --preset freestanding -DCMAKE_CXX_STANDARD=23 -DPROXY_BUILD_MODULES=FALSE -DCMAKE_OSX_ARCHITECTURES=arm64e + cmake --build --preset freestanding -j + ctest --preset freestanding -j + + - name: Run benchmarks + run: build/cmake/benchmarks/msft_proxy_benchmarks --benchmark_min_warmup_time=0.1 --benchmark_min_time=0.1s --benchmark_repetitions=30 --benchmark_enable_random_interleaving=true --benchmark_report_aggregates_only=true --benchmark_format=json > build/cmake/drop/benchmarking-results.json + + - name: Archive benchmarking results + uses: actions/upload-artifact@v7 + with: + name: drop-apple-pac + path: build/cmake/drop/ diff --git a/.github/workflows/pipeline-ci.yml b/.github/workflows/pipeline-ci.yml index 46e3702e..c09f8d63 100644 --- a/.github/workflows/pipeline-ci.yml +++ b/.github/workflows/pipeline-ci.yml @@ -33,6 +33,10 @@ jobs: name: Run BVT with AppleClang if: github.event_name != 'push' || github.repository == 'ngcpp/proxy' + run-bvt-appleclang-arm64e: + uses: ./.github/workflows/bvt-appleclang-arm64e.yml + name: Run BVT with AppleClang (arm64e with PAC) + run-bvt-nvhpc: uses: ./.github/workflows/bvt-nvhpc.yml name: Run BVT with NVHPC @@ -56,4 +60,4 @@ jobs: report: uses: ./.github/workflows/bvt-report.yml name: Generate report - needs: [run-bvt-gcc, run-bvt-clang, run-bvt-msvc, run-bvt-appleclang, run-bvt-nvhpc, run-bvt-oneapi] + needs: [run-bvt-gcc, run-bvt-clang, run-bvt-msvc, run-bvt-appleclang, run-bvt-appleclang-arm64e, run-bvt-nvhpc, run-bvt-oneapi] diff --git a/.gitignore b/.gitignore index 3b200638..e9ff578c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Ignore build directories -build/ +/build* Testing/ # Python bytecode cache diff --git a/CMakeLists.txt b/CMakeLists.txt index 94d89765..aeac43e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,7 +120,7 @@ if(BUILD_TESTING) -Wall -Wextra -Wpedantic - $<$:-Wno-c++2b-extensions> + $<$:-Wno-c++2b-extensions> ) set(PROXY_STRICT_WARNING_FLAGS ${PROXY_BUILD_FLAGS} -Werror) endif() diff --git a/docs/spec/basic_facade_builder/build.md b/docs/spec/basic_facade_builder/build.md index 68d36a1f..4fa34ae2 100644 --- a/docs/spec/basic_facade_builder/build.md +++ b/docs/spec/basic_facade_builder/build.md @@ -59,10 +59,8 @@ int main() { static_assert(std::is_nothrow_move_constructible_v>); static_assert(std::is_nothrow_destructible_v>); - static_assert( - std::is_trivially_copy_constructible_v>); - static_assert( - std::is_trivially_move_constructible_v>); + static_assert(std::is_nothrow_copy_constructible_v>); + static_assert(std::is_nothrow_move_constructible_v>); static_assert(std::is_trivially_destructible_v>); } ``` diff --git a/include/proxy/v4/detail/compatibility_check.h b/include/proxy/v4/detail/compatibility_check.h index 0c06083b..1740b3ae 100644 --- a/include/proxy/v4/detail/compatibility_check.h +++ b/include/proxy/v4/detail/compatibility_check.h @@ -25,4 +25,12 @@ static_assert(sizeof(derived) == sizeof(char), } // namespace pro::inline v4::detail::compatibility_check #endif +#ifdef __has_feature +#if __has_feature(ptrauth_calls) && \ + (!defined(__PTRAUTH__) || !__has_include()) +#error "The Pointer Authentication feature is incomplete" +#endif // __has_feature(ptrauth_calls) && (!defined(__PTRAUTH__) || + // !__has_include()) +#endif // __has_feature + #endif // MSFT_PROXY_V4_DETAIL_COMPATIBILITY_CHECK_H_ diff --git a/include/proxy/v4/detail/core.h b/include/proxy/v4/detail/core.h index 31c2ae2e..07205b2d 100644 --- a/include/proxy/v4/detail/core.h +++ b/include/proxy/v4/detail/core.h @@ -17,6 +17,7 @@ #include #include "../proxy_macros.h" +#include "./facade_meta_traits.h" #if __has_cpp_attribute(msvc::no_unique_address) #define PRO4D_NO_UNIQUE_ADDRESS_ATTRIBUTE msvc::no_unique_address @@ -39,6 +40,9 @@ namespace detail { template struct basic_facade_traits; +template +struct meta_storage; + } // namespace detail enum class constraint_level { none, nontrivial, nothrow, trivial }; @@ -232,14 +236,15 @@ struct proxy_helper { proxy& p_; }; - template - static const M& get_meta(const proxy& p) noexcept { + template + static const meta_storage& get_meta(const proxy& p) noexcept { assert(p.has_value()); - return static_cast(*p.meta_.operator->()); + return p.meta_; } - template - static const M& get_meta(const proxy_indirect_accessor& p) noexcept { - return get_meta(as_proxy(p)); + template + static const meta_storage& + get_meta(const proxy_indirect_accessor& p) noexcept { + return get_meta(as_proxy(p)); } template static add_qualifier_t get_ptr(add_qualifier_t, Q> p) { @@ -348,32 +353,6 @@ consteval void diagnose_proxiable_required_convention_not_implemented() { "not proxiable due to a required convention not implemented"); } -template -struct conv_meta; -#define PRO4D_DEF_CONV_META(oq, pq, ne, ...) \ - template \ - struct conv_meta { \ - conv_meta() = default; \ - template \ - constexpr explicit conv_meta(std::in_place_type_t

) \ - : invoke([](ProP pq self, Args... args) ne -> R { \ - return reinterpret_invoke(static_cast(self), \ - std::forward(args)...); \ - }) {} \ - \ - R (*invoke)(ProP pq, Args...) ne; \ - } -PRO4D_DEF_OVERLOAD_SPECIALIZATIONS(PRO4D_DEF_CONV_META) -#undef PRO4D_DEF_CONV_META - -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 consteval bool is_is_direct_well_formed() { if constexpr (requires { @@ -422,7 +401,7 @@ template struct conv_traits_impl { static_assert((overload_traits>::applicable && ...)); - using meta = composite_meta, proxy_indirect_accessor>, typename C::dispatch_type, substituted_overload_t>...>; template @@ -513,10 +492,10 @@ template struct lifetime_meta_traits : std::type_identity {}; template struct lifetime_meta_traits - : std::type_identity, D, ONE>> {}; + : std::type_identity, D, ONE>> {}; template struct lifetime_meta_traits - : std::type_identity, D, OE>> {}; + : std::type_identity, D, OE>> {}; template using lifetime_meta_t = typename lifetime_meta_traits::type; @@ -648,7 +627,7 @@ struct basic_facade_traits : applicable_traits {}; template struct facade_conv_traits_impl { using conv_meta = - composite_t, typename conv_traits::meta...>; + composite_t, typename conv_traits::meta...>; using conv_indirect_accessor = composite_t, conv_accessor_t...>; using conv_direct_accessor = @@ -665,7 +644,7 @@ struct facade_conv_traits_impl { }; template struct facade_refl_traits_impl { - using refl_meta = composite_meta< + using refl_meta = std::tuple< reflection_meta...>; using refl_indirect_accessor = composite_t, refl_accessor_t...>; @@ -690,15 +669,18 @@ struct facade_traits : specialization_t, specialization_t { - using meta = composite_t< - composite_meta<>, - lifetime_meta_t&) const noexcept, - void(proxy&) const, F::copyability>, - lifetime_meta_t&) && noexcept, - void(proxy&) &&, F::relocatability>, - lifetime_meta_t, - typename facade_traits::conv_meta, typename facade_traits::refl_meta>; + using meta_storage_base = specialization_t< + compact_facade_meta_traits::storage, + composite_t< + std::tuple<>, + lifetime_meta_t&) const noexcept, + void(proxy&) const, F::copyability>, + lifetime_meta_t&) && noexcept, + void(proxy&) &&, F::relocatability>, + lifetime_meta_t, + typename facade_traits::conv_meta, + typename facade_traits::refl_meta>>; using indirect_accessor = composite_t; @@ -728,47 +710,11 @@ struct facade_traits : specialization_t; }; -using ptr_prototype = void* [2]; - -template -struct meta_ptr_indirect_impl { - meta_ptr_indirect_impl() = default; - template - explicit meta_ptr_indirect_impl(std::in_place_type_t

) - : ptr_(&storage

) {} - bool has_value() const noexcept { return ptr_ != nullptr; } - void reset() noexcept { ptr_ = nullptr; } - const M* operator->() const noexcept { return ptr_; } - -private: - const M* ptr_; - template - static inline const M storage{std::in_place_type

}; -}; -template -struct meta_ptr_direct_impl : private M { - using M::M; - bool has_value() const noexcept { return this->DM::invoke != nullptr; } - void reset() noexcept { this->DM::invoke = nullptr; } - const M* operator->() const noexcept { return this; } +template +struct meta_storage : facade_traits::meta_storage_base { + using base = typename facade_traits::meta_storage_base; + using base::base; }; -template -struct meta_ptr_traits_impl : std::type_identity> {}; -template -struct meta_ptr_traits_impl, Ms...>> - : std::type_identity< - meta_ptr_direct_impl, Ms...>, - conv_meta>> {}; -template -struct meta_ptr_traits : std::type_identity> {}; -template - requires(sizeof(M) <= sizeof(ptr_prototype) && - alignof(M) <= alignof(ptr_prototype) && - std::is_nothrow_default_constructible_v && - std::is_trivially_copyable_v) -struct meta_ptr_traits : meta_ptr_traits_impl {}; -template -using meta_ptr = typename meta_ptr_traits::type; template class inplace_ptr { @@ -796,8 +742,9 @@ class inplace_ptr { template ret_t invoke_impl(P&& p, Args&&... args) { - return proxy_helper::get_meta, D, O>>(p) - .invoke(std::forward

(p), std::forward(args)...); + return proxy_helper::get_meta(p) + .template get, D, O>>()( + std::forward

(p), std::forward(args)...); } template add_qualifier_t, Q> @@ -921,7 +868,8 @@ class proxy_indirect_accessor } template friend const R& reflect(const proxy_indirect_accessor& p) noexcept { - return detail::proxy_helper::get_meta>(p) + return detail::proxy_helper::get_meta(p) + .template get>() .reflector; } }; @@ -1159,7 +1107,8 @@ class proxy : public detail::facade_traits::direct_accessor, } template friend const R& reflect(const proxy& p) noexcept { - return detail::proxy_helper::get_meta>(p) + return detail::proxy_helper::get_meta(p) + .template get>() .reflector; } @@ -1210,8 +1159,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_ptr::meta>{ - std::in_place_type

}; + meta_ = detail::meta_storage{std::in_place_type

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

(); } @@ -1238,7 +1186,7 @@ class proxy : public detail::facade_traits::direct_accessor, *std::move(cself); }) - detail::meta_ptr::meta> meta_; + detail::meta_storage meta_; alignas(F::max_align) std::byte ptr_[F::max_size]; }; diff --git a/include/proxy/v4/detail/facade_creation.h b/include/proxy/v4/detail/facade_creation.h index 870fb2d9..82d9e452 100644 --- a/include/proxy/v4/detail/facade_creation.h +++ b/include/proxy/v4/detail/facade_creation.h @@ -29,6 +29,8 @@ consteval std::size_t max_align_of(std::size_t value) { return value < alignof(std::max_align_t) ? value : alignof(std::max_align_t); } +using ptr_prototype = void* [2]; + template using merge_tuple_t = specialization_t; template diff --git a/include/proxy/v4/detail/facade_meta_traits.h b/include/proxy/v4/detail/facade_meta_traits.h new file mode 100644 index 00000000..8f224bcd --- /dev/null +++ b/include/proxy/v4/detail/facade_meta_traits.h @@ -0,0 +1,255 @@ +// Copyright (c) 2022-2026 Microsoft Corporation. +// Copyright (c) 2026-Present Next Gen C++ Foundation. +// Licensed under the MIT License. + +#ifndef MSFT_PROXY_V4_DETAIL_FACADE_META_TRAITS_H_ +#define MSFT_PROXY_V4_DETAIL_FACADE_META_TRAITS_H_ + +#include +#include +#include + +#ifdef __has_feature +#if __has_feature(ptrauth_calls) +#include +#define PRO4D_HAS_PAC +#endif // __has_feature(ptrauth_calls) +#endif // __has_feature + +#include "../proxy_macros.h" + +namespace pro::inline v4 { + +namespace detail { + +#ifdef PRO4D_HAS_PAC +template +class code_ptr { +public: + code_ptr() = default; + template + explicit code_ptr(const F& f) noexcept + : p_(ptrauth_sign_unauthenticated( + ptrauth_strip(static_cast(f), ptrauth_key_function_pointer), + ptrauth_key_function_pointer, schema())) {} + code_ptr(const code_ptr& rhs) noexcept + : p_(ptrauth_auth_and_resign(rhs.p_, ptrauth_key_function_pointer, + rhs.schema(), ptrauth_key_function_pointer, + schema())) {} + code_ptr& operator=(const code_ptr& rhs) noexcept { + p_ = ptrauth_auth_and_resign(rhs.p_, ptrauth_key_function_pointer, + rhs.schema(), ptrauth_key_function_pointer, + schema()); + return *this; + } + code_ptr& operator=(std::nullptr_t) noexcept { + p_ = nullptr; + return *this; + } + bool operator==(std::nullptr_t) const noexcept { return p_ == nullptr; } + template + decltype(auto) operator()(Args&&... args) const { + return ptrauth_auth_function(p_, ptrauth_key_function_pointer, + schema())(std::forward(args)...); + } + +private: + ptrauth_extra_data_t schema() const noexcept { + return ptrauth_blend_discriminator(&p_, ptrauth_type_discriminator(Disc)); + } + + O* p_; +}; + +template +class meta_ptr { +public: + meta_ptr() = default; + explicit meta_ptr(const T* p) noexcept + : p_(ptrauth_sign_unauthenticated(p, ptrauth_key_cxx_vtable_pointer, + schema())) {} + meta_ptr(const meta_ptr& rhs) noexcept + : p_(ptrauth_auth_and_resign(rhs.p_, ptrauth_key_cxx_vtable_pointer, + rhs.schema(), ptrauth_key_cxx_vtable_pointer, + schema())) {} + meta_ptr& operator=(const meta_ptr& rhs) noexcept { + p_ = ptrauth_auth_and_resign(rhs.p_, ptrauth_key_cxx_vtable_pointer, + rhs.schema(), ptrauth_key_cxx_vtable_pointer, + schema()); + return *this; + } + meta_ptr& operator=(std::nullptr_t) noexcept { + p_ = nullptr; + return *this; + } + bool operator==(std::nullptr_t) const noexcept { return p_ == nullptr; } + const T& operator*() const noexcept { + return *ptrauth_auth_data(p_, ptrauth_key_cxx_vtable_pointer, schema()); + } + +private: + ptrauth_extra_data_t schema() const noexcept { + return ptrauth_blend_discriminator(&p_, ptrauth_type_discriminator(Disc)); + } + + const T* p_; +}; +#else +template +using code_ptr = O*; + +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; + template + constexpr explicit invoker_base(const F& f) : p_(f) {} + void reset() noexcept { p_ = nullptr; } + bool has_value() const noexcept { return p_ != nullptr; } + template + decltype(auto) operator()(Args&&... args) const { + return p_(std::forward(args)...); + } + +private: + code_ptr p_; +}; + +template +struct invoker; +#define PRO4D_DEF_INVOKER(oq, pq, ne, ...) \ + template \ + struct invoker \ + : invoker_base { \ + invoker() = default; \ + template \ + constexpr explicit invoker(std::in_place_type_t

) \ + : invoker_base( \ + [](ProP pq self, Args... args) ne -> R { \ + return reinterpret_invoke( \ + static_cast(self), std::forward(args)...); \ + }) {} \ + } +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(); + } + 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); + } +}; + +template +struct static_meta_storage { + static_meta_storage() = default; + 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(); + } + +private: + meta_ptr, void (*)(Ms...)> ptr_; + + template + static inline const inline_meta_storage 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 = typename detail::compact_meta_storage_traits::type; +}; + +struct flat_facade_meta_traits { + template + using invoker = detail::invoker; + + template + using storage = typename detail::flat_meta_storage_traits::type; +}; + +} // namespace pro::inline v4 + +#endif // MSFT_PROXY_V4_DETAIL_FACADE_META_TRAITS_H_ diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 9ecbd9ac..bad8dc9a 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -23,6 +23,7 @@ cc_test( "proxy_integration_tests.cpp", "proxy_invocation_tests.cpp", "proxy_lifetime_tests.cpp", + "proxy_pac_tests.cpp", "proxy_reflection_tests.cpp", "proxy_regression_tests.cpp", "proxy_rtti_tests.cpp", diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 49eafc2f..434789b7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable( proxy_integration_tests.cpp proxy_invocation_tests.cpp proxy_lifetime_tests.cpp + proxy_pac_tests.cpp proxy_reflection_tests.cpp proxy_regression_tests.cpp proxy_rtti_tests.cpp diff --git a/tests/meson.build b/tests/meson.build index df7f405e..7486537c 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -6,6 +6,7 @@ test_srcs = files( 'proxy_integration_tests.cpp', 'proxy_invocation_tests.cpp', 'proxy_lifetime_tests.cpp', + 'proxy_pac_tests.cpp', 'proxy_reflection_tests.cpp', 'proxy_regression_tests.cpp', 'proxy_rtti_tests.cpp', diff --git a/tests/proxy_invocation_tests.cpp b/tests/proxy_invocation_tests.cpp index 6c7b911b..16521bd7 100644 --- a/tests/proxy_invocation_tests.cpp +++ b/tests/proxy_invocation_tests.cpp @@ -218,9 +218,8 @@ TEST(ProxyInvocationTests, TestMultipleDispatches_Duplicated) { ::add_convention)> // ::build {}; - static_assert( - sizeof(pro::detail::facade_traits::meta) == - sizeof(pro::detail::facade_traits>::meta)); + static_assert(sizeof(pro::detail::meta_storage) == + sizeof(pro::detail::meta_storage>)); 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 new file mode 100644 index 00000000..53bf2058 --- /dev/null +++ b/tests/proxy_pac_tests.cpp @@ -0,0 +1,129 @@ +// Copyright (c) 2022-2026 Microsoft Corporation. +// Copyright (c) 2026-Present Next Gen C++ Foundation. +// Licensed under the MIT License. + +#include + +#ifdef PRO4D_HAS_PAC +#include +#include +#include +#include +#include +#include +#include +#include + +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>; + +template +auto GetRawBytes(const T& v) noexcept { + std::array result; + std::memcpy(result.data(), std::addressof(v), sizeof(T)); + return result; +} + +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))); + std::uintptr_t word; + std::memcpy(&word, target, sizeof(word)); + word ^= std::uintptr_t{1} << 54u; // Within the PAC bits for any VA size + std::memcpy(target, &word, sizeof(word)); +} + +template +void BitwiseCopy(pro::proxy& from, pro::proxy& to) { + assert(from.has_value() && !to.has_value()); + std::memcpy(static_cast(&to), static_cast(&from), + sizeof(from)); +} + +// No conventions and a nothrow destructor: a single signed destroy invoker +// lives inline in the proxy. +struct DefaultFacade : pro::facade_builder::build {}; +static_assert(IsInlineMetaPreferred); + +// Non-trivial copyability adds more metas than fit inline, so the proxy stores +// a signed pointer to out-of-line static meta storage. +struct CopyableFacade : pro::facade_builder // + ::support_copy // + ::build {}; +static_assert(!IsInlineMetaPreferred); + +} // namespace proxy_pac_tests_detail + +namespace detail = proxy_pac_tests_detail; + +TEST(ProxyPacTests, TestBinaryRepresentation_InlineMetaStorage) { + pro::proxy p1 = std::make_shared(123); + auto p1_data = detail::GetRawBytes(p1); + auto p2 = std::move(p1); + auto p2_data = detail::GetRawBytes(p2); + + // Pointer authentication signs the stored metadata with address diversity, so + // a genuine relocation re-signs it and the raw bytes of the signed meta + // change + EXPECT_NE(p1_data, p2_data); +} + +TEST(ProxyPacTests, TestBinaryRepresentation_StaticMetaStorage) { + pro::proxy p1 = std::make_shared(123); + auto p1_data = detail::GetRawBytes(p1); + auto p2 = p1; + auto p2_data = detail::GetRawBytes(p2); + + // Both inline metadata and static metadata has pointer authentication + EXPECT_NE(p1_data, p2_data); +} + +TEST(ProxyPacTests, TestBitFlipAttack_InlineMetaStorage) { + EXPECT_DEATH( + { + int a = 123; + pro::proxy p = &a; + detail::CorruptMeta(p); + }, + ""); +} + +TEST(ProxyPacTests, TestBitFlipAttack_StaticMetaStorage) { + EXPECT_DEATH( + { + int a = 123; + pro::proxy p = &a; + detail::CorruptMeta(p); + }, + ""); +} + +TEST(ProxyPacTests, TestRelocationAttack_InlineMetaStorage) { + EXPECT_DEATH( + { + int a = 123; + pro::proxy p1 = &a; + pro::proxy p2; + detail::BitwiseCopy(p1, p2); + }, + ""); +} + +TEST(ProxyPacTests, TestRelocationAttack_StaticMetaStorage) { + EXPECT_DEATH( + { + int a = 123; + pro::proxy p1 = &a; + pro::proxy p2; + detail::BitwiseCopy(p1, p2); + }, + ""); +} +#endif // PRO4D_HAS_PAC diff --git a/tests/proxy_traits_tests.cpp b/tests/proxy_traits_tests.cpp index b671fb1e..cc13c6ba 100644 --- a/tests/proxy_traits_tests.cpp +++ b/tests/proxy_traits_tests.cpp @@ -89,8 +89,7 @@ static_assert( static_assert(std::is_nothrow_constructible_v< pro::proxy, std::in_place_type_t, MockFunctionPtr>); -static_assert(sizeof(pro::proxy) == - 3 * sizeof(void*)); // VTABLE should be embeded +static_assert(sizeof(pro::proxy) == 3 * sizeof(void*)); static_assert(!std::is_copy_constructible_v>); static_assert(!std::is_copy_assignable_v>); @@ -170,8 +169,7 @@ static_assert(std::is_nothrow_constructible_v, MockFunctionPtr>); static_assert( std::is_nothrow_assignable_v, MockFunctionPtr>); -static_assert(sizeof(pro::proxy) == - 4 * sizeof(void*)); // VTABLE should be embeded +static_assert(sizeof(pro::proxy) == 3 * sizeof(void*)); struct CopyableSmallFacade : pro::facade_builder // @@ -203,8 +201,7 @@ static_assert( std::is_constructible_v, MockFunctionPtr>); static_assert( std::is_assignable_v, MockFunctionPtr>); -static_assert(sizeof(pro::proxy) == - 3 * sizeof(void*)); // VTABLE should be embeded +static_assert(sizeof(pro::proxy) == 2 * sizeof(void*)); struct TrivialFacade : pro::facade_builder // ::restrict_layout // @@ -212,14 +209,19 @@ struct TrivialFacade : pro::facade_builder // ::support_relocation // ::support_destruction // ::build {}; +#ifdef PRO4D_HAS_PAC +static_assert(std::is_nothrow_copy_constructible_v>); +static_assert(std::is_nothrow_copy_assignable_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_move_assignable_v>); +#else static_assert( std::is_trivially_copy_constructible_v>); static_assert(std::is_trivially_copy_assignable_v>); -static_assert(std::is_nothrow_move_constructible_v>); static_assert( std::is_trivially_move_constructible_v>); -static_assert(std::is_nothrow_move_assignable_v>); static_assert(std::is_trivially_move_assignable_v>); +#endif // PRO4D_HAS_PAC static_assert(std::is_trivially_destructible_v>); static_assert(!pro::proxiable); static_assert(!pro::proxiable); @@ -244,9 +246,7 @@ static_assert(std::is_nothrow_constructible_v, MockFunctionPtr>); static_assert( std::is_nothrow_assignable_v, MockFunctionPtr>); -static_assert(sizeof(pro::proxy) == - 2 * sizeof(void*)); // VTABLE should be eliminated, but a - // placeholder is required +static_assert(sizeof(pro::proxy) == 2 * sizeof(void*)); struct ReflectionOfSmallPtr { template diff --git a/tests/proxy_view_tests.cpp b/tests/proxy_view_tests.cpp index 69875994..17148b27 100644 --- a/tests/proxy_view_tests.cpp +++ b/tests/proxy_view_tests.cpp @@ -30,14 +30,19 @@ static_assert( SupportsToString>())>); static_assert(sizeof(pro::proxy) == 3 * sizeof(void*)); +#ifdef PRO4D_HAS_PAC +static_assert( + std::is_nothrow_copy_constructible_v>); +#else static_assert( std::is_trivially_copy_constructible_v>); +#endif // PRO4D_HAS_PAC static_assert(std::is_trivially_destructible_v>); static_assert(SupportsIntPlusEqual< decltype(*std::declval>())>); static_assert( SupportsToString>())>); -static_assert(sizeof(pro::proxy_view) == 3 * sizeof(void*)); +static_assert(sizeof(pro::proxy_view) == 2 * sizeof(void*)); static_assert(std::is_nothrow_convertible_v&, pro::proxy_view>); diff --git a/tools/report_generator/report-config.json b/tools/report_generator/report-config.json index f9023c04..c04d2240 100644 --- a/tools/report_generator/report-config.json +++ b/tools/report_generator/report-config.json @@ -23,6 +23,11 @@ "InfoPath": "artifacts/drop-appleclang/env-info.json", "BenchmarkingResultsPath": "artifacts/drop-appleclang/benchmarking-results.json" }, + { + "Description": "AppleClang on macOS (arm64e with PAC)", + "InfoPath": "artifacts/drop-apple-pac/env-info.json", + "BenchmarkingResultsPath": "artifacts/drop-apple-pac/benchmarking-results.json" + }, { "Description": "NVIDIA HPC on Ubuntu", "InfoPath": "artifacts/drop-nvhpc/env-info.json",