aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEwout van Bekkum <ewout@google.com>2021-04-08 08:51:16 -0700
committerCQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>2021-04-12 16:53:37 +0000
commitcc9ef8367394c28f9a57fe0588d6fd9c3f8d0f87 (patch)
treef386a891ca1823a7c6d0cef6d6668f17e8de6d87
parent597ac2a7e9ec542a61c48f6e3c58c06132ed6a0c (diff)
downloadpigweed-cc9ef8367394c28f9a57fe0588d6fd9c3f8d0f87.tar.gz
pw_sync: add lock safety annotations for C++ clang usage
Adds clang thread safety annotations to the interrupt spin lock for C++ usage. Note that this is only checked when compiling with clang. C APIs explicitly escape the thread safety annotations. Change-Id: Ic829afd41b3978c507ebe294d34e1e0dc03865b4 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/40241 Reviewed-by: Keir Mierle <keir@google.com> Reviewed-by: Wyatt Hepler <hepler@google.com> Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com> Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
-rw-r--r--pw_build/BUILD.gn6
-rw-r--r--pw_preprocessor/public/pw_preprocessor/compiler.h9
-rw-r--r--pw_rpc/BUILD1
-rw-r--r--pw_rpc/BUILD.gn1
-rw-r--r--pw_rpc/public/pw_rpc/synchronized_channel_output.h9
-rw-r--r--pw_sync/BUILD22
-rw-r--r--pw_sync/BUILD.gn16
-rw-r--r--pw_sync/docs.rst339
-rw-r--r--pw_sync/interrupt_spin_lock.cc2
-rw-r--r--pw_sync/interrupt_spin_lock_facade_test.cc11
-rw-r--r--pw_sync/mutex_facade_test.cc11
-rw-r--r--pw_sync/public/pw_sync/interrupt_spin_lock.h18
-rw-r--r--pw_sync/public/pw_sync/lock_annotations.h280
-rw-r--r--pw_sync/public/pw_sync/mutex.h15
-rw-r--r--pw_sync/public/pw_sync/timed_mutex.h22
-rw-r--r--pw_sync/timed_mutex_facade_test.cc60
-rw-r--r--targets/host/target_toolchains.gni20
-rw-r--r--targets/lm3s6965evb-qemu/target_toolchains.gni1
18 files changed, 772 insertions, 71 deletions
diff --git a/pw_build/BUILD.gn b/pw_build/BUILD.gn
index 777ef224e..706b8d6e9 100644
--- a/pw_build/BUILD.gn
+++ b/pw_build/BUILD.gn
@@ -87,6 +87,12 @@ config("strict_warnings") {
cflags_cc = [ "-Wnon-virtual-dtor" ]
}
+# Thread safety warnings are only supported by Clang.
+config("clang_thread_safety_warnings") {
+ cflags = [ "-Wthread-safety" ]
+ defines = [ "_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS=1" ]
+}
+
# This config contains warnings that we don't necessarily recommend projects
# enable, but are enabled for upstream Pigweed for maximum project
# compatibility.
diff --git a/pw_preprocessor/public/pw_preprocessor/compiler.h b/pw_preprocessor/public/pw_preprocessor/compiler.h
index 8d002f9a9..00d3ccd9a 100644
--- a/pw_preprocessor/public/pw_preprocessor/compiler.h
+++ b/pw_preprocessor/public/pw_preprocessor/compiler.h
@@ -106,3 +106,12 @@
#else
#define PW_NO_SANITIZE(check)
#endif // __clang__
+
+// Wrapper around `__has_attribute`, which is defined by GCC 5+ and Clang and
+// evaluates to a non zero constant integer if the attribute is supported or 0
+// if not.
+#ifdef __has_attribute
+#define PW_HAVE_ATTRIBUTE(x) __has_attribute(x)
+#else
+#define PW_HAVE_ATTRIBUTE(x) 0
+#endif
diff --git a/pw_rpc/BUILD b/pw_rpc/BUILD
index dfd091090..996a8a0a3 100644
--- a/pw_rpc/BUILD
+++ b/pw_rpc/BUILD
@@ -90,6 +90,7 @@ pw_cc_library(
includes = ["public"],
deps = [
":common",
+ "//pw_sync:lock_annotations",
"//pw_sync:mutex",
],
)
diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn
index 3a5a79f82..5f98883bb 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -108,6 +108,7 @@ pw_source_set("synchronized_channel_output") {
public_configs = [ ":public_include_path" ]
public_deps = [
":common",
+ "$dir_pw_sync:lock_annotations",
"$dir_pw_sync:mutex",
]
public = [ "public/pw_rpc/synchronized_channel_output.h" ]
diff --git a/pw_rpc/public/pw_rpc/synchronized_channel_output.h b/pw_rpc/public/pw_rpc/synchronized_channel_output.h
index 348fde98e..c67fe192f 100644
--- a/pw_rpc/public/pw_rpc/synchronized_channel_output.h
+++ b/pw_rpc/public/pw_rpc/synchronized_channel_output.h
@@ -16,6 +16,7 @@
#include <algorithm>
#include "pw_rpc/channel.h"
+#include "pw_sync/lock_annotations.h"
#include "pw_sync/mutex.h"
namespace pw::rpc {
@@ -25,18 +26,20 @@ namespace pw::rpc {
// ChannelOutput implementation to run in multi-threaded contexts. More complex
// implementations may want to roll their own synchronization.
template <typename BaseChannelOutput>
-class SynchronizedChannelOutput final : public BaseChannelOutput {
+class PW_LOCKABLE("pw::rpc::SynchronizedChannelOutput")
+ SynchronizedChannelOutput final : public BaseChannelOutput {
public:
template <typename... Args>
constexpr SynchronizedChannelOutput(sync::Mutex& mutex, Args&&... args)
: BaseChannelOutput(std::forward<Args>(args)...), mutex_(mutex) {}
- std::span<std::byte> AcquireBuffer() final {
+ std::span<std::byte> AcquireBuffer() final PW_EXCLUSIVE_LOCK_FUNCTION() {
mutex_.lock();
return BaseChannelOutput::AcquireBuffer();
}
- Status SendAndReleaseBuffer(std::span<const std::byte> buffer) final {
+ Status SendAndReleaseBuffer(std::span<const std::byte> buffer) final
+ PW_UNLOCK_FUNCTION() {
Status status = BaseChannelOutput::SendAndReleaseBuffer(buffer);
mutex_.unlock();
return status;
diff --git a/pw_sync/BUILD b/pw_sync/BUILD
index f1c6620f3..e0a474110 100644
--- a/pw_sync/BUILD
+++ b/pw_sync/BUILD
@@ -92,6 +92,17 @@ pw_cc_library(
)
pw_cc_library(
+ name = "lock_annotations",
+ hdrs = [
+ "public/pw_sync/lock_annotations.h",
+ ],
+ includes = ["public"],
+ deps = [
+ "//pw_preprocessor",
+ ],
+)
+
+pw_cc_library(
name = "mutex_facade",
hdrs = [
"public/pw_sync/mutex.h",
@@ -101,8 +112,9 @@ pw_cc_library(
"mutex.cc"
],
deps = [
- PW_SYNC_MUTEX_BACKEND + "_headers",
+ ":lock_annotations",
"//pw_preprocessor",
+ PW_SYNC_MUTEX_BACKEND + "_headers",
],
)
@@ -131,10 +143,11 @@ pw_cc_library(
"timed_mutex.cc"
],
deps = [
- PW_SYNC_TIMED_MUTEX_BACKEND + "_headers",
- "//pw_chrono:system_clock",
+ ":lock_annotations",
":mutex_facade",
+ "//pw_chrono:system_clock",
"//pw_preprocessor",
+ PW_SYNC_TIMED_MUTEX_BACKEND + "_headers",
],
)
@@ -163,8 +176,9 @@ pw_cc_library(
"interrupt_spin_lock.cc"
],
deps = [
- PW_SYNC_INTERRUPT_SPIN_LOCK_BACKEND + "_headers",
+ ":lock_annotations",
"//pw_preprocessor",
+ PW_SYNC_INTERRUPT_SPIN_LOCK_BACKEND + "_headers",
],
)
diff --git a/pw_sync/BUILD.gn b/pw_sync/BUILD.gn
index 673f80b12..db702a615 100644
--- a/pw_sync/BUILD.gn
+++ b/pw_sync/BUILD.gn
@@ -47,11 +47,20 @@ pw_facade("counting_semaphore") {
sources = [ "counting_semaphore.cc" ]
}
+pw_source_set("lock_annotations") {
+ public_configs = [ ":public_include_path" ]
+ public = [ "public/pw_sync/lock_annotations.h" ]
+ public_deps = [ "$dir_pw_preprocessor" ]
+}
+
pw_facade("mutex") {
backend = pw_sync_MUTEX_BACKEND
public_configs = [ ":public_include_path" ]
public = [ "public/pw_sync/mutex.h" ]
- public_deps = [ "$dir_pw_preprocessor" ]
+ public_deps = [
+ ":lock_annotations",
+ "$dir_pw_preprocessor",
+ ]
sources = [ "mutex.cc" ]
}
@@ -71,7 +80,10 @@ pw_facade("interrupt_spin_lock") {
backend = pw_sync_INTERRUPT_SPIN_LOCK_BACKEND
public_configs = [ ":public_include_path" ]
public = [ "public/pw_sync/interrupt_spin_lock.h" ]
- public_deps = [ "$dir_pw_preprocessor" ]
+ public_deps = [
+ ":lock_annotations",
+ "$dir_pw_preprocessor",
+ ]
sources = [ "interrupt_spin_lock.cc" ]
}
diff --git a/pw_sync/docs.rst b/pw_sync/docs.rst
index de5a125aa..f2959c8c5 100644
--- a/pw_sync/docs.rst
+++ b/pw_sync/docs.rst
@@ -611,6 +611,345 @@ Example in C
pw_sync_InterruptSpinLock_Unlock(&interrupt_spin_lock);
}
+Thread Safety Lock Annotations
+==============================
+Pigweed's critical section lock primitives support Clang's thread safety
+analysis extension for C++. The analysis is completely static at compile-time.
+This is only supported when building with Clang. The annotations are no-ops when
+using different compilers.
+
+Pigweed provides the ``pw_sync/lock_annotations.h`` header file with macro
+definitions to allow developers to document the locking policies of
+multi-threaded code. The annotations can also help program analysis tools to
+identify potential thread safety issues.
+
+More information on Clang's thread safety analysis system can be found
+`here <https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`_.
+
+Enabling Clang's Analysis
+-------------------------
+In order to enable the analysis, Clang requires that the ``-Wthread-safety``
+compilation flag be used. In addition, if any STL components like
+``std::lock_guard`` are used, the STL's built in annotations have to be manually
+enabled, typically by setting the ``_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS``
+macro.
+
+If using GN, the ``pw_build:clang_thread_safety_warnings`` config is provided
+to do this for you, when added to your clang toolchain definition's default
+configs.
+
+Why use lock annotations?
+-------------------------
+Lock annotations can help warn you about potential race conditions in your code
+when using locks: you have to remember to grab lock(s) before entering a
+critical section, yuou have to remember to unlock it when you leave, and you
+have to avoid deadlocks.
+
+Clang's lock annotations let you inform the compiler and anyone reading your
+code which variables are guarded by which locks, which locks should or cannot be
+held when calling which function, which order locks should be acquired in, etc.
+
+Using Lock Annotations
+----------------------
+When referring to locks in the arguments of the attributes, you should
+use variable names or more complex expressions (e.g. ``my_object->lock_``)
+that evaluate to a concrete lock object whenever possible. If the lock
+you want to refer to is not in scope, you may use a member pointer
+(e.g. ``&MyClass::lock_``) to refer to a lock in some (unknown) object.
+
+Annotating Lock Usage
+^^^^^^^^^^^^^^^^^^^^^
+.. cpp:function:: PW_GUARDED_BY(x)
+
+ Documents if a shared field or global variable needs to be protected by a
+ lock. ``PW_GUARDED_BY()`` allows the user to specify a particular lock that
+ should be held when accessing the annotated variable.
+
+ Although this annotation (and ``PW_PT_GUARDED_BY``, below) cannot be applied
+ to local variables, a local variable and its associated lock can often be
+ combined into a small class or struct, thereby allowing the annotation.
+
+ Example:
+
+ .. code-block:: cpp
+
+ class Foo {
+ Mutex mu_;
+ int p1_ PW_GUARDED_BY(mu_);
+ ...
+ };
+
+.. cpp:function:: PW_PT_GUARDED_BY(x)
+
+ Documents if the memory location pointed to by a pointer should be guarded
+ by a lock when dereferencing the pointer.
+
+ Example:
+
+ .. code-block:: cpp
+
+ class Foo {
+ Mutex mu_;
+ int *p1_ PW_PT_GUARDED_BY(mu_);
+ ...
+ };
+
+ Note that a pointer variable to a shared memory location could itself be a
+ shared variable.
+
+ Example:
+
+ .. code-block:: cpp
+
+ // `q_`, guarded by `mu1_`, points to a shared memory location that is
+ // guarded by `mu2_`:
+ int *q_ PW_GUARDED_BY(mu1_) PW_PT_GUARDED_BY(mu2_);
+
+.. cpp:function:: PW_ACQUIRED_AFTER(...)
+.. cpp:function:: PW_ACQUIRED_BEFORE(...)
+
+ Documents the acquisition order between locks that can be held
+ simultaneously by a thread. For any two locks that need to be annotated
+ to establish an acquisition order, only one of them needs the annotation.
+ (i.e. You don't have to annotate both locks with both ``PW_ACQUIRED_AFTER``
+ and ``PW_ACQUIRED_BEFORE``.)
+
+ As with ``PW_GUARDED_BY``, this is only applicable to locks that are shared
+ fields or global variables.
+
+ Example:
+
+ .. code-block:: cpp
+
+ Mutex m1_;
+ Mutex m2_ PW_ACQUIRED_AFTER(m1_);
+
+.. cpp:function:: PW_EXCLUSIVE_LOCKS_REQUIRED(...)
+.. cpp:function:: PW_SHARED_LOCKS_REQUIRED(...)
+
+ Documents a function that expects a lock to be held prior to entry.
+ The lock is expected to be held both on entry to, and exit from, the
+ function.
+
+ An exclusive lock allows read-write access to the guarded data member(s), and
+ only one thread can acquire a lock exclusively at any one time. A shared lock
+ allows read-only access, and any number of threads can acquire a shared lock
+ concurrently.
+
+ Generally, non-const methods should be annotated with
+ ``PW_EXCLUSIVE_LOCKS_REQUIRED``, while const methods should be annotated with
+ ``PW_SHARED_LOCKS_REQUIRED``.
+
+ Example:
+
+ .. code-block:: cpp
+
+ Mutex mu1, mu2;
+ int a PW_GUARDED_BY(mu1);
+ int b PW_GUARDED_BY(mu2);
+
+ void foo() PW_EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) { ... }
+ void bar() const PW_SHARED_LOCKS_REQUIRED(mu1, mu2) { ... }
+
+.. cpp:function:: PW_LOCKS_EXCLUDED(...)
+
+ Documents the locks acquired in the body of the function. These locks
+ cannot be held when calling this function (as Pigweed's default locks are
+ non-reentrant).
+
+ Example:
+
+ .. code-block:: cpp
+
+ Mutex mu;
+ int a PW_GUARDED_BY(mu);
+
+ void foo() PW_LOCKS_EXCLUDED(mu) {
+ mu.lock();
+ ...
+ mu.unlock();
+ }
+
+.. cpp:function:: PW_LOCK_RETURNED(...)
+
+ Documents a function that returns a lock without acquiring it. For example,
+ a public getter method that returns a pointer to a private lock should
+ be annotated with ``PW_LOCK_RETURNED``.
+
+ Example:
+
+ .. code-block:: cpp
+
+ class Foo {
+ public:
+ Mutex* mu() PW_LOCK_RETURNED(mu) { return &mu; }
+
+ private:
+ Mutex mu;
+ };
+
+.. cpp:function:: PW_NO_LOCK_SAFETY_ANALYSIS()
+
+ Turns off thread safety checking within the body of a particular function.
+ This annotation is used to mark functions that are known to be correct, but
+ the locking behavior is more complicated than the analyzer can handle.
+
+Annotating Lock Objects
+^^^^^^^^^^^^^^^^^^^^^^^
+In order of lock usage annotation to work, the lock objects themselves need to
+be annotated as well. In case you are providing your own lock or psuedo-lock
+object, you can use the macros in this section to annotate it.
+
+As an example we've annotated a Lock and a RAII ScopedLocker object for you, see
+the macro documentation after for more details:
+
+.. code-block:: cpp
+
+ class PW_LOCKABLE("Lock") Lock {
+ public:
+ void Lock() PW_EXCLUSIVE_LOCK_FUNCTION();
+
+ void ReaderLock() PW_SHARED_LOCK_FUNCTION();
+
+ void Unlock() PW_UNLOCK_FUNCTION();
+
+ void ReaderUnlock() PW_SHARED_TRYLOCK_FUNCTION();
+
+ bool TryLock() PW_EXCLUSIVE_TRYLOCK_FUNCTION(true);
+
+ bool ReaderTryLock() PW_SHARED_TRYLOCK_FUNCTION(true);
+
+ void AssertHeld() PW_ASSERT_EXCLUSIVE_LOCK();
+
+ void AssertReaderHeld() PW_ASSERT_SHARED_LOCK();
+ };
+
+
+ // Tag types for selecting a constructor.
+ struct adopt_lock_t {} inline constexpr adopt_lock = {};
+ struct defer_lock_t {} inline constexpr defer_lock = {};
+ struct shared_lock_t {} inline constexpr shared_lock = {};
+
+ class PW_SCOPED_LOCKABLE ScopedLocker {
+ // Acquire lock, implicitly acquire *this and associate it with lock.
+ ScopedLocker(Lock *lock) PW_EXCLUSIVE_LOCK_FUNCTION(lock)
+ : lock_(lock), locked(true) {
+ lock->Lock();
+ }
+
+ // Assume lock is held, implicitly acquire *this and associate it with lock.
+ ScopedLocker(Lock *lock, adopt_lock_t) PW_EXCLUSIVE_LOCKS_REQUIRED(lock)
+ : lock_(lock), locked(true) {}
+
+ // Acquire lock in shared mode, implicitly acquire *this and associate it
+ // with lock.
+ ScopedLocker(Lock *lock, shared_lock_t) PW_SHARED_LOCK_FUNCTION(lock)
+ : lock_(lock), locked(true) {
+ lock->ReaderLock();
+ }
+
+ // Assume lock is held in shared mode, implicitly acquire *this and associate
+ // it with lock.
+ ScopedLocker(Lock *lock, adopt_lock_t, shared_lock_t)
+ PW_SHARED_LOCKS_REQUIRED(lock) : lock_(lock), locked(true) {}
+
+ // Assume lock is not held, implicitly acquire *this and associate it with
+ // lock.
+ ScopedLocker(Lock *lock, defer_lock_t) PW_LOCKS_EXCLUDED(lock)
+ : lock_(lock), locked(false) {}
+
+ // Release *this and all associated locks, if they are still held.
+ // There is no warning if the scope was already unlocked before.
+ ~ScopedLocker() PW_UNLOCK_FUNCTION() {
+ if (locked)
+ lock_->GenericUnlock();
+ }
+
+ // Acquire all associated locks exclusively.
+ void Lock() PW_EXCLUSIVE_LOCK_FUNCTION() {
+ lock_->Lock();
+ locked = true;
+ }
+
+ // Try to acquire all associated locks exclusively.
+ bool TryLock() PW_EXCLUSIVE_TRYLOCK_FUNCTION(true) {
+ return locked = lock_->TryLock();
+ }
+
+ // Acquire all associated locks in shared mode.
+ void ReaderLock() PW_SHARED_LOCK_FUNCTION() {
+ lock_->ReaderLock();
+ locked = true;
+ }
+
+ // Try to acquire all associated locks in shared mode.
+ bool ReaderTryLock() PW_SHARED_TRYLOCK_FUNCTION(true) {
+ return locked = lock_->ReaderTryLock();
+ }
+
+ // Release all associated locks. Warn on double unlock.
+ void Unlock() PW_UNLOCK_FUNCTION() {
+ lock_->Unlock();
+ locked = false;
+ }
+
+ // Release all associated locks. Warn on double unlock.
+ void ReaderUnlock() PW_UNLOCK_FUNCTION() {
+ lock_->ReaderUnlock();
+ locked = false;
+ }
+
+ private:
+ Lock* lock_;
+ bool locked_;
+ };
+
+.. cpp:function:: PW_LOCKABLE(name)
+
+ Documents if a class/type is a lockable type (such as the ``pw::sync::Mutex``
+ class). The name is used in the warning messages. This can also be useful on
+ classes which have locking like semantics but aren't actually locks.
+
+.. cpp:function:: PW_SCOPED_LOCKABLE()
+
+ Documents if a class does RAII locking. The name is used in the warning
+ messages.
+
+ The constructor should use ``LOCK_FUNCTION()`` to specify the lock that is
+ acquired, and the destructor should use ``UNLOCK_FUNCTION()`` with no
+ arguments; the analysis will assume that the destructor unlocks whatever the
+ constructor locked.
+
+.. cpp:function:: PW_EXCLUSIVE_LOCK_FUNCTION()
+
+ Documents functions that acquire a lock in the body of a function, and do
+ not release it.
+
+.. cpp:function:: PW_SHARED_LOCK_FUNCTION()
+
+ Documents functions that acquire a shared (reader) lock in the body of a
+ function, and do not release it.
+
+.. cpp:function:: PW_UNLOCK_FUNCTION()
+
+ Documents functions that expect a lock to be held on entry to the function,
+ and release it in the body of the function.
+
+.. cpp:function:: PW_EXCLUSIVE_TRYLOCK_FUNCTION(try_success)
+.. cpp:function:: PW_SHARED_TRYLOCK_FUNCTION(try_success)
+
+ Documents functions that try to acquire a lock, and return success or failure
+ (or a non-boolean value that can be interpreted as a boolean).
+ The first argument should be ``true`` for functions that return ``true`` on
+ success, or ``false`` for functions that return `false` on success. The second
+ argument specifies the lock that is locked on success. If unspecified, this
+ lock is assumed to be ``this``.
+
+.. cpp:function:: PW_ASSERT_EXCLUSIVE_LOCK()
+.. cpp:function:: PW_ASSERT_SHARED_LOCK()
+
+ Documents functions that dynamically check to see if a lock is held, and fail
+ if it is not held.
--------------------
Signaling Primitives
diff --git a/pw_sync/interrupt_spin_lock.cc b/pw_sync/interrupt_spin_lock.cc
index 9a8ec6a5e..659ed1916 100644
--- a/pw_sync/interrupt_spin_lock.cc
+++ b/pw_sync/interrupt_spin_lock.cc
@@ -14,6 +14,8 @@
#include "pw_sync/interrupt_spin_lock.h"
+#include "pw_sync/lock_annotations.h"
+
extern "C" void pw_sync_InterruptSpinLock_Lock(
pw_sync_InterruptSpinLock* interrupt_spin_lock) {
interrupt_spin_lock->lock();
diff --git a/pw_sync/interrupt_spin_lock_facade_test.cc b/pw_sync/interrupt_spin_lock_facade_test.cc
index 372623a44..0d8a0593b 100644
--- a/pw_sync/interrupt_spin_lock_facade_test.cc
+++ b/pw_sync/interrupt_spin_lock_facade_test.cc
@@ -49,10 +49,13 @@ TEST(InterruptSpinLock, LockUnlockStatic) {
TEST(InterruptSpinLock, TryLockUnlock) {
pw::sync::InterruptSpinLock interrupt_spin_lock;
- ASSERT_TRUE(interrupt_spin_lock.try_lock());
- // Ensure it fails to lock when already held.
- EXPECT_FALSE(interrupt_spin_lock.try_lock());
- interrupt_spin_lock.unlock();
+ const bool locked = interrupt_spin_lock.try_lock();
+ EXPECT_TRUE(locked);
+ if (locked) {
+ // Ensure it fails to lock when already held.
+ EXPECT_FALSE(interrupt_spin_lock.try_lock());
+ interrupt_spin_lock.unlock();
+ }
}
TEST(InterruptSpinLock, LockUnlockInC) {
diff --git a/pw_sync/mutex_facade_test.cc b/pw_sync/mutex_facade_test.cc
index 0fc720322..6788536e2 100644
--- a/pw_sync/mutex_facade_test.cc
+++ b/pw_sync/mutex_facade_test.cc
@@ -49,10 +49,13 @@ TEST(Mutex, LockUnlockStatic) {
TEST(Mutex, TryLockUnlock) {
pw::sync::Mutex mutex;
- ASSERT_TRUE(mutex.try_lock());
- // TODO(pwbug/291): Ensure it fails to lock when already held.
- // EXPECT_FALSE(mutex.try_lock());
- mutex.unlock();
+ const bool locked = mutex.try_lock();
+ EXPECT_TRUE(locked);
+ if (locked) {
+ // TODO(pwbug/291): Ensure it fails to lock when already held.
+ // EXPECT_FALSE(mutex.try_lock());
+ mutex.unlock();
+ }
}
TEST(Mutex, LockUnlockInC) {
diff --git a/pw_sync/public/pw_sync/interrupt_spin_lock.h b/pw_sync/public/pw_sync/interrupt_spin_lock.h
index f03c8d8f2..8c1e7f22f 100644
--- a/pw_sync/public/pw_sync/interrupt_spin_lock.h
+++ b/pw_sync/public/pw_sync/interrupt_spin_lock.h
@@ -16,6 +16,7 @@
#include <stdbool.h>
#include "pw_preprocessor/util.h"
+#include "pw_sync/lock_annotations.h"
#ifdef __cplusplus
@@ -43,7 +44,7 @@ namespace pw::sync {
//
// Precondition: Code that holds a specific InterruptSpinLock must not try to
// re-acquire it. However, it is okay to nest distinct spinlocks.
-class InterruptSpinLock {
+class PW_LOCKABLE("pw::sync::InterruptSpinLock") InterruptSpinLock {
public:
using native_handle_type = backend::NativeInterruptSpinLockHandle;
@@ -57,19 +58,19 @@ class InterruptSpinLock {
// Locks the spinlock, blocking indefinitely. Failures are fatal.
//
// Precondition: Recursive locking is undefined behavior.
- void lock();
+ void lock() PW_EXCLUSIVE_LOCK_FUNCTION();
// Attempts to lock the spinlock in a non-blocking manner.
// Returns true if the spinlock was successfully acquired.
//
// Precondition: Recursive locking is undefined behavior.
- bool try_lock();
+ bool try_lock() PW_EXCLUSIVE_TRYLOCK_FUNCTION(true);
// Unlocks the spinlock. Failures are fatal.
//
// PRECONDITION:
// The spinlock is held by the caller.
- void unlock();
+ void unlock() PW_UNLOCK_FUNCTION();
native_handle_type native_handle();
@@ -92,8 +93,11 @@ typedef struct pw_sync_InterruptSpinLock pw_sync_InterruptSpinLock;
PW_EXTERN_C_START
-void pw_sync_InterruptSpinLock_Lock(pw_sync_InterruptSpinLock* spin_lock);
-bool pw_sync_InterruptSpinLock_TryLock(pw_sync_InterruptSpinLock* spin_lock);
-void pw_sync_InterruptSpinLock_Unlock(pw_sync_InterruptSpinLock* spin_lock);
+void pw_sync_InterruptSpinLock_Lock(pw_sync_InterruptSpinLock* spin_lock)
+ PW_NO_LOCK_SAFETY_ANALYSIS;
+bool pw_sync_InterruptSpinLock_TryLock(pw_sync_InterruptSpinLock* spin_lock)
+ PW_NO_LOCK_SAFETY_ANALYSIS;
+void pw_sync_InterruptSpinLock_Unlock(pw_sync_InterruptSpinLock* spin_lock)
+ PW_NO_LOCK_SAFETY_ANALYSIS;
PW_EXTERN_C_END
diff --git a/pw_sync/public/pw_sync/lock_annotations.h b/pw_sync/public/pw_sync/lock_annotations.h
new file mode 100644
index 000000000..6d60b5502
--- /dev/null
+++ b/pw_sync/public/pw_sync/lock_annotations.h
@@ -0,0 +1,280 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+// This header file contains macro definitions for thread safety annotations
+// that allow developers to document the locking policies of multi-threaded
+// code. The annotations can also help program analysis tools to identify
+// potential thread safety issues.
+//
+// These annotations are implemented using compiler attributes. Using the macros
+// defined here instead of raw attributes allow for portability and future
+// compatibility.
+//
+// The thread safety analysis system is documented at
+// http://clang.llvm.org/docs/ThreadSafetyAnalysis.html
+//
+// When referring to locks in the arguments of the attributes, you should
+// use variable names or more complex expressions (e.g. my_object->lock_)
+// that evaluate to a concrete lock object whenever possible. If the lock
+// you want to refer to is not in scope, you may use a member pointer
+// (e.g. &MyClass::lock_) to refer to a lock in some (unknown) object.
+
+#pragma once
+
+#include "pw_preprocessor/compiler.h"
+
+// PW_GUARDED_BY()
+//
+// Documents if a shared field or global variable needs to be protected by a
+// lock. PW_GUARDED_BY() allows the user to specify a particular lock that
+// should be held when accessing the annotated variable.
+//
+// Although this annotation (and PW_PT_GUARDED_BY, below) cannot be applied to
+// local variables, a local variable and its associated lock can often be
+// combined into a small class or struct, thereby allowing the annotation.
+//
+// Example:
+//
+// class Foo {
+// Mutex mu_;
+// int p1_ PW_GUARDED_BY(mu_);
+// ...
+// };
+#if PW_HAVE_ATTRIBUTE(guarded_by)
+#define PW_GUARDED_BY(x) __attribute__((guarded_by(x)))
+#else
+#define PW_GUARDED_BY(x)
+#endif
+
+// PW_PT_GUARDED_BY()
+//
+// Documents if the memory location pointed to by a pointer should be guarded
+// by a lock when dereferencing the pointer.
+//
+// Example:
+// class Foo {
+// Mutex mu_;
+// int *p1_ PW_PT_GUARDED_BY(mu_);
+// ...
+// };
+//
+// Note that a pointer variable to a shared memory location could itself be a
+// shared variable.
+//
+// Example:
+//
+// // `q_`, guarded by `mu1_`, points to a shared memory location that is
+// // guarded by `mu2_`:
+// int *q_ PW_GUARDED_BY(mu1_) PW_PT_GUARDED_BY(mu2_);
+#if PW_HAVE_ATTRIBUTE(pt_guarded_by)
+#define PW_PT_GUARDED_BY(x) __attribute__((pt_guarded_by(x)))
+#else
+#define PW_PT_GUARDED_BY(x)
+#endif
+
+// PW_ACQUIRED_AFTER() / PW_ACQUIRED_BEFORE()
+//
+// Documents the acquisition order between locks that can be held
+// simultaneously by a thread. For any two locks that need to be annotated
+// to establish an acquisition order, only one of them needs the annotation.
+// (i.e. You don't have to annotate both locks with both PW_ACQUIRED_AFTER
+// and PW_ACQUIRED_BEFORE.)
+//
+// As with PW_GUARDED_BY, this is only applicable to locks that are shared
+// fields or global variables.
+//
+// Example:
+//
+// Mutex m1_;
+// Mutex m2_ PW_ACQUIRED_AFTER(m1_);
+#if PW_HAVE_ATTRIBUTE(acquired_after)
+#define PW_ACQUIRED_AFTER(...) __attribute__((acquired_after(__VA_ARGS__)))
+#else
+#define PW_ACQUIRED_AFTER(...)
+#endif
+
+#if PW_HAVE_ATTRIBUTE(acquired_before)
+#define PW_ACQUIRED_BEFORE(...) __attribute__((acquired_before(__VA_ARGS__)))
+#else
+#define PW_ACQUIRED_BEFORE(...)
+#endif
+
+// PW_EXCLUSIVE_LOCKS_REQUIRED() / PW_SHARED_LOCKS_REQUIRED()
+//
+// Documents a function that expects a lock to be held prior to entry.
+// The lock is expected to be held both on entry to, and exit from, the
+// function.
+//
+// An exclusive lock allows read-write access to the guarded data member(s), and
+// only one thread can acquire a lock exclusively at any one time. A shared lock
+// allows read-only access, and any number of threads can acquire a shared lock
+// concurrently.
+//
+// Generally, non-const methods should be annotated with
+// PW_EXCLUSIVE_LOCKS_REQUIRED, while const methods should be annotated with
+// PW_SHARED_LOCKS_REQUIRED.
+//
+// Example:
+//
+// Mutex mu1, mu2;
+// int a PW_GUARDED_BY(mu1);
+// int b PW_GUARDED_BY(mu2);
+//
+// void foo() PW_EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) { ... }
+// void bar() const PW_SHARED_LOCKS_REQUIRED(mu1, mu2) { ... }
+#if PW_HAVE_ATTRIBUTE(exclusive_locks_required)
+#define PW_EXCLUSIVE_LOCKS_REQUIRED(...) \
+ __attribute__((exclusive_locks_required(__VA_ARGS__)))
+#else
+#define PW_EXCLUSIVE_LOCKS_REQUIRED(...)
+#endif
+
+#if PW_HAVE_ATTRIBUTE(shared_locks_required)
+#define PW_SHARED_LOCKS_REQUIRED(...) \
+ __attribute__((shared_locks_required(__VA_ARGS__)))
+#else
+#define PW_SHARED_LOCKS_REQUIRED(...)
+#endif
+
+// PW_LOCKS_EXCLUDED()
+//
+// Documents the locks acquired in the body of the function. These locks
+// cannot be held when calling this function (as Pigweed's default locks are
+// non-reentrant).
+#if PW_HAVE_ATTRIBUTE(locks_excluded)
+#define PW_LOCKS_EXCLUDED(...) __attribute__((locks_excluded(__VA_ARGS__)))
+#else
+#define PW_LOCKS_EXCLUDED(...)
+#endif
+
+// PW_LOCK_RETURNED()
+//
+// Documents a function that returns a lock without acquiring it. For example,
+// a public getter method that returns a pointer to a private lock should
+// be annotated with PW_LOCK_RETURNED.
+#if PW_HAVE_ATTRIBUTE(lock_returned)
+#define PW_LOCK_RETURNED(x) __attribute__((lock_returned(x)))
+#else
+#define PW_LOCK_RETURNED(x)
+#endif
+
+// PW_LOCKABLE(name)
+//
+// Documents if a class/type is a lockable type (such as the `pw::sync::Mutex`
+// class). The name is used in the warning messages.
+#if PW_HAVE_ATTRIBUTE(capability)
+#define PW_LOCKABLE(name) __attribute__((capability(name)))
+#elif PW_HAVE_ATTRIBUTE(lockable)
+#define PW_LOCKABLE(name) __attribute__((lockable))
+#else
+#define PW_LOCKABLE(name)
+#endif
+
+// PW_SCOPED_LOCKABLE
+//
+// Documents if a class does RAII locking. The name is used in the warning
+// messages.
+//
+// The constructor should use `LOCK_FUNCTION()` to specify the lock that is
+// acquired, and the destructor should use `UNLOCK_FUNCTION()` with no
+// arguments; the analysis will assume that the destructor unlocks whatever the
+// constructor locked.
+#if PW_HAVE_ATTRIBUTE(scoped_lockable)
+#define PW_SCOPED_LOCKABLE __attribute__((scoped_lockable))
+#else
+#define PW_SCOPED_LOCKABLE
+#endif
+
+// PW_EXCLUSIVE_LOCK_FUNCTION()
+//
+// Documents functions that acquire a lock in the body of a function, and do
+// not release it.
+#if PW_HAVE_ATTRIBUTE(exclusive_lock_function)
+#define PW_EXCLUSIVE_LOCK_FUNCTION(...) \
+ __attribute__((exclusive_lock_function(__VA_ARGS__)))
+#else
+#define PW_EXCLUSIVE_LOCK_FUNCTION(...)
+#endif
+
+// PW_SHARED_LOCK_FUNCTION()
+//
+// Documents functions that acquire a shared (reader) lock in the body of a
+// function, and do not release it.
+#if PW_HAVE_ATTRIBUTE(shared_lock_function)
+#define PW_SHARED_LOCK_FUNCTION(...) \
+ __attribute__((shared_lock_function(__VA_ARGS__)))
+#else
+#define PW_SHARED_LOCK_FUNCTION(...)
+#endif
+
+// PW_UNLOCK_FUNCTION()
+//
+// Documents functions that expect a lock to be held on entry to the function,
+// and release it in the body of the function.
+#if PW_HAVE_ATTRIBUTE(unlock_function)
+#define PW_UNLOCK_FUNCTION(...) __attribute__((unlock_function(__VA_ARGS__)))
+#else
+#define PW_UNLOCK_FUNCTION(...)
+#endif
+
+// PW_EXCLUSIVE_TRYLOCK_FUNCTION() / PW_SHARED_TRYLOCK_FUNCTION()
+//
+// Documents functions that try to acquire a lock, and return success or failure
+// (or a non-boolean value that can be interpreted as a boolean).
+// The first argument should be `true` for functions that return `true` on
+// success, or `false` for functions that return `false` on success. The second
+// argument specifies the lock that is locked on success. If unspecified, this
+// lock is assumed to be `this`.
+#if PW_HAVE_ATTRIBUTE(exclusive_trylock_function)
+#define PW_EXCLUSIVE_TRYLOCK_FUNCTION(...) \
+ __attribute__((exclusive_trylock_function(__VA_ARGS__)))
+#else
+#define PW_EXCLUSIVE_TRYLOCK_FUNCTION(...)
+#endif
+
+#if PW_HAVE_ATTRIBUTE(shared_trylock_function)
+#define PW_SHARED_TRYLOCK_FUNCTION(...) \
+ __attribute__((shared_trylock_function(__VA_ARGS__)))
+#else
+#define PW_SHARED_TRYLOCK_FUNCTION(...)
+#endif
+
+// PW_ASSERT_EXCLUSIVE_LOCK() / PW_ASSERT_SHARED_LOCK()
+//
+// Documents functions that dynamically check to see if a lock is held, and fail
+// if it is not held.
+#if PW_HAVE_ATTRIBUTE(assert_exclusive_lock)
+#define PW_ASSERT_EXCLUSIVE_LOCK(...) \
+ __attribute__((assert_exclusive_lock(__VA_ARGS__)))
+#else
+#define PW_ASSERT_EXCLUSIVE_LOCK(...)
+#endif
+
+#if PW_HAVE_ATTRIBUTE(assert_shared_lock)
+#define PW_ASSERT_SHARED_LOCK(...) \
+ __attribute__((assert_shared_lock(__VA_ARGS__)))
+#else
+#define PW_ASSERT_SHARED_LOCK(...)
+#endif
+
+// PW_NO_LOCK_SAFETY_ANALYSIS
+//
+// Turns off thread safety checking within the body of a particular function.
+// This annotation is used to mark functions that are known to be correct, but
+// the locking behavior is more complicated than the analyzer can handle.
+#if PW_HAVE_ATTRIBUTE(no_thread_safety_analysis)
+#define PW_NO_LOCK_SAFETY_ANALYSIS __attribute__((no_thread_safety_analysis))
+#else
+#define PW_NO_LOCK_SAFETY_ANALYSIS
+#endif
diff --git a/pw_sync/public/pw_sync/mutex.h b/pw_sync/public/pw_sync/mutex.h
index ef4f22014..a2da0f798 100644
--- a/pw_sync/public/pw_sync/mutex.h
+++ b/pw_sync/public/pw_sync/mutex.h
@@ -16,6 +16,7 @@
#include <stdbool.h>
#include "pw_preprocessor/util.h"
+#include "pw_sync/lock_annotations.h"
#ifdef __cplusplus
@@ -33,7 +34,7 @@ namespace pw::sync {
// and/or backend MUST ensure that any initialization required in your
// environment is done prior to the creation and/or initialization of the native
// synchronization primitives (e.g. kernel initialization).
-class Mutex {
+class PW_LOCKABLE("pw::sync::Mutex") Mutex {
public:
using native_handle_type = backend::NativeMutexHandle;
@@ -49,7 +50,7 @@ class Mutex {
// PRECONDITION:
// The lock isn't already held by this thread. Recursive locking is
// undefined behavior.
- void lock();
+ void lock() PW_EXCLUSIVE_LOCK_FUNCTION();
// Attempts to lock the mutex in a non-blocking manner.
// Returns true if the mutex was successfully acquired.
@@ -57,13 +58,13 @@ class Mutex {
// PRECONDITION:
// The lock isn't already held by this thread. Recursive locking is
// undefined behavior.
- bool try_lock();
+ bool try_lock() PW_EXCLUSIVE_TRYLOCK_FUNCTION(true);
// Unlocks the mutex. Failures are fatal.
//
// PRECONDITION:
// The mutex is held by this thread.
- void unlock();
+ void unlock() PW_UNLOCK_FUNCTION();
native_handle_type native_handle();
@@ -86,8 +87,8 @@ typedef struct pw_sync_Mutex pw_sync_Mutex;
PW_EXTERN_C_START
-void pw_sync_Mutex_Lock(pw_sync_Mutex* mutex);
-bool pw_sync_Mutex_TryLock(pw_sync_Mutex* mutex);
-void pw_sync_Mutex_Unlock(pw_sync_Mutex* mutex);
+void pw_sync_Mutex_Lock(pw_sync_Mutex* mutex) PW_NO_LOCK_SAFETY_ANALYSIS;
+bool pw_sync_Mutex_TryLock(pw_sync_Mutex* mutex) PW_NO_LOCK_SAFETY_ANALYSIS;
+void pw_sync_Mutex_Unlock(pw_sync_Mutex* mutex) PW_NO_LOCK_SAFETY_ANALYSIS;
PW_EXTERN_C_END
diff --git a/pw_sync/public/pw_sync/timed_mutex.h b/pw_sync/public/pw_sync/timed_mutex.h
index 7774c8fad..2f44a7899 100644
--- a/pw_sync/public/pw_sync/timed_mutex.h
+++ b/pw_sync/public/pw_sync/timed_mutex.h
@@ -17,6 +17,7 @@
#include "pw_chrono/system_clock.h"
#include "pw_preprocessor/util.h"
+#include "pw_sync/lock_annotations.h"
#include "pw_sync/mutex.h"
#ifdef __cplusplus
@@ -50,7 +51,8 @@ class TimedMutex : public Mutex {
// PRECONDITION:
// The lock isn't already held by this thread. Recursive locking is
// undefined behavior.
- bool try_lock_for(chrono::SystemClock::duration for_at_least);
+ bool try_lock_for(chrono::SystemClock::duration for_at_least)
+ PW_EXCLUSIVE_TRYLOCK_FUNCTION(true);
// Attempts to lock the mutex where, if needed, blocking until at least the
// specified time_point.
@@ -59,7 +61,8 @@ class TimedMutex : public Mutex {
// PRECONDITION:
// The lock isn't already held by this thread. Recursive locking is
// undefined behavior.
- bool try_lock_until(chrono::SystemClock::time_point until_at_least);
+ bool try_lock_until(chrono::SystemClock::time_point until_at_least)
+ PW_EXCLUSIVE_TRYLOCK_FUNCTION(true);
};
} // namespace pw::sync
@@ -76,12 +79,17 @@ typedef struct pw_sync_TimedMutex pw_sync_TimedMutex;
PW_EXTERN_C_START
-void pw_sync_TimedMutex_Lock(pw_sync_TimedMutex* mutex);
-bool pw_sync_TimedMutex_TryLock(pw_sync_TimedMutex* mutex);
+void pw_sync_TimedMutex_Lock(pw_sync_TimedMutex* mutex)
+ PW_NO_LOCK_SAFETY_ANALYSIS;
+bool pw_sync_TimedMutex_TryLock(pw_sync_TimedMutex* mutex)
+ PW_NO_LOCK_SAFETY_ANALYSIS;
bool pw_sync_TimedMutex_TryLockFor(pw_sync_TimedMutex* mutex,
- pw_chrono_SystemClock_Duration for_at_least);
+ pw_chrono_SystemClock_Duration for_at_least)
+ PW_NO_LOCK_SAFETY_ANALYSIS;
bool pw_sync_TimedMutex_TryLockUntil(
- pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_TimePoint until_at_least);
-void pw_sync_TimedMutex_Unlock(pw_sync_TimedMutex* mutex);
+ pw_sync_TimedMutex* mutex,
+ pw_chrono_SystemClock_TimePoint until_at_least) PW_NO_LOCK_SAFETY_ANALYSIS;
+void pw_sync_TimedMutex_Unlock(pw_sync_TimedMutex* mutex)
+ PW_NO_LOCK_SAFETY_ANALYSIS;
PW_EXTERN_C_END
diff --git a/pw_sync/timed_mutex_facade_test.cc b/pw_sync/timed_mutex_facade_test.cc
index 7b81d990e..ad4696ee3 100644
--- a/pw_sync/timed_mutex_facade_test.cc
+++ b/pw_sync/timed_mutex_facade_test.cc
@@ -65,27 +65,33 @@ TEST(TimedMutex, LockUnlockStatic) {
TEST(TimedMutex, TryLockUnlock) {
pw::sync::TimedMutex mutex;
- ASSERT_TRUE(mutex.try_lock());
- // TODO(pwbug/291): Ensure it fails to lock when already held.
- // EXPECT_FALSE(mutex.try_lock());
- mutex.unlock();
+ const bool locked = mutex.try_lock();
+ EXPECT_TRUE(locked);
+ if (locked) {
+ // TODO(pwbug/291): Ensure it fails to lock when already held.
+ // EXPECT_FALSE(mutex.try_lock());
+ mutex.unlock();
+ }
}
TEST(TimedMutex, TryLockUnlockFor) {
pw::sync::TimedMutex mutex;
SystemClock::time_point before = SystemClock::now();
- ASSERT_TRUE(mutex.try_lock_for(kRoundedArbitraryDuration));
- SystemClock::duration time_elapsed = SystemClock::now() - before;
- EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-
- // TODO(pwbug/291): Ensure it blocks fails to lock when already held.
- // before = SystemClock::now();
- // EXPECT_FALSE(mutex.try_lock_for(kRoundedArbitraryDuration));
- // time_elapsed = SystemClock::now() - before;
- /// EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
-
- mutex.unlock();
+ const bool locked = mutex.try_lock_for(kRoundedArbitraryDuration);
+ EXPECT_TRUE(locked);
+ if (locked) {
+ SystemClock::duration time_elapsed = SystemClock::now() - before;
+ EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
+
+ // TODO(pwbug/291): Ensure it blocks fails to lock when already held.
+ // before = SystemClock::now();
+ // EXPECT_FALSE(mutex.try_lock_for(kRoundedArbitraryDuration));
+ // time_elapsed = SystemClock::now() - before;
+ /// EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
+
+ mutex.unlock();
+ }
}
TEST(TimedMutex, TryLockUnlockUntil) {
@@ -93,15 +99,19 @@ TEST(TimedMutex, TryLockUnlockUntil) {
const SystemClock::time_point deadline =
SystemClock::now() + kRoundedArbitraryDuration;
- ASSERT_TRUE(mutex.try_lock_until(deadline));
- EXPECT_LT(SystemClock::now(), deadline);
-
- // TODO(pwbug/291): Ensure it blocks fails to lock when already held.
- // EXPECT_FALSE(
- // mutex.try_lock_until(SystemClock::now() + kRoundedArbitraryDuration));
- // EXPECT_GE(SystemClock::now(), deadline);
-
- mutex.unlock();
+ const bool locked = mutex.try_lock_until(deadline);
+ EXPECT_TRUE(locked);
+ if (locked) {
+ EXPECT_LT(SystemClock::now(), deadline);
+
+ // TODO(pwbug/291): Ensure it blocks fails to lock when already held.
+ // EXPECT_FALSE(
+ // mutex.try_lock_until(SystemClock::now() +
+ // kRoundedArbitraryDuration));
+ // EXPECT_GE(SystemClock::now(), deadline);
+
+ mutex.unlock();
+ }
}
TEST(TimedMutex, LockUnlockInC) {
@@ -155,7 +165,7 @@ TEST(TimedMutex, TryLockUnlockUntilInC) {
// EXPECT_GE(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
// deadline.duration_since_epoch.ticks);
- mutex.unlock();
+ pw_sync_TimedMutex_CallUnlock(&mutex);
}
} // namespace
diff --git a/targets/host/target_toolchains.gni b/targets/host/target_toolchains.gni
index 6ff3789e6..9a7a73341 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -108,7 +108,11 @@ _os_specific_config = {
}
}
-_target_default_configs = [ "$dir_pw_build:extra_strict_warnings" ]
+_clang_default_configs = [
+ "$dir_pw_build:extra_strict_warnings",
+ "$dir_pw_build:clang_thread_safety_warnings",
+]
+_gcc_default_configs = [ "$dir_pw_build:extra_strict_warnings" ]
pw_target_toolchain_host = {
_excluded_members = [
@@ -124,7 +128,7 @@ pw_target_toolchain_host = {
forward_variables_from(_toolchain_base.defaults, "*")
forward_variables_from(_host_common, "*")
forward_variables_from(_os_specific_config, "*")
- default_configs += _target_default_configs
+ default_configs += _clang_default_configs
}
}
@@ -136,7 +140,7 @@ pw_target_toolchain_host = {
forward_variables_from(_toolchain_base.defaults, "*")
forward_variables_from(_host_common, "*")
forward_variables_from(_os_specific_config, "*")
- default_configs += _target_default_configs
+ default_configs += _clang_default_configs
}
}
@@ -148,7 +152,7 @@ pw_target_toolchain_host = {
forward_variables_from(_toolchain_base.defaults, "*")
forward_variables_from(_host_common, "*")
forward_variables_from(_os_specific_config, "*")
- default_configs += _target_default_configs
+ default_configs += _clang_default_configs
}
}
@@ -160,7 +164,7 @@ pw_target_toolchain_host = {
forward_variables_from(_toolchain_base.defaults, "*")
forward_variables_from(_host_common, "*")
forward_variables_from(_os_specific_config, "*")
- default_configs += _target_default_configs
+ default_configs += _clang_default_configs
}
}
@@ -172,7 +176,7 @@ pw_target_toolchain_host = {
forward_variables_from(_toolchain_base.defaults, "*")
forward_variables_from(_host_common, "*")
forward_variables_from(_os_specific_config, "*")
- default_configs += _target_default_configs
+ default_configs += _gcc_default_configs
}
}
@@ -184,7 +188,7 @@ pw_target_toolchain_host = {
forward_variables_from(_toolchain_base.defaults, "*")
forward_variables_from(_host_common, "*")
forward_variables_from(_os_specific_config, "*")
- default_configs += _target_default_configs
+ default_configs += _gcc_default_configs
}
}
@@ -196,7 +200,7 @@ pw_target_toolchain_host = {
forward_variables_from(_toolchain_base.defaults, "*")
forward_variables_from(_host_common, "*")
forward_variables_from(_os_specific_config, "*")
- default_configs += _target_default_configs
+ default_configs += _gcc_default_configs
}
}
}
diff --git a/targets/lm3s6965evb-qemu/target_toolchains.gni b/targets/lm3s6965evb-qemu/target_toolchains.gni
index a5c05ceb2..c3fba371b 100644
--- a/targets/lm3s6965evb-qemu/target_toolchains.gni
+++ b/targets/lm3s6965evb-qemu/target_toolchains.gni
@@ -71,6 +71,7 @@ _gcc_target_default_configs = [
]
_clang_target_default_configs = [
+ "$dir_pw_build:clang_thread_safety_warnings",
"$dir_pw_build:extra_strict_warnings",
"$dir_pw_toolchain/arm_clang:enable_float_printf",
]