Skip to content

Conversation

@pcc
Copy link
Contributor

@pcc pcc commented Dec 13, 2025

Pointer field protection is a use-after-free vulnerability
mitigation that works by changing how data structures' pointer
fields are stored in memory. For more information, see the RFC:
https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555

Created using spr 1.3.6-beta.1
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. labels Dec 13, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 13, 2025

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Peter Collingbourne (pcc)

Changes

Pointer field protection is a use-after-free vulnerability
mitigation that works by changing how data structures' pointer
fields are stored in memory. For more information, see the RFC:
https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555


Patch is 92.42 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/172119.diff

40 Files Affected:

  • (added) clang/docs/StructureProtection.rst (+81)
  • (modified) clang/docs/index.rst (+1)
  • (modified) clang/include/clang/AST/ASTContext.h (+22)
  • (modified) clang/include/clang/AST/CXXRecordDeclDefinitionBits.def (+6)
  • (modified) clang/include/clang/AST/DeclCXX.h (+6)
  • (modified) clang/include/clang/Basic/Attr.td (+13)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/include/clang/Basic/LangOptions.def (+7)
  • (modified) clang/include/clang/Options/Options.td (+16)
  • (modified) clang/lib/AST/ASTContext.cpp (+88)
  • (modified) clang/lib/AST/DeclCXX.cpp (+17-3)
  • (modified) clang/lib/AST/ExprConstant.cpp (+1)
  • (modified) clang/lib/AST/TypePrinter.cpp (+6)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+39-7)
  • (modified) clang/lib/CodeGen/CGCall.cpp (+149-12)
  • (modified) clang/lib/CodeGen/CGClass.cpp (+21-6)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+15-4)
  • (modified) clang/lib/CodeGen/CGExprAgg.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+39-1)
  • (modified) clang/lib/CodeGen/CodeGenFunction.cpp (+67-5)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+10-2)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+40)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+7)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+1)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+1)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+18)
  • (modified) clang/lib/Frontend/InitPreprocessor.cpp (+5)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+15)
  • (modified) clang/lib/Sema/SemaTypeTraits.cpp (+21)
  • (added) clang/test/CodeGenCXX/pfp-attribute-disable.cpp (+33)
  • (added) clang/test/CodeGenCXX/pfp-coerce.cpp (+245)
  • (added) clang/test/CodeGenCXX/pfp-load-store.cpp (+40)
  • (added) clang/test/CodeGenCXX/pfp-member-pointer-offsetof.cpp (+14)
  • (added) clang/test/CodeGenCXX/pfp-memcpy.cpp (+59)
  • (added) clang/test/CodeGenCXX/pfp-null-init.cpp (+21)
  • (added) clang/test/CodeGenCXX/pfp-struct-gep.cpp (+36)
  • (added) clang/test/CodeGenCXX/pfp-trivially-relocatable.cpp (+101)
  • (modified) clang/test/Misc/pragma-attribute-supported-attributes-list.test (+2)
  • (added) clang/test/Preprocessor/pfp-predefines.c (+5)
  • (added) clang/test/SemaCXX/attr-pointer-field-protection.cpp (+6)
diff --git a/clang/docs/StructureProtection.rst b/clang/docs/StructureProtection.rst
new file mode 100644
index 0000000000000..5acbdd322cd0a
--- /dev/null
+++ b/clang/docs/StructureProtection.rst
@@ -0,0 +1,81 @@
+====================
+Structure Protection
+====================
+
+.. contents::
+   :local:
+
+
+Introduction
+============
+
+Structure protection is an *experimental* mitigation
+against use-after-free vulnerabilities. For
+more information, please see the original `RFC
+<https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555>`_.
+An independent set of documentation will be contributed when the feature
+is promoted to stable.
+
+Usage
+=====
+
+To use structure protection, build your program using one or more of these flags:
+
+- ``-fexperimental-allow-pointer-field-protection-attr``: Makes the
+  ``[[clang::pointer_field_protection]]`` attribute described below
+  available for use in code. Without this flag, use of the attribute will
+  cause an error. This flag acts as a guard against use of the feature
+  before it is stabilized. When the feature is stabilized, the intent
+  is that this flag will become a no-op and the attribute will always
+  be available.
+
+- ``-fexperimental-pointer-field-protection-abi``: Enable pointer
+  field protection on all types that are not considered standard-layout
+  according to the C++ rules for standard layout. Because this
+  flag changes the C++ ABI, we refer to this as the pointer
+  field protection ABI. Specifying this flag also defines the
+  predefined macro ``__POINTER_FIELD_PROTECTION_ABI__``. Implies
+  ``-fexperimental-allow-pointer-field-protection-attr``.
+
+- ``-fexperimental-pointer-field-protection-tagged``: On architectures
+  that support it (currently only AArch64), for types that are not
+  considered trivially copyable, use the address of the object to compute
+  the pointer encoding. Specifying this flag also defines the predefined
+  macro ``__POINTER_FIELD_PROTECTION_TAGGED__``.
+
+It is also possible to specify the attribute
+``[[clang::pointer_field_protection]]`` on a struct type to opt the
+struct's pointer fields into pointer field protection, even if the type is
+standard layout or none of the command line flags are specified. Note that
+this means that the type will not comply with pointer interconvertibility
+and other standard layout rules.
+
+Pointer field protection is inherited from bases and non-static data
+members.
+
+In order to avoid ABI breakage, the entire C++ part
+of the program must be built with a consistent set of
+``-fexperimental-pointer-field-protection*`` flags, and the C++ standard
+library must also be built with the same flags and statically linked
+into the program.
+
+To build libc++ with pointer field protection support, pass the following
+CMake flags:
+
+.. code-block:: console
+
+    "-DRUNTIMES_${triple}_LIBCXXABI_ENABLE_SHARED=OFF" \
+    "-DRUNTIMES_${triple}_LIBCXX_USE_COMPILER_RT=ON" \
+    "-DRUNTIMES_${triple}_LIBCXX_PFP=untagged" \
+    "-DRUNTIMES_${triple}_LIBCXX_ENABLE_SHARED=OFF" \
+    "-DRUNTIMES_${triple}_LIBCXX_TEST_CONFIG=llvm-libc++-static.cfg.in" \
+    "-DRUNTIMES_${triple}_LIBUNWIND_ENABLE_SHARED=OFF" \
+
+where ``${triple}`` is your target triple, such as
+``aarch64-unknown-linux``.
+
+The resulting toolchain may then be used to build programs
+with pointer field protection by passing ``-stdlib=libc++
+-fexperimental-pointer-field-protection-abi`` at compile time
+and ``-Wl,-Bstatic -lc++ -lc++abi -Wl,-Bdynamic -lm -fuse-ld=lld
+-static-libstdc++`` at link time.
diff --git a/clang/docs/index.rst b/clang/docs/index.rst
index 70c8737a2fe0d..2a0dc16b5f2a1 100644
--- a/clang/docs/index.rst
+++ b/clang/docs/index.rst
@@ -49,6 +49,7 @@ Using Clang as a Compiler
    PointerAuthentication
    SafeStack
    ShadowCallStack
+   StructureProtection
    SourceBasedCodeCoverage
    StandardCPlusPlusModules
    Modules
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 05926fbd5bd71..ea85a7a4be51c 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -215,6 +215,12 @@ struct TypeInfoChars {
   }
 };
 
+struct PFPField {
+  CharUnits offset;
+  FieldDecl *field;
+  bool isWithinUnion;
+};
+
 /// Holds long-lived AST nodes (such as types and decls) that can be
 /// referred to throughout the semantic analysis of a file.
 class ASTContext : public RefCountedBase<ASTContext> {
@@ -3802,6 +3808,22 @@ OPT_LIST(V)
 
   StringRef getCUIDHash() const;
 
+  void findPFPFields(QualType Ty, CharUnits Offset,
+                     std::vector<PFPField> &Fields, bool IncludeVBases,
+                     bool IsWithinUnion = false) const;
+  bool hasPFPFields(QualType ty) const;
+  bool isPFPField(const FieldDecl *field) const;
+
+  /// Returns whether this record's PFP fields (if any) are trivially
+  /// copyable (i.e. may be memcpy'd). This may also return true if the
+  /// record does not have any PFP fields, so it may be necessary for the caller
+  /// to check for PFP fields, e.g. by calling hasPFPFields().
+  bool arePFPFieldsTriviallyCopyable(const RecordDecl *RD) const;
+
+  llvm::SetVector<const FieldDecl *> PFPFieldsWithEvaluatedOffset;
+  void recordMemberDataPointerEvaluation(const ValueDecl *VD);
+  void recordOffsetOfEvaluation(const OffsetOfExpr *E);
+
 private:
   /// All OMPTraitInfo objects live in this collection, one per
   /// `pragma omp [begin] declare variant` directive.
diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index 6620840df0ced..7e6e2147a448d 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -253,4 +253,10 @@ FIELD(IsAnyDestructorNoReturn, 1, NO_MERGE)
 /// type that is intangible). HLSL only.
 FIELD(IsHLSLIntangible, 1, NO_MERGE)
 
+/// Whether the pointer fields in this class should have pointer field
+/// protection (PFP) by default, either because of an attribute, the
+/// -fexperimental-pointer-field-protection-abi compiler flag or inheritance
+/// from a base or member with PFP.
+FIELD(IsPFPType, 1, NO_MERGE)
+
 #undef FIELD
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 5c4ad3c45da19..15dda098bad47 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1235,6 +1235,12 @@ class CXXRecordDecl : public RecordDecl {
   /// Determine whether this class has any variant members.
   bool hasVariantMembers() const { return data().HasVariantMembers; }
 
+  /// Returns whether the pointer fields in this class should have pointer field
+  /// protection (PFP) by default, either because of an attribute, the
+  /// -fexperimental-pointer-field-protection-abi compiler flag or inheritance
+  /// from a base or member with PFP.
+  bool isPFPType() const { return data().IsPFPType; }
+
   /// Determine whether this class has a trivial default constructor
   /// (C++11 [class.ctor]p5).
   bool hasTrivialDefaultConstructor() const {
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 0b006142cbb74..fd3e186c95738 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2643,6 +2643,19 @@ def CountedByOrNull : DeclOrTypeAttr {
   let LangOpts = [COnly];
 }
 
+def NoFieldProtection : DeclOrTypeAttr {
+  let Spellings = [Clang<"no_field_protection">];
+  let Subjects = SubjectList<[Field], ErrorDiag>;
+  let Documentation = [Undocumented];
+}
+
+def PointerFieldProtection : DeclOrTypeAttr {
+  let Spellings = [Clang<"pointer_field_protection">];
+  let Subjects = SubjectList<[CXXRecord], ErrorDiag>;
+  let Documentation = [Undocumented];
+  let SimpleHandler = 1;
+}
+
 def SizedBy : DeclOrTypeAttr {
   let Spellings = [Clang<"sized_by">];
   let Subjects = SubjectList<[Field], ErrorDiag>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 381d1fb063eba..256dab9d2fc22 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3995,6 +3995,8 @@ def err_attribute_vecreturn_only_vector_member : Error<
   "the vecreturn attribute can only be used on a class or structure with one member, which must be a vector">;
 def err_attribute_vecreturn_only_pod_record : Error<
   "the vecreturn attribute can only be used on a POD (plain old data) class or structure (i.e. no virtual functions)">;
+def err_attribute_pointer_field_protection_experimental: Error<
+  "this attribute is experimental and must be explicitly enabled with flag -fexperimental-allow-pointer-field-protection-attr">;
 def err_sme_attr_mismatch : Error<
   "function declared %0 was previously declared %1, which has different SME function attributes">;
 def err_sme_call_in_non_sme_target : Error<
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 8cba1dbaee24e..ef8632dcc2dd0 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -466,6 +466,13 @@ LANGOPT(RelativeCXXABIVTables, 1, 0, NotCompatible,
 LANGOPT(OmitVTableRTTI, 1, 0, NotCompatible,
         "Use an ABI-incompatible v-table layout that omits the RTTI component")
 
+LANGOPT(PointerFieldProtectionAttr, 1, 0, NotCompatible,
+        "Allow the use of the experimental [[clang::pointer_field_protection]] attribute")
+LANGOPT(PointerFieldProtectionABI, 1, 0, NotCompatible,
+        "Enable pointer field protection by default for all non-standard-layout types")
+LANGOPT(PointerFieldProtectionTagged, 1, 0, NotCompatible,
+        "Use pointer identity (tag) to discriminate pointers of non-trivially-copyable types")
+
 LANGOPT(VScaleMin, 32, 0, NotCompatible, "Minimum vscale value")
 LANGOPT(VScaleMax, 32, 0, NotCompatible, "Maximum vscale value")
 
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 45c5322351a17..f2a448c26743b 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -3178,6 +3178,22 @@ defm experimental_omit_vtable_rtti : BoolFOption<"experimental-omit-vtable-rtti"
   NegFlag<SetFalse, [], [CC1Option], "Do not omit">,
   BothFlags<[], [CC1Option], " the RTTI component from virtual tables">>;
 
+defm experimental_allow_pointer_field_protection_attr : BoolFOption<"experimental-allow-pointer-field-protection-attr",
+  LangOpts<"PointerFieldProtectionAttr">, DefaultFalse,
+  PosFlag<SetTrue, [], [CC1Option], "Allow">,
+  NegFlag<SetFalse, [], [], "Disallow">,
+  BothFlags<[], [ClangOption], " the use of the [[clang::pointer_field_protection]] attribute">>;
+defm experimental_pointer_field_protection_abi : BoolFOption<"experimental-pointer-field-protection-abi",
+  LangOpts<"PointerFieldProtectionABI">, DefaultFalse,
+  PosFlag<SetTrue, [], [CC1Option], "Enable">,
+  NegFlag<SetFalse, [], [], "Do not enable">,
+  BothFlags<[], [ClangOption], " pointer field protection on all non-standard layout struct types">>;
+defm experimental_pointer_field_protection_tagged : BoolFOption<"experimental-pointer-field-protection-tagged",
+  LangOpts<"PointerFieldProtectionTagged">, DefaultFalse,
+  PosFlag<SetTrue, [], [CC1Option], "Use">,
+  NegFlag<SetFalse, [], [], "Do not use">,
+  BothFlags<[], [ClangOption], " pointer identity (tag) to discriminate pointers of non-trivially-copyable types">>;
+
 def fcxx_abi_EQ : Joined<["-"], "fc++-abi=">,
                   Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
                   HelpText<"C++ ABI to use. This will override the target C++ ABI.">;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index e30681e86b1f0..b4b53b35c16b7 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15227,3 +15227,91 @@ bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
   ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames);
   return Result;
 }
+
+bool ASTContext::arePFPFieldsTriviallyCopyable(const RecordDecl *RD) const {
+  bool IsPAuthSupported =
+      getTargetInfo().getTriple().getArch() == llvm::Triple::aarch64;
+  if (!IsPAuthSupported)
+    return true;
+  if (getLangOpts().PointerFieldProtectionTagged)
+    return !isa<CXXRecordDecl>(RD) ||
+           cast<CXXRecordDecl>(RD)->hasTrivialDestructor();
+  return true;
+}
+
+void ASTContext::findPFPFields(QualType Ty, CharUnits Offset,
+                               std::vector<PFPField> &Fields,
+                               bool IncludeVBases, bool IsWithinUnion) const {
+  if (auto *AT = getAsConstantArrayType(Ty)) {
+    if (auto *ElemDecl = AT->getElementType()->getAsCXXRecordDecl()) {
+      const ASTRecordLayout &ElemRL = getASTRecordLayout(ElemDecl);
+      for (unsigned i = 0; i != AT->getSize(); ++i)
+        findPFPFields(AT->getElementType(), Offset + i * ElemRL.getSize(),
+                      Fields, true);
+    }
+  }
+  auto *Decl = Ty->getAsCXXRecordDecl();
+  // isPFPType() is inherited from bases and members (including via arrays), so
+  // we can early exit if it is false.
+  if (!Decl || !Decl->isPFPType())
+    return;
+  IsWithinUnion |= Decl->isUnion();
+  const ASTRecordLayout &RL = getASTRecordLayout(Decl);
+  for (FieldDecl *field : Decl->fields()) {
+    CharUnits fieldOffset =
+        Offset + toCharUnitsFromBits(RL.getFieldOffset(field->getFieldIndex()));
+    if (isPFPField(field))
+      Fields.push_back({fieldOffset, field, IsWithinUnion});
+    findPFPFields(field->getType(), fieldOffset, Fields, /*IncludeVBases=*/true,
+                  IsWithinUnion);
+  }
+  // Pass false for IncludeVBases below because vbases are only included in
+  // layout for top-level types, i.e. not bases or vbases.
+  for (auto &Base : Decl->bases()) {
+    if (Base.isVirtual())
+      continue;
+    CharUnits BaseOffset =
+        Offset + RL.getBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
+    findPFPFields(Base.getType(), BaseOffset, Fields, /*IncludeVBases=*/false,
+                  IsWithinUnion);
+  }
+  if (IncludeVBases) {
+    for (auto &Base : Decl->vbases()) {
+      CharUnits BaseOffset =
+          Offset + RL.getVBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
+      findPFPFields(Base.getType(), BaseOffset, Fields, /*IncludeVBases=*/false,
+                    IsWithinUnion);
+    }
+  }
+}
+
+bool ASTContext::hasPFPFields(QualType Ty) const {
+  std::vector<PFPField> PFPFields;
+  findPFPFields(Ty, CharUnits::Zero(), PFPFields, true);
+  return !PFPFields.empty();
+}
+
+bool ASTContext::isPFPField(const FieldDecl *FD) const {
+  if (auto *RD = dyn_cast<CXXRecordDecl>(FD->getParent()))
+    return RD->isPFPType() && FD->getType()->isPointerType() &&
+           !FD->hasAttr<NoFieldProtectionAttr>();
+  return false;
+}
+
+void ASTContext::recordMemberDataPointerEvaluation(const ValueDecl *VD) {
+  auto *FD = dyn_cast<FieldDecl>(VD);
+  if (!FD)
+    FD = cast<FieldDecl>(cast<IndirectFieldDecl>(VD)->chain().back());
+  if (isPFPField(FD))
+    PFPFieldsWithEvaluatedOffset.insert(FD);
+}
+
+void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
+  if (E->getNumComponents() == 0)
+    return;
+  OffsetOfNode Comp = E->getComponent(E->getNumComponents() - 1);
+  if (Comp.getKind() != OffsetOfNode::Field)
+    return;
+  if (FieldDecl *FD = Comp.getField(); isPFPField(FD))
+    PFPFieldsWithEvaluatedOffset.insert(FD);
+}
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index c16b1bb7a3453..37bc61ca35c4b 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -109,9 +109,9 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
       ImplicitCopyAssignmentHasConstParam(true),
       HasDeclaredCopyConstructorWithConstParam(false),
       HasDeclaredCopyAssignmentWithConstParam(false),
-      IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsLambda(false),
-      IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false),
-      HasODRHash(false), Definition(D) {}
+      IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsPFPType(false),
+      IsLambda(false), IsParsingBaseSpecifiers(false),
+      ComputedVisibleConversions(false), HasODRHash(false), Definition(D) {}
 
 CXXBaseSpecifier *CXXRecordDecl::DefinitionData::getBasesSlowCase() const {
   return Bases.get(Definition->getASTContext().getExternalSource());
@@ -456,6 +456,9 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
     if (!BaseClassDecl->allowConstDefaultInit())
       data().HasUninitializedFields = true;
 
+    if (BaseClassDecl->isPFPType())
+      data().IsPFPType = true;
+
     addedClassSubobject(BaseClassDecl);
   }
 
@@ -1408,6 +1411,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
         if (FieldRec->hasVariantMembers() &&
             Field->isAnonymousStructOrUnion())
           data().HasVariantMembers = true;
+
+        if (FieldRec->isPFPType())
+          data().IsPFPType = true;
       }
     } else {
       // Base element type of field is a non-class type.
@@ -2305,6 +2311,14 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
     }
     setHasUninitializedExplicitInitFields(false);
   }
+
+  if (getLangOpts().PointerFieldProtectionABI && !isStandardLayout()) {
+    data().IsPFPType = true;
+  } else if (hasAttr<PointerFieldProtectionAttr>()) {
+    data().IsPFPType = true;
+    data().IsStandardLayout = false;
+    data().IsCXX11StandardLayout = false;
+  }
 }
 
 bool CXXRecordDecl::mayBeAbstract() const {
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d81496ffd74e0..9713e2467eb20 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -18382,6 +18382,7 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr(
 }
 
 bool IntExprEvaluator::VisitOffsetOfExpr(const OffsetOfExpr *OOE) {
+  Info.Ctx.recordOffsetOfEvaluation(OOE);
   CharUnits Result;
   unsigned n = OOE->getNumComponents();
   if (n == 0)
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index d2881d5ac518a..a3b4f948d1efe 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -2122,6 +2122,12 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::CFISalt:
     OS << "cfi_salt(\"" << cast<CFISaltAttr>(T->getAttr())->getSalt() << "\")";
     break;
+  case attr::NoFieldProtection:
+    OS << "no_field_protection";
+    break;
+  case attr::PointerFieldProtection:
+    OS << "pointer_field_protection";
+    break;
   }
   OS << "))";
 }
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 01be374422d93..8892006394215 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4521,18 +4521,50 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
     Address Dest = EmitPointerWithAlignment(E->getArg(0));
     Address Src = EmitPointerWithAlignment(E->getArg(1));
     Value *SizeVal = EmitScalarExpr(E->getArg(2));
+    Value *TypeSize = ConstantInt::get(
+        SizeVal->getType(),
+        getContext()
+            .getTypeSizeInChars(E->getArg(0)->getType()->getPointeeType())
+            .getQuantity());
     if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate)
-      SizeVal = Builder.CreateMul(
-          SizeVal,
-          ConstantInt::get(
-              SizeVal->getType(),
-              getContext()
-                  .getTypeSizeInChars(E->getArg(0)->getType()->getPointeeType())
-                  .getQuantity()));
+      SizeVal = Builder.CreateMul(SizeVal, TypeSize);
     EmitArgCheck(TCK_Store, Dest, E->getArg(0), 0);
     EmitArgCheck(TCK_Load, Src, E->getArg(1), 1);
     auto *I = Builder.CreateMemMove(Dest, Src, SizeVal, false);
     addInstToNewSourceAtom(I, nullptr);
+    if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate) {
+      if (getContext().hasPFPFields(
+              E->getArg(0)->getType()->getPointeeType())) {
+        // Call emitPFPTrivialRelocation for every object in the array we are
+        // relocating.
+        BasicBlock *Entry = Builder.GetInsertBlock();
+        BasicBlock *Loop = createBasicB...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Dec 13, 2025

@llvm/pr-subscribers-clang-driver

Author: Peter Collingbourne (pcc)

Changes

Pointer field protection is a use-after-free vulnerability
mitigation that works by changing how data structures' pointer
fields are stored in memory. For more information, see the RFC:
https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555


Patch is 92.42 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/172119.diff

40 Files Affected:

  • (added) clang/docs/StructureProtection.rst (+81)
  • (modified) clang/docs/index.rst (+1)
  • (modified) clang/include/clang/AST/ASTContext.h (+22)
  • (modified) clang/include/clang/AST/CXXRecordDeclDefinitionBits.def (+6)
  • (modified) clang/include/clang/AST/DeclCXX.h (+6)
  • (modified) clang/include/clang/Basic/Attr.td (+13)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/include/clang/Basic/LangOptions.def (+7)
  • (modified) clang/include/clang/Options/Options.td (+16)
  • (modified) clang/lib/AST/ASTContext.cpp (+88)
  • (modified) clang/lib/AST/DeclCXX.cpp (+17-3)
  • (modified) clang/lib/AST/ExprConstant.cpp (+1)
  • (modified) clang/lib/AST/TypePrinter.cpp (+6)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+39-7)
  • (modified) clang/lib/CodeGen/CGCall.cpp (+149-12)
  • (modified) clang/lib/CodeGen/CGClass.cpp (+21-6)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+15-4)
  • (modified) clang/lib/CodeGen/CGExprAgg.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+39-1)
  • (modified) clang/lib/CodeGen/CodeGenFunction.cpp (+67-5)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+10-2)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+40)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+7)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+1)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+1)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+18)
  • (modified) clang/lib/Frontend/InitPreprocessor.cpp (+5)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+15)
  • (modified) clang/lib/Sema/SemaTypeTraits.cpp (+21)
  • (added) clang/test/CodeGenCXX/pfp-attribute-disable.cpp (+33)
  • (added) clang/test/CodeGenCXX/pfp-coerce.cpp (+245)
  • (added) clang/test/CodeGenCXX/pfp-load-store.cpp (+40)
  • (added) clang/test/CodeGenCXX/pfp-member-pointer-offsetof.cpp (+14)
  • (added) clang/test/CodeGenCXX/pfp-memcpy.cpp (+59)
  • (added) clang/test/CodeGenCXX/pfp-null-init.cpp (+21)
  • (added) clang/test/CodeGenCXX/pfp-struct-gep.cpp (+36)
  • (added) clang/test/CodeGenCXX/pfp-trivially-relocatable.cpp (+101)
  • (modified) clang/test/Misc/pragma-attribute-supported-attributes-list.test (+2)
  • (added) clang/test/Preprocessor/pfp-predefines.c (+5)
  • (added) clang/test/SemaCXX/attr-pointer-field-protection.cpp (+6)
diff --git a/clang/docs/StructureProtection.rst b/clang/docs/StructureProtection.rst
new file mode 100644
index 0000000000000..5acbdd322cd0a
--- /dev/null
+++ b/clang/docs/StructureProtection.rst
@@ -0,0 +1,81 @@
+====================
+Structure Protection
+====================
+
+.. contents::
+   :local:
+
+
+Introduction
+============
+
+Structure protection is an *experimental* mitigation
+against use-after-free vulnerabilities. For
+more information, please see the original `RFC
+<https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555>`_.
+An independent set of documentation will be contributed when the feature
+is promoted to stable.
+
+Usage
+=====
+
+To use structure protection, build your program using one or more of these flags:
+
+- ``-fexperimental-allow-pointer-field-protection-attr``: Makes the
+  ``[[clang::pointer_field_protection]]`` attribute described below
+  available for use in code. Without this flag, use of the attribute will
+  cause an error. This flag acts as a guard against use of the feature
+  before it is stabilized. When the feature is stabilized, the intent
+  is that this flag will become a no-op and the attribute will always
+  be available.
+
+- ``-fexperimental-pointer-field-protection-abi``: Enable pointer
+  field protection on all types that are not considered standard-layout
+  according to the C++ rules for standard layout. Because this
+  flag changes the C++ ABI, we refer to this as the pointer
+  field protection ABI. Specifying this flag also defines the
+  predefined macro ``__POINTER_FIELD_PROTECTION_ABI__``. Implies
+  ``-fexperimental-allow-pointer-field-protection-attr``.
+
+- ``-fexperimental-pointer-field-protection-tagged``: On architectures
+  that support it (currently only AArch64), for types that are not
+  considered trivially copyable, use the address of the object to compute
+  the pointer encoding. Specifying this flag also defines the predefined
+  macro ``__POINTER_FIELD_PROTECTION_TAGGED__``.
+
+It is also possible to specify the attribute
+``[[clang::pointer_field_protection]]`` on a struct type to opt the
+struct's pointer fields into pointer field protection, even if the type is
+standard layout or none of the command line flags are specified. Note that
+this means that the type will not comply with pointer interconvertibility
+and other standard layout rules.
+
+Pointer field protection is inherited from bases and non-static data
+members.
+
+In order to avoid ABI breakage, the entire C++ part
+of the program must be built with a consistent set of
+``-fexperimental-pointer-field-protection*`` flags, and the C++ standard
+library must also be built with the same flags and statically linked
+into the program.
+
+To build libc++ with pointer field protection support, pass the following
+CMake flags:
+
+.. code-block:: console
+
+    "-DRUNTIMES_${triple}_LIBCXXABI_ENABLE_SHARED=OFF" \
+    "-DRUNTIMES_${triple}_LIBCXX_USE_COMPILER_RT=ON" \
+    "-DRUNTIMES_${triple}_LIBCXX_PFP=untagged" \
+    "-DRUNTIMES_${triple}_LIBCXX_ENABLE_SHARED=OFF" \
+    "-DRUNTIMES_${triple}_LIBCXX_TEST_CONFIG=llvm-libc++-static.cfg.in" \
+    "-DRUNTIMES_${triple}_LIBUNWIND_ENABLE_SHARED=OFF" \
+
+where ``${triple}`` is your target triple, such as
+``aarch64-unknown-linux``.
+
+The resulting toolchain may then be used to build programs
+with pointer field protection by passing ``-stdlib=libc++
+-fexperimental-pointer-field-protection-abi`` at compile time
+and ``-Wl,-Bstatic -lc++ -lc++abi -Wl,-Bdynamic -lm -fuse-ld=lld
+-static-libstdc++`` at link time.
diff --git a/clang/docs/index.rst b/clang/docs/index.rst
index 70c8737a2fe0d..2a0dc16b5f2a1 100644
--- a/clang/docs/index.rst
+++ b/clang/docs/index.rst
@@ -49,6 +49,7 @@ Using Clang as a Compiler
    PointerAuthentication
    SafeStack
    ShadowCallStack
+   StructureProtection
    SourceBasedCodeCoverage
    StandardCPlusPlusModules
    Modules
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 05926fbd5bd71..ea85a7a4be51c 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -215,6 +215,12 @@ struct TypeInfoChars {
   }
 };
 
+struct PFPField {
+  CharUnits offset;
+  FieldDecl *field;
+  bool isWithinUnion;
+};
+
 /// Holds long-lived AST nodes (such as types and decls) that can be
 /// referred to throughout the semantic analysis of a file.
 class ASTContext : public RefCountedBase<ASTContext> {
@@ -3802,6 +3808,22 @@ OPT_LIST(V)
 
   StringRef getCUIDHash() const;
 
+  void findPFPFields(QualType Ty, CharUnits Offset,
+                     std::vector<PFPField> &Fields, bool IncludeVBases,
+                     bool IsWithinUnion = false) const;
+  bool hasPFPFields(QualType ty) const;
+  bool isPFPField(const FieldDecl *field) const;
+
+  /// Returns whether this record's PFP fields (if any) are trivially
+  /// copyable (i.e. may be memcpy'd). This may also return true if the
+  /// record does not have any PFP fields, so it may be necessary for the caller
+  /// to check for PFP fields, e.g. by calling hasPFPFields().
+  bool arePFPFieldsTriviallyCopyable(const RecordDecl *RD) const;
+
+  llvm::SetVector<const FieldDecl *> PFPFieldsWithEvaluatedOffset;
+  void recordMemberDataPointerEvaluation(const ValueDecl *VD);
+  void recordOffsetOfEvaluation(const OffsetOfExpr *E);
+
 private:
   /// All OMPTraitInfo objects live in this collection, one per
   /// `pragma omp [begin] declare variant` directive.
diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index 6620840df0ced..7e6e2147a448d 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -253,4 +253,10 @@ FIELD(IsAnyDestructorNoReturn, 1, NO_MERGE)
 /// type that is intangible). HLSL only.
 FIELD(IsHLSLIntangible, 1, NO_MERGE)
 
+/// Whether the pointer fields in this class should have pointer field
+/// protection (PFP) by default, either because of an attribute, the
+/// -fexperimental-pointer-field-protection-abi compiler flag or inheritance
+/// from a base or member with PFP.
+FIELD(IsPFPType, 1, NO_MERGE)
+
 #undef FIELD
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 5c4ad3c45da19..15dda098bad47 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1235,6 +1235,12 @@ class CXXRecordDecl : public RecordDecl {
   /// Determine whether this class has any variant members.
   bool hasVariantMembers() const { return data().HasVariantMembers; }
 
+  /// Returns whether the pointer fields in this class should have pointer field
+  /// protection (PFP) by default, either because of an attribute, the
+  /// -fexperimental-pointer-field-protection-abi compiler flag or inheritance
+  /// from a base or member with PFP.
+  bool isPFPType() const { return data().IsPFPType; }
+
   /// Determine whether this class has a trivial default constructor
   /// (C++11 [class.ctor]p5).
   bool hasTrivialDefaultConstructor() const {
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 0b006142cbb74..fd3e186c95738 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2643,6 +2643,19 @@ def CountedByOrNull : DeclOrTypeAttr {
   let LangOpts = [COnly];
 }
 
+def NoFieldProtection : DeclOrTypeAttr {
+  let Spellings = [Clang<"no_field_protection">];
+  let Subjects = SubjectList<[Field], ErrorDiag>;
+  let Documentation = [Undocumented];
+}
+
+def PointerFieldProtection : DeclOrTypeAttr {
+  let Spellings = [Clang<"pointer_field_protection">];
+  let Subjects = SubjectList<[CXXRecord], ErrorDiag>;
+  let Documentation = [Undocumented];
+  let SimpleHandler = 1;
+}
+
 def SizedBy : DeclOrTypeAttr {
   let Spellings = [Clang<"sized_by">];
   let Subjects = SubjectList<[Field], ErrorDiag>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 381d1fb063eba..256dab9d2fc22 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3995,6 +3995,8 @@ def err_attribute_vecreturn_only_vector_member : Error<
   "the vecreturn attribute can only be used on a class or structure with one member, which must be a vector">;
 def err_attribute_vecreturn_only_pod_record : Error<
   "the vecreturn attribute can only be used on a POD (plain old data) class or structure (i.e. no virtual functions)">;
+def err_attribute_pointer_field_protection_experimental: Error<
+  "this attribute is experimental and must be explicitly enabled with flag -fexperimental-allow-pointer-field-protection-attr">;
 def err_sme_attr_mismatch : Error<
   "function declared %0 was previously declared %1, which has different SME function attributes">;
 def err_sme_call_in_non_sme_target : Error<
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 8cba1dbaee24e..ef8632dcc2dd0 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -466,6 +466,13 @@ LANGOPT(RelativeCXXABIVTables, 1, 0, NotCompatible,
 LANGOPT(OmitVTableRTTI, 1, 0, NotCompatible,
         "Use an ABI-incompatible v-table layout that omits the RTTI component")
 
+LANGOPT(PointerFieldProtectionAttr, 1, 0, NotCompatible,
+        "Allow the use of the experimental [[clang::pointer_field_protection]] attribute")
+LANGOPT(PointerFieldProtectionABI, 1, 0, NotCompatible,
+        "Enable pointer field protection by default for all non-standard-layout types")
+LANGOPT(PointerFieldProtectionTagged, 1, 0, NotCompatible,
+        "Use pointer identity (tag) to discriminate pointers of non-trivially-copyable types")
+
 LANGOPT(VScaleMin, 32, 0, NotCompatible, "Minimum vscale value")
 LANGOPT(VScaleMax, 32, 0, NotCompatible, "Maximum vscale value")
 
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 45c5322351a17..f2a448c26743b 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -3178,6 +3178,22 @@ defm experimental_omit_vtable_rtti : BoolFOption<"experimental-omit-vtable-rtti"
   NegFlag<SetFalse, [], [CC1Option], "Do not omit">,
   BothFlags<[], [CC1Option], " the RTTI component from virtual tables">>;
 
+defm experimental_allow_pointer_field_protection_attr : BoolFOption<"experimental-allow-pointer-field-protection-attr",
+  LangOpts<"PointerFieldProtectionAttr">, DefaultFalse,
+  PosFlag<SetTrue, [], [CC1Option], "Allow">,
+  NegFlag<SetFalse, [], [], "Disallow">,
+  BothFlags<[], [ClangOption], " the use of the [[clang::pointer_field_protection]] attribute">>;
+defm experimental_pointer_field_protection_abi : BoolFOption<"experimental-pointer-field-protection-abi",
+  LangOpts<"PointerFieldProtectionABI">, DefaultFalse,
+  PosFlag<SetTrue, [], [CC1Option], "Enable">,
+  NegFlag<SetFalse, [], [], "Do not enable">,
+  BothFlags<[], [ClangOption], " pointer field protection on all non-standard layout struct types">>;
+defm experimental_pointer_field_protection_tagged : BoolFOption<"experimental-pointer-field-protection-tagged",
+  LangOpts<"PointerFieldProtectionTagged">, DefaultFalse,
+  PosFlag<SetTrue, [], [CC1Option], "Use">,
+  NegFlag<SetFalse, [], [], "Do not use">,
+  BothFlags<[], [ClangOption], " pointer identity (tag) to discriminate pointers of non-trivially-copyable types">>;
+
 def fcxx_abi_EQ : Joined<["-"], "fc++-abi=">,
                   Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
                   HelpText<"C++ ABI to use. This will override the target C++ ABI.">;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index e30681e86b1f0..b4b53b35c16b7 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15227,3 +15227,91 @@ bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
   ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames);
   return Result;
 }
+
+bool ASTContext::arePFPFieldsTriviallyCopyable(const RecordDecl *RD) const {
+  bool IsPAuthSupported =
+      getTargetInfo().getTriple().getArch() == llvm::Triple::aarch64;
+  if (!IsPAuthSupported)
+    return true;
+  if (getLangOpts().PointerFieldProtectionTagged)
+    return !isa<CXXRecordDecl>(RD) ||
+           cast<CXXRecordDecl>(RD)->hasTrivialDestructor();
+  return true;
+}
+
+void ASTContext::findPFPFields(QualType Ty, CharUnits Offset,
+                               std::vector<PFPField> &Fields,
+                               bool IncludeVBases, bool IsWithinUnion) const {
+  if (auto *AT = getAsConstantArrayType(Ty)) {
+    if (auto *ElemDecl = AT->getElementType()->getAsCXXRecordDecl()) {
+      const ASTRecordLayout &ElemRL = getASTRecordLayout(ElemDecl);
+      for (unsigned i = 0; i != AT->getSize(); ++i)
+        findPFPFields(AT->getElementType(), Offset + i * ElemRL.getSize(),
+                      Fields, true);
+    }
+  }
+  auto *Decl = Ty->getAsCXXRecordDecl();
+  // isPFPType() is inherited from bases and members (including via arrays), so
+  // we can early exit if it is false.
+  if (!Decl || !Decl->isPFPType())
+    return;
+  IsWithinUnion |= Decl->isUnion();
+  const ASTRecordLayout &RL = getASTRecordLayout(Decl);
+  for (FieldDecl *field : Decl->fields()) {
+    CharUnits fieldOffset =
+        Offset + toCharUnitsFromBits(RL.getFieldOffset(field->getFieldIndex()));
+    if (isPFPField(field))
+      Fields.push_back({fieldOffset, field, IsWithinUnion});
+    findPFPFields(field->getType(), fieldOffset, Fields, /*IncludeVBases=*/true,
+                  IsWithinUnion);
+  }
+  // Pass false for IncludeVBases below because vbases are only included in
+  // layout for top-level types, i.e. not bases or vbases.
+  for (auto &Base : Decl->bases()) {
+    if (Base.isVirtual())
+      continue;
+    CharUnits BaseOffset =
+        Offset + RL.getBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
+    findPFPFields(Base.getType(), BaseOffset, Fields, /*IncludeVBases=*/false,
+                  IsWithinUnion);
+  }
+  if (IncludeVBases) {
+    for (auto &Base : Decl->vbases()) {
+      CharUnits BaseOffset =
+          Offset + RL.getVBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
+      findPFPFields(Base.getType(), BaseOffset, Fields, /*IncludeVBases=*/false,
+                    IsWithinUnion);
+    }
+  }
+}
+
+bool ASTContext::hasPFPFields(QualType Ty) const {
+  std::vector<PFPField> PFPFields;
+  findPFPFields(Ty, CharUnits::Zero(), PFPFields, true);
+  return !PFPFields.empty();
+}
+
+bool ASTContext::isPFPField(const FieldDecl *FD) const {
+  if (auto *RD = dyn_cast<CXXRecordDecl>(FD->getParent()))
+    return RD->isPFPType() && FD->getType()->isPointerType() &&
+           !FD->hasAttr<NoFieldProtectionAttr>();
+  return false;
+}
+
+void ASTContext::recordMemberDataPointerEvaluation(const ValueDecl *VD) {
+  auto *FD = dyn_cast<FieldDecl>(VD);
+  if (!FD)
+    FD = cast<FieldDecl>(cast<IndirectFieldDecl>(VD)->chain().back());
+  if (isPFPField(FD))
+    PFPFieldsWithEvaluatedOffset.insert(FD);
+}
+
+void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
+  if (E->getNumComponents() == 0)
+    return;
+  OffsetOfNode Comp = E->getComponent(E->getNumComponents() - 1);
+  if (Comp.getKind() != OffsetOfNode::Field)
+    return;
+  if (FieldDecl *FD = Comp.getField(); isPFPField(FD))
+    PFPFieldsWithEvaluatedOffset.insert(FD);
+}
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index c16b1bb7a3453..37bc61ca35c4b 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -109,9 +109,9 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
       ImplicitCopyAssignmentHasConstParam(true),
       HasDeclaredCopyConstructorWithConstParam(false),
       HasDeclaredCopyAssignmentWithConstParam(false),
-      IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsLambda(false),
-      IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false),
-      HasODRHash(false), Definition(D) {}
+      IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsPFPType(false),
+      IsLambda(false), IsParsingBaseSpecifiers(false),
+      ComputedVisibleConversions(false), HasODRHash(false), Definition(D) {}
 
 CXXBaseSpecifier *CXXRecordDecl::DefinitionData::getBasesSlowCase() const {
   return Bases.get(Definition->getASTContext().getExternalSource());
@@ -456,6 +456,9 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
     if (!BaseClassDecl->allowConstDefaultInit())
       data().HasUninitializedFields = true;
 
+    if (BaseClassDecl->isPFPType())
+      data().IsPFPType = true;
+
     addedClassSubobject(BaseClassDecl);
   }
 
@@ -1408,6 +1411,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
         if (FieldRec->hasVariantMembers() &&
             Field->isAnonymousStructOrUnion())
           data().HasVariantMembers = true;
+
+        if (FieldRec->isPFPType())
+          data().IsPFPType = true;
       }
     } else {
       // Base element type of field is a non-class type.
@@ -2305,6 +2311,14 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
     }
     setHasUninitializedExplicitInitFields(false);
   }
+
+  if (getLangOpts().PointerFieldProtectionABI && !isStandardLayout()) {
+    data().IsPFPType = true;
+  } else if (hasAttr<PointerFieldProtectionAttr>()) {
+    data().IsPFPType = true;
+    data().IsStandardLayout = false;
+    data().IsCXX11StandardLayout = false;
+  }
 }
 
 bool CXXRecordDecl::mayBeAbstract() const {
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d81496ffd74e0..9713e2467eb20 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -18382,6 +18382,7 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr(
 }
 
 bool IntExprEvaluator::VisitOffsetOfExpr(const OffsetOfExpr *OOE) {
+  Info.Ctx.recordOffsetOfEvaluation(OOE);
   CharUnits Result;
   unsigned n = OOE->getNumComponents();
   if (n == 0)
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index d2881d5ac518a..a3b4f948d1efe 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -2122,6 +2122,12 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::CFISalt:
     OS << "cfi_salt(\"" << cast<CFISaltAttr>(T->getAttr())->getSalt() << "\")";
     break;
+  case attr::NoFieldProtection:
+    OS << "no_field_protection";
+    break;
+  case attr::PointerFieldProtection:
+    OS << "pointer_field_protection";
+    break;
   }
   OS << "))";
 }
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 01be374422d93..8892006394215 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4521,18 +4521,50 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
     Address Dest = EmitPointerWithAlignment(E->getArg(0));
     Address Src = EmitPointerWithAlignment(E->getArg(1));
     Value *SizeVal = EmitScalarExpr(E->getArg(2));
+    Value *TypeSize = ConstantInt::get(
+        SizeVal->getType(),
+        getContext()
+            .getTypeSizeInChars(E->getArg(0)->getType()->getPointeeType())
+            .getQuantity());
     if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate)
-      SizeVal = Builder.CreateMul(
-          SizeVal,
-          ConstantInt::get(
-              SizeVal->getType(),
-              getContext()
-                  .getTypeSizeInChars(E->getArg(0)->getType()->getPointeeType())
-                  .getQuantity()));
+      SizeVal = Builder.CreateMul(SizeVal, TypeSize);
     EmitArgCheck(TCK_Store, Dest, E->getArg(0), 0);
     EmitArgCheck(TCK_Load, Src, E->getArg(1), 1);
     auto *I = Builder.CreateMemMove(Dest, Src, SizeVal, false);
     addInstToNewSourceAtom(I, nullptr);
+    if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate) {
+      if (getContext().hasPFPFields(
+              E->getArg(0)->getType()->getPointeeType())) {
+        // Call emitPFPTrivialRelocation for every object in the array we are
+        // relocating.
+        BasicBlock *Entry = Builder.GetInsertBlock();
+        BasicBlock *Loop = createBasicB...
[truncated]

@github-actions
Copy link

github-actions bot commented Dec 13, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@pcc pcc requested review from fmayer and ojhunt December 13, 2025 01:47
@pcc
Copy link
Contributor Author

pcc commented Dec 13, 2025

(Continuation of #133538.)

Created using spr 1.3.6-beta.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants