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>
diff --git a/pw_build/BUILD.gn b/pw_build/BUILD.gn
index 777ef22..706b8d6 100644
--- a/pw_build/BUILD.gn
+++ b/pw_build/BUILD.gn
@@ -87,6 +87,12 @@
   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 8d002f9..00d3ccd 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 dfd0910..996a8a0 100644
--- a/pw_rpc/BUILD
+++ b/pw_rpc/BUILD
@@ -90,6 +90,7 @@
     includes = ["public"],
     deps = [
         ":common",
+        "//pw_sync:lock_annotations",
         "//pw_sync:mutex",
     ],
 )
diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn
index 3a5a79f..5f98883 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -108,6 +108,7 @@
   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 348fde9..c67fe19 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 @@
 // 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 f1c6620..e0a4741 100644
--- a/pw_sync/BUILD
+++ b/pw_sync/BUILD
@@ -92,6 +92,17 @@
 )
 
 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 @@
         "mutex.cc"
     ],
     deps = [
-        PW_SYNC_MUTEX_BACKEND + "_headers",
+		    ":lock_annotations",
         "//pw_preprocessor",
+        PW_SYNC_MUTEX_BACKEND + "_headers",
     ],
 )
 
@@ -131,10 +143,11 @@
         "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 @@
         "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 673f80b..db702a6 100644
--- a/pw_sync/BUILD.gn
+++ b/pw_sync/BUILD.gn
@@ -47,11 +47,20 @@
   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 @@
   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 de5a125..f2959c8 100644
--- a/pw_sync/docs.rst
+++ b/pw_sync/docs.rst
@@ -611,6 +611,345 @@
     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 9a8ec6a..659ed19 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 372623a..0d8a059 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, 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 0fc7203..6788536 100644
--- a/pw_sync/mutex_facade_test.cc
+++ b/pw_sync/mutex_facade_test.cc
@@ -49,10 +49,13 @@
 
 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 f03c8d8..8c1e7f2 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 @@
 //
 // 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 @@
   // 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 @@
 
 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 0000000..6d60b55
--- /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 ef4f220..a2da0f7 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 @@
 // 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 @@
   // 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 @@
   // 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 @@
 
 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 7774c8f..2f44a78 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 @@
   // 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 @@
   // 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 @@
 
 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 7b81d99..ad4696e 100644
--- a/pw_sync/timed_mutex_facade_test.cc
+++ b/pw_sync/timed_mutex_facade_test.cc
@@ -65,27 +65,33 @@
 
 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);
+  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);
+    // 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();
+    mutex.unlock();
+  }
 }
 
 TEST(TimedMutex, TryLockUnlockUntil) {
@@ -93,15 +99,19 @@
 
   const SystemClock::time_point deadline =
       SystemClock::now() + kRoundedArbitraryDuration;
-  ASSERT_TRUE(mutex.try_lock_until(deadline));
-  EXPECT_LT(SystemClock::now(), deadline);
+  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);
+    // 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();
+    mutex.unlock();
+  }
 }
 
 TEST(TimedMutex, LockUnlockInC) {
@@ -155,7 +165,7 @@
   // 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 6ff3789..9a7a733 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -108,7 +108,11 @@
   }
 }
 
-_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 @@
       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 @@
       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 @@
       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 @@
       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 @@
       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 @@
       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 @@
       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 a5c05ce..c3fba37 100644
--- a/targets/lm3s6965evb-qemu/target_toolchains.gni
+++ b/targets/lm3s6965evb-qemu/target_toolchains.gni
@@ -71,6 +71,7 @@
 ]
 
 _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",
 ]