[clang] Thread Safety Analysis: Support attributes on function pointers (PR #191187)
Marco Elver via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 29 06:20:46 PDT 2026
https://github.com/melver updated https://github.com/llvm/llvm-project/pull/191187
>From b9607bf5f909aa41fe22da8fc742f76c8839ac0f Mon Sep 17 00:00:00 2001
From: Marco Elver <elver at google.com>
Date: Thu, 9 Apr 2026 14:55:29 +0200
Subject: [PATCH 1/7] Thread Safety Analysis: Support attributes on function
pointers
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/
---
clang/docs/ReleaseNotes.rst | 6 +
clang/docs/ThreadSafetyAnalysis.rst | 32 +++++
clang/include/clang/Basic/Attr.td | 12 +-
.../clang/Basic/DiagnosticSemaKinds.td | 3 +
clang/include/clang/Sema/Sema.h | 2 +
clang/lib/Analysis/ThreadSafetyCommon.cpp | 11 +-
clang/lib/Sema/SemaDeclAttr.cpp | 60 ++++++--
clang/lib/Sema/SemaTemplateInstantiate.cpp | 2 +-
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 6 +-
clang/test/Sema/attr-capabilities.c | 6 +-
clang/test/Sema/warn-thread-safety-analysis.c | 21 +++
.../SemaCXX/warn-thread-safety-analysis.cpp | 129 ++++++++++++++++++
.../SemaCXX/warn-thread-safety-parsing.cpp | 109 ++++++++++-----
13 files changed, 343 insertions(+), 56 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 2112e6ffb8bc7..31756c363c63e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -300,6 +300,12 @@ 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.
+
- 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..a5908050b2281 100644
--- a/clang/docs/ThreadSafetyAnalysis.rst
+++ b/clang/docs/ThreadSafetyAnalysis.rst
@@ -544,6 +544,38 @@ 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 different (or no) attributes to an
+annotated function pointer variable is not diagnosed. The analysis trusts the
+annotations on the variable at the call site.
+
+
Warning flags
-------------
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 97536ac7a1966..70fb41a5d11c9 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4096,7 +4096,7 @@ def AssertCapability : InheritableAttr {
Clang<"assert_shared_capability", 0>,
GNU<"assert_exclusive_lock">,
GNU<"assert_shared_lock">];
- let Subjects = SubjectList<[Function]>;
+ let Subjects = SubjectList<[Function, NonParmVar, Field]>;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
@@ -4114,7 +4114,7 @@ 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, ParmVar, NonParmVar, Field]>;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
@@ -4132,7 +4132,7 @@ 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, NonParmVar, Field]>;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
@@ -4150,7 +4150,7 @@ 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, ParmVar, NonParmVar, Field]>;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
@@ -4176,7 +4176,7 @@ def RequiresCapability : InheritableAttr {
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
- let Subjects = SubjectList<[Function, ParmVar]>;
+ let Subjects = SubjectList<[Function, ParmVar, NonParmVar, Field]>;
let Accessors = [Accessor<"isShared", [Clang<"requires_shared_capability", 0>,
Clang<"shared_locks_required", 0>]>];
let Documentation = [Undocumented];
@@ -4253,7 +4253,7 @@ def LocksExcluded : InheritableAttr {
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
- let Subjects = SubjectList<[Function, ParmVar]>;
+ let Subjects = SubjectList<[Function, ParmVar, NonParmVar, Field]>;
let Documentation = [Undocumented];
}
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index dc86a0b58d8f9..80103fb3c1097 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 20f7d84cfc475..9f65425a607af 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14246,6 +14246,8 @@ class Sema final : public SemaBase {
};
typedef SmallVector<LateInstantiatedAttribute, 1> LateInstantiatedAttrVec;
+ bool checkDependentThreadSafetyAttrs(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..fd125ee56f136 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.
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index e47a30193567f..76e60812cce1c 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -436,6 +436,18 @@ static void checkAttrArgsAreCapabilityObjs(Sema &S, Decl *D,
}
}
+/// 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 (VD->getType()->isDependentType() ||
+ VD->getType()->isFunctionPointerType())
+ 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) {
@@ -449,6 +461,35 @@ 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 *ParmDecl = dyn_cast<ParmVarDecl>(VD))
+ return checkFunParamsAreScopedLockable(S, ParmDecl, AL);
+ }
+
+ return checkThreadSafetyValueDeclIsFunPtr(S, VD, AL);
+}
+
+/// Recheck instantiated thread-safety attributes that could not be validated
+/// on the dependent pattern declaration.
+bool Sema::checkDependentThreadSafetyAttrs(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 || isa<FunctionDecl, ParmVarDecl>(VD))
+ return true;
+
+ return checkThreadSafetyValueDeclIsFunPtr(*this, VD, *A);
+}
+
//===----------------------------------------------------------------------===//
// Attribute Implementations
//===----------------------------------------------------------------------===//
@@ -630,8 +671,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, true))
return;
if (!AL.checkAtLeastNumArgs(S, 1))
@@ -6673,6 +6713,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;
@@ -6683,8 +6726,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, true))
return;
SmallVector<Expr*, 1> Args;
@@ -6697,6 +6739,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;
@@ -6707,9 +6752,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, true))
return;
+
// Check that all arguments are lockable objects.
SmallVector<Expr *, 1> Args;
checkAttrArgsAreCapabilityObjs(S, D, AL, Args, 0, true);
@@ -6720,8 +6765,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, true))
return;
if (!AL.checkAtLeastNumArgs(S, 1))
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 59d4fde6ada64..58ef375cc59c4 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3699,7 +3699,7 @@ bool Sema::InstantiateClassImpl(
Attr *NewAttr =
instantiateTemplateAttribute(I->TmplAttr, Context, *this, TemplateArgs);
- if (NewAttr)
+ if (NewAttr && checkDependentThreadSafetyAttrs(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 81f730cec4683..2bc6155c37278 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) &&
+ checkDependentThreadSafetyAttrs(New, NewAttr))
New->addAttr(NewAttr);
}
}
@@ -1060,7 +1061,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) &&
+ checkDependentThreadSafetyAttrs(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..533009c47ab5d 100644
--- a/clang/test/Sema/warn-thread-safety-analysis.c
+++ b/clang/test/Sema/warn-thread-safety-analysis.c
@@ -282,6 +282,27 @@ 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}}
+}
+
// 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..110d304502045 100644
--- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
@@ -7885,3 +7885,132 @@ 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();
+}
+
+} // namespace FunctionPointers
diff --git a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
index 862b2d58238c9..ef447fb2d0117 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, parameters, 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, parameters, 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 only applies to functions, variables, and non-static data members}}
// 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 only applies to functions, variables, and non-static data members}}
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,12 +1143,12 @@ 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)); // \
@@ -1157,12 +1157,12 @@ void le_fun_params2(int lvar LOCKS_EXCLUDED(mu1)); // \
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, parameters, variables, and non-static data members}}
};
// Check argument parsing.
@@ -1228,12 +1228,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 +1242,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, parameters, variables, and non-static data members}}
};
// Check argument parsing.
@@ -1315,12 +1315,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 +1329,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, parameters, variables, and non-static data members}}
};
// Check argument parsing.
@@ -1746,3 +1746,46 @@ 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);
+};
+
+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}}
+};
+
+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
>From 3d6688d5739db37a339b9f2177ebbd4c321f2f90 Mon Sep 17 00:00:00 2001
From: Marco Elver <elver at google.com>
Date: Sat, 11 Apr 2026 00:22:38 +0200
Subject: [PATCH 2/7] fixup! Thread Safety Analysis: Support attributes on
function pointers
Fix for attributes on function pointers that refer back to the function
pointer's parameters.
Previously, TSA attributes failed to refer to function pointer parameters
because the parser only searched for function parameters in the outermost
declarator chunk, which for a function pointer is a Pointer rather than a
Function chunk. Furthermore, name lookup for these parameters within C structs
would fail as the temporary prototype scope did not inherit the surrounding
class scope.
Fix it by:
1. Updating Parser::ParseGNUAttributeArgs to search for the innermost
function type info, ensuring parameters are correctly injected into
the scope for late-parsed standard attributes.
2. Ensure PrototypeScope inherits ClassScope flags to maintain
visibility of struct members during attribute parsing in C.
3. Unwrap pointer and reference types when resolving parameter
references during analysis.
---
clang/lib/Analysis/ThreadSafetyCommon.cpp | 40 ++++++++++++++++---
clang/lib/Parse/ParseDecl.cpp | 31 +++++++++-----
clang/test/Sema/warn-thread-safety-analysis.c | 21 ++++++++++
3 files changed, 77 insertions(+), 15 deletions(-)
diff --git a/clang/lib/Analysis/ThreadSafetyCommon.cpp b/clang/lib/Analysis/ThreadSafetyCommon.cpp
index fd125ee56f136..054f86bd44df0 100644
--- a/clang/lib/Analysis/ThreadSafetyCommon.cpp
+++ b/clang/lib/Analysis/ThreadSafetyCommon.cpp
@@ -379,6 +379,29 @@ 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();
+ 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());
@@ -389,9 +412,15 @@ 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;
+
+ if (Match) {
// Substitute call arguments for references to function parameters
if (const Expr *const *FunArgs =
dyn_cast<const Expr *const *>(Ctx->FunArgs)) {
@@ -405,9 +434,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 4f37e1471c29e..208c28e8d19ff 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -674,15 +674,28 @@ 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 && (normalizeAttrName(AttrName->getName()) == "enable_if" ||
+ IsAttributeLateParsedStandard(*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.
+ for (unsigned i = 0; i < D->getNumTypeObjects(); ++i) {
+ if (D->getTypeObject(i).Kind == DeclaratorChunk::Function) {
+ const DeclaratorChunk::FunctionTypeInfo &FTI = D->getTypeObject(i).Fun;
+ // 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 j = 0; j < FTI.NumParams; ++j)
+ Actions.ActOnReenterCXXMethodParameter(
+ getCurScope(),
+ dyn_cast_or_null<ParmVarDecl>(FTI.Params[j].Param));
+ break;
+ }
+ }
}
ParseAttributeArgsCommon(AttrName, AttrNameLoc, Attrs, EndLoc, ScopeName,
diff --git a/clang/test/Sema/warn-thread-safety-analysis.c b/clang/test/Sema/warn-thread-safety-analysis.c
index 533009c47ab5d..c88f13ea1d3c8 100644
--- a/clang/test/Sema/warn-thread-safety-analysis.c
+++ b/clang/test/Sema/warn-thread-safety-analysis.c
@@ -303,6 +303,27 @@ void test_fp_ops_fail(struct FPOps *ops) {
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}}
>From 09acda32155e8106a63d5b61d1d3fc9918cd5b92 Mon Sep 17 00:00:00 2001
From: Marco Elver <elver at google.com>
Date: Tue, 14 Apr 2026 15:50:03 +0200
Subject: [PATCH 3/7] fixup! Thread Safety Analysis: Support attributes on
function pointers
---
clang/include/clang/Sema/Sema.h | 4 +++-
clang/lib/Sema/SemaDeclAttr.cpp | 12 +++++-------
clang/lib/Sema/SemaTemplateInstantiate.cpp | 2 +-
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp | 4 ++--
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 9f65425a607af..a094d8eeee8b4 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14246,7 +14246,9 @@ class Sema final : public SemaBase {
};
typedef SmallVector<LateInstantiatedAttribute, 1> LateInstantiatedAttrVec;
- bool checkDependentThreadSafetyAttrs(Decl *D, const Attr *A);
+ /// Recheck instantiated thread-safety attributes that could not be validated
+ /// on the dependent pattern declaration.
+ bool checkInstantiatedThreadSafetyAttrs(Decl *D, const Attr *A);
void InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
const Decl *Pattern, Decl *Inst,
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 76e60812cce1c..c20d4f30c3b63 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -475,9 +475,7 @@ static bool checkThreadSafetyAttrSubject(Sema &S, Decl *D, const ParsedAttr &AL,
return checkThreadSafetyValueDeclIsFunPtr(S, VD, AL);
}
-/// Recheck instantiated thread-safety attributes that could not be validated
-/// on the dependent pattern declaration.
-bool Sema::checkDependentThreadSafetyAttrs(Decl *D, const Attr *A) {
+bool Sema::checkInstantiatedThreadSafetyAttrs(Decl *D, const Attr *A) {
if (!isa<AssertCapabilityAttr, AcquireCapabilityAttr,
TryAcquireCapabilityAttr, ReleaseCapabilityAttr,
RequiresCapabilityAttr, LocksExcludedAttr>(A))
@@ -671,7 +669,7 @@ static void handleLockReturnedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
}
static void handleLocksExcludedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
- if (!checkThreadSafetyAttrSubject(S, D, AL, true))
+ if (!checkThreadSafetyAttrSubject(S, D, AL, /*CheckParmVar=*/true))
return;
if (!AL.checkAtLeastNumArgs(S, 1))
@@ -6726,7 +6724,7 @@ static void handleAssertCapabilityAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
static void handleAcquireCapabilityAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
- if (!checkThreadSafetyAttrSubject(S, D, AL, true))
+ if (!checkThreadSafetyAttrSubject(S, D, AL, /*CheckParmVar=*/true))
return;
SmallVector<Expr*, 1> Args;
@@ -6752,7 +6750,7 @@ static void handleTryAcquireCapabilityAttr(Sema &S, Decl *D,
static void handleReleaseCapabilityAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
- if (!checkThreadSafetyAttrSubject(S, D, AL, true))
+ if (!checkThreadSafetyAttrSubject(S, D, AL, /*CheckParmVar=*/true))
return;
// Check that all arguments are lockable objects.
@@ -6765,7 +6763,7 @@ static void handleReleaseCapabilityAttr(Sema &S, Decl *D,
static void handleRequiresCapabilityAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
- if (!checkThreadSafetyAttrSubject(S, D, AL, true))
+ 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 58ef375cc59c4..5213fbcd67d19 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3699,7 +3699,7 @@ bool Sema::InstantiateClassImpl(
Attr *NewAttr =
instantiateTemplateAttribute(I->TmplAttr, Context, *this, TemplateArgs);
- if (NewAttr && checkDependentThreadSafetyAttrs(I->NewDecl, 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 2bc6155c37278..89b7b4479549c 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -839,7 +839,7 @@ void Sema::InstantiateAttrsForDecl(
Attr *NewAttr = sema::instantiateTemplateAttributeForDecl(
TmplAttr, Context, *this, TemplateArgs);
if (NewAttr && isRelevantAttr(*this, New, NewAttr) &&
- checkDependentThreadSafetyAttrs(New, NewAttr))
+ checkInstantiatedThreadSafetyAttrs(New, NewAttr))
New->addAttr(NewAttr);
}
}
@@ -1062,7 +1062,7 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
Attr *NewAttr = sema::instantiateTemplateAttribute(TmplAttr, Context,
*this, TemplateArgs);
if (NewAttr && isRelevantAttr(*this, New, TmplAttr) &&
- checkDependentThreadSafetyAttrs(New, NewAttr))
+ checkInstantiatedThreadSafetyAttrs(New, NewAttr))
New->addAttr(NewAttr);
}
}
>From a8cd767ed6f7e949348d820edad032b327605bd2 Mon Sep 17 00:00:00 2001
From: Marco Elver <elver at google.com>
Date: Tue, 14 Apr 2026 16:37:17 +0200
Subject: [PATCH 4/7] fixup! Thread Safety Analysis: Support attributes on
function pointers
---
clang/lib/Sema/SemaDeclAttr.cpp | 14 ++++++++++++--
clang/test/SemaCXX/warn-thread-safety-parsing.cpp | 9 +++++++++
2 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index c20d4f30c3b63..d4194a1636ede 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -450,8 +450,10 @@ static bool checkThreadSafetyValueDeclIsFunPtr(Sema &S, const ValueDecl *VD,
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()))
@@ -482,7 +484,15 @@ bool Sema::checkInstantiatedThreadSafetyAttrs(Decl *D, const Attr *A) {
return true;
const auto *VD = dyn_cast<ValueDecl>(D);
- if (!VD || isa<FunctionDecl, ParmVarDecl>(VD))
+ 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))
+ return checkFunParamsAreScopedLockable(*this, PVD, *A);
+
+ if (isa<FunctionDecl>(VD))
return true;
return checkThreadSafetyValueDeclIsFunPtr(*this, VD, *A);
diff --git a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
index ef447fb2d0117..7c36dabe95353 100644
--- a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
@@ -1154,6 +1154,15 @@ 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); // \
>From 930739ac79711f03bbea40c26ee0f9864464d5be Mon Sep 17 00:00:00 2001
From: Marco Elver <elver at google.com>
Date: Mon, 20 Apr 2026 13:31:27 +0200
Subject: [PATCH 5/7] fixup! Thread Safety Analysis: Support attributes on
function pointers
---
clang/include/clang/Basic/Attr.td | 8 ++++----
clang/test/SemaCXX/warn-thread-safety-parsing.cpp | 10 +++++-----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 70fb41a5d11c9..0ae727b1c88dd 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4114,7 +4114,7 @@ def AcquireCapability : InheritableAttr {
Clang<"acquire_shared_capability", 0>,
GNU<"exclusive_lock_function">,
GNU<"shared_lock_function">];
- let Subjects = SubjectList<[Function, ParmVar, NonParmVar, Field]>;
+ let Subjects = SubjectList<[Function, Var, Field]>;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
@@ -4150,7 +4150,7 @@ def ReleaseCapability : InheritableAttr {
Clang<"release_shared_capability", 0>,
Clang<"release_generic_capability", 0>,
Clang<"unlock_function", 0>];
- let Subjects = SubjectList<[Function, ParmVar, NonParmVar, Field]>;
+ let Subjects = SubjectList<[Function, Var, Field]>;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
@@ -4176,7 +4176,7 @@ def RequiresCapability : InheritableAttr {
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
- let Subjects = SubjectList<[Function, ParmVar, NonParmVar, Field]>;
+ 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 +4253,7 @@ def LocksExcluded : InheritableAttr {
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
- let Subjects = SubjectList<[Function, ParmVar, NonParmVar, Field]>;
+ let Subjects = SubjectList<[Function, Var, Field]>;
let Documentation = [Undocumented];
}
diff --git a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
index 7c36dabe95353..e68c612fde285 100644
--- a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
@@ -612,7 +612,7 @@ class ElfFoo {
};
class EXCLUSIVE_LOCK_FUNCTION() ElfTestClass { // \
- // expected-warning {{'exclusive_lock_function' attribute only applies to functions, parameters, variables, and non-static data members}}
+ // 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));
@@ -712,7 +712,7 @@ class SlfFoo {
};
class SHARED_LOCK_FUNCTION() SlfTestClass { // \
- // expected-warning {{'shared_lock_function' attribute only applies to functions, parameters, variables, and non-static data members}}
+ // expected-warning {{'shared_lock_function' attribute only applies to functions, variables, and non-static data members}}
};
// Check argument parsing.
@@ -1171,7 +1171,7 @@ class LeFoo {
};
class LOCKS_EXCLUDED(mu1) LeTestClass { // \
- // expected-warning {{'locks_excluded' attribute only applies to functions, parameters, variables, and non-static data members}}
+ // expected-warning {{'locks_excluded' attribute only applies to functions, variables, and non-static data members}}
};
// Check argument parsing.
@@ -1256,7 +1256,7 @@ class ElrFoo {
};
class EXCLUSIVE_LOCKS_REQUIRED(mu1) ElrTestClass { // \
- // expected-warning {{'exclusive_locks_required' attribute only applies to functions, parameters, variables, and non-static data members}}
+ // expected-warning {{'exclusive_locks_required' attribute only applies to functions, variables, and non-static data members}}
};
// Check argument parsing.
@@ -1343,7 +1343,7 @@ class SlrFoo {
};
class SHARED_LOCKS_REQUIRED(mu1) SlrTestClass { // \
- // expected-warning {{'shared_locks_required' attribute only applies to functions, parameters, variables, and non-static data members}}
+ // expected-warning {{'shared_locks_required' attribute only applies to functions, variables, and non-static data members}}
};
// Check argument parsing.
>From 1ff1853fb2e363ded92acc7ec5e3bfab192a7264 Mon Sep 17 00:00:00 2001
From: Marco Elver <elver at google.com>
Date: Mon, 20 Apr 2026 16:31:17 +0200
Subject: [PATCH 6/7] fixup! Thread Safety Analysis: Support attributes on
function pointers
---
clang/lib/Analysis/ThreadSafetyCommon.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/clang/lib/Analysis/ThreadSafetyCommon.cpp b/clang/lib/Analysis/ThreadSafetyCommon.cpp
index 054f86bd44df0..bf0f2652b745a 100644
--- a/clang/lib/Analysis/ThreadSafetyCommon.cpp
+++ b/clang/lib/Analysis/ThreadSafetyCommon.cpp
@@ -392,6 +392,7 @@ static const ParmVarDecl *getCanonicalParamDecl(const Decl *D, unsigned I) {
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>())
@@ -419,6 +420,8 @@ til::SExpr *SExprBuilder::translateDeclRefExpr(const DeclRefExpr *DRE,
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
>From b824c6c2dfa32620f2792e9abd9694d141241091 Mon Sep 17 00:00:00 2001
From: Marco Elver <elver at google.com>
Date: Wed, 29 Apr 2026 15:15:57 +0200
Subject: [PATCH 7/7] fixup! Thread Safety Analysis: Support attributes on
function pointers
---
clang/docs/ReleaseNotes.rst | 2 ++
clang/docs/ThreadSafetyAnalysis.rst | 4 +++
clang/include/clang/Basic/Attr.td | 14 +++++++++--
clang/include/clang/Sema/Sema.h | 2 +-
clang/lib/Parse/ParseDecl.cpp | 13 ++++++++--
clang/lib/Sema/SemaDeclAttr.cpp | 25 ++++++++++++++-----
.../SemaCXX/warn-thread-safety-analysis.cpp | 11 ++++++++
.../SemaCXX/warn-thread-safety-parsing.cpp | 23 +++++++++++++++--
clang/utils/TableGen/ClangAttrEmitter.cpp | 18 +++++++++++++
9 files changed, 99 insertions(+), 13 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 31756c363c63e..bc06cd95edbe0 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -305,6 +305,8 @@ Attribute Changes in Clang
``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 a5908050b2281..fee1c2b778c01 100644
--- a/clang/docs/ThreadSafetyAnalysis.rst
+++ b/clang/docs/ThreadSafetyAnalysis.rst
@@ -575,6 +575,10 @@ pointer type. Assigning a function with different (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 0ae727b1c88dd..159bb32b76373 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, NonParmVar, Field]>;
+ 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",
@@ -4119,6 +4124,7 @@ def AcquireCapability : InheritableAttr {
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, NonParmVar, Field]>;
+ 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",
@@ -4155,6 +4162,7 @@ def ReleaseCapability : InheritableAttr {
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
+ let ParseArgsInFunctionScope = 1;
let Args = [VariadicExprArgument<"Args">];
let AcceptsExprPack = 1;
let Accessors = [Accessor<"isShared",
@@ -4176,6 +4184,7 @@ def RequiresCapability : InheritableAttr {
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
+ let ParseArgsInFunctionScope = 1;
let Subjects = SubjectList<[Function, Var, Field]>;
let Accessors = [Accessor<"isShared", [Clang<"requires_shared_capability", 0>,
Clang<"shared_locks_required", 0>]>];
@@ -4253,6 +4262,7 @@ def LocksExcluded : InheritableAttr {
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
+ let ParseArgsInFunctionScope = 1;
let Subjects = SubjectList<[Function, Var, Field]>;
let Documentation = [Undocumented];
}
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a094d8eeee8b4..adb038473d735 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14248,7 +14248,7 @@ class Sema final : public SemaBase {
/// Recheck instantiated thread-safety attributes that could not be validated
/// on the dependent pattern declaration.
- bool checkInstantiatedThreadSafetyAttrs(Decl *D, const Attr *A);
+ bool checkInstantiatedThreadSafetyAttrs(const Decl *D, const Attr *A);
void InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
const Decl *Pattern, Decl *Inst,
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 208c28e8d19ff..0306bf9930d17 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,8 +684,7 @@ 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 (D && (normalizeAttrName(AttrName->getName()) == "enable_if" ||
- IsAttributeLateParsedStandard(*AttrName))) {
+ 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.
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index d4194a1636ede..d0b63aabc5cc4 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -436,12 +436,18 @@ 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 (VD->getType()->isDependentType() ||
- VD->getType()->isFunctionPointerType())
+ if (isFunctionPointerOrDependent(VD->getType()))
return true;
S.Diag(A.getLoc(), diag::warn_thread_attribute_not_on_fun_ptr)
<< A << (isa<FieldDecl>(VD) ? 1 : 0);
@@ -470,14 +476,18 @@ static bool checkThreadSafetyAttrSubject(Sema &S, Decl *D, const ParsedAttr &AL,
return true;
if (CheckParmVar) {
- if (const auto *ParmDecl = dyn_cast<ParmVarDecl>(VD))
- return checkFunParamsAreScopedLockable(S, ParmDecl, AL);
+ 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(Decl *D, const Attr *A) {
+bool Sema::checkInstantiatedThreadSafetyAttrs(const Decl *D, const Attr *A) {
if (!isa<AssertCapabilityAttr, AcquireCapabilityAttr,
TryAcquireCapabilityAttr, ReleaseCapabilityAttr,
RequiresCapabilityAttr, LocksExcludedAttr>(A))
@@ -489,8 +499,11 @@ bool Sema::checkInstantiatedThreadSafetyAttrs(Decl *D, const Attr *A) {
// 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 (const auto *PVD = dyn_cast<ParmVarDecl>(VD)) {
+ if (isFunctionPointerOrDependent(PVD->getType()))
+ return true;
return checkFunParamsAreScopedLockable(*this, PVD, *A);
+ }
if (isa<FunctionDecl>(VD))
return true;
diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
index 110d304502045..78c503cba0ed6 100644
--- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
@@ -8013,4 +8013,15 @@ void testReassignIncompatible() {
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 e68c612fde285..368c20cb45209 100644
--- a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
@@ -810,7 +810,7 @@ class EXCLUSIVE_TRYLOCK_FUNCTION(1) EtfTestClass { // \
};
void etf_fun_params(int lvar EXCLUSIVE_TRYLOCK_FUNCTION(1)); // \
- // expected-warning {{'exclusive_trylock_function' attribute only applies to functions, variables, and non-static data members}}
+ // expected-warning {{'exclusive_trylock_function' attribute on a variable requires the variable to be of function pointer type}}
// Check argument parsing.
@@ -893,7 +893,7 @@ int stf_test_var SHARED_TRYLOCK_FUNCTION(1); // \
// 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, variables, and non-static data members}}
+ // expected-warning {{'shared_trylock_function' attribute on a variable requires the variable to be of function pointer type}}
class StfFoo {
@@ -1778,6 +1778,12 @@ struct FPFields {
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 {
@@ -1785,6 +1791,19 @@ struct BadFPFields {
// 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); // \
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