Skip to content

Commit 427dcd2

Browse files
committed
Thread Safety Analysis: Support reentrant capabilities
Introduce the `reentrant_capability` attribute, which may be specified alongside the `capability(..)` attribute to denote that the defined capability type is reentrant. Marking a capability as reentrant means that acquiring the same capability multiple times is safe, and does not produce warnings on attempted re-acquisition. The most significant changes required are plumbing to propagate if the attribute is present to a CapabilityExpr, and then introducing a ReentrancyCount to FactEntry that can be incremented while a fact remains in the FactSet. Care was taken to avoid increasing the size of both CapabilityExpr and FactEntry by carefully allocating free bits of CapabilityExpr::CapExpr and the bitset respectively.
1 parent a831902 commit 427dcd2

16 files changed

+598
-75
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ Improvements to Clang's diagnostics
369369
as function arguments or return value respectively. Note that
370370
:doc:`ThreadSafetyAnalysis` still does not perform alias analysis. The
371371
feature will be default-enabled with ``-Wthread-safety`` in a future release.
372+
- The :doc:`ThreadSafetyAnalysis` now supports reentrant capabilities.
372373
- Clang will now do a better job producing common nested names, when producing
373374
common types for ternary operator, template argument deduction and multiple return auto deduction.
374375
- The ``-Wsign-compare`` warning now treats expressions with bitwise not(~) and minus(-) as signed integers

clang/docs/ThreadSafetyAnalysis.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,21 @@ class can be used as a capability. The string argument specifies the kind of
434434
capability in error messages, e.g. ``"mutex"``. See the ``Container`` example
435435
given above, or the ``Mutex`` class in :ref:`mutexheader`.
436436

437+
REENTRANT_CAPABILITY
438+
--------------------
439+
440+
``REENTRANT_CAPABILITY`` is an attribute on capability classes, denoting that
441+
they are reentrant. Marking a capability as reentrant means that acquiring the
442+
same capability multiple times is safe. Acquiring the same capability with
443+
different access privileges (exclusive vs. shared) again is not considered
444+
reentrant by the analysis.
445+
446+
Note: In many cases this attribute is only required where a capability is
447+
acquired reentrant within the same function, such as via macros or other
448+
helpers. Otherwise, best practice is to avoid explicitly acquiring a capability
449+
multiple times within the same function, and letting the analysis produce
450+
warnings on double-acquisition attempts.
451+
437452
.. _scoped_capability:
438453

439454
SCOPED_CAPABILITY
@@ -846,6 +861,9 @@ implementation.
846861
#define CAPABILITY(x) \
847862
THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
848863

864+
#define REENTRANT_CAPABILITY \
865+
THREAD_ANNOTATION_ATTRIBUTE__(reentrant_capability)
866+
849867
#define SCOPED_CAPABILITY \
850868
THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
851869

clang/include/clang/Analysis/Analyses/ThreadSafety.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,17 @@ enum AccessKind {
9494

9595
/// This enum distinguishes between different situations where we warn due to
9696
/// inconsistent locking.
97-
/// \enum SK_LockedSomeLoopIterations -- a mutex is locked for some but not all
98-
/// loop iterations.
99-
/// \enum SK_LockedSomePredecessors -- a mutex is locked in some but not all
100-
/// predecessors of a CFGBlock.
101-
/// \enum SK_LockedAtEndOfFunction -- a mutex is still locked at the end of a
102-
/// function.
10397
enum LockErrorKind {
98+
/// A capability is locked for some but not all loop iterations.
10499
LEK_LockedSomeLoopIterations,
100+
/// A capability is locked in some but not all predecessors of a CFGBlock.
105101
LEK_LockedSomePredecessors,
102+
/// A capability is still locked at the end of a function.
106103
LEK_LockedAtEndOfFunction,
104+
/// A capability is locked with mismatching reentrancy depth in predecessors
105+
/// of a CFGBlock.
106+
LEK_LockedReentrancyMismatch,
107+
/// Expecting a capability to be held at the end of function.
107108
LEK_NotLockedAtEndOfFunction
108109
};
109110

clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#define LLVM_CLANG_ANALYSIS_ANALYSES_THREADSAFETYCOMMON_H
2323

2424
#include "clang/AST/Decl.h"
25+
#include "clang/AST/Type.h"
2526
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
2627
#include "clang/Analysis/Analyses/ThreadSafetyTIL.h"
2728
#include "clang/Analysis/Analyses/ThreadSafetyTraverse.h"
@@ -272,27 +273,33 @@ class CFGWalker {
272273
class CapabilityExpr {
273274
private:
274275
static constexpr unsigned FlagNegative = 1u << 0;
276+
static constexpr unsigned FlagReentrant = 1u << 1;
275277

276278
/// The capability expression and flags.
277-
llvm::PointerIntPair<const til::SExpr *, 1, unsigned> CapExpr;
279+
llvm::PointerIntPair<const til::SExpr *, 2, unsigned> CapExpr;
278280

279281
/// The kind of capability as specified by @ref CapabilityAttr::getName.
280282
StringRef CapKind;
281283

282284
public:
283285
CapabilityExpr() : CapExpr(nullptr, 0) {}
284-
CapabilityExpr(const til::SExpr *E, StringRef Kind, bool Neg)
285-
: CapExpr(E, Neg ? FlagNegative : 0), CapKind(Kind) {}
286+
CapabilityExpr(const til::SExpr *E, StringRef Kind, bool Neg, bool Reentrant)
287+
: CapExpr(E, (Neg ? FlagNegative : 0) | (Reentrant ? FlagReentrant : 0)),
288+
CapKind(Kind) {}
289+
CapabilityExpr(const til::SExpr *E, QualType QT, bool Neg);
286290

287291
// Don't allow implicitly-constructed StringRefs since we'll capture them.
288-
template <typename T> CapabilityExpr(const til::SExpr *, T, bool) = delete;
292+
template <typename T>
293+
CapabilityExpr(const til::SExpr *, T, bool, bool) = delete;
289294

290295
const til::SExpr *sexpr() const { return CapExpr.getPointer(); }
291296
StringRef getKind() const { return CapKind; }
292297
bool negative() const { return CapExpr.getInt() & FlagNegative; }
298+
bool reentrant() const { return CapExpr.getInt() & FlagReentrant; }
293299

294300
CapabilityExpr operator!() const {
295-
return CapabilityExpr(CapExpr.getPointer(), CapKind, !negative());
301+
return CapabilityExpr(CapExpr.getPointer(), CapKind, !negative(),
302+
reentrant());
296303
}
297304

298305
bool equals(const CapabilityExpr &other) const {
@@ -389,10 +396,6 @@ class SExprBuilder {
389396
// Translate a variable reference.
390397
til::LiteralPtr *createVariable(const VarDecl *VD);
391398

392-
// Create placeholder for this: we don't know the VarDecl on construction yet.
393-
std::pair<til::LiteralPtr *, StringRef>
394-
createThisPlaceholder(const Expr *Exp);
395-
396399
// Translate a clang statement or expression to a TIL expression.
397400
// Also performs substitution of variables; Ctx provides the context.
398401
// Dispatches on the type of S.

clang/include/clang/Basic/Attr.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4003,6 +4003,13 @@ def LocksExcluded : InheritableAttr {
40034003
let Documentation = [Undocumented];
40044004
}
40054005

4006+
def ReentrantCapability : InheritableAttr {
4007+
let Spellings = [Clang<"reentrant_capability">];
4008+
let Subjects = SubjectList<[Record, TypedefName]>;
4009+
let Documentation = [Undocumented];
4010+
let SimpleHandler = 1;
4011+
}
4012+
40064013
// C/C++ consumed attributes.
40074014

40084015
def Consumable : InheritableAttr {

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4045,6 +4045,9 @@ def warn_thread_attribute_not_on_scoped_lockable_param : Warning<
40454045
"%0 attribute applies to function parameters only if their type is a "
40464046
"reference to a 'scoped_lockable'-annotated type">,
40474047
InGroup<ThreadSafetyAttributes>, DefaultIgnore;
4048+
def warn_reentrant_capability_without_capability : Warning<
4049+
"ignoring %0 attribute on %1 without 'capability' attribute">,
4050+
InGroup<ThreadSafetyAttributes>, DefaultIgnore;
40484051
def err_attribute_argument_out_of_bounds_extra_info : Error<
40494052
"%0 attribute parameter %1 is out of bounds: "
40504053
"%plural{0:no parameters to index into|"
@@ -4073,6 +4076,9 @@ def warn_lock_some_predecessors : Warning<
40734076
def warn_expecting_lock_held_on_loop : Warning<
40744077
"expecting %0 '%1' to be held at start of each loop">,
40754078
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
4079+
def warn_lock_reentrancy_mismatch : Warning<
4080+
"expecting %0 '%1' to be held with matching reentrancy depth on every path through here">,
4081+
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
40764082
def note_locked_here : Note<"%0 acquired here">;
40774083
def note_unlocked_here : Note<"%0 released here">;
40784084
def warn_lock_exclusive_and_shared : Warning<

0 commit comments

Comments
 (0)