Coverage Report

Created: 2025-10-31 09:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/node/src/cppgc_helpers.h
Line
Count
Source
1
#ifndef SRC_CPPGC_HELPERS_H_
2
#define SRC_CPPGC_HELPERS_H_
3
4
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5
6
#include <type_traits>  // std::remove_reference
7
#include "cppgc/garbage-collected.h"
8
#include "cppgc/name-provider.h"
9
#include "cppgc/persistent.h"
10
#include "memory_tracker.h"
11
#include "util.h"
12
#include "v8-cppgc.h"
13
#include "v8-sandbox.h"
14
#include "v8.h"
15
16
namespace node {
17
18
class Environment;
19
class Realm;
20
class CppgcWrapperListNode;
21
22
/**
23
 * This is a helper mixin with a BaseObject-like interface to help
24
 * implementing wrapper objects managed by V8's cppgc (Oilpan) library.
25
 * cppgc-manged objects in Node.js internals should extend this mixin,
26
 * while non-cppgc-managed objects typically extend BaseObject - the
27
 * latter are being migrated to be cppgc-managed wherever it's beneficial
28
 * and practical. Typically cppgc-managed objects are more efficient to
29
 * keep track of (which lowers initialization cost) and work better
30
 * with V8's GC scheduling.
31
 *
32
 * A cppgc-managed native wrapper should look something like this, note
33
 * that per cppgc rules, CPPGC_MIXIN(MyWrap) must be at the left-most
34
 * position in the hierarchy (which ensures cppgc::GarbageCollected
35
 * is at the left-most position).
36
 *
37
 * class MyWrap final : CPPGC_MIXIN(MyWrap) {
38
 *  public:
39
 *   SET_CPPGC_NAME(MyWrap)  // Sets the heap snapshot name to "Node / MyWrap"
40
 *   void Trace(cppgc::Visitor* visitor) const final {
41
 *     CppgcMixin::Trace(visitor);
42
 *     visitor->Trace(...);  // Trace any additional owned traceable data
43
 *   }
44
 * }
45
 *
46
 * If the wrapper needs to perform cleanups when it's destroyed and that
47
 * cleanup relies on a living Node.js `Realm`, it should implement a
48
 * pattern like this:
49
 *
50
 *   ~MyWrap() { this->Destroy(); }
51
 *   void Clean(Realm* env) override {
52
 *     // Do cleanup that relies on a living Environemnt.
53
 *   }
54
 */
55
class CppgcMixin : public cppgc::GarbageCollectedMixin, public MemoryRetainer {
56
 public:
57
  // To help various callbacks access wrapper objects with different memory
58
  // management, cppgc-managed objects share the same layout as BaseObjects.
59
  enum InternalFields { kEmbedderType = 0, kSlot, kInternalFieldCount };
60
61
  // The initialization cannot be done in the mixin constructor but has to be
62
  // invoked from the child class constructor, per cppgc::GarbageCollectedMixin
63
  // rules.
64
  template <typename T>
65
  static inline void Wrap(T* ptr, Realm* realm, v8::Local<v8::Object> obj);
66
  template <typename T>
67
  static inline void Wrap(T* ptr, Environment* env, v8::Local<v8::Object> obj);
68
69
  inline v8::Local<v8::Object> object() const;
70
  inline Environment* env() const;
71
0
  inline Realm* realm() const { return realm_; }
72
0
  inline v8::Local<v8::Object> object(v8::Isolate* isolate) const {
73
0
    return traced_reference_.Get(isolate);
74
0
  }
75
76
  template <typename T>
77
  static inline T* Unwrap(v8::Local<v8::Object> obj);
78
79
  // Subclasses are expected to invoke CppgcMixin::Trace() in their own Trace()
80
  // methods.
81
0
  void Trace(cppgc::Visitor* visitor) const override {
82
0
    visitor->Trace(traced_reference_);
83
0
  }
84
85
  // TODO(joyeecheung): use ObjectSizeTrait;
86
0
  inline size_t SelfSize() const override { return sizeof(*this); }
87
0
  inline bool IsCppgcWrapper() const override { return true; }
88
89
  // This is run for all the remaining Cppgc wrappers tracked in the Realm
90
  // during Realm shutdown. The destruction of the wrappers would happen later,
91
  // when the final garbage collection is triggered when CppHeap is torn down as
92
  // part of the Isolate teardown. If subclasses of CppgcMixin wish to perform
93
  // cleanups that depend on the Realm during destruction, they should implment
94
  // it in a Clean() override, and then call this->Finalize() from their
95
  // destructor. Outside of Finalize(), subclasses should avoid calling
96
  // into JavaScript or perform any operation that can trigger garbage
97
  // collection during the destruction.
98
0
  void Finalize() {
99
0
    if (realm_ == nullptr) return;
100
0
    this->Clean(realm_);
101
0
    realm_ = nullptr;
102
0
  }
103
104
  // The default implementation of Clean() is a no-op. If subclasses wish
105
  // to perform cleanup that require a living Realm, they should
106
  // should put the cleanups in a Clean() override, and call this->Finalize()
107
  // in the destructor, instead of doing those cleanups directly in the
108
  // destructor.
109
0
  virtual void Clean(Realm* realm) {}
110
111
  inline ~CppgcMixin();
112
113
  friend class CppgcWrapperListNode;
114
115
 private:
116
  Realm* realm_ = nullptr;
117
  v8::TracedReference<v8::Object> traced_reference_;
118
};
119
120
// If the class doesn't have additional owned traceable data, use this macro to
121
// save the implementation of a custom Trace() method.
122
#define DEFAULT_CPPGC_TRACE()                                                  \
123
  void Trace(cppgc::Visitor* visitor) const final {                            \
124
    CppgcMixin::Trace(visitor);                                                \
125
  }
126
127
// This macro sets the node name in the heap snapshot with a "Node /" prefix.
128
// Classes that use this macro must extend cppgc::NameProvider.
129
#define SET_CPPGC_NAME(Klass)                                                  \
130
0
  inline const char* GetHumanReadableName() const final {                      \
131
0
    return "Node / " #Klass;                                                   \
132
0
  }                                                                            \
Unexecuted instantiation: node::contextify::ContextifyContext::GetHumanReadableName() const
Unexecuted instantiation: node::contextify::ContextifyScript::GetHumanReadableName() const
133
0
  inline const char* MemoryInfoName() const override { return #Klass; }
Unexecuted instantiation: node::contextify::ContextifyContext::MemoryInfoName() const
Unexecuted instantiation: node::contextify::ContextifyScript::MemoryInfoName() const
134
135
/**
136
 * Similar to ASSIGN_OR_RETURN_UNWRAP() but works on cppgc-managed types
137
 * inheriting CppgcMixin.
138
 */
139
#define ASSIGN_OR_RETURN_UNWRAP_CPPGC(ptr, obj, ...)                           \
140
0
  do {                                                                         \
141
0
    *ptr = CppgcMixin::Unwrap<                                                 \
142
0
        typename std::remove_reference<decltype(**ptr)>::type>(obj);           \
143
0
    if (*ptr == nullptr) return __VA_ARGS__;                                   \
144
0
  } while (0)
145
}  // namespace node
146
147
/**
148
 * Helper macro the manage the cppgc-based wrapper hierarchy. This must
149
 * be used at the left-most position - right after `:` in the class inheritance,
150
 * like this:
151
 * class Klass : CPPGC_MIXIN(Klass) ... {}
152
 *
153
 * This needs to disable linters because it will be at odds with
154
 * clang-format.
155
 */
156
#define CPPGC_MIXIN(Klass)                                                     \
157
  public /* NOLINT(whitespace/indent) */                                       \
158
  v8::Object::Wrappable, public CppgcMixin
159
160
#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
161
162
#endif  // SRC_CPPGC_HELPERS_H_