Skip to content

Commit 423ab37

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.
1 parent a5defd2 commit 423ab37

16 files changed

+624
-77
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ Improvements to Clang's diagnostics
436436
as function arguments or return value respectively. Note that
437437
:doc:`ThreadSafetyAnalysis` still does not perform alias analysis. The
438438
feature will be default-enabled with ``-Wthread-safety`` in a future release.
439+
- The :doc:`ThreadSafetyAnalysis` now supports reentrant capabilities.
439440
- Clang will now do a better job producing common nested names, when producing
440441
common types for ternary operator, template argument deduction and multiple return auto deduction.
441442
- 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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,12 @@ class ThreadSafetyHandler {
166166
/// \param LocEndOfScope -- The location of the end of the scope where the
167167
/// mutex is no longer held
168168
/// \param LEK -- which of the three above cases we should warn for
169+
/// \param ReentrancyMismatch -- mismatching reentrancy depth
169170
virtual void handleMutexHeldEndOfScope(StringRef Kind, Name LockName,
170171
SourceLocation LocLocked,
171172
SourceLocation LocEndOfScope,
172-
LockErrorKind LEK) {}
173+
LockErrorKind LEK,
174+
bool ReentrancyMismatch = false) {}
173175

174176
/// Warn when a mutex is held exclusively and shared at the same point. For
175177
/// example, if a mutex is locked exclusively during an if branch and shared

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

Lines changed: 13 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,34 @@ 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+
// Infers `Kind` and `Reentrant` from `QT`.
290+
CapabilityExpr(const til::SExpr *E, QualType QT, bool Neg);
286291

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

290296
const til::SExpr *sexpr() const { return CapExpr.getPointer(); }
291297
StringRef getKind() const { return CapKind; }
292298
bool negative() const { return CapExpr.getInt() & FlagNegative; }
299+
bool reentrant() const { return CapExpr.getInt() & FlagReentrant; }
293300

294301
CapabilityExpr operator!() const {
295-
return CapabilityExpr(CapExpr.getPointer(), CapKind, !negative());
302+
return CapabilityExpr(CapExpr.getPointer(), CapKind, !negative(),
303+
reentrant());
296304
}
297305

298306
bool equals(const CapabilityExpr &other) const {
@@ -389,10 +397,6 @@ class SExprBuilder {
389397
// Translate a variable reference.
390398
til::LiteralPtr *createVariable(const VarDecl *VD);
391399

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-
396400
// Translate a clang statement or expression to a TIL expression.
397401
// Also performs substitution of variables; Ctx provides the context.
398402
// 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
@@ -4027,6 +4027,13 @@ def LocksExcluded : InheritableAttr {
40274027
let Documentation = [Undocumented];
40284028
}
40294029

4030+
def ReentrantCapability : InheritableAttr {
4031+
let Spellings = [Clang<"reentrant_capability">];
4032+
let Subjects = SubjectList<[Record, TypedefName]>;
4033+
let Documentation = [Undocumented];
4034+
let SimpleHandler = 1;
4035+
}
4036+
40304037
// C/C++ consumed attributes.
40314038

40324039
def Consumable : InheritableAttr {

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4060,6 +4060,9 @@ def warn_thread_attribute_not_on_scoped_lockable_param : Warning<
40604060
"%0 attribute applies to function parameters only if their type is a "
40614061
"reference to a 'scoped_lockable'-annotated type">,
40624062
InGroup<ThreadSafetyAttributes>, DefaultIgnore;
4063+
def warn_thread_attribute_requires_preceded : Warning<
4064+
"%0 attribute on %1 must be preceded by %2 attribute">,
4065+
InGroup<ThreadSafetyAttributes>, DefaultIgnore;
40634066
def err_attribute_argument_out_of_bounds_extra_info : Error<
40644067
"%0 attribute parameter %1 is out of bounds: "
40654068
"%plural{0:no parameters to index into|"
@@ -4083,10 +4086,10 @@ def warn_expecting_locked : Warning<
40834086
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
40844087
// FIXME: improve the error message about locks not in scope
40854088
def warn_lock_some_predecessors : Warning<
4086-
"%0 '%1' is not held on every path through here">,
4089+
"%0 '%1' is not held on every path through here%select{| with equal reentrancy depth}2">,
40874090
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
40884091
def warn_expecting_lock_held_on_loop : Warning<
4089-
"expecting %0 '%1' to be held at start of each loop">,
4092+
"expecting %0 '%1' to be held at start of each loop%select{| with equal reentrancy depth}2">,
40904093
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
40914094
def note_locked_here : Note<"%0 acquired here">;
40924095
def note_unlocked_here : Note<"%0 released here">;

0 commit comments

Comments
 (0)