diff --git a/clang/docs/StructureProtection.rst b/clang/docs/StructureProtection.rst new file mode 100644 index 0000000000000..6db01cc579b8e --- /dev/null +++ b/clang/docs/StructureProtection.rst @@ -0,0 +1,54 @@ +==================== +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 of the flags: + +- ``-fexperimental-pointer-field-protection=untagged``: Enable pointer + field protection with untagged pointers. + +- ``-fexperimental-pointer-field-protection=tagged``: Enable pointer + field protection with heap pointers assumed to be tagged by the allocator. + +The entire C++ part of the program must be built with a consistent +``-fexperimental-pointer-field-protection`` flag, and the C++ standard +library must also be built with the same flag 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=untagged`` 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 4871d05e932ae..e267c66af1e1d 100644 --- a/clang/docs/index.rst +++ b/clang/docs/index.rst @@ -47,6 +47,7 @@ Using Clang as a Compiler LTOVisibility SafeStack ShadowCallStack + StructureProtection SourceBasedCodeCoverage StandardCPlusPlusModules Modules diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 3b98274a80420..b1864ca4eccce 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -183,6 +183,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 { @@ -3720,6 +3726,23 @@ OPT_LIST(V) StringRef getCUIDHash() const; + bool isPFPStruct(const RecordDecl *rec) 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 + /// relocatable (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 arePFPFieldsTriviallyRelocatable(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/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index b3ff45b3e90a3..dc075ac140d65 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2597,6 +2597,12 @@ def CountedByOrNull : DeclOrTypeAttr { let LangOpts = [COnly]; } +def NoPointerFieldProtection : DeclOrTypeAttr { + let Spellings = [Clang<"no_field_protection">]; + let Subjects = SubjectList<[Field], ErrorDiag>; + let Documentation = [Undocumented]; +} + def SizedBy : DeclOrTypeAttr { let Spellings = [Clang<"sized_by">]; let Subjects = SubjectList<[Field], ErrorDiag>; diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def index 72f23614aef11..4a0433df6f62d 100644 --- a/clang/include/clang/Basic/Features.def +++ b/clang/include/clang/Basic/Features.def @@ -312,6 +312,9 @@ FEATURE(shadow_call_stack, FEATURE(tls, PP.getTargetInfo().isTLSSupported()) FEATURE(underlying_type, LangOpts.CPlusPlus) FEATURE(experimental_library, LangOpts.ExperimentalLibrary) +EXTENSION(pointer_field_protection, + LangOpts.getPointerFieldProtection() != + LangOptions::PointerFieldProtectionKind::None) // C11 features supported by other languages as extensions. EXTENSION(c_alignas, true) diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 08d98a77e0252..4cfa68e16b020 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -456,6 +456,9 @@ LANGOPT(RelativeCXXABIVTables, 1, 0, NotCompatible, LANGOPT(OmitVTableRTTI, 1, 0, NotCompatible, "Use an ABI-incompatible v-table layout that omits the RTTI component") +ENUM_LANGOPT(PointerFieldProtection, PointerFieldProtectionKind, 2, PointerFieldProtectionKind::None, NotCompatible, + "Encode struct pointer fields to protect against UAF vulnerabilities") + LANGOPT(VScaleMin, 32, 0, NotCompatible, "Minimum vscale value") LANGOPT(VScaleMax, 32, 0, NotCompatible, "Maximum vscale value") diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h index 0407897359b5e..cbabed552df1e 100644 --- a/clang/include/clang/Basic/LangOptions.h +++ b/clang/include/clang/Basic/LangOptions.h @@ -376,6 +376,17 @@ class LangOptionsBase { BKey }; + enum class PointerFieldProtectionKind { + /// Pointer field protection disabled + None, + /// Pointer field protection enabled, allocator does not tag heap + /// allocations. + Untagged, + /// Pointer field protection enabled, allocator is expected to tag heap + /// allocations. + Tagged, + }; + enum class ThreadModelKind { /// POSIX Threads. POSIX, diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 3c04aeb5af59c..5b371b2e082c4 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -3051,6 +3051,12 @@ defm experimental_omit_vtable_rtti : BoolFOption<"experimental-omit-vtable-rtti" NegFlag, BothFlags<[], [CC1Option], " the RTTI component from virtual tables">>; +def experimental_pointer_field_protection_EQ : Joined<["-"], "fexperimental-pointer-field-protection=">, Group, + Visibility<[ClangOption, CC1Option]>, + Values<"none,untagged,tagged">, NormalizedValuesScope<"LangOptions::PointerFieldProtectionKind">, + NormalizedValues<["None", "Untagged", "Tagged"]>, + MarshallingInfoEnum, "None">; + 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 16cf114981824..5c9d481406bb6 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15181,3 +15181,102 @@ bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl, ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames); return Result; } + +bool ASTContext::arePFPFieldsTriviallyRelocatable(const RecordDecl *RD) const { + bool IsPAuthSupported = + getTargetInfo().getTriple().getArch() == llvm::Triple::aarch64; + if (!IsPAuthSupported) + return true; + if (getLangOpts().getPointerFieldProtection() == + LangOptions::PointerFieldProtectionKind::Tagged) + return !isa(RD) || + cast(RD)->hasTrivialDestructor(); + return true; +} + +bool ASTContext::isPFPStruct(const RecordDecl *rec) const { + if (getLangOpts().getPointerFieldProtection() != + LangOptions::PointerFieldProtectionKind::None) + if (auto *cxxRec = dyn_cast(rec)) + return !cxxRec->isStandardLayout(); + return false; +} + +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(); + if (!Decl) + 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, true, IsWithinUnion); + } + for (auto &Base : Decl->bases()) { + if (Base.isVirtual()) + continue; + CharUnits BaseOffset = + Offset + RL.getBaseClassOffset(Base.getType()->getAsCXXRecordDecl()); + findPFPFields(Base.getType(), BaseOffset, Fields, false, IsWithinUnion); + } + if (IncludeVBases) { + for (auto &Base : Decl->vbases()) { + CharUnits BaseOffset = + Offset + RL.getVBaseClassOffset(Base.getType()->getAsCXXRecordDecl()); + findPFPFields(Base.getType(), BaseOffset, Fields, 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 *field) const { + if (!isPFPStruct(field->getParent())) + return false; + return field->getType()->isPointerType() && + !field->hasAttr(); +} + +void ASTContext::recordMemberDataPointerEvaluation(const ValueDecl *VD) { + if (getLangOpts().getPointerFieldProtection() == + LangOptions::PointerFieldProtectionKind::None) + return; + auto *FD = dyn_cast(VD); + if (!FD) + FD = cast(cast(VD)->chain().back()); + if (!isPFPField(FD)) + return; + PFPFieldsWithEvaluatedOffset.insert(FD); +} + +void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { + if (getLangOpts().getPointerFieldProtection() == + LangOptions::PointerFieldProtectionKind::None || + E->getNumComponents() == 0) + return; + OffsetOfNode Comp = E->getComponent(E->getNumComponents() - 1); + if (Comp.getKind() != OffsetOfNode::Field) + return; + FieldDecl *FD = Comp.getField(); + if (!isPFPField(FD)) + return; + PFPFieldsWithEvaluatedOffset.insert(FD); +} diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 993b64b2752e9..5f4dded57abe3 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -15084,6 +15084,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 deb453fe6ee75..eb61729271f8e 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -2129,6 +2129,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T, case attr::ExtVectorType: OS << "ext_vector_type"; break; + case attr::NoPointerFieldProtection: + OS << "no_field_protection"; + break; } OS << "))"; } diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index e1f7ea08837c5..7be70ee46eaf5 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -4499,18 +4499,55 @@ 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) { + std::vector PFPFields; + getContext().findPFPFields(E->getArg(0)->getType()->getPointeeType(), + CharUnits::Zero(), PFPFields, true); + if (!PFPFields.empty()) { + BasicBlock *Entry = Builder.GetInsertBlock(); + BasicBlock *Loop = createBasicBlock("loop"); + BasicBlock *LoopEnd = createBasicBlock("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); + for (auto &Field : PFPFields) { + if (getContext().arePFPFieldsTriviallyRelocatable( + Field.field->getParent())) + continue; + auto DestFieldPtr = EmitAddressOfPFPField(DestRec, Field); + auto SrcFieldPtr = EmitAddressOfPFPField(SrcRec, Field); + Builder.CreateStore(Builder.CreateLoad(SrcFieldPtr), DestFieldPtr); + } + + 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 d9bd443455e0f..c26d51c28f305 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -1311,6 +1311,61 @@ 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) { + // 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, true); + 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. @@ -1318,7 +1373,8 @@ 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, +static llvm::Value *CreateCoercedLoad(Address Src, QualType SrcFETy, + llvm::Type *Ty, CodeGenFunction &CGF) { llvm::Type *SrcTy = Src.getElementType(); @@ -1326,6 +1382,9 @@ static llvm::Value *CreateCoercedLoad(Address Src, llvm::Type *Ty, 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)) { @@ -1397,7 +1456,60 @@ static llvm::Value *CreateCoercedLoad(Address Src, llvm::Type *Ty, return CGF.Builder.CreateLoad(Tmp); } -void CodeGenFunction::CreateCoercedStore(llvm::Value *Src, Address Dst, +static bool CreatePFPCoercedStore(llvm::Value *Src, QualType SrcFETy, + Address Dst, CodeGenFunction &CGF) { + // Coercion directly through memory does not work if the structure has pointer + // field protection because the struct passed by value 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, true); + 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) @@ -1418,6 +1530,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())) { @@ -3423,6 +3538,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. @@ -3430,7 +3552,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()), @@ -4063,7 +4185,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 @@ -4113,6 +4235,7 @@ void CodeGenFunction::EmitFunctionEpilog( auto eltAddr = Builder.CreateStructGEP(addr, i); llvm::Value *elt = CreateCoercedLoad( eltAddr, + RetTy, unpaddedStruct ? unpaddedStruct->getElementType(unpaddedIndex++) : unpaddedCoercionType, *this); @@ -5601,15 +5724,25 @@ 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); @@ -5628,7 +5761,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 @@ -5690,6 +5823,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, Address eltAddr = Builder.CreateStructGEP(addr, i); llvm::Value *elt = CreateCoercedLoad( eltAddr, + I->Ty, unpaddedStruct ? unpaddedStruct->getElementType(unpaddedIndex++) : unpaddedCoercionType, *this); @@ -6190,7 +6324,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 4a465e6526da0..f6ee019ea077c 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -591,12 +591,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().arePFPFieldsTriviallyRelocatable(D->getParent()) && + CGM.getContext().hasPFPFields( + QualType(D->getParent()->getTypeForDecl(), 0))) + return false; + // We can emit a memcpy for a trivial copy or move constructor/assignment. if (D->isTrivial() && !D->getParent()->mayInsertExtraPadding()) return true; @@ -662,7 +670,7 @@ 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 @@ -929,6 +937,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().arePFPFieldsTriviallyRelocatable(ClassDecl) && + CGF.getContext().isPFPField(F)) + return false; return true; } @@ -1066,7 +1079,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; @@ -1177,7 +1191,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) @@ -2153,7 +2167,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); @@ -2220,7 +2234,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( @@ -2998,7 +3012,15 @@ void CodeGenFunction::EmitForwardingCallToLambda( QualType resultType = FPT->getReturnType(); ReturnValueSlot returnSlot; if (!resultType->isVoidType() && - calleeFnInfo->getReturnInfo().getKind() == ABIArgInfo::Indirect && + (calleeFnInfo->getReturnInfo().getKind() == ABIArgInfo::Indirect || + // With pointer field protection, we need to set up the return slot when + // returning an object with trivial ABI to avoid the memcpy that would + // otherwise be generated by the call to EmitReturnOfRValue() below, as + // that may corrupt the pointer signature. It doesn't hurt to do this all + // the time as it results in slightly simpler codegen. + (resultType->isRecordType() && + resultType->getAsCXXRecordDecl() + ->hasTrivialCopyConstructorForCall())) && !hasScalarEvaluationKind(calleeFnInfo->getReturnType())) returnSlot = ReturnValueSlot(ReturnValue, resultType.isVolatileQualified(), diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 5a3d4e447b229..cd86818a13674 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -5101,12 +5101,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); @@ -5121,6 +5122,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 cad6731173700..a8189643337e9 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()), diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index b8238a4702c4d..b3b60a1edee96 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -260,6 +260,16 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorMemberCallExpr( (MD->isCopyAssignmentOperator() || MD->isMoveAssignmentOperator()) && !MD->getParent()->mayInsertExtraPadding(); + // Assignment operators for objects with non-trivially-relocatable PFP fields + // aren't trivial, we need to auth and sign the fields one by one. + if (TrivialAssignment && + !CGM.getContext().arePFPFieldsTriviallyRelocatable(MD->getParent()) && + CGM.getContext().hasPFPFields( + QualType(MD->getParent()->getTypeForDecl(), 0))) { + TrivialForCodegen = false; + TrivialAssignment = false; + } + // C++17 demands that we evaluate the RHS of a (possibly-compound) assignment // operator before the LHS. CallArgList RtlArgStorage; diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 715bd392f59f7..8f91a75d97617 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; @@ -904,6 +905,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().arePFPFieldsTriviallyRelocatable(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)) & 0xffff); + AddrDisc = Emitter.getCurrentAddrPrivate(); + } + EltInit = llvm::ConstantPtrAuth::get( + EltInit, llvm::ConstantInt::get(CGM.Int32Ty, 2), Disc, AddrDisc, + CGM.getPFPDeactivationSymbol(*Field)); + if (!CGM.getContext().arePFPFieldsTriviallyRelocatable(RD)) + Emitter.registerCurrentAddrPrivate(EltInit, + cast(AddrDisc)); + } + if (ZeroInitPadding) { if (!DoZeroInitPadding(Layout, FieldNo, **Field, AllowOverwrite, SizeSoFar, ZeroFieldSize)) @@ -1654,7 +1678,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, @@ -2609,6 +2646,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 ab345a598c4e8..e8ebe1a231601 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" @@ -2218,6 +2219,44 @@ 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().arePFPFieldsTriviallyRelocatable(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.getBasePointer(), Disc, + IsPAuthSupported ? Builder.getTrue() : Builder.getFalse()}, + DSBundle), + VoidPtrTy, PtrPtr.getAlignment()); +} + void CodeGenFunction::EmitNullInitialization(Address DestPtr, QualType Ty) { // Ignore empty classes in C++. @@ -2280,13 +2319,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) { diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 6c32c98cec011..42139e889f582 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -5020,8 +5020,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. @@ -5509,6 +5509,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 834b1c067d84c..d82838379f34c 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -960,6 +960,7 @@ void CodeGenModule::Release() { applyGlobalValReplacements(); applyReplacements(); emitMultiVersionFunctions(); + emitPFPFieldsWithEvaluatedOffset(); if (Context.getLangOpts().IncrementalExtensions && GlobalTopLevelStmtBlockInFlight.first) { @@ -4563,6 +4564,35 @@ 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"); @@ -8125,3 +8155,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( + QualType(FD->getParent()->getTypeForDecl(), 0), Out, false); + Out << "." << FD->getName(); + return OutName; +} diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index cb013feb769fc..dcb88330d6014 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -1824,6 +1824,9 @@ class CodeGenModule : public CodeGenTypeCache { return PAlign; } + std::string getPFPFieldName(const FieldDecl *FD); + llvm::GlobalValue *getPFPDeactivationSymbol(const FieldDecl *FD); + private: bool shouldDropDLLAttribute(const Decl *D, const llvm::GlobalValue *GV) const; @@ -2018,6 +2021,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 f4a99467010af..cb09bc18a999d 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -1240,6 +1240,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 a181559834296..300fa450c5247 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -2913,6 +2913,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 4e1b1d9e33621..a6833b825366d 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7613,6 +7613,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, Twine("-funique-source-file-identifier=") + Input.getBaseInput())); } + if (!IsCudaDevice) + Args.AddLastArg(CmdArgs, + options::OPT_experimental_pointer_field_protection_EQ); + // Setup statistics file output. SmallString<128> StatsFile = getStatsFileName(Args, Output, Input, D); if (!StatsFile.empty()) { diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 16b18bcb6a2a0..97c1cf2f1c540 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6380,6 +6380,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(NoPointerFieldProtectionAttr::Create(S.Context, AL)); +} + static void handleCountedByAttrField(Sema &S, Decl *D, const ParsedAttr &AL) { auto *FD = dyn_cast(D); assert(FD); @@ -7402,6 +7406,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, handleCountedByAttrField(S, D, AL); break; + case ParsedAttr::AT_NoPointerFieldProtection: + handleNoPFPAttrField(S, D, AL); + break; + // Microsoft attributes: case ParsedAttr::AT_LayoutVersion: handleLayoutVersion(S, D, AL); diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp index c2f0600295e9e..c171b708b818c 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -233,6 +233,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(QualType(D->getTypeForDecl(), 0), + 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}; @@ -274,6 +290,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..a8efb08b9a7ee --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-attribute-disable.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -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{{.*}} + +// 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{{.*}} + +// 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{{.*}} + +// 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{{.*}} diff --git a/clang/test/CodeGenCXX/pfp-coerce.cpp b/clang/test/CodeGenCXX/pfp-coerce.cpp new file mode 100644 index 0000000000000..636d61ccf4858 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-coerce.cpp @@ -0,0 +1,220 @@ +// RUN: %clang_cc1 -triple aarch64-linux -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=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(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ] + // X86_64: %2 = call ptr @llvm.protected.field.ptr(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(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(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(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(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(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(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(); +} + +class __force_nonstandard_layout_base1 {}; +class __force_nonstandard_layout_base2 : __force_nonstandard_layout_base1 {}; +class __force_nonstandard_layout : __force_nonstandard_layout_base1, __force_nonstandard_layout_base2 {}; + +// Non-standard layout, 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]] TrivialAbiPointer : __force_nonstandard_layout { + int *ptr; + ~TrivialAbiPointer(); +}; + +// AARCH64: define dso_local void @_Z24pass_trivial_abi_pointer17TrivialAbiPointerPS_(i64 %p.coerce, ptr noundef %pp) +// X86_64: 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 = inttoptr i64 %p.coerce to ptr + // AARCH64: %1 = getelementptr inbounds i8, ptr %coerce.dive, i64 0 + // AARCH64: %2 = ptrtoint ptr %coerce.dive to i64 + // AARCH64: %3 = call ptr @llvm.protected.field.ptr(ptr %1, i64 %2, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ] + // AARCH64: store ptr %0, ptr %3, align 8 + // AARCH64: store ptr %pp, ptr %pp.addr, align 8 + // AARCH64: %4 = load ptr, ptr %pp.addr, align 8 + // AARCH64: %call = call noundef nonnull align 8 dereferenceable(8) ptr @_ZN17TrivialAbiPointeraSERKS_(ptr noundef nonnull align 8 dereferenceable(8) %4, ptr noundef nonnull align 8 dereferenceable(8) %p) + // 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(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(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(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..b5f6a11574270 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-load-store.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -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{{.*}} + + + +// CHECK-LABEL: test_struct +int* test_struct(Struct *t) { + return (t->ptr).ptr; +} +// CHECK: call {{.*}} @llvm.protected.field.ptr{{.*}} diff --git a/clang/test/CodeGenCXX/pfp-memcpy.cpp b/clang/test/CodeGenCXX/pfp-memcpy.cpp new file mode 100644 index 0000000000000..9df63556d71ff --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-memcpy.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection=tagged -emit-llvm -o - %s | FileCheck %s + +struct ClassWithTrivialCopy { + ClassWithTrivialCopy(); + ~ClassWithTrivialCopy(); + void *a; +private: + void *c; +}; + +// Make sure we don't emit memcpy for operator= and constuctors. +void make_trivial_copy(ClassWithTrivialCopy *s1, ClassWithTrivialCopy *s2) { + *s1 = *s2; + ClassWithTrivialCopy s3(*s2); +} + +// CHECK-LABEL: define{{.*}} void @_Z17make_trivial_copyP20ClassWithTrivialCopyS0_ +// CHECK-NOT: memcpy +// CHECK: ret void diff --git a/clang/test/CodeGenCXX/pfp-null-init.cpp b/clang/test/CodeGenCXX/pfp-null-init.cpp new file mode 100644 index 0000000000000..f0a9152c63ae7 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-null-init.cpp @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -triple aarch64-linux -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=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(ptr %0, i64 29832, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.p) ] + // X86_64: %1 = call ptr @llvm.protected.field.ptr(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..700934baf8420 --- /dev/null +++ b/clang/test/CodeGenCXX/pfp-struct-gep.cpp @@ -0,0 +1,36 @@ +// RUN: %clang_cc1 -triple aarch64-linux -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=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(ptr %ptr, i64 63261, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.ptr) ] + // X86_64: %1 = call ptr @llvm.protected.field.ptr(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(ptr %ptr, i64 63261, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.ptr) ] + // X86_64: %2 = call ptr @llvm.protected.field.ptr(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..e540d2672c84d --- /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=untagged -o - %s | FileCheck --check-prefix=RELOC %s +// RUN: %clang_cc1 -std=c++26 -triple x86_64-linux-gnu -emit-llvm -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=untagged -o - %s | FileCheck --check-prefix=RELOC %s +// RUN: %clang_cc1 -std=c++26 -triple aarch64-linux-gnu -emit-llvm -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 + + // 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 %loop.end, label %loop + + // NONRELOC: loop: + // NONRELOC-NEXT: %2 = phi i64 [ 0, %entry ], [ %19, %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(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(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(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(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 %loop.end, label %loop + + // NONRELOC: 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 + + // 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 %loop.end, label %loop + + // NONRELOC: loop: + // NONRELOC-NEXT: %5 = phi i64 [ 0, %entry ], [ %22, %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(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(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(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(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 %loop.end, label %loop + + // NONRELOC: loop.end: + // NONRELOC-NEXT: ret void + __builtin_trivially_relocate(dest, source, count); +}; diff --git a/clang/test/CodeGenCXX/trivial_abi.cpp b/clang/test/CodeGenCXX/trivial_abi.cpp index eacbde594e517..a637fddc566d4 100644 --- a/clang/test/CodeGenCXX/trivial_abi.cpp +++ b/clang/test/CodeGenCXX/trivial_abi.cpp @@ -283,11 +283,9 @@ static_assert(sizeof(S) == 8 && sizeof(S2) == 8, ""); // CHECK: define{{.*}} @"_ZN3$_08__invokeEv"() // CHECK: %[[RETVAL:.*]] = alloca %[[STRUCT_SMALL]], align 8 -// CHECK: %[[COERCE:.*]] = alloca %[[STRUCT_SMALL]], align 8 // CHECK: %[[CALL:.*]] = call{{.*}} @"_ZNK3$_0clEv" -// CHECK: %[[COERCEDIVE:.*]] = getelementptr{{.*}} %[[COERCE]] +// CHECK: %[[RETVALDIVE:.*]] = getelementptr{{.*}} %[[RETVAL]] // CHECK: %[[COERCEVALIP:.*]] = inttoptr{{.*}} %[[CALL]] -// CHECK: call {{.*}}memcpy{{.*}} %[[RETVAL]]{{.*}} %[[COERCE]] // CHECK: %[[COERCEDIVE1:.*]] = getelementptr{{.*}} %[[RETVAL]] // CHECK: %[[TMP:.*]] = load{{.*}} %[[COERCEDIVE1]] // CHECK: %[[COERCEVALPI:.*]] = ptrtoint{{.*}} %[[TMP]] diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index 05693538252aa..6463b3e433030 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -122,6 +122,7 @@ // CHECK-NEXT: NoMerge (SubjectMatchRule_function, SubjectMatchRule_variable) // CHECK-NEXT: NoMicroMips (SubjectMatchRule_function) // CHECK-NEXT: NoMips16 (SubjectMatchRule_function) +// CHECK-NEXT: NoPointerFieldProtection (SubjectMatchRule_field) // CHECK-NEXT: NoProfileFunction (SubjectMatchRule_function) // CHECK-NEXT: NoRandomizeLayout (SubjectMatchRule_record) // CHECK-NEXT: NoSanitize (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_variable_is_global)