[Clang] Add pointer field protection feature.

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

Reviewers: fmayer, ojhunt

Pull Request: https://github.com/llvm/llvm-project/pull/172119
This commit is contained in:
Peter Collingbourne 2026-02-19 15:19:35 -08:00 committed by GitHub
parent f45754b65e
commit 370d7ce580
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 1033 additions and 34 deletions

View File

@ -0,0 +1,95 @@
====================
Structure Protection
====================
.. contents::
:local:
Introduction
============
Structure protection is an *experimental* mitigation
against use-after-free vulnerabilities. For
more information, please see the original `RFC
<https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555>`_.
An independent set of documentation will be contributed when the feature
is promoted to stable.
Usage
=====
To use structure protection, build your program using one or more of these flags:
- ``-fexperimental-allow-pointer-field-protection-attr``: Makes the
``[[clang::pointer_field_protection]]`` attribute described below
available for use in code. Without this flag, use of the attribute will
cause an error. This flag acts as a guard against use of the feature
before it is stabilized. When the feature is stabilized, the intent
is that this flag will become a no-op and the attribute will always
be available.
- ``-fexperimental-pointer-field-protection-abi``: Enable pointer
field protection on all types that are not considered standard-layout
according to the C++ rules for standard layout. Because this
flag changes the C++ ABI, we refer to this as the pointer
field protection ABI. Specifying this flag also defines the
predefined macro ``__POINTER_FIELD_PROTECTION_ABI__``. Implies
``-fexperimental-allow-pointer-field-protection-attr``.
- ``-fexperimental-pointer-field-protection-tagged``: On architectures
that support it (currently only AArch64), for types that are not
considered trivially copyable, use the address of the object to compute
the pointer encoding. Specifying this flag also defines the predefined
macro ``__POINTER_FIELD_PROTECTION_TAGGED__``.
It is also possible to specify the attribute
``[[clang::pointer_field_protection]]`` on a struct type to opt the
struct's pointer fields into pointer field protection, even if the type is
standard layout or none of the command line flags are specified. Note that
this means that the type will not comply with pointer interconvertibility
and other standard layout rules.
Pointer field protection is inherited from bases and non-static data
members.
In order to avoid ABI breakage, the entire C++ part
of the program must be built with a consistent set of
``-fexperimental-pointer-field-protection*`` flags, and the C++ standard
library must also be built with the same flags and statically linked
into the program.
To build libc++ with pointer field protection support, pass the following
CMake flags:
.. code-block:: console
"-DRUNTIMES_${triple}_LIBCXXABI_ENABLE_SHARED=OFF" \
"-DRUNTIMES_${triple}_LIBCXX_USE_COMPILER_RT=ON" \
"-DRUNTIMES_${triple}_LIBCXX_PFP=untagged" \
"-DRUNTIMES_${triple}_LIBCXX_ENABLE_SHARED=OFF" \
"-DRUNTIMES_${triple}_LIBCXX_TEST_CONFIG=llvm-libc++-static.cfg.in" \
"-DRUNTIMES_${triple}_LIBUNWIND_ENABLE_SHARED=OFF" \
where ``${triple}`` is your target triple, such as
``aarch64-unknown-linux``.
The resulting toolchain may then be used to build programs
with pointer field protection by passing ``-stdlib=libc++
-fexperimental-pointer-field-protection-abi`` at compile time
and ``-Wl,-Bstatic -lc++ -lc++abi -Wl,-Bdynamic -lm -fuse-ld=lld
-static-libstdc++`` at link time.
Implementation
==============
TODO: Document everything else.
The implementation uses deactivation symbols as a mechanism for
globally disabling pointer field protection for a particular field. For
more information, see the `deactivation symbol section of the LangRef
<https://llvm.org/docs/LangRef.html#deactivation-symbol-operand-bundles>`_.
These symbols are named as follows:
``__pfp_ds_`` followed by the ABI encoding of the type's RTTI object
symbol name followed by ``.`` followed by the name of the field.

View File

@ -51,6 +51,7 @@ Using Clang as a Compiler
PointerAuthentication
SafeStack
ShadowCallStack
StructureProtection
SourceBasedCodeCoverage
StandardCPlusPlusModules
Modules

View File

@ -216,6 +216,11 @@ struct TypeInfoChars {
}
};
struct PFPField {
CharUnits Offset;
FieldDecl *Field;
};
/// Holds long-lived AST nodes (such as types and decls) that can be
/// referred to throughout the semantic analysis of a file.
class ASTContext : public RefCountedBase<ASTContext> {
@ -3841,6 +3846,24 @@ public:
StringRef getCUIDHash() const;
/// Returns a list of PFP fields for the given type, including subfields in
/// bases or other fields, except for fields contained within fields of union
/// type.
std::vector<PFPField> findPFPFields(QualType Ty) const;
bool hasPFPFields(QualType Ty) const;
bool isPFPField(const FieldDecl *Field) const;
/// Returns whether this record's PFP fields (if any) are trivially
/// copyable (i.e. may be memcpy'd). This may also return true if the
/// record does not have any PFP fields, so it may be necessary for the caller
/// to check for PFP fields, e.g. by calling hasPFPFields().
bool arePFPFieldsTriviallyCopyable(const RecordDecl *RD) const;
llvm::SetVector<const FieldDecl *> PFPFieldsWithEvaluatedOffset;
void recordMemberDataPointerEvaluation(const ValueDecl *VD);
void recordOffsetOfEvaluation(const OffsetOfExpr *E);
private:
/// All OMPTraitInfo objects live in this collection, one per
/// `pragma omp [begin] declare variant` directive.

View File

@ -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

View File

@ -1235,6 +1235,12 @@ public:
/// Determine whether this class has any variant members.
bool hasVariantMembers() const { return data().HasVariantMembers; }
/// Returns whether the pointer fields in this class should have pointer field
/// protection (PFP) by default, either because of an attribute, the
/// -fexperimental-pointer-field-protection-abi compiler flag or inheritance
/// from a base or member with PFP.
bool isPFPType() const { return data().IsPFPType; }
/// Determine whether this class has a trivial default constructor
/// (C++11 [class.ctor]p5).
bool hasTrivialDefaultConstructor() const {

View File

@ -2637,6 +2637,19 @@ def CountedByOrNull : DeclOrTypeAttr {
let LangOpts = [COnly];
}
def NoFieldProtection : DeclOrTypeAttr {
let Spellings = [Clang<"no_field_protection">];
let Subjects = SubjectList<[Field], ErrorDiag>;
let Documentation = [Undocumented];
}
def PointerFieldProtection : DeclOrTypeAttr {
let Spellings = [Clang<"pointer_field_protection">];
let Subjects = SubjectList<[CXXRecord], ErrorDiag>;
let Documentation = [Undocumented];
let SimpleHandler = 1;
}
def SizedBy : DeclOrTypeAttr {
let Spellings = [Clang<"sized_by">];
let Subjects = SubjectList<[Field], ErrorDiag>;

View File

@ -4004,6 +4004,9 @@ def err_attribute_vecreturn_only_vector_member : Error<
"the vecreturn attribute can only be used on a class or structure with one member, which must be a vector">;
def err_attribute_vecreturn_only_pod_record : Error<
"the vecreturn attribute can only be used on a POD (plain old data) class or structure (i.e. no virtual functions)">;
def err_attribute_pointer_field_protection_experimental
: Error<"this attribute is experimental and must be explicitly enabled "
"with flag -fexperimental-allow-pointer-field-protection-attr">;
def err_sme_attr_mismatch : Error<
"function declared %0 was previously declared %1, which has different SME function attributes">;
def err_sme_call_in_non_sme_target : Error<

View File

@ -470,6 +470,13 @@ LANGOPT(RelativeCXXABIVTables, 1, 0, NotCompatible,
LANGOPT(OmitVTableRTTI, 1, 0, NotCompatible,
"Use an ABI-incompatible v-table layout that omits the RTTI component")
LANGOPT(PointerFieldProtectionAttr, 1, 0, NotCompatible,
"Allow the use of the experimental [[clang::pointer_field_protection]] attribute")
LANGOPT(PointerFieldProtectionABI, 1, 0, NotCompatible,
"Enable pointer field protection by default for all non-standard-layout types")
LANGOPT(PointerFieldProtectionTagged, 1, 0, NotCompatible,
"Use pointer identity (tag) to discriminate pointers of non-trivially-copyable types")
LANGOPT(VScaleMin, 32, 0, NotCompatible, "Minimum vscale value")
LANGOPT(VScaleMax, 32, 0, NotCompatible, "Maximum vscale value")

View File

@ -3237,6 +3237,31 @@ defm experimental_omit_vtable_rtti : BoolFOption<"experimental-omit-vtable-rtti"
NegFlag<SetFalse, [], [CC1Option], "Do not omit">,
BothFlags<[], [CC1Option], " the RTTI component from virtual tables">>;
defm experimental_allow_pointer_field_protection_attr
: BoolFOption<"experimental-allow-pointer-field-protection-attr",
LangOpts<"PointerFieldProtectionAttr">, DefaultFalse,
PosFlag<SetTrue, [], [CC1Option], "Allow">,
NegFlag<SetFalse, [], [], "Disallow">,
BothFlags<[], [ClangOption],
" the use of the "
"[[clang::pointer_field_protection]] attribute">>;
defm experimental_pointer_field_protection_abi
: BoolFOption<"experimental-pointer-field-protection-abi",
LangOpts<"PointerFieldProtectionABI">, DefaultFalse,
PosFlag<SetTrue, [], [CC1Option], "Enable">,
NegFlag<SetFalse, [], [], "Do not enable">,
BothFlags<[], [ClangOption],
" pointer field protection on all non-standard "
"layout struct types">>;
defm experimental_pointer_field_protection_tagged
: BoolFOption<"experimental-pointer-field-protection-tagged",
LangOpts<"PointerFieldProtectionTagged">, DefaultFalse,
PosFlag<SetTrue, [], [CC1Option], "Use">,
NegFlag<SetFalse, [], [], "Do not use">,
BothFlags<[], [ClangOption],
" pointer identity (tag) to discriminate pointers "
"of non-trivially-copyable types">>;
def fcxx_abi_EQ : Joined<["-"], "fc++-abi=">,
Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
HelpText<"C++ ABI to use. This will override the target C++ ABI.">;

View File

@ -15456,3 +15456,97 @@ bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames);
return Result;
}
bool ASTContext::arePFPFieldsTriviallyCopyable(const RecordDecl *RD) const {
// Check for trivially-destructible here because non-trivially-destructible
// types will always cause the type and any types derived from it to be
// considered non-trivially-copyable. The same cannot be said for
// trivially-copyable because deleting special members of a type derived from
// a non-trivially-copyable type can cause the derived type to be considered
// trivially copyable.
if (getLangOpts().PointerFieldProtectionTagged)
return !isa<CXXRecordDecl>(RD) ||
cast<CXXRecordDecl>(RD)->hasTrivialDestructor();
return true;
}
static void findPFPFields(const ASTContext &Ctx, QualType Ty, CharUnits Offset,
std::vector<PFPField> &Fields, bool IncludeVBases) {
if (auto *AT = Ctx.getAsConstantArrayType(Ty)) {
if (auto *ElemDecl = AT->getElementType()->getAsCXXRecordDecl()) {
const ASTRecordLayout &ElemRL = Ctx.getASTRecordLayout(ElemDecl);
for (unsigned i = 0; i != AT->getSize(); ++i)
findPFPFields(Ctx, AT->getElementType(), Offset + i * ElemRL.getSize(),
Fields, true);
}
}
auto *Decl = Ty->getAsCXXRecordDecl();
// isPFPType() is inherited from bases and members (including via arrays), so
// we can early exit if it is false. Unions are excluded per the API
// documentation.
if (!Decl || !Decl->isPFPType() || Decl->isUnion())
return;
const ASTRecordLayout &RL = Ctx.getASTRecordLayout(Decl);
for (FieldDecl *Field : Decl->fields()) {
CharUnits FieldOffset =
Offset +
Ctx.toCharUnitsFromBits(RL.getFieldOffset(Field->getFieldIndex()));
if (Ctx.isPFPField(Field))
Fields.push_back({FieldOffset, Field});
findPFPFields(Ctx, Field->getType(), FieldOffset, Fields,
/*IncludeVBases=*/true);
}
// Pass false for IncludeVBases below because vbases are only included in
// layout for top-level types, i.e. not bases or vbases.
for (CXXBaseSpecifier &Base : Decl->bases()) {
if (Base.isVirtual())
continue;
CharUnits BaseOffset =
Offset + RL.getBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
findPFPFields(Ctx, Base.getType(), BaseOffset, Fields,
/*IncludeVBases=*/false);
}
if (IncludeVBases) {
for (CXXBaseSpecifier &Base : Decl->vbases()) {
CharUnits BaseOffset =
Offset + RL.getVBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
findPFPFields(Ctx, Base.getType(), BaseOffset, Fields,
/*IncludeVBases=*/false);
}
}
}
std::vector<PFPField> ASTContext::findPFPFields(QualType Ty) const {
std::vector<PFPField> PFPFields;
::findPFPFields(*this, Ty, CharUnits::Zero(), PFPFields, true);
return PFPFields;
}
bool ASTContext::hasPFPFields(QualType Ty) const {
return !findPFPFields(Ty).empty();
}
bool ASTContext::isPFPField(const FieldDecl *FD) const {
if (auto *RD = dyn_cast<CXXRecordDecl>(FD->getParent()))
return RD->isPFPType() && FD->getType()->isPointerType() &&
!FD->hasAttr<NoFieldProtectionAttr>();
return false;
}
void ASTContext::recordMemberDataPointerEvaluation(const ValueDecl *VD) {
auto *FD = dyn_cast<FieldDecl>(VD);
if (!FD)
FD = cast<FieldDecl>(cast<IndirectFieldDecl>(VD)->chain().back());
if (isPFPField(FD))
PFPFieldsWithEvaluatedOffset.insert(FD);
}
void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
if (E->getNumComponents() == 0)
return;
OffsetOfNode Comp = E->getComponent(E->getNumComponents() - 1);
if (Comp.getKind() != OffsetOfNode::Field)
return;
if (FieldDecl *FD = Comp.getField(); isPFPField(FD))
PFPFieldsWithEvaluatedOffset.insert(FD);
}

View File

@ -109,9 +109,9 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
ImplicitCopyAssignmentHasConstParam(true),
HasDeclaredCopyConstructorWithConstParam(false),
HasDeclaredCopyAssignmentWithConstParam(false),
IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsLambda(false),
IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false),
HasODRHash(false), Definition(D) {}
IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsPFPType(false),
IsLambda(false), IsParsingBaseSpecifiers(false),
ComputedVisibleConversions(false), HasODRHash(false), Definition(D) {}
CXXBaseSpecifier *CXXRecordDecl::DefinitionData::getBasesSlowCase() const {
return Bases.get(Definition->getASTContext().getExternalSource());
@ -456,6 +456,9 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
if (!BaseClassDecl->allowConstDefaultInit())
data().HasUninitializedFields = true;
if (BaseClassDecl->isPFPType())
data().IsPFPType = true;
addedClassSubobject(BaseClassDecl);
}
@ -1408,6 +1411,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (FieldRec->hasVariantMembers() &&
Field->isAnonymousStructOrUnion())
data().HasVariantMembers = true;
if (FieldRec->isPFPType())
data().IsPFPType = true;
}
} else {
// Base element type of field is a non-class type.
@ -2305,6 +2311,14 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
}
setHasUninitializedExplicitInitFields(false);
}
if (getLangOpts().PointerFieldProtectionABI && !isStandardLayout()) {
data().IsPFPType = true;
} else if (hasAttr<PointerFieldProtectionAttr>()) {
data().IsPFPType = true;
data().IsStandardLayout = false;
data().IsCXX11StandardLayout = false;
}
}
bool CXXRecordDecl::mayBeAbstract() const {

View File

@ -18512,6 +18512,7 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr(
}
bool IntExprEvaluator::VisitOffsetOfExpr(const OffsetOfExpr *OOE) {
Info.Ctx.recordOffsetOfEvaluation(OOE);
CharUnits Result;
unsigned n = OOE->getNumComponents();
if (n == 0)

View File

@ -2089,6 +2089,12 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::CFISalt:
OS << "cfi_salt(\"" << cast<CFISaltAttr>(T->getAttr())->getSalt() << "\")";
break;
case attr::NoFieldProtection:
OS << "no_field_protection";
break;
case attr::PointerFieldProtection:
OS << "pointer_field_protection";
break;
}
OS << "))";
}

View File

@ -1312,6 +1312,54 @@ static llvm::Value *CoerceIntOrPtrToIntOrPtr(llvm::Value *Val, llvm::Type *Ty,
return Val;
}
static llvm::Value *CreatePFPCoercedLoad(Address Src, QualType SrcFETy,
llvm::Type *Ty, CodeGenFunction &CGF) {
std::vector<PFPField> PFPFields = CGF.getContext().findPFPFields(SrcFETy);
if (PFPFields.empty())
return nullptr;
auto LoadCoercedField = [&](CharUnits Offset,
llvm::Type *FieldType) -> llvm::Value * {
// Check whether the field at Offset is a PFP field. This function is called
// in ascending order of offset, and PFPFields is sorted by offset. This
// means that we only need to check the first element (and remove it from
// PFPFields if matching).
if (!PFPFields.empty() && PFPFields[0].Offset == Offset) {
auto FieldAddr = CGF.EmitAddressOfPFPField(Src, PFPFields[0]);
llvm::Value *FieldVal = CGF.Builder.CreateLoad(FieldAddr);
if (isa<llvm::IntegerType>(FieldType))
FieldVal = CGF.Builder.CreatePtrToInt(FieldVal, FieldType);
PFPFields.erase(PFPFields.begin());
return FieldVal;
}
auto FieldAddr =
CGF.Builder
.CreateConstInBoundsByteGEP(Src.withElementType(CGF.Int8Ty), Offset)
.withElementType(FieldType);
return CGF.Builder.CreateLoad(FieldAddr);
};
// The types handled by this function are the only ones that may be generated
// by AArch64ABIInfo::classify{Argument,Return}Type for struct types with
// pointers. PFP is only supported on AArch64.
if (isa<llvm::IntegerType>(Ty) || isa<llvm::PointerType>(Ty)) {
auto Addr = CGF.EmitAddressOfPFPField(Src, PFPFields[0]);
llvm::Value *Val = CGF.Builder.CreateLoad(Addr);
if (isa<llvm::IntegerType>(Ty))
Val = CGF.Builder.CreatePtrToInt(Val, Ty);
return Val;
}
auto *AT = cast<llvm::ArrayType>(Ty);
auto *ET = AT->getElementType();
CharUnits WordSize = CGF.getContext().toCharUnitsFromBits(
CGF.CGM.getDataLayout().getTypeSizeInBits(ET));
CharUnits Offset = CharUnits::Zero();
llvm::Value *Val = llvm::PoisonValue::get(AT);
for (unsigned Idx = 0; Idx != AT->getNumElements(); ++Idx, Offset += WordSize)
Val = CGF.Builder.CreateInsertValue(Val, LoadCoercedField(Offset, ET), Idx);
return Val;
}
/// CreateCoercedLoad - Create a load from \arg SrcPtr interpreted as
/// a pointer to an object of type \arg Ty, known to be aligned to
/// \arg SrcAlign bytes.
@ -1319,14 +1367,17 @@ static llvm::Value *CoerceIntOrPtrToIntOrPtr(llvm::Value *Val, llvm::Type *Ty,
/// This safely handles the case when the src type is smaller than the
/// destination type; in this situation the values of bits which not
/// present in the src are undefined.
static llvm::Value *CreateCoercedLoad(Address Src, llvm::Type *Ty,
CodeGenFunction &CGF) {
static llvm::Value *CreateCoercedLoad(Address Src, QualType SrcFETy,
llvm::Type *Ty, CodeGenFunction &CGF) {
llvm::Type *SrcTy = Src.getElementType();
// If SrcTy and Ty are the same, just do a load.
if (SrcTy == Ty)
return CGF.Builder.CreateLoad(Src);
if (llvm::Value *V = CreatePFPCoercedLoad(Src, SrcFETy, Ty, CGF))
return V;
llvm::TypeSize DstSize = CGF.CGM.getDataLayout().getTypeAllocSize(Ty);
if (llvm::StructType *SrcSTy = dyn_cast<llvm::StructType>(SrcTy)) {
@ -1398,8 +1449,51 @@ static llvm::Value *CreateCoercedLoad(Address Src, llvm::Type *Ty,
return CGF.Builder.CreateLoad(Tmp);
}
void CodeGenFunction::CreateCoercedStore(llvm::Value *Src, Address Dst,
llvm::TypeSize DstSize,
static bool CreatePFPCoercedStore(llvm::Value *Src, QualType SrcFETy,
Address Dst, CodeGenFunction &CGF) {
std::vector<PFPField> PFPFields = CGF.getContext().findPFPFields(SrcFETy);
if (PFPFields.empty())
return false;
llvm::Type *SrcTy = Src->getType();
auto StoreCoercedField = [&](CharUnits Offset, llvm::Value *FieldVal) {
if (!PFPFields.empty() && PFPFields[0].Offset == Offset) {
auto FieldAddr = CGF.EmitAddressOfPFPField(Dst, PFPFields[0]);
if (isa<llvm::IntegerType>(FieldVal->getType()))
FieldVal = CGF.Builder.CreateIntToPtr(FieldVal, CGF.VoidPtrTy);
CGF.Builder.CreateStore(FieldVal, FieldAddr);
PFPFields.erase(PFPFields.begin());
} else {
auto FieldAddr = CGF.Builder
.CreateConstInBoundsByteGEP(
Dst.withElementType(CGF.Int8Ty), Offset)
.withElementType(FieldVal->getType());
CGF.Builder.CreateStore(FieldVal, FieldAddr);
}
};
// The types handled by this function are the only ones that may be generated
// by AArch64ABIInfo::classify{Argument,Return}Type for struct types with
// pointers. PFP is only supported on AArch64.
if (isa<llvm::IntegerType>(SrcTy) || isa<llvm::PointerType>(SrcTy)) {
if (isa<llvm::IntegerType>(SrcTy))
Src = CGF.Builder.CreateIntToPtr(Src, CGF.VoidPtrTy);
auto Addr = CGF.EmitAddressOfPFPField(Dst, PFPFields[0]);
CGF.Builder.CreateStore(Src, Addr);
} else {
auto *AT = cast<llvm::ArrayType>(SrcTy);
auto *ET = AT->getElementType();
CharUnits WordSize = CGF.getContext().toCharUnitsFromBits(
CGF.CGM.getDataLayout().getTypeSizeInBits(ET));
CharUnits Offset = CharUnits::Zero();
for (unsigned i = 0; i != AT->getNumElements(); ++i, Offset += WordSize)
StoreCoercedField(Offset, CGF.Builder.CreateExtractValue(Src, i));
}
return true;
}
void CodeGenFunction::CreateCoercedStore(llvm::Value *Src, QualType SrcFETy,
Address Dst, llvm::TypeSize DstSize,
bool DstIsVolatile) {
if (!DstSize)
return;
@ -1419,6 +1513,9 @@ void CodeGenFunction::CreateCoercedStore(llvm::Value *Src, Address Dst,
}
}
if (CreatePFPCoercedStore(Src, SrcFETy, Dst, *this))
return;
if (SrcSize.isScalable() || SrcSize <= DstSize) {
if (SrcTy->isIntegerTy() && Dst.getElementType()->isPointerTy() &&
SrcSize == CGM.getDataLayout().getTypeAllocSize(Dst.getElementType())) {
@ -3448,6 +3545,13 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI,
if (SrcSize > DstSize) {
Builder.CreateMemCpy(Ptr, AddrToStoreInto, DstSize);
}
// Structures with PFP fields require a coerced store to add any
// pointer signatures.
if (getContext().hasPFPFields(Ty)) {
llvm::Value *Struct = Builder.CreateLoad(Ptr);
CreatePFPCoercedStore(Struct, Ty, Ptr, *this);
}
}
} else {
// Simple case, just do a coerced store of the argument into the alloca.
@ -3455,7 +3559,7 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI,
auto AI = Fn->getArg(FirstIRArg);
AI->setName(Arg->getName() + ".coerce");
CreateCoercedStore(
AI, Ptr,
AI, Ty, Ptr,
llvm::TypeSize::getFixed(
getContext().getTypeSizeInChars(Ty).getQuantity() -
ArgI.getDirectOffset()),
@ -4107,7 +4211,7 @@ void CodeGenFunction::EmitFunctionEpilog(
// If the value is offset in memory, apply the offset now.
Address V = emitAddressAtOffset(*this, ReturnValue, RetAI);
RV = CreateCoercedLoad(V, RetAI.getCoerceToType(), *this);
RV = CreateCoercedLoad(V, RetTy, RetAI.getCoerceToType(), *this);
}
// In ARC, end functions that return a retainable type with a call
@ -4156,7 +4260,7 @@ void CodeGenFunction::EmitFunctionEpilog(
auto eltAddr = Builder.CreateStructGEP(addr, i);
llvm::Value *elt = CreateCoercedLoad(
eltAddr,
eltAddr, RetTy,
unpaddedStruct ? unpaddedStruct->getElementType(unpaddedIndex++)
: unpaddedCoercionType,
*this);
@ -5629,15 +5733,24 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
} else {
uint64_t SrcSize = SrcTypeSize.getFixedValue();
uint64_t DstSize = DstTypeSize.getFixedValue();
bool HasPFPFields = getContext().hasPFPFields(I->Ty);
// If the source type is smaller than the destination type of the
// coerce-to logic, copy the source value into a temp alloca the size
// of the destination type to allow loading all of it. The bits past
// the source value are left undef.
if (SrcSize < DstSize) {
if (HasPFPFields || SrcSize < DstSize) {
Address TempAlloca = CreateTempAlloca(STy, Src.getAlignment(),
Src.getName() + ".coerce");
Builder.CreateMemCpy(TempAlloca, Src, SrcSize);
if (HasPFPFields) {
// Structures with PFP fields require a coerced load to remove any
// pointer signatures.
Builder.CreateStore(
CreatePFPCoercedLoad(Src, I->Ty, ArgInfo.getCoerceToType(),
*this),
TempAlloca);
} else
Builder.CreateMemCpy(TempAlloca, Src, SrcSize);
Src = TempAlloca;
} else {
Src = Src.withElementType(STy);
@ -5656,7 +5769,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
// In the simple case, just pass the coerced loaded value.
assert(NumIRArgs == 1);
llvm::Value *Load =
CreateCoercedLoad(Src, ArgInfo.getCoerceToType(), *this);
CreateCoercedLoad(Src, I->Ty, ArgInfo.getCoerceToType(), *this);
if (CallInfo.isCmseNSCall()) {
// For certain parameter types, clear padding bits, as they may reveal
@ -5716,7 +5829,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
continue;
Address eltAddr = Builder.CreateStructGEP(addr, i);
llvm::Value *elt = CreateCoercedLoad(
eltAddr,
eltAddr, I->Ty,
unpaddedStruct ? unpaddedStruct->getElementType(unpaddedIndex++)
: unpaddedCoercionType,
*this);
@ -6263,7 +6376,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
// If the value is offset in memory, apply the offset now.
Address StorePtr = emitAddressAtOffset(*this, DestPtr, RetAI);
CreateCoercedStore(
CI, StorePtr,
CI, RetTy, StorePtr,
llvm::TypeSize::getFixed(DestSize - RetAI.getDirectOffset()),
DestIsVolatile);
}

View File

@ -570,12 +570,21 @@ static void EmitBaseInitializer(CodeGenFunction &CGF,
isBaseVirtual);
}
static bool isMemcpyEquivalentSpecialMember(const CXXMethodDecl *D) {
static bool isMemcpyEquivalentSpecialMember(CodeGenModule &CGM,
const CXXMethodDecl *D) {
auto *CD = dyn_cast<CXXConstructorDecl>(D);
if (!(CD && CD->isCopyOrMoveConstructor()) &&
!D->isCopyAssignmentOperator() && !D->isMoveAssignmentOperator())
return false;
// Non-trivially-copyable fields with pointer field protection need to be
// copied one by one.
ASTContext &Ctx = CGM.getContext();
const CXXRecordDecl *Parent = D->getParent();
if (!Ctx.arePFPFieldsTriviallyCopyable(Parent) &&
Ctx.hasPFPFields(Ctx.getCanonicalTagType(Parent)))
return false;
// We can emit a memcpy for a trivial copy or move constructor/assignment.
if (D->isTrivial() && !D->getParent()->mayInsertExtraPadding())
return true;
@ -641,7 +650,8 @@ static void EmitMemberInitializer(CodeGenFunction &CGF,
QualType BaseElementTy = CGF.getContext().getBaseElementType(Array);
CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(MemberInit->getInit());
if (BaseElementTy.isPODType(CGF.getContext()) ||
(CE && isMemcpyEquivalentSpecialMember(CE->getConstructor()))) {
(CE &&
isMemcpyEquivalentSpecialMember(CGF.CGM, CE->getConstructor()))) {
unsigned SrcArgIndex =
CGF.CGM.getCXXABI().getSrcArgforCopyCtor(Constructor, Args);
llvm::Value *SrcPtr =
@ -909,6 +919,11 @@ public:
if (PointerAuthQualifier Q = F->getType().getPointerAuth();
Q && Q.isAddressDiscriminated())
return false;
// Non-trivially-copyable fields with pointer field protection need to be
// copied one by one.
if (!CGF.getContext().arePFPFieldsTriviallyCopyable(ClassDecl) &&
CGF.getContext().isPFPField(F))
return false;
return true;
}
@ -1044,7 +1059,8 @@ private:
CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(MemberInit->getInit());
// Bail out on non-memcpyable, not-trivially-copyable members.
if (!(CE && isMemcpyEquivalentSpecialMember(CE->getConstructor())) &&
if (!(CE &&
isMemcpyEquivalentSpecialMember(CGF.CGM, CE->getConstructor())) &&
!(FieldType.isTriviallyCopyableType(CGF.getContext()) ||
FieldType->isReferenceType()))
return false;
@ -1153,7 +1169,7 @@ private:
return nullptr;
} else if (CXXMemberCallExpr *MCE = dyn_cast<CXXMemberCallExpr>(S)) {
CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(MCE->getCalleeDecl());
if (!(MD && isMemcpyEquivalentSpecialMember(MD)))
if (!(MD && isMemcpyEquivalentSpecialMember(CGF.CGM, MD)))
return nullptr;
MemberExpr *IOA = dyn_cast<MemberExpr>(MCE->getImplicitObjectArgument());
if (!IOA)
@ -1234,6 +1250,7 @@ public:
void finish() { emitAggregatedStmts(); }
};
} // end anonymous namespace
static bool isInitializerOfDynamicClass(const CXXCtorInitializer *BaseInit) {
@ -2287,7 +2304,7 @@ void CodeGenFunction::EmitCXXConstructorCall(
// If this is a trivial constructor, emit a memcpy now before we lose
// the alignment information on the argument.
// FIXME: It would be better to preserve alignment information into CallArg.
if (isMemcpyEquivalentSpecialMember(D)) {
if (isMemcpyEquivalentSpecialMember(CGM, D)) {
assert(E->getNumArgs() == 1 && "unexpected argcount for trivial ctor");
const Expr *Arg = E->getArg(0);
@ -2355,7 +2372,7 @@ void CodeGenFunction::EmitCXXConstructorCall(
// If this is a trivial constructor, just emit what's needed. If this is a
// union copy constructor, we must emit a memcpy, because the AST does not
// model that copy.
if (isMemcpyEquivalentSpecialMember(D)) {
if (isMemcpyEquivalentSpecialMember(CGM, D)) {
assert(Args.size() == 2 && "unexpected argcount for trivial ctor");
QualType SrcTy = D->getParamDecl(0)->getType().getNonReferenceType();
Address Src = makeNaturalAddressForPointer(

View File

@ -5589,12 +5589,13 @@ static Address emitAddrOfZeroSizeField(CodeGenFunction &CGF, Address Base,
return CGF.Builder.CreateConstInBoundsByteGEP(Base, Offset);
}
/// Drill down to the storage of a field without walking into
/// reference types.
/// Drill down to the storage of a field without walking into reference types,
/// and without respect for pointer field protection.
///
/// The resulting address doesn't necessarily have the right type.
static Address emitAddrOfFieldStorage(CodeGenFunction &CGF, Address base,
const FieldDecl *field, bool IsInBounds) {
static Address emitRawAddrOfFieldStorage(CodeGenFunction &CGF, Address base,
const FieldDecl *field,
bool IsInBounds) {
if (isEmptyFieldForLayout(CGF.getContext(), field))
return emitAddrOfZeroSizeField(CGF, base, field, IsInBounds);
@ -5617,6 +5618,21 @@ static Address emitAddrOfFieldStorage(CodeGenFunction &CGF, Address base,
return CGF.Builder.CreateStructGEP(base, idx, field->getName());
}
/// Drill down to the storage of a field without walking into reference types,
/// wrapping the address in an llvm.protected.field.ptr intrinsic for the
/// pointer field protection feature if necessary.
///
/// The resulting address doesn't necessarily have the right type.
static Address emitAddrOfFieldStorage(CodeGenFunction &CGF, Address base,
const FieldDecl *field, bool IsInBounds) {
Address Addr = emitRawAddrOfFieldStorage(CGF, base, field, IsInBounds);
if (!CGF.getContext().isPFPField(field))
return Addr;
return CGF.EmitAddressOfPFPField(base, Addr, field);
}
static Address emitPreserveStructAccess(CodeGenFunction &CGF, LValue base,
Address addr, const FieldDecl *field) {
const RecordDecl *rec = field->getParent();

View File

@ -134,7 +134,7 @@ public:
if (llvm::Value *Result = ConstantEmitter(CGF).tryEmitConstantExpr(E)) {
CGF.CreateCoercedStore(
Result, Dest.getAddress(),
Result, E->getType(), Dest.getAddress(),
llvm::TypeSize::getFixed(
Dest.getPreferredSize(CGF.getContext(), E->getType())
.getQuantity()),
@ -2334,6 +2334,7 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, LValue Src, QualType Ty,
auto *Inst = Builder.CreateMemCpy(DestPtr, SrcPtr, SizeVal, isVolatile);
addInstToCurrentSourceAtom(Inst, nullptr);
emitPFPPostCopyUpdates(DestPtr, SrcPtr, Ty);
// Determine the metadata to describe the position of any padding in this
// memcpy, as well as the TBAA tags for the members of the struct, in case

View File

@ -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 <optional>
using namespace clang;
using namespace CodeGen;
@ -905,6 +906,32 @@ bool ConstStructBuilder::Build(const APValue &Val, const RecordDecl *RD,
if (!EltInit)
return false;
if (CGM.getContext().isPFPField(*Field)) {
llvm::ConstantInt *Disc;
llvm::Constant *AddrDisc;
if (CGM.getContext().arePFPFieldsTriviallyCopyable(RD)) {
uint64_t FieldSignature =
llvm::getPointerAuthStableSipHash(CGM.getPFPFieldName(*Field));
Disc = llvm::ConstantInt::get(CGM.Int64Ty, FieldSignature);
AddrDisc = llvm::ConstantPointerNull::get(CGM.VoidPtrTy);
} else if (Emitter.isAbstract()) {
// isAbstract means that we don't know the global's address. Since we
// can only form a pointer without knowing the address if the fields are
// trivially copyable, we need to return false otherwise.
return false;
} else {
Disc = llvm::ConstantInt::get(CGM.Int64Ty,
-(Layout.getFieldOffset(FieldNo) / 8));
AddrDisc = Emitter.getCurrentAddrPrivate();
}
EltInit = llvm::ConstantPtrAuth::get(
EltInit, llvm::ConstantInt::get(CGM.Int32Ty, 2), Disc, AddrDisc,
CGM.getPFPDeactivationSymbol(*Field));
if (!CGM.getContext().arePFPFieldsTriviallyCopyable(RD))
Emitter.registerCurrentAddrPrivate(EltInit,
cast<llvm::GlobalValue>(AddrDisc));
}
if (ZeroInitPadding) {
if (!DoZeroInitPadding(Layout, FieldNo, **Field, AllowOverwrite,
SizeSoFar, ZeroFieldSize))
@ -1654,7 +1681,20 @@ ConstantEmitter::emitAbstract(SourceLocation loc, const APValue &value,
llvm::Constant *ConstantEmitter::tryEmitForInitializer(const VarDecl &D) {
initializeNonAbstract(D.getType().getAddressSpace());
return markIfFailed(tryEmitPrivateForVarInit(D));
llvm::Constant *Init = tryEmitPrivateForVarInit(D);
// If a placeholder address was needed for a TLS variable, implying that the
// initializer's value depends on its address, then the object may not be
// initialized in .tdata because the initializer will be memcpy'd to the
// thread's TLS. Instead the initialization must be done in code.
if (!PlaceholderAddresses.empty() && D.getTLSKind() != VarDecl::TLS_None) {
for (auto [_, GV] : PlaceholderAddresses)
GV->eraseFromParent();
PlaceholderAddresses.clear();
Init = nullptr;
}
return markIfFailed(Init);
}
llvm::Constant *ConstantEmitter::tryEmitForInitializer(const Expr *E,
@ -2609,6 +2649,7 @@ CodeGenModule::getMemberPointerConstant(const UnaryOperator *uo) {
return getCXXABI().EmitMemberFunctionPointer(method);
// Otherwise, a member data pointer.
getContext().recordMemberDataPointerEvaluation(decl);
uint64_t fieldOffset = getContext().getFieldOffset(decl);
CharUnits chars = getContext().toCharUnitsFromBits((int64_t) fieldOffset);
return getCXXABI().EmitMemberDataPointer(type, chars);

View File

@ -47,6 +47,7 @@
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/MDBuilder.h"
#include "llvm/Support/CRC.h"
#include "llvm/Support/SipHash.h"
#include "llvm/Support/xxhash.h"
#include "llvm/Transforms/Scalar/LowerExpectIntrinsic.h"
#include "llvm/Transforms/Utils/PromoteMemToReg.h"
@ -2251,6 +2252,36 @@ static void emitNonZeroVLAInit(CodeGenFunction &CGF, QualType baseType,
CGF.EmitBlock(contBB);
}
Address CodeGenFunction::EmitAddressOfPFPField(Address RecordPtr,
const PFPField &Field) {
return EmitAddressOfPFPField(
RecordPtr,
Builder.CreateConstInBoundsByteGEP(RecordPtr.withElementType(Int8Ty),
Field.Offset),
Field.Field);
}
Address CodeGenFunction::EmitAddressOfPFPField(Address RecordPtr,
Address PtrPtr,
const FieldDecl *Field) {
llvm::Value *Disc;
if (CGM.getContext().arePFPFieldsTriviallyCopyable(Field->getParent())) {
uint64_t FieldSignature =
llvm::getPointerAuthStableSipHash(CGM.getPFPFieldName(Field));
Disc = llvm::ConstantInt::get(CGM.Int64Ty, FieldSignature);
} else
Disc = Builder.CreatePtrToInt(RecordPtr.getBasePointer(), CGM.Int64Ty);
llvm::GlobalValue *DS = CGM.getPFPDeactivationSymbol(Field);
llvm::OperandBundleDef DSBundle("deactivation-symbol", DS);
llvm::Value *Args[] = {PtrPtr.getBasePointer(), Disc, Builder.getTrue()};
return Address(
Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::protected_field_ptr,
PtrPtr.getType()),
Args, DSBundle),
VoidPtrTy, PtrPtr.getAlignment());
}
void
CodeGenFunction::EmitNullInitialization(Address DestPtr, QualType Ty) {
// Ignore empty classes in C++.
@ -2310,13 +2341,20 @@ CodeGenFunction::EmitNullInitialization(Address DestPtr, QualType Ty) {
// Get and call the appropriate llvm.memcpy overload.
Builder.CreateMemCpy(DestPtr, SrcPtr, SizeVal, false);
return;
} else {
// Otherwise, just memset the whole thing to zero. This is legal
// because in LLVM, all default initializers (other than the ones we just
// handled above, and the case handled below) are guaranteed to have a bit
// pattern of all zeros.
Builder.CreateMemSet(DestPtr, Builder.getInt8(0), SizeVal, false);
}
// Otherwise, just memset the whole thing to zero. This is legal
// because in LLVM, all default initializers (other than the ones we just
// handled above) are guaranteed to have a bit pattern of all zeros.
Builder.CreateMemSet(DestPtr, Builder.getInt8(0), SizeVal, false);
// With the pointer field protection feature, null pointers do not have a bit
// pattern of zero in memory, so we must initialize them separately.
for (auto &Field : getContext().findPFPFields(Ty)) {
auto addr = EmitAddressOfPFPField(DestPtr, Field);
Builder.CreateStore(llvm::ConstantPointerNull::get(VoidPtrTy), addr);
}
}
llvm::BlockAddress *CodeGenFunction::GetAddrOfLabel(const LabelDecl *L) {
@ -3426,3 +3464,14 @@ void CodeGenFunction::addInstToNewSourceAtom(llvm::Instruction *KeyInstruction,
DI->addInstToCurrentSourceAtom(KeyInstruction, Backup);
}
}
void CodeGenFunction::emitPFPPostCopyUpdates(Address DestPtr, Address SrcPtr,
QualType Ty) {
for (auto &Field : getContext().findPFPFields(Ty)) {
if (getContext().arePFPFieldsTriviallyCopyable(Field.Field->getParent()))
continue;
auto DestFieldPtr = EmitAddressOfPFPField(DestPtr, Field);
auto SrcFieldPtr = EmitAddressOfPFPField(SrcPtr, Field);
Builder.CreateStore(Builder.CreateLoad(SrcFieldPtr), DestFieldPtr);
}
}

View File

@ -1740,6 +1740,10 @@ public:
void addInstToNewSourceAtom(llvm::Instruction *KeyInstruction,
llvm::Value *Backup);
/// Copy all PFP fields from SrcPtr to DestPtr while updating signatures,
/// assuming that DestPtr was already memcpy'd from SrcPtr.
void emitPFPPostCopyUpdates(Address DestPtr, Address SrcPtr, QualType Ty);
private:
/// SwitchInsn - This is nearest current switch instruction. It is null if
/// current context is not in a switch.
@ -5067,8 +5071,8 @@ public:
/// Create a store to \arg DstPtr from \arg Src, truncating the stored value
/// to at most \arg DstSize bytes.
void CreateCoercedStore(llvm::Value *Src, Address Dst, llvm::TypeSize DstSize,
bool DstIsVolatile);
void CreateCoercedStore(llvm::Value *Src, QualType SrcFETy, Address Dst,
llvm::TypeSize DstSize, bool DstIsVolatile);
/// EmitExtendGCLifetime - Given a pointer to an Objective-C object,
/// make sure it survives garbage collection until this point.
@ -5563,6 +5567,10 @@ public:
void EmitRISCVMultiVersionResolver(llvm::Function *Resolver,
ArrayRef<FMVResolverOption> Options);
Address EmitAddressOfPFPField(Address RecordPtr, const PFPField &Field);
Address EmitAddressOfPFPField(Address RecordPtr, Address FieldPtr,
const FieldDecl *Field);
private:
QualType getVarArgType(const Expr *Arg);

View File

@ -980,6 +980,7 @@ void CodeGenModule::Release() {
applyGlobalValReplacements();
applyReplacements();
emitMultiVersionFunctions();
emitPFPFieldsWithEvaluatedOffset();
if (Context.getLangOpts().IncrementalExtensions &&
GlobalTopLevelStmtBlockInFlight.first) {
@ -4896,6 +4897,40 @@ void CodeGenModule::emitMultiVersionFunctions() {
emitMultiVersionFunctions();
}
// Symbols with this prefix are used as deactivation symbols for PFP fields.
// See clang/docs/StructureProtection.rst for more information.
static const char PFPDeactivationSymbolPrefix[] = "__pfp_ds_";
llvm::GlobalValue *
CodeGenModule::getPFPDeactivationSymbol(const FieldDecl *FD) {
std::string DSName = PFPDeactivationSymbolPrefix + getPFPFieldName(FD);
llvm::GlobalValue *DS = TheModule.getNamedValue(DSName);
if (!DS) {
DS = new llvm::GlobalVariable(TheModule, Int8Ty, false,
llvm::GlobalVariable::ExternalWeakLinkage,
nullptr, DSName);
DS->setVisibility(llvm::GlobalValue::HiddenVisibility);
}
return DS;
}
void CodeGenModule::emitPFPFieldsWithEvaluatedOffset() {
llvm::Constant *Nop = llvm::ConstantExpr::getIntToPtr(
llvm::ConstantInt::get(Int64Ty, 0xd503201f), VoidPtrTy);
for (auto *FD : getContext().PFPFieldsWithEvaluatedOffset) {
std::string DSName = PFPDeactivationSymbolPrefix + getPFPFieldName(FD);
llvm::GlobalValue *OldDS = TheModule.getNamedValue(DSName);
llvm::GlobalValue *DS = llvm::GlobalAlias::create(
Int8Ty, 0, llvm::GlobalValue::ExternalLinkage, DSName, Nop, &TheModule);
DS->setVisibility(llvm::GlobalValue::HiddenVisibility);
if (OldDS) {
DS->takeName(OldDS);
OldDS->replaceAllUsesWith(DS);
OldDS->eraseFromParent();
}
}
}
static void replaceDeclarationWith(llvm::GlobalValue *Old,
llvm::Constant *New) {
assert(cast<llvm::Function>(Old)->isDeclaration() && "Not a declaration");
@ -8474,3 +8509,12 @@ void CodeGenModule::moveLazyEmissionStates(CodeGenModule *NewBuilder) {
NewBuilder->ABI->MangleCtx = std::move(ABI->MangleCtx);
}
std::string CodeGenModule::getPFPFieldName(const FieldDecl *FD) {
std::string OutName;
llvm::raw_string_ostream Out(OutName);
getCXXABI().getMangleContext().mangleCanonicalTypeName(
getContext().getCanonicalTagType(FD->getParent()), Out, false);
Out << "." << FD->getName();
return OutName;
}

View File

@ -1882,6 +1882,9 @@ public:
std::optional<llvm::Attribute::AttrKind>
StackProtectorAttribute(const Decl *D) const;
std::string getPFPFieldName(const FieldDecl *FD);
llvm::GlobalValue *getPFPDeactivationSymbol(const FieldDecl *FD);
private:
bool shouldDropDLLAttribute(const Decl *D, const llvm::GlobalValue *GV) const;
@ -2085,6 +2088,10 @@ private:
llvm::Metadata *CreateMetadataIdentifierImpl(QualType T, MetadataTypeMap &Map,
StringRef Suffix);
/// Emit deactivation symbols for any PFP fields whose offset is taken with
/// offsetof.
void emitPFPFieldsWithEvaluatedOffset();
};
} // end namespace CodeGen

View File

@ -1258,6 +1258,7 @@ llvm::Constant *ItaniumCXXABI::EmitMemberPointer(const APValue &MP,
return pointerAuthResignMemberFunctionPointer(Src, MPType, SrcType, CGM);
}
getContext().recordMemberDataPointerEvaluation(MPD);
CharUnits FieldOffset =
getContext().toCharUnitsFromBits(getContext().getFieldOffset(MPD));
return EmitMemberDataPointer(MPT, ThisAdjustment + FieldOffset);

View File

@ -2968,6 +2968,7 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberPointer(const APValue &MP,
// the class in which it was declared, and convert from there if necessary.
// For indirect field decls, get the outermost anonymous field and use the
// parent class.
Ctx.recordMemberDataPointerEvaluation(MPD);
CharUnits FieldOffset = Ctx.toCharUnitsFromBits(Ctx.getFieldOffset(MPD));
const FieldDecl *FD = dyn_cast<FieldDecl>(MPD);
if (!FD)

View File

@ -7738,6 +7738,24 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Twine("-funique-source-file-identifier=") + Input.getBaseInput()));
}
if (Args.hasFlag(
options::OPT_fexperimental_allow_pointer_field_protection_attr,
options::OPT_fno_experimental_allow_pointer_field_protection_attr,
false) ||
Args.hasFlag(options::OPT_fexperimental_pointer_field_protection_abi,
options::OPT_fno_experimental_pointer_field_protection_abi,
false))
CmdArgs.push_back("-fexperimental-allow-pointer-field-protection-attr");
if (!IsCudaDevice) {
Args.addOptInFlag(
CmdArgs, options::OPT_fexperimental_pointer_field_protection_abi,
options::OPT_fno_experimental_pointer_field_protection_abi);
Args.addOptInFlag(
CmdArgs, options::OPT_fexperimental_pointer_field_protection_tagged,
options::OPT_fno_experimental_pointer_field_protection_tagged);
}
// Setup statistics file output.
SmallString<128> StatsFile = getStatsFileName(Args, Output, Input, D);
if (!StatsFile.empty()) {

View File

@ -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();

View File

@ -6756,6 +6756,10 @@ static void handleZeroCallUsedRegsAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(ZeroCallUsedRegsAttr::Create(S.Context, Kind, AL));
}
static void handleNoPFPAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(NoFieldProtectionAttr::Create(S.Context, AL));
}
static void handleCountedByAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
auto *FD = dyn_cast<FieldDecl>(D);
assert(FD);
@ -7863,6 +7867,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
handleCountedByAttrField(S, D, AL);
break;
case ParsedAttr::AT_NoFieldProtection:
handleNoPFPAttrField(S, D, AL);
break;
// Microsoft attributes:
case ParsedAttr::AT_LayoutVersion:
handleLayoutVersion(S, D, AL);
@ -8145,6 +8153,14 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_GCCStruct:
handleGCCStructAttr(S, D, AL);
break;
case ParsedAttr::AT_PointerFieldProtection:
if (!S.getLangOpts().PointerFieldProtectionAttr)
S.Diag(AL.getLoc(),
diag::err_attribute_pointer_field_protection_experimental)
<< AL << AL.isRegularKeywordAttribute() << D->getLocation();
handleSimpleAttribute<PointerFieldProtectionAttr>(S, D, AL);
break;
}
}

View File

@ -0,0 +1,33 @@
// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s
struct S {
int* ptr1;
__attribute__((no_field_protection)) int* ptr2;
private:
int private_data;
}; // Not Standard-layout, mixed access
// CHECK-LABEL: load_pointers_without_no_field_protection
int* load_pointers_without_no_field_protection(S *t) {
return t->ptr1;
}
// CHECK: call {{.*}} @llvm.protected.field.ptr.p0{{.*}}
// CHECK-LABEL: load_pointers_with_no_field_protection
int* load_pointers_with_no_field_protection(S *t) {
return t->ptr2;
}
// CHECK-NOT: call {{.*}} @llvm.protected.field.ptr.p0{{.*}}
// CHECK-LABEL: store_pointers_without_no_field_protection
void store_pointers_without_no_field_protection(S *t, int *input) {
t->ptr1 = input;
}
// CHECK: call {{.*}} @llvm.protected.field.ptr.p0{{.*}}
// CHECK-LABEL: store_pointers_with_no_field_protection
void store_pointers_with_no_field_protection(S *t, int *input) {
t->ptr2 = input;
}
// CHECK-NOT: call {{.*}} @llvm.protected.field.ptr.p0{{.*}}

View File

@ -0,0 +1,157 @@
// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-allow-pointer-field-protection-attr -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s
// Non-standard layout. Pointer fields are signed and discriminated by type.
struct Pointer {
int* ptr;
private:
int private_data;
};
void pass_pointer_callee(Pointer p);
// CHECK: define dso_local void @_Z12pass_pointerP7Pointer(
void pass_pointer(Pointer *pp) {
// CHECK: %0 = load ptr, ptr %pp.addr, align 8
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %agg.tmp, ptr align 8 %0, i64 16, i1 false)
// CHECK: %1 = getelementptr inbounds i8, ptr %agg.tmp, i64 0
// CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ]
// CHECK: %3 = load ptr, ptr %2, align 8
// CHECK: %4 = ptrtoint ptr %3 to i64
// CHECK: %5 = insertvalue [2 x i64] poison, i64 %4, 0
// CHECK: %6 = getelementptr inbounds i8, ptr %agg.tmp, i64 8
// CHECK: %7 = load i64, ptr %6, align 8
// CHECK: %8 = insertvalue [2 x i64] %5, i64 %7, 1
// CHECK: call void @_Z19pass_pointer_callee7Pointer([2 x i64] %8)
pass_pointer_callee(*pp);
}
// CHECK: define dso_local void @_Z14passed_pointer7PointerPS_([2 x i64] %p.coerce, ptr noundef %pp)
void passed_pointer(Pointer p, Pointer *pp) {
// CHECK: %p = alloca %struct.Pointer, align 8
// CHECK: %pp.addr = alloca ptr, align 8
// CHECK: %0 = extractvalue [2 x i64] %p.coerce, 0
// CHECK: %1 = getelementptr inbounds i8, ptr %p, i64 0
// CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ]
// CHECK: %3 = inttoptr i64 %0 to ptr
// CHECK: store ptr %3, ptr %2, align 8
// CHECK: %4 = extractvalue [2 x i64] %p.coerce, 1
// CHECK: %5 = getelementptr inbounds i8, ptr %p, i64 8
// CHECK: store i64 %4, ptr %5, align 8
// CHECK: store ptr %pp, ptr %pp.addr, align 8
// CHECK: %6 = load ptr, ptr %pp.addr, align 8
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %6, ptr align 8 %p, i64 12, i1 false)
*pp = p;
}
// CHECK: define dso_local [2 x i64] @_Z14return_pointerP7Pointer(ptr noundef %pp)
Pointer return_pointer(Pointer *pp) {
// CHECK: %retval = alloca %struct.Pointer, align 8
// CHECK: %pp.addr = alloca ptr, align 8
// CHECK: store ptr %pp, ptr %pp.addr, align 8
// CHECK: %0 = load ptr, ptr %pp.addr, align 8
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %retval, ptr align 8 %0, i64 16, i1 false)
// CHECK: %1 = getelementptr inbounds i8, ptr %retval, i64 0
// CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ]
// CHECK: %3 = load ptr, ptr %2, align 8
// CHECK: %4 = ptrtoint ptr %3 to i64
// CHECK: %5 = insertvalue [2 x i64] poison, i64 %4, 0
// CHECK: %6 = getelementptr inbounds i8, ptr %retval, i64 8
// CHECK: %7 = load i64, ptr %6, align 8
// CHECK: %8 = insertvalue [2 x i64] %5, i64 %7, 1
// CHECK: ret [2 x i64] %8
return *pp;
}
Pointer returned_pointer_callee();
// CHECK: define dso_local void @_Z16returned_pointerP7Pointer(ptr noundef %pp)
void returned_pointer(Pointer *pp) {
// CHECK: %pp.addr = alloca ptr, align 8
// CHECK: %ref.tmp = alloca %struct.Pointer, align 8
// CHECK: store ptr %pp, ptr %pp.addr, align 8
// CHECK: %call = call [2 x i64] @_Z23returned_pointer_calleev()
// CHECK: %0 = extractvalue [2 x i64] %call, 0
// CHECK: %1 = getelementptr inbounds i8, ptr %ref.tmp, i64 0
// CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 36403, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS7Pointer.ptr) ]
// CHECK: %3 = inttoptr i64 %0 to ptr
// CHECK: store ptr %3, ptr %2, align 8
// CHECK: %4 = extractvalue [2 x i64] %call, 1
// CHECK: %5 = getelementptr inbounds i8, ptr %ref.tmp, i64 8
// CHECK: store i64 %4, ptr %5, align 8
// CHECK: %6 = load ptr, ptr %pp.addr, align 8
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %6, ptr align 8 %ref.tmp, i64 12, i1 false)
*pp = returned_pointer_callee();
}
union PointerUnion {
Pointer ptr;
};
void pass_pointer_union_callee(PointerUnion pu);
// CHECK: define dso_local void @_Z18pass_pointer_unionP12PointerUnion(
void pass_pointer_union(PointerUnion *pup) {
// CHECK: %0 = load ptr, ptr %pup.addr, align 8
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %agg.tmp, ptr align 8 %0, i64 16, i1 false)
// CHECK: %coerce.dive = getelementptr inbounds nuw %union.PointerUnion, ptr %agg.tmp, i32 0, i32 0
// CHECK: %1 = load [2 x i64], ptr %coerce.dive, align 8
// CHECK: call void @_Z25pass_pointer_union_callee12PointerUnion([2 x i64] %1)
pass_pointer_union_callee(*pup);
}
// Manual opt into PFP, non-trivially destructible.
// Pointer fields are signed and discriminated by address.
// Trivial ABI: passed and returned by value despite being non-trivial.
struct [[clang::trivial_abi]] [[clang::pointer_field_protection]] TrivialAbiPointer {
int *ptr;
~TrivialAbiPointer();
};
// CHECK: define dso_local void @_Z24pass_trivial_abi_pointer17TrivialAbiPointerPS_(ptr %p.coerce, ptr noundef %pp)
void pass_trivial_abi_pointer(TrivialAbiPointer p, TrivialAbiPointer *pp) {
// CHECK: %p = alloca %struct.TrivialAbiPointer, align 8
// CHECK: %pp.addr = alloca ptr, align 8
// CHECK: %coerce.dive = getelementptr inbounds nuw %struct.TrivialAbiPointer, ptr %p, i32 0, i32 0
// CHECK: %0 = getelementptr inbounds i8, ptr %coerce.dive, i64 0
// CHECK: %1 = ptrtoint ptr %coerce.dive to i64
// CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %0, i64 %1, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ]
// CHECK: store ptr %p.coerce, ptr %2, align 8
// CHECK: store ptr %pp, ptr %pp.addr, align 8
// CHECK: %3 = load ptr, ptr %pp.addr, align 8
// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %3, ptr align 8 %p, i64 8, i1 false)
// CHECK: %4 = getelementptr inbounds i8, ptr %3, i64 0
// CHECK: %5 = ptrtoint ptr %3 to i64
// CHECK: %6 = call ptr @llvm.protected.field.ptr.p0(ptr %4, i64 %5, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ]
// CHECK: %7 = getelementptr inbounds i8, ptr %p, i64 0
// CHECK: %8 = ptrtoint ptr %p to i64
// CHECK: %9 = call ptr @llvm.protected.field.ptr.p0(ptr %7, i64 %8, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ]
// CHECK: %10 = load ptr, ptr %9, align 8
// CHECK: store ptr %10, ptr %6, align 8
// CHECK: call void @_ZN17TrivialAbiPointerD1Ev(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) %p)
*pp = p;
}
// CHECK: define dso_local i64 @_Z26return_trivial_abi_pointerP17TrivialAbiPointer(ptr noundef %pp)
TrivialAbiPointer return_trivial_abi_pointer(TrivialAbiPointer *pp) {
// CHECK: %retval = alloca %struct.TrivialAbiPointer, align 8
// CHECK: %pp.addr = alloca ptr, align 8
// CHECK: store ptr %pp, ptr %pp.addr, align 8
// CHECK: %0 = load ptr, ptr %pp.addr, align 8
// CHECK: call void @_ZN17TrivialAbiPointerC1ERKS_(ptr noundef nonnull align 8 dereferenceable(8) %retval, ptr noundef nonnull align 8 dereferenceable(8) %0)
// CHECK: %1 = getelementptr inbounds i8, ptr %retval, i64 0
// CHECK: %2 = ptrtoint ptr %retval to i64
// CHECK: %3 = call ptr @llvm.protected.field.ptr.p0(ptr %1, i64 %2, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS17TrivialAbiPointer.ptr) ]
// CHECK: %4 = load ptr, ptr %3, align 8
// CHECK: %5 = ptrtoint ptr %4 to i64
// CHECK: ret i64 %5
return *pp;
}

View File

@ -0,0 +1,40 @@
// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -O1 -o - %s | FileCheck %s
int val;
struct Pointer {
int* ptr;
private:
int private_data;
};
struct ArrayType {
int* array[3];
private:
int private_data;
};
struct Array {
ArrayType array;
private:
int private_data;
};
struct Struct {
Pointer ptr;
};
// CHECK-LABEL: test_pointer
Pointer test_pointer(Pointer t) {
t.ptr = &val;
return t;
}
// CHECK: call {{.*}} @llvm.protected.field.ptr.p0{{.*}}
// CHECK-LABEL: test_struct
int* test_struct(Struct *t) {
return (t->ptr).ptr;
}
// CHECK: call {{.*}} @llvm.protected.field.ptr.p0{{.*}}

View File

@ -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);
}

View File

@ -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

View File

@ -0,0 +1,19 @@
// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s
struct S {
void *p;
private:
int private_data;
};
// CHECK-LABEL: null_init
void null_init() {
// Check that null initialization was correctly applied to the pointer field.
// CHECK: %s = alloca %struct.S, align 8
// CHECK: call void @llvm.memset.p0.i64(ptr align 8 %s, i8 0, i64 16, i1 false)
// CHECK: %0 = getelementptr inbounds i8, ptr %s, i64 0
// CHECK: %1 = call ptr @llvm.protected.field.ptr.p0(ptr %0, i64 29832, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.p) ]
// CHECK: store ptr null, ptr %1, align 8
S s{};
}

View File

@ -0,0 +1,33 @@
// RUN: %clang_cc1 -triple aarch64-linux -fexperimental-pointer-field-protection-abi -fexperimental-pointer-field-protection-tagged -emit-llvm -o - %s | FileCheck %s
struct S {
int* ptr;
private:
int private_data;
}; // Not Standard-layout, mixed access
// CHECK-LABEL: load_pointers
int* load_pointers(S *t) {
// CHECK: %t.addr = alloca ptr, align 8
// CHECK: store ptr %t, ptr %t.addr, align 8
// CHECK: %0 = load ptr, ptr %t.addr, align 8
// CHECK: %ptr = getelementptr inbounds nuw %struct.S, ptr %0, i32 0, i32 0
// CHECK: %1 = call ptr @llvm.protected.field.ptr.p0(ptr %ptr, i64 63261, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.ptr) ]
// CHECK: %2 = load ptr, ptr %1, align 8
// CHECK: ret ptr %2
return t->ptr;
}
// CHECK-LABEL: store_pointers
void store_pointers(S* t, int* p) {
// CHECK: %t.addr = alloca ptr, align 8
// CHECK: %p.addr = alloca ptr, align 8
// CHECK: store ptr %t, ptr %t.addr, align 8
// CHECK: store ptr %p, ptr %p.addr, align 8
// CHECK: %0 = load ptr, ptr %p.addr, align 8
// CHECK: %1 = load ptr, ptr %t.addr, align 8
// CHECK: %ptr = getelementptr inbounds nuw %struct.S, ptr %1, i32 0, i32 0
// CHECK: %2 = call ptr @llvm.protected.field.ptr.p0(ptr %ptr, i64 63261, i1 true) [ "deactivation-symbol"(ptr @__pfp_ds__ZTS1S.ptr) ]
// CHECK: store ptr %0, ptr %2, align 8
t->ptr = p;
}

View File

@ -125,6 +125,7 @@
// CHECK-NEXT: NoDestroy (SubjectMatchRule_variable)
// CHECK-NEXT: NoDuplicate (SubjectMatchRule_function)
// CHECK-NEXT: NoEscape (SubjectMatchRule_variable_is_parameter)
// CHECK-NEXT: NoFieldProtection (SubjectMatchRule_field)
// CHECK-NEXT: NoInline (SubjectMatchRule_function)
// CHECK-NEXT: NoInstrumentFunction (SubjectMatchRule_function, SubjectMatchRule_objc_method)
// CHECK-NEXT: NoMerge (SubjectMatchRule_function, SubjectMatchRule_variable)
@ -183,6 +184,7 @@
// CHECK-NEXT: PassObjectSize (SubjectMatchRule_variable_is_parameter)
// CHECK-NEXT: PatchableFunctionEntry (SubjectMatchRule_function, SubjectMatchRule_objc_method)
// CHECK-NEXT: Pointer (SubjectMatchRule_record_not_is_union)
// CHECK-NEXT: PointerFieldProtection (SubjectMatchRule_record)
// CHECK-NEXT: PreserveNone (SubjectMatchRule_hasType_functionType)
// CHECK-NEXT: RandomizeLayout (SubjectMatchRule_record)
// CHECK-NEXT: ReadOnlyPlacement (SubjectMatchRule_record)

View File

@ -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

View File

@ -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