Skip to content

Commit

Permalink
Thread Safety Analysis: Support passing scoped locks between function…
Browse files Browse the repository at this point in the history
…s with appropriate annotations (#110523)

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.
  • Loading branch information
malek203 authored and aaronpuchert committed Dec 20, 2024
1 parent 415cfaf commit c1e7e45
Show file tree
Hide file tree
Showing 10 changed files with 774 additions and 30 deletions.
25 changes: 25 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,31 @@ Improvements to Clang's diagnostics
- Fixed a bug where Clang hung on an unsupported optional scope specifier ``::`` when parsing
Objective-C. Clang now emits a diagnostic message instead of hanging.

- The :doc:`ThreadSafetyAnalysis` now supports passing scoped capabilities into functions:
an attribute on the scoped capability parameter indicates both the expected associated capabilities and,
like in the case of attributes on the function declaration itself, their state before and after the call.

.. code-block:: c++

#include "mutex.h"

Mutex mu1, mu2;
int a GUARDED_BY(mu1);

void require(MutexLocker& scope REQUIRES(mu1)) {
scope.Unlock();
a = 0; // Warning! Requires mu1.
scope.Lock();
}

void testParameter() {
MutexLocker scope(&mu1), scope2(&mu2);
require(scope2); // Warning! Mutex managed by 'scope2' is 'mu2' instead of 'mu1'
require(scope); // OK.
scope.Unlock();
require(scope); // Warning! Requires mu1.
}

Improvements to Clang's time-trace
----------------------------------

Expand Down
50 changes: 46 additions & 4 deletions clang/docs/ThreadSafetyAnalysis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,13 @@ REQUIRES(...), REQUIRES_SHARED(...)

*Previously*: ``EXCLUSIVE_LOCKS_REQUIRED``, ``SHARED_LOCKS_REQUIRED``

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

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

Expand All @@ -211,17 +214,34 @@ must be held on entry to the function, *and must still be held on exit*.
mu1.Unlock();
}

void require(MutexLocker& scope REQUIRES(mu1)) {
scope.Unlock();
a = 0; // Warning! Requires mu1.
scope.Lock();
}

void testParameter() {
MutexLocker scope(&mu1), scope2(&mu2);
require(scope2); // Warning! Mutex managed by 'scope2' is 'mu2' instead of 'mu1'
require(scope); // OK.
scope.Unlock();
require(scope); // Warning! Requires mu1.
}


ACQUIRE(...), ACQUIRE_SHARED(...), RELEASE(...), RELEASE_SHARED(...), RELEASE_GENERIC(...)
------------------------------------------------------------------------------------------

*Previously*: ``EXCLUSIVE_LOCK_FUNCTION``, ``SHARED_LOCK_FUNCTION``,
``UNLOCK_FUNCTION``

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

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

void release(MutexLocker& scope RELEASE(mu)) {
} // Warning! Need to unlock mu.

void testParameter() {
MutexLocker scope(&mu);
release(scope);
}

If no argument is passed to ``ACQUIRE`` or ``RELEASE``, then the argument is
assumed to be ``this``, and the analysis will not check the body of the
function. This pattern is intended for use by classes which hide locking
Expand Down Expand Up @@ -283,10 +311,13 @@ EXCLUDES(...)

*Previously*: ``LOCKS_EXCLUDED``

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

.. code-block:: c++

Expand All @@ -305,6 +336,16 @@ deadlock can occur if the function acquires the mutex a second time.
mu.Unlock();
}

void exclude(MutexLocker& scope LOCKS_EXCLUDED(mu)) {
scope.Unlock(); // Warning! mu is not locked.
scope.Lock();
} // Warning! mu still held at the end of function.

void testParameter() {
MutexLocker scope(&mu);
exclude(scope); // Warning, mu is held.
}

Unlike ``REQUIRES``, ``EXCLUDES`` is optional. The analysis will not issue a
warning if the attribute is missing, which can lead to false negatives in some
cases. This issue is discussed further in :ref:`negative`.
Expand Down Expand Up @@ -393,6 +434,7 @@ class can be used as a capability. The string argument specifies the kind of
capability in error messages, e.g. ``"mutex"``. See the ``Container`` example
given above, or the ``Mutex`` class in :ref:`mutexheader`.

.. _scoped_capability:

SCOPED_CAPABILITY
-----------------
Expand Down
36 changes: 36 additions & 0 deletions clang/include/clang/Analysis/Analyses/ThreadSafety.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,42 @@ class ThreadSafetyHandler {
virtual void handleFunExcludesLock(StringRef Kind, Name FunName,
Name LockName, SourceLocation Loc) {}

/// Warn when an actual underlying mutex of a scoped lockable does not match
/// the expected.
/// \param Loc -- The location of the call expression.
/// \param DLoc -- The location of the function declaration.
/// \param ScopeName -- The name of the scope passed to the function.
/// \param Kind -- The kind of the expected mutex.
/// \param Expected -- The name of the expected mutex.
/// \param Actual -- The name of the actual mutex.
virtual void handleUnmatchedUnderlyingMutexes(SourceLocation Loc,
SourceLocation DLoc,
Name ScopeName, StringRef Kind,
Name Expected, Name Actual) {}

/// Warn when we get fewer underlying mutexes than expected.
/// \param Loc -- The location of the call expression.
/// \param DLoc -- The location of the function declaration.
/// \param ScopeName -- The name of the scope passed to the function.
/// \param Kind -- The kind of the expected mutex.
/// \param Expected -- The name of the expected mutex.
virtual void handleExpectMoreUnderlyingMutexes(SourceLocation Loc,
SourceLocation DLoc,
Name ScopeName, StringRef Kind,
Name Expected) {}

/// Warn when we get more underlying mutexes than expected.
/// \param Loc -- The location of the call expression.
/// \param DLoc -- The location of the function declaration.
/// \param ScopeName -- The name of the scope passed to the function.
/// \param Kind -- The kind of the actual mutex.
/// \param Actual -- The name of the actual mutex.
virtual void handleExpectFewerUnderlyingMutexes(SourceLocation Loc,
SourceLocation DLoc,
Name ScopeName,
StringRef Kind, Name Actual) {
}

/// Warn that L1 cannot be acquired before L2.
virtual void handleLockAcquiredBefore(StringRef Kind, Name L1Name,
Name L2Name, SourceLocation Loc) {}
Expand Down
8 changes: 4 additions & 4 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -3763,7 +3763,7 @@ def AcquireCapability : InheritableAttr {
Clang<"acquire_shared_capability", 0>,
GNU<"exclusive_lock_function">,
GNU<"shared_lock_function">];
let Subjects = SubjectList<[Function]>;
let Subjects = SubjectList<[Function, ParmVar]>;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
Expand Down Expand Up @@ -3795,7 +3795,7 @@ def ReleaseCapability : InheritableAttr {
Clang<"release_shared_capability", 0>,
Clang<"release_generic_capability", 0>,
Clang<"unlock_function", 0>];
let Subjects = SubjectList<[Function]>;
let Subjects = SubjectList<[Function, ParmVar]>;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
Expand All @@ -3819,7 +3819,7 @@ def RequiresCapability : InheritableAttr {
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
let Subjects = SubjectList<[Function]>;
let Subjects = SubjectList<[Function, ParmVar]>;
let Accessors = [Accessor<"isShared", [Clang<"requires_shared_capability", 0>,
Clang<"shared_locks_required", 0>]>];
let Documentation = [Undocumented];
Expand Down Expand Up @@ -3941,7 +3941,7 @@ def LocksExcluded : InheritableAttr {
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
let Subjects = SubjectList<[Function]>;
let Subjects = SubjectList<[Function, ParmVar]>;
let Documentation = [Undocumented];
}

Expand Down
15 changes: 15 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3999,6 +3999,10 @@ def warn_thread_attribute_decl_not_lockable : Warning<
def warn_thread_attribute_decl_not_pointer : Warning<
"%0 only applies to pointer types; type here is %1">,
InGroup<ThreadSafetyAttributes>, DefaultIgnore;
def warn_thread_attribute_not_on_scoped_lockable_param : Warning<
"%0 attribute applies to function parameters only if their type is a "
"reference to a 'scoped_lockable'-annotated type">,
InGroup<ThreadSafetyAttributes>, DefaultIgnore;
def err_attribute_argument_out_of_bounds_extra_info : Error<
"%0 attribute parameter %1 is out of bounds: "
"%plural{0:no parameters to index into|"
Expand Down Expand Up @@ -4054,6 +4058,17 @@ def warn_acquired_before : Warning<
def warn_acquired_before_after_cycle : Warning<
"cycle in acquired_before/after dependencies, starting with '%0'">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
def warn_unmatched_underlying_mutexes : Warning<
"%0 managed by '%1' is '%3' instead of '%2'">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
def warn_expect_more_underlying_mutexes : Warning<
"%0 '%2' not managed by '%1'">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
def warn_expect_fewer_underlying_mutexes : Warning<
"did not expect %0 '%2' to be managed by '%1'">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
def note_managed_mismatch_here_for_param : Note<
"see attribute on parameter here">;


// Thread safety warnings negative capabilities
Expand Down
Loading

0 comments on commit c1e7e45

Please sign in to comment.