#include "base/sequence_checker_impl.h"
#include <algorithm>
#include <utility>
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/debug/stack_trace.h"
#include "base/sequence_token.h"
#include "base/synchronization/lock_subtle.h"
#include "base/threading/platform_thread.h"
#include "base/threading/platform_thread_ref.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_checker_impl.h"
#include "base/threading/thread_local_storage.h"
namespace base {
namespace {
bool g_log_stack = false;
}
void SequenceCheckerImpl::EnableStackLogging() {
g_log_stack = true;
ThreadChecker::EnableStackLogging();
}
SequenceCheckerImpl::SequenceCheckerImpl() {
AutoLock auto_lock(lock_);
EnsureAssigned();
}
SequenceCheckerImpl::~SequenceCheckerImpl() = default;
SequenceCheckerImpl::SequenceCheckerImpl(SequenceCheckerImpl&& other) {
CHECK(other.CalledOnValidSequence());
bound_at_ = std::move(other.bound_at_);
sequence_token_ = other.sequence_token_;
#if DCHECK_IS_ON()
locks_ = std::move(other.locks_);
#endif
thread_ref_ = other.thread_ref_;
DCHECK(!other.bound_at_);
#if DCHECK_IS_ON()
DCHECK(other.locks_.empty());
#endif
other.sequence_token_ = internal::SequenceToken();
other.thread_ref_ = PlatformThreadRef();
}
SequenceCheckerImpl& SequenceCheckerImpl::operator=(
SequenceCheckerImpl&& other) {
CHECK(other.CalledOnValidSequence());
TS_UNCHECKED_READ(bound_at_) = std::move(TS_UNCHECKED_READ(other.bound_at_));
TS_UNCHECKED_READ(sequence_token_) = TS_UNCHECKED_READ(other.sequence_token_);
#if DCHECK_IS_ON()
TS_UNCHECKED_READ(locks_) = std::move(TS_UNCHECKED_READ(other.locks_));
#endif
TS_UNCHECKED_READ(thread_ref_) = TS_UNCHECKED_READ(other.thread_ref_);
DCHECK(!TS_UNCHECKED_READ(other.bound_at_));
#if DCHECK_IS_ON()
DCHECK(TS_UNCHECKED_READ(other.locks_).empty());
#endif
TS_UNCHECKED_READ(other.sequence_token_) = internal::SequenceToken();
TS_UNCHECKED_READ(other.thread_ref_) = PlatformThreadRef();
return *this;
}
bool SequenceCheckerImpl::CalledOnValidSequence(
std::unique_ptr<debug::StackTrace>* out_bound_at) const {
AutoLock auto_lock(lock_);
EnsureAssigned();
CHECK(!thread_ref_.is_null());
bool is_valid =
(sequence_token_ == internal::SequenceToken::GetForCurrentThread());
if (!is_valid) {
#if DCHECK_IS_ON()
for (uintptr_t lock : subtle::GetTrackedLocksHeldByCurrentThread()) {
if (Contains(locks_, lock)) {
is_valid = true;
break;
}
}
#endif
}
if (!is_valid) {
is_valid = ThreadLocalStorage::HasBeenDestroyed() &&
thread_ref_ == PlatformThread::CurrentRef();
}
if (!is_valid) {
if (out_bound_at && bound_at_) {
*out_bound_at = std::make_unique<debug::StackTrace>(*bound_at_);
}
return false;
}
#if DCHECK_IS_ON()
std::erase_if(locks_, [](uintptr_t lock_ptr) {
return !Contains(subtle::GetTrackedLocksHeldByCurrentThread(), lock_ptr);
});
#endif
if (sequence_token_ != internal::SequenceToken::GetForCurrentThread()) {
sequence_token_ = internal::SequenceToken();
}
return true;
}
void SequenceCheckerImpl::DetachFromSequence() {
AutoLock auto_lock(lock_);
bound_at_.reset();
sequence_token_ = internal::SequenceToken();
#if DCHECK_IS_ON()
locks_.clear();
#endif
thread_ref_ = PlatformThreadRef();
}
void SequenceCheckerImpl::EnsureAssigned() const {
if (!thread_ref_.is_null()) {
return;
}
if (g_log_stack) {
bound_at_ = std::make_unique<debug::StackTrace>(size_t{10});
}
sequence_token_ = internal::SequenceToken::GetForCurrentThread();
#if DCHECK_IS_ON()
DCHECK(locks_.empty());
std::ranges::remove_copy(subtle::GetTrackedLocksHeldByCurrentThread(),
std::back_inserter(locks_),
reinterpret_cast<uintptr_t>(&lock_));
#endif
DCHECK(sequence_token_.IsValid());
thread_ref_ = PlatformThread::CurrentRef();
DCHECK(!thread_ref_.is_null());
}
}