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: 2008 litl, LLC
4 : : // SPDX-FileCopyrightText: 2018 Philip Chimento <philip.chimento@gmail.com>
5 : :
6 : : #pragma once
7 : :
8 : : #include <config.h>
9 : :
10 : : #include <stdint.h>
11 : :
12 : : #include <new> // for operator new
13 : : #include <string>
14 : : #include <type_traits>
15 : :
16 : : #include <girepository/girepository.h>
17 : : #include <glib-object.h>
18 : : #include <glib.h>
19 : :
20 : : #include <js/CallArgs.h>
21 : : #include <js/ComparisonOperators.h>
22 : : #include <js/ErrorReport.h> // for JSEXN_TYPEERR
23 : : #include <js/Id.h>
24 : : #include <js/MemoryFunctions.h>
25 : : #include <js/Object.h>
26 : : #include <js/PropertyAndElement.h> // for JS_DefineFunctionById
27 : : #include <js/RootingAPI.h>
28 : : #include <js/TypeDecls.h>
29 : : #include <js/Value.h>
30 : : #include <jsapi.h> // for JS_GetPrototype
31 : : #include <mozilla/Maybe.h>
32 : :
33 : : #include "gi/arg-inl.h"
34 : : #include "gi/cwrapper.h"
35 : : #include "gi/info.h"
36 : : #include "gjs/atoms.h"
37 : : #include "gjs/auto.h"
38 : : #include "gjs/context-private.h"
39 : : #include "gjs/jsapi-class.h"
40 : : #include "gjs/jsapi-util.h"
41 : : #include "gjs/macros.h"
42 : : #include "gjs/mem-private.h"
43 : : #include "gjs/profiler-private.h"
44 : : #include "util/log.h"
45 : :
46 : : struct JSFunctionSpec;
47 : : struct JSPropertySpec;
48 : : class JSTracer;
49 : :
50 : : GJS_JSAPI_RETURN_CONVENTION
51 : : bool gjs_wrapper_to_string_func(JSContext*, JSObject* this_obj,
52 : : const char* objtype,
53 : : const mozilla::Maybe<const GI::BaseInfo>&,
54 : : GType, const void* native_address,
55 : : JS::MutableHandleValue rval);
56 : :
57 : : // Needed because some of the templates don't have Maybe as their info() type
58 : : GJS_JSAPI_RETURN_CONVENTION
59 : 55 : static inline bool gjs_wrapper_to_string_func(JSContext* cx, JSObject* this_obj,
60 : : const char* objtype,
61 : : const GI::BaseInfo& info,
62 : : GType gtype,
63 : : const void* native_address,
64 : : JS::MutableHandleValue ret) {
65 : 55 : return gjs_wrapper_to_string_func(
66 : 110 : cx, this_obj, objtype, mozilla::Some(info), gtype, native_address, ret);
67 : : }
68 : :
69 : : bool gjs_wrapper_throw_nonexistent_field(JSContext*, GType,
70 : : const char* field_name);
71 : :
72 : : bool gjs_wrapper_throw_readonly_field(JSContext*, GType,
73 : : const char* field_name);
74 : :
75 : : struct GjsTypecheckNoThrow {};
76 : :
77 : : // Some types of introspected wrapper permit creating a new type from JS (e.g.,
78 : : // objects, interfaces.) These JS-created types do not have introspection info
79 : : // and so their GIWrapperPrototype::info() methods return Maybe<const FooInfo>.
80 : : // Others do not permit creating a new type from JS (e.g., enums, boxeds.) These
81 : : // have GIWrapperPrototype::info() methods that return const FooInfo directly.
82 : : // Sometimes we need to have different code for the two cases.
83 : : template <typename>
84 : : struct is_maybe : std::false_type {};
85 : : template <typename T>
86 : : struct is_maybe<mozilla::Maybe<T>> : std::true_type {};
87 : :
88 : : // Add associated memory for `prototype` according to the `class_size`
89 : : // associated with `gtype`.
90 : 2420 : static inline void add_prototype_associated_memory(GType gtype, JS::MutableHandleObject prototype) {
91 [ + + ]: 2420 : if (!G_TYPE_IS_CLASSED(gtype))
92 : 1435 : return;
93 : :
94 : : GTypeQuery query;
95 : 985 : g_type_query(gtype, &query);
96 : 985 : g_assert(query.type);
97 : :
98 : : // Associate the size of the class structure with the prototype
99 : 985 : JS::AddAssociatedMemory(prototype, query.class_size,
100 : : MemoryUse::GObjectClassStruct);
101 : : }
102 : :
103 : : // Remove associated memory for `prototype` according to the `class_size`
104 : : // associated with `gtype`.
105 : 2397 : static inline void remove_prototype_associated_memory(GType gtype, JSObject* obj) {
106 [ + + ]: 2397 : if (!G_TYPE_IS_CLASSED(gtype))
107 : 1415 : return;
108 : :
109 : : GTypeQuery query;
110 : 982 : g_type_query(gtype, &query);
111 : 982 : g_assert(query.type);
112 : :
113 : : // Remove the memory associated with the prototype
114 : 982 : JS::RemoveAssociatedMemory(obj, query.class_size,
115 : : MemoryUse::GObjectClassStruct);
116 : : }
117 : :
118 : : /**
119 : : * gjs_define_static_methods:
120 : : *
121 : : * Defines all static methods from @info on @constructor. Also includes class
122 : : * methods for GI::ObjectInfo, and interface methods for GI::InterfaceInfo.
123 : : */
124 : : template <GI::InfoTag TAG>
125 : : GJS_JSAPI_RETURN_CONVENTION
126 : : bool gjs_define_static_methods(JSContext*, JS::HandleObject constructor, GType,
127 : : const GI::UnownedInfo<TAG>&);
128 : :
129 : : template <GI::InfoTag TAG>
130 : : GJS_JSAPI_RETURN_CONVENTION
131 : 1225 : inline bool gjs_define_static_methods(JSContext* cx,
132 : : JS::HandleObject constructor, GType gtype,
133 : : const GI::OwnedInfo<TAG>& info) {
134 : 2450 : return gjs_define_static_methods(cx, constructor, gtype,
135 : 1225 : GI::UnownedInfo<TAG>{info});
136 : : }
137 : :
138 : : /**
139 : : * GIWrapperBase:
140 : : *
141 : : * In most different kinds of C pointer that we expose to JS through GObject
142 : : * Introspection (boxed, fundamental, gerror, interface, object, union), we want
143 : : * to have different private structures for the prototype JS object and the JS
144 : : * objects representing instances. Both should inherit from a base structure for
145 : : * their common functionality.
146 : : *
147 : : * This is mainly for memory reasons. We need to keep track of the GIBaseInfo*
148 : : * and GType for each dynamically created class, but we don't need to duplicate
149 : : * that information (16 bytes on x64 systems) for every instance. In some cases
150 : : * there can also be other information that's only used on the prototype.
151 : : *
152 : : * So, to conserve memory, we split the private structures in FooInstance and
153 : : * FooPrototype, which both inherit from FooBase. All the repeated code in these
154 : : * structures lives in GIWrapperBase, GIWrapperPrototype, and GIWrapperInstance.
155 : : *
156 : : * The m_proto member needs a bit of explanation, as this is used to implement
157 : : * an unusual form of polymorphism. Sadly, we cannot have virtual methods in
158 : : * FooBase, because SpiderMonkey can be compiled with or without RTTI, so we
159 : : * cannot count on being able to cast FooBase to FooInstance or FooPrototype
160 : : * with dynamic_cast<>, and the vtable would take up just as much space anyway.
161 : : * Instead, we use the CRTP technique, and distinguish between FooInstance and
162 : : * FooPrototype using the m_proto member, which will be null for FooPrototype.
163 : : * Instead of casting, we have the to_prototype() and to_instance() methods
164 : : * which will give you a pointer if the FooBase is of the correct type (and
165 : : * assert if not.)
166 : : *
167 : : * The CRTP requires inheriting classes to declare themselves friends of the
168 : : * parent class, so that the parent class can call their private methods.
169 : : *
170 : : * For more information about the CRTP, the Wikipedia article is informative:
171 : : * https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
172 : : */
173 : : template <class Base, class Prototype, class Instance>
174 : : class GIWrapperBase : public CWrapperPointerOps<Base> {
175 : : protected:
176 : : // nullptr if this Base is a Prototype; points to the corresponding
177 : : // Prototype if this Base is an Instance.
178 : : Prototype* m_proto;
179 : :
180 : 27267 : explicit GIWrapperBase(Prototype* proto = nullptr) : m_proto(proto) {}
181 : :
182 : : // These three can be overridden in subclasses. See define_jsclass().
183 : : static constexpr JSPropertySpec* proto_properties = nullptr;
184 : : static constexpr JSPropertySpec* static_properties = nullptr;
185 : : static constexpr JSFunctionSpec* proto_methods = nullptr;
186 : : static constexpr JSFunctionSpec* static_methods = nullptr;
187 : :
188 : : public:
189 : : // Methods implementing our CRTP polymorphism scheme follow below. We don't
190 : : // use standard C++ polymorphism because that would occupy another 8 bytes
191 : : // for a vtable.
192 : :
193 : : /**
194 : : * GIWrapperBase::is_prototype:
195 : : *
196 : : * Returns whether this Base is actually a Prototype (true) or an Instance
197 : : * (false).
198 : : */
199 : 785634 : [[nodiscard]] bool is_prototype() const { return !m_proto; }
200 : :
201 : : /**
202 : : * GIWrapperBase::to_prototype:
203 : : * GIWrapperBase::to_instance:
204 : : *
205 : : * These methods assert that this Base is of the correct subclass. If you
206 : : * don't want to assert, then either check beforehand with is_prototype(),
207 : : * or use get_prototype().
208 : : */
209 : : [[nodiscard]]
210 : 62598 : Prototype* to_prototype() {
211 : 62598 : g_assert(is_prototype());
212 : 62598 : return reinterpret_cast<Prototype*>(this);
213 : : }
214 : : [[nodiscard]]
215 : 13776 : const Prototype* to_prototype() const {
216 : 13776 : g_assert(is_prototype());
217 : 13776 : return reinterpret_cast<const Prototype*>(this);
218 : : }
219 : : [[nodiscard]]
220 : 158156 : Instance* to_instance() {
221 : 158156 : g_assert(!is_prototype());
222 : 158156 : return reinterpret_cast<Instance*>(this);
223 : : }
224 : : [[nodiscard]]
225 : 7894 : const Instance* to_instance() const {
226 : 7894 : g_assert(!is_prototype());
227 : 7894 : return reinterpret_cast<const Instance*>(this);
228 : : }
229 : :
230 : : /**
231 : : * GIWrapperBase::get_prototype:
232 : : *
233 : : * get_prototype() doesn't assert. If you call it on a Prototype, it returns
234 : : * you the same object cast to the correct type; if you call it on an
235 : : * Instance, it returns you the Prototype belonging to the corresponding JS
236 : : * prototype.
237 : : */
238 : : [[nodiscard]] [[gnu::const]]
239 : 2441 : Prototype* get_prototype() {
240 [ - + ]: 2441 : return is_prototype() ? to_prototype() : m_proto;
241 : : }
242 : : [[nodiscard]]
243 : 264824 : const Prototype* get_prototype() const {
244 [ + + ]: 264824 : return is_prototype() ? to_prototype() : m_proto;
245 : : }
246 : :
247 : : // Accessors for Prototype members follow below. Both Instance and Prototype
248 : : // should be able to access the GIFooInfo and the GType, but for space
249 : : // reasons we store them only on Prototype.
250 : :
251 : 12568 : [[nodiscard]] auto info() const { return get_prototype()->info(); }
252 : 252206 : [[nodiscard]] GType gtype() const { return get_prototype()->gtype(); }
253 : :
254 : : // The next three methods are operations derived from the GIFooInfo.
255 : :
256 : 5081 : [[nodiscard]] const char* type_name() const { return g_type_name(gtype()); }
257 : : [[nodiscard]]
258 : 2214 : const char* ns() const {
259 : : if constexpr (Prototype::may_not_have_info) {
260 : 1050 : const auto i = info();
261 [ + - ]: 1050 : return i ? i->ns() : "";
262 : 1050 : } else {
263 : 1164 : return info().ns();
264 : : }
265 : : }
266 : : [[nodiscard]]
267 : 2428 : const char* name() const {
268 : : if constexpr (Prototype::may_not_have_info) {
269 : 1259 : const auto i = info();
270 [ + + ]: 1259 : return i ? i->name() : type_name();
271 : 1259 : } else {
272 : 1169 : return info().name();
273 : : }
274 : : }
275 : :
276 : : // This exists for genericity, but use the default formatters for info() if
277 : : // info() is always present (i.e. prototypes can't be JS-only)
278 : : [[nodiscard]]
279 : 2363 : std::string format_name() const {
280 : : if constexpr (Prototype::may_not_have_info) {
281 : 1197 : const auto i = info();
282 [ + + + + ]: 2495 : return i ? i->display_string() : type_name();
283 : 1197 : } else {
284 : 1166 : return info().display_string();
285 : : }
286 : : }
287 : :
288 : : private:
289 : : // Accessor for Instance member. Used only in debug methods and toString().
290 : : [[nodiscard]]
291 : 55 : const void* ptr_addr() const {
292 [ - + ]: 55 : return is_prototype() ? nullptr : to_instance()->ptr();
293 : : }
294 : :
295 : : // Debug methods
296 : :
297 : : protected:
298 : 24261 : void debug_lifecycle(const char* message GJS_USED_VERBOSE_LIFECYCLE) const {
299 : : gjs_debug_lifecycle(Base::DEBUG_TOPIC,
300 : : "[{}: {} pointer {} - {} ({})] {}",
301 : : static_cast<const void*>(this), Base::DEBUG_TAG,
302 : : ptr_addr(), format_name(), type_name(), message);
303 : 24261 : }
304 : 64200 : void debug_lifecycle(const void* obj GJS_USED_VERBOSE_LIFECYCLE,
305 : : const char* message GJS_USED_VERBOSE_LIFECYCLE) const {
306 : : gjs_debug_lifecycle(Base::DEBUG_TOPIC,
307 : : "[{}: {} pointer {} - JS wrapper {} - {} ({})] {}",
308 : : static_cast<const void*>(this), Base::DEBUG_TAG,
309 : : ptr_addr(), obj, format_name(), type_name(),
310 : : message);
311 : 64200 : }
312 : 1161 : void debug_jsprop(const char* message GJS_USED_VERBOSE_PROPS,
313 : : const char* id GJS_USED_VERBOSE_PROPS,
314 : : const void* obj GJS_USED_VERBOSE_PROPS) const {
315 : : gjs_debug_jsprop(
316 : : Base::DEBUG_TOPIC,
317 : : "[{}: {} pointer {} - JS wrapper {} - {} ({})] {} '{}'",
318 : : static_cast<const void*>(this), Base::DEBUG_TAG, ptr_addr(), obj,
319 : : format_name(), type_name(), message, id);
320 : 1161 : }
321 : 94096 : void debug_jsprop(const char* message, jsid id, const void* obj) const {
322 : : if constexpr (GJS_VERBOSE_ENABLE_PROPS)
323 : : debug_jsprop(message, gjs_debug_id(id).c_str(), obj);
324 : 94096 : }
325 : : void debug_jsprop(const char* message, JSString* id,
326 : : const void* obj) const {
327 : : if constexpr (GJS_VERBOSE_ENABLE_PROPS)
328 : : debug_jsprop(message, gjs_debug_string(id).c_str(), obj);
329 : : }
330 : 5716 : static void debug_jsprop_static(const char* message GJS_USED_VERBOSE_PROPS,
331 : : jsid id GJS_USED_VERBOSE_PROPS,
332 : : const void* obj GJS_USED_VERBOSE_PROPS) {
333 : : gjs_debug_jsprop(Base::DEBUG_TOPIC,
334 : : "[{} JS wrapper {}] {} '{}', no instance associated",
335 : : Base::DEBUG_TAG, obj, message, id);
336 : 5716 : }
337 : :
338 : : // JS class operations, used only in the JSClassOps struct
339 : :
340 : : /**
341 : : * GIWrapperBase::new_enumerate:
342 : : *
343 : : * Include this in the Base::klass vtable if the class should support lazy
344 : : * enumeration (listing all of the lazy properties that can be defined in
345 : : * resolve().) If it is included, then there must be a corresponding
346 : : * Prototype::new_enumerate_impl() method.
347 : : */
348 : : GJS_JSAPI_RETURN_CONVENTION
349 : 262 : static bool new_enumerate(JSContext* cx, JS::HandleObject obj,
350 : : JS::MutableHandleIdVector properties,
351 : : bool only_enumerable) {
352 : 262 : Base* priv = Base::for_js(cx, obj);
353 : :
354 : 262 : priv->debug_jsprop("Enumerate hook", "(all)", obj);
355 : :
356 [ + + ]: 262 : if (!priv->is_prototype()) {
357 : : // Instances don't have any methods or properties. Spidermonkey will
358 : : // call new_enumerate on the prototype next.
359 : 154 : return true;
360 : : }
361 : :
362 : 108 : return priv->to_prototype()->new_enumerate_impl(cx, obj, properties,
363 : 108 : only_enumerable);
364 : : }
365 : :
366 : : private:
367 : : /**
368 : : * GIWrapperBase::id_is_never_lazy:
369 : : *
370 : : * Returns true if @id should never be treated as a lazy property. The
371 : : * JSResolveOp for an instance is called for every property not defined,
372 : : * even if it's one of the functions or properties we're adding to the
373 : : * prototype manually, such as toString().
374 : : *
375 : : * Override this and chain up if you have Base::resolve in your JSClassOps
376 : : * vtable, and have overridden Base::proto_properties or
377 : : * Base::proto_methods. You should add any identifiers in the override that
378 : : * you have added to the prototype object.
379 : : */
380 : : [[nodiscard]]
381 : 29971 : static bool id_is_never_lazy(jsid id, const GjsAtoms& atoms) {
382 : : // toString() is always defined somewhere on the prototype chain, so it
383 : : // is never a lazy property.
384 : 29971 : return id == atoms.to_string();
385 : : }
386 : :
387 : : protected:
388 : : /**
389 : : * GIWrapperBase::resolve_prototype:
390 : : */
391 : : [[nodiscard]]
392 : 4155 : static Prototype* resolve_prototype(JSContext* cx, JS::HandleObject proto) {
393 [ + + ]: 4155 : if (JS::GetClass(proto) == &Base::klass)
394 : 3641 : return Prototype::for_js(cx, proto);
395 : :
396 : 514 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
397 : :
398 : 514 : bool has_property = false;
399 [ - + ]: 514 : if (!JS_HasOwnPropertyById(cx, proto, atoms.gobject_prototype(),
400 : : &has_property))
401 : 0 : return nullptr;
402 : :
403 [ + + ]: 514 : if (!has_property) {
404 : 2 : gjs_throw(cx, "Tried to construct an object without a GType");
405 : 2 : return nullptr;
406 : : }
407 : :
408 : 512 : JS::RootedValue gobject_proto(cx);
409 [ - + ]: 512 : if (!JS_GetPropertyById(cx, proto, atoms.gobject_prototype(),
410 : 512 : &gobject_proto))
411 : 0 : return nullptr;
412 : :
413 [ - + ]: 512 : if (!gobject_proto.isObject()) {
414 : 0 : gjs_throw(cx, "Tried to construct an object without a GType");
415 : 0 : return nullptr;
416 : : }
417 : :
418 : 512 : JS::RootedObject obj(cx, &gobject_proto.toObject());
419 : : // gobject_prototype is an internal symbol so we can assert that it is
420 : : // only assigned to objects with &Base::klass definitions
421 : 512 : g_assert(JS::GetClass(obj) == &Base::klass);
422 : :
423 : 512 : return Prototype::for_js(cx, obj);
424 : 512 : }
425 : :
426 : : /**
427 : : * GIWrapperBase::resolve:
428 : : *
429 : : * Include this in the Base::klass vtable if the class should support lazy
430 : : * properties. If it is included, then there must be a corresponding
431 : : * Prototype::resolve_impl() method.
432 : : *
433 : : * The *resolved out parameter, on success, should be false to indicate that
434 : : * id was not resolved; and true if id was resolved.
435 : : */
436 : : GJS_JSAPI_RETURN_CONVENTION
437 : 95224 : static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
438 : : bool* resolved) {
439 : 95224 : Base* priv = Base::for_js(cx, obj);
440 : :
441 [ + + ]: 95224 : if (!priv) {
442 : : // This catches a case in Object where the private struct isn't set
443 : : // until the initializer is called, so just defer to prototype
444 : : // chains in this case.
445 : : //
446 : : // This isn't too bad: either you get undefined if the field doesn't
447 : : // exist on any of the prototype chains, or whatever code will run
448 : : // afterwards will fail because of the "!priv" check there.
449 : 3572 : debug_jsprop_static("Resolve hook", id, obj);
450 : 3572 : *resolved = false;
451 : 3572 : return true;
452 : : }
453 : :
454 : 91652 : priv->debug_jsprop("Resolve hook", id, obj);
455 : :
456 [ + + ]: 91652 : if (!priv->is_prototype()) {
457 : : // We are an instance, not a prototype, so look for per-instance
458 : : // props that we want to define on the JSObject. Generally we do not
459 : : // want to cache these in JS, we want to always pull them from the C
460 : : // object, or JS would not see any changes made from C. So we use
461 : : // the property accessors, not this resolve hook.
462 : 61681 : *resolved = false;
463 : 61681 : return true;
464 : : }
465 : :
466 : 29971 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
467 [ + + ]: 29971 : if (id_is_never_lazy(id, atoms)) {
468 : 3702 : *resolved = false;
469 : 3702 : return true;
470 : : }
471 : :
472 : 26269 : return priv->to_prototype()->resolve_impl(cx, obj, id, resolved);
473 : : }
474 : :
475 : : /**
476 : : * GIWrapperBase::finalize:
477 : : *
478 : : * This should always be included in the Base::klass vtable. The destructors
479 : : * of Prototype and Instance will be called in the finalize hook. It is not
480 : : * necessary to include a finalize_impl() function in Prototype or Instance.
481 : : * Any needed finalization should be done in ~Prototype() and ~Instance().
482 : : */
483 : 27242 : static void finalize(JS::GCContext* gcx, JSObject* obj) {
484 : 27242 : Base* priv = Base::for_js_nocheck(obj);
485 [ + + ]: 27242 : if (!priv)
486 : 2 : return; // construction didn't finish
487 : :
488 : : // Call only GIWrapperBase's original method here, not any overrides;
489 : : // e.g., we don't want to deal with a read barrier in ObjectInstance.
490 : 27240 : static_cast<GIWrapperBase*>(priv)->debug_lifecycle(obj, "Finalize");
491 : :
492 [ + + ]: 27240 : if (priv->is_prototype()) {
493 : 2397 : GType gtype = priv->to_prototype()->gtype();
494 : 2397 : remove_prototype_associated_memory(gtype, obj);
495 : 2397 : priv->to_prototype()->finalize_impl(gcx, obj);
496 : : } else {
497 : 24843 : priv->to_instance()->finalize_impl(gcx, obj);
498 : : }
499 : :
500 : 27240 : Base::unset_private(obj);
501 : : }
502 : :
503 : : /**
504 : : * GIWrapperBase::trace:
505 : : *
506 : : * This should be included in the Base::klass vtable if any of the Base,
507 : : * Prototype or Instance structures contain any members that the JS garbage
508 : : * collector must trace. Each struct containing such members must override
509 : : * GIWrapperBase::trace_impl(), GIWrapperPrototype::trace_impl(), and/or
510 : : * GIWrapperInstance::trace_impl() in order to perform the trace.
511 : : */
512 : 7311 : static void trace(JSTracer* trc, JSObject* obj) {
513 : 7311 : Base* priv = Base::for_js_nocheck(obj);
514 [ - + ]: 7311 : if (!priv)
515 : 0 : return;
516 : :
517 : : // Don't log in trace(). That would overrun even the most verbose logs.
518 : :
519 [ + + ]: 7311 : if (priv->is_prototype())
520 : 6424 : priv->to_prototype()->trace_impl(trc);
521 : : else
522 : 887 : priv->to_instance()->trace_impl(trc);
523 : :
524 : 7311 : priv->trace_impl(trc);
525 : : }
526 : :
527 : : /**
528 : : * GIWrapperBase::trace_impl:
529 : : * Override if necessary. See trace().
530 : : */
531 : 7311 : void trace_impl(JSTracer*) {}
532 : :
533 : : // JSNative methods
534 : :
535 : : /**
536 : : * GIWrapperBase::constructor:
537 : : *
538 : : * C++ implementation of the JS constructor passed to JS_InitClass(). Only
539 : : * called on instances, never on prototypes. This method contains the
540 : : * functionality common to all GI wrapper classes. There must be a
541 : : * corresponding Instance::constructor_impl method containing the rest of
542 : : * the functionality.
543 : : */
544 : : GJS_JSAPI_RETURN_CONVENTION
545 : 3325 : static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
546 : 3325 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
547 : :
548 [ + + ]: 3325 : if (!args.isConstructing()) {
549 : 1 : gjs_throw_constructor_error(cx);
550 : 1 : return false;
551 : : }
552 : 6648 : JS::RootedObject obj(
553 : 3324 : cx, JS_NewObjectForConstructor(cx, &Base::klass, args));
554 [ - + ]: 3324 : if (!obj)
555 : 0 : return false;
556 : :
557 : 3324 : JS::RootedObject proto(cx);
558 [ - + ]: 3324 : if (!JS_GetPrototype(cx, obj, &proto))
559 : 0 : return false;
560 : :
561 : 3324 : Prototype* prototype = resolve_prototype(cx, proto);
562 [ + + ]: 3324 : if (!prototype)
563 : 2 : return false;
564 : :
565 : 3322 : args.rval().setUndefined();
566 : :
567 : 3322 : Instance* priv = Instance::new_for_js_object(prototype, obj);
568 : :
569 : : {
570 : 3322 : std::string full_name{
571 [ - + + - ]: 6644 : GJS_PROFILER_DYNAMIC_STRING(cx, priv->format_name())};
572 : 3322 : AutoProfilerLabel label{cx, "constructor", full_name};
573 : :
574 [ + + ]: 3322 : if (!priv->constructor_impl(cx, obj, args))
575 : 47 : return false;
576 [ + + + + ]: 6644 : }
577 : :
578 : 3275 : static_cast<GIWrapperBase*>(priv)->debug_lifecycle(obj,
579 : : "JSObject created");
580 : : gjs_debug_lifecycle(Base::DEBUG_TOPIC, "m_proto is {}",
581 : : static_cast<void*>(priv->get_prototype()));
582 : :
583 : : // We may need to return a value different from obj (for example because
584 : : // we delegate to another constructor)
585 [ + + ]: 3275 : if (args.rval().isUndefined())
586 : 778 : args.rval().setObject(*obj);
587 : 3275 : return true;
588 : 3324 : }
589 : :
590 : : /**
591 : : * GIWrapperBase::to_string:
592 : : *
593 : : * JSNative method connected to the toString() method in JS.
594 : : */
595 : : GJS_JSAPI_RETURN_CONVENTION
596 : 55 : static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp) {
597 [ - + - + ]: 55 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, Base, priv);
598 : 110 : return gjs_wrapper_to_string_func(cx, obj, Base::DEBUG_TAG,
599 : 110 : priv->info(), priv->gtype(),
600 : 55 : priv->ptr_addr(), args.rval());
601 : 55 : }
602 : :
603 : : // Helper methods
604 : :
605 : : public:
606 : : /**
607 : : * GIWrapperBase::check_is_instance:
608 : : * @for_what: string used in the exception message if an exception is thrown
609 : : *
610 : : * Used in JSNative methods to ensure the passed-in JS object is an instance
611 : : * and not the prototype. Throws a JS exception if the prototype is passed
612 : : * in.
613 : : */
614 : : GJS_JSAPI_RETURN_CONVENTION
615 : 125150 : bool check_is_instance(JSContext* cx, const char* for_what) const {
616 [ + - ]: 125150 : if (!is_prototype())
617 : 125150 : return true;
618 : 0 : gjs_throw(cx, "Can't {} on {}.prototype; only on instances", for_what,
619 : 0 : format_name());
620 : 0 : return false;
621 : : }
622 : :
623 : : /**
624 : : * GIWrapperBase::to_c_ptr:
625 : : *
626 : : * Returns the underlying C pointer of the wrapped object, or throws a JS
627 : : * exception if that is not possible (for example, the passed-in JS object
628 : : * is the prototype.)
629 : : *
630 : : * Includes a JS typecheck (but without any extra typecheck of the GType or
631 : : * introspection info that you would get from GIWrapperBase::typecheck(), so
632 : : * if you want that you still have to do the typecheck before calling this
633 : : * method.)
634 : : */
635 : : template <typename T = void>
636 : : GJS_JSAPI_RETURN_CONVENTION
637 : 58945 : static T* to_c_ptr(JSContext* cx, JS::HandleObject obj) {
638 : : Base* priv;
639 [ + - - + ]: 117890 : if (!Base::for_js_typecheck(cx, obj, &priv) ||
640 [ - + ]: 58945 : !priv->check_is_instance(cx, "get a C pointer"))
641 : 0 : return nullptr;
642 : :
643 : 58945 : return static_cast<T*>(priv->to_instance()->ptr());
644 : : }
645 : :
646 : : /**
647 : : * GIWrapperBase::transfer_to_gi_argument:
648 : : * @arg: #GIArgument to fill with the value from @obj
649 : : * @transfer_direction: Either %GI_DIRECTION_IN or %GI_DIRECTION_OUT
650 : : * @transfer_ownership: #GITransfer value specifying whether @arg should
651 : : * copy or acquire a reference to the value or not
652 : : * @expected_gtype: #GType to perform a typecheck with
653 : : * @expected_info: Introspection info to perform a typecheck with
654 : : *
655 : : * Prepares @arg for passing the value from @obj into C code. It will get a
656 : : * C pointer from @obj and assign it to @arg's pointer field, taking a
657 : : * reference with GIWrapperInstance::copy_ptr() if @transfer_direction and
658 : : * @transfer_ownership indicate that it should.
659 : : *
660 : : * Includes a typecheck using GIWrapperBase::typecheck(), to which
661 : : * @expected_gtype and @expected_info are passed.
662 : : *
663 : : * If returning false, then @arg's pointer field is null.
664 : : */
665 : : GJS_JSAPI_RETURN_CONVENTION
666 : 58312 : static bool transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj,
667 : : GIArgument* arg,
668 : : GIDirection transfer_direction,
669 : : GITransfer transfer_ownership,
670 : : GType expected_gtype) {
671 : 58312 : g_assert(transfer_direction != GI_DIRECTION_INOUT &&
672 : : "transfer_to_gi_argument() must choose between in or out");
673 : :
674 [ + + + + ]: 116604 : if (expected_gtype != G_TYPE_NONE &&
675 [ + + ]: 58292 : !Base::typecheck(cx, obj, expected_gtype)) {
676 : 4 : gjs_arg_unset(arg);
677 : 4 : return false;
678 : : }
679 : :
680 : 58308 : gjs_arg_set(arg, Base::to_c_ptr(cx, obj));
681 [ - + ]: 58308 : if (!gjs_arg_get<void*>(arg))
682 : 0 : return false;
683 : :
684 [ + - + + ]: 58308 : if ((transfer_direction == GI_DIRECTION_IN &&
685 [ - + ]: 58184 : transfer_ownership != GI_TRANSFER_NOTHING) ||
686 [ # # ]: 0 : (transfer_direction == GI_DIRECTION_OUT &&
687 : : transfer_ownership == GI_TRANSFER_EVERYTHING)) {
688 : 124 : gjs_arg_set(arg, Instance::copy_ptr(cx, expected_gtype,
689 : : gjs_arg_get<void*>(arg)));
690 [ - + ]: 124 : if (!gjs_arg_get<void*>(arg))
691 : 0 : return false;
692 : : }
693 : :
694 : 58308 : return true;
695 : : }
696 : :
697 : : // Public typecheck API
698 : :
699 : : /**
700 : : * GIWrapperBase::typecheck:
701 : : * @expected_info: (nullable): GI info to check
702 : : * @expected_type: (nullable): GType to check
703 : : *
704 : : * Checks not only that the JS object is of the correct JSClass (like
705 : : * CWrapperPointerOps::typecheck() does); but also that the object is an
706 : : * instance, not the prototype; and that the instance's wrapped pointer is
707 : : * of the correct GType or GI info.
708 : : *
709 : : * The overload with a GjsTypecheckNoThrow parameter will not throw a JS
710 : : * exception if the prototype is passed in or the typecheck fails.
711 : : */
712 : : GJS_JSAPI_RETURN_CONVENTION
713 : 440 : static bool typecheck(JSContext* cx, JS::HandleObject object,
714 : : const GI::BaseInfo& expected_info) {
715 : : Base* priv;
716 [ + - - + ]: 880 : if (!Base::for_js_typecheck(cx, object, &priv) ||
717 [ - + ]: 440 : !priv->check_is_instance(cx, "convert to pointer"))
718 : 0 : return false;
719 : :
720 [ + - ]: 440 : if (priv->to_instance()->typecheck_impl(expected_info))
721 : 440 : return true;
722 : :
723 : 0 : gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr,
724 : : "Object is of type {} - cannot convert to {}",
725 : 0 : priv->format_name(), expected_info);
726 : 0 : return false;
727 : : }
728 : : GJS_JSAPI_RETURN_CONVENTION
729 : 61299 : static bool typecheck(JSContext* cx, JS::HandleObject object,
730 : : GType expected_gtype) {
731 : : Base* priv;
732 [ + + + + ]: 122594 : if (!Base::for_js_typecheck(cx, object, &priv) ||
733 [ - + ]: 61295 : !priv->check_is_instance(cx, "convert to pointer"))
734 : 4 : return false;
735 : :
736 [ + + ]: 61295 : if (priv->to_instance()->typecheck_impl(expected_gtype))
737 : 61290 : return true;
738 : :
739 : 5 : gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr,
740 : : "Object is of type {} - cannot convert to {}",
741 : 10 : priv->format_name(), g_type_name(expected_gtype));
742 : 5 : return false;
743 : : }
744 : : [[nodiscard]]
745 : 241 : static bool typecheck(JSContext* cx, JS::HandleObject object,
746 : : const auto& expected, GjsTypecheckNoThrow) {
747 : 241 : Base* priv = Base::for_js(cx, object);
748 [ + + - + : 241 : if (!priv || priv->is_prototype())
+ + ]
749 : 109 : return false;
750 : :
751 : 132 : return priv->to_instance()->typecheck_impl(expected);
752 : : }
753 : :
754 : : // Deleting these constructors and assignment operators will also delete
755 : : // them from derived classes.
756 : : GIWrapperBase(const GIWrapperBase& other) = delete;
757 : : GIWrapperBase(GIWrapperBase&& other) = delete;
758 : : GIWrapperBase& operator=(const GIWrapperBase& other) = delete;
759 : : GIWrapperBase& operator=(GIWrapperBase&& other) = delete;
760 : : };
761 : :
762 : : /**
763 : : * GIWrapperPrototype:
764 : : *
765 : : * The specialization of GIWrapperBase which becomes the private data of JS
766 : : * prototype objects. For example, it is the parent class of BoxedPrototype.
767 : : *
768 : : * Classes inheriting from GIWrapperPrototype must declare "friend class
769 : : * GIWrapperBase" as well as the normal CRTP requirement of "friend class
770 : : * GIWrapperPrototype", because of the unusual polymorphism scheme, in order for
771 : : * Base to call methods such as trace_impl().
772 : : */
773 : : template <class Base, class Prototype, class Instance, typename OwnedInfo,
774 : : typename UnownedInfo>
775 : : class GIWrapperPrototype : public Base {
776 : : using GjsAutoPrototype =
777 : 0 : Gjs::AutoPointer<Prototype, void, g_atomic_rc_box_release>;
778 : :
779 : : protected:
780 : : // m_info may be null in the case of JS-defined types, or internal types not
781 : : // exposed through introspection, such as GLocalFile. Not all subclasses of
782 : : // GIWrapperPrototype support this. Object and Interface support it in any
783 : : // case.
784 : : OwnedInfo m_info;
785 : : GType m_gtype;
786 : :
787 : 2420 : explicit GIWrapperPrototype(const UnownedInfo& info, GType gtype)
788 : 2420 : : Base(), m_info(info), m_gtype(gtype) {
789 : 2420 : Base::debug_lifecycle("Prototype constructor");
790 : 2420 : }
791 : :
792 : : /**
793 : : * GIWrapperPrototype::init:
794 : : *
795 : : * Performs any initialization that cannot be done in the constructor of
796 : : * GIWrapperPrototype, either because it can fail, or because it can cause a
797 : : * garbage collection.
798 : : *
799 : : * This default implementation does nothing. Override in a subclass if
800 : : * necessary.
801 : : */
802 : : GJS_JSAPI_RETURN_CONVENTION
803 : 2420 : bool init(JSContext*) { return true; }
804 : :
805 : : // The following four methods are private because they are used only in
806 : : // create_class().
807 : :
808 : : private:
809 : : /**
810 : : * GIWrapperPrototype::parent_proto:
811 : : *
812 : : * Returns in @proto the parent class's prototype object, or nullptr if
813 : : * there is none.
814 : : *
815 : : * This default implementation is for GObject introspection types that can't
816 : : * inherit in JS, like Boxed and Union. Override this if the type can
817 : : * inherit in JS.
818 : : */
819 : : GJS_JSAPI_RETURN_CONVENTION
820 : 1421 : static bool get_parent_proto(JSContext*, JS::MutableHandleObject proto) {
821 : 1421 : proto.set(nullptr);
822 : 1421 : return true;
823 : : }
824 : :
825 : : /**
826 : : * GIWrapperPrototype::constructor_nargs:
827 : : *
828 : : * Override this if the type's constructor takes other than 1 argument.
829 : : */
830 : 2304 : [[nodiscard]] unsigned constructor_nargs() const { return 1; }
831 : :
832 : : /**
833 : : * GIWrapperPrototype::define_jsclass:
834 : : * @in_object: JSObject on which to define the class constructor as a
835 : : * property
836 : : * @parent_proto: (nullable): prototype of the prototype
837 : : * @constructor: return location for the constructor function object
838 : : * @prototype: return location for the prototype object
839 : : *
840 : : * Defines a JS class with constructor and prototype, and optionally defines
841 : : * properties and methods on the prototype object, and methods on the
842 : : * constructor object.
843 : : *
844 : : * By default no properties or methods are defined, but derived classes can
845 : : * override the GIWrapperBase::proto_properties,
846 : : * GIWrapperBase::proto_methods, and GIWrapperBase::static_methods members.
847 : : * Static properties would also be possible but are not used anywhere in GJS
848 : : * so are not implemented yet.
849 : : *
850 : : * Note: no prototype methods are defined if @parent_proto is null.
851 : : *
852 : : * Here is a refresher comment on the difference between __proto__ and
853 : : * prototype that has been in the GJS codebase since forever:
854 : : *
855 : : * https://web.archive.org/web/20100716231157/http://egachine.berlios.de/embedding-sm-best-practice/apa.html
856 : : * https://www.sitepoint.com/javascript-inheritance/
857 : : * http://www.cs.rit.edu/~atk/JavaScript/manuals/jsobj/
858 : : *
859 : : * What we want is: repoobj.Gtk.Window is constructor for a GtkWindow
860 : : * wrapper JSObject (gjs_define_object_class() is supposed to define Window
861 : : * in Gtk.)
862 : : *
863 : : * Window.prototype contains the methods on Window, e.g. set_default_size()
864 : : * mywindow.__proto__ is Window.prototype
865 : : * mywindow.__proto__.__proto__ is Bin.prototype
866 : : * mywindow.__proto__.__proto__.__proto__ is Container.prototype
867 : : *
868 : : * Because Window.prototype is an instance of Window in a sense,
869 : : * Window.prototype.__proto__ is Window.prototype, just as
870 : : * mywindow.__proto__ is Window.prototype
871 : : *
872 : : * If we do "mywindow = new Window()" then we should get:
873 : : * mywindow.__proto__ == Window.prototype
874 : : * which means "mywindow instanceof Window" is true.
875 : : *
876 : : * Remember "Window.prototype" is "the __proto__ of stuff constructed with
877 : : * new Window()"
878 : : *
879 : : * __proto__ is used to search for properties if you do "this.foo", while
880 : : * .prototype is only relevant for constructors and is used to set __proto__
881 : : * on new'd objects. So .prototype only makes sense on constructors.
882 : : *
883 : : * JS_SetPrototype() and JS_GetPrototype() are for __proto__. To set/get
884 : : * .prototype, just use the normal property accessors, or JS_InitClass()
885 : : * sets it up automatically.
886 : : */
887 : : GJS_JSAPI_RETURN_CONVENTION
888 : 2314 : bool define_jsclass(JSContext* cx, JS::HandleObject in_object,
889 : : JS::HandleObject parent_proto,
890 : : JS::MutableHandleObject constructor,
891 : : JS::MutableHandleObject prototype) {
892 : : // The GI namespace is only used to set the JSClass->name field (exposed
893 : : // by Object.prototype.toString, for example). We can safely set
894 : : // "unknown" if this is a custom or internal JS class with no GI
895 : : // namespace, as in that case the name is already globally unique (it's
896 : : // a GType name).
897 : : const char* gi_namespace;
898 : : if constexpr (may_not_have_info)
899 [ + + ]: 1150 : gi_namespace = Base::info() ? Base::ns() : "unknown";
900 : : else
901 : 1164 : gi_namespace = Base::ns();
902 : :
903 : 2314 : unsigned nargs = static_cast<Prototype*>(this)->constructor_nargs();
904 : :
905 [ - + ]: 2314 : if (!gjs_init_class_dynamic(
906 : : cx, in_object, parent_proto, gi_namespace, Base::name(),
907 : : &Base::klass, &Base::constructor, nargs, Base::proto_properties,
908 [ + + ]: 2314 : parent_proto ? nullptr : Base::proto_methods,
909 : : Base::static_properties, Base::static_methods, prototype,
910 : : constructor))
911 : 0 : return false;
912 : :
913 : 2314 : gjs_debug(
914 : : Base::DEBUG_TOPIC,
915 : : "Defined class for {} ({}), prototype {:?}, JSClass {}, in {:?}",
916 : 4628 : Base::format_name(), Base::type_name(), prototype,
917 : 2314 : JS::GetClass(prototype)->name, in_object);
918 : :
919 : 2314 : return true;
920 : : }
921 : :
922 : : /**
923 : : * GIWrapperPrototype::define_static_methods:
924 : : *
925 : : * Defines all introspectable static methods on @constructor, including
926 : : * class methods for objects, and interface methods for interfaces. See
927 : : * gjs_define_static_methods() for details.
928 : : */
929 : : GJS_JSAPI_RETURN_CONVENTION
930 : 2420 : bool define_static_methods(JSContext* cx, JS::HandleObject constructor) {
931 : : if constexpr (may_not_have_info) {
932 [ + + ]: 1256 : if (!info())
933 : 206 : return true; // no introspection means no methods to define
934 : 1050 : return gjs_define_static_methods(cx, constructor, m_gtype,
935 : 2100 : info().value());
936 : : } else {
937 : 1164 : return gjs_define_static_methods(cx, constructor, m_gtype, m_info);
938 : : }
939 : : }
940 : :
941 : : GJS_JSAPI_RETURN_CONVENTION
942 : 2420 : static Prototype* create_prototype(const UnownedInfo& info, GType gtype) {
943 : 2420 : g_assert(gtype != G_TYPE_INVALID);
944 : :
945 : : // We have to keep the Prototype in an arcbox because some of its
946 : : // members are needed in some Instance destructors, e.g. m_gtype to
947 : : // figure out how to free the Instance's m_ptr, and m_info to figure out
948 : : // how many bytes to free if it is allocated directly. Storing a
949 : : // refcount on the prototype is cheaper than storing pointers to m_info
950 : : // and m_gtype on each instance.
951 : 2420 : Prototype* priv = g_atomic_rc_box_new0(Prototype);
952 : 2420 : new (priv) Prototype(info, gtype);
953 : :
954 : 2420 : return priv;
955 : : }
956 : :
957 : : public:
958 : : /**
959 : : * GIWrapperPrototype::create_class:
960 : : * @in_object: JSObject on which to define the class constructor as a
961 : : * property
962 : : * @info: (nullable): Introspection info for the class, or null if the class
963 : : * has been defined in JS
964 : : * @gtype: GType for the class
965 : : * @constructor: return location for the constructor function object
966 : : * @prototype: return location for the prototype object
967 : : *
968 : : * Creates a JS class that wraps a GI pointer, by defining its constructor
969 : : * function and prototype object. The prototype object is given an instance
970 : : * of GIWrapperPrototype as its private data, which is also returned.
971 : : * Basically treat this method as the public constructor.
972 : : *
973 : : * Also defines all the requested methods and properties on the prototype
974 : : * and constructor objects (see define_jsclass()), as well as a `$gtype`
975 : : * property and a toString() method.
976 : : *
977 : : * This method can be overridden and chained up to if the derived class
978 : : * needs to define more properties on the constructor or prototype objects,
979 : : * e.g. eager GI properties.
980 : : */
981 : : GJS_JSAPI_RETURN_CONVENTION
982 : 2314 : static Prototype* create_class(JSContext* cx, JS::HandleObject in_object,
983 : : const UnownedInfo& info, GType gtype,
984 : : JS::MutableHandleObject constructor,
985 : : JS::MutableHandleObject prototype) {
986 : 2314 : g_assert(in_object);
987 : :
988 : 2314 : GjsAutoPrototype priv = create_prototype(info, gtype);
989 [ - + ]: 2314 : if (!priv->init(cx))
990 : 0 : return nullptr;
991 : :
992 : 2314 : JS::RootedObject parent_proto(cx);
993 [ + - ]: 4628 : if (!priv->get_parent_proto(cx, &parent_proto) ||
994 [ - + - + ]: 4628 : !priv->define_jsclass(cx, in_object, parent_proto, constructor,
995 : : prototype))
996 : 0 : return nullptr;
997 : :
998 : : // Init the private variable of @private before we do anything else. If
999 : : // a garbage collection or error happens subsequently, then this object
1000 : : // might be traced and we would end up dereferencing a null pointer.
1001 : 2314 : Prototype* proto = priv.release();
1002 : 2314 : Prototype::init_private(prototype, proto);
1003 : :
1004 [ - + ]: 2314 : if (!gjs_wrapper_define_gtype_prop(cx, constructor, gtype))
1005 : 0 : return nullptr;
1006 : :
1007 : : // Every class has a toString() with C++ implementation, so define that
1008 : : // without requiring it to be listed in Base::proto_methods
1009 [ + + ]: 2314 : if (!parent_proto) {
1010 : 1504 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
1011 [ - + ]: 1504 : if (!JS_DefineFunctionById(cx, prototype, atoms.to_string(),
1012 : : &Base::to_string, 0,
1013 : : GJS_MODULE_PROP_FLAGS))
1014 : 0 : return nullptr;
1015 : : }
1016 : :
1017 [ - + ]: 2314 : if (!proto->define_static_methods(cx, constructor))
1018 : 0 : return nullptr;
1019 : :
1020 : 2314 : add_prototype_associated_memory(gtype, prototype);
1021 : :
1022 : 2314 : return proto;
1023 : 2314 : }
1024 : :
1025 : : GJS_JSAPI_RETURN_CONVENTION
1026 : 106 : static Prototype* wrap_class(JSContext* cx, JS::HandleObject in_object,
1027 : : const UnownedInfo& info, GType gtype,
1028 : : JS::HandleObject constructor,
1029 : : JS::MutableHandleObject prototype) {
1030 : 106 : g_assert(in_object);
1031 : :
1032 : 106 : GjsAutoPrototype priv = create_prototype(info, gtype);
1033 [ - + ]: 106 : if (!priv->init(cx))
1034 : 0 : return nullptr;
1035 : :
1036 : 106 : JS::RootedObject parent_proto(cx);
1037 [ - + ]: 106 : if (!priv->get_parent_proto(cx, &parent_proto))
1038 : 0 : return nullptr;
1039 : :
1040 [ + + ]: 106 : if (parent_proto) {
1041 : 103 : prototype.set(
1042 : 103 : JS_NewObjectWithGivenProto(cx, &Base::klass, parent_proto));
1043 : : } else {
1044 : 3 : prototype.set(JS_NewObject(cx, &Base::klass));
1045 : : }
1046 : :
1047 [ - + ]: 106 : if (!prototype)
1048 : 0 : return nullptr;
1049 : :
1050 : 106 : Prototype* proto = priv.release();
1051 : 106 : Prototype::init_private(prototype, proto);
1052 : :
1053 [ - + ]: 106 : if (!proto->define_static_methods(cx, constructor))
1054 : 0 : return nullptr;
1055 : :
1056 [ - + ]: 106 : if (!JS_DefineProperty(cx, in_object, proto->name(), constructor,
1057 : : GJS_MODULE_PROP_FLAGS))
1058 : 0 : return nullptr;
1059 : :
1060 : 106 : add_prototype_associated_memory(gtype, prototype);
1061 : :
1062 : 106 : return proto;
1063 : 106 : }
1064 : :
1065 : : // Methods to get an existing Prototype
1066 : :
1067 : : /**
1068 : : * GIWrapperPrototype::for_js:
1069 : : *
1070 : : * Like Base::for_js(), but asserts that the returned private struct is a
1071 : : * Prototype and not an Instance.
1072 : : */
1073 : : [[nodiscard]]
1074 : 4217 : static Prototype* for_js(JSContext* cx, JS::HandleObject wrapper) {
1075 : 4217 : return Base::for_js(cx, wrapper)->to_prototype();
1076 : : }
1077 : :
1078 : : /**
1079 : : * GIWrapperPrototype::for_js_prototype:
1080 : : *
1081 : : * Gets the Prototype private data from to @wrapper.prototype. Cannot return
1082 : : * null, and asserts so.
1083 : : */
1084 : : [[nodiscard]]
1085 : 20694 : static Prototype* for_js_prototype(JSContext* cx,
1086 : : JS::HandleObject wrapper) {
1087 : 20694 : JS::RootedObject proto(cx);
1088 : 20694 : JS_GetPrototype(cx, wrapper, &proto);
1089 : 20694 : Base* retval = Base::for_js(cx, proto);
1090 : 20694 : g_assert(retval);
1091 : 20694 : return retval->to_prototype();
1092 : 20694 : }
1093 : :
1094 : : // Accessors
1095 : :
1096 : : static constexpr bool may_not_have_info = is_maybe<UnownedInfo>::value;
1097 : 33194 : [[nodiscard]] UnownedInfo info() const { return m_info; }
1098 : 257486 : [[nodiscard]] GType gtype() const { return m_gtype; }
1099 : :
1100 : : // Helper methods
1101 : :
1102 : : private:
1103 : 2397 : static void destroy_notify(void* ptr) {
1104 : 2397 : static_cast<Prototype*>(ptr)->~Prototype();
1105 : 2397 : }
1106 : :
1107 : : public:
1108 : 24847 : Prototype* acquire() {
1109 : 24847 : g_atomic_rc_box_acquire(this);
1110 : 24847 : return static_cast<Prototype*>(this);
1111 : : }
1112 : :
1113 : 27240 : void release() { g_atomic_rc_box_release_full(this, &destroy_notify); }
1114 : :
1115 : : // JSClass operations
1116 : :
1117 : : protected:
1118 : 2397 : void finalize_impl(JS::GCContext*, JSObject*) { release(); }
1119 : :
1120 : : // Override if necessary
1121 : 14 : void trace_impl(JSTracer*) {}
1122 : : };
1123 : :
1124 : : using GIWrappedUnowned = void;
1125 : : namespace Gjs {
1126 : : template <>
1127 : : struct SmartPointer<GIWrappedUnowned>
1128 : : : AutoPointer<GIWrappedUnowned, void, nullptr> {
1129 : : using AutoPointer::AutoPointer;
1130 : : };
1131 : : } // namespace Gjs
1132 : :
1133 : : /**
1134 : : * GIWrapperInstance:
1135 : : *
1136 : : * The specialization of GIWrapperBase which becomes the private data of JS
1137 : : * instance objects. For example, it is the parent class of BoxedInstance.
1138 : : *
1139 : : * Classes inheriting from GIWrapperInstance must declare "friend class
1140 : : * GIWrapperBase" as well as the normal CRTP requirement of "friend class
1141 : : * GIWrapperInstance", because of the unusual polymorphism scheme, in order for
1142 : : * Base to call methods such as trace_impl().
1143 : : */
1144 : : template <class Base, class Prototype, class Instance,
1145 : : typename Wrapped = GIWrappedUnowned>
1146 : : class GIWrapperInstance : public Base {
1147 : : protected:
1148 : : Gjs::SmartPointer<Wrapped> m_ptr;
1149 : :
1150 : 24847 : explicit GIWrapperInstance(Prototype* prototype, JS::HandleObject obj)
1151 : 24847 : : Base(prototype), m_ptr(nullptr) {
1152 : 24847 : Base::m_proto->acquire();
1153 : 24847 : Base::GIWrapperBase::debug_lifecycle(obj, "Instance constructor");
1154 : 24847 : }
1155 : :
1156 : 24843 : ~GIWrapperInstance() { Base::m_proto->release(); }
1157 : :
1158 : : public:
1159 : : /**
1160 : : * GIWrapperInstance::new_for_js_object:
1161 : : *
1162 : : * Creates a GIWrapperInstance and associates it with @obj as its private
1163 : : * data. This is called by the JS constructor.
1164 : : */
1165 : : [[nodiscard]]
1166 : 20694 : static Instance* new_for_js_object(JSContext* cx, JS::HandleObject obj) {
1167 : 20694 : Prototype* prototype = Prototype::for_js_prototype(cx, obj);
1168 : 20694 : auto* priv = new Instance(prototype, obj);
1169 : :
1170 : : // Init the private variable before we do anything else. If a garbage
1171 : : // collection happens when calling the constructor, then this object
1172 : : // might be traced and we would end up dereferencing a null pointer.
1173 : 20694 : Instance::init_private(obj, priv);
1174 : :
1175 : 20694 : return priv;
1176 : : }
1177 : :
1178 : : [[nodiscard]]
1179 : 3322 : static Instance* new_for_js_object(Prototype* prototype,
1180 : : JS::HandleObject obj) {
1181 : 3322 : auto* priv = new Instance(prototype, obj);
1182 : :
1183 : 3322 : Instance::init_private(obj, priv);
1184 : :
1185 : 3322 : return priv;
1186 : : }
1187 : :
1188 : : // Method to get an existing Instance
1189 : :
1190 : : /**
1191 : : * GIWrapperInstance::for_js:
1192 : : *
1193 : : * Like Base::for_js(), but asserts that the returned private struct is an
1194 : : * Instance and not a Prototype.
1195 : : */
1196 : : [[nodiscard]]
1197 : 60 : static Instance* for_js(JSContext* cx, JS::HandleObject wrapper) {
1198 : 60 : return Base::for_js(cx, wrapper)->to_instance();
1199 : : }
1200 : :
1201 : : // Accessors
1202 : :
1203 : 62151 : [[nodiscard]] Wrapped* ptr() const { return m_ptr; }
1204 : : /**
1205 : : * GIWrapperInstance::raw_ptr:
1206 : : *
1207 : : * Like ptr(), but returns a byte pointer for use in byte arithmetic.
1208 : : */
1209 : : [[nodiscard]]
1210 : 122 : uint8_t* raw_ptr() const {
1211 : 122 : return reinterpret_cast<uint8_t*>(ptr());
1212 : : }
1213 : :
1214 : : // JSClass operations
1215 : :
1216 : : protected:
1217 : 24843 : void finalize_impl(JS::GCContext*, JSObject*) {
1218 [ + - ]: 24843 : delete static_cast<Instance*>(this);
1219 : 24843 : }
1220 : :
1221 : : // Override if necessary
1222 : 0 : void trace_impl(JSTracer*) {}
1223 : :
1224 : : // Helper methods
1225 : :
1226 : : /**
1227 : : * GIWrapperInstance::typecheck_impl:
1228 : : *
1229 : : * See GIWrapperBase::typecheck(). Checks that the instance's wrapped
1230 : : * pointer is of the correct GType or GI info. Does not throw a JS
1231 : : * exception.
1232 : : *
1233 : : * It's possible to override typecheck_impl() if you need an extra step in
1234 : : * the check.
1235 : : */
1236 : : [[nodiscard]]
1237 : 495 : bool typecheck_impl(const GI::BaseInfo& expected_info) const {
1238 : : if constexpr (Prototype::may_not_have_info) {
1239 : : if (Base::info())
1240 : : return Base::info().ref() == expected_info;
1241 : : } else {
1242 : 495 : return Base::info() == expected_info;
1243 : : }
1244 : : return true;
1245 : : }
1246 : : [[nodiscard]]
1247 : 61372 : bool typecheck_impl(GType expected_gtype) const {
1248 : 61372 : g_assert(expected_gtype != G_TYPE_NONE &&
1249 : : "should not call typecheck_impl() without a real GType");
1250 [ + + + + ]: 61372 : return g_type_is_a(Base::gtype(), expected_gtype);
1251 : : }
1252 : : };
|