760682da3e
Double-checked locking pattern for destruction was missing the acquire barrier for the initial load. TSAN complained with a data race where: T1: ClearAllUsedNodes(), clearing out the node T2: a. if(GetNodeSafe()) { Lock; ... } T2: b. operator delete Since GetNodeSafe() was a relaxed load, operator delete was allowed to be reordered which raced with ClearAllUsedNodes(). Bug: chromium:1239081, chromium:1242795 Change-Id: I3906555b13cc51538a1a54b7ca481a96d81fd84e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3132264 Commit-Queue: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Omer Katz <omerkatz@chromium.org> Reviewed-by: Anton Bikineev <bikineev@chromium.org> Cr-Commit-Position: refs/heads/main@{#76599}
466 lines
15 KiB
C++
466 lines
15 KiB
C++
// Copyright 2020 the V8 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.
|
|
|
|
#ifndef INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
|
|
#define INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
|
|
|
|
#include <atomic>
|
|
|
|
#include "cppgc/internal/persistent-node.h"
|
|
#include "cppgc/internal/pointer-policies.h"
|
|
#include "cppgc/persistent.h"
|
|
#include "cppgc/visitor.h"
|
|
|
|
namespace cppgc {
|
|
namespace internal {
|
|
|
|
// Wrapper around PersistentBase that allows accessing poisoned memory when
|
|
// using ASAN. This is needed as the GC of the heap that owns the value
|
|
// of a CTP, may clear it (heap termination, weakness) while the object
|
|
// holding the CTP may be poisoned as itself may be deemed dead.
|
|
class CrossThreadPersistentBase : public PersistentBase {
|
|
public:
|
|
CrossThreadPersistentBase() = default;
|
|
explicit CrossThreadPersistentBase(const void* raw) : PersistentBase(raw) {}
|
|
|
|
V8_CLANG_NO_SANITIZE("address") const void* GetValueFromGC() const {
|
|
return raw_;
|
|
}
|
|
|
|
V8_CLANG_NO_SANITIZE("address")
|
|
PersistentNode* GetNodeFromGC() const { return node_; }
|
|
|
|
V8_CLANG_NO_SANITIZE("address")
|
|
void ClearFromGC() const {
|
|
raw_ = nullptr;
|
|
SetNodeSafe(nullptr);
|
|
}
|
|
|
|
// GetNodeSafe() can be used for a thread-safe IsValid() check in a
|
|
// double-checked locking pattern. See ~BasicCrossThreadPersistent.
|
|
PersistentNode* GetNodeSafe() const {
|
|
return reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->load(
|
|
std::memory_order_acquire);
|
|
}
|
|
|
|
// The GC writes using SetNodeSafe() while holding the lock.
|
|
V8_CLANG_NO_SANITIZE("address")
|
|
void SetNodeSafe(PersistentNode* value) const {
|
|
#if defined(__has_feature)
|
|
#if __has_feature(address_sanitizer)
|
|
#define V8_IS_ASAN 1
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef V8_IS_ASAN
|
|
__atomic_store(&node_, &value, __ATOMIC_RELEASE);
|
|
#else // !V8_IS_ASAN
|
|
// Non-ASAN builds can use atomics. This also covers MSVC which does not
|
|
// have the __atomic_store intrinsic.
|
|
reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->store(
|
|
value, std::memory_order_release);
|
|
#endif // !V8_IS_ASAN
|
|
|
|
#undef V8_IS_ASAN
|
|
}
|
|
};
|
|
|
|
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
|
|
typename CheckingPolicy>
|
|
class BasicCrossThreadPersistent final : public CrossThreadPersistentBase,
|
|
public LocationPolicy,
|
|
private WeaknessPolicy,
|
|
private CheckingPolicy {
|
|
public:
|
|
using typename WeaknessPolicy::IsStrongPersistent;
|
|
using PointeeType = T;
|
|
|
|
~BasicCrossThreadPersistent() {
|
|
// This implements fast path for destroying empty/sentinel.
|
|
//
|
|
// Simplified version of `AssignUnsafe()` to allow calling without a
|
|
// complete type `T`. Uses double-checked locking with a simple thread-safe
|
|
// check for a valid handle based on a node.
|
|
if (GetNodeSafe()) {
|
|
PersistentRegionLock guard;
|
|
const void* old_value = GetValue();
|
|
// The fast path check (GetNodeSafe()) does not acquire the lock. Recheck
|
|
// validity while holding the lock to ensure the reference has not been
|
|
// cleared.
|
|
if (IsValid(old_value)) {
|
|
CrossThreadPersistentRegion& region =
|
|
this->GetPersistentRegion(old_value);
|
|
region.FreeNode(GetNode());
|
|
SetNode(nullptr);
|
|
} else {
|
|
CPPGC_DCHECK(!GetNode());
|
|
}
|
|
}
|
|
// No need to call SetValue() as the handle is not used anymore. This can
|
|
// leave behind stale sentinel values but will always destroy the underlying
|
|
// node.
|
|
}
|
|
|
|
BasicCrossThreadPersistent(
|
|
const SourceLocation& loc = SourceLocation::Current())
|
|
: LocationPolicy(loc) {}
|
|
|
|
BasicCrossThreadPersistent(
|
|
std::nullptr_t, const SourceLocation& loc = SourceLocation::Current())
|
|
: LocationPolicy(loc) {}
|
|
|
|
BasicCrossThreadPersistent(
|
|
SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
|
|
: CrossThreadPersistentBase(s), LocationPolicy(loc) {}
|
|
|
|
BasicCrossThreadPersistent(
|
|
T* raw, const SourceLocation& loc = SourceLocation::Current())
|
|
: CrossThreadPersistentBase(raw), LocationPolicy(loc) {
|
|
if (!IsValid(raw)) return;
|
|
PersistentRegionLock guard;
|
|
CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw);
|
|
SetNode(region.AllocateNode(this, &Trace));
|
|
this->CheckPointer(raw);
|
|
}
|
|
|
|
class UnsafeCtorTag {
|
|
private:
|
|
UnsafeCtorTag() = default;
|
|
template <typename U, typename OtherWeaknessPolicy,
|
|
typename OtherLocationPolicy, typename OtherCheckingPolicy>
|
|
friend class BasicCrossThreadPersistent;
|
|
};
|
|
|
|
BasicCrossThreadPersistent(
|
|
UnsafeCtorTag, T* raw,
|
|
const SourceLocation& loc = SourceLocation::Current())
|
|
: CrossThreadPersistentBase(raw), LocationPolicy(loc) {
|
|
if (!IsValid(raw)) return;
|
|
CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw);
|
|
SetNode(region.AllocateNode(this, &Trace));
|
|
this->CheckPointer(raw);
|
|
}
|
|
|
|
BasicCrossThreadPersistent(
|
|
T& raw, const SourceLocation& loc = SourceLocation::Current())
|
|
: BasicCrossThreadPersistent(&raw, loc) {}
|
|
|
|
template <typename U, typename MemberBarrierPolicy,
|
|
typename MemberWeaknessTag, typename MemberCheckingPolicy,
|
|
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
|
BasicCrossThreadPersistent(
|
|
internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
|
|
MemberCheckingPolicy>
|
|
member,
|
|
const SourceLocation& loc = SourceLocation::Current())
|
|
: BasicCrossThreadPersistent(member.Get(), loc) {}
|
|
|
|
BasicCrossThreadPersistent(
|
|
const BasicCrossThreadPersistent& other,
|
|
const SourceLocation& loc = SourceLocation::Current())
|
|
: BasicCrossThreadPersistent(loc) {
|
|
// Invoke operator=.
|
|
*this = other;
|
|
}
|
|
|
|
// Heterogeneous ctor.
|
|
template <typename U, typename OtherWeaknessPolicy,
|
|
typename OtherLocationPolicy, typename OtherCheckingPolicy,
|
|
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
|
BasicCrossThreadPersistent(
|
|
const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
|
|
OtherLocationPolicy,
|
|
OtherCheckingPolicy>& other,
|
|
const SourceLocation& loc = SourceLocation::Current())
|
|
: BasicCrossThreadPersistent(loc) {
|
|
*this = other;
|
|
}
|
|
|
|
BasicCrossThreadPersistent(
|
|
BasicCrossThreadPersistent&& other,
|
|
const SourceLocation& loc = SourceLocation::Current()) noexcept {
|
|
// Invoke operator=.
|
|
*this = std::move(other);
|
|
}
|
|
|
|
BasicCrossThreadPersistent& operator=(
|
|
const BasicCrossThreadPersistent& other) {
|
|
PersistentRegionLock guard;
|
|
AssignSafe(guard, other.Get());
|
|
return *this;
|
|
}
|
|
|
|
template <typename U, typename OtherWeaknessPolicy,
|
|
typename OtherLocationPolicy, typename OtherCheckingPolicy,
|
|
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
|
BasicCrossThreadPersistent& operator=(
|
|
const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
|
|
OtherLocationPolicy,
|
|
OtherCheckingPolicy>& other) {
|
|
PersistentRegionLock guard;
|
|
AssignSafe(guard, other.Get());
|
|
return *this;
|
|
}
|
|
|
|
BasicCrossThreadPersistent& operator=(BasicCrossThreadPersistent&& other) {
|
|
if (this == &other) return *this;
|
|
Clear();
|
|
PersistentRegionLock guard;
|
|
PersistentBase::operator=(std::move(other));
|
|
LocationPolicy::operator=(std::move(other));
|
|
if (!IsValid(GetValue())) return *this;
|
|
GetNode()->UpdateOwner(this);
|
|
other.SetValue(nullptr);
|
|
other.SetNode(nullptr);
|
|
this->CheckPointer(Get());
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Assigns a raw pointer.
|
|
*
|
|
* Note: **Not thread-safe.**
|
|
*/
|
|
BasicCrossThreadPersistent& operator=(T* other) {
|
|
AssignUnsafe(other);
|
|
return *this;
|
|
}
|
|
|
|
// Assignment from member.
|
|
template <typename U, typename MemberBarrierPolicy,
|
|
typename MemberWeaknessTag, typename MemberCheckingPolicy,
|
|
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
|
BasicCrossThreadPersistent& operator=(
|
|
internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
|
|
MemberCheckingPolicy>
|
|
member) {
|
|
return operator=(member.Get());
|
|
}
|
|
|
|
/**
|
|
* Assigns a nullptr.
|
|
*
|
|
* \returns the handle.
|
|
*/
|
|
BasicCrossThreadPersistent& operator=(std::nullptr_t) {
|
|
Clear();
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Assigns the sentinel pointer.
|
|
*
|
|
* \returns the handle.
|
|
*/
|
|
BasicCrossThreadPersistent& operator=(SentinelPointer s) {
|
|
PersistentRegionLock guard;
|
|
AssignSafe(guard, s);
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Returns a pointer to the stored object.
|
|
*
|
|
* Note: **Not thread-safe.**
|
|
*
|
|
* \returns a pointer to the stored object.
|
|
*/
|
|
// CFI cast exemption to allow passing SentinelPointer through T* and support
|
|
// heterogeneous assignments between different Member and Persistent handles
|
|
// based on their actual types.
|
|
V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const {
|
|
return static_cast<T*>(const_cast<void*>(GetValue()));
|
|
}
|
|
|
|
/**
|
|
* Clears the stored object.
|
|
*/
|
|
void Clear() {
|
|
PersistentRegionLock guard;
|
|
AssignSafe(guard, nullptr);
|
|
}
|
|
|
|
/**
|
|
* Returns a pointer to the stored object and releases it.
|
|
*
|
|
* Note: **Not thread-safe.**
|
|
*
|
|
* \returns a pointer to the stored object.
|
|
*/
|
|
T* Release() {
|
|
T* result = Get();
|
|
Clear();
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Conversio to boolean.
|
|
*
|
|
* Note: **Not thread-safe.**
|
|
*
|
|
* \returns true if an actual object has been stored and false otherwise.
|
|
*/
|
|
explicit operator bool() const { return Get(); }
|
|
|
|
/**
|
|
* Conversion to object of type T.
|
|
*
|
|
* Note: **Not thread-safe.**
|
|
*
|
|
* \returns the object.
|
|
*/
|
|
operator T*() const { return Get(); }
|
|
|
|
/**
|
|
* Dereferences the stored object.
|
|
*
|
|
* Note: **Not thread-safe.**
|
|
*/
|
|
T* operator->() const { return Get(); }
|
|
T& operator*() const { return *Get(); }
|
|
|
|
template <typename U, typename OtherWeaknessPolicy = WeaknessPolicy,
|
|
typename OtherLocationPolicy = LocationPolicy,
|
|
typename OtherCheckingPolicy = CheckingPolicy>
|
|
BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
|
|
OtherCheckingPolicy>
|
|
To() const {
|
|
using OtherBasicCrossThreadPersistent =
|
|
BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
|
|
OtherCheckingPolicy>;
|
|
PersistentRegionLock guard;
|
|
return OtherBasicCrossThreadPersistent(
|
|
typename OtherBasicCrossThreadPersistent::UnsafeCtorTag(),
|
|
static_cast<U*>(Get()));
|
|
}
|
|
|
|
template <typename U = T,
|
|
typename = typename std::enable_if<!BasicCrossThreadPersistent<
|
|
U, WeaknessPolicy>::IsStrongPersistent::value>::type>
|
|
BasicCrossThreadPersistent<U, internal::StrongCrossThreadPersistentPolicy>
|
|
Lock() const {
|
|
return BasicCrossThreadPersistent<
|
|
U, internal::StrongCrossThreadPersistentPolicy>(*this);
|
|
}
|
|
|
|
private:
|
|
static bool IsValid(const void* ptr) {
|
|
return ptr && ptr != kSentinelPointer;
|
|
}
|
|
|
|
static void Trace(Visitor* v, const void* ptr) {
|
|
const auto* handle = static_cast<const BasicCrossThreadPersistent*>(ptr);
|
|
v->TraceRoot(*handle, handle->Location());
|
|
}
|
|
|
|
void AssignUnsafe(T* ptr) {
|
|
const void* old_value = GetValue();
|
|
if (IsValid(old_value)) {
|
|
PersistentRegionLock guard;
|
|
old_value = GetValue();
|
|
// The fast path check (IsValid()) does not acquire the lock. Reload
|
|
// the value to ensure the reference has not been cleared.
|
|
if (IsValid(old_value)) {
|
|
CrossThreadPersistentRegion& region =
|
|
this->GetPersistentRegion(old_value);
|
|
if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) {
|
|
SetValue(ptr);
|
|
this->CheckPointer(ptr);
|
|
return;
|
|
}
|
|
region.FreeNode(GetNode());
|
|
SetNode(nullptr);
|
|
} else {
|
|
CPPGC_DCHECK(!GetNode());
|
|
}
|
|
}
|
|
SetValue(ptr);
|
|
if (!IsValid(ptr)) return;
|
|
PersistentRegionLock guard;
|
|
SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace));
|
|
this->CheckPointer(ptr);
|
|
}
|
|
|
|
void AssignSafe(PersistentRegionLock&, T* ptr) {
|
|
PersistentRegionLock::AssertLocked();
|
|
const void* old_value = GetValue();
|
|
if (IsValid(old_value)) {
|
|
CrossThreadPersistentRegion& region =
|
|
this->GetPersistentRegion(old_value);
|
|
if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) {
|
|
SetValue(ptr);
|
|
this->CheckPointer(ptr);
|
|
return;
|
|
}
|
|
region.FreeNode(GetNode());
|
|
SetNode(nullptr);
|
|
}
|
|
SetValue(ptr);
|
|
if (!IsValid(ptr)) return;
|
|
SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace));
|
|
this->CheckPointer(ptr);
|
|
}
|
|
|
|
void ClearFromGC() const {
|
|
if (IsValid(GetValueFromGC())) {
|
|
WeaknessPolicy::GetPersistentRegion(GetValueFromGC())
|
|
.FreeNode(GetNodeFromGC());
|
|
CrossThreadPersistentBase::ClearFromGC();
|
|
}
|
|
}
|
|
|
|
// See Get() for details.
|
|
V8_CLANG_NO_SANITIZE("cfi-unrelated-cast")
|
|
T* GetFromGC() const {
|
|
return static_cast<T*>(const_cast<void*>(GetValueFromGC()));
|
|
}
|
|
|
|
friend class cppgc::Visitor;
|
|
};
|
|
|
|
template <typename T, typename LocationPolicy, typename CheckingPolicy>
|
|
struct IsWeak<
|
|
BasicCrossThreadPersistent<T, internal::WeakCrossThreadPersistentPolicy,
|
|
LocationPolicy, CheckingPolicy>>
|
|
: std::true_type {};
|
|
|
|
} // namespace internal
|
|
|
|
namespace subtle {
|
|
|
|
/**
|
|
* **DO NOT USE: Has known caveats, see below.**
|
|
*
|
|
* CrossThreadPersistent allows retaining objects from threads other than the
|
|
* thread the owning heap is operating on.
|
|
*
|
|
* Known caveats:
|
|
* - Does not protect the heap owning an object from terminating.
|
|
* - Reaching transitively through the graph is unsupported as objects may be
|
|
* moved concurrently on the thread owning the object.
|
|
*/
|
|
template <typename T>
|
|
using CrossThreadPersistent = internal::BasicCrossThreadPersistent<
|
|
T, internal::StrongCrossThreadPersistentPolicy>;
|
|
|
|
/**
|
|
* **DO NOT USE: Has known caveats, see below.**
|
|
*
|
|
* CrossThreadPersistent allows weakly retaining objects from threads other than
|
|
* the thread the owning heap is operating on.
|
|
*
|
|
* Known caveats:
|
|
* - Does not protect the heap owning an object from terminating.
|
|
* - Reaching transitively through the graph is unsupported as objects may be
|
|
* moved concurrently on the thread owning the object.
|
|
*/
|
|
template <typename T>
|
|
using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent<
|
|
T, internal::WeakCrossThreadPersistentPolicy>;
|
|
|
|
} // namespace subtle
|
|
} // namespace cppgc
|
|
|
|
#endif // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
|