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