Bug 1207753 - Base thread-safety attribution support r=nika

With additions of MOZ_UNANNOTATED for *Mutex/*Monitor/RWLock classes

Differential Revision: https://phabricator.services.mozilla.com/D130606
This commit is contained in:
Randell Jesup 2022-03-16 20:10:04 +00:00
parent b1b54e8f25
commit e72ce3cd6c
16 changed files with 666 additions and 151 deletions

View File

@ -21,6 +21,7 @@
#include "mozilla/MemoryChecking.h"
#include "mozilla/OperatorNewExtensions.h"
#include "mozilla/Poison.h"
#include "mozilla/ThreadSafety.h"
class nsCycleCollectionTraversalCallback;
@ -636,7 +637,13 @@ class MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS Maybe
constexpr void reset() {
if (isSome()) {
if constexpr (!std::is_trivially_destructible_v<T>) {
/*
* Static analyzer gets confused if we have Maybe<MutexAutoLock>,
* so we suppress thread-safety warnings here
*/
PUSH_IGNORE_THREAD_SAFETY
ref().T::~T();
POP_THREAD_SAFETY
poisonData();
}
mIsSome = false;

135
mfbt/ThreadSafety.h Normal file
View File

@ -0,0 +1,135 @@
// Note: the file is largely imported directly from WebRTC upstream, so
// comments may not completely apply to Mozilla's usage.
//
// Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
//
// Borrowed from
// https://code.google.com/p/gperftools/source/browse/src/base/thread_annotations.h
// but adapted for clang attributes instead of the gcc.
//
// This header file contains the macro definitions for thread safety
// annotations that allow the developers to document the locking policies
// of their multi-threaded code. The annotations can also help program
// analysis tools to identify potential thread safety issues.
#ifndef mozilla_ThreadSafety_h
#define mozilla_ThreadSafety_h
#include "mozilla/Attributes.h"
#if defined(__clang__) && (!defined(SWIG))
# define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
// Allow for localized suppression of thread-safety warnings; finer-grained
// than NO_THREAD_SAFETY_ANALYSIS
# define PUSH_IGNORE_THREAD_SAFETY \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wthread-safety\"")
# define POP_THREAD_SAFETY _Pragma("GCC diagnostic pop")
#else
# define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
# define PUSH_IGNORE_THREAD_SAFETY
# define POP_THREAD_SAFETY
#endif
// Document if a shared variable/field needs to be protected by a lock.
// GUARDED_BY allows the user to specify a particular lock that should be
// held when accessing the annotated variable, while GUARDED_VAR only
// indicates a shared variable should be guarded (by any lock). GUARDED_VAR
// is primarily used when the client cannot express the name of the lock.
#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
#define GUARDED_VAR THREAD_ANNOTATION_ATTRIBUTE__(guarded)
// Document if the memory location pointed to by a pointer should be guarded
// by a lock when dereferencing the pointer. Similar to GUARDED_VAR,
// PT_GUARDED_VAR is primarily used when the client cannot express the name
// of the lock. Note that a pointer variable to a shared memory location
// could itself be a shared variable. For example, if a shared global pointer
// q, which is guarded by mu1, points to a shared memory location that is
// guarded by mu2, q should be annotated as follows:
// int *q GUARDED_BY(mu1) PT_GUARDED_BY(mu2);
#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
#define PT_GUARDED_VAR THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded)
// Document 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 ACQUIRED_AFTER
// and ACQUIRED_BEFORE.)
#define ACQUIRED_AFTER(...) THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
#define ACQUIRED_BEFORE(...) THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
// The following three annotations document the lock requirements for
// functions/methods.
// Document if a function expects certain locks to be held before it is called
#define REQUIRES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))
#define REQUIRES_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__))
// Document the locks acquired in the body of the function. These locks
// cannot be held when calling this function (as google3's Mutex locks are
// non-reentrant).
#define EXCLUDES(x) THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(x))
// Document the lock the annotated function returns without acquiring it.
#define RETURN_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
// Document if a class/type is a lockable type (such as the Mutex class).
#define CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(lockable)
// Document if a class is a scoped lockable type (such as the MutexLock class).
#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
// The following annotations specify lock and unlock primitives.
#define CAPABILITY_ACQUIRE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__))
#define EXCLUSIVE_RELEASE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
#define ACQUIRE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__))
#define TRY_ACQUIRE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(exclusive_trylock_function(__VA_ARGS__))
#define SHARED_TRYLOCK_FUNCTION(...) \
THREAD_ANNOTATION_ATTRIBUTE__(shared_trylock_function(__VA_ARGS__))
#define CAPABILITY_RELEASE(...) THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__))
// An escape hatch for thread safety analysis to ignore the annotated function.
#define NO_THREAD_SAFETY_ANALYSIS \
THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
// Newer capabilities
#define ASSERT_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
#define ASSERT_SHARED_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
// Additions from current clang assertions.
// Note: new-style definitions, since these didn't exist in the old style
#define RELEASE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
#define RELEASE_GENERIC(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__))
// Mozilla additions:
// AutoUnlock is supported by clang currently, but oddly you must use
// EXCLUSIVE_RELEASE() for both the RAII constructor *and* the destructor.
// This hides the ugliness until they fix it upstream.
#define SCOPED_UNLOCK_RELEASE(...) EXCLUSIVE_RELEASE(__VA_ARGS__)
#define SCOPED_UNLOCK_REACQUIRE(...) EXCLUSIVE_RELEASE(__VA_ARGS__)
#endif /* mozilla_ThreadSafety_h */

View File

@ -108,6 +108,7 @@ EXPORTS.mozilla = [
"TemplateLib.h",
"TextUtils.h",
"ThreadLocal.h",
"ThreadSafety.h",
"ThreadSafeWeakPtr.h",
"ToString.h",
"Tuple.h",

View File

@ -10,6 +10,7 @@
#define BaseProfilerDetail_h
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/PlatformMutex.h"
#include "mozilla/PlatformRWLock.h"

View File

@ -9,10 +9,11 @@
#include "mozilla/Atomics.h"
#include "mozilla/CondVar.h"
#include "mozilla/ThreadSafety.h"
namespace mozilla {
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticMonitor {
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS CAPABILITY StaticMonitor {
public:
// In debug builds, check that mMutex is initialized for us as we expect by
// the compiler. In non-debug builds, don't declare a constructor so that
@ -21,17 +22,20 @@ class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticMonitor {
StaticMonitor() { MOZ_ASSERT(!mMutex); }
#endif
void Lock() { Mutex()->Lock(); }
void Lock() CAPABILITY_ACQUIRE() { Mutex()->Lock(); }
void Unlock() { Mutex()->Unlock(); }
void Unlock() CAPABILITY_RELEASE() { Mutex()->Unlock(); }
void Wait() { CondVar()->Wait(); }
CVStatus Wait(TimeDuration aDuration) { return CondVar()->Wait(aDuration); }
CVStatus Wait(TimeDuration aDuration) {
AssertCurrentThreadOwns();
return CondVar()->Wait(aDuration);
}
void Notify() { CondVar()->Notify(); }
void NotifyAll() { CondVar()->NotifyAll(); }
void AssertCurrentThreadOwns() {
void AssertCurrentThreadOwns() ASSERT_CAPABILITY(this) {
#ifdef DEBUG
Mutex()->AssertCurrentThreadOwns();
#endif
@ -82,14 +86,14 @@ class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticMonitor {
static void operator delete(void*);
};
class MOZ_STACK_CLASS StaticMonitorAutoLock {
class MOZ_STACK_CLASS SCOPED_CAPABILITY StaticMonitorAutoLock {
public:
explicit StaticMonitorAutoLock(StaticMonitor& aMonitor)
explicit StaticMonitorAutoLock(StaticMonitor& aMonitor) CAPABILITY_ACQUIRE(aMonitor)
: mMonitor(&aMonitor) {
mMonitor->Lock();
}
~StaticMonitorAutoLock() { mMonitor->Unlock(); }
~StaticMonitorAutoLock() CAPABILITY_RELEASE() { mMonitor->Unlock(); }
void Wait() { mMonitor->Wait(); }
CVStatus Wait(TimeDuration aDuration) { return mMonitor->Wait(aDuration); }

View File

@ -26,7 +26,7 @@ namespace mozilla {
* initialized to 0 in order to initialize mMutex. It is only safe to use
* StaticMutex as a global or static variable.
*/
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticMutex {
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS CAPABILITY StaticMutex {
public:
// In debug builds, check that mMutex is initialized for us as we expect by
// the compiler. In non-debug builds, don't declare a constructor so that
@ -35,11 +35,11 @@ class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticMutex {
StaticMutex() { MOZ_ASSERT(!mMutex); }
#endif
void Lock() { Mutex()->Lock(); }
void Lock() CAPABILITY_ACQUIRE() { Mutex()->Lock(); }
void Unlock() { Mutex()->Unlock(); }
void Unlock() CAPABILITY_RELEASE() { Mutex()->Unlock(); }
void AssertCurrentThreadOwns() {
void AssertCurrentThreadOwns() ASSERT_CAPABILITY(this) {
#ifdef DEBUG
Mutex()->AssertCurrentThreadOwns();
#endif

View File

@ -71,7 +71,7 @@ static void DisableCrashReporter() {
// Single-threaded sanity tests
// Stupidest possible deadlock.
static int Sanity_Child() {
static int Sanity_Child() NO_THREAD_SAFETY_ANALYSIS {
DisableCrashReporter();
MUTEX m1("dd.sanity.m1");
@ -92,7 +92,7 @@ TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(SanityDeathTest)) {
}
// Slightly less stupid deadlock.
static int Sanity2_Child() {
static int Sanity2_Child() NO_THREAD_SAFETY_ANALYSIS {
DisableCrashReporter();
MUTEX m1("dd.sanity2.m1");
@ -118,7 +118,7 @@ TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity2DeathTest)) {
#if 0
// Temporarily disabled, see bug 1370644.
int
Sanity3_Child()
Sanity3_Child() NO_THREAD_SAFETY_ANALYSIS
{
DisableCrashReporter();
@ -156,7 +156,7 @@ TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity3DeathTest))
}
#endif
static int Sanity4_Child() {
static int Sanity4_Child() NO_THREAD_SAFETY_ANALYSIS {
DisableCrashReporter();
mozilla::ReentrantMonitor m1 MOZ_UNANNOTATED("dd.sanity4.m1");
@ -179,7 +179,7 @@ TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity4DeathTest)) {
ASSERT_DEATH_IF_SUPPORTED(Sanity4_Child(), regex);
}
static int Sanity5_Child() {
static int Sanity5_Child() NO_THREAD_SAFETY_ANALYSIS {
DisableCrashReporter();
mozilla::RecursiveMutex m1 MOZ_UNANNOTATED("dd.sanity4.m1");
@ -225,7 +225,7 @@ struct ThreadState {
#if 0
// Temporarily disabled, see bug 1370644.
static void
TwoThreads_thread(void* arg)
TwoThreads_thread(void* arg) NO_THREAD_SAFETY_ANALYSIS
{
ThreadState* state = static_cast<ThreadState*>(arg);
@ -247,7 +247,7 @@ TwoThreads_thread(void* arg)
}
int
TwoThreads_Child()
TwoThreads_Child() NO_THREAD_SAFETY_ANALYSIS
{
DisableCrashReporter();
@ -284,7 +284,7 @@ TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(TwoThreadsDeathTest))
}
#endif
static void ContentionNoDeadlock_thread(void* arg) {
static void ContentionNoDeadlock_thread(void* arg) NO_THREAD_SAFETY_ANALYSIS {
const uint32_t K = 100000;
ThreadState* state = static_cast<ThreadState*>(arg);
@ -300,7 +300,7 @@ static void ContentionNoDeadlock_thread(void* arg) {
}
}
static int ContentionNoDeadlock_Child() {
static int ContentionNoDeadlock_Child() NO_THREAD_SAFETY_ANALYSIS {
const size_t kMutexCount = 4;
PRThread* threads[3];

View File

@ -15,7 +15,7 @@ using mozilla::RecursiveMutexAutoLock;
// well, actually recursively acquirable.
TEST(RecursiveMutex, SmokeTest)
{
NO_THREAD_SAFETY_ANALYSIS {
RecursiveMutex mutex("testing mutex");
RecursiveMutexAutoLock lock1(mutex);

View File

@ -21,7 +21,7 @@ static PRThread* spawn(void (*run)(void*), void* arg) {
// Sanity check: tests that can be done on a single thread
//
TEST(Synchronization, Sanity)
{
NO_THREAD_SAFETY_ANALYSIS {
Mutex lock("sanity::lock");
lock.Lock();
lock.AssertCurrentThreadOwns();
@ -112,7 +112,7 @@ TEST(Synchronization, MonitorContention)
static ReentrantMonitor* gMon2;
static void MonitorContention2_thread(void* /*arg*/) {
static void MonitorContention2_thread(void* /*arg*/) NO_THREAD_SAFETY_ANALYSIS {
for (int i = 0; i < 100000; ++i) {
gMon2->Enter();
gMon2->AssertCurrentThreadIn();
@ -144,7 +144,7 @@ TEST(Synchronization, MonitorContention2)
static ReentrantMonitor* gMon3;
static int32_t gMonFirst;
static void MonitorSyncSanity_thread(void* /*arg*/) {
static void MonitorSyncSanity_thread(void* /*arg*/) NO_THREAD_SAFETY_ANALYSIS {
gMon3->Enter();
gMon3->AssertCurrentThreadIn();
if (gMonFirst) {
@ -294,7 +294,7 @@ TEST(Synchronization, AutoUnlock)
// AutoMonitor tests
//
TEST(Synchronization, AutoMonitor)
{
NO_THREAD_SAFETY_ANALYSIS {
ReentrantMonitor m1("automonitor");
ReentrantMonitor m2("automonitor2");

View File

@ -502,7 +502,7 @@ void RecursiveMutex::Unlock() {
UnlockInternal();
}
void RecursiveMutex::AssertCurrentThreadIn() {
void RecursiveMutex::AssertCurrentThreadIn() const {
MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
}

View File

@ -87,19 +87,21 @@ class OffTheBooksCondVar : BlockingResourceBase {
* AssertCurrentThreadOwnsMutex
* @see Mutex::AssertCurrentThreadOwns
**/
void AssertCurrentThreadOwnsMutex() { mLock->AssertCurrentThreadOwns(); }
void AssertCurrentThreadOwnsMutex() const ASSERT_CAPABILITY(mLock) {
mLock->AssertCurrentThreadOwns();
}
/**
* AssertNotCurrentThreadOwnsMutex
* @see Mutex::AssertNotCurrentThreadOwns
**/
void AssertNotCurrentThreadOwnsMutex() {
void AssertNotCurrentThreadOwnsMutex() const ASSERT_CAPABILITY(!mLock) {
mLock->AssertNotCurrentThreadOwns();
}
#else
void AssertCurrentThreadOwnsMutex() {}
void AssertNotCurrentThreadOwnsMutex() {}
void AssertCurrentThreadOwnsMutex() const ASSERT_CAPABILITY(mLock) {}
void AssertNotCurrentThreadOwnsMutex() const ASSERT_CAPABILITY(!mLock) {}
#endif // ifdef DEBUG

View File

@ -21,16 +21,16 @@ namespace mozilla {
* to instead use the RAII wrappers MonitorAutoLock and
* MonitorAutoUnlock.
*/
class Monitor {
class CAPABILITY Monitor {
public:
explicit Monitor(const char* aName)
: mMutex(aName), mCondVar(mMutex, "[Monitor.mCondVar]") {}
~Monitor() = default;
void Lock() { mMutex.Lock(); }
[[nodiscard]] bool TryLock() { return mMutex.TryLock(); }
void Unlock() { mMutex.Unlock(); }
void Lock() CAPABILITY_ACQUIRE() { mMutex.Lock(); }
[[nodiscard]] bool TryLock() TRY_ACQUIRE(true) { return mMutex.TryLock(); }
void Unlock() CAPABILITY_RELEASE() { mMutex.Unlock(); }
void Wait() { mCondVar.Wait(); }
CVStatus Wait(TimeDuration aDuration) { return mCondVar.Wait(aDuration); }
@ -38,21 +38,77 @@ class Monitor {
void Notify() { mCondVar.Notify(); }
void NotifyAll() { mCondVar.NotifyAll(); }
void AssertCurrentThreadOwns() const { mMutex.AssertCurrentThreadOwns(); }
void AssertNotCurrentThreadOwns() const {
void AssertCurrentThreadOwns() const ASSERT_CAPABILITY(this) {
mMutex.AssertCurrentThreadOwns();
}
void AssertNotCurrentThreadOwns() const ASSERT_CAPABILITY(!this) {
mMutex.AssertNotCurrentThreadOwns();
}
private:
Monitor();
Monitor(const Monitor&);
Monitor& operator=(const Monitor&);
Monitor() = delete;
Monitor(const Monitor&) = delete;
Monitor& operator=(const Monitor&) = delete;
Mutex mMutex;
CondVar mCondVar;
};
/**
* MonitorSingleWriter
*
* Monitor where a single writer exists, so that reads from the same thread
* will not generate data races or consistency issues.
*
* When possible, use MonitorAutoLock/MonitorAutoUnlock to lock/unlock this
* monitor within a scope, instead of calling Lock/Unlock directly.
*
* This requires an object implementing Mutex's SingleWriterLockOwner, so
* we can do correct-thread checks.
*/
class MonitorSingleWriter : public Monitor {
public:
// aOwner should be the object that contains the mutex, typically. We
// will use that object (which must have a lifetime the same or greater
// than this object) to verify that we're running on the correct thread,
// typically only in DEBUG builds
explicit MonitorSingleWriter(const char* aName, SingleWriterLockOwner* aOwner)
: Monitor(aName)
#ifdef DEBUG
,
mOwner(aOwner)
#endif
{
MOZ_COUNT_CTOR(MonitorSingleWriter);
MOZ_ASSERT(mOwner);
}
MOZ_COUNTED_DTOR(MonitorSingleWriter)
void AssertOnWritingThread() const ASSERT_CAPABILITY(this) {
MOZ_ASSERT(mOwner->OnWritingThread());
}
void AssertOnWritingThreadOrHeld() const ASSERT_CAPABILITY(this) {
#ifdef DEBUG
if (!mOwner->OnWritingThread()) {
AssertCurrentThreadOwns();
}
#endif
}
private:
#ifdef DEBUG
SingleWriterLockOwner* mOwner MOZ_UNSAFE_REF(
"This is normally the object that contains the MonitorSingleWriter, so "
"we don't want to hold a reference to ourselves");
#endif
MonitorSingleWriter() = delete;
MonitorSingleWriter(const MonitorSingleWriter&) = delete;
MonitorSingleWriter& operator=(const MonitorSingleWriter&) = delete;
};
/**
* Lock the monitor for the lexical scope instances of this class are
* bound to (except for MonitorAutoUnlock in nested scopes).
@ -60,30 +116,79 @@ class Monitor {
* The monitor must be unlocked when instances of this class are
* created.
*/
class MOZ_STACK_CLASS MonitorAutoLock {
namespace detail {
template <typename T>
class SCOPED_CAPABILITY MOZ_STACK_CLASS BaseMonitorAutoLock {
public:
explicit MonitorAutoLock(Monitor& aMonitor) : mMonitor(&aMonitor) {
explicit BaseMonitorAutoLock(T& aMonitor) CAPABILITY_ACQUIRE(aMonitor)
: mMonitor(&aMonitor) {
mMonitor->Lock();
}
~MonitorAutoLock() { mMonitor->Unlock(); }
~BaseMonitorAutoLock() CAPABILITY_RELEASE() { mMonitor->Unlock(); }
void Wait() { mMonitor->Wait(); }
CVStatus Wait(TimeDuration aDuration) { return mMonitor->Wait(aDuration); }
void Notify() { mMonitor->Notify(); }
void NotifyAll() { mMonitor->NotifyAll(); }
// Assert that aLock is the monitor passed to the constructor and that the
// current thread owns the monitor. In coding patterns such as:
//
// void LockedMethod(const BaseAutoLock<T>& aProofOfLock)
// {
// aProofOfLock.AssertOwns(mMonitor);
// ...
// }
//
// Without this assertion, it could be that mMonitor is not actually
// locked. It's possible to have code like:
//
// BaseAutoLock lock(someMonitor);
// ...
// BaseAutoUnlock unlock(someMonitor);
// ...
// LockedMethod(lock);
//
// and in such a case, simply asserting that the monitor pointers match is not
// sufficient; monitor ownership must be asserted as well.
//
// Note that if you are going to use the coding pattern presented above, you
// should use this method in preference to using AssertCurrentThreadOwns on
// the mutex you expected to be held, since this method provides stronger
// guarantees.
void AssertOwns(const T& aMonitor) const ASSERT_CAPABILITY(aMonitor) {
MOZ_ASSERT(&aMonitor == mMonitor);
mMonitor->AssertCurrentThreadOwns();
}
private:
MonitorAutoLock();
MonitorAutoLock(const MonitorAutoLock&);
MonitorAutoLock& operator=(const MonitorAutoLock&);
BaseMonitorAutoLock() = delete;
BaseMonitorAutoLock(const BaseMonitorAutoLock&) = delete;
BaseMonitorAutoLock& operator=(const BaseMonitorAutoLock&) = delete;
static void* operator new(size_t) noexcept(true);
friend class MonitorAutoUnlock;
Monitor* mMonitor;
protected:
T* mMonitor;
};
} // namespace detail
typedef detail::BaseMonitorAutoLock<Monitor> MonitorAutoLock;
typedef detail::BaseMonitorAutoLock<MonitorSingleWriter>
MonitorSingleWriterAutoLock;
// clang-format off
// Use if we've done AssertOnWritingThread(), and then later need to take the
// lock to write to a protected member. Instead of
// MutexSingleWriterAutoLock lock(mutex)
// use
// MutexSingleWriterAutoLockOnThread(lock, mutex)
// clang-format on
#define MonitorSingleWriterAutoLockOnThread(lock, monitor) \
PUSH_IGNORE_THREAD_SAFETY \
MonitorSingleWriterAutoLock lock(monitor); \
POP_THREAD_SAFETY
/**
* Unlock the monitor for the lexical scope instances of this class
@ -92,26 +197,99 @@ class MOZ_STACK_CLASS MonitorAutoLock {
* The monitor must be locked by the current thread when instances of
* this class are created.
*/
class MOZ_STACK_CLASS MonitorAutoUnlock {
namespace detail {
template <typename T>
class MOZ_STACK_CLASS SCOPED_CAPABILITY BaseMonitorAutoUnlock {
public:
explicit MonitorAutoUnlock(Monitor& aMonitor) : mMonitor(&aMonitor) {
explicit BaseMonitorAutoUnlock(T& aMonitor) SCOPED_UNLOCK_RELEASE(aMonitor)
: mMonitor(&aMonitor) {
mMonitor->Unlock();
}
explicit MonitorAutoUnlock(MonitorAutoLock& aMonitorLock)
: mMonitor(aMonitorLock.mMonitor) {
mMonitor->Unlock();
}
~MonitorAutoUnlock() { mMonitor->Lock(); }
~BaseMonitorAutoUnlock() SCOPED_UNLOCK_REACQUIRE() { mMonitor->Lock(); }
private:
MonitorAutoUnlock();
MonitorAutoUnlock(const MonitorAutoUnlock&);
MonitorAutoUnlock& operator=(const MonitorAutoUnlock&);
BaseMonitorAutoUnlock() = delete;
BaseMonitorAutoUnlock(const BaseMonitorAutoUnlock&) = delete;
BaseMonitorAutoUnlock& operator=(const BaseMonitorAutoUnlock&) = delete;
static void* operator new(size_t) noexcept(true);
T* mMonitor;
};
} // namespace detail
typedef detail::BaseMonitorAutoUnlock<Monitor> MonitorAutoUnlock;
typedef detail::BaseMonitorAutoUnlock<MonitorSingleWriter>
MonitorSingleWriterAutoUnlock;
/**
* Lock the monitor for the lexical scope instances of this class are
* bound to (except for MonitorAutoUnlock in nested scopes).
*
* The monitor must be unlocked when instances of this class are
* created.
*/
class SCOPED_CAPABILITY MOZ_STACK_CLASS ReleaseableMonitorAutoLock {
public:
explicit ReleaseableMonitorAutoLock(Monitor& aMonitor) CAPABILITY_ACQUIRE(aMonitor)
: mMonitor(&aMonitor) {
mMonitor->Lock();
mLocked = true;
}
~ReleaseableMonitorAutoLock() CAPABILITY_RELEASE() {
if (mLocked) {
mMonitor->Unlock();
}
}
void Wait() { mMonitor->Wait(); }
CVStatus Wait(TimeDuration aDuration) {
MOZ_ASSERT(mLocked);
return mMonitor->Wait(aDuration);
}
void Notify() {
MOZ_ASSERT(mLocked);
mMonitor->Notify();
}
void NotifyAll() {
MOZ_ASSERT(mLocked);
mMonitor->NotifyAll();
}
// Allow dropping the lock prematurely; for example to support something like:
// clang-format off
// MonitorAutoLock lock(mMonitor);
// ...
// if (foo) {
// lock.Unlock();
// MethodThatCantBeCalledWithLock()
// return;
// }
// clang-format on
void Unlock() CAPABILITY_RELEASE() {
MOZ_ASSERT(mLocked);
mMonitor->Unlock();
mLocked = false;
}
void Lock() CAPABILITY_ACQUIRE() {
MOZ_ASSERT(!mLocked);
mMonitor->Lock();
mLocked = true;
}
void AssertCurrentThreadOwns() const ASSERT_CAPABILITY() {
mMonitor->AssertCurrentThreadOwns();
}
private:
bool mLocked;
Monitor* mMonitor;
ReleaseableMonitorAutoLock() = delete;
ReleaseableMonitorAutoLock(const ReleaseableMonitorAutoLock&) = delete;
ReleaseableMonitorAutoLock& operator=(const ReleaseableMonitorAutoLock&) =
delete;
static void* operator new(size_t) noexcept(true);
};
} // namespace mozilla

View File

@ -8,6 +8,7 @@
#define mozilla_Mutex_h
#include "mozilla/BlockingResourceBase.h"
#include "mozilla/ThreadSafety.h"
#include "mozilla/PlatformMutex.h"
#include "nsISupports.h"
@ -33,7 +34,8 @@ namespace mozilla {
* include leak checking. Sometimes you want to intentionally "leak" a mutex
* until shutdown; in these cases, OffTheBooksMutex is for you.
*/
class OffTheBooksMutex : public detail::MutexImpl, BlockingResourceBase {
class CAPABILITY OffTheBooksMutex : public detail::MutexImpl,
BlockingResourceBase {
public:
/**
* @param aName A name which can reference this lock
@ -60,24 +62,24 @@ class OffTheBooksMutex : public detail::MutexImpl, BlockingResourceBase {
/**
* Lock this mutex.
**/
void Lock() { this->lock(); }
void Lock() CAPABILITY_ACQUIRE() { this->lock(); }
/**
* Try to lock this mutex, returning true if we were successful.
**/
[[nodiscard]] bool TryLock() { return this->tryLock(); }
[[nodiscard]] bool TryLock() TRY_ACQUIRE(true) { return this->tryLock(); }
/**
* Unlock this mutex.
**/
void Unlock() { this->unlock(); }
void Unlock() CAPABILITY_RELEASE() { this->unlock(); }
/**
* Assert that the current thread owns this mutex in debug builds.
*
* Does nothing in non-debug builds.
**/
void AssertCurrentThreadOwns() const {}
void AssertCurrentThreadOwns() const ASSERT_CAPABILITY(this) {}
/**
* Assert that the current thread does not own this mutex.
@ -87,25 +89,24 @@ class OffTheBooksMutex : public detail::MutexImpl, BlockingResourceBase {
*
* It is therefore mostly useful as documentation.
**/
void AssertNotCurrentThreadOwns() const {}
void AssertNotCurrentThreadOwns() const ASSERT_CAPABILITY(!this) {}
#else
void Lock();
[[nodiscard]] bool TryLock();
void Unlock();
void Lock() CAPABILITY_ACQUIRE();
void AssertCurrentThreadOwns() const;
[[nodiscard]] bool TryLock() TRY_ACQUIRE(true);
void Unlock() CAPABILITY_RELEASE();
void AssertNotCurrentThreadOwns() const {
void AssertCurrentThreadOwns() const ASSERT_CAPABILITY(this);
void AssertNotCurrentThreadOwns() const ASSERT_CAPABILITY(!this) {
// FIXME bug 476536
}
#endif // ifndef DEBUG
private:
OffTheBooksMutex();
OffTheBooksMutex(const OffTheBooksMutex&);
OffTheBooksMutex& operator=(const OffTheBooksMutex&);
OffTheBooksMutex() = delete;
OffTheBooksMutex(const OffTheBooksMutex&) = delete;
OffTheBooksMutex& operator=(const OffTheBooksMutex&) = delete;
friend class OffTheBooksCondVar;
@ -128,9 +129,85 @@ class Mutex : public OffTheBooksMutex {
MOZ_COUNTED_DTOR(Mutex)
private:
Mutex();
Mutex(const Mutex&);
Mutex& operator=(const Mutex&);
Mutex() = delete;
Mutex(const Mutex&) = delete;
Mutex& operator=(const Mutex&) = delete;
};
/**
* MutexSingleWriter
*
* Mutex where a single writer exists, so that reads from the same thread
* will not generate data races or consistency issues.
*
* When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this
* mutex within a scope, instead of calling Lock/Unlock directly.
*
* This requires an object implementing Mutex's SingleWriterLockOwner, so
* we can do correct-thread checks.
*/
// Subclass this in the object owning the mutex
class SingleWriterLockOwner {
public:
SingleWriterLockOwner() = default;
~SingleWriterLockOwner() = default;
virtual bool OnWritingThread() const = 0;
};
class MutexSingleWriter : public OffTheBooksMutex {
public:
// aOwner should be the object that contains the mutex, typically. We
// will use that object (which must have a lifetime the same or greater
// than this object) to verify that we're running on the correct thread,
// typically only in DEBUG builds
explicit MutexSingleWriter(const char* aName, SingleWriterLockOwner* aOwner)
: OffTheBooksMutex(aName)
#ifdef DEBUG
,
mOwner(aOwner)
#endif
{
MOZ_COUNT_CTOR(MutexSingleWriter);
MOZ_ASSERT(mOwner);
}
MOZ_COUNTED_DTOR(MutexSingleWriter)
/**
* Statically assert that we're on the only thread that modifies data
* guarded by this Mutex. This allows static checking for the pattern of
* having a single thread modify a set of data, and read it (under lock)
* on other threads, and reads on the thread that modifies it doesn't
* require a lock. This doesn't solve the issue of some data under the
* Mutex following this pattern, and other data under the mutex being
* written from multiple threads.
*
* We could set the writing thread and dynamically check it in debug
* builds, but this doesn't. We could also use thread-safety/capability
* system to provide direct thread assertions.
**/
void AssertOnWritingThread() const ASSERT_CAPABILITY(this) {
MOZ_ASSERT(mOwner->OnWritingThread());
}
void AssertOnWritingThreadOrHeld() const ASSERT_CAPABILITY(this) {
#ifdef DEBUG
if (!mOwner->OnWritingThread()) {
AssertCurrentThreadOwns();
}
#endif
}
private:
#ifdef DEBUG
SingleWriterLockOwner* mOwner MOZ_UNSAFE_REF(
"This is normally the object that contains the MonitorSingleWriter, so "
"we don't want to hold a reference to ourselves");
#endif
MutexSingleWriter() = delete;
MutexSingleWriter(const MutexSingleWriter&) = delete;
MutexSingleWriter& operator=(const MutexSingleWriter&) = delete;
};
namespace detail {
@ -145,7 +222,7 @@ class MOZ_RAII BaseAutoUnlock;
* MUCH PREFERRED to bare calls to Mutex.Lock and Unlock.
*/
template <typename T>
class MOZ_RAII BaseAutoLock {
class MOZ_RAII SCOPED_CAPABILITY BaseAutoLock {
public:
/**
* Constructor
@ -155,9 +232,9 @@ class MOZ_RAII BaseAutoLock {
* @param aLock A valid mozilla::Mutex* returned by
* mozilla::Mutex::NewMutex.
**/
explicit BaseAutoLock(T aLock) : mLock(aLock) { mLock.Lock(); }
explicit BaseAutoLock(T aLock) CAPABILITY_ACQUIRE(aLock) : mLock(aLock) { mLock.Lock(); }
~BaseAutoLock(void) { mLock.Unlock(); }
~BaseAutoLock(void) CAPABILITY_RELEASE() { mLock.Unlock(); }
// Assert that aLock is the mutex passed to the constructor and that the
// current thread owns the mutex. In coding patterns such as:
@ -184,15 +261,15 @@ class MOZ_RAII BaseAutoLock {
// should use this method in preference to using AssertCurrentThreadOwns on
// the mutex you expected to be held, since this method provides stronger
// guarantees.
void AssertOwns(const T& aMutex) const {
void AssertOwns(const T& aMutex) const ASSERT_CAPABILITY(aMutex) {
MOZ_ASSERT(&aMutex == &mLock);
mLock.AssertCurrentThreadOwns();
}
private:
BaseAutoLock();
BaseAutoLock(BaseAutoLock&);
BaseAutoLock& operator=(BaseAutoLock&);
BaseAutoLock() = delete;
BaseAutoLock(BaseAutoLock&) = delete;
BaseAutoLock& operator=(BaseAutoLock&) = delete;
static void* operator new(size_t) noexcept(true);
friend class BaseAutoUnlock<T>;
@ -205,8 +282,90 @@ BaseAutoLock(MutexType&) -> BaseAutoLock<MutexType&>;
} // namespace detail
typedef detail::BaseAutoLock<Mutex&> MutexAutoLock;
typedef detail::BaseAutoLock<MutexSingleWriter&> MutexSingleWriterAutoLock;
typedef detail::BaseAutoLock<OffTheBooksMutex&> OffTheBooksMutexAutoLock;
// Use if we've done AssertOnWritingThread(), and then later need to take the
// lock to write to a protected member. Instead of
// MutexSingleWriterAutoLock lock(mutex)
// use
// MutexSingleWriterAutoLockOnThread(lock, mutex)
#define MutexSingleWriterAutoLockOnThread(lock, mutex) \
PUSH_IGNORE_THREAD_SAFETY \
MutexSingleWriterAutoLock lock(mutex); \
POP_THREAD_SAFETY
namespace detail {
/**
* ReleaseableMutexAutoLock
* Acquires the Mutex when it enters scope, and releases it when it leaves
* scope. Allows calling Unlock (and Lock) as an alternative to
* MutexAutoUnlock; this can avoid an extra lock/unlock pair.
*
*/
template <typename T>
class MOZ_RAII SCOPED_CAPABILITY ReleaseableBaseAutoLock {
public:
/**
* Constructor
* The constructor aquires the given lock. The destructor
* releases the lock.
*
* @param aLock A valid mozilla::Mutex& returned by
* mozilla::Mutex::NewMutex.
**/
explicit ReleaseableBaseAutoLock(T aLock) CAPABILITY_ACQUIRE(aLock)
: BaseAutoLock<T>(aLock) {
mLock.Lock();
mLocked = true;
}
~ReleaseableBaseAutoLock(void) CAPABILITY_RELEASE() {
if (!mLocked) {
mLock.Unlock();
}
}
void AssertOwns(const T& aMutex) const ASSERT_CAPABILITY(mLock) {
MOZ_ASSERT(&aMutex == &mLock);
mLock.AssertCurrentThreadOwns();
}
// Allow dropping the lock prematurely; for example to support something like:
// clang-format off
// MutexAutoLock lock(mMutex);
// ...
// if (foo) {
// lock.Unlock();
// MethodThatCantBeCalledWithLock()
// return;
// }
// clang-format on
void Unlock() CAPABILITY_RELEASE() {
mLock.Unlock();
mLocked = false;
}
void Lock() CAPABILITY_ACQUIRE() {
mLock.Lock();
mLocked = true;
}
private:
ReleaseableBaseAutoLock() = delete;
ReleaseableBaseAutoLock(ReleaseableBaseAutoLock&) = delete;
ReleaseableBaseAutoLock& operator=(ReleaseableBaseAutoLock&) = delete;
static void* operator new(size_t) noexcept(true);
bool mLocked;
T mLock;
};
template <typename MutexType>
ReleaseableBaseAutoLock(MutexType&) -> ReleaseableBaseAutoLock<MutexType&>;
} // namespace detail
typedef detail::ReleaseableBaseAutoLock<Mutex&> ReleaseableMutexAutoLock;
namespace detail {
/**
* BaseAutoUnlock
@ -216,21 +375,25 @@ namespace detail {
* MUCH PREFERRED to bare calls to Mutex.Unlock and Lock.
*/
template <typename T>
class MOZ_RAII BaseAutoUnlock {
class MOZ_RAII SCOPED_CAPABILITY BaseAutoUnlock {
public:
explicit BaseAutoUnlock(T aLock) : mLock(aLock) { mLock.Unlock(); }
explicit BaseAutoUnlock(T aLock) SCOPED_UNLOCK_RELEASE(aLock) : mLock(aLock) {
mLock.Unlock();
}
explicit BaseAutoUnlock(BaseAutoLock<T>& aAutoLock) : mLock(aAutoLock.mLock) {
explicit BaseAutoUnlock(BaseAutoLock<T>& aAutoLock)
/* CAPABILITY_RELEASE(aAutoLock.mLock) */
: mLock(aAutoLock.mLock) {
NS_ASSERTION(mLock, "null lock");
mLock->Unlock();
}
~BaseAutoUnlock() { mLock.Lock(); }
~BaseAutoUnlock() SCOPED_UNLOCK_REACQUIRE() { mLock.Lock(); }
private:
BaseAutoUnlock();
BaseAutoUnlock(BaseAutoUnlock&);
BaseAutoUnlock& operator=(BaseAutoUnlock&);
BaseAutoUnlock() = delete;
BaseAutoUnlock(BaseAutoUnlock&) = delete;
BaseAutoUnlock& operator=(BaseAutoUnlock&) = delete;
static void* operator new(size_t) noexcept(true);
T mLock;
@ -241,6 +404,7 @@ BaseAutoUnlock(MutexType&) -> BaseAutoUnlock<MutexType&>;
} // namespace detail
typedef detail::BaseAutoUnlock<Mutex&> MutexAutoUnlock;
typedef detail::BaseAutoUnlock<MutexSingleWriter&> MutexSingleWriterAutoUnlock;
typedef detail::BaseAutoUnlock<OffTheBooksMutex&> OffTheBooksMutexAutoUnlock;
namespace detail {
@ -252,21 +416,21 @@ namespace detail {
* MUCH PREFERRED to bare calls to Mutex.TryLock and Unlock.
*/
template <typename T>
class MOZ_RAII BaseAutoTryLock {
class MOZ_RAII SCOPED_CAPABILITY BaseAutoTryLock {
public:
explicit BaseAutoTryLock(T& aLock)
explicit BaseAutoTryLock(T& aLock) CAPABILITY_ACQUIRE(aLock)
: mLock(aLock.TryLock() ? &aLock : nullptr) {}
~BaseAutoTryLock() {
~BaseAutoTryLock() CAPABILITY_RELEASE() {
if (mLock) {
mLock->Unlock();
mLock = nullptr;
}
}
explicit operator bool() const { return mLock; }
private:
BaseAutoTryLock() = delete;
BaseAutoTryLock(BaseAutoTryLock&) = delete;
BaseAutoTryLock& operator=(BaseAutoTryLock&) = delete;
static void* operator new(size_t) noexcept(true);

View File

@ -13,6 +13,7 @@
#include "mozilla/Atomics.h"
#include "mozilla/BlockingResourceBase.h"
#include "mozilla/PlatformRWLock.h"
#include "mozilla/ThreadSafety.h"
namespace mozilla {
@ -38,27 +39,30 @@ namespace mozilla {
//
// It is unspecified whether RWLock gives priority to waiting readers or
// a waiting writer when unlocking.
class RWLock : public detail::RWLockImpl, public BlockingResourceBase {
class CAPABILITY RWLock : public detail::RWLockImpl,
public BlockingResourceBase {
public:
explicit RWLock(const char* aName);
#ifdef DEBUG
bool LockedForWritingByCurrentThread();
[[nodiscard]] bool TryReadLock();
void ReadLock();
void ReadUnlock();
[[nodiscard]] bool TryWriteLock();
void WriteLock();
void WriteUnlock();
[[nodiscard]] bool TryReadLock() SHARED_TRYLOCK_FUNCTION(true);
void ReadLock() ACQUIRE_SHARED();
void ReadUnlock() RELEASE_SHARED();
[[nodiscard]] bool TryWriteLock() TRY_ACQUIRE(true);
void WriteLock() CAPABILITY_ACQUIRE();
void WriteUnlock() EXCLUSIVE_RELEASE();
#else
[[nodiscard]] bool TryReadLock() { return detail::RWLockImpl::tryReadLock(); }
void ReadLock() { detail::RWLockImpl::readLock(); }
void ReadUnlock() { detail::RWLockImpl::readUnlock(); }
[[nodiscard]] bool TryWriteLock() {
[[nodiscard]] bool TryReadLock() SHARED_TRYLOCK_FUNCTION(true) {
return detail::RWLockImpl::tryReadLock();
}
void ReadLock() ACQUIRE_SHARED() { detail::RWLockImpl::readLock(); }
void ReadUnlock() RELEASE_SHARED() { detail::RWLockImpl::readUnlock(); }
[[nodiscard]] bool TryWriteLock() TRY_ACQUIRE(true) {
return detail::RWLockImpl::tryWriteLock();
}
void WriteLock() { detail::RWLockImpl::writeLock(); }
void WriteUnlock() { detail::RWLockImpl::writeUnlock(); }
void WriteLock() CAPABILITY_ACQUIRE() { detail::RWLockImpl::writeLock(); }
void WriteUnlock() EXCLUSIVE_RELEASE() { detail::RWLockImpl::writeUnlock(); }
#endif
private:
@ -72,6 +76,7 @@ class RWLock : public detail::RWLockImpl, public BlockingResourceBase {
#endif
};
// We only use this once; not sure we can add thread safety attributions here
template <typename T>
class MOZ_RAII BaseAutoTryReadLock {
public:
@ -95,14 +100,17 @@ class MOZ_RAII BaseAutoTryReadLock {
};
template <typename T>
class MOZ_RAII BaseAutoReadLock {
class SCOPED_CAPABILITY MOZ_RAII BaseAutoReadLock {
public:
explicit BaseAutoReadLock(T& aLock) : mLock(&aLock) {
explicit BaseAutoReadLock(T& aLock) ACQUIRE_SHARED(aLock) : mLock(&aLock) {
MOZ_ASSERT(mLock, "null lock");
mLock->ReadLock();
}
~BaseAutoReadLock() { mLock->ReadUnlock(); }
// Not RELEASE_SHARED(), which would make sense - apparently this trips
// over a bug in clang's static analyzer and it says it expected an
// exclusive unlock.
~BaseAutoReadLock() RELEASE_GENERIC() { mLock->ReadUnlock(); }
private:
BaseAutoReadLock() = delete;
@ -112,6 +120,7 @@ class MOZ_RAII BaseAutoReadLock {
T* mLock;
};
// XXX Mutex attributions?
template <typename T>
class MOZ_RAII BaseAutoTryWriteLock {
public:
@ -135,14 +144,14 @@ class MOZ_RAII BaseAutoTryWriteLock {
};
template <typename T>
class MOZ_RAII BaseAutoWriteLock final {
class SCOPED_CAPABILITY MOZ_RAII BaseAutoWriteLock final {
public:
explicit BaseAutoWriteLock(T& aLock) : mLock(&aLock) {
explicit BaseAutoWriteLock(T& aLock) CAPABILITY_ACQUIRE(aLock) : mLock(&aLock) {
MOZ_ASSERT(mLock, "null lock");
mLock->WriteLock();
}
~BaseAutoWriteLock() { mLock->WriteUnlock(); }
~BaseAutoWriteLock() CAPABILITY_RELEASE() { mLock->WriteUnlock(); }
private:
BaseAutoWriteLock() = delete;
@ -177,7 +186,7 @@ typedef BaseAutoWriteLock<RWLock> AutoWriteLock;
namespace detail {
class StaticRWLock {
class CAPABILITY StaticRWLock {
public:
// In debug builds, check that mLock is initialized for us as we expect by
// the compiler. In non-debug builds, don't declare a constructor so that
@ -186,15 +195,19 @@ class StaticRWLock {
StaticRWLock() { MOZ_ASSERT(!mLock); }
#endif
[[nodiscard]] bool TryReadLock() { return Lock()->TryReadLock(); }
void ReadLock() { Lock()->ReadLock(); }
void ReadUnlock() { Lock()->ReadUnlock(); }
[[nodiscard]] bool TryWriteLock() { return Lock()->TryWriteLock(); }
void WriteLock() { Lock()->WriteLock(); }
void WriteUnlock() { Lock()->WriteUnlock(); }
[[nodiscard]] bool TryReadLock() SHARED_TRYLOCK_FUNCTION(true) {
return Lock()->TryReadLock();
}
void ReadLock() ACQUIRE_SHARED() { Lock()->ReadLock(); }
void ReadUnlock() RELEASE_SHARED() { Lock()->ReadUnlock(); }
[[nodiscard]] bool TryWriteLock() TRY_ACQUIRE(true) {
return Lock()->TryWriteLock();
}
void WriteLock() CAPABILITY_ACQUIRE() { Lock()->WriteLock(); }
void WriteUnlock() EXCLUSIVE_RELEASE() { Lock()->WriteUnlock(); }
private:
[[nodiscard]] RWLock* Lock() {
[[nodiscard]] RWLock* Lock() RETURN_CAPABILITY(*mLock) {
if (mLock) {
return mLock;
}

View File

@ -9,6 +9,7 @@
#ifndef mozilla_RecursiveMutex_h
#define mozilla_RecursiveMutex_h
#include "mozilla/ThreadSafety.h"
#include "mozilla/BlockingResourceBase.h"
#ifndef XP_WIN
@ -17,33 +18,33 @@
namespace mozilla {
class RecursiveMutex : public BlockingResourceBase {
class CAPABILITY RecursiveMutex : public BlockingResourceBase {
public:
explicit RecursiveMutex(const char* aName);
~RecursiveMutex();
#ifdef DEBUG
void Lock();
void Unlock();
void Lock() CAPABILITY_ACQUIRE();
void Unlock() CAPABILITY_RELEASE();
#else
void Lock() { LockInternal(); }
void Unlock() { UnlockInternal(); }
void Lock() CAPABILITY_ACQUIRE() { LockInternal(); }
void Unlock() CAPABILITY_RELEASE() { UnlockInternal(); }
#endif
#ifdef DEBUG
/**
* AssertCurrentThreadIn
**/
void AssertCurrentThreadIn();
void AssertCurrentThreadIn() const ASSERT_CAPABILITY(this);
/**
* AssertNotCurrentThreadIn
**/
void AssertNotCurrentThreadIn() {
void AssertNotCurrentThreadIn() const EXCLUDES(this) {
// Not currently implemented. See bug 476536 for discussion.
}
#else
void AssertCurrentThreadIn() {}
void AssertNotCurrentThreadIn() {}
void AssertCurrentThreadIn() const ASSERT_CAPABILITY(this) {}
void AssertNotCurrentThreadIn() const EXCLUDES(this) {}
#endif
private:
@ -69,15 +70,16 @@ class RecursiveMutex : public BlockingResourceBase {
#endif
};
class MOZ_RAII RecursiveMutexAutoLock {
class MOZ_RAII SCOPED_CAPABILITY RecursiveMutexAutoLock {
public:
explicit RecursiveMutexAutoLock(RecursiveMutex& aRecursiveMutex)
CAPABILITY_ACQUIRE(aRecursiveMutex)
: mRecursiveMutex(&aRecursiveMutex) {
NS_ASSERTION(mRecursiveMutex, "null mutex");
mRecursiveMutex->Lock();
}
~RecursiveMutexAutoLock(void) { mRecursiveMutex->Unlock(); }
~RecursiveMutexAutoLock(void) CAPABILITY_RELEASE() { mRecursiveMutex->Unlock(); }
private:
RecursiveMutexAutoLock() = delete;
@ -88,15 +90,18 @@ class MOZ_RAII RecursiveMutexAutoLock {
mozilla::RecursiveMutex* mRecursiveMutex;
};
class MOZ_RAII RecursiveMutexAutoUnlock {
class MOZ_RAII SCOPED_CAPABILITY RecursiveMutexAutoUnlock {
public:
explicit RecursiveMutexAutoUnlock(RecursiveMutex& aRecursiveMutex)
SCOPED_UNLOCK_RELEASE(aRecursiveMutex)
: mRecursiveMutex(&aRecursiveMutex) {
NS_ASSERTION(mRecursiveMutex, "null mutex");
mRecursiveMutex->Unlock();
}
~RecursiveMutexAutoUnlock(void) { mRecursiveMutex->Lock(); }
~RecursiveMutexAutoUnlock(void) SCOPED_UNLOCK_REACQUIRE() {
mRecursiveMutex->Lock();
}
private:
RecursiveMutexAutoUnlock() = delete;

View File

@ -14,8 +14,8 @@
#endif // defined( MOZILLA_INTERNAL_API) && !defined(DEBUG)
#include "mozilla/BlockingResourceBase.h"
#include "mozilla/ThreadSafety.h"
#include "nsISupports.h"
//
// Provides:
//
@ -34,7 +34,7 @@ namespace mozilla {
* When possible, use ReentrantMonitorAutoEnter to hold this monitor within a
* scope, instead of calling Enter/Exit directly.
**/
class ReentrantMonitor : BlockingResourceBase {
class CAPABILITY ReentrantMonitor : BlockingResourceBase {
public:
/**
* ReentrantMonitor
@ -70,19 +70,20 @@ class ReentrantMonitor : BlockingResourceBase {
* Enter
* @see prmon.h
**/
void Enter() { PR_EnterMonitor(mReentrantMonitor); }
void Enter() CAPABILITY_ACQUIRE() { PR_EnterMonitor(mReentrantMonitor); }
/**
* Exit
* @see prmon.h
**/
void Exit() { PR_ExitMonitor(mReentrantMonitor); }
void Exit() CAPABILITY_RELEASE() { PR_ExitMonitor(mReentrantMonitor); }
/**
* Wait
* @see prmon.h
**/
nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) {
PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mReentrantMonitor);
# ifdef MOZILLA_INTERNAL_API
AUTO_PROFILER_THREAD_SLEEP;
# endif // MOZILLA_INTERNAL_API
@ -92,8 +93,8 @@ class ReentrantMonitor : BlockingResourceBase {
}
#else // ifndef DEBUG
void Enter();
void Exit();
void Enter() CAPABILITY_ACQUIRE();
void Exit() CAPABILITY_RELEASE();
nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT);
#endif // ifndef DEBUG
@ -121,7 +122,7 @@ class ReentrantMonitor : BlockingResourceBase {
* AssertCurrentThreadIn
* @see prmon.h
**/
void AssertCurrentThreadIn() {
void AssertCurrentThreadIn() ASSERT_CAPABILITY(mReentrantMonitor) {
PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mReentrantMonitor);
}
@ -129,13 +130,13 @@ class ReentrantMonitor : BlockingResourceBase {
* AssertNotCurrentThreadIn
* @see prmon.h
**/
void AssertNotCurrentThreadIn() {
void AssertNotCurrentThreadIn() EXCLUDES(mReentrantMonitor) {
// FIXME bug 476536
}
#else
void AssertCurrentThreadIn() {}
void AssertNotCurrentThreadIn() {}
void AssertCurrentThreadIn() ASSERT_CAPABILITY(mReentrantMonitor) {}
void AssertNotCurrentThreadIn() EXCLUDES(mReentrantMonitor) {}
#endif // ifdef DEBUG
@ -157,7 +158,7 @@ class ReentrantMonitor : BlockingResourceBase {
*
* MUCH PREFERRED to bare calls to ReentrantMonitor.Enter and Exit.
*/
class MOZ_STACK_CLASS ReentrantMonitorAutoEnter {
class SCOPED_CAPABILITY MOZ_STACK_CLASS ReentrantMonitorAutoEnter {
public:
/**
* Constructor
@ -167,13 +168,13 @@ class MOZ_STACK_CLASS ReentrantMonitorAutoEnter {
* @param aReentrantMonitor A valid mozilla::ReentrantMonitor*.
**/
explicit ReentrantMonitorAutoEnter(
mozilla::ReentrantMonitor& aReentrantMonitor)
mozilla::ReentrantMonitor& aReentrantMonitor) CAPABILITY_ACQUIRE(aReentrantMonitor)
: mReentrantMonitor(&aReentrantMonitor) {
NS_ASSERTION(mReentrantMonitor, "null monitor");
mReentrantMonitor->Enter();
}
~ReentrantMonitorAutoEnter(void) { mReentrantMonitor->Exit(); }
~ReentrantMonitorAutoEnter(void) CAPABILITY_RELEASE() { mReentrantMonitor->Exit(); }
nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) {
return mReentrantMonitor->Wait(aInterval);
@ -200,7 +201,7 @@ class MOZ_STACK_CLASS ReentrantMonitorAutoEnter {
*
* MUCH PREFERRED to bare calls to ReentrantMonitor.Exit and Enter.
*/
class MOZ_STACK_CLASS ReentrantMonitorAutoExit {
class SCOPED_CAPABILITY MOZ_STACK_CLASS ReentrantMonitorAutoExit {
public:
/**
* Constructor
@ -212,6 +213,7 @@ class MOZ_STACK_CLASS ReentrantMonitorAutoExit {
* must be already locked.
**/
explicit ReentrantMonitorAutoExit(ReentrantMonitor& aReentrantMonitor)
EXCLUSIVE_RELEASE(aReentrantMonitor)
: mReentrantMonitor(&aReentrantMonitor) {
NS_ASSERTION(mReentrantMonitor, "null monitor");
mReentrantMonitor->AssertCurrentThreadIn();
@ -220,13 +222,16 @@ class MOZ_STACK_CLASS ReentrantMonitorAutoExit {
explicit ReentrantMonitorAutoExit(
ReentrantMonitorAutoEnter& aReentrantMonitorAutoEnter)
EXCLUSIVE_RELEASE(aReentrantMonitorAutoEnter.mReentrantMonitor)
: mReentrantMonitor(aReentrantMonitorAutoEnter.mReentrantMonitor) {
NS_ASSERTION(mReentrantMonitor, "null monitor");
mReentrantMonitor->AssertCurrentThreadIn();
mReentrantMonitor->Exit();
}
~ReentrantMonitorAutoExit(void) { mReentrantMonitor->Enter(); }
~ReentrantMonitorAutoExit(void) EXCLUSIVE_RELEASE() {
mReentrantMonitor->Enter();
}
private:
ReentrantMonitorAutoExit();