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 +`_. +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 { @@ -3802,6 +3808,22 @@ OPT_LIST(V) StringRef getCUIDHash() const; + void findPFPFields(QualType Ty, CharUnits Offset, + std::vector &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 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, 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 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(RD) || + cast(RD)->hasTrivialDestructor(); + return true; +} + +void ASTContext::findPFPFields(QualType Ty, CharUnits Offset, + std::vector &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 PFPFields; + findPFPFields(Ty, CharUnits::Zero(), PFPFields, true); + return !PFPFields.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 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()) { + 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(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 = createBasicBlock("pfp.relocate.loop"); + BasicBlock *LoopEnd = createBasicBlock("pfp.relocate.loop.end"); + Builder.CreateCondBr( + Builder.CreateICmpEQ(SizeVal, + ConstantInt::get(SizeVal->getType(), 0)), + LoopEnd, Loop); + + EmitBlock(Loop); + PHINode *Offset = Builder.CreatePHI(SizeVal->getType(), 2); + Offset->addIncoming(ConstantInt::get(SizeVal->getType(), 0), Entry); + Address DestRec = Dest.withPointer( + Builder.CreateInBoundsGEP(Int8Ty, Dest.getBasePointer(), {Offset}), + KnownNonNull); + Address SrcRec = Src.withPointer( + Builder.CreateInBoundsGEP(Int8Ty, Src.getBasePointer(), {Offset}), + KnownNonNull); + emitPFPTrivialRelocation(DestRec, SrcRec, + E->getArg(0)->getType()->getPointeeType()); + + Value *NextOffset = Builder.CreateAdd(Offset, TypeSize); + Offset->addIncoming(NextOffset, Loop); + Builder.CreateCondBr(Builder.CreateICmpEQ(NextOffset, SizeVal), LoopEnd, + Loop); + + EmitBlock(LoopEnd); + } + } return RValue::get(Dest, *this); } case Builtin::BImemset: diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 4a9025b6e0b0f..81f874afd59aa 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -1310,6 +1310,74 @@ static llvm::Value *CoerceIntOrPtrToIntOrPtr(llvm::Value *Val, llvm::Type *Ty, return Val; } +static std::vector findPFPCoercedFields(CodeGenFunction &CGF, + QualType SrcFETy) { + // Coercion directly through memory does not work if the structure has pointer + // field protection because the struct in registers has a different bit + // pattern to the struct in memory, so we must read the elements one by one + // and use them to form the coerced structure. + std::vector PFPFields; + CGF.getContext().findPFPFields(SrcFETy, CharUnits::Zero(), PFPFields, + /*IncludeVBases=*/true); + + // Because we don't know which union member is selected, we don't modify the + // in-memory representation when passing a pointer that is part of a union + // field. This requires the union member to be trivially copyable; + // non-trivially-copyable unions cannot be directly passed by value. + llvm::erase_if(PFPFields, [](PFPField F) { return F.isWithinUnion; }); + return PFPFields; +} + +static llvm::Value *CreatePFPCoercedLoad(Address Src, QualType SrcFETy, + llvm::Type *Ty, CodeGenFunction &CGF) { + std::vector PFPFields = findPFPCoercedFields(CGF, SrcFETy); + if (PFPFields.empty()) + return nullptr; + + auto LoadCoercedField = [&](CharUnits Offset, + llvm::Type *FieldType) -> llvm::Value * { + 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); + }; + 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; + } + if (auto *AT = dyn_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 i = 0; i != AT->getNumElements(); ++i, Offset += wordSize) + Val = CGF.Builder.CreateInsertValue(Val, LoadCoercedField(Offset, ET), i); + return Val; + } + auto *ST = cast(Ty); + llvm::Value *Val = llvm::PoisonValue::get(ST); + auto *SL = CGF.CGM.getDataLayout().getStructLayout(ST); + for (unsigned i = 0; i != ST->getNumElements(); ++i) { + CharUnits Offset = CharUnits::fromQuantity(SL->getElementOffset(i)); + Val = CGF.Builder.CreateInsertValue( + Val, LoadCoercedField(Offset, ST->getElementType(i)), i); + } + 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. @@ -1317,14 +1385,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)) { @@ -1396,8 +1467,54 @@ 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 = findPFPCoercedFields(CGF, 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); + } + }; + + 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 if (auto *AT = dyn_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)); + } else { + auto *ST = cast(SrcTy); + auto *SL = CGF.CGM.getDataLayout().getStructLayout(ST); + for (unsigned i = 0; i != ST->getNumElements(); ++i) { + CharUnits Offset = CharUnits::fromQuantity(SL->getElementOffset(i)); + 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; @@ -1417,6 +1534,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())) { @@ -3444,6 +3564,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. @@ -3451,7 +3578,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()), @@ -4103,7 +4230,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 @@ -4152,7 +4279,7 @@ void CodeGenFunction::EmitFunctionEpilog( auto eltAddr = Builder.CreateStructGEP(addr, i); llvm::Value *elt = CreateCoercedLoad( - eltAddr, + eltAddr, RetTy, unpaddedStruct ? unpaddedStruct->getElementType(unpaddedIndex++) : unpaddedCoercionType, *this); @@ -5627,15 +5754,25 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, } else { uint64_t SrcSize = SrcTypeSize.getFixedValue(); uint64_t DstSize = DstTypeSize.getFixedValue(); + bool HasPFPFields = !findPFPCoercedFields(*this, I->Ty).empty(); // 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); @@ -5654,7 +5791,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 @@ -5714,7 +5851,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); @@ -6246,7 +6383,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 826f5799bae3c..a524ee2f72c08 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -585,12 +585,20 @@ 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. + if (!CGM.getContext().arePFPFieldsTriviallyCopyable(D->getParent()) && + CGM.getContext().hasPFPFields( + CGM.getContext().getCanonicalTagType(D->getParent()))) + return false; + // We can emit a memcpy for a trivial copy or move constructor/assignment. if (D->isTrivial() && !D->getParent()->mayInsertExtraPadding()) return true; @@ -656,7 +664,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 @@ -923,6 +932,11 @@ namespace { 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; } @@ -1060,7 +1074,8 @@ namespace { 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; @@ -1171,7 +1186,7 @@ namespace { 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) @@ -2314,7 +2329,7 @@ void CodeGenFunction::EmitCXXConstructorCall(const CXXConstructorDecl *D, // 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); @@ -2382,7 +2397,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 31d6948f08ad5..b8c1608ef43f0 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -5328,12 +5328,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); @@ -5348,6 +5349,16 @@ static Address emitAddrOfFieldStorage(CodeGenFunction &CGF, Address base, return CGF.Builder.CreateStructGEP(base, idx, field->getName()); } +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 7cc4d6c8f06f6..a4969660fbd6d 100644 --- a/clang/lib/CodeGen/CGExprAgg.cpp +++ b/clang/lib/CodeGen/CGExprAgg.cpp @@ -138,7 +138,7 @@ class AggExprEmitter : public StmtVisitor { 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()), @@ -2359,6 +2359,7 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, LValue Src, QualType Ty, auto *Inst = Builder.CreateMemCpy(DestPtr, SrcPtr, SizeVal, isVolatile); addInstToCurrentSourceAtom(Inst, nullptr); + emitPFPTrivialRelocation(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 0eec4dba4824a..ca80fd91fc5c5 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,29 @@ 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()) { + 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)) @@ -1656,7 +1680,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 &entry : PlaceholderAddresses) + entry.second->eraseFromParent(); + PlaceholderAddresses.clear(); + Init = nullptr; + } + + return markIfFailed(Init); } llvm::Constant *ConstantEmitter::tryEmitForInitializer(const Expr *E, @@ -2611,6 +2648,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 ac25bd95f0463..d5b82a663937b 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -46,6 +46,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" @@ -2232,6 +2233,45 @@ 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; + bool IsPAuthSupported = getContext().getTargetInfo().getTriple().getArch() == + llvm::Triple::aarch64; + if (!IsPAuthSupported || + CGM.getContext().arePFPFieldsTriviallyCopyable(Field->getParent())) { + uint64_t FieldSignature = + llvm::getPointerAuthStableSipHash(CGM.getPFPFieldName(Field)); + if (!IsPAuthSupported) + FieldSignature &= 0xff; + 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); + + return Address( + Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::protected_field_ptr, + PtrPtr.getType()), + {PtrPtr.getBasePointer(), Disc, + IsPAuthSupported ? Builder.getTrue() : Builder.getFalse()}, + DSBundle), + VoidPtrTy, PtrPtr.getAlignment()); +} + void CodeGenFunction::EmitNullInitialization(Address DestPtr, QualType Ty) { // Ignore empty classes in C++. @@ -2291,13 +2331,22 @@ 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. + std::vector PFPFields; + getContext().findPFPFields(Ty, CharUnits::Zero(), PFPFields, true); + for (auto &Field : PFPFields) { + auto addr = EmitAddressOfPFPField(DestPtr, Field); + Builder.CreateStore(llvm::ConstantPointerNull::get(VoidPtrTy), addr); + } } llvm::BlockAddress *CodeGenFunction::GetAddrOfLabel(const LabelDecl *L) { @@ -3400,3 +3449,16 @@ void CodeGenFunction::addInstToNewSourceAtom(llvm::Instruction *KeyInstruction, DI->addInstToCurrentSourceAtom(KeyInstruction, Backup); } } + +void CodeGenFunction::emitPFPTrivialRelocation(Address DestPtr, Address SrcPtr, + QualType Ty) { + std::vector PFPFields; + getContext().findPFPFields(Ty, CharUnits::Zero(), PFPFields, true); + for (auto &Field : PFPFields) { + 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 10238ffd3971c..bfe70cc4d5eaf 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -1714,6 +1714,10 @@ class CodeGenFunction : public CodeGenTypeCache { void addInstToNewSourceAtom(llvm::Instruction *KeyInstruction, llvm::Value *Backup); + /// Trivially relocate all PFP fields from SrcPtr (of type Ty) to DestPtr, + /// assuming that DestPtr was already memcpy'd from SrcPtr. + void emitPFPTrivialRelocation(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. @@ -5039,8 +5043,8 @@ class CodeGenFunction : public CodeGenTypeCache { /// 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. @@ -5535,6 +5539,10 @@ class CodeGenFunction : public CodeGenTypeCache { 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 c061575af411c..4fdfe244a628f 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -965,6 +965,7 @@ void CodeGenModule::Release() { applyGlobalValReplacements(); applyReplacements(); emitMultiVersionFunctions(); + emitPFPFieldsWithEvaluatedOffset(); if (Context.getLangOpts().IncrementalExtensions && GlobalTopLevelStmtBlockInFlight.first) { @@ -4761,6 +4762,36 @@ void CodeGenModule::emitMultiVersionFunctions() { emitMultiVersionFunctions(); } +llvm::GlobalValue * +CodeGenModule::getPFPDeactivationSymbol(const FieldDecl *FD) { + std::string DSName = "__pfp_ds_" + 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 = "__pfp_ds_" + 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"); @@ -8344,3 +8375,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 e36ad87e67e3b..02d21f9a98564 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -1840,6 +1840,9 @@ class CodeGenModule : public CodeGenTypeCache { return TrapReasonBuilder(&getDiags(), DiagID, TR); } + std::string getPFPFieldName(const FieldDecl *FD); + llvm::GlobalValue *getPFPDeactivationSymbol(const FieldDecl *FD); + private: bool shouldDropDLLAttribute(const Decl *D, const llvm::GlobalValue *GV) const; @@ -2043,6 +2046,10 @@ class CodeGenModule : public CodeGenTypeCache { 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 c5db314575810..746ae02b25726 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -1257,6 +1257,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 edefaff0ca1ac..503585403c084 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -2964,6 +2964,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 310f3b58a211e..6f10c14391145 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7575,6 +7575,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 8253fad9e5503..4f26f107921e0 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 df4aa0e6189c4..c252a8c162a25 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6639,6 +6639,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); @@ -7742,6 +7746,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); @@ -8021,6 +8029,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/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp index 38877967af05e..5aca197d616ca 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -236,6 +236,22 @@ static bool IsEligibleForReplacement(Sema &SemaRef, const CXXRecordDecl *D) { return !D->hasDeletedDestructor(); } +static bool IsImplementationDefinedNonRelocatable(Sema &SemaRef, + const CXXRecordDecl *D) { + // The implementation-defined carveout only exists for polymorphic types. + if (!D->isPolymorphic()) + return false; + + std::vector pfpFields; + SemaRef.Context.findPFPFields(SemaRef.Context.getCanonicalTagType(D), + CharUnits::Zero(), pfpFields, true); + for (PFPField f : pfpFields) + if (f.isWithinUnion) + return true; + + return false; +} + ASTContext::CXXRecordDeclRelocationInfo Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) { ASTContext::CXXRecordDeclRelocationInfo Info{false, false}; @@ -277,6 +293,11 @@ Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) { if (!IsEligibleForTrivialRelocation(*this, D)) return false; + // if it does not meet implementation-defined criteria for inhibiting + // trivially-relocatable, + if (IsImplementationDefinedNonRelocatable(*this, D)) + return false; + // has the trivially_relocatable_if_eligible class-property-specifier, if (D->hasAttr()) return true; diff --git a/clang/test/CodeGenCXX/pfp-attribute-disable.cpp b/clang/test/CodeGenCXX/pfp-attribute-disable.cpp new file mode 100644 index 0000000000000..1f09f72af66f5 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-attribute-disable.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -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 0000000000000..600523dc8724d --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-coerce.cpp @@ -0,0 +1,245 @@ +// 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 --check-prefixes=CHECK,AARCH64 %s +// RUN: %clang_cc1 -triple x86_64-linux -fexperimental-allow-pointer-field-protection-attr -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,X86_64 %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 + + // AARCH64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // X86_64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 51, i1 false) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + + // CHECK: %3 = load ptr, ptr %2, align 8 + + // AARCH64: %4 = ptrtoint ptr %3 to i64 + // AARCH64: %5 = insertvalue [2 x i64] poison, i64 %4, 0 + // AARCH64: %6 = getelementptr inbounds i8, ptr %agg.tmp, i64 8 + // AARCH64: %7 = load i64, ptr %6, align 8 + // AARCH64: %8 = insertvalue [2 x i64] %5, i64 %7, 1 + // AARCH64: call void @_Z19pass_pointer_callee7Pointer([2 x i64] %8) + + // X86_64: %4 = insertvalue { ptr, i32 } poison, ptr %3, 0 + // X86_64: %5 = getelementptr inbounds i8, ptr %agg.tmp, i64 8 + // X86_64: %6 = load i32, ptr %5, align 8 + // X86_64: %7 = insertvalue { ptr, i32 } %4, i32 %6, 1 + // X86_64: store { ptr, i32 } %7, ptr %agg.tmp.coerce, align 8 + // X86_64: %8 = getelementptr inbounds nuw { ptr, i32 }, ptr %agg.tmp.coerce, i32 0, i32 0 + // X86_64: %9 = load ptr, ptr %8, align 8 + // X86_64: %10 = getelementptr inbounds nuw { ptr, i32 }, ptr %agg.tmp.coerce, i32 0, i32 1 + // X86_64: %11 = load i32, ptr %10, align 8 + // X86_64: call void @_Z19pass_pointer_callee7Pointer(ptr %9, i32 %11) + pass_pointer_callee(*pp); +} + +// AARCH64: define dso_local void @_Z14passed_pointer7PointerPS_([2 x i64] %p.coerce, ptr noundef %pp) +// X86_64: define dso_local void @_Z14passed_pointer7PointerPS_(ptr %p.coerce0, i32 %p.coerce1, ptr noundef %pp) +void passed_pointer(Pointer p, Pointer *pp) { + // AARCH64: %p = alloca %struct.Pointer, align 8 + // AARCH64: %pp.addr = alloca ptr, align 8 + // AARCH64: %0 = extractvalue [2 x i64] %p.coerce, 0 + // AARCH64: %1 = getelementptr inbounds i8, ptr %p, i64 0 + // AARCH64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // AARCH64: %3 = inttoptr i64 %0 to ptr + // AARCH64: store ptr %3, ptr %2, align 8 + // AARCH64: %4 = extractvalue [2 x i64] %p.coerce, 1 + // AARCH64: %5 = getelementptr inbounds i8, ptr %p, i64 8 + // AARCH64: store i64 %4, ptr %5, align 8 + // AARCH64: store ptr %pp, ptr %pp.addr, align 8 + // AARCH64: %6 = load ptr, ptr %pp.addr, align 8 + // AARCH64: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %6, ptr align 8 %p, i64 12, i1 false) + + // X86_64: %p = alloca %struct.Pointer, align 8 + // X86_64: %pp.addr = alloca ptr, align 8 + // X86_64: %0 = getelementptr inbounds nuw { ptr, i32 }, ptr %p, i32 0, i32 0 + // X86_64: store ptr %p.coerce0, ptr %0, align 8 + // X86_64: %1 = getelementptr inbounds nuw { ptr, i32 }, ptr %p, i32 0, i32 1 + // X86_64: store i32 %p.coerce1, ptr %1, align 8 + // X86_64: %2 = load %struct.Pointer, ptr %p, align 8 + // X86_64: %3 = extractvalue %struct.Pointer %2, 0 + // X86_64: %4 = getelementptr inbounds i8, ptr %p, i64 0 + // X86_64: %5 = call ptr @llvm.protected.field.ptr.p0(ptr %4, i64 51, i1 false) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // X86_64: store ptr %3, ptr %5, align 8 + // X86_64: %6 = extractvalue %struct.Pointer %2, 1 + // X86_64: %7 = getelementptr inbounds i8, ptr %p, i64 8 + // X86_64: store i32 %6, ptr %7, align 8 + // X86_64: %8 = extractvalue %struct.Pointer %2, 2 + // X86_64: %9 = getelementptr inbounds i8, ptr %p, i64 12 + // X86_64: store [4 x i8] %8, ptr %9, align 4 + // X86_64: store ptr %pp, ptr %pp.addr, align 8 + // X86_64: %10 = load ptr, ptr %pp.addr, align 8 + // X86_64: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %10, ptr align 8 %p, i64 12, i1 false) + *pp = p; +} + +// AARCH64: define dso_local [2 x i64] @_Z14return_pointerP7Pointer(ptr noundef %pp) +// X86_64: define dso_local { ptr, i32 } @_Z14return_pointerP7Pointer(ptr noundef %pp) +Pointer return_pointer(Pointer *pp) { + // AARCH64: %retval = alloca %struct.Pointer, align 8 + // AARCH64: %pp.addr = alloca ptr, align 8 + // AARCH64: store ptr %pp, ptr %pp.addr, align 8 + // AARCH64: %0 = load ptr, ptr %pp.addr, align 8 + // AARCH64: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %retval, ptr align 8 %0, i64 16, i1 false) + // AARCH64: %1 = getelementptr inbounds i8, ptr %retval, i64 0 + // AARCH64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // AARCH64: %3 = load ptr, ptr %2, align 8 + // AARCH64: %4 = ptrtoint ptr %3 to i64 + // AARCH64: %5 = insertvalue [2 x i64] poison, i64 %4, 0 + // AARCH64: %6 = getelementptr inbounds i8, ptr %retval, i64 8 + // AARCH64: %7 = load i64, ptr %6, align 8 + // AARCH64: %8 = insertvalue [2 x i64] %5, i64 %7, 1 + // AARCH64: ret [2 x i64] %8 + + // X86_64: %retval = alloca %struct.Pointer, align 8 + // X86_64: %pp.addr = alloca ptr, align 8 + // X86_64: store ptr %pp, ptr %pp.addr, align 8 + // X86_64: %0 = load ptr, ptr %pp.addr, align 8 + // X86_64: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %retval, ptr align 8 %0, i64 16, i1 false) + // X86_64: %1 = getelementptr inbounds i8, ptr %retval, i64 0 + // X86_64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 51, i1 false) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // X86_64: %3 = load ptr, ptr %2, align 8 + // X86_64: %4 = insertvalue { ptr, i32 } poison, ptr %3, 0 + // X86_64: %5 = getelementptr inbounds i8, ptr %retval, i64 8 + // X86_64: %6 = load i32, ptr %5, align 8 + // X86_64: %7 = insertvalue { ptr, i32 } %4, i32 %6, 1 + // X86_64: ret { ptr, i32 } %7 + return *pp; +} + +Pointer returned_pointer_callee(); + +// CHECK: define dso_local void @_Z16returned_pointerP7Pointer(ptr noundef %pp) +void returned_pointer(Pointer *pp) { + // AARCH64: %pp.addr = alloca ptr, align 8 + // AARCH64: %ref.tmp = alloca %struct.Pointer, align 8 + // AARCH64: store ptr %pp, ptr %pp.addr, align 8 + // AARCH64: %call = call [2 x i64] @_Z23returned_pointer_calleev() + // AARCH64: %0 = extractvalue [2 x i64] %call, 0 + // AARCH64: %1 = getelementptr inbounds i8, ptr %ref.tmp, i64 0 + // AARCH64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // AARCH64: %3 = inttoptr i64 %0 to ptr + // AARCH64: store ptr %3, ptr %2, align 8 + // AARCH64: %4 = extractvalue [2 x i64] %call, 1 + // AARCH64: %5 = getelementptr inbounds i8, ptr %ref.tmp, i64 8 + // AARCH64: store i64 %4, ptr %5, align 8 + // AARCH64: %6 = load ptr, ptr %pp.addr, align 8 + // AARCH64: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %6, ptr align 8 %ref.tmp, i64 12, i1 false) + + // X86_64: %pp.addr = alloca ptr, align 8 + // X86_64: %ref.tmp = alloca %struct.Pointer, align 8 + // X86_64: store ptr %pp, ptr %pp.addr, align 8 + // X86_64: %call = call { ptr, i32 } @_Z23returned_pointer_calleev() + // X86_64: %0 = extractvalue { ptr, i32 } %call, 0 + // X86_64: %1 = getelementptr inbounds i8, ptr %ref.tmp, i64 0 + // X86_64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 51, i1 false) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // X86_64: store ptr %0, ptr %2, align 8 + // X86_64: %3 = extractvalue { ptr, i32 } %call, 1 + // X86_64: %4 = getelementptr inbounds i8, ptr %ref.tmp, i64 8 + // X86_64: store i32 %3, ptr %4, align 8 + // X86_64: %5 = load ptr, ptr %pp.addr, align 8 + // X86_64: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %5, 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) + + // AARCH64: %coerce.dive = getelementptr inbounds nuw %union.PointerUnion, ptr %agg.tmp, i32 0, i32 0 + // AARCH64: %1 = load [2 x i64], ptr %coerce.dive, align 8 + // AARCH64: call void @_Z25pass_pointer_union_callee12PointerUnion([2 x i64] %1) + + // X86_64: %1 = getelementptr inbounds nuw { ptr, i32 }, ptr %agg.tmp, i32 0, i32 0 + // X86_64: %2 = load ptr, ptr %1, align 8 + // X86_64: %3 = getelementptr inbounds nuw { ptr, i32 }, ptr %agg.tmp, i32 0, i32 1 + // X86_64: %4 = load i32, ptr %3, align 8 + // X86_64: call void @_Z25pass_pointer_union_callee12PointerUnion(ptr %2, i32 %4) + 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) { + // AARCH64: %p = alloca %struct.TrivialAbiPointer, align 8 + // AARCH64: %pp.addr = alloca ptr, align 8 + // AARCH64: %coerce.dive = getelementptr inbounds nuw %struct.TrivialAbiPointer, ptr %p, i32 0, i32 0 + // AARCH64: %0 = getelementptr inbounds i8, ptr %coerce.dive, i64 0 + // AARCH64: %1 = ptrtoint ptr %coerce.dive to i64 + // AARCH64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %0, i64 %1, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // AARCH64: store ptr %p.coerce, ptr %2, align 8 + // AARCH64: store ptr %pp, ptr %pp.addr, align 8 + // AARCH64: %3 = load ptr, ptr %pp.addr, align 8 + // AARCH64: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %3, ptr align 8 %p, i64 8, i1 false) + // AARCH64: %4 = getelementptr inbounds i8, ptr %3, i64 0 + // AARCH64: %5 = ptrtoint ptr %3 to i64 + // AARCH64: %6 = call ptr @llvm.protected.field.ptr.p0(ptr %4, i64 %5, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // AARCH64: %7 = getelementptr inbounds i8, ptr %p, i64 0 + // AARCH64: %8 = ptrtoint ptr %p to i64 + // AARCH64: %9 = call ptr @llvm.protected.field.ptr.p0(ptr %7, i64 %8, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // AARCH64: %10 = load ptr, ptr %9, align 8 + // AARCH64: store ptr %10, ptr %6, align 8 + // AARCH64: call void @_ZN17TrivialAbiPointerD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %p) + + // X86_64: %p = alloca %struct.TrivialAbiPointer, align 8 + // X86_64: %pp.addr = alloca ptr, align 8 + // X86_64: %coerce.dive = getelementptr inbounds nuw %struct.TrivialAbiPointer, ptr %p, i32 0, i32 0 + // X86_64: %0 = getelementptr inbounds i8, ptr %coerce.dive, i64 0 + // X86_64: %1 = call ptr @llvm.protected.field.ptr.p0(ptr %0, i64 33, i1 false) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // X86_64: store ptr %p.coerce, ptr %1, align 8 + // X86_64: store ptr %pp, ptr %pp.addr, align 8 + // X86_64: %2 = load ptr, ptr %pp.addr, align 8 + // X86_64: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %2, ptr align 8 %p, i64 8, i1 false) + // X86_64: call void @_ZN17TrivialAbiPointerD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %p) + *pp = p; +} + +// AARCH64: define dso_local i64 @_Z26return_trivial_abi_pointerP17TrivialAbiPointer(ptr noundef %pp) +// X86_64: define dso_local ptr @_Z26return_trivial_abi_pointerP17TrivialAbiPointer(ptr noundef %pp) +TrivialAbiPointer return_trivial_abi_pointer(TrivialAbiPointer *pp) { + // AARCH64: %retval = alloca %struct.TrivialAbiPointer, align 8 + // AARCH64: %pp.addr = alloca ptr, align 8 + // AARCH64: store ptr %pp, ptr %pp.addr, align 8 + // AARCH64: %0 = load ptr, ptr %pp.addr, align 8 + // AARCH64: call void @_ZN17TrivialAbiPointerC1ERKS_(ptr noundef nonnull align 8 dereferenceable(8) %retval, ptr noundef nonnull align 8 dereferenceable(8) %0) + // AARCH64: %1 = getelementptr inbounds i8, ptr %retval, i64 0 + // AARCH64: %2 = ptrtoint ptr %retval to i64 + // AARCH64: %3 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 %2, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // AARCH64: %4 = load ptr, ptr %3, align 8 + // AARCH64: %5 = ptrtoint ptr %4 to i64 + // AARCH64: ret i64 %5 + + // X86_64: %retval = alloca %struct.TrivialAbiPointer, align 8 + // X86_64: %pp.addr = alloca ptr, align 8 + // X86_64: store ptr %pp, ptr %pp.addr, align 8 + // X86_64: %0 = load ptr, ptr %pp.addr, align 8 + // X86_64: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %retval, ptr align 8 %0, i64 8, i1 false) + // X86_64: %1 = getelementptr inbounds i8, ptr %retval, i64 0 + // X86_64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 33, i1 false) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // X86_64: %3 = load ptr, ptr %2, align 8 + // X86_64: ret ptr %3 + 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 0000000000000..118dda7789bba --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-load-store.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -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 0000000000000..842bdd7e9f83d --- /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 0000000000000..0f97747e7917f --- /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 0000000000000..1485f066c4024 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-null-init.cpp @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,AARCH64 %s +// RUN: %clang_cc1 -triple x86_64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,X86_64 %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 + // AARCH64: %1 = call ptr @llvm.protected.field.ptr.p0(ptr %0, i64 29832, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.p) ] + // X86_64: %1 = call ptr @llvm.protected.field.ptr.p0(ptr %0, i64 136, i1 false) [ "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 0000000000000..4e234cafc9454 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-struct-gep.cpp @@ -0,0 +1,36 @@ +// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,AARCH64 +// RUN: %clang_cc1 -triple x86_64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,X86_64 + +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 + // AARCH64: %1 = call ptr @llvm.protected.field.ptr.p0(ptr %ptr, i64 63261, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.ptr) ] + // X86_64: %1 = call ptr @llvm.protected.field.ptr.p0(ptr %ptr, i64 29, i1 false) [ "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 + // AARCH64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %ptr, i64 63261, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.ptr) ] + // X86_64: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %ptr, i64 29, i1 false) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.ptr) ] + // CHECK: store ptr %0, ptr %2, align 8 + t->ptr = p; +} diff --git a/clang/test/CodeGenCXX/pfp-trivially-relocatable.cpp b/clang/test/CodeGenCXX/pfp-trivially-relocatable.cpp new file mode 100644 index 0000000000000..ea0a8a73c19b7 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-trivially-relocatable.cpp @@ -0,0 +1,101 @@ +// RUN: %clang_cc1 -std=c++26 -triple x86_64-linux-gnu -emit-llvm -fexperimental-pointer-field-protection-abi -o - %s | FileCheck --check-prefix=RELOC %s +// RUN: %clang_cc1 -std=c++26 -triple x86_64-linux-gnu -emit-llvm -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -o - %s | FileCheck --check-prefix=RELOC %s +// RUN: %clang_cc1 -std=c++26 -triple aarch64-linux-gnu -emit-llvm -fexperimental-pointer-field-protection-abi -o - %s | FileCheck --check-prefix=RELOC %s +// RUN: %clang_cc1 -std=c++26 -triple aarch64-linux-gnu -emit-llvm -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -o - %s | FileCheck --check-prefix=NONRELOC %s + +typedef __SIZE_TYPE__ size_t; + +struct S trivially_relocatable_if_eligible { + S(const S&); + ~S(); + int* a; +private: + int* b; +}; + +// CHECK: define dso_local void @_Z5test1P1SS0_( +void test1(S* source, S* dest) { + // RELOC: %0 = load ptr, ptr %dest.addr, align 8 + // RELOC-NEXT: %1 = load ptr, ptr %source.addr, align 8 + // RELOC-NEXT: call void @llvm.memmove.p0.p0.i64(ptr align 8 %0, ptr align 8 %1, i64 16, i1 false) + // RELOC-NOT: @llvm.protected.field.ptr.p0 + + // NONRELOC: %0 = load ptr, ptr %dest.addr, align 8 + // NONRELOC-NEXT: %1 = load ptr, ptr %source.addr, align 8 + // NONRELOC-NEXT: call void @llvm.memmove.p0.p0.i64(ptr align 8 %0, ptr align 8 %1, i64 16, i1 false) + // NONRELOC-NEXT: br i1 false, label %pfp.relocate.loop.end, label %pfp.relocate.loop + + // NONRELOC: pfp.relocate.loop: + // NONRELOC-NEXT: %2 = phi i64 [ 0, %entry ], [ %19, %pfp.relocate.loop ] + // NONRELOC-NEXT: %3 = getelementptr inbounds i8, ptr %0, i64 %2 + // NONRELOC-NEXT: %4 = getelementptr inbounds i8, ptr %1, i64 %2 + // NONRELOC-NEXT: %5 = getelementptr inbounds i8, ptr %3, i64 0 + // NONRELOC-NEXT: %6 = ptrtoint ptr %3 to i64 + // NONRELOC-NEXT: %7 = call ptr @llvm.protected.field.ptr.p0(ptr %5, i64 %6, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.a) ] + // NONRELOC-NEXT: %8 = getelementptr inbounds i8, ptr %4, i64 0 + // NONRELOC-NEXT: %9 = ptrtoint ptr %4 to i64 + // NONRELOC-NEXT: %10 = call ptr @llvm.protected.field.ptr.p0(ptr %8, i64 %9, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.a) ] + // NONRELOC-NEXT: %11 = load ptr, ptr %10, align 8 + // NONRELOC-NEXT: store ptr %11, ptr %7, align 8 + // NONRELOC-NEXT: %12 = getelementptr inbounds i8, ptr %3, i64 8 + // NONRELOC-NEXT: %13 = ptrtoint ptr %3 to i64 + // NONRELOC-NEXT: %14 = call ptr @llvm.protected.field.ptr.p0(ptr %12, i64 %13, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.b) ] + // NONRELOC-NEXT: %15 = getelementptr inbounds i8, ptr %4, i64 8 + // NONRELOC-NEXT: %16 = ptrtoint ptr %4 to i64 + // NONRELOC-NEXT: %17 = call ptr @llvm.protected.field.ptr.p0(ptr %15, i64 %16, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.b) ] + // NONRELOC-NEXT: %18 = load ptr, ptr %17, align 8 + // NONRELOC-NEXT: store ptr %18, ptr %14, align 8 + // NONRELOC-NEXT: %19 = add i64 %2, 16 + // NONRELOC-NEXT: %20 = icmp eq i64 %19, 16 + // NONRELOC-NEXT: br i1 %20, label %pfp.relocate.loop.end, label %pfp.relocate.loop + + // NONRELOC: pfp.relocate.loop.end: + // NONRELOC-NEXT: ret void + __builtin_trivially_relocate(dest, source, 1); +} + +// CHECK: define dso_local void @_Z5testNP1SS0_m( +void testN(S* source, S* dest, size_t count) { + // RELOC: %0 = load ptr, ptr %dest.addr, align 8 + // RELOC-NEXT: %1 = load ptr, ptr %source.addr, align 8 + // RELOC-NEXT: %2 = load i64, ptr %count.addr, align 8 + // RELOC-NEXT: %3 = mul i64 %2, 16 + // RELOC-NEXT: call void @llvm.memmove.p0.p0.i64(ptr align 8 %0, ptr align 8 %1, i64 %3, i1 false) + // RELOC-NOT: @llvm.protected.field.ptr.p0 + + // NONRELOC: %0 = load ptr, ptr %dest.addr, align 8 + // NONRELOC-NEXT: %1 = load ptr, ptr %source.addr, align 8 + // NONRELOC-NEXT: %2 = load i64, ptr %count.addr, align 8 + // NONRELOC-NEXT: %3 = mul i64 %2, 16 + // NONRELOC-NEXT: call void @llvm.memmove.p0.p0.i64(ptr align 8 %0, ptr align 8 %1, i64 %3, i1 false) + // NONRELOC-NEXT: %4 = icmp eq i64 %3, 0 + // NONRELOC-NEXT: br i1 %4, label %pfp.relocate.loop.end, label %pfp.relocate.loop + + // NONRELOC: pfp.relocate.loop: + // NONRELOC-NEXT: %5 = phi i64 [ 0, %entry ], [ %22, %pfp.relocate.loop ] + // NONRELOC-NEXT: %6 = getelementptr inbounds i8, ptr %0, i64 %5 + // NONRELOC-NEXT: %7 = getelementptr inbounds i8, ptr %1, i64 %5 + // NONRELOC-NEXT: %8 = getelementptr inbounds i8, ptr %6, i64 0 + // NONRELOC-NEXT: %9 = ptrtoint ptr %6 to i64 + // NONRELOC-NEXT: %10 = call ptr @llvm.protected.field.ptr.p0(ptr %8, i64 %9, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.a) ] + // NONRELOC-NEXT: %11 = getelementptr inbounds i8, ptr %7, i64 0 + // NONRELOC-NEXT: %12 = ptrtoint ptr %7 to i64 + // NONRELOC-NEXT: %13 = call ptr @llvm.protected.field.ptr.p0(ptr %11, i64 %12, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.a) ] + // NONRELOC-NEXT: %14 = load ptr, ptr %13, align 8 + // NONRELOC-NEXT: store ptr %14, ptr %10, align 8 + // NONRELOC-NEXT: %15 = getelementptr inbounds i8, ptr %6, i64 8 + // NONRELOC-NEXT: %16 = ptrtoint ptr %6 to i64 + // NONRELOC-NEXT: %17 = call ptr @llvm.protected.field.ptr.p0(ptr %15, i64 %16, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.b) ] + // NONRELOC-NEXT: %18 = getelementptr inbounds i8, ptr %7, i64 8 + // NONRELOC-NEXT: %19 = ptrtoint ptr %7 to i64 + // NONRELOC-NEXT: %20 = call ptr @llvm.protected.field.ptr.p0(ptr %18, i64 %19, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.b) ] + // NONRELOC-NEXT: %21 = load ptr, ptr %20, align 8 + // NONRELOC-NEXT: store ptr %21, ptr %17, align 8 + // NONRELOC-NEXT: %22 = add i64 %5, 16 + // NONRELOC-NEXT: %23 = icmp eq i64 %22, %3 + // NONRELOC-NEXT: br i1 %23, label %pfp.relocate.loop.end, label %pfp.relocate.loop + + // NONRELOC: pfp.relocate.loop.end: + // NONRELOC-NEXT: ret void + __builtin_trivially_relocate(dest, source, count); +}; diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index 82072ce9f69f7..3642606405396 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -124,6 +124,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) @@ -180,6 +181,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 0000000000000..ec3ba0a524f52 --- /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 0000000000000..3917002c7290c --- /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