1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
6#define INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
7
8#include <atomic>
9
10#include "cppgc/internal/persistent-node.h"
11#include "cppgc/internal/pointer-policies.h"
12#include "cppgc/persistent.h"
13#include "cppgc/visitor.h"
14
15namespace cppgc {
16namespace internal {
17
18// Wrapper around PersistentBase that allows accessing poisoned memory when
19// using ASAN. This is needed as the GC of the heap that owns the value
20// of a CTP, may clear it (heap termination, weakness) while the object
21// holding the CTP may be poisoned as itself may be deemed dead.
22class CrossThreadPersistentBase : public PersistentBase {
23 public:
24  CrossThreadPersistentBase() = default;
25  explicit CrossThreadPersistentBase(const void* raw) : PersistentBase(raw) {}
26
27  V8_CLANG_NO_SANITIZE("address") const void* GetValueFromGC() const {
28    return raw_;
29  }
30
31  V8_CLANG_NO_SANITIZE("address")
32  PersistentNode* GetNodeFromGC() const { return node_; }
33
34  V8_CLANG_NO_SANITIZE("address")
35  void ClearFromGC() const {
36    raw_ = nullptr;
37    SetNodeSafe(nullptr);
38  }
39
40  // GetNodeSafe() can be used for a thread-safe IsValid() check in a
41  // double-checked locking pattern. See ~BasicCrossThreadPersistent.
42  PersistentNode* GetNodeSafe() const {
43    return reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->load(
44        std::memory_order_acquire);
45  }
46
47  // The GC writes using SetNodeSafe() while holding the lock.
48  V8_CLANG_NO_SANITIZE("address")
49  void SetNodeSafe(PersistentNode* value) const {
50#if defined(__has_feature)
51#if __has_feature(address_sanitizer)
52#define V8_IS_ASAN 1
53#endif
54#endif
55
56#ifdef V8_IS_ASAN
57    __atomic_store(&node_, &value, __ATOMIC_RELEASE);
58#else   // !V8_IS_ASAN
59    // Non-ASAN builds can use atomics. This also covers MSVC which does not
60    // have the __atomic_store intrinsic.
61    reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->store(
62        value, std::memory_order_release);
63#endif  // !V8_IS_ASAN
64
65#undef V8_IS_ASAN
66  }
67};
68
69template <typename T, typename WeaknessPolicy, typename LocationPolicy,
70          typename CheckingPolicy>
71class BasicCrossThreadPersistent final : public CrossThreadPersistentBase,
72                                         public LocationPolicy,
73                                         private WeaknessPolicy,
74                                         private CheckingPolicy {
75 public:
76  using typename WeaknessPolicy::IsStrongPersistent;
77  using PointeeType = T;
78
79  ~BasicCrossThreadPersistent() {
80    //  This implements fast path for destroying empty/sentinel.
81    //
82    // Simplified version of `AssignUnsafe()` to allow calling without a
83    // complete type `T`. Uses double-checked locking with a simple thread-safe
84    // check for a valid handle based on a node.
85    if (GetNodeSafe()) {
86      PersistentRegionLock guard;
87      const void* old_value = GetValue();
88      // The fast path check (GetNodeSafe()) does not acquire the lock. Recheck
89      // validity while holding the lock to ensure the reference has not been
90      // cleared.
91      if (IsValid(old_value)) {
92        CrossThreadPersistentRegion& region =
93            this->GetPersistentRegion(old_value);
94        region.FreeNode(GetNode());
95        SetNode(nullptr);
96      } else {
97        CPPGC_DCHECK(!GetNode());
98      }
99    }
100    // No need to call SetValue() as the handle is not used anymore. This can
101    // leave behind stale sentinel values but will always destroy the underlying
102    // node.
103  }
104
105  BasicCrossThreadPersistent(
106      const SourceLocation& loc = SourceLocation::Current())
107      : LocationPolicy(loc) {}
108
109  BasicCrossThreadPersistent(
110      std::nullptr_t, const SourceLocation& loc = SourceLocation::Current())
111      : LocationPolicy(loc) {}
112
113  BasicCrossThreadPersistent(
114      SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
115      : CrossThreadPersistentBase(s), LocationPolicy(loc) {}
116
117  BasicCrossThreadPersistent(
118      T* raw, const SourceLocation& loc = SourceLocation::Current())
119      : CrossThreadPersistentBase(raw), LocationPolicy(loc) {
120    if (!IsValid(raw)) return;
121    PersistentRegionLock guard;
122    CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw);
123    SetNode(region.AllocateNode(this, &TraceAsRoot));
124    this->CheckPointer(raw);
125  }
126
127  class UnsafeCtorTag {
128   private:
129    UnsafeCtorTag() = default;
130    template <typename U, typename OtherWeaknessPolicy,
131              typename OtherLocationPolicy, typename OtherCheckingPolicy>
132    friend class BasicCrossThreadPersistent;
133  };
134
135  BasicCrossThreadPersistent(
136      UnsafeCtorTag, T* raw,
137      const SourceLocation& loc = SourceLocation::Current())
138      : CrossThreadPersistentBase(raw), LocationPolicy(loc) {
139    if (!IsValid(raw)) return;
140    CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw);
141    SetNode(region.AllocateNode(this, &TraceAsRoot));
142    this->CheckPointer(raw);
143  }
144
145  BasicCrossThreadPersistent(
146      T& raw, const SourceLocation& loc = SourceLocation::Current())
147      : BasicCrossThreadPersistent(&raw, loc) {}
148
149  template <typename U, typename MemberBarrierPolicy,
150            typename MemberWeaknessTag, typename MemberCheckingPolicy,
151            typename MemberStorageType,
152            typename = std::enable_if_t<std::is_base_of<T, U>::value>>
153  BasicCrossThreadPersistent(
154      internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
155                            MemberCheckingPolicy, MemberStorageType>
156          member,
157      const SourceLocation& loc = SourceLocation::Current())
158      : BasicCrossThreadPersistent(member.Get(), loc) {}
159
160  BasicCrossThreadPersistent(
161      const BasicCrossThreadPersistent& other,
162      const SourceLocation& loc = SourceLocation::Current())
163      : BasicCrossThreadPersistent(loc) {
164    // Invoke operator=.
165    *this = other;
166  }
167
168  // Heterogeneous ctor.
169  template <typename U, typename OtherWeaknessPolicy,
170            typename OtherLocationPolicy, typename OtherCheckingPolicy,
171            typename = std::enable_if_t<std::is_base_of<T, U>::value>>
172  BasicCrossThreadPersistent(
173      const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
174                                       OtherLocationPolicy,
175                                       OtherCheckingPolicy>& other,
176      const SourceLocation& loc = SourceLocation::Current())
177      : BasicCrossThreadPersistent(loc) {
178    *this = other;
179  }
180
181  BasicCrossThreadPersistent(
182      BasicCrossThreadPersistent&& other,
183      const SourceLocation& loc = SourceLocation::Current()) noexcept {
184    // Invoke operator=.
185    *this = std::move(other);
186  }
187
188  BasicCrossThreadPersistent& operator=(
189      const BasicCrossThreadPersistent& other) {
190    PersistentRegionLock guard;
191    AssignSafe(guard, other.Get());
192    return *this;
193  }
194
195  template <typename U, typename OtherWeaknessPolicy,
196            typename OtherLocationPolicy, typename OtherCheckingPolicy,
197            typename = std::enable_if_t<std::is_base_of<T, U>::value>>
198  BasicCrossThreadPersistent& operator=(
199      const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
200                                       OtherLocationPolicy,
201                                       OtherCheckingPolicy>& other) {
202    PersistentRegionLock guard;
203    AssignSafe(guard, other.Get());
204    return *this;
205  }
206
207  BasicCrossThreadPersistent& operator=(BasicCrossThreadPersistent&& other) {
208    if (this == &other) return *this;
209    Clear();
210    PersistentRegionLock guard;
211    PersistentBase::operator=(std::move(other));
212    LocationPolicy::operator=(std::move(other));
213    if (!IsValid(GetValue())) return *this;
214    GetNode()->UpdateOwner(this);
215    other.SetValue(nullptr);
216    other.SetNode(nullptr);
217    this->CheckPointer(Get());
218    return *this;
219  }
220
221  /**
222   * Assigns a raw pointer.
223   *
224   * Note: **Not thread-safe.**
225   */
226  BasicCrossThreadPersistent& operator=(T* other) {
227    AssignUnsafe(other);
228    return *this;
229  }
230
231  // Assignment from member.
232  template <typename U, typename MemberBarrierPolicy,
233            typename MemberWeaknessTag, typename MemberCheckingPolicy,
234            typename MemberStorageType,
235            typename = std::enable_if_t<std::is_base_of<T, U>::value>>
236  BasicCrossThreadPersistent& operator=(
237      internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
238                            MemberCheckingPolicy, MemberStorageType>
239          member) {
240    return operator=(member.Get());
241  }
242
243  /**
244   * Assigns a nullptr.
245   *
246   * \returns the handle.
247   */
248  BasicCrossThreadPersistent& operator=(std::nullptr_t) {
249    Clear();
250    return *this;
251  }
252
253  /**
254   * Assigns the sentinel pointer.
255   *
256   * \returns the handle.
257   */
258  BasicCrossThreadPersistent& operator=(SentinelPointer s) {
259    PersistentRegionLock guard;
260    AssignSafe(guard, s);
261    return *this;
262  }
263
264  /**
265   * Returns a pointer to the stored object.
266   *
267   * Note: **Not thread-safe.**
268   *
269   * \returns a pointer to the stored object.
270   */
271  // CFI cast exemption to allow passing SentinelPointer through T* and support
272  // heterogeneous assignments between different Member and Persistent handles
273  // based on their actual types.
274  V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const {
275    return static_cast<T*>(const_cast<void*>(GetValue()));
276  }
277
278  /**
279   * Clears the stored object.
280   */
281  void Clear() {
282    PersistentRegionLock guard;
283    AssignSafe(guard, nullptr);
284  }
285
286  /**
287   * Returns a pointer to the stored object and releases it.
288   *
289   * Note: **Not thread-safe.**
290   *
291   * \returns a pointer to the stored object.
292   */
293  T* Release() {
294    T* result = Get();
295    Clear();
296    return result;
297  }
298
299  /**
300   * Conversio to boolean.
301   *
302   * Note: **Not thread-safe.**
303   *
304   * \returns true if an actual object has been stored and false otherwise.
305   */
306  explicit operator bool() const { return Get(); }
307
308  /**
309   * Conversion to object of type T.
310   *
311   * Note: **Not thread-safe.**
312   *
313   * \returns the object.
314   */
315  operator T*() const { return Get(); }
316
317  /**
318   * Dereferences the stored object.
319   *
320   * Note: **Not thread-safe.**
321   */
322  T* operator->() const { return Get(); }
323  T& operator*() const { return *Get(); }
324
325  template <typename U, typename OtherWeaknessPolicy = WeaknessPolicy,
326            typename OtherLocationPolicy = LocationPolicy,
327            typename OtherCheckingPolicy = CheckingPolicy>
328  BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
329                             OtherCheckingPolicy>
330  To() const {
331    using OtherBasicCrossThreadPersistent =
332        BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
333                                   OtherCheckingPolicy>;
334    PersistentRegionLock guard;
335    return OtherBasicCrossThreadPersistent(
336        typename OtherBasicCrossThreadPersistent::UnsafeCtorTag(),
337        static_cast<U*>(Get()));
338  }
339
340  template <typename U = T,
341            typename = typename std::enable_if<!BasicCrossThreadPersistent<
342                U, WeaknessPolicy>::IsStrongPersistent::value>::type>
343  BasicCrossThreadPersistent<U, internal::StrongCrossThreadPersistentPolicy>
344  Lock() const {
345    return BasicCrossThreadPersistent<
346        U, internal::StrongCrossThreadPersistentPolicy>(*this);
347  }
348
349 private:
350  static bool IsValid(const void* ptr) {
351    return ptr && ptr != kSentinelPointer;
352  }
353
354  static void TraceAsRoot(RootVisitor& root_visitor, const void* ptr) {
355    root_visitor.Trace(*static_cast<const BasicCrossThreadPersistent*>(ptr));
356  }
357
358  void AssignUnsafe(T* ptr) {
359    const void* old_value = GetValue();
360    if (IsValid(old_value)) {
361      PersistentRegionLock guard;
362      old_value = GetValue();
363      // The fast path check (IsValid()) does not acquire the lock. Reload
364      // the value to ensure the reference has not been cleared.
365      if (IsValid(old_value)) {
366        CrossThreadPersistentRegion& region =
367            this->GetPersistentRegion(old_value);
368        if (IsValid(ptr) && (&region == &this->GetPersistentRegion(ptr))) {
369          SetValue(ptr);
370          this->CheckPointer(ptr);
371          return;
372        }
373        region.FreeNode(GetNode());
374        SetNode(nullptr);
375      } else {
376        CPPGC_DCHECK(!GetNode());
377      }
378    }
379    SetValue(ptr);
380    if (!IsValid(ptr)) return;
381    PersistentRegionLock guard;
382    SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &TraceAsRoot));
383    this->CheckPointer(ptr);
384  }
385
386  void AssignSafe(PersistentRegionLock&, T* ptr) {
387    PersistentRegionLock::AssertLocked();
388    const void* old_value = GetValue();
389    if (IsValid(old_value)) {
390      CrossThreadPersistentRegion& region =
391          this->GetPersistentRegion(old_value);
392      if (IsValid(ptr) && (&region == &this->GetPersistentRegion(ptr))) {
393        SetValue(ptr);
394        this->CheckPointer(ptr);
395        return;
396      }
397      region.FreeNode(GetNode());
398      SetNode(nullptr);
399    }
400    SetValue(ptr);
401    if (!IsValid(ptr)) return;
402    SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &TraceAsRoot));
403    this->CheckPointer(ptr);
404  }
405
406  void ClearFromGC() const {
407    if (IsValid(GetValueFromGC())) {
408      WeaknessPolicy::GetPersistentRegion(GetValueFromGC())
409          .FreeNode(GetNodeFromGC());
410      CrossThreadPersistentBase::ClearFromGC();
411    }
412  }
413
414  // See Get() for details.
415  V8_CLANG_NO_SANITIZE("cfi-unrelated-cast")
416  T* GetFromGC() const {
417    return static_cast<T*>(const_cast<void*>(GetValueFromGC()));
418  }
419
420  friend class internal::RootVisitor;
421};
422
423template <typename T, typename LocationPolicy, typename CheckingPolicy>
424struct IsWeak<
425    BasicCrossThreadPersistent<T, internal::WeakCrossThreadPersistentPolicy,
426                               LocationPolicy, CheckingPolicy>>
427    : std::true_type {};
428
429}  // namespace internal
430
431namespace subtle {
432
433/**
434 * **DO NOT USE: Has known caveats, see below.**
435 *
436 * CrossThreadPersistent allows retaining objects from threads other than the
437 * thread the owning heap is operating on.
438 *
439 * Known caveats:
440 * - Does not protect the heap owning an object from terminating.
441 * - Reaching transitively through the graph is unsupported as objects may be
442 *   moved concurrently on the thread owning the object.
443 */
444template <typename T>
445using CrossThreadPersistent = internal::BasicCrossThreadPersistent<
446    T, internal::StrongCrossThreadPersistentPolicy>;
447
448/**
449 * **DO NOT USE: Has known caveats, see below.**
450 *
451 * CrossThreadPersistent allows weakly retaining objects from threads other than
452 * the thread the owning heap is operating on.
453 *
454 * Known caveats:
455 * - Does not protect the heap owning an object from terminating.
456 * - Reaching transitively through the graph is unsupported as objects may be
457 *   moved concurrently on the thread owning the object.
458 */
459template <typename T>
460using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent<
461    T, internal::WeakCrossThreadPersistentPolicy>;
462
463}  // namespace subtle
464}  // namespace cppgc
465
466#endif  // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
467