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) && (®ion == &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) && (®ion == &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