Skip to content

Commit 83695bf

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, which is enabled by default, to warn about using a reentrant capability as a negative capability: this usage is likely contradictory. Users that explicitly want this behaviour are free to compile with -Wno-thread-safety-pedantic.
1 parent e64f8e0 commit 83695bf

File tree

6 files changed

+82
-32
lines changed

6 files changed

+82
-32
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,9 @@ Improvements to Clang's diagnostics
468468
:doc:`ThreadSafetyAnalysis` still does not perform alias analysis. The
469469
feature will be default-enabled with ``-Wthread-safety`` in a future release.
470470
- The :doc:`ThreadSafetyAnalysis` now supports reentrant capabilities.
471+
- New warning group ``-Wthread-safety-pedantic`` warns about contradictory
472+
:doc:`ThreadSafetyAnalysis` usage patterns; currently warns about use of a
473+
reentrant capability as a negative capability.
471474
- Clang will now do a better job producing common nested names, when producing
472475
common types for ternary operator, template argument deduction and multiple return auto deduction.
473476
- 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
@@ -528,6 +528,7 @@ Warning flags
528528

529529
+ ``-Wthread-safety-attributes``: Semantic checks for thread safety attributes.
530530
+ ``-Wthread-safety-analysis``: The core analysis.
531+
+ ``-Wthread-safety-pedantic``: Contradictory usage patterns.
531532
+ ``-Wthread-safety-precise``: Requires that mutex expressions match precisely.
532533
This warning can be disabled for code which has a lot of aliases.
533534
+ ``-Wthread-safety-reference``: Checks when guarded members are passed or

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,7 @@ def Most : DiagGroup<"most", [
12651265
def ThreadSafetyAttributes : DiagGroup<"thread-safety-attributes">;
12661266
def ThreadSafetyAnalysis : DiagGroup<"thread-safety-analysis">;
12671267
def ThreadSafetyPrecise : DiagGroup<"thread-safety-precise">;
1268+
def ThreadSafetyPedantic : DiagGroup<"thread-safety-pedantic">;
12681269
def ThreadSafetyReferenceReturn : DiagGroup<"thread-safety-reference-return">;
12691270
def ThreadSafetyReference : DiagGroup<"thread-safety-reference",
12701271
[ThreadSafetyReferenceReturn]>;
@@ -1274,6 +1275,7 @@ def ThreadSafety : DiagGroup<"thread-safety",
12741275
[ThreadSafetyAttributes,
12751276
ThreadSafetyAnalysis,
12761277
ThreadSafetyPrecise,
1278+
ThreadSafetyPedantic,
12771279
ThreadSafetyReference]>;
12781280
def ThreadSafetyVerbose : DiagGroup<"thread-safety-verbose">;
12791281
def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

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

4241+
// Pedantic thread safety warnings enabled by default
4242+
def warn_thread_reentrant_with_negative_capability : Warning<
4243+
"%0 is marked reentrant but used as a negative capability; this may be contradictory">,
4244+
InGroup<ThreadSafetyPedantic>, DefaultIgnore;
4245+
42414246
// Verbose thread safety warnings
42424247
def warn_thread_safety_verbose : Warning<"thread safety verbose warning">,
42434248
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: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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)