Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2017 Endless Mobile, Inc.
4 : : // SPDX-FileCopyrightText: 2019 Canonical, Ltd.
5 : :
6 : : #ifndef GJS_JSAPI_UTIL_ROOT_H_
7 : : #define GJS_JSAPI_UTIL_ROOT_H_
8 : :
9 : : #include <config.h>
10 : :
11 : : #include <cstddef> // for nullptr_t
12 : : #include <memory>
13 : : #include <new>
14 : :
15 : : #include <glib.h>
16 : :
17 : : #include <js/ComparisonOperators.h>
18 : : #include <js/GCAPI.h>
19 : : #include <js/HeapAPI.h> // for ExposeObjectToActiveJS, GetGCThingZone
20 : : #include <js/RootingAPI.h> // for SafelyInitialized
21 : : #include <js/TracingAPI.h>
22 : : #include <js/TypeDecls.h>
23 : :
24 : : #include "util/log.h"
25 : :
26 : : namespace JS { template <typename T> struct GCPolicy; }
27 : :
28 : : /* jsapi-util-root.h - Utilities for dealing with the lifetime and ownership of
29 : : * JS Objects and other things that can be collected by the garbage collector
30 : : * (collectively called "GC things.")
31 : : *
32 : : * GjsMaybeOwned is a multi-purpose wrapper for a JSObject. You can
33 : : * wrap a thing in one of three ways:
34 : : *
35 : : * - trace the object (tie it to the lifetime of another GC thing),
36 : : * - root the object (keep it alive as long as the wrapper is in existence),
37 : : * - maintain a weak pointer to the object (not keep it alive at all and have it
38 : : * possibly be finalized out from under you).
39 : : *
40 : : * To trace or maintain a weak pointer, simply assign an object to the
41 : : * GjsMaybeOwned wrapper. For tracing, you must call the trace() method when
42 : : * your other GC thing is traced.
43 : : *
44 : : * Rooting requires a JSContext so can't just assign a thing of type T. Instead
45 : : * you need to call the root() method to set up rooting.
46 : : *
47 : : * If the thing is rooted, it will be unrooted when the GjsMaybeOwned is
48 : : * destroyed.
49 : : *
50 : : * To switch between one of the three modes, you must first call reset(). This
51 : : * drops all references to any object and leaves the GjsMaybeOwned in the
52 : : * same state as if it had just been constructed.
53 : : */
54 : :
55 : : /* GjsMaybeOwned is intended for use as a member of classes that are allocated
56 : : * on the heap. Do not allocate GjsMaybeOwned on the stack, and do not allocate
57 : : * any instances of classes that have it as a member on the stack either. */
58 : : class GjsMaybeOwned {
59 : : private:
60 : : /* m_root value controls which of these members we can access. When switching
61 : : * from one to the other, be careful to call the constructor and destructor
62 : : * of JS::Heap, since they use post barriers. */
63 : : JS::Heap<JSObject*> m_heap;
64 : : std::unique_ptr<JS::PersistentRootedObject> m_root;
65 : :
66 : : /* No-op unless GJS_VERBOSE_ENABLE_LIFECYCLE is defined to 1. */
67 : 61510 : inline void debug(const char* what GJS_USED_VERBOSE_LIFECYCLE) {
68 : : gjs_debug_lifecycle(GJS_DEBUG_KEEP_ALIVE, "GjsMaybeOwned %p %s", this,
69 : : what);
70 : 61510 : }
71 : :
72 : : void
73 : 10500 : teardown_rooting()
74 : : {
75 : 10500 : debug("teardown_rooting()");
76 : 10500 : g_assert(m_root);
77 : :
78 : 10500 : m_root.reset();
79 : :
80 : 10500 : new (&m_heap) JS::Heap<JSObject*>();
81 : 10500 : }
82 : :
83 : : public:
84 : 10770 : GjsMaybeOwned() {
85 : 10770 : debug("created");
86 : 10770 : }
87 : :
88 : 10743 : ~GjsMaybeOwned() {
89 : 10743 : debug("destroyed");
90 : 10743 : }
91 : :
92 : : // COMPAT: constexpr in C++23
93 : 23770 : [[nodiscard]] JSObject* get() const {
94 [ + + ]: 23770 : return m_root ? m_root->get() : m_heap.get();
95 : : }
96 : :
97 : : // Use debug_addr() only for debug logging, because it is unbarriered.
98 : : // COMPAT: constexpr in C++23
99 : 6629 : [[nodiscard]] const void* debug_addr() const {
100 [ + + ]: 6629 : return m_root ? m_root->get() : m_heap.unbarrieredGet();
101 : : }
102 : :
103 : : // COMPAT: constexpr in C++23
104 : 477 : bool operator==(JSObject* other) const {
105 [ + + ]: 477 : if (m_root)
106 : 43 : return m_root->get() == other;
107 : 434 : return m_heap == other;
108 : : }
109 : 474 : bool operator!=(JSObject* other) const { return !(*this == other); }
110 : :
111 : : // We can access the pointer without a read barrier if the only thing we are
112 : : // are doing with it is comparing it to nullptr.
113 : : // COMPAT: constexpr in C++23
114 : 28462 : bool operator==(std::nullptr_t) const {
115 [ + + ]: 28462 : if (m_root)
116 : 24363 : return m_root->get() == nullptr;
117 : 4099 : return m_heap.unbarrieredGet() == nullptr;
118 : : }
119 : 28462 : bool operator!=(std::nullptr_t) const { return !(*this == nullptr); }
120 : :
121 : : // Likewise the truth value does not require a read barrier
122 : : // COMPAT: constexpr in C++23
123 : 28462 : explicit operator bool() const { return *this != nullptr; }
124 : :
125 : : // You can get a Handle<T> if the thing is rooted, so that you can use this
126 : : // wrapper with stack rooting. However, you must not do this if the
127 : : // JSContext can be destroyed while the Handle is live. */
128 : : // COMPAT: constexpr in C++23
129 : : [[nodiscard]] JS::HandleObject handle() {
130 : : g_assert(m_root);
131 : : return *m_root;
132 : : }
133 : :
134 : : /* Roots the GC thing. You must not use this if you're already using the
135 : : * wrapper to store a non-rooted GC thing. */
136 : 10508 : void root(JSContext* cx, JSObject* thing) {
137 : 10508 : debug("root()");
138 : 10508 : g_assert(!m_root);
139 : 10508 : g_assert(!m_heap);
140 : 10508 : m_heap.~Heap();
141 : 10508 : m_root = std::make_unique<JS::PersistentRootedObject>(cx, thing);
142 : 10508 : }
143 : :
144 : : /* You can only assign directly to the GjsMaybeOwned wrapper in the
145 : : * non-rooted case. */
146 : 1808 : void operator=(JSObject* thing) {
147 : 1808 : g_assert(!m_root);
148 : 1808 : m_heap = thing;
149 : 1808 : }
150 : :
151 : : /* Marks an object as reachable for one GC with ExposeObjectToActiveJS().
152 : : * Use to avoid stopping tracing an object during GC. This makes no sense
153 : : * in the rooted case. */
154 : 390 : void prevent_collection() {
155 : 390 : debug("prevent_collection()");
156 : 390 : g_assert(!m_root);
157 : 390 : JSObject* obj = m_heap.unbarrieredGet();
158 : : // If the object has been swept already, then the zone is nullptr
159 [ + - - + : 390 : if (!obj || !JS::GetGCThingZone(JS::GCCellPtr(obj)))
- + ]
160 : 0 : return;
161 [ + + ]: 390 : if (!JS::RuntimeHeapIsCollecting())
162 : 143 : JS::ExposeObjectToActiveJS(obj);
163 : : }
164 : :
165 : 14019 : void reset() {
166 : 14019 : debug("reset()");
167 [ + + ]: 14019 : if (!m_root) {
168 : 3519 : m_heap = nullptr;
169 : 3519 : return;
170 : : }
171 : :
172 : 10500 : teardown_rooting();
173 : : }
174 : :
175 : 1563 : void switch_to_rooted(JSContext* cx) {
176 : 1563 : debug("switch to rooted");
177 : 1563 : g_assert(!m_root);
178 : :
179 : : /* Prevent the thing from being garbage collected while it is in neither
180 : : * m_heap nor m_root */
181 : 1563 : JS::RootedObject thing{cx, m_heap};
182 : :
183 : 1563 : reset();
184 : 1563 : root(cx, thing);
185 : 1563 : g_assert(m_root);
186 : 1563 : }
187 : :
188 : 1422 : void switch_to_unrooted(JSContext* cx) {
189 : 1422 : debug("switch to unrooted");
190 : 1422 : g_assert(m_root);
191 : :
192 : : /* Prevent the thing from being garbage collected while it is in neither
193 : : * m_heap nor m_root */
194 : 1422 : JS::RootedObject thing{cx, *m_root};
195 : :
196 : 1422 : reset();
197 : 1422 : m_heap = thing;
198 : 1422 : g_assert(!m_root);
199 : 1422 : }
200 : :
201 : : /* Tracing makes no sense in the rooted case, because JS::PersistentRooted
202 : : * already takes care of that. */
203 : : void
204 : 178 : trace(JSTracer *tracer,
205 : : const char *name)
206 : : {
207 : 178 : debug("trace()");
208 : 178 : g_assert(!m_root);
209 : 178 : JS::TraceEdge(tracer, &m_heap, name);
210 : 178 : }
211 : :
212 : : /* If not tracing, then you must call this method during GC in order to
213 : : * update the object's location if it was moved, or null it out if it was
214 : : * finalized. If the object was finalized, returns true. */
215 : 1417 : bool update_after_gc(JSTracer* trc) {
216 : 1417 : debug("update_after_gc()");
217 : 1417 : g_assert(!m_root);
218 : 1417 : JS_UpdateWeakPointerAfterGC(trc, &m_heap);
219 : 1417 : return !m_heap;
220 : : }
221 : :
222 : : // COMPAT: constexpr in C++23
223 : 17617 : [[nodiscard]] bool rooted() const { return m_root != nullptr; }
224 : : };
225 : :
226 : : namespace Gjs {
227 : :
228 : : template <typename T>
229 : : class WeakPtr : public JS::Heap<T> {
230 : : public:
231 : : using JS::Heap<T>::Heap;
232 : : using JS::Heap<T>::operator=;
233 : : };
234 : :
235 : : } // namespace Gjs
236 : :
237 : : namespace JS {
238 : :
239 : : template <typename T>
240 : : struct GCPolicy<Gjs::WeakPtr<T>> {
241 : : static void trace(JSTracer* trc, Gjs::WeakPtr<T>* thingp,
242 : : const char* name) {
243 : : return JS::TraceEdge(trc, thingp, name);
244 : : }
245 : :
246 : 4702 : static bool traceWeak(JSTracer* trc, Gjs::WeakPtr<T>* thingp) {
247 : 4702 : return js::gc::TraceWeakEdge(trc, thingp);
248 : : }
249 : :
250 : 0 : static bool needsSweep(JSTracer* trc, const Gjs::WeakPtr<T>* thingp) {
251 : 0 : Gjs::WeakPtr<T> thing{*thingp};
252 : 0 : return !js::gc::TraceWeakEdge(trc, &thing);
253 : 0 : }
254 : : };
255 : :
256 : : } // namespace JS
257 : :
258 : : #endif // GJS_JSAPI_UTIL_ROOT_H_
|