Skip to content

Commit 161753f

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 01f9dff commit 161753f

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
@@ -1269,6 +1269,7 @@ def Most : DiagGroup<"most", [
12691269
def ThreadSafetyAttributes : DiagGroup<"thread-safety-attributes">;
12701270
def ThreadSafetyAnalysis : DiagGroup<"thread-safety-analysis">;
12711271
def ThreadSafetyPrecise : DiagGroup<"thread-safety-precise">;
1272+
def ThreadSafetyPedantic : DiagGroup<"thread-safety-pedantic">;
12721273
def ThreadSafetyReferenceReturn : DiagGroup<"thread-safety-reference-return">;
12731274
def ThreadSafetyReference : DiagGroup<"thread-safety-reference",
12741275
[ThreadSafetyReferenceReturn]>;
@@ -1278,6 +1279,7 @@ def ThreadSafety : DiagGroup<"thread-safety",
12781279
[ThreadSafetyAttributes,
12791280
ThreadSafetyAnalysis,
12801281
ThreadSafetyPrecise,
1282+
ThreadSafetyPedantic,
12811283
ThreadSafetyReference]>;
12821284
def ThreadSafetyVerbose : DiagGroup<"thread-safety-verbose">;
12831285
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
@@ -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: 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)