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