[clang] d1e84bb - Thread Safety Analysis: Support attributes on function pointers (#191187)

via cfe-commits cfe-commits at lists.llvm.org
Mon May 4 03:38:13 PDT 2026


Author: Marco Elver
Date: 2026-05-04T12:38:08+02:00
New Revision: d1e84bb9ad3b6a844cd902cf70e2764cb8126979

URL: https://github.com/llvm/llvm-project/commit/d1e84bb9ad3b6a844cd902cf70e2764cb8126979
DIFF: https://github.com/llvm/llvm-project/commit/d1e84bb9ad3b6a844cd902cf70e2764cb8126979.diff

LOG: Thread Safety Analysis: Support attributes on function pointers (#191187)

Allow acquire_capability, release_capability, requires_capability,
try_acquire_capability, assert_capability, and locks_excluded attributes
(incl. their shared variants) on function pointer variables and struct
fields. Calls through annotated function pointers are checked the same
way as direct function calls.

The attributes are placed on variable/field declarations, not on the
function pointer type itself. This is a deliberate trade-off: making
these "attributes" part of the type system would require diagnosing
mismatched assignments, which would be a significant type-system
extension with limited practical benefit, which would likely require
promoting the TSA vocabulary to full type-qualifiers. Instead, the
analysis trusts the annotations on the variable at the call site, and
sticks with the attribute-based semantics. This matches the existing
philosophy where the analysis tries to avoid false positives where
possible and attribute mismatches on direct functions are likewise not
hard errors or warnings (yet).

The primary motivation is to avoid false positives in large C codebases,
such as the Linux kernel [1], which tend to use structs containing
function pointers to emulate subtype polymorphism and dynamic dispatch.

[1] https://lore.kernel.org/all/20260409064221.GA8378@lst.de/

Added: 
    

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/docs/ThreadSafetyAnalysis.rst
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Sema/Sema.h
    clang/lib/Analysis/ThreadSafetyCommon.cpp
    clang/lib/Parse/ParseDecl.cpp
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/lib/Sema/SemaTemplateInstantiate.cpp
    clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
    clang/test/Sema/attr-capabilities.c
    clang/test/Sema/warn-thread-safety-analysis.c
    clang/test/SemaCXX/warn-thread-safety-analysis.cpp
    clang/test/SemaCXX/warn-thread-safety-parsing.cpp
    clang/utils/TableGen/ClangAttrEmitter.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7027a3d836cd6..eb1d926926d9e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -324,6 +324,14 @@ Attribute Changes in Clang
   sound because any writer must hold all capabilities, so holding any one
   prevents concurrent writes.
 
+- :doc:`ThreadSafetyAnalysis` attributes like ``acquire_capability``,
+  ``release_capability``, ``requires_capability``, ``locks_excluded``,
+  ``try_acquire_capability``, and ``assert_capability`` can now be applied to
+  function pointer variables and fields.  The analysis checks calls through
+  annotated function pointers the same way it checks direct function calls.
+  Only plain function pointers are supported; pointers-to-member functions,
+  blocks, or wrappers (e.g. ``std::function``) are not yet supported.
+
 - The ``[[clang::unsafe_buffer_usage]]`` attribute is now supported in API
   notes. For example:
   

diff  --git a/clang/docs/ThreadSafetyAnalysis.rst b/clang/docs/ThreadSafetyAnalysis.rst
index 571c6c0e04ea1..fee1c2b778c01 100644
--- a/clang/docs/ThreadSafetyAnalysis.rst
+++ b/clang/docs/ThreadSafetyAnalysis.rst
@@ -544,6 +544,42 @@ GUARDED_VAR and PT_GUARDED_VAR
 Use of these attributes has been deprecated.
 
 
+Function Pointers
+-----------------
+
+Thread safety attributes may also be applied to function pointer variables and
+fields.  The attributes describe the locking behavior of calling through that
+pointer, and the analysis will check calls through the pointer accordingly.
+
+.. code-block:: c++
+
+  Mutex mu;
+  int x GUARDED_BY(mu);
+
+  void (*lock_fn)(void)   ACQUIRE(mu);
+  void (*unlock_fn)(void) RELEASE(mu);
+
+  struct Ops {
+    void (*read)(void) REQUIRES(mu);
+  };
+
+  void test(Ops *ops) {
+    lock_fn();
+    x = 1;
+    ops->read();
+    unlock_fn();
+  }
+
+Note that the attributes are on the *variable* (or field), not on the function
+pointer type.  Assigning a function with 
diff erent (or no) attributes to an
+annotated function pointer variable is not diagnosed.  The analysis trusts the
+annotations on the variable at the call site.
+
+This support is limited to plain function pointers.  Pointers-to-member
+functions, blocks, and wrapper types such as ``std::function`` are not
+supported yet.
+
+
 Warning flags
 -------------
 

diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 04b7f420e1b32..70b5773f95b08 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -747,6 +747,9 @@ class Attr {
   bit PragmaAttributeSupport;
   // Set to true if this attribute accepts parameter pack expansion expressions.
   bit AcceptsExprPack = 0;
+  // Set to true if this attribute's argument list is parsed inside a function
+  // prototype scope, so it can reference the function's parameters.
+  bit ParseArgsInFunctionScope = 0;
   // To support multiple enum parameters to an attribute without breaking
   // our existing general parsing we need to have a separate flag that
   // opts an attribute into strict parsing of attribute parameters
@@ -1910,6 +1913,7 @@ def EnableIf : InheritableAttr {
   let Subjects = SubjectList<[Function]>;
   let Args = [ExprArgument<"Cond">, StringArgument<"Message">];
   let TemplateDependent = 1;
+  let ParseArgsInFunctionScope = 1;
   let Documentation = [EnableIfDocs];
 }
 
@@ -4096,11 +4100,12 @@ def AssertCapability : InheritableAttr {
                    Clang<"assert_shared_capability", 0>,
                    GNU<"assert_exclusive_lock">,
                    GNU<"assert_shared_lock">];
-  let Subjects = SubjectList<[Function]>;
+  let Subjects = SubjectList<[Function, Var, Field]>;
   let LateParsed = LateAttrParseStandard;
   let TemplateDependent = 1;
   let ParseArgumentsAsUnevaluated = 1;
   let InheritEvenIfAlreadyPresent = 1;
+  let ParseArgsInFunctionScope = 1;
   let Args = [VariadicExprArgument<"Args">];
   let AcceptsExprPack = 1;
   let Accessors = [Accessor<"isShared",
@@ -4114,11 +4119,12 @@ def AcquireCapability : InheritableAttr {
                    Clang<"acquire_shared_capability", 0>,
                    GNU<"exclusive_lock_function">,
                    GNU<"shared_lock_function">];
-  let Subjects = SubjectList<[Function, ParmVar]>;
+  let Subjects = SubjectList<[Function, Var, Field]>;
   let LateParsed = LateAttrParseStandard;
   let TemplateDependent = 1;
   let ParseArgumentsAsUnevaluated = 1;
   let InheritEvenIfAlreadyPresent = 1;
+  let ParseArgsInFunctionScope = 1;
   let Args = [VariadicExprArgument<"Args">];
   let AcceptsExprPack = 1;
   let Accessors = [Accessor<"isShared",
@@ -4132,11 +4138,12 @@ def TryAcquireCapability : InheritableAttr {
                    Clang<"try_acquire_shared_capability", 0>,
                    GNU<"exclusive_trylock_function">,
                    GNU<"shared_trylock_function">];
-  let Subjects = SubjectList<[Function]>;
+  let Subjects = SubjectList<[Function, Var, Field]>;
   let LateParsed = LateAttrParseStandard;
   let TemplateDependent = 1;
   let ParseArgumentsAsUnevaluated = 1;
   let InheritEvenIfAlreadyPresent = 1;
+  let ParseArgsInFunctionScope = 1;
   let Args = [ExprArgument<"SuccessValue">, VariadicExprArgument<"Args">];
   let AcceptsExprPack = 1;
   let Accessors = [Accessor<"isShared",
@@ -4150,11 +4157,12 @@ def ReleaseCapability : InheritableAttr {
                    Clang<"release_shared_capability", 0>,
                    Clang<"release_generic_capability", 0>,
                    Clang<"unlock_function", 0>];
-  let Subjects = SubjectList<[Function, ParmVar]>;
+  let Subjects = SubjectList<[Function, Var, Field]>;
   let LateParsed = LateAttrParseStandard;
   let TemplateDependent = 1;
   let ParseArgumentsAsUnevaluated = 1;
   let InheritEvenIfAlreadyPresent = 1;
+  let ParseArgsInFunctionScope = 1;
   let Args = [VariadicExprArgument<"Args">];
   let AcceptsExprPack = 1;
   let Accessors = [Accessor<"isShared",
@@ -4176,7 +4184,8 @@ def RequiresCapability : InheritableAttr {
   let TemplateDependent = 1;
   let ParseArgumentsAsUnevaluated = 1;
   let InheritEvenIfAlreadyPresent = 1;
-  let Subjects = SubjectList<[Function, ParmVar]>;
+  let ParseArgsInFunctionScope = 1;
+  let Subjects = SubjectList<[Function, Var, Field]>;
   let Accessors = [Accessor<"isShared", [Clang<"requires_shared_capability", 0>,
                                          Clang<"shared_locks_required", 0>]>];
   let Documentation = [Undocumented];
@@ -4253,7 +4262,8 @@ def LocksExcluded : InheritableAttr {
   let TemplateDependent = 1;
   let ParseArgumentsAsUnevaluated = 1;
   let InheritEvenIfAlreadyPresent = 1;
-  let Subjects = SubjectList<[Function, ParmVar]>;
+  let ParseArgsInFunctionScope = 1;
+  let Subjects = SubjectList<[Function, Var, Field]>;
   let Documentation = [Undocumented];
 }
 

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8c4c6f6bce8d7..e059260778631 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4306,6 +4306,9 @@ def warn_thread_attribute_not_on_scoped_lockable_param : Warning<
 def warn_thread_attribute_requires_preceded : Warning<
   "%0 attribute on %1 must be preceded by %2 attribute">,
   InGroup<ThreadSafetyAttributes>, DefaultIgnore;
+def warn_thread_attribute_not_on_fun_ptr : Warning<
+  "%0 attribute on a %select{variable|field}1 requires the %select{variable|field}1 to be of function pointer type">,
+  InGroup<ThreadSafetyAttributes>, DefaultIgnore;
 def err_attribute_argument_out_of_bounds_extra_info : Error<
   "%0 attribute parameter %1 is out of bounds: "
   "%plural{0:no parameters to index into|"

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 8f98e104d3797..72beac7526dc5 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14251,6 +14251,10 @@ class Sema final : public SemaBase {
   };
   typedef SmallVector<LateInstantiatedAttribute, 1> LateInstantiatedAttrVec;
 
+  /// Recheck instantiated thread-safety attributes that could not be validated
+  /// on the dependent pattern declaration.
+  bool checkInstantiatedThreadSafetyAttrs(const Decl *D, const Attr *A);
+
   void InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
                         const Decl *Pattern, Decl *Inst,
                         LateInstantiatedAttrVec *LateAttrs = nullptr,

diff  --git a/clang/lib/Analysis/ThreadSafetyCommon.cpp b/clang/lib/Analysis/ThreadSafetyCommon.cpp
index b43a986521f99..bf0f2652b745a 100644
--- a/clang/lib/Analysis/ThreadSafetyCommon.cpp
+++ b/clang/lib/Analysis/ThreadSafetyCommon.cpp
@@ -187,10 +187,15 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp,
   }
 
   // If the attribute has no arguments, then assume the argument is "this".
-  if (!AttrExp)
+  // SelfArg may be null for non-method callees (e.g. function pointers).
+  if (!AttrExp) {
+    if (!Ctx.SelfArg)
+      return CapabilityExpr();
     return translateAttrExpr(cast<const Expr *>(Ctx.SelfArg), nullptr);
-  else  // For most attributes.
-    return translateAttrExpr(AttrExp, &Ctx);
+  }
+
+  // For most attributes.
+  return translateAttrExpr(AttrExp, &Ctx);
 }
 
 /// Translate a clang expression in an attribute to a til::SExpr.
@@ -374,6 +379,30 @@ til::SExpr *SExprBuilder::translate(const Stmt *S, CallingContext *Ctx) {
   return new (Arena) til::Undefined(S);
 }
 
+/// Helper to extract the canonical parameter declaration from a function or
+/// function pointer. This unwraps pointer and reference types to reach the
+/// underlying function prototype.
+static const ParmVarDecl *getCanonicalParamDecl(const Decl *D, unsigned I) {
+  if (const auto *FD = dyn_cast<FunctionDecl>(D))
+    return FD->getCanonicalDecl()->getParamDecl(I);
+  if (const auto *MD = dyn_cast<ObjCMethodDecl>(D))
+    return MD->getCanonicalDecl()->getParamDecl(I);
+  if (const auto *DD = dyn_cast<DeclaratorDecl>(D)) {
+    if (auto *TSI = DD->getTypeSourceInfo()) {
+      TypeLoc TL = TSI->getTypeLoc();
+      if (auto RTL = TL.getAsAdjusted<ReferenceTypeLoc>())
+        TL = RTL.getPointeeLoc();
+      // A function pointer can be multiple levels deep.
+      while (auto PTL = TL.getAsAdjusted<PointerTypeLoc>())
+        TL = PTL.getPointeeLoc();
+      if (auto FPTL = TL.getAsAdjusted<FunctionProtoTypeLoc>())
+        if (I < FPTL.getNumParams())
+          return FPTL.getParam(I);
+    }
+  }
+  return nullptr;
+}
+
 til::SExpr *SExprBuilder::translateDeclRefExpr(const DeclRefExpr *DRE,
                                                CallingContext *Ctx) {
   const auto *VD = cast<ValueDecl>(DRE->getDecl()->getCanonicalDecl());
@@ -384,9 +413,17 @@ til::SExpr *SExprBuilder::translateDeclRefExpr(const DeclRefExpr *DRE,
     const DeclContext *D = PV->getDeclContext();
     if (Ctx && Ctx->FunArgs) {
       const Decl *Canonical = Ctx->AttrDecl->getCanonicalDecl();
-      if (isa<FunctionDecl>(D)
-              ? (cast<FunctionDecl>(D)->getCanonicalDecl() == Canonical)
-              : (cast<ObjCMethodDecl>(D)->getCanonicalDecl() == Canonical)) {
+      bool Match = false;
+      if (const auto *FD = dyn_cast<FunctionDecl>(D))
+        Match = (FD->getCanonicalDecl() == Canonical);
+      else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D))
+        Match = (MD->getCanonicalDecl() == Canonical);
+      else if (getCanonicalParamDecl(Canonical, I) == PV->getCanonicalDecl())
+        Match = true;
+      else
+        llvm_unreachable("ParmVarDecl does not belong to current declaration");
+
+      if (Match) {
         // Substitute call arguments for references to function parameters
         if (const Expr *const *FunArgs =
                 dyn_cast<const Expr *const *>(Ctx->FunArgs)) {
@@ -400,9 +437,8 @@ til::SExpr *SExprBuilder::translateDeclRefExpr(const DeclRefExpr *DRE,
     }
     // Map the param back to the param of the original function declaration
     // for consistent comparisons.
-    VD = isa<FunctionDecl>(D)
-             ? cast<FunctionDecl>(D)->getCanonicalDecl()->getParamDecl(I)
-             : cast<ObjCMethodDecl>(D)->getCanonicalDecl()->getParamDecl(I);
+    if (const auto *PVD = getCanonicalParamDecl(cast<Decl>(D), I))
+      VD = PVD;
   }
 
   if (const auto *VarD = dyn_cast<VarDecl>(VD))

diff  --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 06203ba8aa64c..55ea562faacaa 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -107,6 +107,16 @@ static bool IsAttributeLateParsedStandard(const IdentifierInfo &II) {
 #undef CLANG_ATTR_LATE_PARSED_LIST
 }
 
+/// Such attributes need their arguments parsed inside a function prototype
+/// scope so the arguments can reference the function's parameters.
+static bool IsAttributeArgsParsedInFunctionScope(const IdentifierInfo &II) {
+#define CLANG_ATTR_PARSE_ARGS_IN_FUNCTION_SCOPE_LIST
+  return llvm::StringSwitch<bool>(normalizeAttrName(II.getName()))
+#include "clang/Parse/AttrParserStringSwitches.inc"
+      .Default(false);
+#undef CLANG_ATTR_PARSE_ARGS_IN_FUNCTION_SCOPE_LIST
+}
+
 /// Check if the a start and end source location expand to the same macro.
 static bool FindLocsWithCommonFileID(Preprocessor &PP, SourceLocation StartLoc,
                                      SourceLocation EndLoc) {
@@ -674,15 +684,30 @@ void Parser::ParseGNUAttributeArgs(
   // These may refer to the function arguments, but need to be parsed early to
   // participate in determining whether it's a redeclaration.
   std::optional<ParseScope> PrototypeScope;
-  if (normalizeAttrName(AttrName->getName()) == "enable_if" &&
-      D && D->isFunctionDeclarator()) {
-    const DeclaratorChunk::FunctionTypeInfo& FTI = D->getFunctionTypeInfo();
-    PrototypeScope.emplace(this, Scope::FunctionPrototypeScope |
-                                     Scope::FunctionDeclarationScope |
-                                     Scope::DeclScope);
-    for (unsigned i = 0; i != FTI.NumParams; ++i)
-      Actions.ActOnReenterCXXMethodParameter(
-          getCurScope(), dyn_cast_or_null<ParmVarDecl>(FTI.Params[i].Param));
+  if (D && IsAttributeArgsParsedInFunctionScope(*AttrName)) {
+    // Find the innermost function chunk to make its parameters available for
+    // attribute argument parsing. This is necessary for attributes like thread
+    // safety annotations on function pointers which reference their parameters.
+    const DeclaratorChunk::FunctionTypeInfo *FTI = nullptr;
+    for (unsigned i = 0; i < D->getNumTypeObjects(); ++i) {
+      if (D->getTypeObject(i).Kind == DeclaratorChunk::Function) {
+        FTI = &D->getTypeObject(i).Fun;
+        break;
+      }
+    }
+
+    if (FTI) {
+      // Inherit the class scope flag from the current context. This is safe
+      // because it only preserves existing struct/class visibility, which is
+      // required for attributes to resolve sibling members in C structs.
+      PrototypeScope.emplace(
+          this, Scope::FunctionPrototypeScope |
+                    Scope::FunctionDeclarationScope | Scope::DeclScope |
+                    (getCurScope()->getFlags() & Scope::ClassScope));
+      for (unsigned i = 0; i < FTI->NumParams; ++i)
+        Actions.ActOnReenterCXXMethodParameter(
+            getCurScope(), dyn_cast_or_null<ParmVarDecl>(FTI->Params[i].Param));
+    }
   }
 
   ParseAttributeArgsCommon(AttrName, AttrNameLoc, Attrs, EndLoc, ScopeName,

diff  --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 28e51848609bb..386651fa691e0 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -436,10 +436,30 @@ static void checkAttrArgsAreCapabilityObjs(Sema &S, Decl *D,
   }
 }
 
+/// True if T (or its pointee, after stripping a top-level reference) is a
+/// function pointer or dependent.
+static bool isFunctionPointerOrDependent(QualType T) {
+  T = T.getNonReferenceType();
+  return T->isDependentType() || T->isFunctionPointerType();
+}
+
+/// Checks that thread-safety attributes on variables or fields apply only to
+/// function pointer types.
+static bool checkThreadSafetyValueDeclIsFunPtr(Sema &S, const ValueDecl *VD,
+                                               const AttributeCommonInfo &A) {
+  if (isFunctionPointerOrDependent(VD->getType()))
+    return true;
+  S.Diag(A.getLoc(), diag::warn_thread_attribute_not_on_fun_ptr)
+      << A << (isa<FieldDecl>(VD) ? 1 : 0);
+  return false;
+}
+
 static bool checkFunParamsAreScopedLockable(Sema &S,
                                             const ParmVarDecl *ParamDecl,
-                                            const ParsedAttr &AL) {
+                                            const AttributeCommonInfo &AL) {
   QualType ParamType = ParamDecl->getType();
+  if (ParamType->isDependentType())
+    return true;
   if (const auto *RefType = ParamType->getAs<ReferenceType>();
       RefType &&
       checkRecordTypeForScopedCapability(S, RefType->getPointeeType()))
@@ -449,6 +469,48 @@ static bool checkFunParamsAreScopedLockable(Sema &S,
   return false;
 }
 
+static bool checkThreadSafetyAttrSubject(Sema &S, Decl *D, const ParsedAttr &AL,
+                                         bool CheckParmVar = false) {
+  const auto *VD = dyn_cast<ValueDecl>(D);
+  if (!VD || isa<FunctionDecl>(VD))
+    return true;
+
+  if (CheckParmVar) {
+    if (const auto *PVD = dyn_cast<ParmVarDecl>(VD)) {
+      // A function-pointer parameter is also valid here.
+      if (isFunctionPointerOrDependent(PVD->getType()))
+        return true;
+      return checkFunParamsAreScopedLockable(S, PVD, AL);
+    }
+  }
+
+  return checkThreadSafetyValueDeclIsFunPtr(S, VD, AL);
+}
+
+bool Sema::checkInstantiatedThreadSafetyAttrs(const Decl *D, const Attr *A) {
+  if (!isa<AssertCapabilityAttr, AcquireCapabilityAttr,
+           TryAcquireCapabilityAttr, ReleaseCapabilityAttr,
+           RequiresCapabilityAttr, LocksExcludedAttr>(A))
+    return true;
+
+  const auto *VD = dyn_cast<ValueDecl>(D);
+  if (!VD)
+    return true;
+
+  // Parameters of template functions need to be re-checked during
+  // instantiation because their types might have been dependent.
+  if (const auto *PVD = dyn_cast<ParmVarDecl>(VD)) {
+    if (isFunctionPointerOrDependent(PVD->getType()))
+      return true;
+    return checkFunParamsAreScopedLockable(*this, PVD, *A);
+  }
+
+  if (isa<FunctionDecl>(VD))
+    return true;
+
+  return checkThreadSafetyValueDeclIsFunPtr(*this, VD, *A);
+}
+
 //===----------------------------------------------------------------------===//
 // Attribute Implementations
 //===----------------------------------------------------------------------===//
@@ -630,8 +692,7 @@ static void handleLockReturnedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
 }
 
 static void handleLocksExcludedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
-  if (const auto *ParmDecl = dyn_cast<ParmVarDecl>(D);
-      ParmDecl && !checkFunParamsAreScopedLockable(S, ParmDecl, AL))
+  if (!checkThreadSafetyAttrSubject(S, D, AL, /*CheckParmVar=*/true))
     return;
 
   if (!AL.checkAtLeastNumArgs(S, 1))
@@ -6689,6 +6750,9 @@ static void handleReentrantCapabilityAttr(Sema &S, Decl *D,
 }
 
 static void handleAssertCapabilityAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  if (!checkThreadSafetyAttrSubject(S, D, AL))
+    return;
+
   SmallVector<Expr*, 1> Args;
   if (!checkLockFunAttrCommon(S, D, AL, Args))
     return;
@@ -6699,8 +6763,7 @@ static void handleAssertCapabilityAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
 
 static void handleAcquireCapabilityAttr(Sema &S, Decl *D,
                                         const ParsedAttr &AL) {
-  if (const auto *ParmDecl = dyn_cast<ParmVarDecl>(D);
-      ParmDecl && !checkFunParamsAreScopedLockable(S, ParmDecl, AL))
+  if (!checkThreadSafetyAttrSubject(S, D, AL, /*CheckParmVar=*/true))
     return;
 
   SmallVector<Expr*, 1> Args;
@@ -6713,6 +6776,9 @@ static void handleAcquireCapabilityAttr(Sema &S, Decl *D,
 
 static void handleTryAcquireCapabilityAttr(Sema &S, Decl *D,
                                            const ParsedAttr &AL) {
+  if (!checkThreadSafetyAttrSubject(S, D, AL))
+    return;
+
   SmallVector<Expr*, 2> Args;
   if (!checkTryLockFunAttrCommon(S, D, AL, Args))
     return;
@@ -6723,9 +6789,9 @@ static void handleTryAcquireCapabilityAttr(Sema &S, Decl *D,
 
 static void handleReleaseCapabilityAttr(Sema &S, Decl *D,
                                         const ParsedAttr &AL) {
-  if (const auto *ParmDecl = dyn_cast<ParmVarDecl>(D);
-      ParmDecl && !checkFunParamsAreScopedLockable(S, ParmDecl, AL))
+  if (!checkThreadSafetyAttrSubject(S, D, AL, /*CheckParmVar=*/true))
     return;
+
   // Check that all arguments are lockable objects.
   SmallVector<Expr *, 1> Args;
   checkAttrArgsAreCapabilityObjs(S, D, AL, Args, 0, true);
@@ -6736,8 +6802,7 @@ static void handleReleaseCapabilityAttr(Sema &S, Decl *D,
 
 static void handleRequiresCapabilityAttr(Sema &S, Decl *D,
                                          const ParsedAttr &AL) {
-  if (const auto *ParmDecl = dyn_cast<ParmVarDecl>(D);
-      ParmDecl && !checkFunParamsAreScopedLockable(S, ParmDecl, AL))
+  if (!checkThreadSafetyAttrSubject(S, D, AL, /*CheckParmVar=*/true))
     return;
 
   if (!AL.checkAtLeastNumArgs(S, 1))

diff  --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index f4cd6ca9930ff..c0a2c9f747bc4 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3703,7 +3703,7 @@ bool Sema::InstantiateClassImpl(
 
     Attr *NewAttr =
       instantiateTemplateAttribute(I->TmplAttr, Context, *this, TemplateArgs);
-    if (NewAttr)
+    if (NewAttr && checkInstantiatedThreadSafetyAttrs(I->NewDecl, NewAttr))
       I->NewDecl->addAttr(NewAttr);
     LocalInstantiationScope::deleteScopes(I->Scope,
                                           Instantiator.getStartingScope());

diff  --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index c5b192373b358..c9bc613a7c4ea 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -838,7 +838,8 @@ void Sema::InstantiateAttrsForDecl(
 
       Attr *NewAttr = sema::instantiateTemplateAttributeForDecl(
           TmplAttr, Context, *this, TemplateArgs);
-      if (NewAttr && isRelevantAttr(*this, New, NewAttr))
+      if (NewAttr && isRelevantAttr(*this, New, NewAttr) &&
+          checkInstantiatedThreadSafetyAttrs(New, NewAttr))
         New->addAttr(NewAttr);
     }
   }
@@ -1067,7 +1068,8 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
 
       Attr *NewAttr = sema::instantiateTemplateAttribute(TmplAttr, Context,
                                                          *this, TemplateArgs);
-      if (NewAttr && isRelevantAttr(*this, New, TmplAttr))
+      if (NewAttr && isRelevantAttr(*this, New, TmplAttr) &&
+          checkInstantiatedThreadSafetyAttrs(New, NewAttr))
         New->addAttr(NewAttr);
     }
   }

diff  --git a/clang/test/Sema/attr-capabilities.c b/clang/test/Sema/attr-capabilities.c
index 12b18b687803e..6d594bca07535 100644
--- a/clang/test/Sema/attr-capabilities.c
+++ b/clang/test/Sema/attr-capabilities.c
@@ -13,9 +13,9 @@ struct __attribute__((capability("custom"))) CustomName {};
 
 int Test1 __attribute__((capability("test1")));  // expected-error {{'capability' attribute only applies to structs, unions, classes, and typedefs}}
 int Test2 __attribute__((shared_capability("test2"))); // expected-error {{'shared_capability' attribute only applies to structs, unions, classes, and typedefs}}
-int Test3 __attribute__((acquire_capability("test3")));  // expected-warning {{'acquire_capability' attribute only applies to functions}}
-int Test4 __attribute__((try_acquire_capability("test4"))); // expected-warning {{'try_acquire_capability' attribute only applies to functions}}
-int Test5 __attribute__((release_capability("test5"))); // expected-warning {{'release_capability' attribute only applies to functions}}
+int Test3 __attribute__((acquire_capability("test3")));  // expected-warning {{'acquire_capability' attribute on a variable requires the variable to be of function pointer type}}
+int Test4 __attribute__((try_acquire_capability("test4"))); // expected-warning {{'try_acquire_capability' attribute on a variable requires the variable to be of function pointer type}}
+int Test5 __attribute__((release_capability("test5"))); // expected-warning {{'release_capability' attribute on a variable requires the variable to be of function pointer type}}
 
 struct __attribute__((capability(12))) Test3 {}; // expected-error {{expected string literal as argument of 'capability' attribute}}
 struct __attribute__((shared_capability(Test2))) Test4 {}; // expected-error {{expected string literal as argument of 'shared_capability' attribute}}

diff  --git a/clang/test/Sema/warn-thread-safety-analysis.c b/clang/test/Sema/warn-thread-safety-analysis.c
index 0fd382eb02b78..c88f13ea1d3c8 100644
--- a/clang/test/Sema/warn-thread-safety-analysis.c
+++ b/clang/test/Sema/warn-thread-safety-analysis.c
@@ -282,6 +282,48 @@ struct TestInit test_init(void) {
   return foo;
 }
 
+// Function pointer struct members.
+struct FPOps {
+  struct Mutex mu;
+  int a GUARDED_BY(&mu);
+  void (*lock)(void) EXCLUSIVE_LOCK_FUNCTION(&mu);
+  void (*unlock)(void) UNLOCK_FUNCTION(&mu);
+  void (*requires_mu)(void) EXCLUSIVE_LOCKS_REQUIRED(&mu);
+};
+
+void test_fp_ops(struct FPOps *ops) {
+  ops->lock();
+  ops->a = 42;
+  ops->requires_mu();
+  ops->unlock();
+}
+
+void test_fp_ops_fail(struct FPOps *ops) {
+  ops->a = 42; // expected-warning {{writing variable 'a' requires holding mutex '&FPOps::mu' exclusively}}
+  ops->requires_mu(); // expected-warning {{calling function 'requires_mu' requires holding mutex '&FPOps::mu' exclusively}}
+}
+
+// Function pointer attributes referring to parameters.
+struct BDev {
+  struct Mutex lock;
+  int a GUARDED_BY(&lock);
+};
+
+struct BDevOps {
+  void (*lock)(struct BDev *bdev) EXCLUSIVE_LOCK_FUNCTION(bdev->lock);
+  void (*unlock)(struct BDev *bdev) UNLOCK_FUNCTION(bdev->lock);
+};
+
+void test_bdev_ops(struct BDevOps *ops, struct BDev *bdev) {
+  ops->lock(bdev);
+  bdev->a = 42;
+  ops->unlock(bdev);
+}
+
+void test_bdev_ops_fail(struct BDevOps *ops, struct BDev *bdev) {
+  ops->unlock(bdev); // expected-warning {{releasing mutex 'bdev->lock' that was not held}}
+}
+
 // We had a problem where we'd skip all attributes that follow a late-parsed
 // attribute in a single __attribute__.
 void run(void) __attribute__((guarded_by(mu1), guarded_by(mu1))); // expected-warning 2{{only applies to non-static data members and global variables}}

diff  --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
index 079e068ca7811..78c503cba0ed6 100644
--- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
@@ -7885,3 +7885,143 @@ void test3() {
 }
 
 } // namespace WideStringLiteral
+
+namespace FunctionPointers {
+
+Mutex mu;
+int x GUARDED_BY(mu);
+
+void (*lock_fn)(void) EXCLUSIVE_LOCK_FUNCTION(mu);
+void (*shared_lock_fn)(void) SHARED_LOCK_FUNCTION(mu);
+void (*unlock_fn)(void) UNLOCK_FUNCTION(mu);
+void (*shared_unlock_fn)(void) SHARED_UNLOCK_FUNCTION(mu);
+void (*requires_fn)(void) EXCLUSIVE_LOCKS_REQUIRED(mu);
+void (*shared_requires_fn)(void) SHARED_LOCKS_REQUIRED(mu);
+void (*excludes_fn)(void) LOCKS_EXCLUDED(mu);
+bool (*try_lock_fn)(void) EXCLUSIVE_TRYLOCK_FUNCTION(true, mu);
+bool (*shared_try_lock_fn)(void) SHARED_TRYLOCK_FUNCTION(true, mu);
+void (*assert_fn)(void) ASSERT_EXCLUSIVE_LOCK(mu);
+void (*shared_assert_fn)(void) ASSERT_SHARED_LOCK(mu);
+
+void testAcquireRelease() {
+  lock_fn();
+  x = 1;
+  unlock_fn();
+}
+
+void testSharedAcquireRelease() {
+  shared_lock_fn();
+  (void)x;
+  shared_unlock_fn();
+}
+
+void testSharedAcquireWriteFail() {
+  shared_lock_fn();
+  x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
+  shared_unlock_fn();
+}
+
+void testAcquireNoRelease() {
+  lock_fn(); // expected-note {{mutex acquired here}}
+  x = 1;
+} // expected-warning {{mutex 'mu' is still held at the end of function}}
+
+void testNoAcquire() {
+  x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
+}
+
+void testRequires() {
+  mu.Lock();
+  requires_fn();
+  mu.Unlock();
+}
+
+void testRequiresFail() {
+  requires_fn(); // expected-warning {{calling function 'requires_fn' requires holding mutex 'mu' exclusively}}
+}
+
+void testSharedRequires() {
+  mu.ReaderLock();
+  shared_requires_fn();
+  mu.ReaderUnlock();
+}
+
+void testSharedRequiresFail() {
+  shared_requires_fn(); // expected-warning {{calling function 'shared_requires_fn' requires holding mutex 'mu'}}
+}
+
+void testExcludes() {
+  excludes_fn();
+}
+
+void testExcludesFail() {
+  mu.Lock();
+  excludes_fn(); // expected-warning {{cannot call function 'excludes_fn' while mutex 'mu' is held}}
+  mu.Unlock();
+}
+
+void testTryLock() {
+  if (try_lock_fn()) {
+    x = 1;
+    mu.Unlock();
+  }
+}
+
+void testSharedTryLock() {
+  if (shared_try_lock_fn()) {
+    (void)x;
+    mu.Unlock();
+  }
+}
+
+void testAssert() {
+  assert_fn();
+  x = 1;
+}
+
+void testSharedAssert() {
+  shared_assert_fn();
+  (void)x;
+}
+
+struct Ops {
+  void (*lock)(void) EXCLUSIVE_LOCK_FUNCTION(mu);
+  void (*unlock)(void) UNLOCK_FUNCTION(mu);
+  void (*do_thing)(void) EXCLUSIVE_LOCKS_REQUIRED(mu);
+  void (*read_thing)(void) SHARED_LOCKS_REQUIRED(mu);
+};
+
+void testStructOps(Ops *ops) {
+  ops->lock();
+  x = 1;
+  ops->do_thing();
+  ops->read_thing();
+  ops->unlock();
+}
+
+void testStructFail(Ops *ops) {
+  ops->do_thing(); // expected-warning {{calling function 'do_thing' requires holding mutex 'mu' exclusively}}
+  ops->read_thing(); // expected-warning {{calling function 'read_thing' requires holding mutex 'mu'}}
+}
+
+// Incompatible reassignment not an error.
+void otherLock();
+void testReassignIncompatible() {
+  lock_fn = otherLock;
+  lock_fn();
+  x = 1;
+  mu.Unlock();
+}
+
+// A function pointer attribute may reference the pointer's own parameter.
+void (*lock_param_fn)(Mutex *m) EXCLUSIVE_LOCK_FUNCTION(m);
+void (*req_param_fn)(Mutex *m) EXCLUSIVE_LOCKS_REQUIRED(m);
+
+void test_attr_refers_to_param(Mutex *m) {
+  req_param_fn(m); // expected-warning {{calling function 'req_param_fn' requires holding mutex 'm' exclusively}}
+  lock_param_fn(m);
+  req_param_fn(m);
+  m->Unlock();
+}
+
+} // namespace FunctionPointers

diff  --git a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
index 862b2d58238c9..368c20cb45209 100644
--- a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
@@ -596,23 +596,23 @@ int elf_testfn(int y) EXCLUSIVE_LOCK_FUNCTION(); // expected-warning {{'exclusiv
 
 int elf_testfn(int y) {
   int x EXCLUSIVE_LOCK_FUNCTION() = y; // \
-    // expected-warning {{'exclusive_lock_function' attribute only applies to functions}}
+    // expected-warning {{'exclusive_lock_function' attribute on a variable requires the variable to be of function pointer type}}
   return x;
 };
 
 int elf_test_var EXCLUSIVE_LOCK_FUNCTION(); // \
-  // expected-warning {{'exclusive_lock_function' attribute only applies to functions}}
+  // expected-warning {{'exclusive_lock_function' attribute on a variable requires the variable to be of function pointer type}}
 
 class ElfFoo {
  private:
   int test_field EXCLUSIVE_LOCK_FUNCTION(); // \
-    // expected-warning {{'exclusive_lock_function' attribute only applies to functions}}
+    // expected-warning {{'exclusive_lock_function' attribute on a field requires the field to be of function pointer type}}
   void test_method() EXCLUSIVE_LOCK_FUNCTION(); // \
     // expected-warning {{'exclusive_lock_function' attribute without capability arguments refers to 'this', but 'ElfFoo' isn't annotated with 'capability' or 'scoped_lockable' attribute}}
 };
 
 class EXCLUSIVE_LOCK_FUNCTION() ElfTestClass { // \
-  // expected-warning {{'exclusive_lock_function' attribute only applies to functions}}
+  // expected-warning {{'exclusive_lock_function' attribute only applies to functions, variables, and non-static data members}}
 };
 
 void elf_fun_params1(MutexLock& scope EXCLUSIVE_LOCK_FUNCTION(mu1));
@@ -690,12 +690,12 @@ int slf_testfn(int y) SHARED_LOCK_FUNCTION(); // expected-warning {{'shared_lock
 
 int slf_testfn(int y) {
   int x SHARED_LOCK_FUNCTION() = y; // \
-    // expected-warning {{'shared_lock_function' attribute only applies to functions}}
+    // expected-warning {{'shared_lock_function' attribute on a variable requires the variable to be of function pointer type}}
   return x;
 };
 
 int slf_test_var SHARED_LOCK_FUNCTION(); // \
-  // expected-warning {{'shared_lock_function' attribute only applies to functions}}
+  // expected-warning {{'shared_lock_function' attribute on a variable requires the variable to be of function pointer type}}
 
 void slf_fun_params1(MutexLock& scope SHARED_LOCK_FUNCTION(mu1));
 void slf_fun_params2(int lvar SHARED_LOCK_FUNCTION(mu1)); // \
@@ -706,13 +706,13 @@ void slf_fun_params3(MutexLock& scope SHARED_LOCK_FUNCTION()); // \
 class SlfFoo {
  private:
   int test_field SHARED_LOCK_FUNCTION(); // \
-    // expected-warning {{'shared_lock_function' attribute only applies to functions}}
+    // expected-warning {{'shared_lock_function' attribute on a field requires the field to be of function pointer type}}
   void test_method() SHARED_LOCK_FUNCTION(); // \
     // expected-warning {{'shared_lock_function' attribute without capability arguments refers to 'this', but 'SlfFoo' isn't annotated with 'capability' or 'scoped_lockable' attribute}}
 };
 
 class SHARED_LOCK_FUNCTION() SlfTestClass { // \
-  // expected-warning {{'shared_lock_function' attribute only applies to functions}}
+  // expected-warning {{'shared_lock_function' attribute only applies to functions, variables, and non-static data members}}
 };
 
 // Check argument parsing.
@@ -790,27 +790,27 @@ int etf_testfn(int y) EXCLUSIVE_TRYLOCK_FUNCTION(1); // \
 
 int etf_testfn(int y) {
   int x EXCLUSIVE_TRYLOCK_FUNCTION(1) = y; // \
-    // expected-warning {{'exclusive_trylock_function' attribute only applies to functions}}
+    // expected-warning {{'exclusive_trylock_function' attribute on a variable requires the variable to be of function pointer type}}
   return x;
 };
 
 int etf_test_var EXCLUSIVE_TRYLOCK_FUNCTION(1); // \
-  // expected-warning {{'exclusive_trylock_function' attribute only applies to functions}}
+  // expected-warning {{'exclusive_trylock_function' attribute on a variable requires the variable to be of function pointer type}}
 
 class EtfFoo {
  private:
   int test_field EXCLUSIVE_TRYLOCK_FUNCTION(1); // \
-    // expected-warning {{'exclusive_trylock_function' attribute only applies to functions}}
+    // expected-warning {{'exclusive_trylock_function' attribute on a field requires the field to be of function pointer type}}
   void test_method() EXCLUSIVE_TRYLOCK_FUNCTION(1); // \
     // expected-warning {{'exclusive_trylock_function' attribute without capability arguments refers to 'this', but 'EtfFoo' isn't annotated with 'capability' or 'scoped_lockable' attribute}}
 };
 
 class EXCLUSIVE_TRYLOCK_FUNCTION(1) EtfTestClass { // \
-  // expected-warning {{'exclusive_trylock_function' attribute only applies to functions}}
+  // expected-warning {{'exclusive_trylock_function' attribute only applies to functions, variables, and non-static data members}}
 };
 
 void etf_fun_params(int lvar EXCLUSIVE_TRYLOCK_FUNCTION(1)); // \
-  // expected-warning {{'exclusive_trylock_function' attribute only applies to functions}}
+  // expected-warning {{'exclusive_trylock_function' attribute on a variable requires the variable to be of function pointer type}}
 
 // Check argument parsing.
 
@@ -885,27 +885,27 @@ int stf_testfn(int y) SHARED_TRYLOCK_FUNCTION(1); // \
 
 int stf_testfn(int y) {
   int x SHARED_TRYLOCK_FUNCTION(1) = y; // \
-    // expected-warning {{'shared_trylock_function' attribute only applies to functions}}
+    // expected-warning {{'shared_trylock_function' attribute on a variable requires the variable to be of function pointer type}}
   return x;
 };
 
 int stf_test_var SHARED_TRYLOCK_FUNCTION(1); // \
-  // expected-warning {{'shared_trylock_function' attribute only applies to functions}}
+  // expected-warning {{'shared_trylock_function' attribute on a variable requires the variable to be of function pointer type}}
 
 void stf_fun_params(int lvar SHARED_TRYLOCK_FUNCTION(1)); // \
-  // expected-warning {{'shared_trylock_function' attribute only applies to functions}}
+  // expected-warning {{'shared_trylock_function' attribute on a variable requires the variable to be of function pointer type}}
 
 
 class StfFoo {
  private:
   int test_field SHARED_TRYLOCK_FUNCTION(1); // \
-    // expected-warning {{'shared_trylock_function' attribute only applies to functions}}
+    // expected-warning {{'shared_trylock_function' attribute on a field requires the field to be of function pointer type}}
   void test_method() SHARED_TRYLOCK_FUNCTION(1); // \
     // expected-warning {{'shared_trylock_function' attribute without capability arguments refers to 'this', but 'StfFoo' isn't annotated with 'capability' or 'scoped_lockable' attribute}}
 };
 
 class SHARED_TRYLOCK_FUNCTION(1) StfTestClass { // \
-    // expected-warning {{'shared_trylock_function' attribute only applies to functions}}
+    // expected-warning {{'shared_trylock_function' attribute only applies to functions, variables, and non-static data members}}
 };
 
 // Check argument parsing.
@@ -978,17 +978,17 @@ int uf_testfn(int y) UNLOCK_FUNCTION(); //\
 
 int uf_testfn(int y) {
   int x UNLOCK_FUNCTION() = y; // \
-    // expected-warning {{'unlock_function' attribute only applies to functions}}
+    // expected-warning {{'unlock_function' attribute on a variable requires the variable to be of function pointer type}}
   return x;
 };
 
 int uf_test_var UNLOCK_FUNCTION(); // \
-  // expected-warning {{'unlock_function' attribute only applies to functions}}
+  // expected-warning {{'unlock_function' attribute on a variable requires the variable to be of function pointer type}}
 
 class UfFoo {
  private:
   int test_field UNLOCK_FUNCTION(); // \
-    // expected-warning {{'unlock_function' attribute only applies to functions}}
+    // expected-warning {{'unlock_function' attribute on a field requires the field to be of function pointer type}}
   void test_method() UNLOCK_FUNCTION(); // \
     // expected-warning {{'unlock_function' attribute without capability arguments refers to 'this', but 'UfFoo' isn't annotated with 'capability' or 'scoped_lockable' attribute}}
 };
@@ -1143,26 +1143,35 @@ int le_testfn(int y) LOCKS_EXCLUDED(mu1);
 
 int le_testfn(int y) {
   int x LOCKS_EXCLUDED(mu1) = y; // \
-    // expected-warning {{'locks_excluded' attribute only applies to functions}}
+    // expected-warning {{'locks_excluded' attribute on a variable requires the variable to be of function pointer type}}
   return x;
 };
 
 int le_test_var LOCKS_EXCLUDED(mu1); // \
-  // expected-warning {{'locks_excluded' attribute only applies to functions}}
+  // expected-warning {{'locks_excluded' attribute on a variable requires the variable to be of function pointer type}}
 
 void le_fun_params1(MutexLock& scope LOCKS_EXCLUDED(mu1));
 void le_fun_params2(int lvar LOCKS_EXCLUDED(mu1)); // \
   // expected-warning{{'locks_excluded' attribute applies to function parameters only if their type is a reference to a 'scoped_lockable'-annotated type}}
 
+template <typename T>
+void le_fun_params3(T& lvar LOCKS_EXCLUDED(mu1)) {} // \
+  // expected-warning{{'locks_excluded' attribute applies to function parameters only if their type is a reference to a 'scoped_lockable'-annotated type}}
+void call_le_fun_params3(int i) {
+  MutexLock scope(&mu1);
+  le_fun_params3(i); // expected-note {{while substituting deduced template arguments into function template 'le_fun_params3' [with T = int]}}
+  le_fun_params3(scope);
+}
+
 class LeFoo {
  private:
   int test_field LOCKS_EXCLUDED(mu1); // \
-    // expected-warning {{'locks_excluded' attribute only applies to functions}}
+    // expected-warning {{'locks_excluded' attribute on a field requires the field to be of function pointer type}}
   void test_method() LOCKS_EXCLUDED(mu1);
 };
 
 class LOCKS_EXCLUDED(mu1) LeTestClass { // \
-  // expected-warning {{'locks_excluded' attribute only applies to functions}}
+  // expected-warning {{'locks_excluded' attribute only applies to functions, variables, and non-static data members}}
 };
 
 // Check argument parsing.
@@ -1228,12 +1237,12 @@ int elr_testfn(int y) EXCLUSIVE_LOCKS_REQUIRED(mu1);
 
 int elr_testfn(int y) {
   int x EXCLUSIVE_LOCKS_REQUIRED(mu1) = y; // \
-    // expected-warning {{'exclusive_locks_required' attribute only applies to functions}}
+    // expected-warning {{'exclusive_locks_required' attribute on a variable requires the variable to be of function pointer type}}
   return x;
 };
 
 int elr_test_var EXCLUSIVE_LOCKS_REQUIRED(mu1); // \
-  // expected-warning {{'exclusive_locks_required' attribute only applies to functions}}
+  // expected-warning {{'exclusive_locks_required' attribute on a variable requires the variable to be of function pointer type}}
 
 void elr_fun_params1(MutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu1));
 void elr_fun_params2(int lvar EXCLUSIVE_LOCKS_REQUIRED(mu1)); // \
@@ -1242,12 +1251,12 @@ void elr_fun_params2(int lvar EXCLUSIVE_LOCKS_REQUIRED(mu1)); // \
 class ElrFoo {
  private:
   int test_field EXCLUSIVE_LOCKS_REQUIRED(mu1); // \
-    // expected-warning {{'exclusive_locks_required' attribute only applies to functions}}
+    // expected-warning {{'exclusive_locks_required' attribute on a field requires the field to be of function pointer type}}
   void test_method() EXCLUSIVE_LOCKS_REQUIRED(mu1);
 };
 
 class EXCLUSIVE_LOCKS_REQUIRED(mu1) ElrTestClass { // \
-  // expected-warning {{'exclusive_locks_required' attribute only applies to functions}}
+  // expected-warning {{'exclusive_locks_required' attribute only applies to functions, variables, and non-static data members}}
 };
 
 // Check argument parsing.
@@ -1315,12 +1324,12 @@ int slr_testfn(int y) SHARED_LOCKS_REQUIRED(mu1);
 
 int slr_testfn(int y) {
   int x SHARED_LOCKS_REQUIRED(mu1) = y; // \
-    // expected-warning {{'shared_locks_required' attribute only applies to functions}}
+    // expected-warning {{'shared_locks_required' attribute on a variable requires the variable to be of function pointer type}}
   return x;
 };
 
 int slr_test_var SHARED_LOCKS_REQUIRED(mu1); // \
-  // expected-warning {{'shared_locks_required' attribute only applies to functions}}
+  // expected-warning {{'shared_locks_required' attribute on a variable requires the variable to be of function pointer type}}
 
 void slr_fun_params1(MutexLock& scope SHARED_LOCKS_REQUIRED(mu1));
 void slr_fun_params2(int lvar SHARED_LOCKS_REQUIRED(mu1)); // \
@@ -1329,12 +1338,12 @@ void slr_fun_params2(int lvar SHARED_LOCKS_REQUIRED(mu1)); // \
 class SlrFoo {
  private:
   int test_field SHARED_LOCKS_REQUIRED(mu1); // \
-    // expected-warning {{'shared_locks_required' attribute only applies to functions}}
+    // expected-warning {{'shared_locks_required' attribute on a field requires the field to be of function pointer type}}
   void test_method() SHARED_LOCKS_REQUIRED(mu1);
 };
 
 class SHARED_LOCKS_REQUIRED(mu1) SlrTestClass { // \
-  // expected-warning {{'shared_locks_required' attribute only applies to functions}}
+  // expected-warning {{'shared_locks_required' attribute only applies to functions, variables, and non-static data members}}
 };
 
 // Check argument parsing.
@@ -1746,3 +1755,65 @@ namespace CRASH_POST_R301735 {
    };
 }
 #endif
+
+//-----------------------------------------//
+//  Function pointer attributes
+//-----------------------------------------//
+
+namespace FunctionPointers {
+
+void (*fp_lock)(void) EXCLUSIVE_LOCK_FUNCTION(mu1);
+void (*fp_unlock)(void) UNLOCK_FUNCTION(mu1);
+void (*fp_requires)(void) EXCLUSIVE_LOCKS_REQUIRED(mu1);
+void (*fp_excludes)(void) LOCKS_EXCLUDED(mu1);
+bool (*fp_trylock)(void) EXCLUSIVE_TRYLOCK_FUNCTION(true, mu1);
+void (*fp_assert)(void) ASSERT_EXCLUSIVE_LOCK(mu1);
+void (*fp_shared_lock)(void) SHARED_LOCK_FUNCTION(mu1);
+void (*fp_shared_require)(void) SHARED_LOCKS_REQUIRED(mu1);
+void (*fp_shared_trylock)(void) SHARED_TRYLOCK_FUNCTION(true, mu1);
+
+struct FPFields {
+  void (*lock)(void) EXCLUSIVE_LOCK_FUNCTION(mu1);
+  void (*unlock)(void) UNLOCK_FUNCTION(mu1);
+  void (*requires_mu)(void) EXCLUSIVE_LOCKS_REQUIRED(mu1);
+};
+
+// Function pointer parameters and references-to-function-pointer.
+void fp_param(void (*pf)(void) EXCLUSIVE_LOCK_FUNCTION(mu1));
+void fp_param_assert(void (*pf)(void) ASSERT_EXCLUSIVE_LOCK(mu1));
+void fp_param_try(bool (*pf)(void) EXCLUSIVE_TRYLOCK_FUNCTION(true, mu1));
+void fp_ref(void (*&rf)(void) EXCLUSIVE_LOCKS_REQUIRED(mu1));
+
+int bad_fp_var EXCLUSIVE_LOCK_FUNCTION(mu1); // \
+  // expected-warning {{'exclusive_lock_function' attribute on a variable requires the variable to be of function pointer type}}
+struct BadFPFields {
+  int bad_field EXCLUSIVE_LOCKS_REQUIRED(mu1); // \
+    // expected-warning {{'exclusive_locks_required' attribute on a field requires the field to be of function pointer type}}
+};
+
+// Compound types (array of, pointer/reference to array of function pointers)
+// are not analyzed; a plain function pointer is required.
+void (*fp_array[4])(void) EXCLUSIVE_LOCK_FUNCTION(mu1); // \
+  // expected-warning {{'exclusive_lock_function' attribute on a variable requires the variable to be of function pointer type}}
+void (*(*fp_ptr_to_array)[4])(void) EXCLUSIVE_LOCK_FUNCTION(mu1); // \
+  // expected-warning {{'exclusive_lock_function' attribute on a variable requires the variable to be of function pointer type}}
+void (*(&fp_ref_to_array)[4])(void) EXCLUSIVE_LOCK_FUNCTION(mu1) = fp_array; // \
+  // expected-warning {{'exclusive_lock_function' attribute on a variable requires the variable to be of function pointer type}}
+
+// C++11 spelling at the declaration prefix so attribute applies to variable.
+[[clang::acquire_capability(mu1)]] void (*fp_cxx11)(void);
+[[clang::requires_capability(mu1)]] void (*fp_cxx11_req)(void);
+
+template <typename FuncPtr>
+struct DependentFPFields {
+  FuncPtr lock EXCLUSIVE_LOCK_FUNCTION(mu1); // \
+    // expected-warning {{'exclusive_lock_function' attribute on a field requires the field to be of function pointer type}}
+  FuncPtr requires_mu EXCLUSIVE_LOCKS_REQUIRED(mu1); // \
+    // expected-warning {{'exclusive_locks_required' attribute on a field requires the field to be of function pointer type}}
+};
+
+typedef void (*GoodLockFn)(void);
+DependentFPFields<GoodLockFn> dependent_fp_fields_ok;
+DependentFPFields<int> dependent_fp_fields_bad; // expected-note {{in instantiation of template class 'FunctionPointers::DependentFPFields<int>' requested here}}
+
+}  // namespace FunctionPointers

diff  --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index d709445b59f2a..ab7dab77ff6dc 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -2008,6 +2008,23 @@ static void emitClangAttrLateParsedExperimentalList(const RecordKeeper &Records,
   OS << "#endif // CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_EXT_LIST\n\n";
 }
 
+// Emits a list of attributes whose argument list is parsed inside a function
+// prototype scope so it can refer to the enclosing function's parameters.
+static void
+emitClangAttrParseArgsInFunctionScopeList(const RecordKeeper &Records,
+                                          raw_ostream &OS) {
+  OS << "#if defined(CLANG_ATTR_PARSE_ARGS_IN_FUNCTION_SCOPE_LIST)\n";
+  for (const auto *Attr : Records.getAllDerivedDefinitions("Attr")) {
+    if (!Attr->getValueAsBit("ParseArgsInFunctionScope"))
+      continue;
+    // FIXME: Handle non-GNU attributes
+    for (const auto &I : GetFlattenedSpellings(*Attr))
+      if (I.variety() == "GNU")
+        OS << ".Case(\"" << I.name() << "\", 1)\n";
+  }
+  OS << "#endif // CLANG_ATTR_PARSE_ARGS_IN_FUNCTION_SCOPE_LIST\n\n";
+}
+
 static bool hasGNUorCXX11Spelling(const Record &Attribute) {
   std::vector<FlattenedSpelling> Spellings = GetFlattenedSpellings(Attribute);
   for (const auto &I : Spellings) {
@@ -5268,6 +5285,7 @@ void EmitClangAttrParserStringSwitches(const RecordKeeper &Records,
   emitClangAttrTypeArgList(Records, OS);
   emitClangAttrLateParsedList(Records, OS);
   emitClangAttrLateParsedExperimentalList(Records, OS);
+  emitClangAttrParseArgsInFunctionScopeList(Records, OS);
   emitClangAttrStrictIdentifierArgList(Records, OS);
 }
 


        


More information about the cfe-commits mailing list