Skip to content

Commit c66172e

Browse files
committed
Thread Safety Analysis: Warn when using negative reentrant capability
The purpose of negative capabilities is documented as helping to prevent double locking, which is not an issue for most reentrant capabilities (such as mutexes). Introduce a pedantic warning group to warn about using a reentrant capability as a negative capability: this usage is likely contradictory.
1 parent 00cdaa5 commit c66172e

File tree

6 files changed

+85
-36
lines changed

6 files changed

+85
-36
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,9 @@ Improvements to Clang's diagnostics
494494
:doc:`ThreadSafetyAnalysis` still does not perform alias analysis. The
495495
feature will be default-enabled with ``-Wthread-safety`` in a future release.
496496
- The :doc:`ThreadSafetyAnalysis` now supports reentrant capabilities.
497+
- New warning group ``-Wthread-safety-pedantic`` warns about contradictory
498+
:doc:`ThreadSafetyAnalysis` usage patterns; currently warns about use of a
499+
reentrant capability as a negative capability.
497500
- Clang will now do a better job producing common nested names, when producing
498501
common types for ternary operator, template argument deduction and multiple return auto deduction.
499502
- The ``-Wsign-compare`` warning now treats expressions with bitwise not(~) and minus(-) as signed integers

clang/docs/ThreadSafetyAnalysis.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ Warning flags
536536
* ``-Wthread-safety-pointer``: Checks when passing or returning pointers to
537537
guarded variables, or pointers to guarded data, as function argument or
538538
return value respectively.
539+
* ``-Wthread-safety-pedantic``: Contradictory usage patterns.
539540

540541
:ref:`negative` are an experimental feature, which are enabled with:
541542

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,7 @@ def Most : DiagGroup<"most", [
12721272
def ThreadSafetyAttributes : DiagGroup<"thread-safety-attributes">;
12731273
def ThreadSafetyAnalysis : DiagGroup<"thread-safety-analysis">;
12741274
def ThreadSafetyPrecise : DiagGroup<"thread-safety-precise">;
1275+
def ThreadSafetyPedantic : DiagGroup<"thread-safety-pedantic">;
12751276
def ThreadSafetyReferenceReturn : DiagGroup<"thread-safety-reference-return">;
12761277
def ThreadSafetyReference : DiagGroup<"thread-safety-reference",
12771278
[ThreadSafetyReferenceReturn]>;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4254,6 +4254,11 @@ def warn_fun_requires_lock_precise :
42544254
InGroup<ThreadSafetyPrecise>, DefaultIgnore;
42554255
def note_found_mutex_near_match : Note<"found near match '%0'">;
42564256

4257+
// Pedantic thread safety warnings
4258+
def warn_thread_reentrant_with_negative_capability : Warning<
4259+
"%0 is marked reentrant but used as a negative capability; this may be contradictory">,
4260+
InGroup<ThreadSafetyPedantic>, DefaultIgnore;
4261+
42574262
// Verbose thread safety warnings
42584263
def warn_thread_safety_verbose : Warning<"thread safety verbose warning">,
42594264
InGroup<ThreadSafetyVerbose>, DefaultIgnore;

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -256,22 +256,27 @@ static bool checkRecordDeclForAttr(const RecordDecl *RD) {
256256
return false;
257257
}
258258

259-
static bool checkRecordTypeForCapability(Sema &S, QualType Ty) {
259+
static std::optional<TypeDecl *> checkRecordTypeForCapability(Sema &S,
260+
QualType Ty) {
260261
const RecordType *RT = getRecordType(Ty);
261262

262263
if (!RT)
263-
return false;
264+
return std::nullopt;
264265

265266
// Don't check for the capability if the class hasn't been defined yet.
266267
if (RT->isIncompleteType())
267-
return true;
268+
return {nullptr};
268269

269270
// Allow smart pointers to be used as capability objects.
270271
// FIXME -- Check the type that the smart pointer points to.
271272
if (threadSafetyCheckIsSmartPointer(S, RT))
272-
return true;
273+
return {nullptr};
273274

274-
return checkRecordDeclForAttr<CapabilityAttr>(RT->getDecl());
275+
RecordDecl *RD = RT->getDecl();
276+
if (checkRecordDeclForAttr<CapabilityAttr>(RD))
277+
return {RD};
278+
279+
return std::nullopt;
275280
}
276281

277282
static bool checkRecordTypeForScopedCapability(Sema &S, QualType Ty) {
@@ -287,51 +292,76 @@ static bool checkRecordTypeForScopedCapability(Sema &S, QualType Ty) {
287292
return checkRecordDeclForAttr<ScopedLockableAttr>(RT->getDecl());
288293
}
289294

290-
static bool checkTypedefTypeForCapability(QualType Ty) {
295+
static std::optional<TypeDecl *> checkTypedefTypeForCapability(QualType Ty) {
291296
const auto *TD = Ty->getAs<TypedefType>();
292297
if (!TD)
293-
return false;
298+
return std::nullopt;
294299

295300
TypedefNameDecl *TN = TD->getDecl();
296301
if (!TN)
297-
return false;
302+
return std::nullopt;
298303

299-
return TN->hasAttr<CapabilityAttr>();
300-
}
301-
302-
static bool typeHasCapability(Sema &S, QualType Ty) {
303-
if (checkTypedefTypeForCapability(Ty))
304-
return true;
304+
if (TN->hasAttr<CapabilityAttr>())
305+
return {TN};
305306

306-
if (checkRecordTypeForCapability(S, Ty))
307-
return true;
307+
return std::nullopt;
308+
}
308309

309-
return false;
310+
/// Returns capability TypeDecl if defined, nullptr if not yet defined (maybe
311+
/// capability), and nullopt if it definitely is not a capability.
312+
static std::optional<TypeDecl *> checkTypeForCapability(Sema &S, QualType Ty) {
313+
if (auto TD = checkTypedefTypeForCapability(Ty))
314+
return TD;
315+
if (auto TD = checkRecordTypeForCapability(S, Ty))
316+
return TD;
317+
return std::nullopt;
310318
}
311319

312-
static bool isCapabilityExpr(Sema &S, const Expr *Ex) {
320+
static bool validateCapabilityExpr(Sema &S, const ParsedAttr &AL,
321+
const Expr *Ex, bool Neg = false) {
313322
// Capability expressions are simple expressions involving the boolean logic
314323
// operators &&, || or !, a simple DeclRefExpr, CastExpr or a ParenExpr. Once
315324
// a DeclRefExpr is found, its type should be checked to determine whether it
316325
// is a capability or not.
317326

318327
if (const auto *E = dyn_cast<CastExpr>(Ex))
319-
return isCapabilityExpr(S, E->getSubExpr());
328+
return validateCapabilityExpr(S, AL, E->getSubExpr(), Neg);
320329
else if (const auto *E = dyn_cast<ParenExpr>(Ex))
321-
return isCapabilityExpr(S, E->getSubExpr());
330+
return validateCapabilityExpr(S, AL, E->getSubExpr(), Neg);
322331
else if (const auto *E = dyn_cast<UnaryOperator>(Ex)) {
323-
if (E->getOpcode() == UO_LNot || E->getOpcode() == UO_AddrOf ||
324-
E->getOpcode() == UO_Deref)
325-
return isCapabilityExpr(S, E->getSubExpr());
326-
return false;
332+
switch (E->getOpcode()) {
333+
case UO_LNot:
334+
Neg = !Neg;
335+
[[fallthrough]];
336+
case UO_AddrOf:
337+
case UO_Deref:
338+
return validateCapabilityExpr(S, AL, E->getSubExpr(), Neg);
339+
default:
340+
return false;
341+
}
327342
} else if (const auto *E = dyn_cast<BinaryOperator>(Ex)) {
328343
if (E->getOpcode() == BO_LAnd || E->getOpcode() == BO_LOr)
329-
return isCapabilityExpr(S, E->getLHS()) &&
330-
isCapabilityExpr(S, E->getRHS());
344+
return validateCapabilityExpr(S, AL, E->getLHS(), Neg) &&
345+
validateCapabilityExpr(S, AL, E->getRHS(), Neg);
331346
return false;
347+
} else if (const auto *E = dyn_cast<CXXOperatorCallExpr>(Ex)) {
348+
if (E->getOperator() == OO_Exclaim && E->getNumArgs() == 1) {
349+
// operator!(this) - return type is the expression to check below.
350+
Neg = !Neg;
351+
}
332352
}
333353

334-
return typeHasCapability(S, Ex->getType());
354+
// Base case: check the inner type for capability.
355+
QualType Ty = Ex->getType();
356+
if (auto TD = checkTypeForCapability(S, Ty)) {
357+
if (Neg && *TD != nullptr && (*TD)->hasAttr<ReentrantCapabilityAttr>()) {
358+
S.Diag(AL.getLoc(), diag::warn_thread_reentrant_with_negative_capability)
359+
<< Ty.getUnqualifiedType();
360+
}
361+
return true;
362+
}
363+
364+
return false;
335365
}
336366

337367
/// Checks that all attribute arguments, starting from Sidx, resolve to
@@ -420,11 +450,12 @@ static void checkAttrArgsAreCapabilityObjs(Sema &S, Decl *D,
420450
}
421451
}
422452

423-
// If the type does not have a capability, see if the components of the
424-
// expression have capabilities. This allows for writing C code where the
453+
// If ArgTy is not a capability, this also checks if components of the
454+
// expression are capabilities. This allows for writing C code where the
425455
// capability may be on the type, and the expression is a capability
426456
// boolean logic expression. Eg) requires_capability(A || B && !C)
427-
if (!typeHasCapability(S, ArgTy) && !isCapabilityExpr(S, ArgExp))
457+
if (!validateCapabilityExpr(S, AL, ArgExp) &&
458+
!checkTypeForCapability(S, ArgTy))
428459
S.Diag(AL.getLoc(), diag::warn_thread_attribute_argument_not_lockable)
429460
<< AL << ArgTy;
430461

@@ -496,7 +527,7 @@ static bool checkAcquireOrderAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
496527

497528
// Check that this attribute only applies to lockable types.
498529
QualType QT = cast<ValueDecl>(D)->getType();
499-
if (!QT->isDependentType() && !typeHasCapability(S, QT)) {
530+
if (!QT->isDependentType() && !checkTypeForCapability(S, QT)) {
500531
S.Diag(AL.getLoc(), diag::warn_thread_attribute_decl_not_lockable) << AL;
501532
return false;
502533
}

clang/test/SemaCXX/warn-thread-safety-analysis.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=0 %s
2-
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=1 %s
3-
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=0 %s
4-
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=1 %s
1+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-pedantic -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=0 %s
2+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-pedantic -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=1 %s
3+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-pedantic -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=0 %s
4+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-pedantic -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=1 %s
55

66
// FIXME: should also run %clang_cc1 -fsyntax-only -verify -Wthread-safety -std=c++11 -Wc++98-compat %s
77
// FIXME: should also run %clang_cc1 -fsyntax-only -verify -Wthread-safety %s
@@ -7209,12 +7209,14 @@ void testReentrantTypedef() {
72097209
bit_unlock(bl);
72107210
}
72117211

7212+
// Negative + reentrant capability tests.
72127213
class TestNegativeWithReentrantMutex {
72137214
ReentrantMutex rmu;
72147215
int a GUARDED_BY(rmu);
72157216

72167217
public:
7217-
void baz() EXCLUSIVE_LOCKS_REQUIRED(!rmu) {
7218+
void baz() EXCLUSIVE_LOCKS_REQUIRED(!rmu) { // \
7219+
// expected-warning{{'ReentrantMutex' is marked reentrant but used as a negative capability; this may be contradictory}}
72187220
rmu.Lock();
72197221
rmu.Lock();
72207222
a = 0;
@@ -7223,4 +7225,10 @@ class TestNegativeWithReentrantMutex {
72237225
}
72247226
};
72257227

7228+
typedef int __attribute__((capability("role"), reentrant_capability)) ThreadRole;
7229+
ThreadRole FlightControl1, FlightControl2;
7230+
void dispatch_log(const char *msg) __attribute__((requires_capability(!FlightControl1 && !FlightControl2))) {} // \
7231+
// expected-warning{{'ThreadRole' (aka 'int') is marked reentrant but used as a negative capability; this may be contradictory}} \
7232+
// expected-warning{{'ThreadRole' (aka 'int') is marked reentrant but used as a negative capability; this may be contradictory}}
7233+
72267234
} // namespace Reentrancy

0 commit comments

Comments
 (0)