[clang] [clang][TSA] Make RequiresCapability a DeclOrType attribute (PR #67095)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 19 03:12:11 PST 2025
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/67095
>From 354359e251ddb5cc4e77e7f78bfc6917d26f5f46 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Fri, 22 Sep 2023 08:42:05 +0200
Subject: [PATCH] [clang][TSA] Make RequiresCapability a DeclOrType attribute
---
clang/include/clang/Basic/Attr.td | 12 +++---
clang/include/clang/Basic/AttrDocs.td | 7 +++
clang/include/clang/Sema/Sema.h | 6 +++
clang/lib/AST/TypePrinter.cpp | 3 ++
clang/lib/Sema/SemaDeclAttr.cpp | 43 +++++++++----------
clang/lib/Sema/SemaType.cpp | 25 +++++++++++
clang/test/Sema/warn-thread-safety-analysis.c | 10 +++++
.../SemaCXX/warn-thread-safety-parsing.cpp | 2 +
8 files changed, 79 insertions(+), 29 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 65c91ccd75ecc..e888a6c5ffbf5 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3851,7 +3851,7 @@ def ReleaseCapability : InheritableAttr {
let Documentation = [ReleaseCapabilityDocs];
}
-def RequiresCapability : InheritableAttr {
+def RequiresCapability : DeclOrTypeAttr {
let Spellings = [Clang<"requires_capability", 0>,
Clang<"exclusive_locks_required", 0>,
Clang<"requires_shared_capability", 0>,
@@ -3861,16 +3861,16 @@ def RequiresCapability : InheritableAttr {
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
- let Subjects = SubjectList<[Function, ParmVar]>;
+ let Subjects = SubjectList<[Function, FunctionPointer, ParmVar]>;
let Accessors = [Accessor<"isShared", [Clang<"requires_shared_capability", 0>,
Clang<"shared_locks_required", 0>]>];
- let Documentation = [Undocumented];
+ let Documentation = [ThreadSafetyDocs];
}
def NoThreadSafetyAnalysis : InheritableAttr {
let Spellings = [Clang<"no_thread_safety_analysis">];
let Subjects = SubjectList<[Function]>;
- let Documentation = [Undocumented];
+ let Documentation = [ThreadSafetyDocs];
let SimpleHandler = 1;
}
@@ -3882,7 +3882,7 @@ def GuardedBy : InheritableAttr {
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
let Subjects = SubjectList<[Field, SharedVar]>;
- let Documentation = [Undocumented];
+ let Documentation = [ThreadSafetyDocs];
}
def PtGuardedBy : InheritableAttr {
@@ -3893,7 +3893,7 @@ def PtGuardedBy : InheritableAttr {
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
let Subjects = SubjectList<[Field, SharedVar]>;
- let Documentation = [Undocumented];
+ let Documentation = [ThreadSafetyDocs];
}
def AcquiredAfter : InheritableAttr {
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index f5362b4d59142..18720c0e8aa30 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9037,3 +9037,10 @@ Declares that a function potentially allocates heap memory, and prevents any pot
of ``nonallocating`` by the compiler.
}];
}
+
+def ThreadSafetyDocs : Documentation {
+ let Category = DocCatFunction;
+ let Content = [{
+Part of Thread Safety Analysis (TSA) in Clang, as documented at https://clang.llvm.org/docs/ThreadSafetyAnalysis.html.
+ }];
+}
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index c55b964650323..ae623957896ac 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -13895,6 +13895,12 @@ class Sema final : public SemaBase {
LocalInstantiationScope &Scope,
const MultiLevelTemplateArgumentList &TemplateArgs);
+public:
+ void checkAttrArgsAreCapabilityObjs(Decl *D, const ParsedAttr &AL,
+ SmallVectorImpl<Expr *> &Args,
+ unsigned Sidx = 0,
+ bool ParamIdxOk = false);
+
int ParsingClassDepth = 0;
class SavePendingParsedClassStateRAII {
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 31695374cb52b..1f40f5d4e7096 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -2073,6 +2073,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::ArmMveStrictPolymorphism:
OS << "__clang_arm_mve_strict_polymorphism";
break;
+ case attr::RequiresCapability:
+ OS << "requires_capability(...)";
+ break;
}
OS << "))";
}
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 620290af9509f..a20b300e8c04b 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -337,12 +337,10 @@ static bool isCapabilityExpr(Sema &S, const Expr *Ex) {
/// \param Sidx The attribute argument index to start checking with.
/// \param ParamIdxOk Whether an argument can be indexing into a function
/// parameter list.
-static void checkAttrArgsAreCapabilityObjs(Sema &S, Decl *D,
- const ParsedAttr &AL,
- SmallVectorImpl<Expr *> &Args,
- unsigned Sidx = 0,
- bool ParamIdxOk = false) {
- if (Sidx == AL.getNumArgs()) {
+void Sema::checkAttrArgsAreCapabilityObjs(Decl *D, const ParsedAttr &AL,
+ SmallVectorImpl<Expr *> &Args,
+ unsigned Sidx, bool ParamIdxOk) {
+ if (D && Sidx == AL.getNumArgs()) {
// If we don't have any capability arguments, the attribute implicitly
// refers to 'this'. So we need to make sure that 'this' exists, i.e. we're
// a non-static method, and that the class is a (scoped) capability.
@@ -352,11 +350,10 @@ static void checkAttrArgsAreCapabilityObjs(Sema &S, Decl *D,
// FIXME -- need to check this again on template instantiation
if (!checkRecordDeclForAttr<CapabilityAttr>(RD) &&
!checkRecordDeclForAttr<ScopedLockableAttr>(RD))
- S.Diag(AL.getLoc(),
- diag::warn_thread_attribute_not_on_capability_member)
+ Diag(AL.getLoc(), diag::warn_thread_attribute_not_on_capability_member)
<< AL << MD->getParent();
} else {
- S.Diag(AL.getLoc(), diag::warn_thread_attribute_not_on_non_static_member)
+ Diag(AL.getLoc(), diag::warn_thread_attribute_not_on_non_static_member)
<< AL;
}
}
@@ -381,7 +378,7 @@ static void checkAttrArgsAreCapabilityObjs(Sema &S, Decl *D,
// We allow constant strings to be used as a placeholder for expressions
// that are not valid C++ syntax, but warn that they are ignored.
- S.Diag(AL.getLoc(), diag::warn_thread_attribute_ignored) << AL;
+ Diag(AL.getLoc(), diag::warn_thread_attribute_ignored) << AL;
Args.push_back(ArgExp);
continue;
}
@@ -400,7 +397,7 @@ static void checkAttrArgsAreCapabilityObjs(Sema &S, Decl *D,
const RecordType *RT = getRecordType(ArgTy);
// Now check if we index into a record type function param.
- if(!RT && ParamIdxOk) {
+ if (D && !RT && ParamIdxOk) {
const auto *FD = dyn_cast<FunctionDecl>(D);
const auto *IL = dyn_cast<IntegerLiteral>(ArgExp);
if(FD && IL) {
@@ -409,8 +406,8 @@ static void checkAttrArgsAreCapabilityObjs(Sema &S, Decl *D,
uint64_t ParamIdxFromOne = ArgValue.getZExtValue();
uint64_t ParamIdxFromZero = ParamIdxFromOne - 1;
if (!ArgValue.isStrictlyPositive() || ParamIdxFromOne > NumParams) {
- S.Diag(AL.getLoc(),
- diag::err_attribute_argument_out_of_bounds_extra_info)
+ Diag(AL.getLoc(),
+ diag::err_attribute_argument_out_of_bounds_extra_info)
<< AL << Idx + 1 << NumParams;
continue;
}
@@ -422,8 +419,8 @@ static void checkAttrArgsAreCapabilityObjs(Sema &S, Decl *D,
// expression have capabilities. This allows for writing C code where the
// capability may be on the type, and the expression is a capability
// boolean logic expression. Eg) requires_capability(A || B && !C)
- if (!typeHasCapability(S, ArgTy) && !isCapabilityExpr(S, ArgExp))
- S.Diag(AL.getLoc(), diag::warn_thread_attribute_argument_not_lockable)
+ if (!typeHasCapability(*this, ArgTy) && !isCapabilityExpr(*this, ArgExp))
+ Diag(AL.getLoc(), diag::warn_thread_attribute_argument_not_lockable)
<< AL << ArgTy;
Args.push_back(ArgExp);
@@ -458,7 +455,7 @@ static bool checkGuardedByAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
Expr *&Arg) {
SmallVector<Expr *, 1> Args;
// check that all arguments are lockable objects
- checkAttrArgsAreCapabilityObjs(S, D, AL, Args);
+ S.checkAttrArgsAreCapabilityObjs(D, AL, Args);
unsigned Size = Args.size();
if (Size != 1)
return false;
@@ -500,7 +497,7 @@ static bool checkAcquireOrderAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
}
// Check that all arguments are lockable objects.
- checkAttrArgsAreCapabilityObjs(S, D, AL, Args);
+ S.checkAttrArgsAreCapabilityObjs(D, AL, Args);
if (Args.empty())
return false;
@@ -531,7 +528,7 @@ static bool checkLockFunAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
SmallVectorImpl<Expr *> &Args) {
// zero or more arguments ok
// check that all arguments are lockable objects
- checkAttrArgsAreCapabilityObjs(S, D, AL, Args, 0, /*ParamIdxOk=*/true);
+ S.checkAttrArgsAreCapabilityObjs(D, AL, Args, 0, /*ParamIdxOk=*/true);
return true;
}
@@ -633,7 +630,7 @@ static bool checkTryLockFunAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
}
// check that all arguments are lockable objects
- checkAttrArgsAreCapabilityObjs(S, D, AL, Args, 1);
+ S.checkAttrArgsAreCapabilityObjs(D, AL, Args, 1);
return true;
}
@@ -661,7 +658,7 @@ static void handleExclusiveTrylockFunctionAttr(Sema &S, Decl *D,
static void handleLockReturnedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// check that the argument is lockable object
SmallVector<Expr*, 1> Args;
- checkAttrArgsAreCapabilityObjs(S, D, AL, Args);
+ S.checkAttrArgsAreCapabilityObjs(D, AL, Args);
unsigned Size = Args.size();
if (Size == 0)
return;
@@ -679,7 +676,7 @@ static void handleLocksExcludedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// check that all arguments are lockable objects
SmallVector<Expr*, 1> Args;
- checkAttrArgsAreCapabilityObjs(S, D, AL, Args);
+ S.checkAttrArgsAreCapabilityObjs(D, AL, Args);
unsigned Size = Args.size();
if (Size == 0)
return;
@@ -6020,7 +6017,7 @@ static void handleReleaseCapabilityAttr(Sema &S, Decl *D,
return;
// Check that all arguments are lockable objects.
SmallVector<Expr *, 1> Args;
- checkAttrArgsAreCapabilityObjs(S, D, AL, Args, 0, true);
+ S.checkAttrArgsAreCapabilityObjs(D, AL, Args, 0, true);
D->addAttr(::new (S.Context) ReleaseCapabilityAttr(S.Context, AL, Args.data(),
Args.size()));
@@ -6037,7 +6034,7 @@ static void handleRequiresCapabilityAttr(Sema &S, Decl *D,
// check that all arguments are lockable objects
SmallVector<Expr*, 1> Args;
- checkAttrArgsAreCapabilityObjs(S, D, AL, Args);
+ S.checkAttrArgsAreCapabilityObjs(D, AL, Args);
if (Args.empty())
return;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index db0177f9750e0..adadf56594ab7 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8679,6 +8679,25 @@ static void HandleAnnotateTypeAttr(TypeProcessingState &State,
CurType = State.getAttributedType(AnnotateTypeAttr, CurType, CurType);
}
+static void HandleRequiresCapabilityAttr(TypeProcessingState &State,
+ QualType &CurType,
+ const ParsedAttr &PA) {
+ Sema &S = State.getSema();
+
+ if (PA.getNumArgs() < 1) {
+ // Already diganosed elsewhere, just ignore.
+ return;
+ }
+
+ llvm::SmallVector<Expr *, 4> Args;
+ Args.reserve(PA.getNumArgs() - 1);
+ State.getSema().checkAttrArgsAreCapabilityObjs(/*Decl=*/nullptr, PA, Args);
+
+ auto *RCAttr =
+ RequiresCapabilityAttr::Create(S.Context, Args.data(), Args.size(), PA);
+ CurType = State.getAttributedType(RCAttr, CurType, CurType);
+}
+
static void HandleLifetimeBoundAttr(TypeProcessingState &State,
QualType &CurType,
ParsedAttr &Attr) {
@@ -9026,6 +9045,12 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
attr.setUsedAsTypeAttr();
break;
}
+
+ case ParsedAttr::AT_RequiresCapability: {
+ HandleRequiresCapabilityAttr(state, type, attr);
+ attr.setUsedAsTypeAttr();
+ break;
+ }
}
// Handle attributes that are defined in a macro. We do not want this to be
diff --git a/clang/test/Sema/warn-thread-safety-analysis.c b/clang/test/Sema/warn-thread-safety-analysis.c
index 73b4e4798f644..e9f59dc65d935 100644
--- a/clang/test/Sema/warn-thread-safety-analysis.c
+++ b/clang/test/Sema/warn-thread-safety-analysis.c
@@ -189,6 +189,16 @@ int main(void) {
mutex_exclusive_unlock(late_parsing.a_mutex_defined_very_late);
mutex_exclusive_unlock(late_parsing.a_mutex_defined_late);
#endif
+ /// Function pointers
+ {
+ int __attribute__((requires_capability(&mu1))) (*function_ptr)(int) = Foo_fun1;
+
+ function_ptr(5); // expected-warning {{calling function 'function_ptr' requires holding mutex 'mu1'}}
+
+ mutex_exclusive_lock(&mu1);
+ int five = function_ptr(5);
+ mutex_exclusive_unlock(&mu1);
+ }
return 0;
}
diff --git a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
index 752803e4a0543..b6fd0016e7fa1 100644
--- a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
@@ -1109,6 +1109,8 @@ void elr_function_args() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2);
int elr_testfn(int y) EXCLUSIVE_LOCKS_REQUIRED(mu1);
+int EXCLUSIVE_LOCKS_REQUIRED(mu1) (*function_ptr)(int);
+
int elr_testfn(int y) {
int x EXCLUSIVE_LOCKS_REQUIRED(mu1) = y; // \
// expected-warning {{'exclusive_locks_required' attribute only applies to functions}}
More information about the cfe-commits
mailing list