Skip to content

Conversation

pcc
Copy link
Contributor

@pcc pcc commented Aug 1, 2025

Pointer field protection has the following characteristics:

  • Pointer fields in RTTI types are unsigned. Signing these fields
    is unnecessary because PFP is a mechanism for protecting the heap,
    and the RTTI objects typically live in global variables. Therefore,
    mark the fields with the no_field_protection to inhibit PFP for these
    fields.

  • libcxx's interim trivial relocatability implementation, which memcpy's
    some non-trivially-copyable objects according to the value of a
    trait, conflicts with PFP by causing such copies to result in a
    crash in some cases where the object being copied includes a pointer
    field. These copies are undefined behavior and in C++26 they must
    be made using std::trivially_relocate which is not yet implemented,
    together with using a different predicate for the trait. Therefore,
    make libcxx's trivially relocatable predicate simply return the value
    of the trivially copyable trait when PFP is enabled, which results in
    standards compliant behavior that is accommodated by PFP.

  • Pointer field protection is an ABI break, so adjust the default
    namespace name when it is enabled.

Created using spr 1.3.6-beta.1
@pcc pcc requested review from a team as code owners August 1, 2025 06:18
@llvmbot llvmbot added libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. libc++abi libc++abi C++ Runtime Library. Not libc++. labels Aug 1, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 1, 2025

@llvm/pr-subscribers-libcxx

@llvm/pr-subscribers-libcxxabi

Author: Peter Collingbourne (pcc)

Changes

Pointer field protection has the following characteristics:

  • Pointer fields in RTTI types are unsigned. Signing these fields
    is unnecessary because PFP is a mechanism for protecting the heap,
    and the RTTI objects typically live in global variables. Therefore,
    mark the fields with the no_field_protection to inhibit PFP for these
    fields.

  • libcxx's interim trivial relocatability implementation, which memcpy's
    some non-trivially-copyable objects according to the value of a
    trait, conflicts with PFP by causing such copies to result in a
    crash in some cases where the object being copied includes a pointer
    field. These copies are undefined behavior and in C++26 they must
    be made using std::trivially_relocate which is not yet implemented,
    together with using a different predicate for the trait. Therefore,
    make libcxx's trivially relocatable predicate simply return the value
    of the trivially copyable trait when PFP is enabled, which results in
    standards compliant behavior that is accommodated by PFP.

  • Pointer field protection is an ABI break, so adjust the ABI tag when
    it is enabled.


Full diff: https://github.com/llvm/llvm-project/pull/151651.diff

7 Files Affected:

  • (modified) libcxx/include/__config (+20-1)
  • (modified) libcxx/include/__type_traits/is_trivially_relocatable.h (+3)
  • (modified) libcxx/include/typeinfo (+1-1)
  • (modified) libcxx/test/libcxx/gdb/gdb_pretty_printer_test.sh.cpp (+9)
  • (modified) libcxx/test/libcxx/type_traits/is_trivially_relocatable.compile.pass.cpp (+37-31)
  • (modified) libcxxabi/include/__cxxabi_config.h (+10)
  • (modified) libcxxabi/src/private_typeinfo.h (+3-3)
diff --git a/libcxx/include/__config b/libcxx/include/__config
index 77a71b6cf1cae..60bbcb8fa61ed 100644
--- a/libcxx/include/__config
+++ b/libcxx/include/__config
@@ -484,8 +484,21 @@ typedef __char32_t char32_t;
 #    define _LIBCPP_EXCEPTIONS_SIG e
 #  endif
 
+#  if !_LIBCPP_HAS_EXCEPTIONS
+#    define _LIBCPP_EXCEPTIONS_SIG n
+#  else
+#    define _LIBCPP_EXCEPTIONS_SIG e
+#  endif
+
+#  if __has_feature(pointer_field_protection)
+#    define _LIBCPP_PFP_SIG p
+#  else
+#    define _LIBCPP_PFP_SIG
+#  endif
+
 #  define _LIBCPP_ODR_SIGNATURE                                                                                        \
-    _LIBCPP_CONCAT(_LIBCPP_CONCAT(_LIBCPP_HARDENING_SIG, _LIBCPP_EXCEPTIONS_SIG), _LIBCPP_VERSION)
+    _LIBCPP_CONCAT(_LIBCPP_CONCAT(_LIBCPP_CONCAT(_LIBCPP_HARDENING_SIG, _LIBCPP_EXCEPTIONS_SIG), _LIBCPP_PFP_SIG),     \
+                   _LIBCPP_VERSION)
 
 // This macro marks a symbol as being hidden from libc++'s ABI. This is achieved
 // on two levels:
@@ -1262,6 +1275,12 @@ typedef __char32_t char32_t;
 #    define _LIBCPP_HAS_EXPLICIT_THIS_PARAMETER 0
 #  endif
 
+#  if __has_feature(pointer_field_protection)
+#    define _LIBCPP_NO_PFP [[clang::no_field_protection]]
+#  else
+#    define _LIBCPP_NO_PFP
+#  endif
+
 #endif // __cplusplus
 
 #endif // _LIBCPP___CONFIG
diff --git a/libcxx/include/__type_traits/is_trivially_relocatable.h b/libcxx/include/__type_traits/is_trivially_relocatable.h
index 9b0e240de55f4..dc8b0e23360fd 100644
--- a/libcxx/include/__type_traits/is_trivially_relocatable.h
+++ b/libcxx/include/__type_traits/is_trivially_relocatable.h
@@ -34,10 +34,13 @@ template <class _Tp, class = void>
 struct __libcpp_is_trivially_relocatable : is_trivially_copyable<_Tp> {};
 #endif
 
+// __trivially_relocatable on libc++'s builtin types does not currently return the right answer with PFP.
+#if !__has_feature(pointer_field_protection)
 template <class _Tp>
 struct __libcpp_is_trivially_relocatable<_Tp,
                                          __enable_if_t<is_same<_Tp, typename _Tp::__trivially_relocatable>::value> >
     : true_type {};
+#endif
 
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/include/typeinfo b/libcxx/include/typeinfo
index 24aaabf0a87df..a1ee4155bcb3d 100644
--- a/libcxx/include/typeinfo
+++ b/libcxx/include/typeinfo
@@ -300,7 +300,7 @@ class _LIBCPP_EXPORTED_FROM_ABI _LIBCPP_TYPE_INFO_VTABLE_POINTER_AUTH type_info
 protected:
   typedef __type_info_implementations::__impl __impl;
 
-  __impl::__type_name_t __type_name;
+  _LIBCPP_NO_PFP __impl::__type_name_t __type_name;
 
   _LIBCPP_HIDE_FROM_ABI explicit type_info(const char* __n) : __type_name(__impl::__string_to_type_name(__n)) {}
 
diff --git a/libcxx/test/libcxx/gdb/gdb_pretty_printer_test.sh.cpp b/libcxx/test/libcxx/gdb/gdb_pretty_printer_test.sh.cpp
index f125cc9adc491..d84914999b8fa 100644
--- a/libcxx/test/libcxx/gdb/gdb_pretty_printer_test.sh.cpp
+++ b/libcxx/test/libcxx/gdb/gdb_pretty_printer_test.sh.cpp
@@ -256,9 +256,12 @@ void unique_ptr_test() {
   ComparePrettyPrintToRegex(std::move(forty_two),
       R"(std::unique_ptr<int> containing = {__ptr_ = 0x[a-f0-9]+})");
 
+#if !__has_feature(pointer_field_protection)
+  // GDB doesn't know how to read PFP fields correctly yet.
   std::unique_ptr<int> this_is_null;
   ComparePrettyPrintToChars(std::move(this_is_null),
       R"(std::unique_ptr is nullptr)");
+#endif
 }
 
 void bitset_test() {
@@ -476,10 +479,13 @@ void vector_test() {
                             "std::vector of length "
                             "3, capacity 3 = {5, 6, 7}");
 
+#if !__has_feature(pointer_field_protection)
+  // GDB doesn't know how to read PFP fields correctly yet.
   std::vector<int, UncompressibleAllocator<int>> test3({7, 8});
   ComparePrettyPrintToChars(std::move(test3),
                             "std::vector of length "
                             "2, capacity 2 = {7, 8}");
+#endif
 }
 
 void set_iterator_test() {
@@ -650,8 +656,11 @@ void shared_ptr_test() {
       test0,
       R"(std::shared_ptr<int> count [3\?], weak [0\?]( \(libc\+\+ missing debug info\))? containing = {__ptr_ = 0x[a-f0-9]+})");
 
+#if !__has_feature(pointer_field_protection)
+  // GDB doesn't know how to read PFP fields correctly yet.
   std::shared_ptr<const int> test3;
   ComparePrettyPrintToChars(test3, "std::shared_ptr is nullptr");
+#endif
 }
 
 void streampos_test() {
diff --git a/libcxx/test/libcxx/type_traits/is_trivially_relocatable.compile.pass.cpp b/libcxx/test/libcxx/type_traits/is_trivially_relocatable.compile.pass.cpp
index 8066925f2900a..3b9d7e1776a73 100644
--- a/libcxx/test/libcxx/type_traits/is_trivially_relocatable.compile.pass.cpp
+++ b/libcxx/test/libcxx/type_traits/is_trivially_relocatable.compile.pass.cpp
@@ -28,6 +28,12 @@
 #  include <locale>
 #endif
 
+#if __has_feature(pointer_field_protection)
+constexpr bool pfp_disabled = false;
+#else
+constexpr bool pfp_disabled = true;
+#endif
+
 static_assert(std::__libcpp_is_trivially_relocatable<char>::value, "");
 static_assert(std::__libcpp_is_trivially_relocatable<int>::value, "");
 static_assert(std::__libcpp_is_trivially_relocatable<double>::value, "");
@@ -70,8 +76,8 @@ static_assert(!std::__libcpp_is_trivially_relocatable<NonTrivialDestructor>::val
 // ----------------------
 
 // __split_buffer
-static_assert(std::__libcpp_is_trivially_relocatable<std::__split_buffer<int> >::value, "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::__split_buffer<NotTriviallyCopyable> >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::__split_buffer<int> >::value == pfp_disabled, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::__split_buffer<NotTriviallyCopyable> >::value == pfp_disabled, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::__split_buffer<int, test_allocator<int> > >::value, "");
 
 // standard library types
@@ -84,7 +90,7 @@ static_assert(std::__libcpp_is_trivially_relocatable<std::array<std::unique_ptr<
 
 static_assert(std::__libcpp_is_trivially_relocatable<std::array<int, 1> >::value, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::array<NotTriviallyCopyable, 1> >::value, "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::array<std::unique_ptr<int>, 1> >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::array<std::unique_ptr<int>, 1> >::value == pfp_disabled, "");
 
 // basic_string
 #if !__has_feature(address_sanitizer) || !_LIBCPP_INSTRUMENTED_WITH_ASAN
@@ -99,17 +105,17 @@ struct NotTriviallyRelocatableCharTraits : constexpr_char_traits<T> {
 };
 
 static_assert(std::__libcpp_is_trivially_relocatable<
-                  std::basic_string<char, std::char_traits<char>, std::allocator<char> > >::value,
+                  std::basic_string<char, std::char_traits<char>, std::allocator<char> > >::value == pfp_disabled,
               "");
 static_assert(std::__libcpp_is_trivially_relocatable<
-                  std::basic_string<char, NotTriviallyRelocatableCharTraits<char>, std::allocator<char> > >::value,
+                  std::basic_string<char, NotTriviallyRelocatableCharTraits<char>, std::allocator<char> > >::value == pfp_disabled,
               "");
 static_assert(std::__libcpp_is_trivially_relocatable<
-                  std::basic_string<MyChar, constexpr_char_traits<MyChar>, std::allocator<MyChar> > >::value,
+                  std::basic_string<MyChar, constexpr_char_traits<MyChar>, std::allocator<MyChar> > >::value == pfp_disabled,
               "");
 static_assert(
     std::__libcpp_is_trivially_relocatable<
-        std::basic_string<MyChar, NotTriviallyRelocatableCharTraits<MyChar>, std::allocator<MyChar> > >::value,
+        std::basic_string<MyChar, NotTriviallyRelocatableCharTraits<MyChar>, std::allocator<MyChar> > >::value == pfp_disabled,
     "");
 static_assert(!std::__libcpp_is_trivially_relocatable<
                   std::basic_string<char, std::char_traits<char>, test_allocator<char> > >::value,
@@ -121,21 +127,21 @@ static_assert(
 #endif
 
 // deque
-static_assert(std::__libcpp_is_trivially_relocatable<std::deque<int> >::value, "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::deque<NotTriviallyCopyable> >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::deque<int> >::value == pfp_disabled, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::deque<NotTriviallyCopyable> >::value == pfp_disabled, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::deque<int, test_allocator<int> > >::value, "");
 
 // exception_ptr
 #ifndef _LIBCPP_ABI_MICROSOFT // FIXME: Is this also the case on windows?
-static_assert(std::__libcpp_is_trivially_relocatable<std::exception_ptr>::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::exception_ptr>::value == pfp_disabled, "");
 #endif
 
 // expected
 #if TEST_STD_VER >= 23
-static_assert(std::__libcpp_is_trivially_relocatable<std::expected<int, int> >::value);
-static_assert(std::__libcpp_is_trivially_relocatable<std::expected<std::unique_ptr<int>, int>>::value);
-static_assert(std::__libcpp_is_trivially_relocatable<std::expected<int, std::unique_ptr<int>>>::value);
-static_assert(std::__libcpp_is_trivially_relocatable<std::expected<std::unique_ptr<int>, std::unique_ptr<int>>>::value);
+static_assert(std::__libcpp_is_trivially_relocatable<std::expected<int, int> >::value == pfp_disabled);
+static_assert(std::__libcpp_is_trivially_relocatable<std::expected<std::unique_ptr<int>, int>>::value == pfp_disabled);
+static_assert(std::__libcpp_is_trivially_relocatable<std::expected<int, std::unique_ptr<int>>>::value == pfp_disabled);
+static_assert(std::__libcpp_is_trivially_relocatable<std::expected<std::unique_ptr<int>, std::unique_ptr<int>>>::value == pfp_disabled);
 
 static_assert(!std::__libcpp_is_trivially_relocatable<std::expected<int, NotTriviallyCopyable>>::value);
 static_assert(!std::__libcpp_is_trivially_relocatable<std::expected<NotTriviallyCopyable, int>>::value);
@@ -145,42 +151,42 @@ static_assert(
 
 // locale
 #ifndef TEST_HAS_NO_LOCALIZATION
-static_assert(std::__libcpp_is_trivially_relocatable<std::locale>::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::locale>::value == pfp_disabled, "");
 #endif
 
 // optional
 #if TEST_STD_VER >= 17
 static_assert(std::__libcpp_is_trivially_relocatable<std::optional<int>>::value, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::optional<NotTriviallyCopyable>>::value, "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::optional<std::unique_ptr<int>>>::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::optional<std::unique_ptr<int>>>::value == pfp_disabled, "");
 #endif // TEST_STD_VER >= 17
 
 // pair
-static_assert(std::__libcpp_is_trivially_relocatable<std::pair<int, int> >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::pair<int, int> >::value == pfp_disabled, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::pair<NotTriviallyCopyable, int> >::value, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::pair<int, NotTriviallyCopyable> >::value, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::pair<NotTriviallyCopyable, NotTriviallyCopyable> >::value,
               "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::pair<std::unique_ptr<int>, std::unique_ptr<int> > >::value,
+static_assert(std::__libcpp_is_trivially_relocatable<std::pair<std::unique_ptr<int>, std::unique_ptr<int> > >::value == pfp_disabled,
               "");
 
 // shared_ptr
-static_assert(std::__libcpp_is_trivially_relocatable<std::shared_ptr<NotTriviallyCopyable> >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::shared_ptr<NotTriviallyCopyable> >::value == pfp_disabled, "");
 
 // tuple
 #if TEST_STD_VER >= 11
 static_assert(std::__libcpp_is_trivially_relocatable<std::tuple<> >::value, "");
 
-static_assert(std::__libcpp_is_trivially_relocatable<std::tuple<int> >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::tuple<int> >::value == pfp_disabled, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::tuple<NotTriviallyCopyable> >::value, "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::tuple<std::unique_ptr<int> > >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::tuple<std::unique_ptr<int> > >::value == pfp_disabled, "");
 
-static_assert(std::__libcpp_is_trivially_relocatable<std::tuple<int, int> >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::tuple<int, int> >::value == pfp_disabled, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::tuple<NotTriviallyCopyable, int> >::value, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::tuple<int, NotTriviallyCopyable> >::value, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::tuple<NotTriviallyCopyable, NotTriviallyCopyable> >::value,
               "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::tuple<std::unique_ptr<int>, std::unique_ptr<int> > >::value,
+static_assert(std::__libcpp_is_trivially_relocatable<std::tuple<std::unique_ptr<int>, std::unique_ptr<int> > >::value == pfp_disabled,
               "");
 #endif // TEST_STD_VER >= 11
 
@@ -205,9 +211,9 @@ struct NotTriviallyRelocatablePointer {
   void operator()(T*);
 };
 
-static_assert(std::__libcpp_is_trivially_relocatable<std::unique_ptr<int> >::value, "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::unique_ptr<NotTriviallyCopyable> >::value, "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::unique_ptr<int[]> >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::unique_ptr<int> >::value == pfp_disabled, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::unique_ptr<NotTriviallyCopyable> >::value == pfp_disabled, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::unique_ptr<int[]> >::value == pfp_disabled, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::unique_ptr<int, NotTriviallyRelocatableDeleter> >::value,
               "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::unique_ptr<int[], NotTriviallyRelocatableDeleter> >::value,
@@ -221,23 +227,23 @@ static_assert(!std::__libcpp_is_trivially_relocatable<std::unique_ptr<int[], Not
 #if TEST_STD_VER >= 17
 static_assert(std::__libcpp_is_trivially_relocatable<std::variant<int> >::value, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::variant<NotTriviallyCopyable> >::value, "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::variant<std::unique_ptr<int> > >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::variant<std::unique_ptr<int> > >::value == pfp_disabled, "");
 
 static_assert(std::__libcpp_is_trivially_relocatable<std::variant<int, int> >::value, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::variant<NotTriviallyCopyable, int> >::value, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::variant<int, NotTriviallyCopyable> >::value, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::variant<NotTriviallyCopyable, NotTriviallyCopyable> >::value,
               "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::variant<std::unique_ptr<int>, std::unique_ptr<int> > >::value,
+static_assert(std::__libcpp_is_trivially_relocatable<std::variant<std::unique_ptr<int>, std::unique_ptr<int> > >::value == pfp_disabled,
               "");
 #endif // TEST_STD_VER >= 17
 
 // vector
-static_assert(std::__libcpp_is_trivially_relocatable<std::vector<int> >::value, "");
-static_assert(std::__libcpp_is_trivially_relocatable<std::vector<NotTriviallyCopyable> >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::vector<int> >::value == pfp_disabled, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::vector<NotTriviallyCopyable> >::value == pfp_disabled, "");
 static_assert(!std::__libcpp_is_trivially_relocatable<std::vector<int, test_allocator<int> > >::value, "");
 
 // weak_ptr
-static_assert(std::__libcpp_is_trivially_relocatable<std::weak_ptr<NotTriviallyCopyable> >::value, "");
+static_assert(std::__libcpp_is_trivially_relocatable<std::weak_ptr<NotTriviallyCopyable> >::value == pfp_disabled, "");
 
 // TODO: Mark all the trivially relocatable STL types as such
diff --git a/libcxxabi/include/__cxxabi_config.h b/libcxxabi/include/__cxxabi_config.h
index 759445dac91f9..00e1357cdab85 100644
--- a/libcxxabi/include/__cxxabi_config.h
+++ b/libcxxabi/include/__cxxabi_config.h
@@ -109,4 +109,14 @@
 #  define _LIBCXXABI_NOEXCEPT noexcept
 #endif
 
+#if defined(_LIBCXXABI_COMPILER_CLANG)
+#  if __has_feature(pointer_field_protection)
+#    define _LIBCXXABI_NO_PFP [[clang::no_field_protection]]
+#  else
+#    define _LIBCXXABI_NO_PFP
+#  endif
+#else
+#  define _LIBCXXABI_NO_PFP
+#endif
+
 #endif // ____CXXABI_CONFIG_H
diff --git a/libcxxabi/src/private_typeinfo.h b/libcxxabi/src/private_typeinfo.h
index 328a02edef5c1..a3bc0bffd41bc 100644
--- a/libcxxabi/src/private_typeinfo.h
+++ b/libcxxabi/src/private_typeinfo.h
@@ -145,7 +145,7 @@ class _LIBCXXABI_TYPE_VIS __class_type_info : public __shim_type_info {
 // Has one non-virtual public base class at offset zero
 class _LIBCXXABI_TYPE_VIS __si_class_type_info : public __class_type_info {
 public:
-  const __class_type_info *__base_type;
+  _LIBCXXABI_NO_PFP const __class_type_info *__base_type;
 
   _LIBCXXABI_HIDDEN virtual ~__si_class_type_info();
 
@@ -204,7 +204,7 @@ class _LIBCXXABI_TYPE_VIS __vmi_class_type_info : public __class_type_info {
 class _LIBCXXABI_TYPE_VIS __pbase_type_info : public __shim_type_info {
 public:
   unsigned int __flags;
-  const __shim_type_info *__pointee;
+  _LIBCXXABI_NO_PFP const __shim_type_info *__pointee;
 
   enum __masks {
     __const_mask = 0x1,
@@ -245,7 +245,7 @@ class _LIBCXXABI_TYPE_VIS __pointer_type_info : public __pbase_type_info {
 class _LIBCXXABI_TYPE_VIS __pointer_to_member_type_info
     : public __pbase_type_info {
 public:
-  const __class_type_info *__context;
+  _LIBCXXABI_NO_PFP const __class_type_info *__context;
 
   _LIBCXXABI_HIDDEN virtual ~__pointer_to_member_type_info();
   _LIBCXXABI_HIDDEN virtual bool can_catch(const __shim_type_info *,

Copy link

github-actions bot commented Aug 1, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff origin/main HEAD --extensions ,h -- libcxx/include/__config libcxx/include/__type_traits/is_trivially_relocatable.h libcxx/include/typeinfo libcxxabi/include/__cxxabi_config.h libcxxabi/src/private_typeinfo.h

⚠️
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing origin/main to the base branch/commit you want to compare against.
⚠️

View the diff from clang-format here.
diff --git a/libcxxabi/src/private_typeinfo.h b/libcxxabi/src/private_typeinfo.h
index a3bc0bffd..c472bf076 100644
--- a/libcxxabi/src/private_typeinfo.h
+++ b/libcxxabi/src/private_typeinfo.h
@@ -145,7 +145,7 @@ public:
 // Has one non-virtual public base class at offset zero
 class _LIBCXXABI_TYPE_VIS __si_class_type_info : public __class_type_info {
 public:
-  _LIBCXXABI_NO_PFP const __class_type_info *__base_type;
+  _LIBCXXABI_NO_PFP const __class_type_info* __base_type;
 
   _LIBCXXABI_HIDDEN virtual ~__si_class_type_info();
 
@@ -204,7 +204,7 @@ public:
 class _LIBCXXABI_TYPE_VIS __pbase_type_info : public __shim_type_info {
 public:
   unsigned int __flags;
-  _LIBCXXABI_NO_PFP const __shim_type_info *__pointee;
+  _LIBCXXABI_NO_PFP const __shim_type_info* __pointee;
 
   enum __masks {
     __const_mask = 0x1,
@@ -245,7 +245,7 @@ public:
 class _LIBCXXABI_TYPE_VIS __pointer_to_member_type_info
     : public __pbase_type_info {
 public:
-  _LIBCXXABI_NO_PFP const __class_type_info *__context;
+  _LIBCXXABI_NO_PFP const __class_type_info* __context;
 
   _LIBCXXABI_HIDDEN virtual ~__pointer_to_member_type_info();
   _LIBCXXABI_HIDDEN virtual bool can_catch(const __shim_type_info *,

pcc added a commit to pcc/llvm-project that referenced this pull request Aug 1, 2025
Pointer field protection has the following characteristics:

- Pointer fields in RTTI types are unsigned. Signing these fields
  is unnecessary because PFP is a mechanism for protecting the heap,
  and the RTTI objects typically live in global variables. Therefore,
  mark the fields with the no_field_protection to inhibit PFP for these
  fields.

- libcxx's interim trivial relocatability implementation, which memcpy's
  some non-trivially-copyable objects according to the value of a
  trait, conflicts with PFP by causing such copies to result in a
  crash in some cases where the object being copied includes a pointer
  field. These copies are undefined behavior and in C++26 they must
  be made using std::trivially_relocate which is not yet implemented,
  together with using a different predicate for the trait. Therefore,
  make libcxx's trivially relocatable predicate simply return the value
  of the trivially copyable trait when PFP is enabled, which results in
  standards compliant behavior that is accommodated by PFP.

- Pointer field protection is an ABI break, so adjust the ABI tag when
  it is enabled.

Pull Request: llvm#151651
Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires and RFC, and, more importantly, CI coverage.

Created using spr 1.3.6-beta.1
@pcc
Copy link
Contributor Author

pcc commented Aug 4, 2025

Created using spr 1.3.6-beta.1
Comment on lines 493 to 497
# if defined(__POINTER_FIELD_PROTECTION__)
# define _LIBCPP_PFP_SIG p
# else
# define _LIBCPP_PFP_SIG
# endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that pfp changes the layout of certain types? Why should there be an ABI tag for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the in-memory pointer format changes so it's effectively a layout (ABI) change. Therefore we need an ABI tag change to detect/prevent linking against mismatching ABIs. This was requested by @mordante in #133538.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's only for ceses where we want different TUs to link together. Since PFP changes the layout of types that's not something we want users to do, which in turn means that we don't want to make it visible in the ABI tag. We should instead change the whole inline namespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I removed this logic and added CMake logic to modify the namespace name if PFP is enabled.

Comment on lines 1279 to 1280
# define _LIBCPP_PFP [[clang::pointer_field_protection]]
# define _LIBCPP_NO_PFP [[clang::no_field_protection]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be _Uglified. Do these attributes do anything with pfp disabled? If no, why not simply check for their availability like with other attributes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[[clang::pointer_field_protection]] will enable PFP on a type even if the command line flag is not passed, so we should avoid it when PFP is not enabled on the command line to avoid ABI breaks.

Checking attribute availability for [[clang::no_field_protection]] sounds fine to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 259 to 260
#if !defined(__POINTER_FIELD_PROTECTION__)
// GDB doesn't know how to read PFP fields correctly yet.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does GCC have pfp in general? If not, IMO we should just disable the pretty printer test with pfp enabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The support for this feature in GCC is independent of support in GDB. We could imagine debug info extensions being developed in the future to make it possible for this to pass in GDB even without GCC support.

That being said, disabling the test is also fine with me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 29 to 33
#if defined(__POINTER_FIELD_PROTECTION__)
constexpr bool pfp_disabled = false;
#else
constexpr bool pfp_disabled = true;
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, can we just disable the test instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine with me I suppose. The correct result for __libcpp_is_trivially_relocatable is implicitly tested by the other tests (which would crash if it was wrong).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 112 to 120
#if defined(_LIBCXXABI_COMPILER_CLANG)
# if defined(__POINTER_FIELD_PROTECTION__)
# define _LIBCXXABI_NO_PFP [[clang::no_field_protection]]
# else
# define _LIBCXXABI_NO_PFP
# endif
#else
# define _LIBCXXABI_NO_PFP
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this different from the libc++ version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to change it to be the same. I'll do that. This was a leftover from when __POINTER_FIELD_PROTECTION__ was under __has_extension.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

pcc added 3 commits September 10, 2025 18:03
Created using spr 1.3.6-beta.1
Created using spr 1.3.6-beta.1
Created using spr 1.3.6-beta.1
Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, I feel like PFP needs its own __config_site instead of the ad-hoc annotations all around. @ldionne any thoughts?

Created using spr 1.3.6-beta.1
Copy link
Contributor Author

@pcc pcc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, I feel like PFP needs its own __config_site instead of the ad-hoc annotations all around. @ldionne any thoughts?

Not sure exactly what you had in mind. Would this be another macro like _LIBCPP_HARDENING_MODE_DEFAULT? I guess __config_site is necessary for default hardening mode because it's only specified via config options, but with PFP the compiler "knows" whether it is enabled via the builtin macro so we can just test for the macro in __config.

Comment on lines 493 to 497
# if defined(__POINTER_FIELD_PROTECTION__)
# define _LIBCPP_PFP_SIG p
# else
# define _LIBCPP_PFP_SIG
# endif
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I removed this logic and added CMake logic to modify the namespace name if PFP is enabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libc++abi libc++abi C++ Runtime Library. Not libc++. libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants