Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#ifndef threading_ProtectedData_h
#define threading_ProtectedData_h
#include "mozilla/Atomics.h"
#include "jstypes.h"
#include "threading/LockGuard.h"
#include "threading/Mutex.h"
#include "threading/ThreadId.h"
struct JS_PUBLIC_API JSContext;
namespace js {
// This file provides classes for encapsulating pieces of data with a check
// that ensures the data is only accessed if certain conditions are met.
// Checking is only done in debug builds; in release builds these classes
// have no space or time overhead. These classes are mainly used for ensuring
// that data is used in threadsafe ways.
//
// ProtectedData does not by itself ensure that data is threadsafe: it only
// documents and checks synchronization constraints that need to be established
// by the code using the data. If a mutex can be created and directly
// associated with the data, consider using the ExclusiveData class instead.
// Otherwise, ProtectedData should be used to document whatever synchronization
// method is used.
// Protected data checks are enabled in debug builds, except on android where
// they cause some permatimeouts in automation.
#if defined(DEBUG) && !defined(ANDROID)
# define JS_HAS_PROTECTED_DATA_CHECKS
#endif
#define DECLARE_ONE_BOOL_OPERATOR(OP, T) \
template <typename U> \
bool operator OP(const U& other) const { \
if constexpr (std::is_integral_v<T>) { \
return ref() OP static_cast<T>(other); \
} else { \
return ref() OP other; \
} \
}
#define DECLARE_BOOL_OPERATORS(T) \
DECLARE_ONE_BOOL_OPERATOR(==, T) \
DECLARE_ONE_BOOL_OPERATOR(!=, T) \
DECLARE_ONE_BOOL_OPERATOR(<=, T) \
DECLARE_ONE_BOOL_OPERATOR(>=, T) \
DECLARE_ONE_BOOL_OPERATOR(<, T) \
DECLARE_ONE_BOOL_OPERATOR(>, T)
// Mark a region of code that should be treated as single threaded and suppress
// any ProtectedData checks.
//
// Note that in practice there may be multiple threads running when this class
// is used, due to the presence of multiple runtimes in the process. When each
// process has only a single runtime this will no longer be a concern.
class MOZ_RAII AutoNoteSingleThreadedRegion {
public:
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
static mozilla::Atomic<size_t, mozilla::SequentiallyConsistent> count;
AutoNoteSingleThreadedRegion() { count++; }
~AutoNoteSingleThreadedRegion() { count--; }
#else
AutoNoteSingleThreadedRegion() {}
#endif
};
// Class for protected data that may be written to any number of times. Checks
// occur when the data is both read from and written to.
template <typename Check, typename T>
class ProtectedData {
using ThisType = ProtectedData<Check, T>;
public:
template <typename... Args>
explicit ProtectedData(const Check& check, Args&&... args)
: value(std::forward<Args>(args)...)
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
,
check(check)
#endif
{
}
DECLARE_BOOL_OPERATORS(T)
operator const T&() const { return ref(); }
const T& operator->() const { return ref(); }
template <typename U>
ThisType& operator=(const U& p) {
this->ref() = p;
return *this;
}
template <typename U>
ThisType& operator=(U&& p) {
this->ref() = std::move(p);
return *this;
}
template <typename U>
T& operator+=(const U& rhs) {
return ref() += rhs;
}
template <typename U>
T& operator-=(const U& rhs) {
return ref() -= rhs;
}
template <typename U>
T& operator*=(const U& rhs) {
return ref() *= rhs;
}
template <typename U>
T& operator/=(const U& rhs) {
return ref() /= rhs;
}
template <typename U>
T& operator&=(const U& rhs) {
return ref() &= rhs;
}
template <typename U>
T& operator|=(const U& rhs) {
return ref() |= rhs;
}
T& operator++() { return ++ref(); }
T& operator--() { return --ref(); }
T operator++(int) { return ref()++; }
T operator--(int) { return ref()--; }
T& ref() {
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
if (!AutoNoteSingleThreadedRegion::count) {
check.check();
}
#endif
return value;
}
const T& ref() const {
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
if (!AutoNoteSingleThreadedRegion::count) {
check.check();
}
#endif
return value;
}
T& refNoCheck() { return value; }
const T& refNoCheck() const { return value; }
static size_t offsetOfValue() { return offsetof(ThisType, value); }
private:
T value;
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
Check check;
#endif
};
// Intermediate class for protected data whose checks take no constructor
// arguments.
template <typename Check, typename T>
class ProtectedDataNoCheckArgs : public ProtectedData<Check, T> {
using Base = ProtectedData<Check, T>;
public:
template <typename... Args>
explicit ProtectedDataNoCheckArgs(Args&&... args)
: ProtectedData<Check, T>(Check(), std::forward<Args>(args)...) {}
using Base::operator=;
};
// Intermediate class for protected data whose checks take a JSContext.
template <typename Check, typename T>
class ProtectedDataContextArg : public ProtectedData<Check, T> {
using Base = ProtectedData<Check, T>;
public:
template <typename... Args>
explicit ProtectedDataContextArg(JSContext* cx, Args&&... args)
: ProtectedData<Check, T>(Check(cx), std::forward<Args>(args)...) {}
using Base::operator=;
};
class CheckUnprotected {
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
public:
inline void check() const {}
#endif
};
// Data with a no-op check that permits all accesses. This is tantamount to not
// using ProtectedData at all, but is in place to document points which need
template <typename T>
using UnprotectedData = ProtectedDataNoCheckArgs<CheckUnprotected, T>;
class CheckThreadLocal {
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
ThreadId id;
public:
CheckThreadLocal() : id(ThreadId::ThisThreadId()) {}
void check() const;
#endif
};
class CheckContextLocal {
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
JSContext* cx_;
public:
explicit CheckContextLocal(JSContext* cx) : cx_(cx) {}
void check() const;
#else
public:
explicit CheckContextLocal(JSContext* cx) {}
#endif
};
// Data which may only be accessed by the thread on which it is created.
template <typename T>
using ThreadData = ProtectedDataNoCheckArgs<CheckThreadLocal, T>;
// Data which belongs to a JSContext and should only be accessed from that
// JSContext's thread. Note that a JSContext may not have a thread currently
// associated with it and any associated thread may change over time.
template <typename T>
using ContextData = ProtectedDataContextArg<CheckContextLocal, T>;
// Enum describing which helper threads (GC tasks or Ion compilations) may
// access data.
enum class AllowedHelperThread {
None,
GCTask,
IonCompile,
GCTaskOrIonCompile,
};
template <AllowedHelperThread Helper>
class CheckMainThread {
public:
void check() const;
};
// Data which may only be accessed by the runtime's main thread.
template <typename T>
using MainThreadData =
ProtectedDataNoCheckArgs<CheckMainThread<AllowedHelperThread::None>, T>;
// Data which may only be accessed by the runtime's main thread or by various
// helper thread tasks.
template <typename T>
using MainThreadOrGCTaskData =
ProtectedDataNoCheckArgs<CheckMainThread<AllowedHelperThread::GCTask>, T>;
template <typename T>
using MainThreadOrIonCompileData =
ProtectedDataNoCheckArgs<CheckMainThread<AllowedHelperThread::IonCompile>,
T>;
template <typename T>
using MainThreadOrGCTaskOrIonCompileData = ProtectedDataNoCheckArgs<
CheckMainThread<AllowedHelperThread::GCTaskOrIonCompile>, T>;
// Runtime wide locks which might protect some data.
enum class GlobalLock { GCLock, HelperThreadLock };
template <GlobalLock Lock, AllowedHelperThread Helper>
class CheckGlobalLock {
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
public:
void check() const;
#endif
};
// Data which may only be accessed while holding the GC lock.
template <typename T>
using GCLockData = ProtectedDataNoCheckArgs<
CheckGlobalLock<GlobalLock::GCLock, AllowedHelperThread::None>, T>;
// Data which may only be accessed while holding the helper thread lock.
template <typename T>
using HelperThreadLockData = ProtectedDataNoCheckArgs<
CheckGlobalLock<GlobalLock::HelperThreadLock, AllowedHelperThread::None>,
T>;
// Class for protected data that is only written to once. 'const' may sometimes
// be usable instead of this class, but in cases where the data cannot be set
// to its final value in its constructor this class is helpful. Protected data
// checking only occurs when writes are performed, not reads. Steps may need to
// be taken to ensure that reads do not occur until the written value is fully
// initialized, as such guarantees are not provided by this class.
template <typename Check, typename T>
class ProtectedDataWriteOnce {
using ThisType = ProtectedDataWriteOnce<Check, T>;
public:
template <typename... Args>
explicit ProtectedDataWriteOnce(Args&&... args)
: value(std::forward<Args>(args)...)
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
,
nwrites(0)
#endif
{
}
DECLARE_BOOL_OPERATORS(T)
operator const T&() const { return ref(); }
const T& operator->() const { return ref(); }
template <typename U>
ThisType& operator=(const U& p) {
if (ref() != p) {
this->writeRef() = p;
}
return *this;
}
const T& ref() const { return value; }
T& writeRef() {
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
if (!AutoNoteSingleThreadedRegion::count) {
check.check();
}
// Despite the WriteOnce name, actually allow two writes to accommodate
// data that is cleared during teardown.
MOZ_ASSERT(++nwrites <= 2);
#endif
return value;
}
private:
T value;
#ifdef JS_HAS_PROTECTED_DATA_CHECKS
Check check;
size_t nwrites;
#endif
};
// Data that is written once with no requirements for exclusive access when
// that write occurs.
template <typename T>
using WriteOnceData = ProtectedDataWriteOnce<CheckUnprotected, T>;
#undef DECLARE_ASSIGNMENT_OPERATOR
#undef DECLARE_ONE_BOOL_OPERATOR
#undef DECLARE_BOOL_OPERATORS
} // namespace js
#endif // threading_ProtectedData_h