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