https://github.com/PiJoules updated 
https://github.com/llvm/llvm-project/pull/135836

>From 010aede1b6f703ddd465de8cfe575697bd2a5e5b Mon Sep 17 00:00:00 2001
From: Leonard Chan <leonardc...@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/Type.h                |  39 ++-
 clang/include/clang/AST/TypeProperties.td     |   4 +
 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        |   5 +
 clang/include/clang/Sema/Sema.h               |  22 +-
 clang/lib/AST/Type.cpp                        |   8 +
 clang/lib/AST/TypePrinter.cpp                 |   6 +
 clang/lib/CodeGen/CGExpr.cpp                  |  12 +-
 clang/lib/CodeGen/CGExprConstant.cpp          |   8 +-
 clang/lib/CodeGen/CGPointerAuth.cpp           |   5 +
 clang/lib/CodeGen/ItaniumCXXABI.cpp           |  11 +
 clang/lib/Sema/SemaChecking.cpp               |  45 ++++
 clang/lib/Sema/SemaDeclAttr.cpp               |  18 ++
 clang/lib/Sema/SemaExpr.cpp                   |  20 +-
 clang/lib/Sema/SemaExprCXX.cpp                |   5 +
 clang/lib/Sema/SemaOverload.cpp               |  49 +++-
 clang/lib/Sema/SemaType.cpp                   |  22 ++
 ...ecked-callee-attribute-member-function.cpp |  53 ++++
 .../cfi-unchecked-callee-attribute.cpp        |  77 ++++++
 .../cfi-unchecked-callee-attribute-n3037.c    |  31 +++
 .../Frontend/cfi-unchecked-callee-attribute.c |  71 ++++++
 .../cfi-unchecked-callee-attribute.cpp        | 235 ++++++++++++++++++
 24 files changed, 781 insertions(+), 20 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-n3037.c
 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/Type.h b/clang/include/clang/AST/Type.h
index 5c8c0e1cf1d00..fb561ee60bb83 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -1985,6 +1985,10 @@ class alignas(TypeAlignment) Type : public 
ExtQualsTypeCommonBase {
     LLVM_PREFERRED_TYPE(bool)
     unsigned HasTrailingReturn : 1;
 
+    /// Whether this function has is a cfi unchecked callee.
+    LLVM_PREFERRED_TYPE(bool)
+    unsigned CFIUncheckedCallee : 1;
+
     /// Extra information which affects how the function is called, like
     /// regparm and the calling convention.
     LLVM_PREFERRED_TYPE(CallingConv)
@@ -2566,6 +2570,8 @@ class alignas(TypeAlignment) Type : public 
ExtQualsTypeCommonBase {
   bool isSignableIntegerType(const ASTContext &Ctx) const;
   bool isAnyPointerType() const;   // Any C pointer or ObjC object pointer
   bool isCountAttributedType() const;
+  bool isCFIUncheckedCalleeFunctionType() const;
+  bool isPointerToCFIUncheckedCalleeFunctionType() const;
   bool isBlockPointerType() const;
   bool isVoidPointerType() const;
   bool isReferenceType() const;
@@ -4712,6 +4718,10 @@ class FunctionType : public Type {
   /// type.
   bool getNoReturnAttr() const { return getExtInfo().getNoReturn(); }
 
+  /// Determine whether this is a function prototype that includes the
+  /// cfi_unchecked_callee attribute.
+  bool getCFIUncheckedCalleeAttr() const;
+
   bool getCmseNSCallAttr() const { return getExtInfo().getCmseNSCall(); }
   CallingConv getCallConv() const { return getExtInfo().getCC(); }
   ExtInfo getExtInfo() const { return ExtInfo(FunctionTypeBits.ExtInfo); }
@@ -5250,6 +5260,7 @@ class FunctionProtoType final
     FunctionType::ExtInfo ExtInfo;
     unsigned Variadic : 1;
     unsigned HasTrailingReturn : 1;
+    unsigned CFIUncheckedCallee : 1;
     unsigned AArch64SMEAttributes : 9;
     Qualifiers TypeQuals;
     RefQualifierKind RefQualifier = RQ_None;
@@ -5259,12 +5270,12 @@ class FunctionProtoType final
     FunctionEffectsRef FunctionEffects;
 
     ExtProtoInfo()
-        : Variadic(false), HasTrailingReturn(false),
+        : Variadic(false), HasTrailingReturn(false), CFIUncheckedCallee(false),
           AArch64SMEAttributes(SME_NormalFunction) {}
 
     ExtProtoInfo(CallingConv CC)
         : ExtInfo(CC), Variadic(false), HasTrailingReturn(false),
-          AArch64SMEAttributes(SME_NormalFunction) {}
+          CFIUncheckedCallee(false), AArch64SMEAttributes(SME_NormalFunction) 
{}
 
     ExtProtoInfo withExceptionSpec(const ExceptionSpecInfo &ESI) {
       ExtProtoInfo Result(*this);
@@ -5272,6 +5283,12 @@ class FunctionProtoType final
       return Result;
     }
 
+    ExtProtoInfo withCFIUncheckedCallee(bool CFIUncheckedCallee) {
+      ExtProtoInfo Result(*this);
+      Result.CFIUncheckedCallee = CFIUncheckedCallee;
+      return Result;
+    }
+
     bool requiresFunctionProtoTypeExtraBitfields() const {
       return ExceptionSpec.Type == EST_Dynamic ||
              requiresFunctionProtoTypeArmAttributes() ||
@@ -5431,6 +5448,7 @@ class FunctionProtoType final
     EPI.Variadic = isVariadic();
     EPI.EllipsisLoc = getEllipsisLoc();
     EPI.HasTrailingReturn = hasTrailingReturn();
+    EPI.CFIUncheckedCallee = hasCFIUncheckedCallee();
     EPI.ExceptionSpec = getExceptionSpecInfo();
     EPI.TypeQuals = getMethodQuals();
     EPI.RefQualifier = getRefQualifier();
@@ -5556,6 +5574,10 @@ class FunctionProtoType final
   /// Whether this function prototype has a trailing return type.
   bool hasTrailingReturn() const { return FunctionTypeBits.HasTrailingReturn; }
 
+  bool hasCFIUncheckedCallee() const {
+    return FunctionTypeBits.CFIUncheckedCallee;
+  }
+
   Qualifiers getMethodQuals() const {
     if (hasExtQualifiers())
       return *getTrailingObjects<Qualifiers>();
@@ -8253,6 +8275,19 @@ inline bool Type::isObjectPointerType() const {
     return false;
 }
 
+inline bool Type::isCFIUncheckedCalleeFunctionType() const {
+  if (const auto *Fn = getAs<FunctionProtoType>())
+    return Fn->hasCFIUncheckedCallee();
+  return false;
+}
+
+inline bool Type::isPointerToCFIUncheckedCalleeFunctionType() const {
+  QualType Pointee = getPointeeType();
+  if (Pointee.isNull())
+    return false;
+  return Pointee->isCFIUncheckedCalleeFunctionType();
+}
+
 inline bool Type::isFunctionPointerType() const {
   if (const auto *T = getAs<PointerType>())
     return T->getPointeeType()->isFunctionType();
diff --git a/clang/include/clang/AST/TypeProperties.td 
b/clang/include/clang/AST/TypeProperties.td
index f4b8ce0994ba8..aa6cca4842896 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -317,6 +317,9 @@ let Class = FunctionProtoType in {
   def : Property<"trailingReturn", Bool> {
     let Read = [{ node->hasTrailingReturn() }];
   }
+  def : Property<"cfiUncheckedCallee", Bool> {
+    let Read = [{ node->hasCFIUncheckedCallee() }];
+  }
   def : Property<"methodQualifiers", Qualifiers> {
     let Read = [{ node->getMethodQuals() }];
   }
@@ -353,6 +356,7 @@ let Class = FunctionProtoType in {
     epi.ExtInfo = extInfo;
     epi.Variadic = variadic;
     epi.HasTrailingReturn = trailingReturn;
+    epi.CFIUncheckedCallee = cfiUncheckedCallee;
     epi.TypeQuals = methodQualifiers;
     epi.RefQualifier = refQualifier;
     epi.ExceptionSpec = exceptionSpecifier;
diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index a6a7482a94a29..dd3e64c19f8e3 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3098,6 +3098,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 65d66dd398ad1..a5898b743a7ea 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 616f2555931f5..248fa3c6a2a01 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1625,6 +1625,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 d78a757c72e4a..467fdf62b13f0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12681,6 +12681,11 @@ 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_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/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5ec67087aeea4..17d9c80d03ef5 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2645,6 +2645,16 @@ class Sema final : public SemaBase {
   /// void*).
   void DiscardMisalignedMemberAddress(const Type *T, Expr *E);
 
+  /// Returns true if `From` is a function or pointer to a function with the
+  /// `cfi_unchecked_callee` attribute but `To` is a function or pointer to
+  /// function without this attribute.
+  bool DiscardingCFIUncheckedCallee(QualType From, QualType To) const;
+
+  /// Returns true if `From` is a function or pointer to a function without the
+  /// `cfi_unchecked_callee` attribute but `To` is a function or pointer to
+  /// function with this attribute.
+  bool AddingCFIUncheckedCallee(QualType From, QualType To) const;
+
   /// This function calls Action when it determines that E designates a
   /// misaligned member due to the packed attribute. This is used to emit
   /// local diagnostics like in reference binding.
@@ -10238,9 +10248,15 @@ class Sema final : public SemaBase {
                                  bool CStyle, bool &ObjCLifetimeConversion);
 
   /// Determine whether the conversion from FromType to ToType is a valid
-  /// conversion that strips "noexcept" or "noreturn" off the nested function
-  /// type.
-  bool IsFunctionConversion(QualType FromType, QualType ToType) const;
+  /// conversion that strips "noexcept" or "noreturn" or "cfi_unchecked_callee"
+  /// off the nested function type. This also checks if "cfi_unchecked_callee"
+  /// was added to the function type. If "cfi_unchecked_callee" is added and
+  /// `AddingCFIUncheckedCallee` is provided, it will be set to true. The same
+  /// thing applies for `DiscardingCFIUncheckedCallee` if the attribute is
+  /// discarded.
+  bool IsFunctionConversion(QualType FromType, QualType ToType,
+                            bool *DiscardingCFIUncheckedCallee = nullptr,
+                            bool *AddingCFIUncheckedCallee = nullptr) const;
 
   /// Same as `IsFunctionConversion`, but if this would return true, it sets
   /// `ResultTy` to `ToType`.
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 5d7a4bef8d995..9e02ff4f10fa8 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3584,6 +3584,12 @@ QualType QualType::getNonLValueExprType(const ASTContext 
&Context) const {
   return *this;
 }
 
+bool FunctionType::getCFIUncheckedCalleeAttr() const {
+  if (const auto *FPT = getAs<FunctionProtoType>())
+    return FPT->hasCFIUncheckedCallee();
+  return false;
+}
+
 StringRef FunctionType::getNameForCallConv(CallingConv CC) {
   switch (CC) {
   case CC_C:
@@ -3675,6 +3681,7 @@ FunctionProtoType::FunctionProtoType(QualType result, 
ArrayRef<QualType> params,
   FunctionTypeBits.HasExtParameterInfos = !!epi.ExtParameterInfos;
   FunctionTypeBits.Variadic = epi.Variadic;
   FunctionTypeBits.HasTrailingReturn = epi.HasTrailingReturn;
+  FunctionTypeBits.CFIUncheckedCallee = epi.CFIUncheckedCallee;
 
   if (epi.requiresFunctionProtoTypeExtraBitfields()) {
     FunctionTypeBits.HasExtraBitfields = true;
@@ -3942,6 +3949,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID 
&ID, QualType Result,
 
   ID.AddInteger((EffectCount << 3) | (HasConds << 2) |
                 (epi.AArch64SMEAttributes << 1) | epi.HasTrailingReturn);
+  ID.AddInteger(epi.CFIUncheckedCallee);
 
   for (unsigned Idx = 0; Idx != EffectCount; ++Idx) {
     ID.AddInteger(epi.FunctionEffects.Effects[Idx].toOpaqueInt32());
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index cba1a2d98d660..fb95266f5297a 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1049,6 +1049,9 @@ void TypePrinter::printFunctionProtoAfter(const 
FunctionProtoType *T,
     OS << "))";
   }
 
+  if (T->hasCFIUncheckedCallee())
+    OS << " __attribute__((cfi_unchecked_callee))";
+
   if (T->hasTrailingReturn()) {
     OS << " -> ";
     print(T->getReturnType(), OS, StringRef());
@@ -2089,6 +2092,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;
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 708002a7f9ab3..04dd1dd312128 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3027,9 +3027,13 @@ static LValue EmitFunctionDeclLValue(CodeGenFunction 
&CGF, const Expr *E,
                                      GlobalDecl GD) {
   const FunctionDecl *FD = cast<FunctionDecl>(GD.getDecl());
   llvm::Constant *V = CGF.CGM.getFunctionPointer(GD);
+  auto ETy = E->getType();
+  if (ETy->isCFIUncheckedCalleeFunctionType()) {
+    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);
+  return CGF.MakeAddrLValue(V, ETy, Alignment, AlignmentSource::Decl);
 }
 
 static LValue EmitCapturedFieldLValue(CodeGenFunction &CGF, const FieldDecl 
*FD,
@@ -6314,10 +6318,12 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
       FD && FD->hasAttr<OpenCLKernelAttr>())
     CGM.getTargetCodeGenInfo().setOCLKernelStubCallingConvention(FnType);
 
+  bool CFIUnchecked = CalleeType->isPointerToCFIUncheckedCalleeFunctionType();
+
   // 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);
     ApplyDebugLocation ApplyTrapDI(
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp 
b/clang/lib/CodeGen/CGExprConstant.cpp
index 6ba45f42db4d1..069cc09cd91d3 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()->isCFIUncheckedCalleeFunctionType())
+        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 bf22912988d38..30182b2de9aab 100644
--- a/clang/lib/CodeGen/CGPointerAuth.cpp
+++ b/clang/lib/CodeGen/CGPointerAuth.cpp
@@ -520,6 +520,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->isPointerToCFIUncheckedCalleeFunctionType())
+      Pointer = llvm::NoCFIValue::get(cast<llvm::GlobalValue>(Pointer));
+  }
+
   return Pointer;
 }
 
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp 
b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index faa07024a6052..7074fc0e05d4f 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -693,6 +693,17 @@ 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() &&
+          BinOp->getRHS()
+              ->getType()
+              ->isPointerToCFIUncheckedCalleeFunctionType())
+        ShouldEmitCFICheck = false;
+    }
+  }
+
   bool ShouldEmitVFEInfo = CGM.getCodeGenOpts().VirtualFunctionElimination &&
                            CGM.HasHiddenLTOVisibility(RD);
   bool ShouldEmitWPDInfo =
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index a960b9931ddfd..a385dabf5ff61 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -11910,6 +11910,46 @@ static void 
DiagnoseMixedUnicodeImplicitConversion(Sema &S, const Type *Source,
   }
 }
 
+enum CFIUncheckedCalleeChange {
+  None,
+  Adding,
+  Discarding,
+};
+
+static CFIUncheckedCalleeChange AdjustingCFIUncheckedCallee(QualType From,
+                                                            QualType To) {
+  QualType MaybePointee = From->getPointeeType();
+  if (!MaybePointee.isNull() && MaybePointee->getAs<FunctionType>())
+    From = MaybePointee;
+  MaybePointee = To->getPointeeType();
+  if (!MaybePointee.isNull() && MaybePointee->getAs<FunctionType>())
+    To = MaybePointee;
+
+  if (const auto *FromFn = From->getAs<FunctionType>()) {
+    if (const auto *ToFn = To->getAs<FunctionType>()) {
+      if (FromFn->getCFIUncheckedCalleeAttr() &&
+          !ToFn->getCFIUncheckedCalleeAttr())
+        return Discarding;
+      if (!FromFn->getCFIUncheckedCalleeAttr() &&
+          ToFn->getCFIUncheckedCalleeAttr())
+        return Adding;
+    }
+  }
+  return None;
+}
+
+bool Sema::DiscardingCFIUncheckedCallee(QualType From, QualType To) const {
+  From = Context.getCanonicalType(From);
+  To = Context.getCanonicalType(To);
+  return ::AdjustingCFIUncheckedCallee(From, To) == Discarding;
+}
+
+bool Sema::AddingCFIUncheckedCallee(QualType From, QualType To) const {
+  From = Context.getCanonicalType(From);
+  To = Context.getCanonicalType(To);
+  return ::AdjustingCFIUncheckedCallee(From, To) == Adding;
+}
+
 void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC,
                                    bool *ICContext, bool IsListInit) {
   if (E->isTypeDependent() || E->isValueDependent()) return;
@@ -12255,6 +12295,11 @@ void Sema::CheckImplicitConversion(Expr *E, QualType 
T, SourceLocation CC,
   if (Target->isBooleanType())
     DiagnoseIntInBoolContext(*this, E);
 
+  if (DiscardingCFIUncheckedCallee(QualType(Source, 0), QualType(Target, 0))) {
+    Diag(CC, diag::warn_cast_discards_cfi_unchecked_callee)
+        << QualType(Source, 0) << QualType(Target, 0);
+  }
+
   if (!Source->isIntegerType() || !Target->isIntegerType())
     return;
 
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 4d7f0455444f1..89f18c8c4ddbd 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -872,6 +872,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 = "*";
 
@@ -7094,6 +7109,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 d1889100c382e..da6363608f913 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -9281,8 +9281,14 @@ static AssignConvertType 
checkPointerTypesForAssignment(Sema &S,
       return AssignConvertType::IncompatibleFunctionPointer;
     return AssignConvertType::IncompatiblePointer;
   }
-  if (!S.getLangOpts().CPlusPlus && S.IsFunctionConversion(ltrans, rtrans))
-    return AssignConvertType::IncompatibleFunctionPointer;
+  bool DiscardingCFIUncheckedCallee, AddingCFIUncheckedCallee;
+  if (!S.getLangOpts().CPlusPlus &&
+      S.IsFunctionConversion(ltrans, rtrans, &DiscardingCFIUncheckedCallee,
+                             &AddingCFIUncheckedCallee)) {
+    // Allow conversions between CFIUncheckedCallee-ness.
+    if (!DiscardingCFIUncheckedCallee && !AddingCFIUncheckedCallee)
+      return AssignConvertType::IncompatibleFunctionPointer;
+  }
   if (IsInvalidCmseNSCallConversion(S, ltrans, rtrans))
     return AssignConvertType::IncompatibleFunctionPointer;
   if (S.IsInvalidSMECallConversion(rtrans, ltrans))
@@ -12733,9 +12739,17 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, 
ExprResult &RHS,
       LangAS AddrSpaceR = RCanPointeeTy.getAddressSpace();
       CastKind Kind = AddrSpaceL != AddrSpaceR ? CK_AddressSpaceConversion
                                                : CK_BitCast;
+
+      const FunctionType *LFn = LCanPointeeTy->getAs<FunctionType>();
+      const FunctionType *RFn = RCanPointeeTy->getAs<FunctionType>();
+      bool LHSHasCFIUncheckedCallee = LFn && LFn->getCFIUncheckedCalleeAttr();
+      bool RHSHasCFIUncheckedCallee = RFn && RFn->getCFIUncheckedCalleeAttr();
+      bool ChangingCFIUncheckedCallee =
+          LHSHasCFIUncheckedCallee != RHSHasCFIUncheckedCallee;
+
       if (LHSIsNull && !RHSIsNull)
         LHS = ImpCastExprToType(LHS.get(), RHSType, Kind);
-      else
+      else if (!ChangingCFIUncheckedCallee)
         RHS = ImpCastExprToType(RHS.get(), LHSType, Kind);
     }
     return computeResultTy();
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index b071c98051bbe..b13c99412ef43 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -8034,6 +8034,11 @@ QualType Sema::FindCompositePointerType(SourceLocation 
Loc,
         EPI1.ExtInfo = EPI1.ExtInfo.withNoReturn(Noreturn);
         EPI2.ExtInfo = EPI2.ExtInfo.withNoReturn(Noreturn);
 
+        bool CFIUncheckedCallee =
+            EPI1.CFIUncheckedCallee || EPI2.CFIUncheckedCallee;
+        EPI1.CFIUncheckedCallee = CFIUncheckedCallee;
+        EPI2.CFIUncheckedCallee = CFIUncheckedCallee;
+
         // The result is nothrow if both operands are.
         SmallVector<QualType, 8> ExceptionTypeStorage;
         EPI1.ExceptionSpec = EPI2.ExceptionSpec = Context.mergeExceptionSpecs(
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 23304e12f8c31..c520cbdd6156c 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1889,7 +1889,14 @@ bool Sema::TryFunctionConversion(QualType FromType, 
QualType ToType,
   return Changed;
 }
 
-bool Sema::IsFunctionConversion(QualType FromType, QualType ToType) const {
+bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
+                                bool *DiscardingCFIUncheckedCallee,
+                                bool *AddingCFIUncheckedCallee) const {
+  if (DiscardingCFIUncheckedCallee)
+    *DiscardingCFIUncheckedCallee = false;
+  if (AddingCFIUncheckedCallee)
+    *AddingCFIUncheckedCallee = false;
+
   if (Context.hasSameUnqualifiedType(FromType, ToType))
     return false;
 
@@ -1944,9 +1951,34 @@ bool Sema::IsFunctionConversion(QualType FromType, 
QualType ToType) const {
     Changed = true;
   }
 
+  const auto *FromFPT = dyn_cast<FunctionProtoType>(FromFn);
+  const auto *ToFPT = dyn_cast<FunctionProtoType>(ToFn);
+
+  if (FromFPT && ToFPT) {
+    if (FromFPT->hasCFIUncheckedCallee() && !ToFPT->hasCFIUncheckedCallee()) {
+      QualType NewTy = Context.getFunctionType(
+          FromFPT->getReturnType(), FromFPT->getParamTypes(),
+          FromFPT->getExtProtoInfo().withCFIUncheckedCallee(false));
+      FromFPT = cast<FunctionProtoType>(NewTy.getTypePtr());
+      FromFn = FromFPT;
+      Changed = true;
+      if (DiscardingCFIUncheckedCallee)
+        *DiscardingCFIUncheckedCallee = true;
+    } else if (!FromFPT->hasCFIUncheckedCallee() &&
+               ToFPT->hasCFIUncheckedCallee()) {
+      QualType NewTy = Context.getFunctionType(
+          FromFPT->getReturnType(), FromFPT->getParamTypes(),
+          FromFPT->getExtProtoInfo().withCFIUncheckedCallee(true));
+      FromFPT = cast<FunctionProtoType>(NewTy.getTypePtr());
+      FromFn = FromFPT;
+      Changed = true;
+      if (AddingCFIUncheckedCallee)
+        *AddingCFIUncheckedCallee = true;
+    }
+  }
+
   // Drop 'noexcept' if not present in target type.
-  if (const auto *FromFPT = dyn_cast<FunctionProtoType>(FromFn)) {
-    const auto *ToFPT = cast<FunctionProtoType>(ToFn);
+  if (FromFPT) {
     if (FromFPT->isNothrow() && !ToFPT->isNothrow()) {
       FromFn = cast<FunctionType>(
           Context.getFunctionTypeWithExceptionSpec(QualType(FromFPT, 0),
@@ -2510,12 +2542,15 @@ static bool IsStandardConversion(Sema &S, Expr* From, 
QualType ToType,
 
   SCS.setToType(2, FromType);
 
-  if (CanonFrom == CanonTo)
-    return true;
-
   // If we have not converted the argument type to the parameter type,
   // this is a bad conversion sequence, unless we're resolving an overload in 
C.
-  if (S.getLangOpts().CPlusPlus || !InOverloadResolution)
+  //
+  // Permit conversions from a function without `cfi_unchecked_callee` to a
+  // function with `cfi_unchecked_callee`.
+  if (CanonFrom == CanonTo || S.AddingCFIUncheckedCallee(CanonFrom, CanonTo))
+    return true;
+
+  if ((S.getLangOpts().CPlusPlus || !InOverloadResolution))
     return false;
 
   ExprResult ER = ExprResult{From};
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 9ed2326f151a3..01c3540f85756 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:                                  
\
@@ -7813,6 +7814,27 @@ static bool handleFunctionTypeAttr(TypeProcessingState 
&state, ParsedAttr &attr,
     return true;
   }
 
+  if (attr.getKind() == ParsedAttr::AT_CFIUncheckedCallee) {
+    // Delay if this is not a prototyped function type.
+    if (!unwrapped.isFunctionType())
+      return false;
+
+    if (!unwrapped.get()->isFunctionProtoType()) {
+      S.Diag(attr.getLoc(), diag::warn_attribute_wrong_decl_type)
+          << attr << attr.isRegularKeywordAttribute()
+          << ExpectedFunctionWithProtoType;
+      attr.setInvalid();
+      return true;
+    }
+
+    const auto *FPT = unwrapped.get()->getAs<FunctionProtoType>();
+    type = S.Context.getFunctionType(
+        FPT->getReturnType(), FPT->getParamTypes(),
+        FPT->getExtProtoInfo().withCFIUncheckedCallee(true));
+    type = unwrapped.wrap(S, cast<FunctionType>(type.getTypePtr()));
+    return true;
+  }
+
   if (attr.getKind() == ParsedAttr::AT_CmseNSCall) {
     // Delay if this is not a function type.
     if (!unwrapped.isFunctionType())
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-n3037.c 
b/clang/test/Frontend/cfi-unchecked-callee-attribute-n3037.c
new file mode 100644
index 0000000000000..ae5c14096f837
--- /dev/null
+++ b/clang/test/Frontend/cfi-unchecked-callee-attribute-n3037.c
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -Wall -Wno-unused -Wno-uninitialized -verify -std=c17 %s
+// RUN: %clang_cc1 -Wall -Wno-unused -Wno-uninitialized -verify -std=c23 %s
+
+#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee))
+
+#if __STDC_VERSION__ >= 202311L
+// expected-no-diagnostics
+#endif
+
+#if __STDC_VERSION__ < 202311L
+// expected-note@+2 2 {{previous definition is here}}
+#endif
+struct field_attr_test {
+  void (CFI_UNCHECKED_CALLEE *func)(void);
+};
+
+#if __STDC_VERSION__ < 202311L
+// expected-error@+2{{redefinition of 'field_attr_test'}}
+#endif
+struct field_attr_test {
+  void (CFI_UNCHECKED_CALLEE *func)(void);
+};
+
+typedef void (CFI_UNCHECKED_CALLEE func_t)(void);
+
+#if __STDC_VERSION__ < 202311L
+// expected-error@+2{{redefinition of 'field_attr_test'}}
+#endif
+struct field_attr_test {
+  func_t *func;
+};
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..80a11fd387b78
--- /dev/null
+++ b/clang/test/Frontend/cfi-unchecked-callee-attribute.c
@@ -0,0 +1,71 @@
+// 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 (void) __attribute__((cfi_unchecked_callee))' to 'void 
(*)(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;
+
+typedef void (CFI_UNCHECKED_CALLEE unchecked_func_t)(void);
+typedef void (checked_func_t)(void);
+typedef void (CFI_UNCHECKED_CALLEE *cfi_unchecked_func_ptr_t)(void);
+typedef void (*checked_func_ptr_t)(void);
+checked_func_t *cfi_func = unchecked;  // expected-warning{{implicit 
conversion from 'void (void) __attribute__((cfi_unchecked_callee))' to 'void 
(*)(void)' discards `cfi_unchecked_callee` attribute}}
+unchecked_func_t *unchecked_func = unchecked;
+
+void CFI_UNCHECKED_CALLEE after_ret_type(void);
+CFI_UNCHECKED_CALLEE void before_ret_type(void);
+void (* CFI_UNCHECKED_CALLEE after_name)(void);
+
+void UsageOnImproperTypes() {
+  int CFI_UNCHECKED_CALLEE i;  // expected-warning{{'cfi_unchecked_callee' 
only applies to function types; type here is 'int'}}
+
+  /// The attribute must be used only on functions with prototypes. The 
omission of `void` means it is not prototyped.
+  void (CFI_UNCHECKED_CALLEE *noproto)(void);  // 
expecteed-warning{{'cfi_unchecked_callee' attribute only applies to 
non-K&R-style functions}}
+}
+
+/// Explicit casts suppress the warning.
+void CheckCasts() {
+  void (*should_warn)(void) = unchecked;  // expected-warning{{implicit 
conversion from 'void (void) __attribute__((cfi_unchecked_callee))' to 'void 
(*)(void)' discards `cfi_unchecked_callee` attribute}}
+
+  void (*no_warn_c_style_cast)(void) = (void (*)(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);
+  void (*cfi_elem)(void) = arr[1];  // expected-warning{{implicit conversion 
from 'void (*)(void) __attribute__((cfi_unchecked_callee))' to 'void (*)(void)' 
discards `cfi_unchecked_callee` attribute}}
+  void (CFI_UNCHECKED_CALLEE *cfi_unchecked_elem)(void) = arr[1];
+
+  int invoke = checked_arg_func(unchecked);  // expected-warning{{implicit 
conversion from 'void (void) __attribute__((cfi_unchecked_callee))' to 'void 
(*)(void)' discards `cfi_unchecked_callee` attribute}}
+}
+
+checked_func_t *returning_checked_func() {
+  return unchecked;  // expected-warning{{implicit conversion from 'void 
(void) __attribute__((cfi_unchecked_callee))' to 'void (*)(void)' discards 
`cfi_unchecked_callee` attribute}}
+}
+
+void no_args(void) __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 (*)(void))unchecked == checked_ptr;
+  checked_ptr == (void (*)(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..295fb85406556
--- /dev/null
+++ b/clang/test/Frontend/cfi-unchecked-callee-attribute.cpp
@@ -0,0 +1,235 @@
+// RUN: %clang_cc1 -Wall -Wno-unused -Wno-uninitialized -std=c++2b -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 '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 (* CFI_UNCHECKED_CALLEE after_name)(void);
+
+void UsageOnImproperTypes() {
+  int CFI_UNCHECKED_CALLEE i;  // expected-warning{{'cfi_unchecked_callee' 
only applies to function types; type here is 'int'}}
+}
+
+/// 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);
+  unsigned long long ull = (unsigned long long)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 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void 
(*)()' discards `cfi_unchecked_callee` attribute}}
+  new (checked_func_t *)(unchecked_func);  // expected-warning{{implicit 
conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void 
(*)()' discards `cfi_unchecked_callee` attribute}}
+  struct S {
+    checked_func_t *checked_func;
+
+    S(unchecked_func_t *unchecked_func) : checked_func(unchecked_func) {}  // 
expected-warning{{implicit conversion from 'void (*)() 
__attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 
`cfi_unchecked_callee` attribute}}
+  };
+
+  checked_func_t *checked_func2{unchecked_func};  // 
expected-warning{{implicit conversion from 'void (*)() 
__attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 
`cfi_unchecked_callee` attribute}}
+  checked_ptr = checked_func_ptr_t(unchecked);
+
+  auto checked_auto = checked;
+  auto unchecked_auto = unchecked;
+  unchecked_ptr = checked_auto;
+  unchecked_ptr = unchecked_auto;
+  checked_ptr = checked_auto;
+  checked_ptr = unchecked_auto;  // expected-warning{{implicit conversion from 
'void (*)() __attribute__((cfi_unchecked_callee))' to '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 '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 'void (*)()' 
discards `cfi_unchecked_callee` attribute}}
+
+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);  // expected-warning{{implicit conversion 
from 'void () __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 
`cfi_unchecked_callee` attribute}}
+S s5(checked);
+S s6(unchecked);
+
+template <typename T, typename U>
+struct is_same {
+  static constexpr bool value = false;
+};
+template <typename T>
+struct is_same<T,T> {
+  static constexpr bool value = true;
+};
+
+template <typename T>
+struct ExpectingCFIUncheckedCallee {
+  static_assert(is_same<T, unchecked_func_t>::value);
+  ExpectingCFIUncheckedCallee(T *) {}
+  ExpectingCFIUncheckedCallee() = default;
+};
+ExpectingCFIUncheckedCallee<unchecked_func_t> expecting;
+ExpectingCFIUncheckedCallee expecting2(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;
+  };
+}
+
+/// Type aliasing
+typedef void (BaseType)(void);
+using WithoutAttr = BaseType;
+using WithAttr = __attribute__((cfi_unchecked_callee)) BaseType;
+
+WithoutAttr *checked_func_alias = unchecked;  // expected-warning{{implicit 
conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void (*)()' 
discards `cfi_unchecked_callee` attribute}}
+WithAttr *unchecked_func_allias = unchecked;
+WithoutAttr *checked_func_alias2 = checked;
+WithAttr *unchecked_func_alias2 = checked;
+
+using MyType = WithAttr;  // expected-note{{previous definition is here}}
+using MyType = WithoutAttr;  // expected-error{{type alias redefinition with 
different types ('WithoutAttr' (aka 'void ()') vs 'WithAttr' (aka 'void () 
__attribute__((cfi_unchecked_callee))'))}}
+
+void MemberFunctionPointer() {
+  struct A {
+    void unchecked() CFI_UNCHECKED_CALLEE {}
+    virtual void unchecked_virtual() CFI_UNCHECKED_CALLEE {}
+    static void unchecked_static() CFI_UNCHECKED_CALLEE {}
+    void unchecked_explicit_this(this A&) CFI_UNCHECKED_CALLEE {}
+    int operator+=(int i) CFI_UNCHECKED_CALLEE { return i; }
+
+    void checked() {}
+    virtual void checked_virtual() {}
+    static void checked_static() {}
+    void checked_explicit_this(this A&) {}
+    int operator-=(int i) { return i; }
+  };
+
+  void (CFI_UNCHECKED_CALLEE A::*unchecked)() = &A::unchecked;
+  unchecked = &A::unchecked_virtual;
+  void (CFI_UNCHECKED_CALLEE *unchecked_explicit_this)(A&) = 
&A::unchecked_explicit_this;
+  void (CFI_UNCHECKED_CALLEE *unchecked_static)() = &A::unchecked_static;
+  int (CFI_UNCHECKED_CALLEE A::*unchecked_overloaded)(int) = &A::operator+=;
+
+  void (A::*checked)() = &A::unchecked;  // expected-warning{{implicit 
conversion from 'void (A::*)() __attribute__((cfi_unchecked_callee))' to 'void 
(A::*)()' discards `cfi_unchecked_callee` attribute}}
+  checked = &A::unchecked_virtual;  // expected-warning{{implicit conversion 
from 'void (A::*)() __attribute__((cfi_unchecked_callee))' to 'void (A::*)()' 
discards `cfi_unchecked_callee` attribute}}
+  void (*checked_explicit_this)(A&) = &A::unchecked_explicit_this;  // 
expected-warning{{implicit conversion from 'void (*)(A &) 
__attribute__((cfi_unchecked_callee))' to 'void (*)(A &)' discards 
`cfi_unchecked_callee` attribute}}
+  void (*checked_static)() = &A::unchecked_static;  // 
expected-warning{{implicit conversion from 'void (*)() 
__attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 
`cfi_unchecked_callee` attribute}}
+  int (A::*checked_overloaded)(int) = &A::operator+=;  // 
expected-warning{{implicit conversion from 'int (A::*)(int) 
__attribute__((cfi_unchecked_callee))' to 'int (A::*)(int)' discards 
`cfi_unchecked_callee` attribute}}
+
+  unchecked = &A::checked;
+  unchecked = &A::checked_virtual;
+  unchecked_explicit_this = &A::checked_explicit_this;
+  unchecked_static = &A::checked_static;
+  unchecked_overloaded = &A::operator-=;
+
+  checked = &A::checked;
+  checked = &A::checked_virtual;
+  checked_explicit_this = &A::checked_explicit_this;
+  checked_static = &A::checked_static;
+  checked_overloaded = &A::operator-=;
+
+  typedef void (CFI_UNCHECKED_CALLEE A::*WithAttr)();
+  typedef void (CFI_UNCHECKED_CALLEE A::*WithoutAttr)();
+  using WithoutAttr = decltype(unchecked);
+}
+
+void lambdas() {
+  auto unchecked_lambda = [](void) CFI_UNCHECKED_CALLEE -> void {};
+  auto checked_lambda = [](void) -> void {};
+  void (CFI_UNCHECKED_CALLEE *unchecked_func)(void) = unchecked_lambda;
+  unchecked_func = checked_lambda;
+  void (*checked_func)(void) = unchecked_lambda;  // 
expected-warning{{implicit conversion from 'void (*)() 
__attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 
`cfi_unchecked_callee` attribute}}
+  checked_func = checked_lambda;
+
+  auto capture_by_value = [unchecked_lambda, checked_lambda]() {
+    void (CFI_UNCHECKED_CALLEE *unchecked_func)(void) = unchecked_lambda;
+    unchecked_func = checked_lambda;
+    void (*checked_func)(void) = unchecked_lambda;  // 
expected-warning{{implicit conversion from 'void (*)() 
__attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 
`cfi_unchecked_callee` attribute}}
+    checked_func = checked_lambda;
+  };
+
+  auto capture_by_ref = [&unchecked_lambda, &checked_lambda]() {
+    void (CFI_UNCHECKED_CALLEE *unchecked_func)(void) = unchecked_lambda;
+    unchecked_func = checked_lambda;
+    void (*checked_func)(void) = unchecked_lambda;  // 
expected-warning{{implicit conversion from 'void (*)() 
__attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 
`cfi_unchecked_callee` attribute}}
+    checked_func = checked_lambda;
+  };
+
+  auto capture_all_by_value = [=]() {
+    void (CFI_UNCHECKED_CALLEE *unchecked_func)(void) = unchecked_lambda;
+    unchecked_func = checked_lambda;
+    void (*checked_func)(void) = unchecked_lambda;  // 
expected-warning{{implicit conversion from 'void (*)() 
__attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 
`cfi_unchecked_callee` attribute}}
+    checked_func = checked_lambda;
+  };
+
+  auto capture_all_by_ref = [&]() {
+    void (CFI_UNCHECKED_CALLEE *unchecked_func)(void) = unchecked_lambda;
+    unchecked_func = checked_lambda;
+    void (*checked_func)(void) = unchecked_lambda;  // 
expected-warning{{implicit conversion from 'void (*)() 
__attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 
`cfi_unchecked_callee` attribute}}
+    checked_func = checked_lambda;
+  };
+}

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to