Skip to content

Commit 48e8aab

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 936bf29 commit 48e8aab

File tree

4 files changed

+78
-32
lines changed

4 files changed

+78
-32
lines changed

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,7 @@ def Most : DiagGroup<"most", [
12591259
def ThreadSafetyAttributes : DiagGroup<"thread-safety-attributes">;
12601260
def ThreadSafetyAnalysis : DiagGroup<"thread-safety-analysis">;
12611261
def ThreadSafetyPrecise : DiagGroup<"thread-safety-precise">;
1262+
def ThreadSafetyPedantic : DiagGroup<"thread-safety-pedantic">;
12621263
def ThreadSafetyReferenceReturn : DiagGroup<"thread-safety-reference-return">;
12631264
def ThreadSafetyReference : DiagGroup<"thread-safety-reference",
12641265
[ThreadSafetyReferenceReturn]>;
@@ -1268,6 +1269,7 @@ def ThreadSafety : DiagGroup<"thread-safety",
12681269
[ThreadSafetyAttributes,
12691270
ThreadSafetyAnalysis,
12701271
ThreadSafetyPrecise,
1272+
ThreadSafetyPedantic,
12711273
ThreadSafetyReference]>;
12721274
def ThreadSafetyVerbose : DiagGroup<"thread-safety-verbose">;
12731275
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
@@ -4222,6 +4222,11 @@ def warn_fun_requires_lock_precise :
42224222
InGroup<ThreadSafetyPrecise>, DefaultIgnore;
42234223
def note_found_mutex_near_match : Note<"found near match '%0'">;
42244224

4225+
// Pedantic thread safety warnings enabled by default
4226+
def warn_thread_reentrant_with_negative_capability : Warning<
4227+
"%0 is marked reentrant but used as a negative capability; this may be contradictory">,
4228+
InGroup<ThreadSafetyPedantic>, DefaultIgnore;
4229+
42254230
// Verbose thread safety warnings
42264231
def warn_thread_safety_verbose : Warning<"thread safety verbose warning">,
42274232
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)