[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:
parent
f45754b65e
commit
370d7ce580
95
clang/docs/StructureProtection.rst
Normal file
95
clang/docs/StructureProtection.rst
Normal 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.
|
||||
@ -51,6 +51,7 @@ Using Clang as a Compiler
|
||||
PointerAuthentication
|
||||
SafeStack
|
||||
ShadowCallStack
|
||||
StructureProtection
|
||||
SourceBasedCodeCoverage
|
||||
StandardCPlusPlusModules
|
||||
Modules
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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<
|
||||
|
||||
@ -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")
|
||||
|
||||
|
||||
@ -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.">;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 << "))";
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
clang/test/CodeGenCXX/pfp-attribute-disable.cpp
Normal file
33
clang/test/CodeGenCXX/pfp-attribute-disable.cpp
Normal 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{{.*}}
|
||||
157
clang/test/CodeGenCXX/pfp-coerce.cpp
Normal file
157
clang/test/CodeGenCXX/pfp-coerce.cpp
Normal 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;
|
||||
}
|
||||
|
||||
40
clang/test/CodeGenCXX/pfp-load-store.cpp
Normal file
40
clang/test/CodeGenCXX/pfp-load-store.cpp
Normal 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{{.*}}
|
||||
14
clang/test/CodeGenCXX/pfp-member-pointer-offsetof.cpp
Normal file
14
clang/test/CodeGenCXX/pfp-member-pointer-offsetof.cpp
Normal 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);
|
||||
}
|
||||
59
clang/test/CodeGenCXX/pfp-memcpy.cpp
Normal file
59
clang/test/CodeGenCXX/pfp-memcpy.cpp
Normal 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
|
||||
19
clang/test/CodeGenCXX/pfp-null-init.cpp
Normal file
19
clang/test/CodeGenCXX/pfp-null-init.cpp
Normal 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{};
|
||||
}
|
||||
|
||||
33
clang/test/CodeGenCXX/pfp-struct-gep.cpp
Normal file
33
clang/test/CodeGenCXX/pfp-struct-gep.cpp
Normal 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;
|
||||
}
|
||||
@ -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)
|
||||
|
||||
5
clang/test/Preprocessor/pfp-predefines.c
Normal file
5
clang/test/Preprocessor/pfp-predefines.c
Normal 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
|
||||
6
clang/test/SemaCXX/attr-pointer-field-protection.cpp
Normal file
6
clang/test/SemaCXX/attr-pointer-field-protection.cpp
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user