• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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()27   V8_CLANG_NO_SANITIZE("address") const void* GetValueFromGC() const {
28     return raw_;
29   }
30 
31   V8_CLANG_NO_SANITIZE("address")
GetNodeFromGC()32   PersistentNode* GetNodeFromGC() const { return node_; }
33 
34   V8_CLANG_NO_SANITIZE("address")
ClearFromGC()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.
GetNodeSafe()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")
SetNodeSafe(PersistentNode * value)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>
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 
105   BasicCrossThreadPersistent(
106       const SourceLocation& loc = SourceLocation::Current())
LocationPolicy(loc)107       : LocationPolicy(loc) {}
108 
109   BasicCrossThreadPersistent(
110       std::nullptr_t, const SourceLocation& loc = SourceLocation::Current())
LocationPolicy(loc)111       : LocationPolicy(loc) {}
112 
113   BasicCrossThreadPersistent(
114       SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
CrossThreadPersistentBase(s)115       : CrossThreadPersistentBase(s), LocationPolicy(loc) {}
116 
117   BasicCrossThreadPersistent(
118       T* raw, const SourceLocation& loc = SourceLocation::Current())
CrossThreadPersistentBase(raw)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 
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())
CrossThreadPersistentBase(raw)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 
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 = std::enable_if_t<std::is_base_of<T, U>::value>>
152   BasicCrossThreadPersistent(
153       internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
154                             MemberCheckingPolicy>
155           member,
156       const SourceLocation& loc = SourceLocation::Current())
157       : BasicCrossThreadPersistent(member.Get(), loc) {}
158 
159   BasicCrossThreadPersistent(
160       const BasicCrossThreadPersistent& other,
161       const SourceLocation& loc = SourceLocation::Current())
BasicCrossThreadPersistent(loc)162       : BasicCrossThreadPersistent(loc) {
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>>
171   BasicCrossThreadPersistent(
172       const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
173                                        OtherLocationPolicy,
174                                        OtherCheckingPolicy>& other,
175       const SourceLocation& loc = SourceLocation::Current())
BasicCrossThreadPersistent(loc)176       : BasicCrossThreadPersistent(loc) {
177     *this = other;
178   }
179 
180   BasicCrossThreadPersistent(
181       BasicCrossThreadPersistent&& other,
182       const SourceLocation& loc = SourceLocation::Current()) noexcept {
183     // Invoke operator=.
184     *this = std::move(other);
185   }
186 
187   BasicCrossThreadPersistent& operator=(
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>>
197   BasicCrossThreadPersistent& operator=(
198       const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
199                                        OtherLocationPolicy,
200                                        OtherCheckingPolicy>& other) {
201     PersistentRegionLock guard;
202     AssignSafe(guard, other.Get());
203     return *this;
204   }
205 
206   BasicCrossThreadPersistent& operator=(BasicCrossThreadPersistent&& other) {
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    */
225   BasicCrossThreadPersistent& operator=(T* other) {
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>>
234   BasicCrossThreadPersistent& operator=(
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    */
246   BasicCrossThreadPersistent& operator=(std::nullptr_t) {
247     Clear();
248     return *this;
249   }
250 
251   /**
252    * Assigns the sentinel pointer.
253    *
254    * \returns the handle.
255    */
256   BasicCrossThreadPersistent& operator=(SentinelPointer s) {
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.
Get()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    */
Clear()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    */
Release()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>
To()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>
341   BasicCrossThreadPersistent<U, internal::StrongCrossThreadPersistentPolicy>
Lock()342   Lock() const {
343     return BasicCrossThreadPersistent<
344         U, internal::StrongCrossThreadPersistentPolicy>(*this);
345   }
346 
347  private:
IsValid(const void * ptr)348   static bool IsValid(const void* ptr) {
349     return ptr && ptr != kSentinelPointer;
350   }
351 
Trace(Visitor * v,const void * ptr)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 
AssignUnsafe(T * ptr)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 {
375         CPPGC_DCHECK(!GetNode());
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 
AssignSafe(PersistentRegionLock &,T * ptr)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 
ClearFromGC()405   void ClearFromGC() const {
406     if (IsValid(GetValueFromGC())) {
407       WeaknessPolicy::GetPersistentRegion(GetValueFromGC())
408           .FreeNode(GetNodeFromGC());
409       CrossThreadPersistentBase::ClearFromGC();
410     }
411   }
412 
413   // See Get() for details.
414   V8_CLANG_NO_SANITIZE("cfi-unrelated-cast")
GetFromGC()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<
424     BasicCrossThreadPersistent<T, internal::WeakCrossThreadPersistentPolicy,
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<
445     T, internal::StrongCrossThreadPersistentPolicy>;
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<
460     T, internal::WeakCrossThreadPersistentPolicy>;
461 
462 }  // namespace subtle
463 }  // namespace cppgc
464 
465 #endif  // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
466