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 : : #include <type_traits> // for enable_if_t, is_pointer
15 : :
16 : : #include <glib.h>
17 : :
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 : : /* jsapi-util-root.h - Utilities for dealing with the lifetime and ownership of
27 : : * JS Objects and other things that can be collected by the garbage collector
28 : : * (collectively called "GC things.")
29 : : *
30 : : * GjsMaybeOwned<T> is a multi-purpose wrapper for a GC thing of type T. You can
31 : : * wrap a thing in one of three ways:
32 : : *
33 : : * - trace the thing (tie it to the lifetime of another GC thing),
34 : : * - root the thing (keep it alive as long as the wrapper is in existence),
35 : : * - maintain a weak pointer to the thing (not keep it alive at all and have it
36 : : * possibly be finalized out from under you).
37 : : *
38 : : * To trace or maintain a weak pointer, simply assign a thing of type T to the
39 : : * GjsMaybeOwned wrapper. For tracing, you must call the trace() method when
40 : : * your other GC thing is traced.
41 : : *
42 : : * Rooting requires a JSContext so can't just assign a thing of type T. Instead
43 : : * you need to call the root() method to set up rooting.
44 : : *
45 : : * If the thing is rooted, it will be unrooted either when the GjsMaybeOwned is
46 : : * destroyed, or when the JSContext is destroyed. In the latter case, you can
47 : : * get an optional notification by registering a callback in the PrivateContext.
48 : : *
49 : : * To switch between one of the three modes, you must first call reset(). This
50 : : * drops all references to any GC thing and leaves the GjsMaybeOwned in the
51 : : * same state as if it had just been constructed.
52 : : */
53 : :
54 : : /* This struct contains operations that must be implemented differently
55 : : * depending on the type of the GC thing. Add more types as necessary. If an
56 : : * implementation is never used, it's OK to leave it out. The compiler will
57 : : * complain if it's used somewhere but not instantiated here.
58 : : */
59 : : template<typename T>
60 : : struct GjsHeapOperation {
61 : : [[nodiscard]] static bool update_after_gc(JS::Heap<T>* location);
62 : : static void expose_to_js(JS::Heap<T>& thing);
63 : : };
64 : :
65 : : template<>
66 : : struct GjsHeapOperation<JSObject *> {
67 : 1540 : [[nodiscard]] static bool update_after_gc(JSTracer* trc,
68 : : JS::Heap<JSObject*>* location) {
69 : 1540 : JS_UpdateWeakPointerAfterGC(trc, location);
70 : 1540 : return (location->unbarrieredGet() == nullptr);
71 : : }
72 : :
73 : 416 : static void expose_to_js(JS::Heap<JSObject *>& thing) {
74 : 416 : JSObject *obj = thing.unbarrieredGet();
75 : : /* If the object has been swept already, then the zone is nullptr */
76 [ + - - + : 416 : if (!obj || !JS::GetGCThingZone(JS::GCCellPtr(obj)))
- + ]
77 : 0 : return;
78 [ + + ]: 416 : if (!JS::RuntimeHeapIsCollecting())
79 : 144 : JS::ExposeObjectToActiveJS(obj);
80 : : }
81 : : };
82 : :
83 : : /* GjsMaybeOwned is intended for use as a member of classes that are allocated
84 : : * on the heap. Do not allocate GjsMaybeOwned on the stack, and do not allocate
85 : : * any instances of classes that have it as a member on the stack either. */
86 : : template<typename T>
87 : : class GjsMaybeOwned {
88 : : private:
89 : : /* m_root value controls which of these members we can access. When switching
90 : : * from one to the other, be careful to call the constructor and destructor
91 : : * of JS::Heap, since they use post barriers. */
92 : : JS::Heap<T> m_heap;
93 : : std::unique_ptr<JS::PersistentRooted<T>> m_root;
94 : :
95 : : /* No-op unless GJS_VERBOSE_ENABLE_LIFECYCLE is defined to 1. */
96 : 76710 : inline void debug(const char* what GJS_USED_VERBOSE_LIFECYCLE) {
97 : : gjs_debug_lifecycle(GJS_DEBUG_KEEP_ALIVE, "GjsMaybeOwned %p %s", this,
98 : : what);
99 : 76710 : }
100 : :
101 : : void
102 : 13406 : teardown_rooting()
103 : : {
104 : 13406 : debug("teardown_rooting()");
105 : 13406 : g_assert(m_root);
106 : :
107 : 13406 : m_root.reset();
108 : :
109 : 13406 : new (&m_heap) JS::Heap<T>();
110 : 13406 : }
111 : :
112 : : public:
113 : 13789 : GjsMaybeOwned() {
114 : 13789 : debug("created");
115 : 13789 : }
116 : :
117 : 13762 : ~GjsMaybeOwned() {
118 : 13762 : debug("destroyed");
119 : 13762 : }
120 : :
121 : : /* To access the GC thing, call get(). In many cases you can just use the
122 : : * GjsMaybeOwned wrapper in place of the GC thing itself due to the implicit
123 : : * cast operator. But if you want to call methods on the GC thing, for
124 : : * example if it's a JS::Value, you have to use get(). */
125 : 29538 : [[nodiscard]] constexpr const T get() const {
126 [ + + ]: 29538 : return m_root ? m_root->get() : m_heap.get();
127 : : }
128 : 29538 : constexpr operator const T() const { return get(); }
129 : :
130 : : /* Use debug_addr() only for debug logging, because it is unbarriered. */
131 : : template <typename U = T>
132 : 6924 : [[nodiscard]] constexpr const void* debug_addr(
133 : : std::enable_if_t<std::is_pointer_v<U>>* = nullptr) const {
134 [ + + ]: 6924 : return m_root ? m_root->get() : m_heap.unbarrieredGet();
135 : : }
136 : :
137 : 485 : constexpr bool operator==(const T& other) const {
138 [ + + ]: 485 : if (m_root)
139 : 43 : return m_root->get() == other;
140 : 442 : return m_heap == other;
141 : : }
142 : 482 : constexpr bool operator!=(const T& other) const {
143 : 482 : return !(*this == other);
144 : : }
145 : :
146 : : /* We can access the pointer without a read barrier if the only thing we
147 : : * are doing with it is comparing it to nullptr. */
148 : 36268 : constexpr bool operator==(std::nullptr_t) const {
149 [ + + ]: 36268 : if (m_root)
150 : 31963 : return m_root->get() == nullptr;
151 : 4305 : return m_heap.unbarrieredGet() == nullptr;
152 : : }
153 : 36268 : constexpr bool operator!=(std::nullptr_t) const {
154 : 36268 : return !(*this == nullptr);
155 : : }
156 : :
157 : : /* Likewise the truth value does not require a read barrier */
158 : 36268 : constexpr explicit operator bool() const { return *this != nullptr; }
159 : :
160 : : /* You can get a Handle<T> if the thing is rooted, so that you can use this
161 : : * wrapper with stack rooting. However, you must not do this if the
162 : : * JSContext can be destroyed while the Handle is live. */
163 : : [[nodiscard]] constexpr JS::Handle<T> handle() {
164 : : g_assert(m_root);
165 : : return *m_root;
166 : : }
167 : :
168 : : /* Roots the GC thing. You must not use this if you're already using the
169 : : * wrapper to store a non-rooted GC thing. */
170 : 13414 : void root(JSContext* cx, const T& thing) {
171 : 13414 : debug("root()");
172 : 13414 : g_assert(!m_root);
173 : 13414 : g_assert(m_heap.get() == JS::SafelyInitialized<T>::create());
174 : 13414 : m_heap.~Heap();
175 : 13414 : m_root = std::make_unique<JS::PersistentRooted<T>>(cx, thing);
176 : 13414 : }
177 : :
178 : : /* You can only assign directly to the GjsMaybeOwned wrapper in the
179 : : * non-rooted case. */
180 : : void
181 : 1967 : operator=(const T& thing)
182 : : {
183 : 1967 : g_assert(!m_root);
184 : 1967 : m_heap = thing;
185 : 1967 : }
186 : :
187 : : /* Marks an object as reachable for one GC with ExposeObjectToActiveJS().
188 : : * Use to avoid stopping tracing an object during GC. This makes no sense
189 : : * in the rooted case. */
190 : 416 : void prevent_collection() {
191 : 416 : debug("prevent_collection()");
192 : 416 : g_assert(!m_root);
193 : 416 : GjsHeapOperation<T>::expose_to_js(m_heap);
194 : 416 : }
195 : :
196 : 17130 : void reset() {
197 : 17130 : debug("reset()");
198 [ + + ]: 17130 : if (!m_root) {
199 : 3724 : m_heap = JS::SafelyInitialized<T>::create();
200 : 3724 : return;
201 : : }
202 : :
203 : 13406 : teardown_rooting();
204 : : }
205 : :
206 : 1609 : void switch_to_rooted(JSContext* cx) {
207 : 1609 : debug("switch to rooted");
208 : 1609 : g_assert(!m_root);
209 : :
210 : : /* Prevent the thing from being garbage collected while it is in neither
211 : : * m_heap nor m_root */
212 : 1609 : JS::Rooted<T> thing(cx, m_heap);
213 : :
214 : 1609 : reset();
215 : 1609 : root(cx, thing);
216 : 1609 : g_assert(m_root);
217 : 1609 : }
218 : :
219 : 1468 : void switch_to_unrooted(JSContext* cx) {
220 : 1468 : debug("switch to unrooted");
221 : 1468 : g_assert(m_root);
222 : :
223 : : /* Prevent the thing from being garbage collected while it is in neither
224 : : * m_heap nor m_root */
225 : 1468 : JS::Rooted<T> thing(cx, *m_root);
226 : :
227 : 1468 : reset();
228 : 1468 : m_heap = thing;
229 : 1468 : g_assert(!m_root);
230 : 1468 : }
231 : :
232 : : /* Tracing makes no sense in the rooted case, because JS::PersistentRooted
233 : : * already takes care of that. */
234 : : void
235 : 176 : trace(JSTracer *tracer,
236 : : const char *name)
237 : : {
238 : 176 : debug("trace()");
239 : 176 : g_assert(!m_root);
240 : 176 : JS::TraceEdge<T>(tracer, &m_heap, name);
241 : 176 : }
242 : :
243 : : /* If not tracing, then you must call this method during GC in order to
244 : : * update the object's location if it was moved, or null it out if it was
245 : : * finalized. If the object was finalized, returns true. */
246 : 1540 : bool update_after_gc(JSTracer* trc) {
247 : 1540 : debug("update_after_gc()");
248 : 1540 : g_assert(!m_root);
249 : 1540 : return GjsHeapOperation<T>::update_after_gc(trc, &m_heap);
250 : : }
251 : :
252 : 21115 : [[nodiscard]] constexpr bool rooted() const { return m_root != nullptr; }
253 : : };
254 : :
255 : : #endif // GJS_JSAPI_UTIL_ROOT_H_
|