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.
22 class CrossThreadPersistentBase : public PersistentBase {
23  public:
24   CrossThreadPersistentBase() = default;
CrossThreadPersistentBase(const void* raw)25   explicit CrossThreadPersistentBase(const void* raw) : PersistentBase(raw) {}
26 
GetValueFromGC() const27   V8_CLANG_NO_SANITIZE("address") const void* GetValueFromGC() const {
28     return raw_;
29   }
30 
31   V8_CLANG_NO_SANITIZE("address")
GetNodeFromGC() const32   PersistentNode* GetNodeFromGC() const { return node_; }
33 
34   V8_CLANG_NO_SANITIZE("address")
ClearFromGC() const35   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.
GetNodeSafe() const42   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")
SetNodeSafe(PersistentNode* value) const49   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>
71 class 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 
~BasicCrossThreadPersistent()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 
BasicCrossThreadPersistent( const SourceLocation& loc = SourceLocation::Current())105   BasicCrossThreadPersistent(
106       const SourceLocation& loc = SourceLocation::Current())
107       : LocationPolicy(loc) {}
108 
BasicCrossThreadPersistent( std::nullptr_t, const SourceLocation& loc = SourceLocation::Current())109   BasicCrossThreadPersistent(
110       std::nullptr_t, const SourceLocation& loc = SourceLocation::Current())
111       : LocationPolicy(loc) {}
112 
BasicCrossThreadPersistent( SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())113   BasicCrossThreadPersistent(
114       SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
115       : CrossThreadPersistentBase(s), LocationPolicy(loc) {}
116 
BasicCrossThreadPersistent( T* raw, const SourceLocation& loc = SourceLocation::Current())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 
BasicCrossThreadPersistent( UnsafeCtorTag, T* raw, const SourceLocation& loc = SourceLocation::Current())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 
BasicCrossThreadPersistent( T& raw, const SourceLocation& loc = SourceLocation::Current())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>>
BasicCrossThreadPersistent( internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag, MemberCheckingPolicy, MemberStorageType> member, const SourceLocation& loc = SourceLocation::Current())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 
BasicCrossThreadPersistent( const BasicCrossThreadPersistent& other, const SourceLocation& loc = SourceLocation::Current())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>>
BasicCrossThreadPersistent( const BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy, OtherCheckingPolicy>& other, const SourceLocation& loc = SourceLocation::Current())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 
operator =( const BasicCrossThreadPersistent& other)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>>
operator =( const BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy, OtherCheckingPolicy>& other)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 
operator =(BasicCrossThreadPersistent&& other)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    */
operator =(T* other)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>>
operator =( internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag, MemberCheckingPolicy, MemberStorageType> member)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    */
operator =(std::nullptr_t)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    */
operator =(SentinelPointer s)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.
Get() const274   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    */
Clear()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    */
Release()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    */
operator bool() const306   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    */
operator T*() const315   operator T*() const { return Get(); }
316 
317   /**
318    * Dereferences the stored object.
319    *
320    * Note: **Not thread-safe.**
321    */
operator ->() const322   T* operator->() const { return Get(); }
operator *() const323   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>
To() const330   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>
Lock() const344   Lock() const {
345     return BasicCrossThreadPersistent<
346         U, internal::StrongCrossThreadPersistentPolicy>(*this);
347   }
348 
349  private:
IsValid(const void* ptr)350   static bool IsValid(const void* ptr) {
351     return ptr && ptr != kSentinelPointer;
352   }
353 
TraceAsRoot(RootVisitor& root_visitor, const void* ptr)354   static void TraceAsRoot(RootVisitor& root_visitor, const void* ptr) {
355     root_visitor.Trace(*static_cast<const BasicCrossThreadPersistent*>(ptr));
356   }
357 
AssignUnsafe(T* ptr)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 
AssignSafe(PersistentRegionLock&, T* ptr)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 
ClearFromGC() const406   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")
GetFromGC() const416   T* GetFromGC() const {
417     return static_cast<T*>(const_cast<void*>(GetValueFromGC()));
418   }
419 
420   friend class internal::RootVisitor;
421 };
422 
423 template <typename T, typename LocationPolicy, typename CheckingPolicy>
424 struct IsWeak<
425     BasicCrossThreadPersistent<T, internal::WeakCrossThreadPersistentPolicy,
426                                LocationPolicy, CheckingPolicy>>
427     : std::true_type {};
428 
429 }  // namespace internal
430 
431 namespace 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  */
444 template <typename T>
445 using 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  */
459 template <typename T>
460 using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent<
461     T, internal::WeakCrossThreadPersistentPolicy>;
462 
463 }  // namespace subtle
464 }  // namespace cppgc
465 
466 #endif  // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
467