From 370d7ce58011eccfab8105eddbc028cc09c4c5e5 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Thu, 19 Feb 2026 15:19:35 -0800 Subject: [PATCH] [Clang] Add pointer field protection feature. 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 Reviewers: fmayer, ojhunt Pull Request: https://github.com/llvm/llvm-project/pull/172119 --- clang/docs/StructureProtection.rst | 95 +++++++++++ clang/docs/index.rst | 1 + clang/include/clang/AST/ASTContext.h | 23 +++ .../clang/AST/CXXRecordDeclDefinitionBits.def | 6 + clang/include/clang/AST/DeclCXX.h | 6 + clang/include/clang/Basic/Attr.td | 13 ++ .../clang/Basic/DiagnosticSemaKinds.td | 3 + clang/include/clang/Basic/LangOptions.def | 7 + clang/include/clang/Options/Options.td | 25 +++ clang/lib/AST/ASTContext.cpp | 94 +++++++++++ clang/lib/AST/DeclCXX.cpp | 20 ++- clang/lib/AST/ExprConstant.cpp | 1 + clang/lib/AST/TypePrinter.cpp | 6 + clang/lib/CodeGen/CGCall.cpp | 137 +++++++++++++-- clang/lib/CodeGen/CGClass.cpp | 29 +++- clang/lib/CodeGen/CGExpr.cpp | 24 ++- clang/lib/CodeGen/CGExprAgg.cpp | 3 +- clang/lib/CodeGen/CGExprConstant.cpp | 43 ++++- clang/lib/CodeGen/CodeGenFunction.cpp | 59 ++++++- clang/lib/CodeGen/CodeGenFunction.h | 12 +- clang/lib/CodeGen/CodeGenModule.cpp | 44 +++++ clang/lib/CodeGen/CodeGenModule.h | 7 + clang/lib/CodeGen/ItaniumCXXABI.cpp | 1 + clang/lib/CodeGen/MicrosoftCXXABI.cpp | 1 + clang/lib/Driver/ToolChains/Clang.cpp | 18 ++ clang/lib/Frontend/InitPreprocessor.cpp | 5 + clang/lib/Sema/SemaDeclAttr.cpp | 16 ++ .../test/CodeGenCXX/pfp-attribute-disable.cpp | 33 ++++ clang/test/CodeGenCXX/pfp-coerce.cpp | 157 ++++++++++++++++++ clang/test/CodeGenCXX/pfp-load-store.cpp | 40 +++++ .../pfp-member-pointer-offsetof.cpp | 14 ++ clang/test/CodeGenCXX/pfp-memcpy.cpp | 59 +++++++ clang/test/CodeGenCXX/pfp-null-init.cpp | 19 +++ clang/test/CodeGenCXX/pfp-struct-gep.cpp | 33 ++++ ...a-attribute-supported-attributes-list.test | 2 + clang/test/Preprocessor/pfp-predefines.c | 5 + .../SemaCXX/attr-pointer-field-protection.cpp | 6 + 37 files changed, 1033 insertions(+), 34 deletions(-) create mode 100644 clang/docs/StructureProtection.rst create mode 100644 clang/test/CodeGenCXX/pfp-attribute-disable.cpp create mode 100644 clang/test/CodeGenCXX/pfp-coerce.cpp create mode 100644 clang/test/CodeGenCXX/pfp-load-store.cpp create mode 100644 clang/test/CodeGenCXX/pfp-member-pointer-offsetof.cpp create mode 100644 clang/test/CodeGenCXX/pfp-memcpy.cpp create mode 100644 clang/test/CodeGenCXX/pfp-null-init.cpp create mode 100644 clang/test/CodeGenCXX/pfp-struct-gep.cpp create mode 100644 clang/test/Preprocessor/pfp-predefines.c create mode 100644 clang/test/SemaCXX/attr-pointer-field-protection.cpp diff --git a/clang/docs/StructureProtection.rst b/clang/docs/StructureProtection.rst new file mode 100644 index 000000000000..a5ca1e64c35b --- /dev/null +++ b/clang/docs/StructureProtection.rst @@ -0,0 +1,95 @@ +==================== +Structure Protection +==================== + +.. contents:: + :local: + + +Introduction +============ + +Structure protection is an *experimental* mitigation +against use-after-free vulnerabilities. For +more information, please see the original `RFC +`_. +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. + +Implementation +============== + +TODO: Document everything else. + +The implementation uses deactivation symbols as a mechanism for +globally disabling pointer field protection for a particular field. For +more information, see the `deactivation symbol section of the LangRef +`_. +These symbols are named as follows: + +``__pfp_ds_`` followed by the ABI encoding of the type's RTTI object +symbol name followed by ``.`` followed by the name of the field. diff --git a/clang/docs/index.rst b/clang/docs/index.rst index 83aed67959fe..9647d1cd2fae 100644 --- a/clang/docs/index.rst +++ b/clang/docs/index.rst @@ -51,6 +51,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 32f3f0736fa8..8f9ea2622005 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -216,6 +216,11 @@ struct TypeInfoChars { } }; +struct PFPField { + CharUnits Offset; + FieldDecl *Field; +}; + /// 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 { @@ -3841,6 +3846,24 @@ public: StringRef getCUIDHash() const; + /// Returns a list of PFP fields for the given type, including subfields in + /// bases or other fields, except for fields contained within fields of union + /// type. + std::vector findPFPFields(QualType Ty) 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 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 6620840df0ce..7e6e2147a448 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 5c4ad3c45da1..15dda098bad4 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -1235,6 +1235,12 @@ public: /// 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 9c6594a1c9ff..aec13e703ef6 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2637,6 +2637,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 615fdba99c30..5254003f0c21 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4004,6 +4004,9 @@ 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 f39dbe241ccb..4373ae86895d 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -470,6 +470,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 d9a1a2f78f22..4ac812e92e2c 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -3237,6 +3237,31 @@ defm experimental_omit_vtable_rtti : BoolFOption<"experimental-omit-vtable-rtti" NegFlag, 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, + NegFlag, + 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, + NegFlag, + 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, + NegFlag, + BothFlags<[], [ClangOption], + " pointer identity (tag) to discriminate pointers " + "of non-trivially-copyable types">>; + def fcxx_abi_EQ : Joined<["-"], "fc++-abi=">, 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 8c59adba53f9..d97a97091e4e 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15456,3 +15456,97 @@ bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl, ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames); return Result; } + +bool ASTContext::arePFPFieldsTriviallyCopyable(const RecordDecl *RD) const { + // Check for trivially-destructible here because non-trivially-destructible + // types will always cause the type and any types derived from it to be + // considered non-trivially-copyable. The same cannot be said for + // trivially-copyable because deleting special members of a type derived from + // a non-trivially-copyable type can cause the derived type to be considered + // trivially copyable. + if (getLangOpts().PointerFieldProtectionTagged) + return !isa(RD) || + cast(RD)->hasTrivialDestructor(); + return true; +} + +static void findPFPFields(const ASTContext &Ctx, QualType Ty, CharUnits Offset, + std::vector &Fields, bool IncludeVBases) { + if (auto *AT = Ctx.getAsConstantArrayType(Ty)) { + if (auto *ElemDecl = AT->getElementType()->getAsCXXRecordDecl()) { + const ASTRecordLayout &ElemRL = Ctx.getASTRecordLayout(ElemDecl); + for (unsigned i = 0; i != AT->getSize(); ++i) + findPFPFields(Ctx, 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. Unions are excluded per the API + // documentation. + if (!Decl || !Decl->isPFPType() || Decl->isUnion()) + return; + const ASTRecordLayout &RL = Ctx.getASTRecordLayout(Decl); + for (FieldDecl *Field : Decl->fields()) { + CharUnits FieldOffset = + Offset + + Ctx.toCharUnitsFromBits(RL.getFieldOffset(Field->getFieldIndex())); + if (Ctx.isPFPField(Field)) + Fields.push_back({FieldOffset, Field}); + findPFPFields(Ctx, Field->getType(), FieldOffset, Fields, + /*IncludeVBases=*/true); + } + // Pass false for IncludeVBases below because vbases are only included in + // layout for top-level types, i.e. not bases or vbases. + for (CXXBaseSpecifier &Base : Decl->bases()) { + if (Base.isVirtual()) + continue; + CharUnits BaseOffset = + Offset + RL.getBaseClassOffset(Base.getType()->getAsCXXRecordDecl()); + findPFPFields(Ctx, Base.getType(), BaseOffset, Fields, + /*IncludeVBases=*/false); + } + if (IncludeVBases) { + for (CXXBaseSpecifier &Base : Decl->vbases()) { + CharUnits BaseOffset = + Offset + RL.getVBaseClassOffset(Base.getType()->getAsCXXRecordDecl()); + findPFPFields(Ctx, Base.getType(), BaseOffset, Fields, + /*IncludeVBases=*/false); + } + } +} + +std::vector ASTContext::findPFPFields(QualType Ty) const { + std::vector PFPFields; + ::findPFPFields(*this, Ty, CharUnits::Zero(), PFPFields, true); + return PFPFields; +} + +bool ASTContext::hasPFPFields(QualType Ty) const { + return !findPFPFields(Ty).empty(); +} + +bool ASTContext::isPFPField(const FieldDecl *FD) const { + if (auto *RD = dyn_cast(FD->getParent())) + return RD->isPFPType() && FD->getType()->isPointerType() && + !FD->hasAttr(); + return false; +} + +void ASTContext::recordMemberDataPointerEvaluation(const ValueDecl *VD) { + auto *FD = dyn_cast(VD); + if (!FD) + FD = cast(cast(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 c16b1bb7a345..37bc61ca35c4 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()) { + 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 06a957966ba4..b06233423db4 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -18512,6 +18512,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 de7b429a8e58..d8a48af62bb7 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -2089,6 +2089,12 @@ void TypePrinter::printAttributedAfter(const AttributedType *T, case attr::CFISalt: OS << "cfi_salt(\"" << cast(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/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 93176eabb928..1d950ffed8a0 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -1312,6 +1312,54 @@ static llvm::Value *CoerceIntOrPtrToIntOrPtr(llvm::Value *Val, llvm::Type *Ty, return Val; } +static llvm::Value *CreatePFPCoercedLoad(Address Src, QualType SrcFETy, + llvm::Type *Ty, CodeGenFunction &CGF) { + std::vector PFPFields = CGF.getContext().findPFPFields(SrcFETy); + if (PFPFields.empty()) + return nullptr; + + auto LoadCoercedField = [&](CharUnits Offset, + llvm::Type *FieldType) -> llvm::Value * { + // Check whether the field at Offset is a PFP field. This function is called + // in ascending order of offset, and PFPFields is sorted by offset. This + // means that we only need to check the first element (and remove it from + // PFPFields if matching). + if (!PFPFields.empty() && PFPFields[0].Offset == Offset) { + auto FieldAddr = CGF.EmitAddressOfPFPField(Src, PFPFields[0]); + llvm::Value *FieldVal = CGF.Builder.CreateLoad(FieldAddr); + if (isa(FieldType)) + FieldVal = CGF.Builder.CreatePtrToInt(FieldVal, FieldType); + PFPFields.erase(PFPFields.begin()); + return FieldVal; + } + auto FieldAddr = + CGF.Builder + .CreateConstInBoundsByteGEP(Src.withElementType(CGF.Int8Ty), Offset) + .withElementType(FieldType); + return CGF.Builder.CreateLoad(FieldAddr); + }; + + // The types handled by this function are the only ones that may be generated + // by AArch64ABIInfo::classify{Argument,Return}Type for struct types with + // pointers. PFP is only supported on AArch64. + if (isa(Ty) || isa(Ty)) { + auto Addr = CGF.EmitAddressOfPFPField(Src, PFPFields[0]); + llvm::Value *Val = CGF.Builder.CreateLoad(Addr); + if (isa(Ty)) + Val = CGF.Builder.CreatePtrToInt(Val, Ty); + return Val; + } + auto *AT = cast(Ty); + auto *ET = AT->getElementType(); + CharUnits WordSize = CGF.getContext().toCharUnitsFromBits( + CGF.CGM.getDataLayout().getTypeSizeInBits(ET)); + CharUnits Offset = CharUnits::Zero(); + llvm::Value *Val = llvm::PoisonValue::get(AT); + for (unsigned Idx = 0; Idx != AT->getNumElements(); ++Idx, Offset += WordSize) + Val = CGF.Builder.CreateInsertValue(Val, LoadCoercedField(Offset, ET), Idx); + return Val; +} + /// CreateCoercedLoad - Create a load from \arg SrcPtr interpreted as /// a pointer to an object of type \arg Ty, known to be aligned to /// \arg SrcAlign bytes. @@ -1319,14 +1367,17 @@ static llvm::Value *CoerceIntOrPtrToIntOrPtr(llvm::Value *Val, llvm::Type *Ty, /// This safely handles the case when the src type is smaller than the /// destination type; in this situation the values of bits which not /// present in the src are undefined. -static llvm::Value *CreateCoercedLoad(Address Src, llvm::Type *Ty, - CodeGenFunction &CGF) { +static llvm::Value *CreateCoercedLoad(Address Src, QualType SrcFETy, + llvm::Type *Ty, CodeGenFunction &CGF) { llvm::Type *SrcTy = Src.getElementType(); // If SrcTy and Ty are the same, just do a load. if (SrcTy == Ty) return CGF.Builder.CreateLoad(Src); + if (llvm::Value *V = CreatePFPCoercedLoad(Src, SrcFETy, Ty, CGF)) + return V; + llvm::TypeSize DstSize = CGF.CGM.getDataLayout().getTypeAllocSize(Ty); if (llvm::StructType *SrcSTy = dyn_cast(SrcTy)) { @@ -1398,8 +1449,51 @@ static llvm::Value *CreateCoercedLoad(Address Src, llvm::Type *Ty, return CGF.Builder.CreateLoad(Tmp); } -void CodeGenFunction::CreateCoercedStore(llvm::Value *Src, Address Dst, - llvm::TypeSize DstSize, +static bool CreatePFPCoercedStore(llvm::Value *Src, QualType SrcFETy, + Address Dst, CodeGenFunction &CGF) { + std::vector PFPFields = CGF.getContext().findPFPFields(SrcFETy); + if (PFPFields.empty()) + return false; + + llvm::Type *SrcTy = Src->getType(); + auto StoreCoercedField = [&](CharUnits Offset, llvm::Value *FieldVal) { + if (!PFPFields.empty() && PFPFields[0].Offset == Offset) { + auto FieldAddr = CGF.EmitAddressOfPFPField(Dst, PFPFields[0]); + if (isa(FieldVal->getType())) + FieldVal = CGF.Builder.CreateIntToPtr(FieldVal, CGF.VoidPtrTy); + CGF.Builder.CreateStore(FieldVal, FieldAddr); + PFPFields.erase(PFPFields.begin()); + } else { + auto FieldAddr = CGF.Builder + .CreateConstInBoundsByteGEP( + Dst.withElementType(CGF.Int8Ty), Offset) + .withElementType(FieldVal->getType()); + CGF.Builder.CreateStore(FieldVal, FieldAddr); + } + }; + + // The types handled by this function are the only ones that may be generated + // by AArch64ABIInfo::classify{Argument,Return}Type for struct types with + // pointers. PFP is only supported on AArch64. + if (isa(SrcTy) || isa(SrcTy)) { + if (isa(SrcTy)) + Src = CGF.Builder.CreateIntToPtr(Src, CGF.VoidPtrTy); + auto Addr = CGF.EmitAddressOfPFPField(Dst, PFPFields[0]); + CGF.Builder.CreateStore(Src, Addr); + } else { + auto *AT = cast(SrcTy); + auto *ET = AT->getElementType(); + CharUnits WordSize = CGF.getContext().toCharUnitsFromBits( + CGF.CGM.getDataLayout().getTypeSizeInBits(ET)); + CharUnits Offset = CharUnits::Zero(); + for (unsigned i = 0; i != AT->getNumElements(); ++i, Offset += WordSize) + StoreCoercedField(Offset, CGF.Builder.CreateExtractValue(Src, i)); + } + return true; +} + +void CodeGenFunction::CreateCoercedStore(llvm::Value *Src, QualType SrcFETy, + Address Dst, llvm::TypeSize DstSize, bool DstIsVolatile) { if (!DstSize) return; @@ -1419,6 +1513,9 @@ void CodeGenFunction::CreateCoercedStore(llvm::Value *Src, Address Dst, } } + if (CreatePFPCoercedStore(Src, SrcFETy, Dst, *this)) + return; + if (SrcSize.isScalable() || SrcSize <= DstSize) { if (SrcTy->isIntegerTy() && Dst.getElementType()->isPointerTy() && SrcSize == CGM.getDataLayout().getTypeAllocSize(Dst.getElementType())) { @@ -3448,6 +3545,13 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, if (SrcSize > DstSize) { Builder.CreateMemCpy(Ptr, AddrToStoreInto, DstSize); } + + // Structures with PFP fields require a coerced store to add any + // pointer signatures. + if (getContext().hasPFPFields(Ty)) { + llvm::Value *Struct = Builder.CreateLoad(Ptr); + CreatePFPCoercedStore(Struct, Ty, Ptr, *this); + } } } else { // Simple case, just do a coerced store of the argument into the alloca. @@ -3455,7 +3559,7 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, auto AI = Fn->getArg(FirstIRArg); AI->setName(Arg->getName() + ".coerce"); CreateCoercedStore( - AI, Ptr, + AI, Ty, Ptr, llvm::TypeSize::getFixed( getContext().getTypeSizeInChars(Ty).getQuantity() - ArgI.getDirectOffset()), @@ -4107,7 +4211,7 @@ void CodeGenFunction::EmitFunctionEpilog( // If the value is offset in memory, apply the offset now. Address V = emitAddressAtOffset(*this, ReturnValue, RetAI); - RV = CreateCoercedLoad(V, RetAI.getCoerceToType(), *this); + RV = CreateCoercedLoad(V, RetTy, RetAI.getCoerceToType(), *this); } // In ARC, end functions that return a retainable type with a call @@ -4156,7 +4260,7 @@ void CodeGenFunction::EmitFunctionEpilog( auto eltAddr = Builder.CreateStructGEP(addr, i); llvm::Value *elt = CreateCoercedLoad( - eltAddr, + eltAddr, RetTy, unpaddedStruct ? unpaddedStruct->getElementType(unpaddedIndex++) : unpaddedCoercionType, *this); @@ -5629,15 +5733,24 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, } else { uint64_t SrcSize = SrcTypeSize.getFixedValue(); uint64_t DstSize = DstTypeSize.getFixedValue(); + bool HasPFPFields = getContext().hasPFPFields(I->Ty); // If the source type is smaller than the destination type of the // coerce-to logic, copy the source value into a temp alloca the size // of the destination type to allow loading all of it. The bits past // the source value are left undef. - if (SrcSize < DstSize) { + if (HasPFPFields || SrcSize < DstSize) { Address TempAlloca = CreateTempAlloca(STy, Src.getAlignment(), Src.getName() + ".coerce"); - Builder.CreateMemCpy(TempAlloca, Src, SrcSize); + if (HasPFPFields) { + // Structures with PFP fields require a coerced load to remove any + // pointer signatures. + Builder.CreateStore( + CreatePFPCoercedLoad(Src, I->Ty, ArgInfo.getCoerceToType(), + *this), + TempAlloca); + } else + Builder.CreateMemCpy(TempAlloca, Src, SrcSize); Src = TempAlloca; } else { Src = Src.withElementType(STy); @@ -5656,7 +5769,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, // In the simple case, just pass the coerced loaded value. assert(NumIRArgs == 1); llvm::Value *Load = - CreateCoercedLoad(Src, ArgInfo.getCoerceToType(), *this); + CreateCoercedLoad(Src, I->Ty, ArgInfo.getCoerceToType(), *this); if (CallInfo.isCmseNSCall()) { // For certain parameter types, clear padding bits, as they may reveal @@ -5716,7 +5829,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, continue; Address eltAddr = Builder.CreateStructGEP(addr, i); llvm::Value *elt = CreateCoercedLoad( - eltAddr, + eltAddr, I->Ty, unpaddedStruct ? unpaddedStruct->getElementType(unpaddedIndex++) : unpaddedCoercionType, *this); @@ -6263,7 +6376,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, // If the value is offset in memory, apply the offset now. Address StorePtr = emitAddressAtOffset(*this, DestPtr, RetAI); CreateCoercedStore( - CI, StorePtr, + CI, RetTy, StorePtr, llvm::TypeSize::getFixed(DestSize - RetAI.getDirectOffset()), DestIsVolatile); } diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 831f63084fd6..26a7f08f5cfc 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -570,12 +570,21 @@ static void EmitBaseInitializer(CodeGenFunction &CGF, isBaseVirtual); } -static bool isMemcpyEquivalentSpecialMember(const CXXMethodDecl *D) { +static bool isMemcpyEquivalentSpecialMember(CodeGenModule &CGM, + const CXXMethodDecl *D) { auto *CD = dyn_cast(D); if (!(CD && CD->isCopyOrMoveConstructor()) && !D->isCopyAssignmentOperator() && !D->isMoveAssignmentOperator()) return false; + // Non-trivially-copyable fields with pointer field protection need to be + // copied one by one. + ASTContext &Ctx = CGM.getContext(); + const CXXRecordDecl *Parent = D->getParent(); + if (!Ctx.arePFPFieldsTriviallyCopyable(Parent) && + Ctx.hasPFPFields(Ctx.getCanonicalTagType(Parent))) + return false; + // We can emit a memcpy for a trivial copy or move constructor/assignment. if (D->isTrivial() && !D->getParent()->mayInsertExtraPadding()) return true; @@ -641,7 +650,8 @@ static void EmitMemberInitializer(CodeGenFunction &CGF, QualType BaseElementTy = CGF.getContext().getBaseElementType(Array); CXXConstructExpr *CE = dyn_cast(MemberInit->getInit()); if (BaseElementTy.isPODType(CGF.getContext()) || - (CE && isMemcpyEquivalentSpecialMember(CE->getConstructor()))) { + (CE && + isMemcpyEquivalentSpecialMember(CGF.CGM, CE->getConstructor()))) { unsigned SrcArgIndex = CGF.CGM.getCXXABI().getSrcArgforCopyCtor(Constructor, Args); llvm::Value *SrcPtr = @@ -909,6 +919,11 @@ public: if (PointerAuthQualifier Q = F->getType().getPointerAuth(); Q && Q.isAddressDiscriminated()) return false; + // Non-trivially-copyable fields with pointer field protection need to be + // copied one by one. + if (!CGF.getContext().arePFPFieldsTriviallyCopyable(ClassDecl) && + CGF.getContext().isPFPField(F)) + return false; return true; } @@ -1044,7 +1059,8 @@ private: CXXConstructExpr *CE = dyn_cast(MemberInit->getInit()); // Bail out on non-memcpyable, not-trivially-copyable members. - if (!(CE && isMemcpyEquivalentSpecialMember(CE->getConstructor())) && + if (!(CE && + isMemcpyEquivalentSpecialMember(CGF.CGM, CE->getConstructor())) && !(FieldType.isTriviallyCopyableType(CGF.getContext()) || FieldType->isReferenceType())) return false; @@ -1153,7 +1169,7 @@ private: return nullptr; } else if (CXXMemberCallExpr *MCE = dyn_cast(S)) { CXXMethodDecl *MD = dyn_cast(MCE->getCalleeDecl()); - if (!(MD && isMemcpyEquivalentSpecialMember(MD))) + if (!(MD && isMemcpyEquivalentSpecialMember(CGF.CGM, MD))) return nullptr; MemberExpr *IOA = dyn_cast(MCE->getImplicitObjectArgument()); if (!IOA) @@ -1234,6 +1250,7 @@ public: void finish() { emitAggregatedStmts(); } }; + } // end anonymous namespace static bool isInitializerOfDynamicClass(const CXXCtorInitializer *BaseInit) { @@ -2287,7 +2304,7 @@ void CodeGenFunction::EmitCXXConstructorCall( // If this is a trivial constructor, emit a memcpy now before we lose // the alignment information on the argument. // FIXME: It would be better to preserve alignment information into CallArg. - if (isMemcpyEquivalentSpecialMember(D)) { + if (isMemcpyEquivalentSpecialMember(CGM, D)) { assert(E->getNumArgs() == 1 && "unexpected argcount for trivial ctor"); const Expr *Arg = E->getArg(0); @@ -2355,7 +2372,7 @@ void CodeGenFunction::EmitCXXConstructorCall( // If this is a trivial constructor, just emit what's needed. If this is a // union copy constructor, we must emit a memcpy, because the AST does not // model that copy. - if (isMemcpyEquivalentSpecialMember(D)) { + if (isMemcpyEquivalentSpecialMember(CGM, D)) { assert(Args.size() == 2 && "unexpected argcount for trivial ctor"); QualType SrcTy = D->getParamDecl(0)->getType().getNonReferenceType(); Address Src = makeNaturalAddressForPointer( diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 473a65720477..7fee9a1122a7 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -5589,12 +5589,13 @@ static Address emitAddrOfZeroSizeField(CodeGenFunction &CGF, Address Base, return CGF.Builder.CreateConstInBoundsByteGEP(Base, Offset); } -/// Drill down to the storage of a field without walking into -/// reference types. +/// Drill down to the storage of a field without walking into reference types, +/// and without respect for pointer field protection. /// /// The resulting address doesn't necessarily have the right type. -static Address emitAddrOfFieldStorage(CodeGenFunction &CGF, Address base, - const FieldDecl *field, bool IsInBounds) { +static Address emitRawAddrOfFieldStorage(CodeGenFunction &CGF, Address base, + const FieldDecl *field, + bool IsInBounds) { if (isEmptyFieldForLayout(CGF.getContext(), field)) return emitAddrOfZeroSizeField(CGF, base, field, IsInBounds); @@ -5617,6 +5618,21 @@ static Address emitAddrOfFieldStorage(CodeGenFunction &CGF, Address base, return CGF.Builder.CreateStructGEP(base, idx, field->getName()); } +/// Drill down to the storage of a field without walking into reference types, +/// wrapping the address in an llvm.protected.field.ptr intrinsic for the +/// pointer field protection feature if necessary. +/// +/// The resulting address doesn't necessarily have the right type. +static Address emitAddrOfFieldStorage(CodeGenFunction &CGF, Address base, + const FieldDecl *field, bool IsInBounds) { + Address Addr = emitRawAddrOfFieldStorage(CGF, base, field, IsInBounds); + + if (!CGF.getContext().isPFPField(field)) + return Addr; + + return CGF.EmitAddressOfPFPField(base, Addr, field); +} + static Address emitPreserveStructAccess(CodeGenFunction &CGF, LValue base, Address addr, const FieldDecl *field) { const RecordDecl *rec = field->getParent(); diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp index a8fa7eab5736..0ca6af3def57 100644 --- a/clang/lib/CodeGen/CGExprAgg.cpp +++ b/clang/lib/CodeGen/CGExprAgg.cpp @@ -134,7 +134,7 @@ public: if (llvm::Value *Result = ConstantEmitter(CGF).tryEmitConstantExpr(E)) { CGF.CreateCoercedStore( - Result, Dest.getAddress(), + Result, E->getType(), Dest.getAddress(), llvm::TypeSize::getFixed( Dest.getPreferredSize(CGF.getContext(), E->getType()) .getQuantity()), @@ -2334,6 +2334,7 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, LValue Src, QualType Ty, auto *Inst = Builder.CreateMemCpy(DestPtr, SrcPtr, SizeVal, isVolatile); addInstToCurrentSourceAtom(Inst, nullptr); + emitPFPPostCopyUpdates(DestPtr, SrcPtr, Ty); // Determine the metadata to describe the position of any padding in this // memcpy, as well as the TBAA tags for the members of the struct, in case diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 5ecb6fd789a1..3f44243e1c35 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -31,6 +31,7 @@ #include "llvm/IR/DataLayout.h" #include "llvm/IR/Function.h" #include "llvm/IR/GlobalVariable.h" +#include "llvm/Support/SipHash.h" #include using namespace clang; using namespace CodeGen; @@ -905,6 +906,32 @@ bool ConstStructBuilder::Build(const APValue &Val, const RecordDecl *RD, if (!EltInit) return false; + if (CGM.getContext().isPFPField(*Field)) { + llvm::ConstantInt *Disc; + llvm::Constant *AddrDisc; + if (CGM.getContext().arePFPFieldsTriviallyCopyable(RD)) { + uint64_t FieldSignature = + llvm::getPointerAuthStableSipHash(CGM.getPFPFieldName(*Field)); + Disc = llvm::ConstantInt::get(CGM.Int64Ty, FieldSignature); + AddrDisc = llvm::ConstantPointerNull::get(CGM.VoidPtrTy); + } else if (Emitter.isAbstract()) { + // isAbstract means that we don't know the global's address. Since we + // can only form a pointer without knowing the address if the fields are + // trivially copyable, we need to return false otherwise. + return false; + } else { + Disc = llvm::ConstantInt::get(CGM.Int64Ty, + -(Layout.getFieldOffset(FieldNo) / 8)); + AddrDisc = Emitter.getCurrentAddrPrivate(); + } + EltInit = llvm::ConstantPtrAuth::get( + EltInit, llvm::ConstantInt::get(CGM.Int32Ty, 2), Disc, AddrDisc, + CGM.getPFPDeactivationSymbol(*Field)); + if (!CGM.getContext().arePFPFieldsTriviallyCopyable(RD)) + Emitter.registerCurrentAddrPrivate(EltInit, + cast(AddrDisc)); + } + if (ZeroInitPadding) { if (!DoZeroInitPadding(Layout, FieldNo, **Field, AllowOverwrite, SizeSoFar, ZeroFieldSize)) @@ -1654,7 +1681,20 @@ ConstantEmitter::emitAbstract(SourceLocation loc, const APValue &value, llvm::Constant *ConstantEmitter::tryEmitForInitializer(const VarDecl &D) { initializeNonAbstract(D.getType().getAddressSpace()); - return markIfFailed(tryEmitPrivateForVarInit(D)); + llvm::Constant *Init = tryEmitPrivateForVarInit(D); + + // If a placeholder address was needed for a TLS variable, implying that the + // initializer's value depends on its address, then the object may not be + // initialized in .tdata because the initializer will be memcpy'd to the + // thread's TLS. Instead the initialization must be done in code. + if (!PlaceholderAddresses.empty() && D.getTLSKind() != VarDecl::TLS_None) { + for (auto [_, GV] : PlaceholderAddresses) + GV->eraseFromParent(); + PlaceholderAddresses.clear(); + Init = nullptr; + } + + return markIfFailed(Init); } llvm::Constant *ConstantEmitter::tryEmitForInitializer(const Expr *E, @@ -2609,6 +2649,7 @@ CodeGenModule::getMemberPointerConstant(const UnaryOperator *uo) { return getCXXABI().EmitMemberFunctionPointer(method); // Otherwise, a member data pointer. + getContext().recordMemberDataPointerEvaluation(decl); uint64_t fieldOffset = getContext().getFieldOffset(decl); CharUnits chars = getContext().toCharUnitsFromBits((int64_t) fieldOffset); return getCXXABI().EmitMemberDataPointer(type, chars); diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 73232ae86b73..b14d9d7e8d06 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -47,6 +47,7 @@ #include "llvm/IR/Intrinsics.h" #include "llvm/IR/MDBuilder.h" #include "llvm/Support/CRC.h" +#include "llvm/Support/SipHash.h" #include "llvm/Support/xxhash.h" #include "llvm/Transforms/Scalar/LowerExpectIntrinsic.h" #include "llvm/Transforms/Utils/PromoteMemToReg.h" @@ -2251,6 +2252,36 @@ static void emitNonZeroVLAInit(CodeGenFunction &CGF, QualType baseType, CGF.EmitBlock(contBB); } +Address CodeGenFunction::EmitAddressOfPFPField(Address RecordPtr, + const PFPField &Field) { + return EmitAddressOfPFPField( + RecordPtr, + Builder.CreateConstInBoundsByteGEP(RecordPtr.withElementType(Int8Ty), + Field.Offset), + Field.Field); +} + +Address CodeGenFunction::EmitAddressOfPFPField(Address RecordPtr, + Address PtrPtr, + const FieldDecl *Field) { + llvm::Value *Disc; + if (CGM.getContext().arePFPFieldsTriviallyCopyable(Field->getParent())) { + uint64_t FieldSignature = + llvm::getPointerAuthStableSipHash(CGM.getPFPFieldName(Field)); + Disc = llvm::ConstantInt::get(CGM.Int64Ty, FieldSignature); + } else + Disc = Builder.CreatePtrToInt(RecordPtr.getBasePointer(), CGM.Int64Ty); + + llvm::GlobalValue *DS = CGM.getPFPDeactivationSymbol(Field); + llvm::OperandBundleDef DSBundle("deactivation-symbol", DS); + llvm::Value *Args[] = {PtrPtr.getBasePointer(), Disc, Builder.getTrue()}; + return Address( + Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::protected_field_ptr, + PtrPtr.getType()), + Args, DSBundle), + VoidPtrTy, PtrPtr.getAlignment()); +} + void CodeGenFunction::EmitNullInitialization(Address DestPtr, QualType Ty) { // Ignore empty classes in C++. @@ -2310,13 +2341,20 @@ CodeGenFunction::EmitNullInitialization(Address DestPtr, QualType Ty) { // Get and call the appropriate llvm.memcpy overload. Builder.CreateMemCpy(DestPtr, SrcPtr, SizeVal, false); - return; + } else { + // Otherwise, just memset the whole thing to zero. This is legal + // because in LLVM, all default initializers (other than the ones we just + // handled above, and the case handled below) are guaranteed to have a bit + // pattern of all zeros. + Builder.CreateMemSet(DestPtr, Builder.getInt8(0), SizeVal, false); } - // Otherwise, just memset the whole thing to zero. This is legal - // because in LLVM, all default initializers (other than the ones we just - // handled above) are guaranteed to have a bit pattern of all zeros. - Builder.CreateMemSet(DestPtr, Builder.getInt8(0), SizeVal, false); + // With the pointer field protection feature, null pointers do not have a bit + // pattern of zero in memory, so we must initialize them separately. + for (auto &Field : getContext().findPFPFields(Ty)) { + auto addr = EmitAddressOfPFPField(DestPtr, Field); + Builder.CreateStore(llvm::ConstantPointerNull::get(VoidPtrTy), addr); + } } llvm::BlockAddress *CodeGenFunction::GetAddrOfLabel(const LabelDecl *L) { @@ -3426,3 +3464,14 @@ void CodeGenFunction::addInstToNewSourceAtom(llvm::Instruction *KeyInstruction, DI->addInstToCurrentSourceAtom(KeyInstruction, Backup); } } + +void CodeGenFunction::emitPFPPostCopyUpdates(Address DestPtr, Address SrcPtr, + QualType Ty) { + for (auto &Field : getContext().findPFPFields(Ty)) { + if (getContext().arePFPFieldsTriviallyCopyable(Field.Field->getParent())) + continue; + auto DestFieldPtr = EmitAddressOfPFPField(DestPtr, Field); + auto SrcFieldPtr = EmitAddressOfPFPField(SrcPtr, Field); + Builder.CreateStore(Builder.CreateLoad(SrcFieldPtr), DestFieldPtr); + } +} diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index f769fee22787..ae2956eeac57 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -1740,6 +1740,10 @@ public: void addInstToNewSourceAtom(llvm::Instruction *KeyInstruction, llvm::Value *Backup); + /// Copy all PFP fields from SrcPtr to DestPtr while updating signatures, + /// assuming that DestPtr was already memcpy'd from SrcPtr. + void emitPFPPostCopyUpdates(Address DestPtr, Address SrcPtr, QualType Ty); + private: /// SwitchInsn - This is nearest current switch instruction. It is null if /// current context is not in a switch. @@ -5067,8 +5071,8 @@ public: /// Create a store to \arg DstPtr from \arg Src, truncating the stored value /// to at most \arg DstSize bytes. - void CreateCoercedStore(llvm::Value *Src, Address Dst, llvm::TypeSize DstSize, - bool DstIsVolatile); + void CreateCoercedStore(llvm::Value *Src, QualType SrcFETy, Address Dst, + llvm::TypeSize DstSize, bool DstIsVolatile); /// EmitExtendGCLifetime - Given a pointer to an Objective-C object, /// make sure it survives garbage collection until this point. @@ -5563,6 +5567,10 @@ public: void EmitRISCVMultiVersionResolver(llvm::Function *Resolver, ArrayRef Options); + Address EmitAddressOfPFPField(Address RecordPtr, const PFPField &Field); + Address EmitAddressOfPFPField(Address RecordPtr, Address FieldPtr, + const FieldDecl *Field); + private: QualType getVarArgType(const Expr *Arg); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 43b8af0b2156..bff2a5ba5d24 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -980,6 +980,7 @@ void CodeGenModule::Release() { applyGlobalValReplacements(); applyReplacements(); emitMultiVersionFunctions(); + emitPFPFieldsWithEvaluatedOffset(); if (Context.getLangOpts().IncrementalExtensions && GlobalTopLevelStmtBlockInFlight.first) { @@ -4896,6 +4897,40 @@ void CodeGenModule::emitMultiVersionFunctions() { emitMultiVersionFunctions(); } +// Symbols with this prefix are used as deactivation symbols for PFP fields. +// See clang/docs/StructureProtection.rst for more information. +static const char PFPDeactivationSymbolPrefix[] = "__pfp_ds_"; + +llvm::GlobalValue * +CodeGenModule::getPFPDeactivationSymbol(const FieldDecl *FD) { + std::string DSName = PFPDeactivationSymbolPrefix + getPFPFieldName(FD); + llvm::GlobalValue *DS = TheModule.getNamedValue(DSName); + if (!DS) { + DS = new llvm::GlobalVariable(TheModule, Int8Ty, false, + llvm::GlobalVariable::ExternalWeakLinkage, + nullptr, DSName); + DS->setVisibility(llvm::GlobalValue::HiddenVisibility); + } + return DS; +} + +void CodeGenModule::emitPFPFieldsWithEvaluatedOffset() { + llvm::Constant *Nop = llvm::ConstantExpr::getIntToPtr( + llvm::ConstantInt::get(Int64Ty, 0xd503201f), VoidPtrTy); + for (auto *FD : getContext().PFPFieldsWithEvaluatedOffset) { + std::string DSName = PFPDeactivationSymbolPrefix + getPFPFieldName(FD); + llvm::GlobalValue *OldDS = TheModule.getNamedValue(DSName); + llvm::GlobalValue *DS = llvm::GlobalAlias::create( + Int8Ty, 0, llvm::GlobalValue::ExternalLinkage, DSName, Nop, &TheModule); + DS->setVisibility(llvm::GlobalValue::HiddenVisibility); + if (OldDS) { + DS->takeName(OldDS); + OldDS->replaceAllUsesWith(DS); + OldDS->eraseFromParent(); + } + } +} + static void replaceDeclarationWith(llvm::GlobalValue *Old, llvm::Constant *New) { assert(cast(Old)->isDeclaration() && "Not a declaration"); @@ -8474,3 +8509,12 @@ void CodeGenModule::moveLazyEmissionStates(CodeGenModule *NewBuilder) { NewBuilder->ABI->MangleCtx = std::move(ABI->MangleCtx); } + +std::string CodeGenModule::getPFPFieldName(const FieldDecl *FD) { + std::string OutName; + llvm::raw_string_ostream Out(OutName); + getCXXABI().getMangleContext().mangleCanonicalTypeName( + getContext().getCanonicalTagType(FD->getParent()), Out, false); + Out << "." << FD->getName(); + return OutName; +} diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 3ed1dd7a5722..0081bf5c4cf5 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -1882,6 +1882,9 @@ public: std::optional StackProtectorAttribute(const Decl *D) const; + std::string getPFPFieldName(const FieldDecl *FD); + llvm::GlobalValue *getPFPDeactivationSymbol(const FieldDecl *FD); + private: bool shouldDropDLLAttribute(const Decl *D, const llvm::GlobalValue *GV) const; @@ -2085,6 +2088,10 @@ private: llvm::Metadata *CreateMetadataIdentifierImpl(QualType T, MetadataTypeMap &Map, StringRef Suffix); + + /// Emit deactivation symbols for any PFP fields whose offset is taken with + /// offsetof. + void emitPFPFieldsWithEvaluatedOffset(); }; } // end namespace CodeGen diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp index 7b267f0ff8b3..397db2ee5940 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -1258,6 +1258,7 @@ llvm::Constant *ItaniumCXXABI::EmitMemberPointer(const APValue &MP, return pointerAuthResignMemberFunctionPointer(Src, MPType, SrcType, CGM); } + getContext().recordMemberDataPointerEvaluation(MPD); CharUnits FieldOffset = getContext().toCharUnitsFromBits(getContext().getFieldOffset(MPD)); return EmitMemberDataPointer(MPT, ThisAdjustment + FieldOffset); diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp index 283b641b656f..06fce6171eb2 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -2968,6 +2968,7 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberPointer(const APValue &MP, // the class in which it was declared, and convert from there if necessary. // For indirect field decls, get the outermost anonymous field and use the // parent class. + Ctx.recordMemberDataPointerEvaluation(MPD); CharUnits FieldOffset = Ctx.toCharUnitsFromBits(Ctx.getFieldOffset(MPD)); const FieldDecl *FD = dyn_cast(MPD); if (!FD) diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 63ee1ed51d6b..528a38cb067c 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7738,6 +7738,24 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, Twine("-funique-source-file-identifier=") + Input.getBaseInput())); } + if (Args.hasFlag( + options::OPT_fexperimental_allow_pointer_field_protection_attr, + options::OPT_fno_experimental_allow_pointer_field_protection_attr, + false) || + Args.hasFlag(options::OPT_fexperimental_pointer_field_protection_abi, + options::OPT_fno_experimental_pointer_field_protection_abi, + false)) + CmdArgs.push_back("-fexperimental-allow-pointer-field-protection-attr"); + + if (!IsCudaDevice) { + Args.addOptInFlag( + CmdArgs, options::OPT_fexperimental_pointer_field_protection_abi, + options::OPT_fno_experimental_pointer_field_protection_abi); + Args.addOptInFlag( + CmdArgs, options::OPT_fexperimental_pointer_field_protection_tagged, + options::OPT_fno_experimental_pointer_field_protection_tagged); + } + // Setup statistics file output. SmallString<128> StatsFile = getStatsFileName(Args, Output, Input, D); if (!StatsFile.empty()) { diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 18c694579abd..1ccd74314f37 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -1509,6 +1509,11 @@ static void InitializePredefinedMacros(const TargetInfo &TI, if (LangOpts.Sanitize.has(SanitizerKind::AllocToken)) Builder.defineMacro("__SANITIZE_ALLOC_TOKEN__"); + if (LangOpts.PointerFieldProtectionABI) + Builder.defineMacro("__POINTER_FIELD_PROTECTION_ABI__"); + if (LangOpts.PointerFieldProtectionTagged) + Builder.defineMacro("__POINTER_FIELD_PROTECTION_TAGGED__"); + // Target OS macro definitions. if (PPOpts.DefineTargetOSMacros) { const llvm::Triple &Triple = TI.getTriple(); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 5dbff18fff7a..c97356d58019 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6756,6 +6756,10 @@ static void handleZeroCallUsedRegsAttr(Sema &S, Decl *D, const ParsedAttr &AL) { D->addAttr(ZeroCallUsedRegsAttr::Create(S.Context, Kind, AL)); } +static void handleNoPFPAttrField(Sema &S, Decl *D, const ParsedAttr &AL) { + D->addAttr(NoFieldProtectionAttr::Create(S.Context, AL)); +} + static void handleCountedByAttrField(Sema &S, Decl *D, const ParsedAttr &AL) { auto *FD = dyn_cast(D); assert(FD); @@ -7863,6 +7867,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, handleCountedByAttrField(S, D, AL); break; + case ParsedAttr::AT_NoFieldProtection: + handleNoPFPAttrField(S, D, AL); + break; + // Microsoft attributes: case ParsedAttr::AT_LayoutVersion: handleLayoutVersion(S, D, AL); @@ -8145,6 +8153,14 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_GCCStruct: handleGCCStructAttr(S, D, AL); break; + + case ParsedAttr::AT_PointerFieldProtection: + if (!S.getLangOpts().PointerFieldProtectionAttr) + S.Diag(AL.getLoc(), + diag::err_attribute_pointer_field_protection_experimental) + << AL << AL.isRegularKeywordAttribute() << D->getLocation(); + handleSimpleAttribute(S, D, AL); + break; } } diff --git a/clang/test/CodeGenCXX/pfp-attribute-disable.cpp b/clang/test/CodeGenCXX/pfp-attribute-disable.cpp new file mode 100644 index 000000000000..4a7ec7974636 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-attribute-disable.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s + + +struct S { + int* ptr1; + __attribute__((no_field_protection)) int* ptr2; +private: + int private_data; +}; // Not Standard-layout, mixed access + +// CHECK-LABEL: load_pointers_without_no_field_protection +int* load_pointers_without_no_field_protection(S *t) { + return t->ptr1; +} +// CHECK: call {{.*}} @llvm.protected.field.ptr.p0{{.*}} + +// CHECK-LABEL: load_pointers_with_no_field_protection +int* load_pointers_with_no_field_protection(S *t) { + return t->ptr2; +} +// CHECK-NOT: call {{.*}} @llvm.protected.field.ptr.p0{{.*}} + +// CHECK-LABEL: store_pointers_without_no_field_protection +void store_pointers_without_no_field_protection(S *t, int *input) { + t->ptr1 = input; +} +// CHECK: call {{.*}} @llvm.protected.field.ptr.p0{{.*}} + +// CHECK-LABEL: store_pointers_with_no_field_protection +void store_pointers_with_no_field_protection(S *t, int *input) { + t->ptr2 = input; +} +// CHECK-NOT: call {{.*}} @llvm.protected.field.ptr.p0{{.*}} diff --git a/clang/test/CodeGenCXX/pfp-coerce.cpp b/clang/test/CodeGenCXX/pfp-coerce.cpp new file mode 100644 index 000000000000..60f8ef5a8242 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-coerce.cpp @@ -0,0 +1,157 @@ +// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-allow-pointer-field-protection-attr -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s + +// Non-standard layout. Pointer fields are signed and discriminated by type. +struct Pointer { + int* ptr; +private: + int private_data; +}; + +void pass_pointer_callee(Pointer p); + +// CHECK: define dso_local void @_Z12pass_pointerP7Pointer( +void pass_pointer(Pointer *pp) { + // CHECK: %0 = load ptr, ptr %pp.addr, align 8 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %agg.tmp, ptr align 8 %0, i64 16, i1 false) + // CHECK: %1 = getelementptr inbounds i8, ptr %agg.tmp, i64 0 + // CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // CHECK: %3 = load ptr, ptr %2, align 8 + // CHECK: %4 = ptrtoint ptr %3 to i64 + // CHECK: %5 = insertvalue [2 x i64] poison, i64 %4, 0 + // CHECK: %6 = getelementptr inbounds i8, ptr %agg.tmp, i64 8 + // CHECK: %7 = load i64, ptr %6, align 8 + // CHECK: %8 = insertvalue [2 x i64] %5, i64 %7, 1 + // CHECK: call void @_Z19pass_pointer_callee7Pointer([2 x i64] %8) + + pass_pointer_callee(*pp); +} + +// CHECK: define dso_local void @_Z14passed_pointer7PointerPS_([2 x i64] %p.coerce, ptr noundef %pp) +void passed_pointer(Pointer p, Pointer *pp) { + // CHECK: %p = alloca %struct.Pointer, align 8 + // CHECK: %pp.addr = alloca ptr, align 8 + // CHECK: %0 = extractvalue [2 x i64] %p.coerce, 0 + // CHECK: %1 = getelementptr inbounds i8, ptr %p, i64 0 + // CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // CHECK: %3 = inttoptr i64 %0 to ptr + // CHECK: store ptr %3, ptr %2, align 8 + // CHECK: %4 = extractvalue [2 x i64] %p.coerce, 1 + // CHECK: %5 = getelementptr inbounds i8, ptr %p, i64 8 + // CHECK: store i64 %4, ptr %5, align 8 + // CHECK: store ptr %pp, ptr %pp.addr, align 8 + // CHECK: %6 = load ptr, ptr %pp.addr, align 8 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %6, ptr align 8 %p, i64 12, i1 false) + + *pp = p; +} + +// CHECK: define dso_local [2 x i64] @_Z14return_pointerP7Pointer(ptr noundef %pp) +Pointer return_pointer(Pointer *pp) { + // CHECK: %retval = alloca %struct.Pointer, align 8 + // CHECK: %pp.addr = alloca ptr, align 8 + // CHECK: store ptr %pp, ptr %pp.addr, align 8 + // CHECK: %0 = load ptr, ptr %pp.addr, align 8 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %retval, ptr align 8 %0, i64 16, i1 false) + // CHECK: %1 = getelementptr inbounds i8, ptr %retval, i64 0 + // CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // CHECK: %3 = load ptr, ptr %2, align 8 + // CHECK: %4 = ptrtoint ptr %3 to i64 + // CHECK: %5 = insertvalue [2 x i64] poison, i64 %4, 0 + // CHECK: %6 = getelementptr inbounds i8, ptr %retval, i64 8 + // CHECK: %7 = load i64, ptr %6, align 8 + // CHECK: %8 = insertvalue [2 x i64] %5, i64 %7, 1 + // CHECK: ret [2 x i64] %8 + + return *pp; +} + +Pointer returned_pointer_callee(); + +// CHECK: define dso_local void @_Z16returned_pointerP7Pointer(ptr noundef %pp) +void returned_pointer(Pointer *pp) { + // CHECK: %pp.addr = alloca ptr, align 8 + // CHECK: %ref.tmp = alloca %struct.Pointer, align 8 + // CHECK: store ptr %pp, ptr %pp.addr, align 8 + // CHECK: %call = call [2 x i64] @_Z23returned_pointer_calleev() + // CHECK: %0 = extractvalue [2 x i64] %call, 0 + // CHECK: %1 = getelementptr inbounds i8, ptr %ref.tmp, i64 0 + // CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // CHECK: %3 = inttoptr i64 %0 to ptr + // CHECK: store ptr %3, ptr %2, align 8 + // CHECK: %4 = extractvalue [2 x i64] %call, 1 + // CHECK: %5 = getelementptr inbounds i8, ptr %ref.tmp, i64 8 + // CHECK: store i64 %4, ptr %5, align 8 + // CHECK: %6 = load ptr, ptr %pp.addr, align 8 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %6, ptr align 8 %ref.tmp, i64 12, i1 false) + + *pp = returned_pointer_callee(); +} + +union PointerUnion { + Pointer ptr; +}; + +void pass_pointer_union_callee(PointerUnion pu); + +// CHECK: define dso_local void @_Z18pass_pointer_unionP12PointerUnion( +void pass_pointer_union(PointerUnion *pup) { + // CHECK: %0 = load ptr, ptr %pup.addr, align 8 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %agg.tmp, ptr align 8 %0, i64 16, i1 false) + + // CHECK: %coerce.dive = getelementptr inbounds nuw %union.PointerUnion, ptr %agg.tmp, i32 0, i32 0 + // CHECK: %1 = load [2 x i64], ptr %coerce.dive, align 8 + // CHECK: call void @_Z25pass_pointer_union_callee12PointerUnion([2 x i64] %1) + + pass_pointer_union_callee(*pup); +} + +// Manual opt into PFP, non-trivially destructible. +// Pointer fields are signed and discriminated by address. +// Trivial ABI: passed and returned by value despite being non-trivial. +struct [[clang::trivial_abi]] [[clang::pointer_field_protection]] TrivialAbiPointer { + int *ptr; + ~TrivialAbiPointer(); +}; + +// CHECK: define dso_local void @_Z24pass_trivial_abi_pointer17TrivialAbiPointerPS_(ptr %p.coerce, ptr noundef %pp) +void pass_trivial_abi_pointer(TrivialAbiPointer p, TrivialAbiPointer *pp) { + // CHECK: %p = alloca %struct.TrivialAbiPointer, align 8 + // CHECK: %pp.addr = alloca ptr, align 8 + // CHECK: %coerce.dive = getelementptr inbounds nuw %struct.TrivialAbiPointer, ptr %p, i32 0, i32 0 + // CHECK: %0 = getelementptr inbounds i8, ptr %coerce.dive, i64 0 + // CHECK: %1 = ptrtoint ptr %coerce.dive to i64 + // CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %0, i64 %1, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // CHECK: store ptr %p.coerce, ptr %2, align 8 + // CHECK: store ptr %pp, ptr %pp.addr, align 8 + // CHECK: %3 = load ptr, ptr %pp.addr, align 8 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %3, ptr align 8 %p, i64 8, i1 false) + // CHECK: %4 = getelementptr inbounds i8, ptr %3, i64 0 + // CHECK: %5 = ptrtoint ptr %3 to i64 + // CHECK: %6 = call ptr @llvm.protected.field.ptr.p0(ptr %4, i64 %5, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // CHECK: %7 = getelementptr inbounds i8, ptr %p, i64 0 + // CHECK: %8 = ptrtoint ptr %p to i64 + // CHECK: %9 = call ptr @llvm.protected.field.ptr.p0(ptr %7, i64 %8, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // CHECK: %10 = load ptr, ptr %9, align 8 + // CHECK: store ptr %10, ptr %6, align 8 + // CHECK: call void @_ZN17TrivialAbiPointerD1Ev(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) %p) + + *pp = p; +} + +// CHECK: define dso_local i64 @_Z26return_trivial_abi_pointerP17TrivialAbiPointer(ptr noundef %pp) +TrivialAbiPointer return_trivial_abi_pointer(TrivialAbiPointer *pp) { + // CHECK: %retval = alloca %struct.TrivialAbiPointer, align 8 + // CHECK: %pp.addr = alloca ptr, align 8 + // CHECK: store ptr %pp, ptr %pp.addr, align 8 + // CHECK: %0 = load ptr, ptr %pp.addr, align 8 + // CHECK: call void @_ZN17TrivialAbiPointerC1ERKS_(ptr noundef nonnull align 8 dereferenceable(8) %retval, ptr noundef nonnull align 8 dereferenceable(8) %0) + // CHECK: %1 = getelementptr inbounds i8, ptr %retval, i64 0 + // CHECK: %2 = ptrtoint ptr %retval to i64 + // CHECK: %3 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 %2, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // CHECK: %4 = load ptr, ptr %3, align 8 + // CHECK: %5 = ptrtoint ptr %4 to i64 + // CHECK: ret i64 %5 + + return *pp; +} + diff --git a/clang/test/CodeGenCXX/pfp-load-store.cpp b/clang/test/CodeGenCXX/pfp-load-store.cpp new file mode 100644 index 000000000000..bc947cdd0cf6 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-load-store.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -O1 -o - %s | FileCheck %s + +int val; + +struct Pointer { + int* ptr; +private: + int private_data; +}; + +struct ArrayType { + int* array[3]; +private: + int private_data; +}; + +struct Array { + ArrayType array; +private: + int private_data; +}; + +struct Struct { + Pointer ptr; +}; + +// CHECK-LABEL: test_pointer +Pointer test_pointer(Pointer t) { + t.ptr = &val; + return t; +} +// CHECK: call {{.*}} @llvm.protected.field.ptr.p0{{.*}} + + + +// CHECK-LABEL: test_struct +int* test_struct(Struct *t) { + return (t->ptr).ptr; +} +// CHECK: call {{.*}} @llvm.protected.field.ptr.p0{{.*}} diff --git a/clang/test/CodeGenCXX/pfp-member-pointer-offsetof.cpp b/clang/test/CodeGenCXX/pfp-member-pointer-offsetof.cpp new file mode 100644 index 000000000000..842bdd7e9f83 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-member-pointer-offsetof.cpp @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -triple aarch64-linux-gnu -emit-llvm -fexperimental-allow-pointer-field-protection-attr -fexperimental-pointer-field-protection-abi -o - %s | FileCheck %s + +// CHECK: @__pfp_ds__ZTS1S.ptr1 = hidden alias i8, inttoptr (i64 3573751839 to ptr) +// CHECK: @__pfp_ds__ZTS1S.ptr2 = hidden alias i8, inttoptr (i64 3573751839 to ptr) + +struct [[clang::pointer_field_protection]] S { + int *ptr1; + int *ptr2; +}; + +void f() { + &S::ptr1; + __builtin_offsetof(S, ptr2); +} diff --git a/clang/test/CodeGenCXX/pfp-memcpy.cpp b/clang/test/CodeGenCXX/pfp-memcpy.cpp new file mode 100644 index 000000000000..0f97747e7917 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-memcpy.cpp @@ -0,0 +1,59 @@ +// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s + +struct ClassWithTrivialCopy { + ClassWithTrivialCopy(); + ~ClassWithTrivialCopy(); + void *a; +private: + void *c; +}; + +// Make sure that trivial assignments and copies include protected field copies. +// CHECK-LABEL: define dso_local void @_Z14trivial_assignP20ClassWithTrivialCopyS0_ +void trivial_assign(ClassWithTrivialCopy *s1, ClassWithTrivialCopy *s2) { + // CHECK: %0 = load ptr, ptr %s2.addr, align 8 + // CHECK-NEXT: %1 = load ptr, ptr %s1.addr, align 8 + // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %1, ptr align 8 %0, i64 16, i1 false) + // CHECK-NEXT: %2 = getelementptr inbounds i8, ptr %1, i64 0 + // CHECK-NEXT: %3 = ptrtoint ptr %1 to i64 + // CHECK-NEXT: %4 = call ptr @llvm.protected.field.ptr.p0(ptr %2, i64 %3, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS20ClassWithTrivialCopy.a) ] + // CHECK-NEXT: %5 = getelementptr inbounds i8, ptr %0, i64 0 + // CHECK-NEXT: %6 = ptrtoint ptr %0 to i64 + // CHECK-NEXT: %7 = call ptr @llvm.protected.field.ptr.p0(ptr %5, i64 %6, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS20ClassWithTrivialCopy.a) ] + // CHECK-NEXT: %8 = load ptr, ptr %7, align 8 + // CHECK-NEXT: store ptr %8, ptr %4, align 8 + // CHECK-NEXT: %9 = getelementptr inbounds i8, ptr %1, i64 8 + // CHECK-NEXT: %10 = ptrtoint ptr %1 to i64 + // CHECK-NEXT: %11 = call ptr @llvm.protected.field.ptr.p0(ptr %9, i64 %10, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS20ClassWithTrivialCopy.c) ] + // CHECK-NEXT: %12 = getelementptr inbounds i8, ptr %0, i64 8 + // CHECK-NEXT: %13 = ptrtoint ptr %0 to i64 + // CHECK-NEXT: %14 = call ptr @llvm.protected.field.ptr.p0(ptr %12, i64 %13, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS20ClassWithTrivialCopy.c) ] + // CHECK-NEXT: %15 = load ptr, ptr %14, align 8 + // CHECK-NEXT: store ptr %15, ptr %11, align 8 + *s1 = *s2; +} + +void trivial_copy(ClassWithTrivialCopy *s1) { + ClassWithTrivialCopy s2(*s1); +} + +// CHECK-LABEL: define linkonce_odr void @_ZN20ClassWithTrivialCopyC2ERKS_ +// CHECK: %this1 = load ptr, ptr %this.addr, align 8 +// CHECK-NEXT: %a = getelementptr inbounds nuw %struct.ClassWithTrivialCopy, ptr %this1, i32 0, i32 0 +// CHECK-NEXT: %1 = ptrtoint ptr %this1 to i64 +// CHECK-NEXT: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %a, i64 %1, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS20ClassWithTrivialCopy.a) ] +// CHECK-NEXT: %3 = load ptr, ptr %.addr, align 8, !nonnull !2, !align !3 +// CHECK-NEXT: %a2 = getelementptr inbounds nuw %struct.ClassWithTrivialCopy, ptr %3, i32 0, i32 0 +// CHECK-NEXT: %4 = ptrtoint ptr %3 to i64 +// CHECK-NEXT: %5 = call ptr @llvm.protected.field.ptr.p0(ptr %a2, i64 %4, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS20ClassWithTrivialCopy.a) ] +// CHECK-NEXT: %6 = load ptr, ptr %5, align 8 +// CHECK-NEXT: store ptr %6, ptr %2, align 8 +// CHECK-NEXT: %c = getelementptr inbounds nuw %struct.ClassWithTrivialCopy, ptr %this1, i32 0, i32 1 +// CHECK-NEXT: %7 = ptrtoint ptr %this1 to i64 +// CHECK-NEXT: %8 = call ptr @llvm.protected.field.ptr.p0(ptr %c, i64 %7, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS20ClassWithTrivialCopy.c) ] +// CHECK-NEXT: %9 = load ptr, ptr %.addr, align 8, !nonnull !2, !align !3 +// CHECK-NEXT: %c3 = getelementptr inbounds nuw %struct.ClassWithTrivialCopy, ptr %9, i32 0, i32 1 +// CHECK-NEXT: %10 = ptrtoint ptr %9 to i64 +// CHECK-NEXT: %11 = call ptr @llvm.protected.field.ptr.p0(ptr %c3, i64 %10, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS20ClassWithTrivialCopy.c) ] +// CHECK-NEXT: %12 = load ptr, ptr %11, align 8 +// CHECK-NEXT: store ptr %12, ptr %8, align 8 diff --git a/clang/test/CodeGenCXX/pfp-null-init.cpp b/clang/test/CodeGenCXX/pfp-null-init.cpp new file mode 100644 index 000000000000..50f96726c466 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-null-init.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s + +struct S { + void *p; +private: + int private_data; +}; + +// CHECK-LABEL: null_init +void null_init() { + // Check that null initialization was correctly applied to the pointer field. + // CHECK: %s = alloca %struct.S, align 8 + // CHECK: call void @llvm.memset.p0.i64(ptr align 8 %s, i8 0, i64 16, i1 false) + // CHECK: %0 = getelementptr inbounds i8, ptr %s, i64 0 + // CHECK: %1 = call ptr @llvm.protected.field.ptr.p0(ptr %0, i64 29832, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.p) ] + // CHECK: store ptr null, ptr %1, align 8 + S s{}; +} + diff --git a/clang/test/CodeGenCXX/pfp-struct-gep.cpp b/clang/test/CodeGenCXX/pfp-struct-gep.cpp new file mode 100644 index 000000000000..427cdf852009 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-struct-gep.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s + +struct S { + int* ptr; +private: + int private_data; +}; // Not Standard-layout, mixed access + +// CHECK-LABEL: load_pointers +int* load_pointers(S *t) { + // CHECK: %t.addr = alloca ptr, align 8 + // CHECK: store ptr %t, ptr %t.addr, align 8 + // CHECK: %0 = load ptr, ptr %t.addr, align 8 + // CHECK: %ptr = getelementptr inbounds nuw %struct.S, ptr %0, i32 0, i32 0 + // CHECK: %1 = call ptr @llvm.protected.field.ptr.p0(ptr %ptr, i64 63261, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.ptr) ] + // CHECK: %2 = load ptr, ptr %1, align 8 + // CHECK: ret ptr %2 + return t->ptr; +} + +// CHECK-LABEL: store_pointers +void store_pointers(S* t, int* p) { + // CHECK: %t.addr = alloca ptr, align 8 + // CHECK: %p.addr = alloca ptr, align 8 + // CHECK: store ptr %t, ptr %t.addr, align 8 + // CHECK: store ptr %p, ptr %p.addr, align 8 + // CHECK: %0 = load ptr, ptr %p.addr, align 8 + // CHECK: %1 = load ptr, ptr %t.addr, align 8 + // CHECK: %ptr = getelementptr inbounds nuw %struct.S, ptr %1, i32 0, i32 0 + // CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %ptr, i64 63261, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.ptr) ] + // CHECK: store ptr %0, ptr %2, align 8 + t->ptr = p; +} diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index cff9073f9161..fde8f14f5cd8 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -125,6 +125,7 @@ // CHECK-NEXT: NoDestroy (SubjectMatchRule_variable) // CHECK-NEXT: NoDuplicate (SubjectMatchRule_function) // CHECK-NEXT: NoEscape (SubjectMatchRule_variable_is_parameter) +// CHECK-NEXT: NoFieldProtection (SubjectMatchRule_field) // CHECK-NEXT: NoInline (SubjectMatchRule_function) // CHECK-NEXT: NoInstrumentFunction (SubjectMatchRule_function, SubjectMatchRule_objc_method) // CHECK-NEXT: NoMerge (SubjectMatchRule_function, SubjectMatchRule_variable) @@ -183,6 +184,7 @@ // CHECK-NEXT: PassObjectSize (SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: PatchableFunctionEntry (SubjectMatchRule_function, SubjectMatchRule_objc_method) // CHECK-NEXT: Pointer (SubjectMatchRule_record_not_is_union) +// CHECK-NEXT: PointerFieldProtection (SubjectMatchRule_record) // CHECK-NEXT: PreserveNone (SubjectMatchRule_hasType_functionType) // CHECK-NEXT: RandomizeLayout (SubjectMatchRule_record) // CHECK-NEXT: ReadOnlyPlacement (SubjectMatchRule_record) diff --git a/clang/test/Preprocessor/pfp-predefines.c b/clang/test/Preprocessor/pfp-predefines.c new file mode 100644 index 000000000000..ec3ba0a524f5 --- /dev/null +++ b/clang/test/Preprocessor/pfp-predefines.c @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -E -dM -triple aarch64-unknown-linux -fexperimental-pointer-field-protection-abi %s | FileCheck %s --check-prefix=PFP +// RUN: %clang_cc1 -E -dM -triple aarch64-unknown-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged %s | FileCheck %s --check-prefixes=PFP,PFP-TAGGED + +// PFP: #define __POINTER_FIELD_PROTECTION_ABI__ 1 +// PFP-TAGGED: #define __POINTER_FIELD_PROTECTION_TAGGED__ 1 diff --git a/clang/test/SemaCXX/attr-pointer-field-protection.cpp b/clang/test/SemaCXX/attr-pointer-field-protection.cpp new file mode 100644 index 000000000000..3917002c7290 --- /dev/null +++ b/clang/test/SemaCXX/attr-pointer-field-protection.cpp @@ -0,0 +1,6 @@ +// RUN: %clang_cc1 -triple aarch64-unknown-linux -fsyntax-only -verify=disabled %s +// RUN: %clang_cc1 -fexperimental-allow-pointer-field-protection-attr -triple aarch64-unknown-linux -fsyntax-only -verify=enabled %s + +struct [[clang::pointer_field_protection]] S {}; // disabled-error {{this attribute is experimental and must be explicitly enabled with flag -fexperimental-allow-pointer-field-protection-attr}} + +// enabled-no-diagnostics