v8/include/cppgc/persistent.h
Michael Lippautz c58862c360 cppgc: Allow CrossThreadPersistent to access poisoned memory from GC
Allow CrossThreadPersistent and its weak form to access ASAN poisoned
memory from the GC entry points.

In general, payloads of to-be-finalized objects are poisoned until the
finalizer actually runs to avoid accidentally touching that payload.

In the case of cross-thread handles, these may need to be cleared by a
different thread before the finalizer actually runs. In order to clear
those references, the slot needs to be unpoisoned.

This is issue is ASAN-only and does not affect production or other
debug builds.

Bug: chromium:1230599, chromium:1056170
Change-Id: If4d0808953047319b02653821abbb5c638084dc5
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3040845
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: Omer Katz <omerkatz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75846}
2021-07-21 19:11:01 +00:00

372 lines
14 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_PERSISTENT_H_
#define INCLUDE_CPPGC_PERSISTENT_H_
#include <type_traits>
#include "cppgc/internal/persistent-node.h"
#include "cppgc/internal/pointer-policies.h"
#include "cppgc/sentinel-pointer.h"
#include "cppgc/source-location.h"
#include "cppgc/type-traits.h"
#include "cppgc/visitor.h"
#include "v8config.h" // NOLINT(build/include_directory)
namespace cppgc {
class Visitor;
namespace internal {
// PersistentBase always refers to the object as const object and defers to
// BasicPersistent on casting to the right type as needed.
class PersistentBase {
protected:
PersistentBase() = default;
explicit PersistentBase(const void* raw) : raw_(raw) {}
const void* GetValue() const { return raw_; }
void SetValue(const void* value) { raw_ = value; }
PersistentNode* GetNode() const { return node_; }
void SetNode(PersistentNode* node) { node_ = node; }
// Performs a shallow clear which assumes that internal persistent nodes are
// destroyed elsewhere.
void ClearFromGC() const {
raw_ = nullptr;
node_ = nullptr;
}
protected:
mutable const void* raw_ = nullptr;
mutable PersistentNode* node_ = nullptr;
friend class PersistentRegion;
};
// The basic class from which all Persistent classes are generated.
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
typename CheckingPolicy>
class BasicPersistent final : public PersistentBase,
public LocationPolicy,
private WeaknessPolicy,
private CheckingPolicy {
public:
using typename WeaknessPolicy::IsStrongPersistent;
using PointeeType = T;
// Null-state/sentinel constructors.
BasicPersistent( // NOLINT
const SourceLocation& loc = SourceLocation::Current())
: LocationPolicy(loc) {}
BasicPersistent(std::nullptr_t, // NOLINT
const SourceLocation& loc = SourceLocation::Current())
: LocationPolicy(loc) {}
BasicPersistent( // NOLINT
SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
: PersistentBase(s), LocationPolicy(loc) {}
// Raw value constructors.
BasicPersistent(T* raw, // NOLINT
const SourceLocation& loc = SourceLocation::Current())
: PersistentBase(raw), LocationPolicy(loc) {
if (!IsValid()) return;
SetNode(WeaknessPolicy::GetPersistentRegion(GetValue())
.AllocateNode(this, &BasicPersistent::Trace));
this->CheckPointer(Get());
}
BasicPersistent(T& raw, // NOLINT
const SourceLocation& loc = SourceLocation::Current())
: BasicPersistent(&raw, loc) {}
// Copy ctor.
BasicPersistent(const BasicPersistent& other,
const SourceLocation& loc = SourceLocation::Current())
: BasicPersistent(other.Get(), loc) {}
// Heterogeneous ctor.
template <typename U, typename OtherWeaknessPolicy,
typename OtherLocationPolicy, typename OtherCheckingPolicy,
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
BasicPersistent(
const BasicPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
OtherCheckingPolicy>& other,
const SourceLocation& loc = SourceLocation::Current())
: BasicPersistent(other.Get(), loc) {}
// Move ctor. The heterogeneous move ctor is not supported since e.g.
// persistent can't reuse persistent node from weak persistent.
BasicPersistent(
BasicPersistent&& other,
const SourceLocation& loc = SourceLocation::Current()) noexcept
: PersistentBase(std::move(other)), LocationPolicy(std::move(other)) {
if (!IsValid()) return;
GetNode()->UpdateOwner(this);
other.SetValue(nullptr);
other.SetNode(nullptr);
this->CheckPointer(Get());
}
// Constructor from member.
template <typename U, typename MemberBarrierPolicy,
typename MemberWeaknessTag, typename MemberCheckingPolicy,
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
BasicPersistent(internal::BasicMember<U, MemberBarrierPolicy,
MemberWeaknessTag, MemberCheckingPolicy>
member,
const SourceLocation& loc = SourceLocation::Current())
: BasicPersistent(member.Get(), loc) {}
~BasicPersistent() { Clear(); }
// Copy assignment.
BasicPersistent& operator=(const BasicPersistent& other) {
return operator=(other.Get());
}
template <typename U, typename OtherWeaknessPolicy,
typename OtherLocationPolicy, typename OtherCheckingPolicy,
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
BasicPersistent& operator=(
const BasicPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
OtherCheckingPolicy>& other) {
return operator=(other.Get());
}
// Move assignment.
BasicPersistent& operator=(BasicPersistent&& other) noexcept {
if (this == &other) return *this;
Clear();
PersistentBase::operator=(std::move(other));
LocationPolicy::operator=(std::move(other));
if (!IsValid()) return *this;
GetNode()->UpdateOwner(this);
other.SetValue(nullptr);
other.SetNode(nullptr);
this->CheckPointer(Get());
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>>
BasicPersistent& operator=(
internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
MemberCheckingPolicy>
member) {
return operator=(member.Get());
}
BasicPersistent& operator=(T* other) {
Assign(other);
return *this;
}
BasicPersistent& operator=(std::nullptr_t) {
Clear();
return *this;
}
BasicPersistent& operator=(SentinelPointer s) {
Assign(s);
return *this;
}
explicit operator bool() const { return Get(); }
operator T*() const { return Get(); }
T* operator->() const { return Get(); }
T& operator*() const { return *Get(); }
// 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 {
// The const_cast below removes the constness from PersistentBase storage.
// The following static_cast re-adds any constness if specified through the
// user-visible template parameter T.
return static_cast<T*>(const_cast<void*>(GetValue()));
}
void Clear() {
// Simplified version of `Assign()` to allow calling without a complete type
// `T`.
if (IsValid()) {
WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode());
SetNode(nullptr);
}
SetValue(nullptr);
}
T* Release() {
T* result = Get();
Clear();
return result;
}
template <typename U, typename OtherWeaknessPolicy = WeaknessPolicy,
typename OtherLocationPolicy = LocationPolicy,
typename OtherCheckingPolicy = CheckingPolicy>
BasicPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
OtherCheckingPolicy>
To() const {
return BasicPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
OtherCheckingPolicy>(static_cast<U*>(Get()));
}
private:
static void Trace(Visitor* v, const void* ptr) {
const auto* persistent = static_cast<const BasicPersistent*>(ptr);
v->TraceRoot(*persistent, persistent->Location());
}
bool IsValid() const {
// Ideally, handling kSentinelPointer would be done by the embedder. On the
// other hand, having Persistent aware of it is beneficial since no node
// gets wasted.
return GetValue() != nullptr && GetValue() != kSentinelPointer;
}
void Assign(T* ptr) {
if (IsValid()) {
if (ptr && ptr != kSentinelPointer) {
// Simply assign the pointer reusing the existing node.
SetValue(ptr);
this->CheckPointer(ptr);
return;
}
WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode());
SetNode(nullptr);
}
SetValue(ptr);
if (!IsValid()) return;
SetNode(WeaknessPolicy::GetPersistentRegion(GetValue())
.AllocateNode(this, &BasicPersistent::Trace));
this->CheckPointer(Get());
}
void ClearFromGC() const {
if (IsValid()) {
WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode());
PersistentBase::ClearFromGC();
}
}
// Set Get() for details.
V8_CLANG_NO_SANITIZE("cfi-unrelated-cast")
T* GetFromGC() const {
return static_cast<T*>(const_cast<void*>(GetValue()));
}
friend class cppgc::Visitor;
};
template <typename T1, typename WeaknessPolicy1, typename LocationPolicy1,
typename CheckingPolicy1, typename T2, typename WeaknessPolicy2,
typename LocationPolicy2, typename CheckingPolicy2>
bool operator==(const BasicPersistent<T1, WeaknessPolicy1, LocationPolicy1,
CheckingPolicy1>& p1,
const BasicPersistent<T2, WeaknessPolicy2, LocationPolicy2,
CheckingPolicy2>& p2) {
return p1.Get() == p2.Get();
}
template <typename T1, typename WeaknessPolicy1, typename LocationPolicy1,
typename CheckingPolicy1, typename T2, typename WeaknessPolicy2,
typename LocationPolicy2, typename CheckingPolicy2>
bool operator!=(const BasicPersistent<T1, WeaknessPolicy1, LocationPolicy1,
CheckingPolicy1>& p1,
const BasicPersistent<T2, WeaknessPolicy2, LocationPolicy2,
CheckingPolicy2>& p2) {
return !(p1 == p2);
}
template <typename T1, typename PersistentWeaknessPolicy,
typename PersistentLocationPolicy, typename PersistentCheckingPolicy,
typename T2, typename MemberWriteBarrierPolicy,
typename MemberWeaknessTag, typename MemberCheckingPolicy>
bool operator==(const BasicPersistent<T1, PersistentWeaknessPolicy,
PersistentLocationPolicy,
PersistentCheckingPolicy>& p,
BasicMember<T2, MemberWeaknessTag, MemberWriteBarrierPolicy,
MemberCheckingPolicy>
m) {
return p.Get() == m.Get();
}
template <typename T1, typename PersistentWeaknessPolicy,
typename PersistentLocationPolicy, typename PersistentCheckingPolicy,
typename T2, typename MemberWriteBarrierPolicy,
typename MemberWeaknessTag, typename MemberCheckingPolicy>
bool operator!=(const BasicPersistent<T1, PersistentWeaknessPolicy,
PersistentLocationPolicy,
PersistentCheckingPolicy>& p,
BasicMember<T2, MemberWeaknessTag, MemberWriteBarrierPolicy,
MemberCheckingPolicy>
m) {
return !(p == m);
}
template <typename T1, typename MemberWriteBarrierPolicy,
typename MemberWeaknessTag, typename MemberCheckingPolicy,
typename T2, typename PersistentWeaknessPolicy,
typename PersistentLocationPolicy, typename PersistentCheckingPolicy>
bool operator==(BasicMember<T2, MemberWeaknessTag, MemberWriteBarrierPolicy,
MemberCheckingPolicy>
m,
const BasicPersistent<T1, PersistentWeaknessPolicy,
PersistentLocationPolicy,
PersistentCheckingPolicy>& p) {
return m.Get() == p.Get();
}
template <typename T1, typename MemberWriteBarrierPolicy,
typename MemberWeaknessTag, typename MemberCheckingPolicy,
typename T2, typename PersistentWeaknessPolicy,
typename PersistentLocationPolicy, typename PersistentCheckingPolicy>
bool operator!=(BasicMember<T2, MemberWeaknessTag, MemberWriteBarrierPolicy,
MemberCheckingPolicy>
m,
const BasicPersistent<T1, PersistentWeaknessPolicy,
PersistentLocationPolicy,
PersistentCheckingPolicy>& p) {
return !(m == p);
}
template <typename T, typename LocationPolicy, typename CheckingPolicy>
struct IsWeak<BasicPersistent<T, internal::WeakPersistentPolicy, LocationPolicy,
CheckingPolicy>> : std::true_type {};
} // namespace internal
/**
* Persistent is a way to create a strong pointer from an off-heap object to
* another on-heap object. As long as the Persistent handle is alive the GC will
* keep the object pointed to alive. The Persistent handle is always a GC root
* from the point of view of the GC. Persistent must be constructed and
* destructed in the same thread.
*/
template <typename T>
using Persistent =
internal::BasicPersistent<T, internal::StrongPersistentPolicy>;
/**
* WeakPersistent is a way to create a weak pointer from an off-heap object to
* an on-heap object. The pointer is automatically cleared when the pointee gets
* collected. WeakPersistent must be constructed and destructed in the same
* thread.
*/
template <typename T>
using WeakPersistent =
internal::BasicPersistent<T, internal::WeakPersistentPolicy>;
} // namespace cppgc
#endif // INCLUDE_CPPGC_PERSISTENT_H_