Skip to content

Commit 9fdf6ed

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 08d747c commit 9fdf6ed

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
@@ -488,6 +488,9 @@ Improvements to Clang's diagnostics
488488
:doc:`ThreadSafetyAnalysis` still does not perform alias analysis. The
489489
feature will be default-enabled with ``-Wthread-safety`` in a future release.
490490
- The :doc:`ThreadSafetyAnalysis` now supports reentrant capabilities.
491+
- New warning group ``-Wthread-safety-pedantic`` warns about contradictory
492+
:doc:`ThreadSafetyAnalysis` usage patterns; currently warns about use of a
493+
reentrant capability as a negative capability.
491494
- Clang will now do a better job producing common nested names, when producing
492495
common types for ternary operator, template argument deduction and multiple return auto deduction.
493496
- 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
@@ -4250,6 +4250,11 @@ def warn_fun_requires_lock_precise :
42504250
InGroup<ThreadSafetyPrecise>, DefaultIgnore;
42514251
def note_found_mutex_near_match : Note<"found near match '%0'">;
42524252

4253+
// Pedantic thread safety warnings
4254+
def warn_thread_reentrant_with_negative_capability : Warning<
4255+
"%0 is marked reentrant but used as a negative capability; this may be contradictory">,
4256+
InGroup<ThreadSafetyPedantic>, DefaultIgnore;
4257+
42534258
// Verbose thread safety warnings
42544259
def warn_thread_safety_verbose : Warning<"thread safety verbose warning">,
42554260
InGroup<ThreadSafetyVerbose>, DefaultIgnore;

clang/lib/Sema/SemaDeclAttr.cpp

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

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

261262
if (!RT)
262-
return false;
263+
return std::nullopt;
263264

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

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

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

276281
static bool checkRecordTypeForScopedCapability(Sema &S, QualType Ty) {
@@ -286,51 +291,76 @@ static bool checkRecordTypeForScopedCapability(Sema &S, QualType Ty) {
286291
return checkRecordDeclForAttr<ScopedLockableAttr>(RT->getDecl());
287292
}
288293

289-
static bool checkTypedefTypeForCapability(QualType Ty) {
294+
static std::optional<TypeDecl *> checkTypedefTypeForCapability(QualType Ty) {
290295
const auto *TD = Ty->getAs<TypedefType>();
291296
if (!TD)
292-
return false;
297+
return std::nullopt;
293298

294299
TypedefNameDecl *TN = TD->getDecl();
295300
if (!TN)
296-
return false;
301+
return std::nullopt;
297302

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

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

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

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

317326
if (const auto *E = dyn_cast<CastExpr>(Ex))
318-
return isCapabilityExpr(S, E->getSubExpr());
327+
return validateCapabilityExpr(S, AL, E->getSubExpr(), Neg);
319328
else if (const auto *E = dyn_cast<ParenExpr>(Ex))
320-
return isCapabilityExpr(S, E->getSubExpr());
329+
return validateCapabilityExpr(S, AL, E->getSubExpr(), Neg);
321330
else if (const auto *E = dyn_cast<UnaryOperator>(Ex)) {
322-
if (E->getOpcode() == UO_LNot || E->getOpcode() == UO_AddrOf ||
323-
E->getOpcode() == UO_Deref)
324-
return isCapabilityExpr(S, E->getSubExpr());
325-
return false;
331+
switch (E->getOpcode()) {
332+
case UO_LNot:
333+
Neg = !Neg;
334+
[[fallthrough]];
335+
case UO_AddrOf:
336+
case UO_Deref:
337+
return validateCapabilityExpr(S, AL, E->getSubExpr(), Neg);
338+
default:
339+
return false;
340+
}
326341
} else if (const auto *E = dyn_cast<BinaryOperator>(Ex)) {
327342
if (E->getOpcode() == BO_LAnd || E->getOpcode() == BO_LOr)
328-
return isCapabilityExpr(S, E->getLHS()) &&
329-
isCapabilityExpr(S, E->getRHS());
343+
return validateCapabilityExpr(S, AL, E->getLHS(), Neg) &&
344+
validateCapabilityExpr(S, AL, E->getRHS(), Neg);
330345
return false;
346+
} else if (const auto *E = dyn_cast<CXXOperatorCallExpr>(Ex)) {
347+
if (E->getOperator() == OO_Exclaim && E->getNumArgs() == 1) {
348+
// operator!(this) - return type is the expression to check below.
349+
Neg = !Neg;
350+
}
331351
}
332352

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

336366
/// Checks that all attribute arguments, starting from Sidx, resolve to
@@ -419,11 +449,12 @@ static void checkAttrArgsAreCapabilityObjs(Sema &S, Decl *D,
419449
}
420450
}
421451

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

@@ -495,7 +526,7 @@ static bool checkAcquireOrderAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
495526

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

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)