diff options
author | Ewout van Bekkum <ewout@google.com> | 2021-04-08 08:51:16 -0700 |
---|---|---|
committer | CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2021-04-12 16:53:37 +0000 |
commit | cc9ef8367394c28f9a57fe0588d6fd9c3f8d0f87 (patch) | |
tree | f386a891ca1823a7c6d0cef6d6668f17e8de6d87 | |
parent | 597ac2a7e9ec542a61c48f6e3c58c06132ed6a0c (diff) | |
download | pigweed-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.gn | 6 | ||||
-rw-r--r-- | pw_preprocessor/public/pw_preprocessor/compiler.h | 9 | ||||
-rw-r--r-- | pw_rpc/BUILD | 1 | ||||
-rw-r--r-- | pw_rpc/BUILD.gn | 1 | ||||
-rw-r--r-- | pw_rpc/public/pw_rpc/synchronized_channel_output.h | 9 | ||||
-rw-r--r-- | pw_sync/BUILD | 22 | ||||
-rw-r--r-- | pw_sync/BUILD.gn | 16 | ||||
-rw-r--r-- | pw_sync/docs.rst | 339 | ||||
-rw-r--r-- | pw_sync/interrupt_spin_lock.cc | 2 | ||||
-rw-r--r-- | pw_sync/interrupt_spin_lock_facade_test.cc | 11 | ||||
-rw-r--r-- | pw_sync/mutex_facade_test.cc | 11 | ||||
-rw-r--r-- | pw_sync/public/pw_sync/interrupt_spin_lock.h | 18 | ||||
-rw-r--r-- | pw_sync/public/pw_sync/lock_annotations.h | 280 | ||||
-rw-r--r-- | pw_sync/public/pw_sync/mutex.h | 15 | ||||
-rw-r--r-- | pw_sync/public/pw_sync/timed_mutex.h | 22 | ||||
-rw-r--r-- | pw_sync/timed_mutex_facade_test.cc | 60 | ||||
-rw-r--r-- | targets/host/target_toolchains.gni | 20 | ||||
-rw-r--r-- | targets/lm3s6965evb-qemu/target_toolchains.gni | 1 |
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 μ } + + 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", ] |