v8  10.1.124 (node 18.2.0)
V8 is Google's open source JavaScript engine
cross-thread-persistent.h
Go to the documentation of this file.
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 
15 namespace cppgc {
16 namespace 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.
23  public:
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 
69 template <typename T, typename WeaknessPolicy, typename LocationPolicy,
70  typename CheckingPolicy>
72  public LocationPolicy,
73  private WeaknessPolicy,
74  private CheckingPolicy {
75  public:
76  using typename WeaknessPolicy::IsStrongPersistent;
77  using PointeeType = T;
78 
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);
95  SetNode(nullptr);
96  } else {
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 
106  const SourceLocation& loc = SourceLocation::Current())
107  : LocationPolicy(loc) {}
108 
110  std::nullptr_t, const SourceLocation& loc = SourceLocation::Current())
111  : LocationPolicy(loc) {}
112 
114  SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
115  : CrossThreadPersistentBase(s), LocationPolicy(loc) {}
116 
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, &Trace));
124  this->CheckPointer(raw);
125  }
126 
128  private:
129  UnsafeCtorTag() = default;
130  template <typename U, typename OtherWeaknessPolicy,
131  typename OtherLocationPolicy, typename OtherCheckingPolicy>
133  };
134 
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, &Trace));
142  this->CheckPointer(raw);
143  }
144 
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 = std::enable_if_t<std::is_base_of<T, U>::value>>
153  internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
154  MemberCheckingPolicy>
155  member,
156  const SourceLocation& loc = SourceLocation::Current())
157  : BasicCrossThreadPersistent(member.Get(), loc) {}
158 
160  const BasicCrossThreadPersistent& other,
161  const SourceLocation& loc = SourceLocation::Current())
163  // Invoke operator=.
164  *this = other;
165  }
166 
167  // Heterogeneous ctor.
168  template <typename U, typename OtherWeaknessPolicy,
169  typename OtherLocationPolicy, typename OtherCheckingPolicy,
170  typename = std::enable_if_t<std::is_base_of<T, U>::value>>
172  const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
173  OtherLocationPolicy,
174  OtherCheckingPolicy>& other,
175  const SourceLocation& loc = SourceLocation::Current())
177  *this = other;
178  }
179 
182  const SourceLocation& loc = SourceLocation::Current()) noexcept {
183  // Invoke operator=.
184  *this = std::move(other);
185  }
186 
188  const BasicCrossThreadPersistent& other) {
189  PersistentRegionLock guard;
190  AssignSafe(guard, other.Get());
191  return *this;
192  }
193 
194  template <typename U, typename OtherWeaknessPolicy,
195  typename OtherLocationPolicy, typename OtherCheckingPolicy,
196  typename = std::enable_if_t<std::is_base_of<T, U>::value>>
198  const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
199  OtherLocationPolicy,
200  OtherCheckingPolicy>& other) {
201  PersistentRegionLock guard;
202  AssignSafe(guard, other.Get());
203  return *this;
204  }
205 
207  if (this == &other) return *this;
208  Clear();
209  PersistentRegionLock guard;
210  PersistentBase::operator=(std::move(other));
211  LocationPolicy::operator=(std::move(other));
212  if (!IsValid(GetValue())) return *this;
213  GetNode()->UpdateOwner(this);
214  other.SetValue(nullptr);
215  other.SetNode(nullptr);
216  this->CheckPointer(Get());
217  return *this;
218  }
219 
220  /**
221  * Assigns a raw pointer.
222  *
223  * Note: **Not thread-safe.**
224  */
226  AssignUnsafe(other);
227  return *this;
228  }
229 
230  // Assignment from member.
231  template <typename U, typename MemberBarrierPolicy,
232  typename MemberWeaknessTag, typename MemberCheckingPolicy,
233  typename = std::enable_if_t<std::is_base_of<T, U>::value>>
235  internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
236  MemberCheckingPolicy>
237  member) {
238  return operator=(member.Get());
239  }
240 
241  /**
242  * Assigns a nullptr.
243  *
244  * \returns the handle.
245  */
247  Clear();
248  return *this;
249  }
250 
251  /**
252  * Assigns the sentinel pointer.
253  *
254  * \returns the handle.
255  */
257  PersistentRegionLock guard;
258  AssignSafe(guard, s);
259  return *this;
260  }
261 
262  /**
263  * Returns a pointer to the stored object.
264  *
265  * Note: **Not thread-safe.**
266  *
267  * \returns a pointer to the stored object.
268  */
269  // CFI cast exemption to allow passing SentinelPointer through T* and support
270  // heterogeneous assignments between different Member and Persistent handles
271  // based on their actual types.
272  V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const {
273  return static_cast<T*>(const_cast<void*>(GetValue()));
274  }
275 
276  /**
277  * Clears the stored object.
278  */
279  void Clear() {
280  PersistentRegionLock guard;
281  AssignSafe(guard, nullptr);
282  }
283 
284  /**
285  * Returns a pointer to the stored object and releases it.
286  *
287  * Note: **Not thread-safe.**
288  *
289  * \returns a pointer to the stored object.
290  */
291  T* Release() {
292  T* result = Get();
293  Clear();
294  return result;
295  }
296 
297  /**
298  * Conversio to boolean.
299  *
300  * Note: **Not thread-safe.**
301  *
302  * \returns true if an actual object has been stored and false otherwise.
303  */
304  explicit operator bool() const { return Get(); }
305 
306  /**
307  * Conversion to object of type T.
308  *
309  * Note: **Not thread-safe.**
310  *
311  * \returns the object.
312  */
313  operator T*() const { return Get(); }
314 
315  /**
316  * Dereferences the stored object.
317  *
318  * Note: **Not thread-safe.**
319  */
320  T* operator->() const { return Get(); }
321  T& operator*() const { return *Get(); }
322 
323  template <typename U, typename OtherWeaknessPolicy = WeaknessPolicy,
324  typename OtherLocationPolicy = LocationPolicy,
325  typename OtherCheckingPolicy = CheckingPolicy>
326  BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
327  OtherCheckingPolicy>
328  To() const {
329  using OtherBasicCrossThreadPersistent =
330  BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
331  OtherCheckingPolicy>;
332  PersistentRegionLock guard;
333  return OtherBasicCrossThreadPersistent(
334  typename OtherBasicCrossThreadPersistent::UnsafeCtorTag(),
335  static_cast<U*>(Get()));
336  }
337 
338  template <typename U = T,
339  typename = typename std::enable_if<!BasicCrossThreadPersistent<
340  U, WeaknessPolicy>::IsStrongPersistent::value>::type>
342  Lock() const {
345  }
346 
347  private:
348  static bool IsValid(const void* ptr) {
349  return ptr && ptr != kSentinelPointer;
350  }
351 
352  static void Trace(Visitor* v, const void* ptr) {
353  const auto* handle = static_cast<const BasicCrossThreadPersistent*>(ptr);
354  v->TraceRoot(*handle, handle->Location());
355  }
356 
357  void AssignUnsafe(T* ptr) {
358  const void* old_value = GetValue();
359  if (IsValid(old_value)) {
360  PersistentRegionLock guard;
361  old_value = GetValue();
362  // The fast path check (IsValid()) does not acquire the lock. Reload
363  // the value to ensure the reference has not been cleared.
364  if (IsValid(old_value)) {
365  CrossThreadPersistentRegion& region =
366  this->GetPersistentRegion(old_value);
367  if (IsValid(ptr) && (&region == &this->GetPersistentRegion(ptr))) {
368  SetValue(ptr);
369  this->CheckPointer(ptr);
370  return;
371  }
372  region.FreeNode(GetNode());
373  SetNode(nullptr);
374  } else {
376  }
377  }
378  SetValue(ptr);
379  if (!IsValid(ptr)) return;
380  PersistentRegionLock guard;
381  SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace));
382  this->CheckPointer(ptr);
383  }
384 
385  void AssignSafe(PersistentRegionLock&, T* ptr) {
386  PersistentRegionLock::AssertLocked();
387  const void* old_value = GetValue();
388  if (IsValid(old_value)) {
389  CrossThreadPersistentRegion& region =
390  this->GetPersistentRegion(old_value);
391  if (IsValid(ptr) && (&region == &this->GetPersistentRegion(ptr))) {
392  SetValue(ptr);
393  this->CheckPointer(ptr);
394  return;
395  }
396  region.FreeNode(GetNode());
397  SetNode(nullptr);
398  }
399  SetValue(ptr);
400  if (!IsValid(ptr)) return;
401  SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace));
402  this->CheckPointer(ptr);
403  }
404 
405  void ClearFromGC() const {
406  if (IsValid(GetValueFromGC())) {
407  WeaknessPolicy::GetPersistentRegion(GetValueFromGC())
408  .FreeNode(GetNodeFromGC());
410  }
411  }
412 
413  // See Get() for details.
414  V8_CLANG_NO_SANITIZE("cfi-unrelated-cast")
415  T* GetFromGC() const {
416  return static_cast<T*>(const_cast<void*>(GetValueFromGC()));
417  }
418 
419  friend class cppgc::Visitor;
420 };
421 
422 template <typename T, typename LocationPolicy, typename CheckingPolicy>
423 struct IsWeak<
425  LocationPolicy, CheckingPolicy>>
426  : std::true_type {};
427 
428 } // namespace internal
429 
430 namespace subtle {
431 
432 /**
433  * **DO NOT USE: Has known caveats, see below.**
434  *
435  * CrossThreadPersistent allows retaining objects from threads other than the
436  * thread the owning heap is operating on.
437  *
438  * Known caveats:
439  * - Does not protect the heap owning an object from terminating.
440  * - Reaching transitively through the graph is unsupported as objects may be
441  * moved concurrently on the thread owning the object.
442  */
443 template <typename T>
444 using CrossThreadPersistent = internal::BasicCrossThreadPersistent<
446 
447 /**
448  * **DO NOT USE: Has known caveats, see below.**
449  *
450  * CrossThreadPersistent allows weakly retaining objects from threads other than
451  * the thread the owning heap is operating on.
452  *
453  * Known caveats:
454  * - Does not protect the heap owning an object from terminating.
455  * - Reaching transitively through the graph is unsupported as objects may be
456  * moved concurrently on the thread owning the object.
457  */
458 template <typename T>
459 using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent<
461 
462 } // namespace subtle
463 } // namespace cppgc
464 
465 #endif // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_