Skip to content

Commit f669df2

Browse files
committed
Thread Safety Analysis: Support passing scoped locks between functions with appropriate annotations
This is helpful when multiple functions operate on the same capabilities, but we still want to use scoped lockable types for readability and exception safety. - Introduce support for thread safety annotations on function parameters marked with the 'scoped_lockable' attribute. - Add semantic checks for annotated function parameters, ensuring correct usage. - Enhance the analysis to recognize and handle parameters annotated for thread safety, extending the scope of analysis to track these across function boundries. - Verify that the underlying mutexes of function arguments match the expectations set by the annotations. Limitation: This does not work when the attribute arguments are class members, because attributes on function parameters are parsed differently from attributes on functions.
1 parent f404207 commit f669df2

File tree

9 files changed

+740
-29
lines changed

9 files changed

+740
-29
lines changed

clang/docs/ThreadSafetyAnalysis.rst

+47-4
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,13 @@ REQUIRES(...), REQUIRES_SHARED(...)
187187

188188
*Previously*: ``EXCLUSIVE_LOCKS_REQUIRED``, ``SHARED_LOCKS_REQUIRED``
189189

190-
``REQUIRES`` is an attribute on functions or methods, which
190+
``REQUIRES`` is an attribute on functions, methods or function parameters of
191+
reference to :ref:`scoped_capability`-annotated type, which
191192
declares that the calling thread must have exclusive access to the given
192193
capabilities. More than one capability may be specified. The capabilities
193194
must be held on entry to the function, *and must still be held on exit*.
195+
Additionally, if the attribute is on a function parameter, it declares that
196+
the scoped capability manages the specified capabilities in the given order.
194197

195198
``REQUIRES_SHARED`` is similar, but requires only shared access.
196199

@@ -211,17 +214,35 @@ must be held on entry to the function, *and must still be held on exit*.
211214
mu1.Unlock();
212215
}
213216

217+
void require(MutexLocker& scope REQUIRES(mu1)) {
218+
scope.Unlock();
219+
a=0; // Warning! Requires mu1.
220+
scope.Lock();
221+
}
222+
223+
void testParameter() {
224+
MutexLocker scope(&mu1);
225+
MutexLocker scope2(&mu2);
226+
require(scope2); // Warning! Mutex managed by 'scope' is 'mu2' instead of 'mu1'
227+
require(scope); // OK.
228+
scope.Unlock();
229+
require(scope); // Warning! Requires mu1.
230+
}
231+
214232

215233
ACQUIRE(...), ACQUIRE_SHARED(...), RELEASE(...), RELEASE_SHARED(...), RELEASE_GENERIC(...)
216234
------------------------------------------------------------------------------------------
217235

218236
*Previously*: ``EXCLUSIVE_LOCK_FUNCTION``, ``SHARED_LOCK_FUNCTION``,
219237
``UNLOCK_FUNCTION``
220238

221-
``ACQUIRE`` and ``ACQUIRE_SHARED`` are attributes on functions or methods
222-
declaring that the function acquires a capability, but does not release it.
239+
``ACQUIRE`` and ``ACQUIRE_SHARED`` are attributes on functions, methods
240+
or function parameters of reference to :ref:`scoped_capability`-annotated type,
241+
which declare that the function acquires a capability, but does not release it.
223242
The given capability must not be held on entry, and will be held on exit
224243
(exclusively for ``ACQUIRE``, shared for ``ACQUIRE_SHARED``).
244+
Additionally, if the attribute is on a function parameter, it declares that
245+
the scoped capability manages the specified capabilities in the given order.
225246

226247
``RELEASE``, ``RELEASE_SHARED``, and ``RELEASE_GENERIC`` declare that the
227248
function releases the given capability. The capability must be held on entry
@@ -249,6 +270,14 @@ shared for ``RELEASE_GENERIC``), and will no longer be held on exit.
249270
myObject.doSomething(); // Warning, mu is not locked.
250271
}
251272

273+
void release(MutexLocker& scope RELEASE(mu)) {
274+
} // Warning! Need to unlock mu.
275+
276+
void testParameter() {
277+
MutexLocker scope(&mu);
278+
release(scope);
279+
}
280+
252281
If no argument is passed to ``ACQUIRE`` or ``RELEASE``, then the argument is
253282
assumed to be ``this``, and the analysis will not check the body of the
254283
function. This pattern is intended for use by classes which hide locking
@@ -283,10 +312,13 @@ EXCLUDES(...)
283312

284313
*Previously*: ``LOCKS_EXCLUDED``
285314

286-
``EXCLUDES`` is an attribute on functions or methods, which declares that
315+
``EXCLUDES`` is an attribute on functions, methods or function parameters
316+
of reference to :ref:`scoped_capability`-annotated type, which declares that
287317
the caller must *not* hold the given capabilities. This annotation is
288318
used to prevent deadlock. Many mutex implementations are not re-entrant, so
289319
deadlock can occur if the function acquires the mutex a second time.
320+
Additionally, if the attribute is on a function parameter, it declares that
321+
the scoped capability manages the specified capabilities in the given order.
290322

291323
.. code-block:: c++
292324

@@ -305,6 +337,16 @@ deadlock can occur if the function acquires the mutex a second time.
305337
mu.Unlock();
306338
}
307339

340+
void exclude(MutexLocker& scope LOCKS_EXCLUDED(mu)){
341+
scope.Unlock(); // Warning! mu is not locked.
342+
scope.Lock();
343+
} // Warning! mu still held at the end of function.
344+
345+
void testParameter() {
346+
MutexLocker scope(&mu);
347+
exclude(scope); // Warning, mu is held.
348+
}
349+
308350
Unlike ``REQUIRES``, ``EXCLUDES`` is optional. The analysis will not issue a
309351
warning if the attribute is missing, which can lead to false negatives in some
310352
cases. This issue is discussed further in :ref:`negative`.
@@ -393,6 +435,7 @@ class can be used as a capability. The string argument specifies the kind of
393435
capability in error messages, e.g. ``"mutex"``. See the ``Container`` example
394436
given above, or the ``Mutex`` class in :ref:`mutexheader`.
395437

438+
.. _scoped_capability:
396439

397440
SCOPED_CAPABILITY
398441
-----------------

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

+35
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,41 @@ class ThreadSafetyHandler {
223223
virtual void handleFunExcludesLock(StringRef Kind, Name FunName,
224224
Name LockName, SourceLocation Loc) {}
225225

226+
/// Warn when an actual underlying mutex of a scoped lockable does not match
227+
/// the expected.
228+
/// \param Loc -- The location of the call expression.
229+
/// \param DLoc -- The location of the function declaration.
230+
/// \param ScopeName -- The name of the scope passed to the function.
231+
/// \param Kind -- The kind of the expected mutex.
232+
/// \param Expected -- The name of the expected mutex.
233+
/// \param Actual -- The name of the actual mutex.
234+
virtual void handleUnmatchedUnderlyingMutexes(SourceLocation Loc,
235+
SourceLocation DLoc,
236+
Name ScopeName, StringRef Kind,
237+
Name Expected, Name Actual) {}
238+
239+
/// Warn when we get fewer underlying mutexes than expected.
240+
/// \param Loc -- The location of the call expression.
241+
/// \param DLoc -- The location of the function declaration.
242+
/// \param ScopeName -- The name of the scope passed to the function.
243+
/// \param Kind -- The kind of the expected mutex.
244+
/// \param Expected -- The name of the expected mutex.
245+
virtual void handleExpectMoreUnderlyingMutexes(SourceLocation Loc,
246+
SourceLocation DLoc,
247+
Name ScopeName, StringRef Kind,
248+
Name Expected) {}
249+
250+
/// Warn when we get more underlying mutexes than expected.
251+
/// \param Loc -- The location of the call expression.
252+
/// \param DLoc -- The location of the function declaration.
253+
/// \param ScopeName -- The name of the scope passed to the function.
254+
/// \param Kind -- The kind of the actual mutex.
255+
/// \param Actual -- The name of the actual mutex.
256+
virtual void handleExpectFewerUnderlyingMutexes(SourceLocation Loc,
257+
SourceLocation DLoc,
258+
Name ScopeName,
259+
StringRef Kind, Name Actual) {}
260+
226261
/// Warn that L1 cannot be acquired before L2.
227262
virtual void handleLockAcquiredBefore(StringRef Kind, Name L1Name,
228263
Name L2Name, SourceLocation Loc) {}

clang/include/clang/Basic/Attr.td

+4-4
Original file line numberDiff line numberDiff line change
@@ -3685,7 +3685,7 @@ def AcquireCapability : InheritableAttr {
36853685
Clang<"acquire_shared_capability", 0>,
36863686
GNU<"exclusive_lock_function">,
36873687
GNU<"shared_lock_function">];
3688-
let Subjects = SubjectList<[Function]>;
3688+
let Subjects = SubjectList<[Function, ParmVar]>;
36893689
let LateParsed = LateAttrParseStandard;
36903690
let TemplateDependent = 1;
36913691
let ParseArgumentsAsUnevaluated = 1;
@@ -3717,7 +3717,7 @@ def ReleaseCapability : InheritableAttr {
37173717
Clang<"release_shared_capability", 0>,
37183718
Clang<"release_generic_capability", 0>,
37193719
Clang<"unlock_function", 0>];
3720-
let Subjects = SubjectList<[Function]>;
3720+
let Subjects = SubjectList<[Function, ParmVar]>;
37213721
let LateParsed = LateAttrParseStandard;
37223722
let TemplateDependent = 1;
37233723
let ParseArgumentsAsUnevaluated = 1;
@@ -3741,7 +3741,7 @@ def RequiresCapability : InheritableAttr {
37413741
let TemplateDependent = 1;
37423742
let ParseArgumentsAsUnevaluated = 1;
37433743
let InheritEvenIfAlreadyPresent = 1;
3744-
let Subjects = SubjectList<[Function]>;
3744+
let Subjects = SubjectList<[Function, ParmVar]>;
37453745
let Accessors = [Accessor<"isShared", [Clang<"requires_shared_capability", 0>,
37463746
Clang<"shared_locks_required", 0>]>];
37473747
let Documentation = [Undocumented];
@@ -3863,7 +3863,7 @@ def LocksExcluded : InheritableAttr {
38633863
let TemplateDependent = 1;
38643864
let ParseArgumentsAsUnevaluated = 1;
38653865
let InheritEvenIfAlreadyPresent = 1;
3866-
let Subjects = SubjectList<[Function]>;
3866+
let Subjects = SubjectList<[Function, ParmVar]>;
38673867
let Documentation = [Undocumented];
38683868
}
38693869

clang/include/clang/Basic/DiagnosticSemaKinds.td

+15
Original file line numberDiff line numberDiff line change
@@ -3984,6 +3984,10 @@ def warn_thread_attribute_decl_not_lockable : Warning<
39843984
def warn_thread_attribute_decl_not_pointer : Warning<
39853985
"%0 only applies to pointer types; type here is %1">,
39863986
InGroup<ThreadSafetyAttributes>, DefaultIgnore;
3987+
def warn_thread_attribute_not_on_scoped_lockable_param : Warning<
3988+
"%0 attribute applies to function parameters only if their type is a "
3989+
"reference to a 'scoped_lockable'-annotated type">,
3990+
InGroup<ThreadSafetyAttributes>, DefaultIgnore;
39873991
def err_attribute_argument_out_of_bounds_extra_info : Error<
39883992
"%0 attribute parameter %1 is out of bounds: "
39893993
"%plural{0:no parameters to index into|"
@@ -4039,6 +4043,17 @@ def warn_acquired_before : Warning<
40394043
def warn_acquired_before_after_cycle : Warning<
40404044
"cycle in acquired_before/after dependencies, starting with '%0'">,
40414045
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
4046+
def warn_unmatched_underlying_mutexes : Warning<
4047+
"%0 managed by '%1' is '%3' instead of '%2'">,
4048+
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
4049+
def warn_expect_more_underlying_mutexes : Warning<
4050+
"%0 '%2' not managed by '%1'">,
4051+
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
4052+
def warn_expect_fewer_underlying_mutexes : Warning<
4053+
"did not expect %0 '%2' to be managed by '%1'">,
4054+
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
4055+
def note_managed_mismatch_here_for_param : Note<
4056+
"see attribute on parameter here">;
40424057

40434058

40444059
// Thread safety warnings negative capabilities

0 commit comments

Comments
 (0)