1 // Copyright 2021 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_V8_TRACED_HANDLE_H_
6 #define INCLUDE_V8_TRACED_HANDLE_H_
7
8 #include <stddef.h>
9 #include <stdint.h>
10 #include <stdio.h>
11
12 #include <atomic>
13 #include <memory>
14 #include <type_traits>
15 #include <utility>
16
17 #include "v8-internal.h" // NOLINT(build/include_directory)
18 #include "v8-local-handle.h" // NOLINT(build/include_directory)
19 #include "v8-weak-callback-info.h" // NOLINT(build/include_directory)
20 #include "v8config.h" // NOLINT(build/include_directory)
21
22 namespace v8 {
23
24 class Value;
25
26 namespace internal {
27
28 class BasicTracedReferenceExtractor;
29
30 enum class GlobalHandleStoreMode {
31 kInitializingStore,
32 kAssigningStore,
33 };
34
35 V8_EXPORT internal::Address* GlobalizeTracedReference(
36 internal::Isolate* isolate, internal::Address* handle,
37 internal::Address* slot, GlobalHandleStoreMode store_mode);
38 V8_EXPORT void MoveTracedReference(internal::Address** from,
39 internal::Address** to);
40 V8_EXPORT void CopyTracedReference(const internal::Address* const* from,
41 internal::Address** to);
42 V8_EXPORT void DisposeTracedReference(internal::Address* global_handle);
43
44 } // namespace internal
45
46 class TracedReferenceBase {
47 public:
48 /**
49 * Returns true if the reference is empty, i.e., has not been assigned
50 * object.
51 */
IsEmpty()52 bool IsEmpty() const { return val_ == nullptr; }
53
54 /**
55 * If non-empty, destroy the underlying storage cell. |IsEmpty| will return
56 * true after this call.
57 */
58 V8_INLINE void Reset();
59
60 /**
61 * Construct a Local<Value> from this handle.
62 */
Get(v8::Isolate * isolate)63 V8_INLINE v8::Local<v8::Value> Get(v8::Isolate* isolate) const {
64 if (IsEmpty()) return Local<Value>();
65 return Local<Value>::New(isolate, reinterpret_cast<Value*>(val_));
66 }
67
68 /**
69 * Returns true if this TracedReference is empty, i.e., has not been
70 * assigned an object. This version of IsEmpty is thread-safe.
71 */
IsEmptyThreadSafe()72 bool IsEmptyThreadSafe() const {
73 return this->GetSlotThreadSafe() == nullptr;
74 }
75
76 /**
77 * Assigns a wrapper class ID to the handle.
78 */
79 V8_INLINE void SetWrapperClassId(uint16_t class_id);
80
81 /**
82 * Returns the class ID previously assigned to this handle or 0 if no class ID
83 * was previously assigned.
84 */
85 V8_INLINE uint16_t WrapperClassId() const;
86
87 protected:
88 /**
89 * Update this reference in a thread-safe way.
90 */
SetSlotThreadSafe(void * new_val)91 void SetSlotThreadSafe(void* new_val) {
92 reinterpret_cast<std::atomic<void*>*>(&val_)->store(
93 new_val, std::memory_order_relaxed);
94 }
95
96 /**
97 * Get this reference in a thread-safe way
98 */
GetSlotThreadSafe()99 const void* GetSlotThreadSafe() const {
100 return reinterpret_cast<std::atomic<const void*> const*>(&val_)->load(
101 std::memory_order_relaxed);
102 }
103
104 V8_EXPORT void CheckValue() const;
105
106 // val_ points to a GlobalHandles node.
107 internal::Address* val_ = nullptr;
108
109 friend class internal::BasicTracedReferenceExtractor;
110 template <typename F>
111 friend class Local;
112 template <typename U>
113 friend bool operator==(const TracedReferenceBase&, const Local<U>&);
114 friend bool operator==(const TracedReferenceBase&,
115 const TracedReferenceBase&);
116 };
117
118 /**
119 * A traced handle with copy and move semantics. The handle is to be used
120 * together with |v8::EmbedderHeapTracer| or as part of GarbageCollected objects
121 * (see v8-cppgc.h) and specifies edges from C++ objects to JavaScript.
122 *
123 * The exact semantics are:
124 * - Tracing garbage collections use |v8::EmbedderHeapTracer| or cppgc.
125 * - Non-tracing garbage collections refer to
126 * |v8::EmbedderRootsHandler::IsRoot()| whether the handle should
127 * be treated as root or not.
128 *
129 * Note that the base class cannot be instantiated itself, use |TracedReference|
130 * instead.
131 */
132 template <typename T>
133 class BasicTracedReference : public TracedReferenceBase {
134 public:
135 /**
136 * Construct a Local<T> from this handle.
137 */
Get(Isolate * isolate)138 Local<T> Get(Isolate* isolate) const { return Local<T>::New(isolate, *this); }
139
140 template <class S>
As()141 V8_INLINE BasicTracedReference<S>& As() const {
142 return reinterpret_cast<BasicTracedReference<S>&>(
143 const_cast<BasicTracedReference<T>&>(*this));
144 }
145
146 T* operator->() const {
147 #ifdef V8_ENABLE_CHECKS
148 CheckValue();
149 #endif // V8_ENABLE_CHECKS
150 return reinterpret_cast<T*>(val_);
151 }
152 T* operator*() const {
153 #ifdef V8_ENABLE_CHECKS
154 CheckValue();
155 #endif // V8_ENABLE_CHECKS
156 return reinterpret_cast<T*>(val_);
157 }
158
159 private:
160 /**
161 * An empty BasicTracedReference without storage cell.
162 */
163 BasicTracedReference() = default;
164
165 V8_INLINE static internal::Address* New(
166 Isolate* isolate, T* that, void* slot,
167 internal::GlobalHandleStoreMode store_mode);
168
169 friend class EmbedderHeapTracer;
170 template <typename F>
171 friend class Local;
172 friend class Object;
173 template <typename F>
174 friend class TracedReference;
175 template <typename F>
176 friend class BasicTracedReference;
177 template <typename F>
178 friend class ReturnValue;
179 };
180
181 /**
182 * A traced handle without destructor that clears the handle. The embedder needs
183 * to ensure that the handle is not accessed once the V8 object has been
184 * reclaimed. This can happen when the handle is not passed through the
185 * EmbedderHeapTracer. For more details see BasicTracedReference.
186 *
187 * The reference assumes the embedder has precise knowledge about references at
188 * all times. In case V8 needs to separately handle on-stack references, the
189 * embedder is required to set the stack start through
190 * |EmbedderHeapTracer::SetStackStart|.
191 */
192 template <typename T>
193 class TracedReference : public BasicTracedReference<T> {
194 public:
195 using BasicTracedReference<T>::Reset;
196
197 /**
198 * An empty TracedReference without storage cell.
199 */
TracedReference()200 TracedReference() : BasicTracedReference<T>() {}
201
202 /**
203 * Construct a TracedReference from a Local.
204 *
205 * When the Local is non-empty, a new storage cell is created
206 * pointing to the same object.
207 */
208 template <class S>
TracedReference(Isolate * isolate,Local<S> that)209 TracedReference(Isolate* isolate, Local<S> that) : BasicTracedReference<T>() {
210 this->val_ = this->New(isolate, that.val_, &this->val_,
211 internal::GlobalHandleStoreMode::kInitializingStore);
212 static_assert(std::is_base_of<T, S>::value, "type check");
213 }
214
215 /**
216 * Move constructor initializing TracedReference from an
217 * existing one.
218 */
TracedReference(TracedReference && other)219 V8_INLINE TracedReference(TracedReference&& other) noexcept {
220 // Forward to operator=.
221 *this = std::move(other);
222 }
223
224 /**
225 * Move constructor initializing TracedReference from an
226 * existing one.
227 */
228 template <typename S>
TracedReference(TracedReference<S> && other)229 V8_INLINE TracedReference(TracedReference<S>&& other) noexcept {
230 // Forward to operator=.
231 *this = std::move(other);
232 }
233
234 /**
235 * Copy constructor initializing TracedReference from an
236 * existing one.
237 */
TracedReference(const TracedReference & other)238 V8_INLINE TracedReference(const TracedReference& other) {
239 // Forward to operator=;
240 *this = other;
241 }
242
243 /**
244 * Copy constructor initializing TracedReference from an
245 * existing one.
246 */
247 template <typename S>
TracedReference(const TracedReference<S> & other)248 V8_INLINE TracedReference(const TracedReference<S>& other) {
249 // Forward to operator=;
250 *this = other;
251 }
252
253 /**
254 * Move assignment operator initializing TracedReference from an existing one.
255 */
256 V8_INLINE TracedReference& operator=(TracedReference&& rhs) noexcept;
257
258 /**
259 * Move assignment operator initializing TracedReference from an existing one.
260 */
261 template <class S>
262 V8_INLINE TracedReference& operator=(TracedReference<S>&& rhs) noexcept;
263
264 /**
265 * Copy assignment operator initializing TracedReference from an existing one.
266 */
267 V8_INLINE TracedReference& operator=(const TracedReference& rhs);
268
269 /**
270 * Copy assignment operator initializing TracedReference from an existing one.
271 */
272 template <class S>
273 V8_INLINE TracedReference& operator=(const TracedReference<S>& rhs);
274
275 /**
276 * If non-empty, destroy the underlying storage cell and create a new one with
277 * the contents of other if other is non empty
278 */
279 template <class S>
280 V8_INLINE void Reset(Isolate* isolate, const Local<S>& other);
281
282 template <class S>
As()283 V8_INLINE TracedReference<S>& As() const {
284 return reinterpret_cast<TracedReference<S>&>(
285 const_cast<TracedReference<T>&>(*this));
286 }
287 };
288
289 // --- Implementation ---
290 template <class T>
New(Isolate * isolate,T * that,void * slot,internal::GlobalHandleStoreMode store_mode)291 internal::Address* BasicTracedReference<T>::New(
292 Isolate* isolate, T* that, void* slot,
293 internal::GlobalHandleStoreMode store_mode) {
294 if (that == nullptr) return nullptr;
295 internal::Address* p = reinterpret_cast<internal::Address*>(that);
296 return internal::GlobalizeTracedReference(
297 reinterpret_cast<internal::Isolate*>(isolate), p,
298 reinterpret_cast<internal::Address*>(slot), store_mode);
299 }
300
Reset()301 void TracedReferenceBase::Reset() {
302 if (IsEmpty()) return;
303 internal::DisposeTracedReference(reinterpret_cast<internal::Address*>(val_));
304 SetSlotThreadSafe(nullptr);
305 }
306
307 V8_INLINE bool operator==(const TracedReferenceBase& lhs,
308 const TracedReferenceBase& rhs) {
309 v8::internal::Address* a = reinterpret_cast<v8::internal::Address*>(lhs.val_);
310 v8::internal::Address* b = reinterpret_cast<v8::internal::Address*>(rhs.val_);
311 if (a == nullptr) return b == nullptr;
312 if (b == nullptr) return false;
313 return *a == *b;
314 }
315
316 template <typename U>
317 V8_INLINE bool operator==(const TracedReferenceBase& lhs,
318 const v8::Local<U>& rhs) {
319 v8::internal::Address* a = reinterpret_cast<v8::internal::Address*>(lhs.val_);
320 v8::internal::Address* b = reinterpret_cast<v8::internal::Address*>(*rhs);
321 if (a == nullptr) return b == nullptr;
322 if (b == nullptr) return false;
323 return *a == *b;
324 }
325
326 template <typename U>
327 V8_INLINE bool operator==(const v8::Local<U>& lhs,
328 const TracedReferenceBase& rhs) {
329 return rhs == lhs;
330 }
331
332 V8_INLINE bool operator!=(const TracedReferenceBase& lhs,
333 const TracedReferenceBase& rhs) {
334 return !(lhs == rhs);
335 }
336
337 template <typename U>
338 V8_INLINE bool operator!=(const TracedReferenceBase& lhs,
339 const v8::Local<U>& rhs) {
340 return !(lhs == rhs);
341 }
342
343 template <typename U>
344 V8_INLINE bool operator!=(const v8::Local<U>& lhs,
345 const TracedReferenceBase& rhs) {
346 return !(rhs == lhs);
347 }
348
349 template <class T>
350 template <class S>
Reset(Isolate * isolate,const Local<S> & other)351 void TracedReference<T>::Reset(Isolate* isolate, const Local<S>& other) {
352 static_assert(std::is_base_of<T, S>::value, "type check");
353 this->Reset();
354 if (other.IsEmpty()) return;
355 this->SetSlotThreadSafe(
356 this->New(isolate, other.val_, &this->val_,
357 internal::GlobalHandleStoreMode::kAssigningStore));
358 }
359
360 template <class T>
361 template <class S>
362 TracedReference<T>& TracedReference<T>::operator=(
363 TracedReference<S>&& rhs) noexcept {
364 static_assert(std::is_base_of<T, S>::value, "type check");
365 *this = std::move(rhs.template As<T>());
366 return *this;
367 }
368
369 template <class T>
370 template <class S>
371 TracedReference<T>& TracedReference<T>::operator=(
372 const TracedReference<S>& rhs) {
373 static_assert(std::is_base_of<T, S>::value, "type check");
374 *this = rhs.template As<T>();
375 return *this;
376 }
377
378 template <class T>
379 TracedReference<T>& TracedReference<T>::operator=(
380 TracedReference&& rhs) noexcept {
381 if (this != &rhs) {
382 internal::MoveTracedReference(
383 reinterpret_cast<internal::Address**>(&rhs.val_),
384 reinterpret_cast<internal::Address**>(&this->val_));
385 }
386 return *this;
387 }
388
389 template <class T>
390 TracedReference<T>& TracedReference<T>::operator=(const TracedReference& rhs) {
391 if (this != &rhs) {
392 this->Reset();
393 if (rhs.val_ != nullptr) {
394 internal::CopyTracedReference(
395 reinterpret_cast<const internal::Address* const*>(&rhs.val_),
396 reinterpret_cast<internal::Address**>(&this->val_));
397 }
398 }
399 return *this;
400 }
401
SetWrapperClassId(uint16_t class_id)402 void TracedReferenceBase::SetWrapperClassId(uint16_t class_id) {
403 using I = internal::Internals;
404 if (IsEmpty()) return;
405 internal::Address* obj = reinterpret_cast<internal::Address*>(val_);
406 uint8_t* addr = reinterpret_cast<uint8_t*>(obj) + I::kNodeClassIdOffset;
407 *reinterpret_cast<uint16_t*>(addr) = class_id;
408 }
409
WrapperClassId()410 uint16_t TracedReferenceBase::WrapperClassId() const {
411 using I = internal::Internals;
412 if (IsEmpty()) return 0;
413 internal::Address* obj = reinterpret_cast<internal::Address*>(val_);
414 uint8_t* addr = reinterpret_cast<uint8_t*>(obj) + I::kNodeClassIdOffset;
415 return *reinterpret_cast<uint16_t*>(addr);
416 }
417
418 } // namespace v8
419
420 #endif // INCLUDE_V8_TRACED_HANDLE_H_
421