[clang] [clang] Function type attribute to prevent CFI instrumentation (PR #135836)
via cfe-commits
cfe-commits at lists.llvm.org
Fri May 2 11:15:59 PDT 2025
https://github.com/PiJoules updated https://github.com/llvm/llvm-project/pull/135836
>From bcb99801417631cefbc175b578a216ef9f0e24a6 Mon Sep 17 00:00:00 2001
From: Leonard Chan <leonardchan at google.com>
Date: Wed, 9 Apr 2025 14:21:00 -0700
Subject: [PATCH] [clang] Function type attribute to prevent CFI
instrumentation
This introduces the attribute discussed in
https://discourse.llvm.org/t/rfc-function-type-attribute-to-prevent-cfi-instrumentation/85458.
The proposed name has been changed from `no_cfi` to
`cfi_unchecked_callee` to help differentiate from `no_sanitize("cfi")`
more easily. The proposed attribute has the following semantics:
1. Indirect calls to a function type with this attribute will not be
instrumented with CFI. That is, the indirect call will not be
checked. Note that this only changes the behavior for indirect calls
on pointers to function types having this attribute. It does not
prevent all indirect function calls for a given type from being checked.
2. All direct references to a function whose type has this attribute will
always reference the true function definition rather than an entry
in the CFI jump table.
3. When a pointer to a function with this attribute is implicitly cast
to a pointer to a function without this attribute, the compiler
will give a warning saying this attribute is discarded. This warning
can be silenced with an explicit C-style cast or C++ static_cast.
---
clang/include/clang/AST/ASTContext.h | 5 +
clang/include/clang/AST/ASTNodeTraverser.h | 3 +
clang/include/clang/AST/RecursiveASTVisitor.h | 6 +
clang/include/clang/AST/Type.h | 46 ++++++
clang/include/clang/AST/TypeLoc.h | 27 ++++
clang/include/clang/AST/TypeProperties.td | 9 ++
clang/include/clang/Basic/Attr.td | 5 +
clang/include/clang/Basic/AttrDocs.td | 48 ++++++
clang/include/clang/Basic/DiagnosticGroups.td | 2 +
.../clang/Basic/DiagnosticSemaKinds.td | 9 ++
clang/include/clang/Basic/TypeNodes.td | 1 +
.../clang/Serialization/TypeBitCodes.def | 1 +
clang/lib/AST/ASTContext.cpp | 39 +++++
clang/lib/AST/ASTDiagnostic.cpp | 6 +
clang/lib/AST/ASTImporter.cpp | 9 ++
clang/lib/AST/ASTStructuralEquivalence.cpp | 7 +
clang/lib/AST/ItaniumMangle.cpp | 1 +
clang/lib/AST/Type.cpp | 18 +++
clang/lib/AST/TypeLoc.cpp | 4 +
clang/lib/AST/TypePrinter.cpp | 15 ++
clang/lib/CodeGen/CGDebugInfo.cpp | 1 +
clang/lib/CodeGen/CGExpr.cpp | 16 +-
clang/lib/CodeGen/CGExprConstant.cpp | 8 +-
clang/lib/CodeGen/CGPointerAuth.cpp | 5 +
clang/lib/CodeGen/CodeGenFunction.cpp | 1 +
clang/lib/CodeGen/ItaniumCXXABI.cpp | 10 ++
clang/lib/Sema/SemaDeclAttr.cpp | 18 +++
clang/lib/Sema/SemaExpr.cpp | 17 ++
clang/lib/Sema/SemaExprCXX.cpp | 30 ++++
clang/lib/Sema/SemaInit.cpp | 15 +-
clang/lib/Sema/SemaTemplateInstantiate.cpp | 5 +
clang/lib/Sema/SemaType.cpp | 37 +++++
clang/lib/Sema/TreeTransform.h | 25 +++
clang/lib/Serialization/ASTReader.cpp | 5 +
clang/lib/Serialization/ASTWriter.cpp | 5 +
...ecked-callee-attribute-member-function.cpp | 53 +++++++
.../cfi-unchecked-callee-attribute.cpp | 77 +++++++++
.../Frontend/cfi-unchecked-callee-attribute.c | 70 +++++++++
.../cfi-unchecked-callee-attribute.cpp | 146 ++++++++++++++++++
clang/tools/libclang/CIndex.cpp | 5 +
40 files changed, 803 insertions(+), 7 deletions(-)
create mode 100644 clang/test/CodeGen/cfi-unchecked-callee-attribute-member-function.cpp
create mode 100644 clang/test/CodeGen/cfi-unchecked-callee-attribute.cpp
create mode 100644 clang/test/Frontend/cfi-unchecked-callee-attribute.c
create mode 100644 clang/test/Frontend/cfi-unchecked-callee-attribute.cpp
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 50083b055199e..7d7c529da26c6 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -262,6 +262,8 @@ class ASTContext : public RefCountedBase<ASTContext> {
mutable llvm::FoldingSet<CountAttributedType> CountAttributedTypes;
+ mutable llvm::FoldingSet<CFIUncheckedCalleeType> CFIUncheckedCalleeTypes;
+
mutable llvm::FoldingSet<QualifiedTemplateName> QualifiedTemplateNames;
mutable llvm::FoldingSet<DependentTemplateName> DependentTemplateNames;
mutable llvm::FoldingSet<SubstTemplateTemplateParmStorage>
@@ -1464,6 +1466,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
bool OrNull,
ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const;
+ /// Return a type wrapped with the `cfi_unchecked_callee` attribute.
+ QualType getCFIUncheckedCalleeType(QualType Wrapped) const;
+
/// Return the uniqued reference to a type adjusted from the original
/// type to a new type.
QualType getAdjustedType(QualType Orig, QualType New) const;
diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index 7bb435146f752..0656c32e648d7 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -445,6 +445,9 @@ class ASTNodeTraverser
void VisitBTFTagAttributedType(const BTFTagAttributedType *T) {
Visit(T->getWrappedType());
}
+ void VisitCFIUncheckedCalleeType(const CFIUncheckedCalleeType *T) {
+ Visit(T->getWrappedType());
+ }
void VisitHLSLAttributedResourceType(const HLSLAttributedResourceType *T) {
QualType Contained = T->getContainedType();
if (!Contained.isNull())
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 3edc8684d0a19..4d8b77f12445f 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1142,6 +1142,9 @@ DEF_TRAVERSE_TYPE(InjectedClassNameType, {})
DEF_TRAVERSE_TYPE(AttributedType,
{ TRY_TO(TraverseType(T->getModifiedType())); })
+DEF_TRAVERSE_TYPE(CFIUncheckedCalleeType,
+ { TRY_TO(TraverseType(T->getWrappedType())); })
+
DEF_TRAVERSE_TYPE(CountAttributedType, {
if (T->getCountExpr())
TRY_TO(TraverseStmt(T->getCountExpr()));
@@ -1448,6 +1451,9 @@ DEF_TRAVERSE_TYPELOC(MacroQualifiedType,
DEF_TRAVERSE_TYPELOC(AttributedType,
{ TRY_TO(TraverseTypeLoc(TL.getModifiedLoc())); })
+DEF_TRAVERSE_TYPELOC(CFIUncheckedCalleeType,
+ { TRY_TO(TraverseTypeLoc(TL.getWrappedLoc())); })
+
DEF_TRAVERSE_TYPELOC(CountAttributedType,
{ TRY_TO(TraverseTypeLoc(TL.getInnerLoc())); })
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 3e1fb05ad537c..9f261515d0e5e 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -2566,6 +2566,8 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
bool isSignableType() const;
bool isAnyPointerType() const; // Any C pointer or ObjC object pointer
bool isCountAttributedType() const;
+ bool isCFIUncheckedCalleeType() const;
+ bool isPointerToCFIUncheckedCalleeType() const;
bool isBlockPointerType() const;
bool isVoidPointerType() const;
bool isReferenceType() const;
@@ -3060,6 +3062,10 @@ template <> const TemplateSpecializationType *Type::getAs() const;
/// until it reaches an AttributedType or a non-sugared type.
template <> const AttributedType *Type::getAs() const;
+/// This will check for an CFIUncheckedCalleeType by removing any existing sugar
+/// until it reaches an CFIUncheckedCalleeType or a non-sugared type.
+template <> const CFIUncheckedCalleeType *Type::getAs() const;
+
/// This will check for a BoundsAttributedType by removing any existing
/// sugar until it reaches an BoundsAttributedType or a non-sugared type.
template <> const BoundsAttributedType *Type::getAs() const;
@@ -3349,6 +3355,30 @@ class BoundsAttributedType : public Type, public llvm::FoldingSetNode {
}
};
+class CFIUncheckedCalleeType : public Type, public llvm::FoldingSetNode {
+public:
+ bool isSugared() const { return true; }
+ QualType desugar() const { return WrappedTy; }
+
+ QualType getWrappedType() const { return WrappedTy; }
+
+ static bool classof(const Type *T) {
+ return T->getTypeClass() == CFIUncheckedCallee;
+ }
+
+ static void Profile(llvm::FoldingSetNodeID &ID, QualType WrappedTy);
+
+ void Profile(llvm::FoldingSetNodeID &ID) { Profile(ID, WrappedTy); }
+
+protected:
+ friend class ASTContext;
+
+ CFIUncheckedCalleeType(QualType Wrapped, QualType Canon);
+
+private:
+ QualType WrappedTy;
+};
+
/// Represents a sugar type with `__counted_by` or `__sized_by` annotations,
/// including their `_or_null` variants.
class CountAttributedType final
@@ -8244,6 +8274,20 @@ inline bool Type::isObjectPointerType() const {
return false;
}
+inline bool Type::isCFIUncheckedCalleeType() const {
+ return getAs<CFIUncheckedCalleeType>();
+}
+
+inline bool Type::isPointerToCFIUncheckedCalleeType() const {
+ if (const auto *MFT = getAs<MemberPointerType>()) {
+ if (MFT->isMemberFunctionPointer())
+ return MFT->getPointeeType()->getAs<CFIUncheckedCalleeType>();
+ }
+ if (const auto *T = getAs<PointerType>())
+ return T->getPointeeType()->getAs<CFIUncheckedCalleeType>();
+ return false;
+}
+
inline bool Type::isFunctionPointerType() const {
if (const auto *T = getAs<PointerType>())
return T->getPointeeType()->isFunctionType();
@@ -8799,6 +8843,8 @@ template <typename T> const T *Type::getAsAdjusted() const {
Ty = A->desugar().getTypePtr();
else if (const auto *M = dyn_cast<MacroQualifiedType>(Ty))
Ty = M->desugar().getTypePtr();
+ else if (const auto *M = dyn_cast<CFIUncheckedCalleeType>(Ty))
+ Ty = M->desugar().getTypePtr();
else
break;
}
diff --git a/clang/include/clang/AST/TypeLoc.h b/clang/include/clang/AST/TypeLoc.h
index 92661b8b13fe0..9c43f90327df1 100644
--- a/clang/include/clang/AST/TypeLoc.h
+++ b/clang/include/clang/AST/TypeLoc.h
@@ -973,6 +973,31 @@ class HLSLAttributedResourceTypeLoc
}
};
+struct CFIUncheckedCalleeLocInfo {
+ SourceLocation AttrLoc;
+};
+
+/// Type source information for the CFIUncheckedCallee Type
+class CFIUncheckedCalleeTypeLoc
+ : public ConcreteTypeLoc<UnqualTypeLoc, CFIUncheckedCalleeTypeLoc,
+ CFIUncheckedCalleeType,
+ CFIUncheckedCalleeLocInfo> {
+public:
+ TypeLoc getWrappedLoc() const { return getInnerTypeLoc(); }
+
+ void setAttrLoc(const SourceLocation &Loc) { getLocalData()->AttrLoc = Loc; }
+ SourceLocation getAttrLoc() const { return getLocalData()->AttrLoc; }
+ void initializeLocal(ASTContext &Context, SourceLocation Loc) {
+ setAttrLoc(Loc);
+ }
+ QualType getWrappedType() const { return getTypePtr()->getWrappedType(); }
+ QualType getInnerType() const { return getTypePtr()->getWrappedType(); }
+
+ SourceRange getLocalSourceRange() const {
+ return getInnerTypeLoc().getLocalSourceRange();
+ }
+};
+
struct ObjCObjectTypeLocInfo {
SourceLocation TypeArgsLAngleLoc;
SourceLocation TypeArgsRAngleLoc;
@@ -2736,6 +2761,8 @@ inline T TypeLoc::getAsAdjusted() const {
Cur = ATL.getOriginalLoc();
else if (auto MQL = Cur.getAs<MacroQualifiedTypeLoc>())
Cur = MQL.getInnerLoc();
+ else if (auto CFIUCL = Cur.getAs<CFIUncheckedCalleeTypeLoc>())
+ Cur = CFIUCL.getWrappedLoc();
else
break;
}
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index f4b8ce0994ba8..d881368f8d4c0 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -633,6 +633,15 @@ let Class = ParenType in {
}]>;
}
+let Class = CFIUncheckedCalleeType in {
+ def : Property<"WrappedTy", QualType> {
+ let Read = [{ node->getWrappedType() }];
+ }
+ def : Creator<[{
+ return ctx.getCFIUncheckedCalleeType(WrappedTy);
+ }]>;
+}
+
let Class = MacroQualifiedType in {
def : Property<"underlyingType", QualType> {
let Read = [{ node->getUnderlyingType() }];
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index a734eb6658c3d..40537f2666667 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3082,6 +3082,11 @@ def NoDeref : TypeAttr {
let Documentation = [NoDerefDocs];
}
+def CFIUncheckedCallee : TypeAttr {
+ let Spellings = [Clang<"cfi_unchecked_callee">];
+ let Documentation = [CFIUncheckedCalleeDocs];
+}
+
def ReqdWorkGroupSize : InheritableAttr {
// Does not have a [[]] spelling because it is an OpenCL-related attribute.
let Spellings = [GNU<"reqd_work_group_size">];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index cbb397cb31dfb..60e833311b66d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6869,6 +6869,54 @@ for references or Objective-C object pointers.
}];
}
+def CFIUncheckedCalleeDocs : Documentation {
+ let Category = DocCatType;
+ let Content = [{
+``cfi_unchecked_callee`` is a function type attribute which prevents the compiler from instrumenting
+`Control Flow Integrity <https://clang.llvm.org/docs/ControlFlowIntegrity.html>`_ checks on indirect
+function calls. Specifically, the attribute has the following semantics:
+
+1. Indirect calls to a function type with this attribute will not be instrumented with CFI. That is,
+ the indirect call will not be checked. Note that this only changes the behavior for indirect calls
+ on pointers to function types having this attribute. It does not prevent all indirect function calls
+ for a given type from being checked.
+2. All direct references to a function whose type has this attribute will always reference the
+ function definition rather than an entry in the CFI jump table.
+3. When a pointer to a function with this attribute is implicitly cast to a pointer to a function
+ without this attribute, the compiler will give a warning saying this attribute is discarded. This
+ warning can be silenced with an explicit cast. Note an explicit cast just disables the warning, so
+ direct references to a function with a ``cfi_unchecked_callee`` attribute will still reference the
+ function definition rather than the CFI jump table.
+
+.. code-block:: c
+
+ #define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee))
+
+ void no_cfi() CFI_UNCHECKED_CALLEE {}
+
+ void (*with_cfi)() = no_cfi; // warning: implicit conversion discards `cfi_unchecked_callee` attribute.
+ // `with_cfi` also points to the actual definition of `no_cfi` rather than
+ // its jump table entry.
+
+ void invoke(void (CFI_UNCHECKED_CALLEE *func)()) {
+ func(); // CFI will not instrument this indirect call.
+
+ void (*func2)() = func; // warning: implicit conversion discards `cfi_unchecked_callee` attribute.
+
+ func2(); // CFI will instrument this indirect call. Users should be careful however because if this
+ // references a function with type `cfi_unchecked_callee`, then the CFI check may incorrectly
+ // fail because the reference will be to the function definition rather than the CFI jump
+ // table entry.
+ }
+
+This attribute can only be applied on functions or member functions. This attribute can be a good
+alternative to ``no_sanitize("cfi")`` if you only want to disable innstrumentation for specific indirect
+calls rather than applying ``no_sanitize("cfi")`` on the whole function containing indirect call. Note
+that ``cfi_unchecked_attribute`` is a type attribute doesn't disable CFI instrumentation on a function
+body.
+}];
+}
+
def ReinitializesDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 3c24387630d0c..d48a8f105742c 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1576,6 +1576,8 @@ def FunctionMultiVersioning
def NoDeref : DiagGroup<"noderef">;
+def CFIUncheckedCallee : DiagGroup<"cfi-unchecked-callee">;
+
// -fbounds-safety and bounds annotation related warnings
def BoundsSafetyCountedByEltTyUnknownSize :
DiagGroup<"bounds-safety-counted-by-elt-type-unknown-size">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e5a7cdc14a737..b3828169c85aa 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12628,6 +12628,15 @@ def warn_noderef_on_non_pointer_or_array : Warning<
def warn_noderef_to_dereferenceable_pointer : Warning<
"casting to dereferenceable pointer removes 'noderef' attribute">, InGroup<NoDeref>;
+def warn_cfi_unchecked_callee_on_non_function
+ : Warning<"use of `cfi_unchecked_callee` on %0; can only be used on "
+ "function types">,
+ InGroup<CFIUncheckedCallee>;
+def warn_cast_discards_cfi_unchecked_callee
+ : Warning<"implicit conversion from %0 to %1 discards "
+ "`cfi_unchecked_callee` attribute">,
+ InGroup<CFIUncheckedCallee>;
+
def err_builtin_launder_invalid_arg : Error<
"%select{non-pointer|function pointer|void pointer}0 argument to "
"'__builtin_launder' is not allowed">;
diff --git a/clang/include/clang/Basic/TypeNodes.td b/clang/include/clang/Basic/TypeNodes.td
index 7e550ca2992f3..2d0dc6b257783 100644
--- a/clang/include/clang/Basic/TypeNodes.td
+++ b/clang/include/clang/Basic/TypeNodes.td
@@ -111,6 +111,7 @@ def ObjCObjectType : TypeNode<Type>;
def ObjCInterfaceType : TypeNode<ObjCObjectType>, LeafType;
def ObjCObjectPointerType : TypeNode<Type>;
def BoundsAttributedType : TypeNode<Type, 1>;
+def CFIUncheckedCalleeType : TypeNode<Type>, NeverCanonical;
def CountAttributedType : TypeNode<BoundsAttributedType>, NeverCanonical;
def PipeType : TypeNode<Type>;
def AtomicType : TypeNode<Type>;
diff --git a/clang/include/clang/Serialization/TypeBitCodes.def b/clang/include/clang/Serialization/TypeBitCodes.def
index 3c78b87805010..939656f2d50f3 100644
--- a/clang/include/clang/Serialization/TypeBitCodes.def
+++ b/clang/include/clang/Serialization/TypeBitCodes.def
@@ -68,5 +68,6 @@ TYPE_BIT_CODE(PackIndexing, PACK_INDEXING, 56)
TYPE_BIT_CODE(CountAttributed, COUNT_ATTRIBUTED, 57)
TYPE_BIT_CODE(ArrayParameter, ARRAY_PARAMETER, 58)
TYPE_BIT_CODE(HLSLAttributedResource, HLSLRESOURCE_ATTRIBUTED, 59)
+TYPE_BIT_CODE(CFIUncheckedCallee, CFI_UNCHECKED_CALLEE, 60)
#undef TYPE_BIT_CODE
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index ae136ae271882..597d743cbb661 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -2464,6 +2464,10 @@ TypeInfo ASTContext::getTypeInfoImpl(const Type *T) const {
return getTypeInfo(
cast<AttributedType>(T)->getEquivalentType().getTypePtr());
+ case Type::CFIUncheckedCallee:
+ return getTypeInfo(
+ cast<CFIUncheckedCalleeType>(T)->getWrappedType().getTypePtr());
+
case Type::CountAttributed:
return getTypeInfo(cast<CountAttributedType>(T)->desugar().getTypePtr());
@@ -3589,6 +3593,26 @@ QualType ASTContext::removePtrSizeAddrSpace(QualType T) const {
return T;
}
+QualType ASTContext::getCFIUncheckedCalleeType(QualType Wrapped) const {
+ llvm::FoldingSetNodeID ID;
+ CFIUncheckedCalleeType::Profile(ID, Wrapped);
+
+ void *InsertPos = nullptr;
+ CFIUncheckedCalleeType *Ty =
+ CFIUncheckedCalleeTypes.FindNodeOrInsertPos(ID, InsertPos);
+ if (Ty)
+ return QualType(Ty, 0);
+
+ QualType CanonTy = getCanonicalType(Wrapped);
+ Ty = new (*this, alignof(CFIUncheckedCalleeType))
+ CFIUncheckedCalleeType(Wrapped, CanonTy);
+
+ Types.push_back(Ty);
+ CFIUncheckedCalleeTypes.InsertNode(Ty, InsertPos);
+
+ return QualType(Ty, 0);
+}
+
QualType ASTContext::getCountAttributedType(
QualType WrappedTy, Expr *CountExpr, bool CountInBytes, bool OrNull,
ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const {
@@ -3655,6 +3679,12 @@ ASTContext::adjustType(QualType Orig,
MQT->getMacroIdentifier());
}
+ case Type::CFIUncheckedCallee: {
+ const auto *CFIUCT = cast<CFIUncheckedCalleeType>(Orig);
+ return getCFIUncheckedCalleeType(
+ adjustType(CFIUCT->getWrappedType(), Adjust));
+ }
+
default:
return Adjust(Orig);
}
@@ -14223,6 +14253,15 @@ static QualType getCommonSugarTypeNode(ASTContext &Ctx, const Type *X,
return Ctx.getDecayedType(Ctx.getCommonSugaredType(OX, OY),
Ctx.getQualifiedType(Underlying));
}
+ case Type::CFIUncheckedCallee: {
+ const auto *CX = cast<CFIUncheckedCalleeType>(X),
+ *CY = cast<CFIUncheckedCalleeType>(Y);
+ QualType WX = CX->getWrappedType(), WY = CY->getWrappedType();
+ if (!Ctx.hasSameType(WX, WY))
+ return QualType();
+ // FIXME: It's inefficient to have to unify the wrapped types.
+ return Ctx.getCFIUncheckedCalleeType(Ctx.getCommonSugaredType(WX, WY));
+ }
case Type::Attributed: {
const auto *AX = cast<AttributedType>(X), *AY = cast<AttributedType>(Y);
AttributedType::Kind Kind = AX->getAttrKind();
diff --git a/clang/lib/AST/ASTDiagnostic.cpp b/clang/lib/AST/ASTDiagnostic.cpp
index 6cb09b0492ac9..9e32ed167a848 100644
--- a/clang/lib/AST/ASTDiagnostic.cpp
+++ b/clang/lib/AST/ASTDiagnostic.cpp
@@ -54,6 +54,12 @@ QualType clang::desugarForDiagnostic(ASTContext &Context, QualType QT,
QT = MDT->desugar();
continue;
}
+ // ... or a cfi_unchecked_callee type ...
+ if (const CFIUncheckedCalleeType *CFIUCT =
+ dyn_cast<CFIUncheckedCalleeType>(Ty)) {
+ QT = CFIUCT->desugar();
+ continue;
+ }
// ...or a substituted template type parameter ...
if (const SubstTemplateTypeParmType *ST =
dyn_cast<SubstTemplateTypeParmType>(Ty)) {
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index eb0e6866e367b..10e18a2b9dd1a 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -1784,6 +1784,15 @@ ASTNodeImporter::VisitMacroQualifiedType(const MacroQualifiedType *T) {
ToIdentifier);
}
+ExpectedType
+ASTNodeImporter::VisitCFIUncheckedCalleeType(const CFIUncheckedCalleeType *T) {
+ ExpectedType ToWrappedTypeOrErr = import(T->getWrappedType());
+ if (!ToWrappedTypeOrErr)
+ return ToWrappedTypeOrErr.takeError();
+
+ return Importer.getToContext().getCFIUncheckedCalleeType(*ToWrappedTypeOrErr);
+}
+
ExpectedType clang::ASTNodeImporter::VisitAdjustedType(const AdjustedType *T) {
Error Err = Error::success();
QualType ToOriginalType = importChecked(Err, T->getOriginalType());
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 499854a75abc6..4b979dd6d8a3b 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1129,6 +1129,13 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return false;
break;
+ case Type::CFIUncheckedCallee:
+ if (!IsStructurallyEquivalent(
+ Context, cast<CFIUncheckedCalleeType>(T1)->getWrappedType(),
+ cast<CFIUncheckedCalleeType>(T2)->getWrappedType()))
+ return false;
+ break;
+
case Type::CountAttributed:
if (!IsStructurallyEquivalent(Context,
cast<CountAttributedType>(T1)->desugar(),
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 33a8728728574..33a68e5fde107 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -2474,6 +2474,7 @@ bool CXXNameMangler::mangleUnresolvedTypeOrSimpleId(QualType Ty,
case Type::BitInt:
case Type::DependentBitInt:
case Type::CountAttributed:
+ case Type::CFIUncheckedCallee:
llvm_unreachable("type is illegal as a nested name specifier");
case Type::SubstTemplateTypeParmPack:
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 59369fba2e772..7b0f9f4d5e09d 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -464,6 +464,11 @@ void CountAttributedType::Profile(llvm::FoldingSetNodeID &ID,
ID.AddPointer(CountExpr);
}
+void CFIUncheckedCalleeType::Profile(llvm::FoldingSetNodeID &ID,
+ QualType WrappedTy) {
+ ID.AddPointer(WrappedTy.getAsOpaquePtr());
+}
+
/// getArrayElementTypeNoTypeQual - If this is an array type, return the
/// element type of the array, potentially with type qualifiers missing.
/// This method should never be used when type qualifiers are meaningful.
@@ -622,6 +627,10 @@ template <typename T> static const T *getAsSugar(const Type *Cur) {
}
}
+template <> const CFIUncheckedCalleeType *Type::getAs() const {
+ return getAsSugar<CFIUncheckedCalleeType>(this);
+}
+
template <> const TypedefType *Type::getAs() const {
return getAsSugar<TypedefType>(this);
}
@@ -1227,6 +1236,7 @@ struct SimpleTransformVisitor : public TypeVisitor<Derived, QualType> {
SUGARED_TYPE_CLASS(Typedef)
SUGARED_TYPE_CLASS(ObjCTypeParam)
SUGARED_TYPE_CLASS(MacroQualified)
+ SUGARED_TYPE_CLASS(CFIUncheckedCallee)
QualType VisitAdjustedType(const AdjustedType *T) {
QualType originalType = recurse(T->getOriginalType());
@@ -2048,6 +2058,10 @@ class GetContainedDeducedTypeVisitor
return Visit(T->getUnderlyingType());
}
+ Type *VisitCFIUncheckedCalleeType(const CFIUncheckedCalleeType *T) {
+ return Visit(T->getWrappedType());
+ }
+
Type *VisitAdjustedType(const AdjustedType *T) {
return Visit(T->getOriginalType());
}
@@ -3980,6 +3994,10 @@ void TypeCoupledDeclRefInfo::setFromOpaqueValue(void *V) {
Data.setFromOpaqueValue(V);
}
+CFIUncheckedCalleeType::CFIUncheckedCalleeType(QualType Wrapped, QualType Canon)
+ : Type(CFIUncheckedCallee, Canon, Wrapped->getDependence()),
+ WrappedTy(Wrapped) {}
+
BoundsAttributedType::BoundsAttributedType(TypeClass TC, QualType Wrapped,
QualType Canon)
: Type(TC, Canon, Wrapped->getDependence()), WrappedTy(Wrapped) {}
diff --git a/clang/lib/AST/TypeLoc.cpp b/clang/lib/AST/TypeLoc.cpp
index 3d1b5ca966b66..6787662669033 100644
--- a/clang/lib/AST/TypeLoc.cpp
+++ b/clang/lib/AST/TypeLoc.cpp
@@ -746,6 +746,10 @@ namespace {
return Visit(T.getInnerLoc());
}
+ TypeLoc VisitCFIUncheckedCalleeTypeLoc(CFIUncheckedCalleeTypeLoc T) {
+ return Visit(T.getWrappedLoc());
+ }
+
TypeLoc VisitAdjustedTypeLoc(AdjustedTypeLoc T) {
return Visit(T.getOriginalLoc());
}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index cba1a2d98d660..4fd427ebfab23 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -247,6 +247,7 @@ bool TypePrinter::canPrefixQualifiers(const Type *T,
case Type::DependentBitInt:
case Type::BTFTagAttributed:
case Type::HLSLAttributedResource:
+ case Type::CFIUncheckedCallee:
CanPrefixQualifiers = true;
break;
@@ -2089,6 +2090,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::NoDeref:
OS << "noderef";
break;
+ case attr::CFIUncheckedCallee:
+ OS << "cfi_unchecked_callee";
+ break;
case attr::AcquireHandle:
OS << "acquire_handle";
break;
@@ -2113,6 +2117,17 @@ void TypePrinter::printBTFTagAttributedAfter(const BTFTagAttributedType *T,
printAfter(T->getWrappedType(), OS);
}
+void TypePrinter::printCFIUncheckedCalleeBefore(const CFIUncheckedCalleeType *T,
+ raw_ostream &OS) {
+ printBefore(T->getWrappedType(), OS);
+}
+
+void TypePrinter::printCFIUncheckedCalleeAfter(const CFIUncheckedCalleeType *T,
+ raw_ostream &OS) {
+ printAfter(T->getWrappedType(), OS);
+ OS << " __attribute__((cfi_unchecked_callee))";
+}
+
void TypePrinter::printHLSLAttributedResourceBefore(
const HLSLAttributedResourceType *T, raw_ostream &OS) {
printBefore(T->getWrappedType(), OS);
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index f3ec498d4064b..6e6003ed726d2 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -3906,6 +3906,7 @@ llvm::DIType *CGDebugInfo::CreateTypeNode(QualType Ty, llvm::DIFile *Unit) {
case Type::Decltype:
case Type::PackIndexing:
case Type::UnaryTransform:
+ case Type::CFIUncheckedCallee:
break;
}
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index bba7d1e805f3f..adb486bd58da6 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3003,6 +3003,10 @@ static LValue EmitFunctionDeclLValue(CodeGenFunction &CGF, const Expr *E,
GlobalDecl GD) {
const FunctionDecl *FD = cast<FunctionDecl>(GD.getDecl());
llvm::Constant *V = CGF.CGM.getFunctionPointer(GD);
+ if (E->getType()->isCFIUncheckedCalleeType()) {
+ if (auto *GV = dyn_cast<llvm::GlobalValue>(V))
+ V = llvm::NoCFIValue::get(GV);
+ }
CharUnits Alignment = CGF.getContext().getDeclAlign(FD);
return CGF.MakeAddrLValue(V, E->getType(), Alignment,
AlignmentSource::Decl);
@@ -6136,7 +6140,7 @@ LValue CodeGenFunction::EmitStmtExprLValue(const StmtExpr *E) {
AlignmentSource::Decl);
}
-RValue CodeGenFunction::EmitCall(QualType CalleeType,
+RValue CodeGenFunction::EmitCall(QualType OriginalCalleeType,
const CGCallee &OrigCallee, const CallExpr *E,
ReturnValueSlot ReturnValue,
llvm::Value *Chain,
@@ -6144,7 +6148,7 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
CGFunctionInfo const **ResolvedFnInfo) {
// Get the actual function type. The callee type will always be a pointer to
// function type or a block pointer type.
- assert(CalleeType->isFunctionPointerType() &&
+ assert(OriginalCalleeType->isFunctionPointerType() &&
"Call must have function pointer type!");
const Decl *TargetDecl =
@@ -6154,7 +6158,7 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
!cast<FunctionDecl>(TargetDecl)->isImmediateFunction()) &&
"trying to emit a call to an immediate function");
- CalleeType = getContext().getCanonicalType(CalleeType);
+ QualType CalleeType = getContext().getCanonicalType(OriginalCalleeType);
auto PointeeType = cast<PointerType>(CalleeType)->getPointeeType();
@@ -6240,10 +6244,14 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
FD && FD->hasAttr<OpenCLKernelAttr>())
CGM.getTargetCodeGenInfo().setOCLKernelStubCallingConvention(FnType);
+ // Use the original callee type because the canonical type will have
+ // attributes stripped.
+ bool CFIUnchecked = OriginalCalleeType->isPointerToCFIUncheckedCalleeType();
+
// If we are checking indirect calls and this call is indirect, check that the
// function pointer is a member of the bit set for the function type.
if (SanOpts.has(SanitizerKind::CFIICall) &&
- (!TargetDecl || !isa<FunctionDecl>(TargetDecl))) {
+ (!TargetDecl || !isa<FunctionDecl>(TargetDecl)) && !CFIUnchecked) {
SanitizerScope SanScope(this);
EmitSanitizerStatReport(llvm::SanStat_CFI_ICall);
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp
index b21ebeee4bed1..0e5279d23e078 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -2236,8 +2236,12 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) {
return ConstantLValue(C);
};
- if (const auto *FD = dyn_cast<FunctionDecl>(D))
- return PtrAuthSign(CGM.getRawFunctionPointer(FD));
+ if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
+ llvm::Constant *C = CGM.getRawFunctionPointer(FD);
+ if (FD->getType()->isCFIUncheckedCalleeType())
+ C = llvm::NoCFIValue::get(cast<llvm::GlobalValue>(C));
+ return PtrAuthSign(C);
+ }
if (const auto *VD = dyn_cast<VarDecl>(D)) {
// We can never refer to a variable with local storage.
diff --git a/clang/lib/CodeGen/CGPointerAuth.cpp b/clang/lib/CodeGen/CGPointerAuth.cpp
index 0a183a8524c17..1df89db85eddf 100644
--- a/clang/lib/CodeGen/CGPointerAuth.cpp
+++ b/clang/lib/CodeGen/CGPointerAuth.cpp
@@ -517,6 +517,11 @@ llvm::Constant *CodeGenModule::getMemberFunctionPointer(llvm::Constant *Pointer,
Pointer, PointerAuth.getKey(), nullptr,
cast_or_null<llvm::ConstantInt>(PointerAuth.getDiscriminator()));
+ if (const auto *MFT = dyn_cast<MemberPointerType>(FT.getTypePtr())) {
+ if (MFT->isPointerToCFIUncheckedCalleeType())
+ Pointer = llvm::NoCFIValue::get(cast<llvm::GlobalValue>(Pointer));
+ }
+
return Pointer;
}
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index d773cdd505ff4..0aed5550d95a7 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -2573,6 +2573,7 @@ void CodeGenFunction::EmitVariablyModifiedType(QualType type) {
case Type::SubstTemplateTypeParm:
case Type::MacroQualified:
case Type::CountAttributed:
+ case Type::CFIUncheckedCallee:
// Keep walking after single level desugaring.
type = type.getSingleStepDesugaredType(getContext());
break;
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 70b53be7e77a3..4aa80845acbe4 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -693,6 +693,16 @@ CGCallee ItaniumCXXABI::EmitLoadOfMemberFunctionPointer(
llvm::Constant *CheckTypeDesc;
bool ShouldEmitCFICheck = CGF.SanOpts.has(SanitizerKind::CFIMFCall) &&
CGM.HasHiddenLTOVisibility(RD);
+
+ if (ShouldEmitCFICheck) {
+ if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
+ if (BinOp->isPtrMemOp()) {
+ if (BinOp->getRHS()->getType()->isPointerToCFIUncheckedCalleeType())
+ ShouldEmitCFICheck = false;
+ }
+ }
+ }
+
bool ShouldEmitVFEInfo = CGM.getCodeGenOpts().VirtualFunctionElimination &&
CGM.HasHiddenLTOVisibility(RD);
bool ShouldEmitWPDInfo =
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index d9d535c22fc40..48abb91b12b26 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -873,6 +873,21 @@ static void handleDiagnoseIfAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
cast<NamedDecl>(D)));
}
+static void handleCFIUncheckedCalleeAttr(Sema &S, Decl *D,
+ const ParsedAttr &Attrs) {
+ if (hasDeclarator(D))
+ return;
+
+ if (!isa<ObjCMethodDecl>(D)) {
+ S.Diag(Attrs.getLoc(), diag::warn_attribute_wrong_decl_type)
+ << Attrs << Attrs.isRegularKeywordAttribute()
+ << ExpectedFunctionOrMethod;
+ return;
+ }
+
+ D->addAttr(::new (S.Context) CFIUncheckedCalleeAttr(S.Context, Attrs));
+}
+
static void handleNoBuiltinAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
static constexpr const StringRef kWildcard = "*";
@@ -7092,6 +7107,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_NoBuiltin:
handleNoBuiltinAttr(S, D, AL);
break;
+ case ParsedAttr::AT_CFIUncheckedCallee:
+ handleCFIUncheckedCalleeAttr(S, D, AL);
+ break;
case ParsedAttr::AT_ExtVectorType:
handleExtVectorTypeAttr(S, D, AL);
break;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index d72d97addfac2..6d4353aa56551 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4542,6 +4542,7 @@ static void captureVariablyModifiedType(ASTContext &Context, QualType T,
case Type::SubstTemplateTypeParm:
case Type::MacroQualified:
case Type::CountAttributed:
+ case Type::CFIUncheckedCallee:
// Keep walking after single level desugaring.
T = T.getSingleStepDesugaredType(Context);
break;
@@ -9866,6 +9867,22 @@ AssignConvertType Sema::CheckSingleAssignmentConstraints(QualType LHSType,
AssignConvertType result =
CheckAssignmentConstraints(LHSType, RHS, Kind, ConvertRHS);
+ if (const auto *IC = dyn_cast_or_null<ImplicitCastExpr>(RHS.get())) {
+ if (result != AssignConvertType::Incompatible &&
+ !IC->isPartOfExplicitCast()) {
+ bool DiscardingCFIuncheckedCallee =
+ IC->getType()->isPointerToCFIUncheckedCalleeType() &&
+ !LHSType->isPointerToCFIUncheckedCalleeType();
+ DiscardingCFIuncheckedCallee |=
+ IC->getType()->isCFIUncheckedCalleeType() &&
+ !LHSType->isCFIUncheckedCalleeType();
+ if (DiscardingCFIuncheckedCallee) {
+ Diag(IC->getExprLoc(), diag::warn_cast_discards_cfi_unchecked_callee)
+ << IC->getType() << LHSType;
+ }
+ }
+ }
+
// C99 6.5.16.1p2: The value of the right operand is converted to the
// type of the assignment expression.
// CheckAssignmentConstraints allows the left-hand side to be a reference,
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 235ea1529b0b8..4dcadeed4afab 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -4628,6 +4628,24 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
return ExprError();
}
+ if (CCK == CheckedConversionKind::Implicit) {
+ if (From->getType()->isPointerToCFIUncheckedCalleeType() &&
+ !ToType->isPointerToCFIUncheckedCalleeType()) {
+ // Note that if the ToType was the result of a template substitution for a
+ // template parameter, the resulting type will not by wrapped with
+ // CFIUncheckedCallee because only canonical types are passed as template
+ // params (see `Sema::FinishTemplateArgumentDeduction`) meaning the
+ // CFIUncheckedCallee wrapper would be stripped. We don't have a good way
+ // of propagating sugared types through templates, so for now just don't
+ // diagnose for this one specific case to avoid a false positive.
+ QualType Pointee = ToType->getPointeeType();
+ if (!Pointee.isNull() && !Pointee->getAs<SubstTemplateTypeParmType>()) {
+ Diag(From->getExprLoc(), diag::warn_cast_discards_cfi_unchecked_callee)
+ << From->getType() << ToType;
+ }
+ }
+ }
+
// Everything went well.
return From;
}
@@ -7932,10 +7950,22 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
EPI1.ExceptionSpec, EPI2.ExceptionSpec, ExceptionTypeStorage,
getLangOpts().CPlusPlus17);
+ // `cfi_unchecked_callee` needs to be preserved to prevent diagnosing on
+ // implicit casts when finding common types for binary operations. If
+ // one of the operands has the attribute, let's make the common type
+ // have it also.
+ bool HasCFIUncheckedCallee = Composite1->isCFIUncheckedCalleeType() ||
+ Composite2->isCFIUncheckedCalleeType();
+
Composite1 = Context.getFunctionType(FPT1->getReturnType(),
FPT1->getParamTypes(), EPI1);
Composite2 = Context.getFunctionType(FPT2->getReturnType(),
FPT2->getParamTypes(), EPI2);
+
+ if (HasCFIUncheckedCallee) {
+ Composite1 = Context.getCFIUncheckedCalleeType(Composite1);
+ Composite2 = Context.getCFIUncheckedCalleeType(Composite2);
+ }
}
}
}
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 160fbddbdff15..2bedf2c4eff71 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -8001,7 +8001,7 @@ ExprResult InitializationSequence::Perform(Sema &S,
break;
}
- case SK_BindReference:
+ case SK_BindReference: {
// Reference binding does not have any corresponding ASTs.
// Check exception specifications
@@ -8022,7 +8022,20 @@ ExprResult InitializationSequence::Perform(Sema &S,
}
CheckForNullPointerDereference(S, CurInit.get());
+
+ QualType InitTy = CurInit.get()->getType();
+ bool DiscardingCFIUnchecked =
+ (InitTy->isPointerToCFIUncheckedCalleeType() &&
+ !DestType->isPointerToCFIUncheckedCalleeType()) ||
+ (InitTy->isCFIUncheckedCalleeType() &&
+ !DestType->isCFIUncheckedCalleeType());
+ if (DiscardingCFIUnchecked) {
+ S.Diag(CurInit.get()->getExprLoc(),
+ diag::warn_cast_discards_cfi_unchecked_callee)
+ << InitTy << DestType;
+ }
break;
+ }
case SK_BindReferenceToTemporary: {
// Make sure the "temporary" is actually an rvalue.
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 390ff3ef02df5..32296d653d940 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3131,6 +3131,11 @@ namespace {
return Visit(T->getUnderlyingType());
}
+ TemplateTypeParmDecl *
+ VisitCFIUncheckedCalleeType(const CFIUncheckedCalleeType *T) {
+ return Visit(T->getWrappedType());
+ }
+
TemplateTypeParmDecl *VisitAdjustedType(const AdjustedType *T) {
return Visit(T->getOriginalType());
}
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 294daef70c339..dd4b563f6ef9e 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -155,6 +155,7 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
case ParsedAttr::AT_Blocking: \
case ParsedAttr::AT_Allocating: \
case ParsedAttr::AT_Regparm: \
+ case ParsedAttr::AT_CFIUncheckedCallee: \
case ParsedAttr::AT_CmseNSCall: \
case ParsedAttr::AT_ArmStreaming: \
case ParsedAttr::AT_ArmStreamingCompatible: \
@@ -5893,6 +5894,9 @@ namespace {
TL.setExpansionLoc(
State.getExpansionLocForMacroQualifiedType(TL.getTypePtr()));
}
+ void VisitCFIUncheckedCalleeTypeLoc(CFIUncheckedCalleeTypeLoc TL) {
+ Visit(TL.getWrappedLoc());
+ }
void VisitQualifiedTypeLoc(QualifiedTypeLoc TL) {
Visit(TL.getUnqualifiedLoc());
}
@@ -6204,6 +6208,9 @@ namespace {
void VisitMacroQualifiedTypeLoc(MacroQualifiedTypeLoc TL) {
TL.setExpansionLoc(Chunk.Loc);
}
+ void VisitCFIUncheckedCalleeTypeLoc(CFIUncheckedCalleeTypeLoc TL) {
+ TL.setAttrLoc(Chunk.Loc);
+ }
void VisitVectorTypeLoc(VectorTypeLoc TL) { TL.setNameLoc(Chunk.Loc); }
void VisitDependentVectorTypeLoc(DependentVectorTypeLoc TL) {
TL.setNameLoc(Chunk.Loc);
@@ -6299,6 +6306,10 @@ GetTypeSourceInfoForDeclarator(TypeProcessingState &State,
break;
}
+ case TypeLoc::CFIUncheckedCallee: {
+ CurrTL = CurrTL.getNextTypeLoc().getUnqualifiedLoc();
+ break;
+ }
case TypeLoc::Adjusted:
case TypeLoc::BTFTagAttributed: {
CurrTL = CurrTL.getNextTypeLoc().getUnqualifiedLoc();
@@ -6855,6 +6866,7 @@ namespace {
Reference,
MemberPointer,
MacroQualified,
+ CFIUncheckedCallee,
};
QualType Original;
@@ -6892,6 +6904,9 @@ namespace {
} else if (isa<MacroQualifiedType>(Ty)) {
T = cast<MacroQualifiedType>(Ty)->getUnderlyingType();
Stack.push_back(MacroQualified);
+ } else if (isa<CFIUncheckedCalleeType>(Ty)) {
+ T = cast<CFIUncheckedCalleeType>(Ty)->getWrappedType();
+ Stack.push_back(CFIUncheckedCallee);
} else {
const Type *DTy = Ty->getUnqualifiedDesugaredType();
if (Ty == DTy) {
@@ -6951,6 +6966,12 @@ namespace {
case MacroQualified:
return wrap(C, cast<MacroQualifiedType>(Old)->getUnderlyingType(), I);
+ case CFIUncheckedCallee: {
+ QualType New =
+ wrap(C, cast<CFIUncheckedCalleeType>(Old)->getWrappedType(), I);
+ return C.getCFIUncheckedCalleeType(New);
+ }
+
case Array: {
if (const auto *CAT = dyn_cast<ConstantArrayType>(Old)) {
QualType New = wrap(C, CAT->getElementType(), I);
@@ -7813,6 +7834,22 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
return true;
}
+ if (attr.getKind() == ParsedAttr::AT_CFIUncheckedCallee) {
+ // Delay if this is not a function type.
+ if (!unwrapped.isFunctionType()) {
+ return false;
+ }
+
+ if (!type->isFunctionType() && !type->isDependentType()) {
+ state.getSema().Diag(attr.getLoc(),
+ diag::warn_cfi_unchecked_callee_on_non_function)
+ << type;
+ }
+
+ type = S.Context.getCFIUncheckedCalleeType(type);
+ return true;
+ }
+
if (attr.getKind() == ParsedAttr::AT_CmseNSCall) {
// Delay if this is not a function type.
if (!unwrapped.isFunctionType())
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index aed00e0ff06cd..866c89cfb36db 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -1034,6 +1034,11 @@ class TreeTransform {
return SemaRef.Context.getMacroQualifiedType(T, MacroII);
}
+ /// Build a new CFIUncheckedCallee type.
+ QualType RebuildCFIUncheckedCalleeType(QualType T) {
+ return SemaRef.Context.getCFIUncheckedCalleeType(T);
+ }
+
/// Build a new class/struct/union type.
QualType RebuildRecordType(RecordDecl *Record) {
return SemaRef.Context.getTypeDeclType(Record);
@@ -7597,6 +7602,26 @@ QualType TreeTransform<Derived>::TransformAttributedType(TypeLocBuilder &TLB,
return result;
}
+template <typename Derived>
+QualType TreeTransform<Derived>::TransformCFIUncheckedCalleeType(
+ TypeLocBuilder &TLB, CFIUncheckedCalleeTypeLoc TL) {
+ const CFIUncheckedCalleeType *OldTy = TL.getTypePtr();
+ QualType InnerTy = getDerived().TransformType(TLB, TL.getWrappedLoc());
+ if (InnerTy.isNull())
+ return QualType();
+
+ QualType Result = TL.getType();
+ if (getDerived().AlwaysRebuild() || InnerTy != OldTy->desugar()) {
+ Result = getDerived().RebuildCFIUncheckedCalleeType(InnerTy);
+ if (Result.isNull())
+ return QualType();
+ }
+
+ CFIUncheckedCalleeTypeLoc NewTL = TLB.push<CFIUncheckedCalleeTypeLoc>(Result);
+ NewTL.setAttrLoc(TL.getAttrLoc());
+ return Result;
+}
+
template <typename Derived>
QualType TreeTransform<Derived>::TransformCountAttributedType(
TypeLocBuilder &TLB, CountAttributedTypeLoc TL) {
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index cda521808d1de..6fdbd0d52dec6 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -7349,6 +7349,11 @@ void TypeLocReader::VisitHLSLAttributedResourceTypeLoc(
// Nothing to do.
}
+void TypeLocReader::VisitCFIUncheckedCalleeTypeLoc(
+ CFIUncheckedCalleeTypeLoc TL) {
+ TL.setAttrLoc(readSourceLocation());
+}
+
void TypeLocReader::VisitTemplateTypeParmTypeLoc(TemplateTypeParmTypeLoc TL) {
TL.setNameLoc(readSourceLocation());
}
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 2772038bf3755..d10776f269b26 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -591,6 +591,11 @@ void TypeLocWriter::VisitAttributedTypeLoc(AttributedTypeLoc TL) {
Record.AddAttr(TL.getAttr());
}
+void TypeLocWriter::VisitCFIUncheckedCalleeTypeLoc(
+ CFIUncheckedCalleeTypeLoc TL) {
+ addSourceLocation(TL.getAttrLoc());
+}
+
void TypeLocWriter::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
// Nothing to do
}
diff --git a/clang/test/CodeGen/cfi-unchecked-callee-attribute-member-function.cpp b/clang/test/CodeGen/cfi-unchecked-callee-attribute-member-function.cpp
new file mode 100644
index 0000000000000..637b5201ad6b4
--- /dev/null
+++ b/clang/test/CodeGen/cfi-unchecked-callee-attribute-member-function.cpp
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=cfi-mfcall -o - %s -fvisibility=hidden | FileCheck %s
+
+#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee))
+
+class A {};
+
+// CHECK-LABEL: _Z14MemberFuncCallP1AMS_FvvE
+void MemberFuncCall(A *s, void (A::*p)()) {
+ // CHECK: memptr.virtual:
+ // CHECK-NEXT: [[VTABLE:%.*]] = load ptr, ptr {{.*}}, align 8
+ // CHECK-NEXT: [[OFFSET:%.*]] = sub i64 %memptr.ptr, 1
+ // CHECK-NEXT: [[FUNC:%.*]] = getelementptr i8, ptr [[VTABLE]], i64 [[OFFSET]]
+ // CHECK-NEXT: [[VALID:%.*]] = call i1 @llvm.type.test(ptr [[FUNC]], metadata !"_ZTSM1AFvvE.virtual")
+ // CHECK-NEXT: [[FUNC:%.*]] = getelementptr i8, ptr [[VTABLE]], i64 [[OFFSET]]
+ // CHECK-NEXT: %memptr.virtualfn = load ptr, ptr [[FUNC]], align 8
+ // CHECK-NEXT: {{.*}}= call i1 @llvm.type.test(ptr [[VTABLE]], metadata !"all-vtables")
+ // CHECK-NEXT: br i1 [[VALID]], label %[[CONT:.*]], label %handler.cfi_check_fail{{.*}}
+
+ // CHECK: [[CONT]]:
+ // CHECK-NEXT: br label %memptr.end
+
+ // CHECK: memptr.nonvirtual:
+ // CHECK-NEXT: %memptr.nonvirtualfn = inttoptr i64 %memptr.ptr to ptr
+ // CHECK-NEXT: [[VALID:%.*]] = call i1 @llvm.type.test(ptr %memptr.nonvirtualfn, metadata !"_ZTSM1AFvvE")
+ // CHECK-NEXT: [[VALID2:%.*]] = or i1 false, [[VALID]]
+ // CHECK-NEXT: br i1 [[VALID2]], label %[[CONT2:.*]], label %handler.cfi_check_fail{{.*}}
+
+ // CHECK: [[CONT2]]:
+ // CHECK-NEXT: br label %memptr.end
+
+ // CHECK: memptr.end:
+ // CHECK-NEXT: {{.*}} = phi ptr [ %memptr.virtualfn, %[[CONT]] ], [ %memptr.nonvirtualfn, %[[CONT2]] ]
+ (s->*p)();
+}
+
+// CHECK-LABEL: _Z19MemberFuncCallNoCFIP1AMS_FvvE
+// CHECK-NOT: llvm.type.test
+void MemberFuncCallNoCFI(A *s, void (CFI_UNCHECKED_CALLEE A::*p)()) {
+ // CHECK: memptr.virtual:
+ // CHECK-NEXT: [[VTABLE:%.*]] = load ptr, ptr {{.*}}, align 8
+ // CHECK-NEXT: [[OFFSET:%.*]] = sub i64 %memptr.ptr, 1
+ // CHECK-NEXT: [[FUNC:%.*]] = getelementptr i8, ptr [[VTABLE]], i64 [[OFFSET]]
+ // CHECK-NEXT: %memptr.virtualfn = load ptr, ptr [[FUNC]], align 8
+ // CHECK-NEXT: br label %memptr.end
+
+ // CHECK: memptr.nonvirtual:
+ // CHECK-NEXT: %memptr.nonvirtualfn = inttoptr i64 %memptr.ptr to ptr
+ // CHECK-NEXT: br label %memptr.end
+
+ // CHECK: memptr.end:
+ // CHECK-NEXT: {{.*}} = phi ptr [ %memptr.virtualfn, %memptr.virtual ], [ %memptr.nonvirtualfn, %memptr.nonvirtual ]
+ (s->*p)();
+}
diff --git a/clang/test/CodeGen/cfi-unchecked-callee-attribute.cpp b/clang/test/CodeGen/cfi-unchecked-callee-attribute.cpp
new file mode 100644
index 0000000000000..feb7c9c30a219
--- /dev/null
+++ b/clang/test/CodeGen/cfi-unchecked-callee-attribute.cpp
@@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=cfi-icall -o - %s | FileCheck %s
+
+#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee))
+
+void unchecked(void) CFI_UNCHECKED_CALLEE {}
+
+/// All references to unchecked function with `cfi_unchecked_callee` should have the `cfi_unchecked_callee` wrapper.
+// CHECK: @checked = global ptr no_cfi @_Z9uncheckedv
+void (*checked)(void) = unchecked;
+
+// CHECK: @unchecked2 = global ptr no_cfi @_Z9uncheckedv
+void (CFI_UNCHECKED_CALLEE *unchecked2)(void) = unchecked;
+
+// CHECK: @checked2 = global ptr no_cfi @_Z9uncheckedv
+constexpr void (CFI_UNCHECKED_CALLEE *unchecked_constexpr)(void) = unchecked;
+void (*checked2)(void) = unchecked_constexpr;
+
+/// Note we still reference the `no_cfi` function rather than the jump table entry.
+/// The explicit cast will only silence the warning.
+// CHECK: @checked_explicit_cast = global ptr no_cfi @_Z9uncheckedv
+void (*checked_explicit_cast)(void) = (void (*)(void))unchecked;
+
+// CHECK: @checked_array = global [3 x ptr] [ptr no_cfi @_Z9uncheckedv, ptr no_cfi @_Z9uncheckedv, ptr no_cfi @_Z9uncheckedv]
+void (*checked_array[])(void) = {
+ unchecked,
+ (void (*)(void))unchecked,
+ reinterpret_cast<void (*)(void)>(unchecked),
+};
+
+void func_accepting_checked(void (*p)(void)) {}
+
+// CHECK-LABEL: _Z9InvokeCFIv
+void InvokeCFI() {
+ // CHECK: %0 = load ptr, ptr @checked, align 8
+ // CHECK: %1 = call i1 @llvm.type.test(ptr %0, metadata !"_ZTSFvvE")
+ checked();
+}
+
+// CHECK-LABEL: _Z11InvokeNoCFIv
+void InvokeNoCFI() {
+ // CHECK: %0 = load ptr, ptr @unchecked2, align 8
+ // CHECK: call void %0()
+ unchecked2();
+}
+
+struct A {
+ void unchecked_method() CFI_UNCHECKED_CALLEE {}
+ virtual void unchecked_virtual_method() CFI_UNCHECKED_CALLEE {}
+ static void unchecked_static_method() CFI_UNCHECKED_CALLEE {}
+ int unchecked_const_method() const CFI_UNCHECKED_CALLEE { return 0; }
+ int unchecked_const_method_int_arg(int n) const CFI_UNCHECKED_CALLEE { return 0; }
+};
+
+void h(void) {
+ // CHECK: store ptr no_cfi @_Z9uncheckedv, ptr %unchecked_local
+ void (*unchecked_local)(void) = unchecked;
+
+ // CHECK: call void @_Z22func_accepting_checkedPFvvE(ptr noundef no_cfi @_Z9uncheckedv)
+ func_accepting_checked(unchecked);
+
+ // CHECK: [[B:%.*]] = load ptr, ptr @checked
+ // CHECK-NEXT: call void @_Z22func_accepting_checkedPFvvE(ptr noundef [[B]])
+ func_accepting_checked(checked);
+
+ // CHECK: store { i64, i64 } { i64 ptrtoint (ptr no_cfi @_ZN1A16unchecked_methodEv to i64), i64 0 }, ptr %A1
+ auto A1 = &A::unchecked_method;
+ /// Storing unchecked virtual function pointer stores an offset instead. This is part of the
+ /// normal Itanium C++ ABI, but let's make sure we don't change anything.
+ // CHECK: store { i64, i64 } { i64 1, i64 0 }, ptr %A2
+ auto A2 = &A::unchecked_virtual_method;
+ // CHECK: store ptr no_cfi @_ZN1A23unchecked_static_methodEv, ptr %A3
+ auto A3 = &A::unchecked_static_method;
+ // CHECK: store { i64, i64 } { i64 ptrtoint (ptr no_cfi @_ZNK1A22unchecked_const_methodEv to i64), i64 0 }, ptr %A4
+ auto A4 = (int(CFI_UNCHECKED_CALLEE A::*)() const)(&A::unchecked_const_method);
+ // CHECK: store { i64, i64 } { i64 ptrtoint (ptr no_cfi @_ZNK1A30unchecked_const_method_int_argEi to i64), i64 0 }, ptr %A5
+ auto A5 = (int(CFI_UNCHECKED_CALLEE A::*)(int) const)(&A::unchecked_const_method_int_arg);
+}
diff --git a/clang/test/Frontend/cfi-unchecked-callee-attribute.c b/clang/test/Frontend/cfi-unchecked-callee-attribute.c
new file mode 100644
index 0000000000000..a982715123a34
--- /dev/null
+++ b/clang/test/Frontend/cfi-unchecked-callee-attribute.c
@@ -0,0 +1,70 @@
+// RUN: %clang_cc1 -Wall -Wno-unused -Wno-uninitialized -verify %s
+
+#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee))
+
+void unchecked() CFI_UNCHECKED_CALLEE {}
+void checked() {}
+
+void (*checked_ptr)() = unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards `cfi_unchecked_callee` attribute}}
+void (CFI_UNCHECKED_CALLEE *unchecked_ptr)() = unchecked;
+void (CFI_UNCHECKED_CALLEE *from_normal)() = checked;
+void (CFI_UNCHECKED_CALLEE *c_no_function_decay)() = &unchecked;
+
+typedef void (CFI_UNCHECKED_CALLEE unchecked_func_t)();
+typedef void (checked_func_t)();
+typedef void (CFI_UNCHECKED_CALLEE *cfi_unchecked_func_ptr_t)();
+typedef void (*checked_func_ptr_t)();
+checked_func_t *cfi_func = unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'checked_func_t *' (aka 'void (*)()') discards `cfi_unchecked_callee` attribute}}
+unchecked_func_t *unchecked_func = unchecked;
+
+void CFI_UNCHECKED_CALLEE after_ret_type();
+CFI_UNCHECKED_CALLEE void before_ret_type();
+
+void UsageOnImproperTypes() {
+ int CFI_UNCHECKED_CALLEE i; // expected-warning{{'cfi_unchecked_callee' only applies to function types; type here is 'int'}}
+
+ /// Here `cfi_unchecked_callee` is applied to the pointer here rather than the actual function.
+ void (* CFI_UNCHECKED_CALLEE func_ptr)(); // expected-warning{{use of `cfi_unchecked_callee` on 'void (*)()'; can only be used on function types}}
+}
+
+/// Explicit casts suppress the warning.
+void CheckCasts() {
+ void (*should_warn)() = unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards `cfi_unchecked_callee` attribute}}
+
+ void (*no_warn_c_style_cast)() = (void (*)())unchecked;
+
+ struct B {} CFI_UNCHECKED_CALLEE b; // expected-warning{{'cfi_unchecked_callee' attribute only applies to functions and methods}}
+ struct CFI_UNCHECKED_CALLEE C {} c; // expected-warning{{'cfi_unchecked_callee' attribute only applies to functions and methods}}
+ CFI_UNCHECKED_CALLEE struct D {} d; // expected-warning{{'cfi_unchecked_callee' only applies to function types; type here is 'struct D'}}
+
+ void *ptr2 = (void *)unchecked;
+}
+
+int checked_arg_func(checked_func_t *checked_func);
+
+void CheckDifferentConstructions() {
+ void (CFI_UNCHECKED_CALLEE *arr[10])();
+ void (*cfi_elem)() = arr[1]; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards `cfi_unchecked_callee` attribute}}
+ void (CFI_UNCHECKED_CALLEE *cfi_unchecked_elem)() = arr[1];
+
+ int invoke = checked_arg_func(unchecked); // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'checked_func_t *' (aka 'void (*)()') discards `cfi_unchecked_callee` attribute}}
+}
+
+checked_func_t *returning_checked_func() {
+ return unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'checked_func_t *' (aka 'void (*)()') discards `cfi_unchecked_callee` attribute}}
+}
+
+void no_args() __attribute__((cfi_unchecked_callee(10))); // expected-error{{'cfi_unchecked_callee' attribute takes no arguments}}
+
+void Comparisons() {
+ /// Let's be able to compare checked and unchecked pointers without warnings.
+ unchecked == checked_ptr;
+ checked_ptr == unchecked;
+ unchecked == unchecked_ptr;
+ unchecked != checked_ptr;
+ checked_ptr != unchecked;
+ unchecked != unchecked_ptr;
+
+ (void (*)())unchecked == checked_ptr;
+ checked_ptr == (void (*)())unchecked;
+}
diff --git a/clang/test/Frontend/cfi-unchecked-callee-attribute.cpp b/clang/test/Frontend/cfi-unchecked-callee-attribute.cpp
new file mode 100644
index 0000000000000..048f579fce36f
--- /dev/null
+++ b/clang/test/Frontend/cfi-unchecked-callee-attribute.cpp
@@ -0,0 +1,146 @@
+// RUN: %clang_cc1 -Wall -Wno-unused -Wno-uninitialized -verify %s
+
+#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee))
+
+void unchecked(void) CFI_UNCHECKED_CALLEE {}
+void checked(void) {}
+
+void (*checked_ptr)(void) = unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards `cfi_unchecked_callee` attribute}}
+void (CFI_UNCHECKED_CALLEE *unchecked_ptr)(void) = unchecked;
+void (CFI_UNCHECKED_CALLEE *from_normal)(void) = checked;
+void (CFI_UNCHECKED_CALLEE *c_no_function_decay)(void) = &unchecked;
+void (CFI_UNCHECKED_CALLEE *arr[10])(void);
+void (*cfi_elem)(void) = arr[1]; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards `cfi_unchecked_callee` attribute}}
+void (CFI_UNCHECKED_CALLEE *cfi_unchecked_elem)(void) = arr[1];
+void (CFI_UNCHECKED_CALLEE &ref)(void) = unchecked;
+void (CFI_UNCHECKED_CALLEE &ref2)(void) = *unchecked;
+void (&ref_cfi_checked)(void) = unchecked; // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void ()' discards `cfi_unchecked_callee` attribute}}
+void (&ref_cfi_checked2)(void) = *unchecked; // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void ()' discards `cfi_unchecked_callee` attribute}}
+
+void (CFI_UNCHECKED_CALLEE *unchecked_from_deref)(void) = &*unchecked;
+void (*checked_from_deref)(void) = &*unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards `cfi_unchecked_callee` attribute}}
+
+typedef void (CFI_UNCHECKED_CALLEE unchecked_func_t)(void);
+typedef void (checked_func_t)(void);
+typedef void (CFI_UNCHECKED_CALLEE *unchecked_func_ptr_t)(void);
+typedef void (*checked_func_ptr_t)(void);
+checked_func_t *checked_func = unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'checked_func_t *' (aka 'void (*)()') discards `cfi_unchecked_callee` attribute}}
+unchecked_func_t *unchecked_func = unchecked;
+
+void CFI_UNCHECKED_CALLEE before_func(void);
+CFI_UNCHECKED_CALLEE void before_return_type(void);
+
+void UsageOnImproperTypes() {
+ int CFI_UNCHECKED_CALLEE i; // expected-warning{{'cfi_unchecked_callee' only applies to function types; type here is 'int'}}
+
+ /// Here `cfi_unchecked_callee` is applied to the pointer here rather than the actual function.
+ void (* CFI_UNCHECKED_CALLEE func)(void); // expected-warning{{use of `cfi_unchecked_callee` on 'void (*)()'; can only be used on function types}}
+}
+
+/// Explicit casts suppress the warning.
+void CheckCasts() {
+ void (*should_warn)(void) = unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards `cfi_unchecked_callee` attribute}}
+
+ void (*no_warn_c_style_cast)(void) = (void (*)(void))unchecked;
+ void (*no_warn_static_cast)(void) = static_cast<void (*)(void)>(unchecked);
+ void (*no_warn_reinterpret_cast)(void) = reinterpret_cast<void (*)(void)>(unchecked);
+
+ struct A {};
+ void (CFI_UNCHECKED_CALLEE A::*cfi_unchecked_member_ptr)(void);
+ void (A::*member_ptr)(void) = cfi_unchecked_member_ptr; // expected-warning{{implicit conversion from 'void (A::*)() __attribute__((cfi_unchecked_callee))' to 'void (A::*)()' discards `cfi_unchecked_callee` attribute}}
+
+ struct B {} CFI_UNCHECKED_CALLEE b; // expected-warning{{'cfi_unchecked_callee' attribute only applies to functions and methods}}
+ struct CFI_UNCHECKED_CALLEE C {} c; // expected-warning{{'cfi_unchecked_callee' attribute only applies to functions and methods}}
+ CFI_UNCHECKED_CALLEE struct D {} d; // expected-warning{{'cfi_unchecked_callee' only applies to function types; type here is 'struct D'}}
+
+ void *ptr2 = (void *)unchecked;
+}
+
+void CheckDifferentConstructions() {
+ checked_func_t *checked_func(unchecked_func); // expected-warning{{implicit conversion from 'unchecked_func_t *' (aka 'void (*)()') to 'checked_func_t *' (aka 'void (*)()') discards `cfi_unchecked_callee` attribute}}
+ new (checked_func_t *)(unchecked_func); // expected-warning{{implicit conversion from 'unchecked_func_t *' (aka 'void (*)()') to 'checked_func_t *' (aka 'void (*)()') discards `cfi_unchecked_callee` attribute}}
+ struct S {
+ checked_func_t *checked_func;
+
+ // expected-warning at +1{{implicit conversion from 'unchecked_func_t *' (aka 'void (*)()') to 'checked_func_t *' (aka 'void (*)()') discards `cfi_unchecked_callee` attribute}}
+ S(unchecked_func_t *unchecked_func) : checked_func(unchecked_func) {}
+ };
+
+ checked_func_t *checked_func2{unchecked_func}; // expected-warning{{implicit conversion from 'unchecked_func_t *' (aka 'void (*)()') to 'checked_func_t *' (aka 'void (*)()') discards `cfi_unchecked_callee` attribute}}
+ checked_ptr = checked_func_ptr_t(unchecked);
+}
+
+checked_func_t *returning_checked_func() {
+ return unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'checked_func_t *' (aka 'void (*)()') discards `cfi_unchecked_callee` attribute}}
+}
+
+int checked_arg_func(checked_func_t *checked_func);
+int invoke = checked_arg_func(unchecked); // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'checked_func_t *' (aka 'void (*)()') discards `cfi_unchecked_callee` attribute}}
+
+/// Note that function attributes aren't propagated through templates, so T
+/// will always be the canonical (non-attributed) type.
+template <typename T>
+struct S {
+ S(T *ptr) {}
+};
+S<unchecked_func_t> s(checked);
+S<unchecked_func_t> s2(unchecked);
+S<checked_func_t> s3(checked);
+S<checked_func_t> s4(unchecked);
+
+void no_args() __attribute__((cfi_unchecked_callee(10))); // expected-error{{'cfi_unchecked_callee' attribute takes no arguments}}
+
+void bracket_cfi_unchecked(void) [[clang::cfi_unchecked_callee]] {}
+
+void BracketNotation() {
+ checked_ptr = bracket_cfi_unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards `cfi_unchecked_callee` attribute}}
+}
+
+void Comparisons() {
+ /// Let's be able to compare checked and unchecked pointers without warnings.
+ unchecked == checked_ptr;
+ checked_ptr == unchecked;
+ unchecked == unchecked_ptr;
+ unchecked != checked_ptr;
+ checked_ptr != unchecked;
+ unchecked != unchecked_ptr;
+
+ (void (*)(void))unchecked == checked_ptr;
+ checked_ptr == (void (*)(void))unchecked;
+
+ struct S {
+ typedef void CB() CFI_UNCHECKED_CALLEE;
+ constexpr bool operator==(const S &other) const {
+ return cb == other.cb;
+ }
+ CB *cb;
+ };
+}
+
+void MemberFunctionPointer() {
+ struct A {
+ void unchecked() CFI_UNCHECKED_CALLEE {}
+ virtual void unchecked_virtual() CFI_UNCHECKED_CALLEE {}
+ static void unchecked_static() CFI_UNCHECKED_CALLEE {}
+
+ void checked() {}
+ virtual void checked_virtual() {}
+ static void checked_static() {}
+ };
+
+ void (CFI_UNCHECKED_CALLEE A::*unchecked_func)() = &A::unchecked;
+ unchecked_func = &A::unchecked_virtual;
+ unchecked_ptr = &A::unchecked_static;
+
+ void (A::*checked_func)() = &A::unchecked; // expected-warning{{implicit conversion from 'void (A::*)() __attribute__((cfi_unchecked_callee))' to 'void (A::*)()' discards `cfi_unchecked_callee` attribute}}
+ checked_func = &A::unchecked_virtual; // expected-warning{{implicit conversion from 'void (A::*)() __attribute__((cfi_unchecked_callee))' to 'void (A::*)()' discards `cfi_unchecked_callee` attribute}}
+ checked_ptr = &A::unchecked_static; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards `cfi_unchecked_callee` attribute}}
+
+ unchecked_func = &A::checked;
+ unchecked_func = &A::checked_virtual;
+ unchecked_ptr = &A::checked_static;
+
+ checked_func = &A::checked;
+ checked_func = &A::checked_virtual;
+ checked_ptr = &A::checked_static;
+}
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 809000a5086b1..c9f65f9006eb2 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -1749,6 +1749,11 @@ bool CursorVisitor::VisitMacroQualifiedTypeLoc(MacroQualifiedTypeLoc TL) {
return Visit(TL.getInnerLoc());
}
+bool CursorVisitor::VisitCFIUncheckedCalleeTypeLoc(
+ CFIUncheckedCalleeTypeLoc TL) {
+ return Visit(TL.getWrappedLoc());
+}
+
bool CursorVisitor::VisitPointerTypeLoc(PointerTypeLoc TL) {
return Visit(TL.getPointeeLoc());
}
More information about the cfe-commits
mailing list