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 : : #include <config.h>
7 : :
8 : : #include <stdint.h>
9 : : #include <string.h> // for memset, strcmp
10 : :
11 : : #include <algorithm> // for find
12 : : #include <functional> // for mem_fn
13 : : #include <limits>
14 : : #include <string>
15 : : #include <tuple> // for tie
16 : : #include <unordered_set>
17 : : #include <utility> // for move
18 : : #include <vector>
19 : :
20 : : #include <girepository.h>
21 : : #include <glib-object.h>
22 : : #include <glib.h>
23 : :
24 : : #include <js/CallAndConstruct.h> // for IsCallable, JS_CallFunctionValue
25 : : #include <js/CallArgs.h>
26 : : #include <js/CharacterEncoding.h>
27 : : #include <js/Class.h>
28 : : #include <js/ComparisonOperators.h>
29 : : #include <js/ErrorReport.h> // for JS_ReportOutOfMemory
30 : : #include <js/GCAPI.h> // for JS_AddWeakPointerCompartmentCallback
31 : : #include <js/GCVector.h> // for MutableWrappedPtrOperations
32 : : #include <js/HeapAPI.h>
33 : : #include <js/MemoryFunctions.h> // for AddAssociatedMemory, RemoveAssoci...
34 : : #include <js/PropertyAndElement.h>
35 : : #include <js/PropertyDescriptor.h> // for JSPROP_PERMANENT, JSPROP_READONLY
36 : : #include <js/String.h>
37 : : #include <js/Symbol.h>
38 : : #include <js/TypeDecls.h>
39 : : #include <js/Utility.h> // for UniqueChars
40 : : #include <js/Value.h>
41 : : #include <js/ValueArray.h>
42 : : #include <js/Warnings.h>
43 : : #include <jsapi.h> // for JS_GetFunctionObject, IdVector
44 : : #include <jsfriendapi.h> // for JS_GetObjectFunction, GetFunctionNativeReserved
45 : : #include <mozilla/HashTable.h>
46 : :
47 : : #include "gi/arg-inl.h"
48 : : #include "gi/arg.h"
49 : : #include "gi/closure.h"
50 : : #include "gi/cwrapper.h"
51 : : #include "gi/function.h"
52 : : #include "gi/gjs_gi_trace.h"
53 : : #include "gi/object.h"
54 : : #include "gi/repo.h"
55 : : #include "gi/toggle.h"
56 : : #include "gi/utils-inl.h" // for gjs_int_to_pointer
57 : : #include "gi/value.h"
58 : : #include "gi/wrapperutils.h"
59 : : #include "gjs/atoms.h"
60 : : #include "gjs/context-private.h"
61 : : #include "gjs/deprecation.h"
62 : : #include "gjs/jsapi-class.h"
63 : : #include "gjs/jsapi-util.h"
64 : : #include "gjs/jsapi-util-args.h"
65 : : #include "gjs/jsapi-util-root.h"
66 : : #include "gjs/macros.h"
67 : : #include "gjs/mem-private.h"
68 : : #include "gjs/profiler-private.h"
69 : : #include "util/log.h"
70 : :
71 : : class JSTracer;
72 : :
73 : : /* This is a trick to print out the sizes of the structs at compile time, in
74 : : * an error message. */
75 : : // template <int s> struct Measure;
76 : : // Measure<sizeof(ObjectInstance)> instance_size;
77 : : // Measure<sizeof(ObjectPrototype)> prototype_size;
78 : :
79 : : #if defined(__x86_64__) && defined(__clang__)
80 : : /* This isn't meant to be comprehensive, but should trip on at least one CI job
81 : : * if sizeof(ObjectInstance) is increased. */
82 : : static_assert(sizeof(ObjectInstance) <= 64,
83 : : "Think very hard before increasing the size of ObjectInstance. "
84 : : "There can be tens of thousands of them alive in a typical "
85 : : "gnome-shell run.");
86 : : #endif // x86-64 clang
87 : :
88 : : bool ObjectInstance::s_weak_pointer_callback = false;
89 : : decltype(ObjectInstance::s_wrapped_gobject_list)
90 : : ObjectInstance::s_wrapped_gobject_list;
91 : :
92 : : static const auto DISPOSED_OBJECT = std::numeric_limits<uintptr_t>::max();
93 : :
94 : : GJS_JSAPI_RETURN_CONVENTION
95 : : static JSObject* gjs_lookup_object_prototype_from_info(JSContext*, GIBaseInfo*,
96 : : GType);
97 : :
98 : : // clang-format off
99 [ + + ]: 7240 : G_DEFINE_QUARK(gjs::custom-type, ObjectBase::custom_type)
100 [ + + ]: 498 : G_DEFINE_QUARK(gjs::custom-property, ObjectBase::custom_property)
101 [ + + ]: 3 : G_DEFINE_QUARK(gjs::instance-strings, ObjectBase::instance_strings)
102 [ + + ]: 1865 : G_DEFINE_QUARK(gjs::disposed, ObjectBase::disposed)
103 : : // clang-format on
104 : :
105 : 6439 : [[nodiscard]] static GQuark gjs_object_priv_quark() {
106 : : static GQuark val = 0;
107 [ + + ]: 6439 : if (G_UNLIKELY (!val))
108 : 29 : val = g_quark_from_static_string ("gjs::private");
109 : :
110 : 6439 : return val;
111 : : }
112 : :
113 : 2752 : bool ObjectBase::is_custom_js_class() {
114 : 2752 : return !!g_type_get_qdata(gtype(), ObjectBase::custom_type_quark());
115 : : }
116 : :
117 : : // Plain g_type_query fails and leaves @query uninitialized for dynamic types.
118 : : // See https://gitlab.gnome.org/GNOME/glib/issues/623
119 : 3242 : void ObjectBase::type_query_dynamic_safe(GTypeQuery* query) {
120 : 3242 : GType type = gtype();
121 [ + + ]: 4362 : while (g_type_get_qdata(type, ObjectBase::custom_type_quark()))
122 : 1120 : type = g_type_parent(type);
123 : :
124 : 3242 : g_type_query(type, query);
125 : 3242 : }
126 : :
127 : 1546 : void ObjectInstance::link() {
128 : 1546 : g_assert(std::find(s_wrapped_gobject_list.begin(),
129 : : s_wrapped_gobject_list.end(),
130 : : this) == s_wrapped_gobject_list.end());
131 : 1546 : s_wrapped_gobject_list.push_back(this);
132 : 1546 : }
133 : :
134 : 1562 : void ObjectInstance::unlink() {
135 : 1562 : Gjs::remove_one_from_unsorted_vector(&s_wrapped_gobject_list, this);
136 : 1562 : }
137 : :
138 : 7596 : const void* ObjectBase::jsobj_addr(void) const {
139 [ + + ]: 7596 : if (is_prototype())
140 : 672 : return nullptr;
141 : 6924 : return to_instance()->m_wrapper.debug_addr();
142 : : }
143 : :
144 : : // Overrides GIWrapperBase::typecheck(). We only override the overload that
145 : : // throws, so that we can throw our own more informative error.
146 : 1978 : bool ObjectBase::typecheck(JSContext* cx, JS::HandleObject obj,
147 : : GIObjectInfo* expected_info, GType expected_gtype) {
148 [ + + ]: 1978 : if (GIWrapperBase::typecheck(cx, obj, expected_info, expected_gtype))
149 : 1973 : return true;
150 : :
151 : 5 : gjs_throw(cx,
152 : : "This JS object wrapper isn't wrapping a GObject."
153 : : " If this is a custom subclass, are you sure you chained"
154 : : " up to the parent _init properly?");
155 : 5 : return false;
156 : : }
157 : :
158 : 3770 : bool ObjectInstance::check_gobject_disposed_or_finalized(
159 : : const char* for_what) const {
160 [ + + ]: 3770 : if (!m_gobj_disposed)
161 : 3730 : return true;
162 : :
163 [ + + ]: 40 : g_critical(
164 : : "Object %s.%s (%p), has been already %s — impossible to %s "
165 : : "it. This might be caused by the object having been destroyed from C "
166 : : "code using something such as destroy(), dispose(), or remove() "
167 : : "vfuncs.\n%s",
168 : : ns(), name(), m_ptr.get(), m_gobj_finalized ? "finalized" : "disposed",
169 : : for_what, gjs_dumpstack_string().c_str());
170 : 40 : return false;
171 : : }
172 : :
173 : 2715 : bool ObjectInstance::check_gobject_finalized(const char* for_what) const {
174 [ + + ]: 2715 : if (check_gobject_disposed_or_finalized(for_what))
175 : 2681 : return true;
176 : :
177 : 34 : return !m_gobj_finalized;
178 : : }
179 : :
180 : : ObjectInstance *
181 : 2957 : ObjectInstance::for_gobject(GObject *gobj)
182 : : {
183 : 2957 : auto priv = static_cast<ObjectInstance *>(g_object_get_qdata(gobj,
184 : : gjs_object_priv_quark()));
185 : :
186 [ + + ]: 2957 : if (priv)
187 : 1910 : priv->check_js_object_finalized();
188 : :
189 : 2957 : return priv;
190 : : }
191 : :
192 : : void
193 : 1910 : ObjectInstance::check_js_object_finalized(void)
194 : : {
195 [ + + ]: 1910 : if (!m_uses_toggle_ref)
196 : 285 : return;
197 [ - + ]: 1625 : if (G_UNLIKELY(m_wrapper_finalized)) {
198 : 0 : g_critical(
199 : : "Object %p (a %s) resurfaced after the JS wrapper was finalized. "
200 : : "This is some library doing dubious memory management inside "
201 : : "dispose()",
202 : : m_ptr.get(), type_name());
203 : 0 : m_wrapper_finalized = false;
204 : 0 : g_assert(!m_wrapper); /* should associate again with a new wrapper */
205 : : }
206 : : }
207 : :
208 : 136 : ObjectPrototype* ObjectPrototype::for_gtype(GType gtype) {
209 : : return static_cast<ObjectPrototype*>(
210 : 136 : g_type_get_qdata(gtype, gjs_object_priv_quark()));
211 : : }
212 : :
213 : 117 : void ObjectPrototype::set_type_qdata(void) {
214 : 117 : g_type_set_qdata(m_gtype, gjs_object_priv_quark(), this);
215 : 117 : }
216 : :
217 : : void
218 : 1546 : ObjectInstance::set_object_qdata(void)
219 : : {
220 : 1546 : g_object_set_qdata_full(
221 : 1547 : m_ptr, gjs_object_priv_quark(), this, [](void* object) {
222 : 1 : auto* self = static_cast<ObjectInstance*>(object);
223 [ - + ]: 1 : if (G_UNLIKELY(!self->m_gobj_disposed)) {
224 : 0 : g_warning(
225 : : "Object %p (a %s) was finalized but we didn't track "
226 : : "its disposal",
227 : : self->m_ptr.get(), g_type_name(self->gtype()));
228 : 0 : self->m_gobj_disposed = true;
229 : : }
230 : 1 : self->m_gobj_finalized = true;
231 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
232 : : "Wrapped GObject %p finalized",
233 : : self->m_ptr.get());
234 : 1 : });
235 : 1546 : }
236 : :
237 : : void
238 : 1683 : ObjectInstance::unset_object_qdata(void)
239 : : {
240 : 1683 : auto priv_quark = gjs_object_priv_quark();
241 [ + + ]: 1683 : if (g_object_get_qdata(m_ptr, priv_quark) == this)
242 : 1544 : g_object_steal_qdata(m_ptr, priv_quark);
243 : 1683 : }
244 : :
245 : 605 : GParamSpec* ObjectPrototype::find_param_spec_from_id(
246 : : JSContext* cx, GjsAutoTypeClass<GObjectClass> const& object_class,
247 : : JS::HandleString key) {
248 : : /* First check for the ID in the cache */
249 : :
250 : 605 : JS::UniqueChars js_prop_name(JS_EncodeStringToUTF8(cx, key));
251 [ - + ]: 605 : if (!js_prop_name)
252 : 0 : return nullptr;
253 : :
254 : 605 : GjsAutoChar gname = gjs_hyphen_from_camel(js_prop_name.get());
255 : 605 : GParamSpec* pspec = g_object_class_find_property(object_class, gname);
256 : :
257 [ + + ]: 605 : if (!pspec) {
258 : 2 : gjs_wrapper_throw_nonexistent_field(cx, m_gtype, js_prop_name.get());
259 : 2 : return nullptr;
260 : : }
261 : :
262 : 603 : return pspec;
263 : 605 : }
264 : :
265 : : /* A hook on adding a property to an object. This is called during a set
266 : : * property operation after all the resolve hooks on the prototype chain have
267 : : * failed to resolve. We use this to mark an object as needing toggle refs when
268 : : * custom state is set on it, because we need to keep the JS GObject wrapper
269 : : * alive in order not to lose custom "expando" properties.
270 : : */
271 : 9380 : bool ObjectBase::add_property(JSContext* cx, JS::HandleObject obj,
272 : : JS::HandleId id, JS::HandleValue value) {
273 : 9380 : auto* priv = ObjectBase::for_js(cx, obj);
274 : :
275 : : /* priv is null during init: property is not being added from JS */
276 [ + + ]: 9380 : if (!priv) {
277 : 1549 : debug_jsprop_static("Add property hook", id, obj);
278 : 1549 : return true;
279 : : }
280 [ + + ]: 7831 : if (priv->is_prototype())
281 : 6156 : return true;
282 : :
283 : 1675 : return priv->to_instance()->add_property_impl(cx, obj, id, value);
284 : : }
285 : :
286 : 1675 : bool ObjectInstance::add_property_impl(JSContext* cx, JS::HandleObject obj,
287 : : JS::HandleId id, JS::HandleValue) {
288 : 1675 : debug_jsprop("Add property hook", id, obj);
289 : :
290 [ + + ]: 1675 : if (is_custom_js_class())
291 : 1040 : return true;
292 : :
293 : 635 : ensure_uses_toggle_ref(cx);
294 : 635 : return true;
295 : : }
296 : :
297 : 479 : bool ObjectBase::prop_getter(JSContext* cx, unsigned argc, JS::Value* vp) {
298 [ - + - + ]: 479 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
299 : :
300 : : auto* pspec = static_cast<GParamSpec*>(
301 : 479 : gjs_dynamic_property_private_slot(&args.callee()).toPrivate());
302 : :
303 : 479 : std::string fullName{priv->format_name() + "[\"" + pspec->name + "\"]"};
304 : 479 : AutoProfilerLabel label(cx, "property getter", fullName.c_str());
305 : :
306 : 479 : priv->debug_jsprop("Property getter", pspec->name, obj);
307 : :
308 [ + + ]: 479 : if (priv->is_prototype())
309 : 155 : return true;
310 : : /* Ignore silently; note that this is different from what we do for
311 : : * boxed types, for historical reasons */
312 : :
313 : 324 : return priv->to_instance()->prop_getter_impl(cx, pspec, args.rval());
314 : 479 : }
315 : :
316 : 324 : bool ObjectInstance::prop_getter_impl(JSContext* cx, GParamSpec* param,
317 : : JS::MutableHandleValue rval) {
318 [ + + ]: 324 : if (!check_gobject_finalized("get any property from")) {
319 : 2 : rval.setUndefined();
320 : 2 : return true;
321 : : }
322 : :
323 [ - + ]: 322 : if (param->flags & G_PARAM_DEPRECATED) {
324 : 0 : const std::string& class_name = format_name();
325 : 0 : _gjs_warn_deprecated_once_per_callsite(
326 : 0 : cx, DeprecatedGObjectProperty, {class_name.c_str(), param->name});
327 : 0 : }
328 : :
329 [ + + ]: 322 : if ((param->flags & G_PARAM_READABLE) == 0) {
330 : 1 : rval.setUndefined();
331 : 1 : return true;
332 : : }
333 : :
334 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s",
335 : : param->name);
336 : :
337 : 321 : Gjs::AutoGValue gvalue(G_PARAM_SPEC_VALUE_TYPE(param));
338 : 321 : g_object_get_property(m_ptr, param->name, &gvalue);
339 : :
340 : 321 : return gjs_value_from_g_value(cx, rval, &gvalue);
341 : 321 : }
342 : :
343 : 3633 : [[nodiscard]] static GjsAutoFieldInfo lookup_field_info(GIObjectInfo* info,
344 : : const char* name) {
345 : 3633 : int n_fields = g_object_info_get_n_fields(info);
346 : : int ix;
347 : 3633 : GjsAutoFieldInfo retval;
348 : :
349 [ + + ]: 12529 : for (ix = 0; ix < n_fields; ix++) {
350 : 8901 : retval = g_object_info_get_field(info, ix);
351 [ + + ]: 8901 : if (strcmp(name, retval.name()) == 0)
352 : 5 : break;
353 : 8896 : retval.reset();
354 : : }
355 : :
356 [ + + - + : 3633 : if (!retval || !(g_field_info_get_flags(retval) & GI_FIELD_IS_READABLE))
+ + ]
357 : 3628 : return nullptr;
358 : :
359 : 5 : return retval;
360 : 3633 : }
361 : :
362 : 8 : bool ObjectBase::field_getter(JSContext* cx, unsigned argc, JS::Value* vp) {
363 [ - + - + ]: 8 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
364 : :
365 : : JS::RootedString name(cx,
366 : 8 : gjs_dynamic_property_private_slot(&args.callee()).toString());
367 : :
368 : 16 : std::string fullName{priv->format_name() + "[" + gjs_debug_string(name) +
369 : 8 : "]"};
370 : 8 : AutoProfilerLabel label(cx, "field getter", fullName.c_str());
371 : :
372 : 8 : priv->debug_jsprop("Field getter", name, obj);
373 : :
374 [ - + ]: 8 : if (priv->is_prototype())
375 : 0 : return true;
376 : : /* Ignore silently; note that this is different from what we do for
377 : : * boxed types, for historical reasons */
378 : :
379 : 8 : return priv->to_instance()->field_getter_impl(cx, name, args.rval());
380 : 8 : }
381 : :
382 : 8 : bool ObjectInstance::field_getter_impl(JSContext* cx, JS::HandleString name,
383 : : JS::MutableHandleValue rval) {
384 [ - + ]: 8 : if (!check_gobject_finalized("get any property from"))
385 : 0 : return true;
386 : :
387 : 8 : ObjectPrototype* proto_priv = get_prototype();
388 : 8 : GIFieldInfo* field = proto_priv->lookup_cached_field_info(cx, name);
389 : : GITypeTag tag;
390 : 8 : GIArgument arg = { 0 };
391 : :
392 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Overriding %s with GObject field",
393 : : gjs_debug_string(name).c_str());
394 : :
395 : 8 : GjsAutoTypeInfo type = g_field_info_get_type(field);
396 : 8 : tag = g_type_info_get_tag(type);
397 : :
398 [ + + ]: 8 : switch (tag) {
399 : 2 : case GI_TYPE_TAG_ARRAY:
400 : : case GI_TYPE_TAG_ERROR:
401 : : case GI_TYPE_TAG_GHASH:
402 : : case GI_TYPE_TAG_GLIST:
403 : : case GI_TYPE_TAG_GSLIST:
404 : : case GI_TYPE_TAG_INTERFACE:
405 : 4 : gjs_throw(cx,
406 : : "Can't get field %s; GObject introspection supports only "
407 : : "fields with simple types, not %s",
408 : 4 : gjs_debug_string(name).c_str(),
409 : : g_type_tag_to_string(tag));
410 : 2 : return false;
411 : :
412 : 6 : default:
413 : 6 : break;
414 : : }
415 : :
416 [ - + ]: 6 : if (!g_field_info_get_field(field, m_ptr, &arg)) {
417 : 0 : gjs_throw(cx, "Error getting field %s from object",
418 : 0 : gjs_debug_string(name).c_str());
419 : 0 : return false;
420 : : }
421 : :
422 : 6 : return gjs_value_from_gi_argument(cx, rval, type, GJS_ARGUMENT_FIELD,
423 : 6 : GI_TRANSFER_EVERYTHING, &arg);
424 : : /* transfer is irrelevant because g_field_info_get_field() doesn't
425 : : * handle boxed types */
426 : 8 : }
427 : :
428 : : /* Dynamic setter for GObject properties. Returns false on OOM/exception.
429 : : * args.rval() becomes the "stored value" for the property. */
430 : 195 : bool ObjectBase::prop_setter(JSContext* cx, unsigned argc, JS::Value* vp) {
431 [ - + - + ]: 195 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
432 : :
433 : : auto* pspec = static_cast<GParamSpec*>(
434 : 195 : gjs_dynamic_property_private_slot(&args.callee()).toPrivate());
435 : :
436 : 195 : std::string fullName{priv->format_name() + "[\"" + pspec->name + "\"]"};
437 : 195 : AutoProfilerLabel label(cx, "property setter", fullName.c_str());
438 : :
439 : 195 : priv->debug_jsprop("Property setter", pspec->name, obj);
440 : :
441 [ - + ]: 195 : if (priv->is_prototype())
442 : 0 : return true;
443 : : /* Ignore silently; note that this is different from what we do for
444 : : * boxed types, for historical reasons */
445 : :
446 : : /* Clear the JS stored value, to avoid keeping additional references */
447 : 195 : args.rval().setUndefined();
448 : :
449 : 195 : return priv->to_instance()->prop_setter_impl(cx, pspec, args[0]);
450 : 195 : }
451 : :
452 : 195 : bool ObjectInstance::prop_setter_impl(JSContext* cx, GParamSpec* param_spec,
453 : : JS::HandleValue value) {
454 [ + + ]: 195 : if (!check_gobject_finalized("set any property on"))
455 : 1 : return true;
456 : :
457 [ + + ]: 194 : if (!(param_spec->flags & G_PARAM_WRITABLE))
458 : : /* prevent setting the prop even in JS */
459 : 1 : return gjs_wrapper_throw_readonly_field(cx, gtype(), param_spec->name);
460 : :
461 [ - + ]: 193 : if (param_spec->flags & G_PARAM_DEPRECATED) {
462 : 0 : const std::string& class_name = format_name();
463 : 0 : _gjs_warn_deprecated_once_per_callsite(
464 : : cx, DeprecatedGObjectProperty,
465 : 0 : {class_name.c_str(), param_spec->name});
466 : 0 : }
467 : :
468 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop %s",
469 : : param_spec->name);
470 : :
471 : 193 : Gjs::AutoGValue gvalue(G_PARAM_SPEC_VALUE_TYPE(param_spec));
472 [ + + ]: 193 : if (!gjs_value_to_g_value(cx, value, &gvalue))
473 : 7 : return false;
474 : :
475 : 186 : g_object_set_property(m_ptr, param_spec->name, &gvalue);
476 : :
477 : 186 : return true;
478 : 193 : }
479 : :
480 : 1 : bool ObjectBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) {
481 [ - + - + ]: 1 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
482 : :
483 : : JS::RootedString name(cx,
484 : 1 : gjs_dynamic_property_private_slot(&args.callee()).toString());
485 : :
486 : 2 : std::string fullName{priv->format_name() + "[" + gjs_debug_string(name) +
487 : 1 : "]"};
488 : 1 : AutoProfilerLabel label(cx, "field setter", fullName.c_str());
489 : :
490 : 1 : priv->debug_jsprop("Field setter", name, obj);
491 : :
492 [ - + ]: 1 : if (priv->is_prototype())
493 : 0 : return true;
494 : : /* Ignore silently; note that this is different from what we do for
495 : : * boxed types, for historical reasons */
496 : :
497 : : /* We have to update args.rval(), because JS caches it as the property's "stored
498 : : * value" (https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/Stored_value)
499 : : * and so subsequent gets would get the stored value instead of accessing
500 : : * the field */
501 : 1 : args.rval().setUndefined();
502 : :
503 : 1 : return priv->to_instance()->field_setter_not_impl(cx, name);
504 : 1 : }
505 : :
506 : 1 : bool ObjectInstance::field_setter_not_impl(JSContext* cx,
507 : : JS::HandleString name) {
508 [ - + ]: 1 : if (!check_gobject_finalized("set GObject field on"))
509 : 0 : return true;
510 : :
511 : 1 : ObjectPrototype* proto_priv = get_prototype();
512 : 1 : GIFieldInfo* field = proto_priv->lookup_cached_field_info(cx, name);
513 : :
514 : : /* As far as I know, GI never exposes GObject instance struct fields as
515 : : * writable, so no need to implement this for the time being */
516 [ - + ]: 1 : if (g_field_info_get_flags(field) & GI_FIELD_IS_WRITABLE) {
517 : 0 : g_message("Field %s of a GObject is writable, but setting it is not "
518 : : "implemented", gjs_debug_string(name).c_str());
519 : 0 : return true;
520 : : }
521 : :
522 : 1 : return gjs_wrapper_throw_readonly_field(cx, gtype(),
523 : 1 : g_base_info_get_name(field));
524 : : }
525 : :
526 : 0 : bool ObjectPrototype::is_vfunc_unchanged(GIVFuncInfo* info) {
527 : 0 : GjsAutoError error;
528 : 0 : GType ptype = g_type_parent(m_gtype);
529 : : gpointer addr1, addr2;
530 : :
531 : 0 : addr1 = g_vfunc_info_get_address(info, m_gtype, &error);
532 [ # # ]: 0 : if (error)
533 : 0 : return false;
534 : :
535 : 0 : addr2 = g_vfunc_info_get_address(info, ptype, &error);
536 [ # # ]: 0 : if (error)
537 : 0 : return false;
538 : :
539 : 0 : return addr1 == addr2;
540 : 0 : }
541 : :
542 : 18 : [[nodiscard]] static GjsAutoVFuncInfo find_vfunc_on_parents(
543 : : GIObjectInfo* info, const char* name, bool* out_defined_by_parent) {
544 : 18 : bool defined_by_parent = false;
545 : :
546 : : /* ref the first info so that we don't destroy
547 : : * it when unrefing parents later */
548 : 18 : GjsAutoObjectInfo parent(info, GjsAutoTakeOwnership());
549 : :
550 : : /* Since it isn't possible to override a vfunc on
551 : : * an interface without reimplementing it, we don't need
552 : : * to search the parent types when looking for a vfunc. */
553 : : GjsAutoVFuncInfo vfunc =
554 : 18 : g_object_info_find_vfunc_using_interfaces(parent, name, nullptr);
555 [ + + + + : 51 : while (!vfunc && parent) {
+ + ]
556 : 33 : parent = g_object_info_get_parent(parent);
557 [ + + ]: 33 : if (parent)
558 : 16 : vfunc = g_object_info_find_vfunc(parent, name);
559 : :
560 : 33 : defined_by_parent = true;
561 : : }
562 : :
563 [ + - ]: 18 : if (out_defined_by_parent)
564 : 18 : *out_defined_by_parent = defined_by_parent;
565 : :
566 : 18 : return vfunc;
567 : 18 : }
568 : :
569 : : /* Taken from GLib */
570 : 3430 : static void canonicalize_key(const GjsAutoChar& key) {
571 [ + + ]: 42639 : for (char* p = key; *p != 0; p++) {
572 : 39209 : char c = *p;
573 : :
574 [ + + + + : 39209 : if (c != '-' && (c < '0' || c > '9') && (c < 'A' || c > 'Z') &&
+ + + + +
- + + ]
575 [ - + ]: 35204 : (c < 'a' || c > 'z'))
576 : 2814 : *p = '-';
577 : : }
578 : 3430 : }
579 : :
580 : : /* @name must already be canonicalized */
581 : 1369 : [[nodiscard]] static bool is_ginterface_property_name(GIInterfaceInfo* info,
582 : : const char* name) {
583 : 1369 : int n_props = g_interface_info_get_n_properties(info);
584 : 1369 : GjsAutoPropertyInfo prop_info;
585 : :
586 [ + + ]: 1512 : for (int ix = 0; ix < n_props; ix++) {
587 : 148 : prop_info = g_interface_info_get_property(info, ix);
588 [ + + ]: 148 : if (strcmp(name, prop_info.name()) == 0)
589 : 5 : break;
590 : 143 : prop_info.reset();
591 : : }
592 : :
593 : 1369 : return !!prop_info;
594 : 1369 : }
595 : :
596 : 246 : bool ObjectPrototype::lazy_define_gobject_property(
597 : : JSContext* cx, JS::HandleObject obj, JS::HandleId id, GParamSpec* pspec,
598 : : bool* resolved, const char* name) {
599 : 246 : bool found = false;
600 [ - + ]: 246 : if (!JS_AlreadyHasOwnPropertyById(cx, obj, id, &found))
601 : 0 : return false;
602 [ - + ]: 246 : if (found) {
603 : : /* Already defined, so *resolved = false because we didn't just
604 : : * define it */
605 : 0 : *resolved = false;
606 : 0 : return true;
607 : : }
608 : :
609 : 246 : debug_jsprop("Defining lazy GObject property", id, obj);
610 : :
611 : : // Do not fetch JS overridden properties from GObject, to avoid
612 : : // infinite recursion.
613 [ - + ]: 246 : if (g_param_spec_get_qdata(pspec, ObjectBase::custom_property_quark())) {
614 : 0 : *resolved = false;
615 : 0 : return true;
616 : : }
617 : :
618 : 246 : JS::RootedValue private_value{cx, JS::PrivateValue(pspec)};
619 [ - + ]: 246 : if (!gjs_define_property_dynamic(
620 : : cx, obj, name, id, "gobject_prop", &ObjectBase::prop_getter,
621 : : &ObjectBase::prop_setter, private_value,
622 : : // Make property configurable so that interface properties can be
623 : : // overridden by GObject.ParamSpec.override in the class that
624 : : // implements them
625 : : GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT))
626 : 0 : return false;
627 : :
628 : 246 : *resolved = true;
629 : 246 : return true;
630 : 246 : }
631 : :
632 : : // An object shared by the getter and setter to store the interface' prototype
633 : : // and overrides.
634 : : static constexpr size_t ACCESSOR_SLOT = 0;
635 : :
636 : 355 : static bool interface_getter(JSContext* cx, unsigned argc, JS::Value* vp) {
637 : 355 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
638 : :
639 : : JS::RootedValue v_accessor(
640 : 355 : cx, js::GetFunctionNativeReserved(&args.callee(), ACCESSOR_SLOT));
641 : 355 : g_assert(v_accessor.isObject() && "accessor must be an object");
642 : 355 : JS::RootedObject accessor(cx, &v_accessor.toObject());
643 : :
644 : 355 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
645 : :
646 : : // Check if an override value has been set
647 : 355 : bool has_override_symbol = false;
648 [ - + ]: 355 : if (!JS_HasPropertyById(cx, accessor, atoms.override(),
649 : : &has_override_symbol))
650 : 0 : return false;
651 : :
652 [ + + ]: 355 : if (has_override_symbol) {
653 : 45 : JS::RootedValue v_override_symbol(cx);
654 [ - + ]: 45 : if (!JS_GetPropertyById(cx, accessor, atoms.override(),
655 : : &v_override_symbol))
656 : 0 : return false;
657 : 45 : g_assert(v_override_symbol.isSymbol() &&
658 : : "override symbol must be a symbol");
659 : 45 : JS::RootedSymbol override_symbol(cx, v_override_symbol.toSymbol());
660 : 45 : JS::RootedId override_id(cx, JS::PropertyKey::Symbol(override_symbol));
661 : :
662 : 45 : JS::RootedObject this_obj(cx);
663 [ - + ]: 45 : if (!args.computeThis(cx, &this_obj))
664 : 0 : return false;
665 : :
666 : 45 : bool has_override = false;
667 [ - + ]: 45 : if (!JS_HasPropertyById(cx, this_obj, override_id, &has_override))
668 : 0 : return false;
669 : :
670 [ + + ]: 45 : if (has_override)
671 : 40 : return JS_GetPropertyById(cx, this_obj, override_id, args.rval());
672 [ + + + + : 165 : }
+ + + + ]
673 : :
674 : 315 : JS::RootedValue v_prototype(cx);
675 [ - + ]: 315 : if (!JS_GetPropertyById(cx, accessor, atoms.prototype(), &v_prototype))
676 : 0 : return false;
677 : 315 : g_assert(v_prototype.isObject() && "prototype must be an object");
678 : :
679 : 315 : JS::RootedObject prototype(cx, &v_prototype.toObject());
680 : 315 : JS::RootedId id(cx, JS::PropertyKey::NonIntAtom(JS_GetFunctionId(
681 : 630 : JS_GetObjectFunction(&args.callee()))));
682 : 315 : return JS_GetPropertyById(cx, prototype, id, args.rval());
683 : 355 : }
684 : :
685 : 74 : static bool interface_setter(JSContext* cx, unsigned argc, JS::Value* vp) {
686 : 74 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
687 : : JS::RootedValue v_accessor(
688 : 74 : cx, js::GetFunctionNativeReserved(&args.callee(), ACCESSOR_SLOT));
689 : 74 : JS::RootedObject accessor(cx, &v_accessor.toObject());
690 : : JS::RootedString description(
691 : 74 : cx, JS_AtomizeAndPinString(cx, "Private interface function setter"));
692 : 74 : JS::RootedSymbol symbol(cx, JS::NewSymbol(cx, description));
693 : 74 : JS::RootedValue v_symbol(cx, JS::SymbolValue(symbol));
694 : :
695 : 74 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
696 [ - + ]: 74 : if (!JS_SetPropertyById(cx, accessor, atoms.override(), v_symbol))
697 : 0 : return false;
698 : :
699 : 74 : args.rval().setUndefined();
700 : :
701 : 74 : JS::RootedObject this_obj(cx);
702 [ - + ]: 74 : if (!args.computeThis(cx, &this_obj))
703 : 0 : return false;
704 : 74 : JS::RootedId override_id(cx, JS::PropertyKey::Symbol(symbol));
705 : :
706 : 74 : return JS_SetPropertyById(cx, this_obj, override_id, args[0]);
707 : 74 : }
708 : :
709 : 769 : static bool resolve_on_interface_prototype(JSContext* cx,
710 : : GIInterfaceInfo* iface_info,
711 : : JS::HandleId identifier,
712 : : JS::HandleObject class_prototype,
713 : : bool* found) {
714 : 769 : GType gtype = g_base_info_get_type(iface_info);
715 : : JS::RootedObject interface_prototype(
716 : 769 : cx, gjs_lookup_object_prototype_from_info(cx, iface_info, gtype));
717 [ - + ]: 769 : if (!interface_prototype)
718 : 0 : return false;
719 : :
720 : 769 : bool exists = false;
721 [ - + ]: 769 : if (!JS_HasPropertyById(cx, interface_prototype, identifier, &exists))
722 : 0 : return false;
723 : :
724 : : // If the property doesn't exist on the interface prototype, we don't need
725 : : // to perform this trick.
726 [ + + ]: 769 : if (!exists) {
727 : 661 : *found = false;
728 : 661 : return true;
729 : : }
730 : :
731 : : // Lazily define a property on the class prototype if a property
732 : : // of that name is present on an interface prototype that the class
733 : : // implements.
734 : : //
735 : : // Define a property of the same name on the class prototype, with a
736 : : // getter and setter. This is so that e.g. file.dup() calls the _current_
737 : : // value of Gio.File.prototype.dup(), not the original, so that it can be
738 : : // overridden (or monkeypatched).
739 : : //
740 : : // The setter (interface_setter() above) marks the property as overridden if
741 : : // it is set from user code. The getter (interface_getter() above) proxies
742 : : // the interface prototype's property, unless it was marked as overridden.
743 : : //
744 : : // Store the identifier in the getter and setter function's ID slots for
745 : : // to enable looking up the original value on the interface prototype.
746 : : JS::RootedObject getter(
747 : 108 : cx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
748 : 108 : cx, interface_getter, 0, 0, identifier)));
749 [ - + ]: 108 : if (!getter)
750 : 0 : return false;
751 : :
752 : : JS::RootedObject setter(
753 : 108 : cx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
754 : 108 : cx, interface_setter, 1, 0, identifier)));
755 [ - + ]: 108 : if (!setter)
756 : 0 : return false;
757 : :
758 : 108 : JS::RootedObject accessor(cx, JS_NewPlainObject(cx));
759 [ - + ]: 108 : if (!accessor)
760 : 0 : return false;
761 : :
762 : 108 : js::SetFunctionNativeReserved(setter, ACCESSOR_SLOT,
763 : 108 : JS::ObjectValue(*accessor));
764 : 108 : js::SetFunctionNativeReserved(getter, ACCESSOR_SLOT,
765 : 108 : JS::ObjectValue(*accessor));
766 : :
767 : 108 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
768 : 108 : JS::RootedValue v_prototype(cx, JS::ObjectValue(*interface_prototype));
769 [ - + ]: 108 : if (!JS_SetPropertyById(cx, accessor, atoms.prototype(), v_prototype))
770 : 0 : return false;
771 : :
772 : : // Create a new descriptor with our getter and setter, that is configurable
773 : : // and enumerable, because GObject may need to redefine it later.
774 : 108 : JS::PropertyAttributes attrs{JS::PropertyAttribute::Configurable,
775 : : JS::PropertyAttribute::Enumerable};
776 : : JS::Rooted<JS::PropertyDescriptor> desc(
777 : 108 : cx, JS::PropertyDescriptor::Accessor(getter, setter, attrs));
778 : :
779 [ - + ]: 108 : if (!JS_DefinePropertyById(cx, class_prototype, identifier, desc))
780 : 0 : return false;
781 : :
782 : 108 : *found = true;
783 : 108 : return true;
784 : 769 : }
785 : :
786 : 2179 : bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
787 : : JS::HandleId id, bool* resolved,
788 : : const char* name,
789 : : ResolveWhat resolve_props) {
790 : : guint n_interfaces;
791 : : guint i;
792 : :
793 : 2179 : GjsAutoChar canonical_name;
794 [ + + ]: 2179 : if (resolve_props == ConsiderMethodsAndProperties) {
795 : : // Optimization: GObject property names must start with a letter
796 [ + + ]: 356 : if (g_ascii_isalpha(name[0])) {
797 : 183 : canonical_name = gjs_hyphen_from_camel(name);
798 : 183 : canonicalize_key(canonical_name);
799 : : }
800 : : }
801 : :
802 : : GIInterfaceInfo** interfaces;
803 : 2179 : g_irepository_get_object_gtype_interfaces(nullptr, m_gtype, &n_interfaces,
804 : : &interfaces);
805 : :
806 : : /* Fallback to GType system for non custom GObjects with no GI information
807 : : */
808 [ + + + - : 2179 : if (canonical_name && G_TYPE_IS_CLASSED(m_gtype) && !is_custom_js_class()) {
+ + + + ]
809 : 51 : GjsAutoTypeClass<GObjectClass> oclass(m_gtype);
810 : :
811 [ + + ]: 51 : if (GParamSpec* pspec =
812 : 51 : g_object_class_find_property(oclass, canonical_name))
813 : 4 : return lazy_define_gobject_property(cx, obj, id, pspec, resolved,
814 : 4 : name);
815 : :
816 [ + + ]: 92 : for (i = 0; i < n_interfaces; i++) {
817 : : GType iface_gtype =
818 : 45 : g_registered_type_info_get_g_type(interfaces[i]);
819 [ + - ]: 45 : if (!G_TYPE_IS_CLASSED(iface_gtype))
820 : 45 : continue;
821 : :
822 : 0 : GjsAutoTypeClass<GObjectClass> iclass(iface_gtype);
823 : :
824 [ # # ]: 0 : if (GParamSpec* pspec =
825 : 0 : g_object_class_find_property(iclass, canonical_name))
826 : 0 : return lazy_define_gobject_property(cx, obj, id, pspec,
827 : 0 : resolved, name);
828 [ # # ]: 0 : }
829 [ + + ]: 51 : }
830 : :
831 [ + + ]: 2175 : for (i = 0; i < n_interfaces; i++) {
832 : 692 : GIInterfaceInfo* iface_info = interfaces[i];
833 : : GjsAutoFunctionInfo method_info =
834 : 692 : g_interface_info_find_method(iface_info, name);
835 [ + + ]: 692 : if (method_info) {
836 [ + - ]: 29 : if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) {
837 : 29 : bool found = false;
838 [ - + ]: 29 : if (!resolve_on_interface_prototype(cx, iface_info, id, obj,
839 : : &found))
840 : 0 : return false;
841 : :
842 : : // Fallback to defining the function from type info...
843 [ - + - - : 29 : if (!found &&
- + ]
844 : 0 : !gjs_define_function(cx, obj, m_gtype, method_info))
845 : 0 : return false;
846 : :
847 : 29 : *resolved = true;
848 : 29 : return true;
849 : : }
850 : : }
851 : :
852 : :
853 : : /* If the name refers to a GObject property, lazily define the property
854 : : * in JS as we do below in the real resolve hook. We ignore fields here
855 : : * because I don't think interfaces can have fields */
856 [ + + + + : 711 : if (canonical_name &&
+ + ]
857 : 711 : is_ginterface_property_name(iface_info, canonical_name)) {
858 : 3 : GjsAutoTypeClass<GObjectClass> oclass(m_gtype);
859 : : // unowned
860 : 3 : GParamSpec* pspec = g_object_class_find_property(
861 : : oclass, canonical_name); // unowned
862 [ + + - + ]: 3 : if (pspec && pspec->owner_type == m_gtype) {
863 : 0 : return lazy_define_gobject_property(cx, obj, id, pspec,
864 : 0 : resolved, name);
865 : : }
866 [ + - ]: 3 : }
867 : :
868 : 663 : return resolve_on_interface_prototype(cx, iface_info, id, obj,
869 : 663 : resolved);
870 : 692 : }
871 : :
872 : 1483 : *resolved = false;
873 : 1483 : return true;
874 : 2179 : }
875 : :
876 : 3875 : [[nodiscard]] static GjsAutoChar get_gobject_property_name(GIObjectInfo* info,
877 : : const char* name) {
878 : : // Optimization: GObject property names must start with a letter
879 [ + + ]: 3875 : if (!g_ascii_isalpha(name[0]))
880 : 628 : return nullptr;
881 : :
882 : 3247 : int n_props = g_object_info_get_n_properties(info);
883 : 3247 : int n_ifaces = g_object_info_get_n_interfaces(info);
884 : : int ix;
885 : :
886 : 3247 : GjsAutoChar canonical_name = gjs_hyphen_from_camel(name);
887 : 3247 : canonicalize_key(canonical_name);
888 : :
889 [ + + ]: 17445 : for (ix = 0; ix < n_props; ix++) {
890 : 14438 : GjsAutoPropertyInfo prop_info = g_object_info_get_property(info, ix);
891 [ + + ]: 14438 : if (strcmp(canonical_name, prop_info.name()) == 0)
892 : 240 : return canonical_name;
893 [ + + ]: 14438 : }
894 : :
895 [ + + ]: 4326 : for (ix = 0; ix < n_ifaces; ix++) {
896 : 1321 : GjsAutoInterfaceInfo iface_info = g_object_info_get_interface(info, ix);
897 [ + + ]: 1321 : if (is_ginterface_property_name(iface_info, canonical_name))
898 : 2 : return canonical_name;
899 [ + + ]: 1321 : }
900 : 3005 : return nullptr;
901 : 3247 : }
902 : :
903 : : // Override of GIWrapperBase::id_is_never_lazy()
904 : 0 : bool ObjectBase::id_is_never_lazy(jsid name, const GjsAtoms& atoms) {
905 : : // Keep this list in sync with ObjectBase::proto_properties and
906 : : // ObjectBase::proto_methods. However, explicitly do not include
907 : : // connect() in it, because there are a few cases where the lazy property
908 : : // should override the predefined one, such as Gio.Cancellable.connect().
909 [ # # # # : 0 : return name == atoms.init() || name == atoms.connect_after() ||
# # ]
910 : 0 : name == atoms.emit();
911 : : }
912 : :
913 : 14374 : bool ObjectPrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
914 : : JS::HandleId id, bool* resolved) {
915 [ + + ]: 14374 : if (m_unresolvable_cache.has(id)) {
916 : 6556 : *resolved = false;
917 : 6556 : return true;
918 : : }
919 : :
920 : 7818 : JS::UniqueChars prop_name;
921 [ - + ]: 7818 : if (!gjs_get_string_id(context, id, &prop_name))
922 : 0 : return false;
923 [ + + ]: 7818 : if (!prop_name) {
924 : 3586 : *resolved = false;
925 : 3586 : return true; // not resolved, but no error
926 : : }
927 : :
928 [ - + ]: 4232 : if (!uncached_resolve(context, obj, id, prop_name.get(), resolved))
929 : 0 : return false;
930 : :
931 [ + + - + : 4232 : if (!*resolved && !m_unresolvable_cache.putNew(id)) {
- + ]
932 : 0 : JS_ReportOutOfMemory(context);
933 : 0 : return false;
934 : : }
935 : :
936 : 4232 : return true;
937 : 7818 : }
938 : :
939 : 4232 : bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj,
940 : : JS::HandleId id, const char* name,
941 : : bool* resolved) {
942 : : // If we have no GIRepository information (we're a JS GObject subclass or an
943 : : // internal non-introspected class such as GLocalFile), we need to look at
944 : : // exposing interfaces. Look up our interfaces through GType data, and then
945 : : // hope that *those* are introspectable.
946 [ + + ]: 4232 : if (!info())
947 : 356 : return resolve_no_info(context, obj, id, resolved, name,
948 : 356 : ConsiderMethodsAndProperties);
949 : :
950 [ - + + + : 3876 : if (g_str_has_prefix(name, "vfunc_")) {
+ + ]
951 : : /* The only time we find a vfunc info is when we're the base
952 : : * class that defined the vfunc. If we let regular prototype
953 : : * chaining resolve this, we'd have the implementation for the base's
954 : : * vfunc on the base class, without any other "real" implementations
955 : : * in the way. If we want to expose a "real" vfunc implementation,
956 : : * we need to go down to the parent infos and look at their VFuncInfos.
957 : : *
958 : : * This is good, but it's memory-hungry -- we would define every
959 : : * possible vfunc on every possible object, even if it's the same
960 : : * "real" vfunc underneath. Instead, only expose vfuncs that are
961 : : * different from their parent, and let prototype chaining do the
962 : : * rest.
963 : : */
964 : :
965 : 18 : const char *name_without_vfunc_ = &(name[6]); /* lifetime tied to name */
966 : : bool defined_by_parent;
967 : : GjsAutoVFuncInfo vfunc = find_vfunc_on_parents(
968 : 18 : m_info, name_without_vfunc_, &defined_by_parent);
969 [ + + ]: 18 : if (vfunc) {
970 : : /* In the event that the vfunc is unchanged, let regular
971 : : * prototypal inheritance take over. */
972 [ - + - - : 1 : if (defined_by_parent && is_vfunc_unchanged(vfunc)) {
- + ]
973 : 0 : *resolved = false;
974 : 0 : return true;
975 : : }
976 : :
977 [ - + ]: 1 : if (!gjs_define_function(context, obj, m_gtype, vfunc))
978 : 0 : return false;
979 : :
980 : 1 : *resolved = true;
981 : 1 : return true;
982 : : }
983 : :
984 : : /* If the vfunc wasn't found, fall through, back to normal
985 : : * method resolution. */
986 [ + + ]: 18 : }
987 : :
988 [ + + ]: 3875 : if (auto const& canonical_name = get_gobject_property_name(m_info, name)) {
989 : 242 : GjsAutoTypeClass<GObjectClass> gobj_class{m_gtype};
990 [ + - ]: 242 : if (GParamSpec* pspec =
991 : 242 : g_object_class_find_property(gobj_class, canonical_name))
992 : 242 : return lazy_define_gobject_property(context, obj, id, pspec,
993 : 242 : resolved, name);
994 [ - + + + ]: 4117 : }
995 : :
996 : 3633 : GjsAutoFieldInfo field_info = lookup_field_info(m_info, name);
997 [ + + ]: 3633 : if (field_info) {
998 : 5 : bool found = false;
999 [ - + ]: 5 : if (!JS_AlreadyHasOwnPropertyById(context, obj, id, &found))
1000 : 0 : return false;
1001 [ - + ]: 5 : if (found) {
1002 : 0 : *resolved = false;
1003 : 0 : return true;
1004 : : }
1005 : :
1006 : 5 : debug_jsprop("Defining lazy GObject field", id, obj);
1007 : :
1008 : 5 : unsigned flags = GJS_MODULE_PROP_FLAGS;
1009 [ + - ]: 5 : if (!(g_field_info_get_flags(field_info) & GI_FIELD_IS_WRITABLE))
1010 : 5 : flags |= JSPROP_READONLY;
1011 : :
1012 : 5 : JS::RootedString key(context, id.toString());
1013 [ - + ]: 5 : if (!m_field_cache.putNew(key, field_info.release())) {
1014 : 0 : JS_ReportOutOfMemory(context);
1015 : 0 : return false;
1016 : : }
1017 : :
1018 : 5 : JS::RootedValue private_id(context, JS::StringValue(key));
1019 [ - + ]: 5 : if (!gjs_define_property_dynamic(
1020 : : context, obj, name, id, "gobject_field",
1021 : : &ObjectBase::field_getter, &ObjectBase::field_setter,
1022 : : private_id, flags))
1023 : 0 : return false;
1024 : :
1025 : 5 : *resolved = true;
1026 : 5 : return true;
1027 : 5 : }
1028 : :
1029 : : /* find_method does not look at methods on parent classes,
1030 : : * we rely on javascript to walk up the __proto__ chain
1031 : : * and find those and define them in the right prototype.
1032 : : *
1033 : : * Note that if it isn't a method on the object, since JS
1034 : : * lacks multiple inheritance, we're sticking the iface
1035 : : * methods in the object prototype, which means there are many
1036 : : * copies of the iface methods (one per object class node that
1037 : : * introduces the iface)
1038 : : */
1039 : :
1040 : 3628 : GjsAutoBaseInfo implementor_info;
1041 : : GjsAutoFunctionInfo method_info =
1042 : : g_object_info_find_method_using_interfaces(m_info, name,
1043 : 3628 : implementor_info.out());
1044 : :
1045 : : /**
1046 : : * Search through any interfaces implemented by the GType;
1047 : : * See https://bugzilla.gnome.org/show_bug.cgi?id=632922
1048 : : * for background on why we need to do this.
1049 : : */
1050 [ + + ]: 3628 : if (!method_info)
1051 : 1823 : return resolve_no_info(context, obj, id, resolved, name,
1052 : 1823 : ConsiderOnlyMethods);
1053 : :
1054 : : #if GJS_VERBOSE_ENABLE_GI_USAGE
1055 : : _gjs_log_info_usage(method_info);
1056 : : #endif
1057 : :
1058 [ + - ]: 1805 : if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) {
1059 : 1805 : gjs_debug(GJS_DEBUG_GOBJECT,
1060 : : "Defining method %s in prototype for %s (%s.%s)",
1061 : : method_info.name(), type_name(), ns(), this->name());
1062 [ + + ]: 1805 : if (GI_IS_INTERFACE_INFO(implementor_info)) {
1063 : 77 : bool found = false;
1064 [ - + ]: 77 : if (!resolve_on_interface_prototype(context, implementor_info, id,
1065 : : obj, &found))
1066 : 0 : return false;
1067 : :
1068 : : // If the method was not found fallback to defining the function
1069 : : // from type info...
1070 [ - + - - : 77 : if (!found &&
- + ]
1071 : 0 : !gjs_define_function(context, obj, m_gtype, method_info)) {
1072 : 0 : return false;
1073 : : }
1074 [ - + ]: 1728 : } else if (!gjs_define_function(context, obj, m_gtype, method_info)) {
1075 : 0 : return false;
1076 : : }
1077 : :
1078 : 1805 : *resolved = true; /* we defined the prop in obj */
1079 : : }
1080 : :
1081 : 1805 : return true;
1082 : 3633 : }
1083 : :
1084 : 22 : bool ObjectPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject,
1085 : : JS::MutableHandleIdVector properties,
1086 : : bool only_enumerable
1087 : : [[maybe_unused]]) {
1088 : : unsigned n_interfaces;
1089 : 22 : GType* interfaces = g_type_interfaces(gtype(), &n_interfaces);
1090 : :
1091 [ - + ]: 22 : for (unsigned k = 0; k < n_interfaces; k++) {
1092 : : GjsAutoInterfaceInfo iface_info =
1093 : 0 : g_irepository_find_by_gtype(nullptr, interfaces[k]);
1094 : :
1095 [ # # ]: 0 : if (!iface_info) {
1096 : 0 : continue;
1097 : : }
1098 : :
1099 : 0 : int n_methods = g_interface_info_get_n_methods(iface_info);
1100 : 0 : int n_properties = g_interface_info_get_n_properties(iface_info);
1101 [ # # ]: 0 : if (!properties.reserve(properties.length() + n_methods +
1102 : 0 : n_properties)) {
1103 : 0 : JS_ReportOutOfMemory(cx);
1104 : 0 : return false;
1105 : : }
1106 : :
1107 : : // Methods
1108 [ # # ]: 0 : for (int i = 0; i < n_methods; i++) {
1109 : : GjsAutoFunctionInfo meth_info =
1110 : 0 : g_interface_info_get_method(iface_info, i);
1111 : 0 : GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info);
1112 : :
1113 [ # # ]: 0 : if (flags & GI_FUNCTION_IS_METHOD) {
1114 : 0 : const char* name = meth_info.name();
1115 : 0 : jsid id = gjs_intern_string_to_id(cx, name);
1116 [ # # ]: 0 : if (id.isVoid())
1117 : 0 : return false;
1118 : 0 : properties.infallibleAppend(id);
1119 : : }
1120 [ # # ]: 0 : }
1121 : :
1122 : : // Properties
1123 [ # # ]: 0 : for (int i = 0; i < n_properties; i++) {
1124 : : GjsAutoPropertyInfo prop_info =
1125 : 0 : g_interface_info_get_property(iface_info, i);
1126 : :
1127 : 0 : GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name());
1128 : :
1129 : 0 : jsid id = gjs_intern_string_to_id(cx, js_name);
1130 [ # # ]: 0 : if (id.isVoid())
1131 : 0 : return false;
1132 : 0 : properties.infallibleAppend(id);
1133 [ # # # # ]: 0 : }
1134 [ # # # ]: 0 : }
1135 : :
1136 : 22 : g_free(interfaces);
1137 : :
1138 [ + - ]: 22 : if (info()) {
1139 : 22 : int n_methods = g_object_info_get_n_methods(info());
1140 : 22 : int n_properties = g_object_info_get_n_properties(info());
1141 [ - + ]: 44 : if (!properties.reserve(properties.length() + n_methods +
1142 : 22 : n_properties)) {
1143 : 0 : JS_ReportOutOfMemory(cx);
1144 : 0 : return false;
1145 : : }
1146 : :
1147 : : // Methods
1148 [ + + ]: 1078 : for (int i = 0; i < n_methods; i++) {
1149 : 1056 : GjsAutoFunctionInfo meth_info = g_object_info_get_method(info(), i);
1150 : 1056 : GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info);
1151 : :
1152 [ + + ]: 1056 : if (flags & GI_FUNCTION_IS_METHOD) {
1153 : 858 : const char* name = meth_info.name();
1154 : 858 : jsid id = gjs_intern_string_to_id(cx, name);
1155 [ - + ]: 858 : if (id.isVoid())
1156 : 0 : return false;
1157 : 858 : properties.infallibleAppend(id);
1158 : : }
1159 [ + - ]: 1056 : }
1160 : :
1161 : : // Properties
1162 [ + + ]: 176 : for (int i = 0; i < n_properties; i++) {
1163 : : GjsAutoPropertyInfo prop_info =
1164 : 154 : g_object_info_get_property(info(), i);
1165 : :
1166 : 154 : GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name());
1167 : 154 : jsid id = gjs_intern_string_to_id(cx, js_name);
1168 [ - + ]: 154 : if (id.isVoid())
1169 : 0 : return false;
1170 : 154 : properties.infallibleAppend(id);
1171 [ + - + - ]: 154 : }
1172 : : }
1173 : :
1174 : 22 : return true;
1175 : : }
1176 : :
1177 : : /* Set properties from args to constructor (args[0] is supposed to be
1178 : : * a hash) */
1179 : 262 : bool ObjectPrototype::props_to_g_parameters(
1180 : : JSContext* context, GjsAutoTypeClass<GObjectClass> const& object_class,
1181 : : JS::HandleObject props, std::vector<const char*>* names,
1182 : : AutoGValueVector* values) {
1183 : : size_t ix, length;
1184 : 262 : JS::RootedId prop_id(context);
1185 : 262 : JS::RootedValue value(context);
1186 : 262 : JS::Rooted<JS::IdVector> ids(context, context);
1187 : 262 : std::unordered_set<GParamSpec*> visited_params;
1188 [ - + ]: 262 : if (!JS_Enumerate(context, props, &ids)) {
1189 : 0 : gjs_throw(context, "Failed to create property iterator for object props hash");
1190 : 0 : return false;
1191 : : }
1192 : :
1193 : 262 : values->reserve(ids.length());
1194 [ + + ]: 858 : for (ix = 0, length = ids.length(); ix < length; ix++) {
1195 : : /* ids[ix] is reachable because props is rooted, but require_property
1196 : : * doesn't know that */
1197 : 605 : prop_id = ids[ix];
1198 : :
1199 [ - + ]: 605 : if (!prop_id.isString())
1200 : 0 : return gjs_wrapper_throw_nonexistent_field(
1201 : 0 : context, m_gtype, gjs_debug_id(prop_id).c_str());
1202 : :
1203 : 605 : JS::RootedString js_prop_name(context, prop_id.toString());
1204 : : GParamSpec* param_spec =
1205 : 605 : find_param_spec_from_id(context, object_class, js_prop_name);
1206 [ + + ]: 605 : if (!param_spec)
1207 : 2 : return false;
1208 : :
1209 [ - + ]: 603 : if (visited_params.find(param_spec) != visited_params.end())
1210 : 0 : continue;
1211 : 603 : visited_params.insert(param_spec);
1212 : :
1213 [ - + ]: 603 : if (!JS_GetPropertyById(context, props, prop_id, &value))
1214 : 0 : return false;
1215 [ + + ]: 603 : if (value.isUndefined()) {
1216 : 1 : gjs_throw(context, "Invalid value 'undefined' for property %s in "
1217 : : "object initializer.", param_spec->name);
1218 : 1 : return false;
1219 : : }
1220 : :
1221 [ - + ]: 602 : if (!(param_spec->flags & G_PARAM_WRITABLE))
1222 : 0 : return gjs_wrapper_throw_readonly_field(context, m_gtype,
1223 : 0 : param_spec->name);
1224 : : /* prevent setting the prop even in JS */
1225 : :
1226 : : Gjs::AutoGValue& gvalue =
1227 : 602 : values->emplace_back(G_PARAM_SPEC_VALUE_TYPE(param_spec));
1228 [ + + ]: 602 : if (!gjs_value_to_g_value(context, value, &gvalue))
1229 : 6 : return false;
1230 : :
1231 : 596 : names->push_back(param_spec->name); // owned by GParamSpec
1232 [ + + - ]: 605 : }
1233 : :
1234 : 253 : return true;
1235 : 262 : }
1236 : :
1237 : 157 : void ObjectInstance::wrapped_gobj_dispose_notify(
1238 : : void* data, GObject* where_the_object_was GJS_USED_VERBOSE_LIFECYCLE) {
1239 : 157 : auto *priv = static_cast<ObjectInstance *>(data);
1240 : 157 : priv->gobj_dispose_notify();
1241 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Wrapped GObject %p disposed",
1242 : : where_the_object_was);
1243 : 157 : }
1244 : :
1245 : 157 : void ObjectInstance::track_gobject_finalization() {
1246 : 157 : auto quark = ObjectBase::disposed_quark();
1247 : 157 : g_object_steal_qdata(m_ptr, quark);
1248 : 157 : g_object_set_qdata_full(m_ptr, quark, this, [](void* data) {
1249 : 18 : auto* self = static_cast<ObjectInstance*>(data);
1250 : 18 : self->m_gobj_finalized = true;
1251 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Wrapped GObject %p finalized",
1252 : : self->m_ptr.get());
1253 : 18 : });
1254 : 157 : }
1255 : :
1256 : 162 : void ObjectInstance::ignore_gobject_finalization() {
1257 : 162 : auto quark = ObjectBase::disposed_quark();
1258 [ + + ]: 162 : if (g_object_get_qdata(m_ptr, quark) == this) {
1259 : 139 : g_object_steal_qdata(m_ptr, quark);
1260 : 139 : g_object_set_qdata(m_ptr, quark, gjs_int_to_pointer(DISPOSED_OBJECT));
1261 : : }
1262 : 162 : }
1263 : :
1264 : : void
1265 : 157 : ObjectInstance::gobj_dispose_notify(void)
1266 : : {
1267 : 157 : m_gobj_disposed = true;
1268 : :
1269 : 157 : unset_object_qdata();
1270 : 157 : track_gobject_finalization();
1271 : :
1272 [ + + ]: 157 : if (m_uses_toggle_ref) {
1273 : 70 : g_object_ref(m_ptr.get());
1274 : 70 : g_object_remove_toggle_ref(m_ptr, wrapped_gobj_toggle_notify, this);
1275 : 70 : ToggleQueue::get_default()->cancel(this);
1276 : 70 : wrapped_gobj_toggle_notify(this, m_ptr, TRUE);
1277 : 70 : m_uses_toggle_ref = false;
1278 : : }
1279 : :
1280 [ + + ]: 157 : if (GjsContextPrivate::from_current_context()->is_owner_thread())
1281 : 154 : discard_wrapper();
1282 : 157 : }
1283 : :
1284 : 476 : void ObjectInstance::remove_wrapped_gobjects_if(
1285 : : const ObjectInstance::Predicate& predicate,
1286 : : const ObjectInstance::Action& action) {
1287 : : // Note: remove_if() does not actually remove elements, just reorders them
1288 : : // and returns a start iterator of elements to remove
1289 : 952 : s_wrapped_gobject_list.erase(
1290 : 476 : std::remove_if(s_wrapped_gobject_list.begin(),
1291 : : s_wrapped_gobject_list.end(),
1292 : 952 : ([predicate, action](ObjectInstance* link) {
1293 [ + + ]: 2326 : if (predicate(link)) {
1294 : 1231 : action(link);
1295 : 1231 : return true;
1296 : : }
1297 : 1095 : return false;
1298 : : })),
1299 : 476 : s_wrapped_gobject_list.end());
1300 : 476 : }
1301 : :
1302 : : /*
1303 : : * ObjectInstance::context_dispose_notify:
1304 : : *
1305 : : * Callback called when the #GjsContext is disposed. It just calls
1306 : : * handle_context_dispose() on every ObjectInstance.
1307 : : */
1308 : 240 : void ObjectInstance::context_dispose_notify(void*, GObject* where_the_object_was
1309 : : [[maybe_unused]]) {
1310 : 240 : std::for_each(s_wrapped_gobject_list.begin(), s_wrapped_gobject_list.end(),
1311 : : std::mem_fn(&ObjectInstance::handle_context_dispose));
1312 : 240 : }
1313 : :
1314 : : /*
1315 : : * ObjectInstance::handle_context_dispose:
1316 : : *
1317 : : * Called on each existing ObjectInstance when the #GjsContext is disposed.
1318 : : */
1319 : 1027 : void ObjectInstance::handle_context_dispose(void) {
1320 [ + + ]: 1027 : if (wrapper_is_rooted()) {
1321 : 140 : debug_lifecycle("Was rooted, but unrooting due to GjsContext dispose");
1322 : 140 : discard_wrapper();
1323 : : }
1324 : 1027 : }
1325 : :
1326 : : void
1327 : 1604 : ObjectInstance::toggle_down(void)
1328 : : {
1329 : 1604 : debug_lifecycle("Toggle notify DOWN");
1330 : :
1331 : : /* Change to weak ref so the wrapper-wrappee pair can be
1332 : : * collected by the GC
1333 : : */
1334 [ + + ]: 1604 : if (wrapper_is_rooted()) {
1335 : 1466 : debug_lifecycle("Unrooting wrapper");
1336 : 1466 : GjsContextPrivate* gjs = GjsContextPrivate::from_current_context();
1337 : 1466 : switch_to_unrooted(gjs->context());
1338 : :
1339 : : /* During a GC, the collector asks each object which other
1340 : : * objects that it wants to hold on to so if there's an entire
1341 : : * section of the heap graph that's not connected to anything
1342 : : * else, and not reachable from the root set, then it can be
1343 : : * trashed all at once.
1344 : : *
1345 : : * GObjects, however, don't work like that, there's only a
1346 : : * reference count but no notion of who owns the reference so,
1347 : : * a JS object that's wrapping a GObject is unconditionally held
1348 : : * alive as long as the GObject has >1 references.
1349 : : *
1350 : : * Since we cannot know how many more wrapped GObjects are going
1351 : : * be marked for garbage collection after the owner is destroyed,
1352 : : * always queue a garbage collection when a toggle reference goes
1353 : : * down.
1354 : : */
1355 [ + - ]: 1466 : if (!gjs->destroying())
1356 : 1466 : gjs->schedule_gc();
1357 : : }
1358 : 1604 : }
1359 : :
1360 : : void
1361 : 877 : ObjectInstance::toggle_up(void)
1362 : : {
1363 [ + - + + : 877 : if (G_UNLIKELY(!m_ptr || m_gobj_disposed || m_gobj_finalized)) {
- + + + ]
1364 : 1 : if (m_ptr) {
1365 : : gjs_debug_lifecycle(
1366 : : GJS_DEBUG_GOBJECT,
1367 : : "Avoid to toggle up a wrapper for a %s object: %p (%s)",
1368 : : m_gobj_finalized ? "finalized" : "disposed", m_ptr.get(),
1369 : : g_type_name(gtype()));
1370 : : } else {
1371 : : gjs_debug_lifecycle(
1372 : : GJS_DEBUG_GOBJECT,
1373 : : "Avoid to toggle up a wrapper for a released %s object (%p)",
1374 : : g_type_name(gtype()), this);
1375 : : }
1376 : 1 : return;
1377 : : }
1378 : :
1379 : : /* We need to root the JSObject associated with the passed in GObject so it
1380 : : * doesn't get garbage collected (and lose any associated javascript state
1381 : : * such as custom properties).
1382 : : */
1383 [ - + ]: 876 : if (!has_wrapper()) /* Object already GC'd */
1384 : 0 : return;
1385 : :
1386 : 876 : debug_lifecycle("Toggle notify UP");
1387 : :
1388 : : /* Change to strong ref so the wrappee keeps the wrapper alive
1389 : : * in case the wrapper has data in it that the app cares about
1390 : : */
1391 [ + + ]: 876 : if (!wrapper_is_rooted()) {
1392 : : // FIXME: thread the context through somehow. Maybe by looking up the
1393 : : // realm that obj belongs to.
1394 : 875 : debug_lifecycle("Rooting wrapper");
1395 : 875 : auto* cx = GjsContextPrivate::from_current_context()->context();
1396 : 875 : switch_to_rooted(cx);
1397 : : }
1398 : : }
1399 : :
1400 : 9 : static void toggle_handler(ObjectInstance* self,
1401 : : ToggleQueue::Direction direction) {
1402 [ + + - ]: 9 : switch (direction) {
1403 : 4 : case ToggleQueue::UP:
1404 : 4 : self->toggle_up();
1405 : 4 : break;
1406 : 5 : case ToggleQueue::DOWN:
1407 : 5 : self->toggle_down();
1408 : 5 : break;
1409 : 0 : default:
1410 : : g_assert_not_reached();
1411 : : }
1412 : 9 : }
1413 : :
1414 : 2529 : void ObjectInstance::wrapped_gobj_toggle_notify(void* instance, GObject*,
1415 : : gboolean is_last_ref) {
1416 : : bool is_main_thread;
1417 : : bool toggle_up_queued, toggle_down_queued;
1418 : 2529 : auto* self = static_cast<ObjectInstance*>(instance);
1419 : :
1420 : 2529 : GjsContextPrivate* gjs = GjsContextPrivate::from_current_context();
1421 [ - + ]: 2529 : if (gjs->destroying()) {
1422 : : /* Do nothing here - we're in the process of disassociating
1423 : : * the objects.
1424 : : */
1425 : 0 : return;
1426 : : }
1427 : :
1428 : : /* We only want to touch javascript from one thread.
1429 : : * If we're not in that thread, then we need to defer processing
1430 : : * to it.
1431 : : * In case we're toggling up (and thus rooting the JS object) we
1432 : : * also need to take care if GC is running. The marking side
1433 : : * of it is taken care by JS::Heap, which we use in GjsMaybeOwned,
1434 : : * so we're safe. As for sweeping, it is too late: the JS object
1435 : : * is dead, and attempting to keep it alive would soon crash
1436 : : * the process. Plus, if we touch the JSAPI from another thread, libmozjs
1437 : : * aborts in most cases when in debug mode.
1438 : : * Thus, we drain the toggle queue when GC starts, in order to
1439 : : * prevent this from happening.
1440 : : * In practice, a toggle up during JS finalize can only happen
1441 : : * for temporary refs/unrefs of objects that are garbage anyway,
1442 : : * because JS code is never invoked while the finalizers run
1443 : : * and C code needs to clean after itself before it returns
1444 : : * from dispose()/finalize().
1445 : : * On the other hand, toggling down is a lot simpler, because
1446 : : * we're creating more garbage. So we just unroot the object, make it a
1447 : : * weak pointer, and wait for the next GC cycle.
1448 : : *
1449 : : * Note that one would think that toggling up only happens
1450 : : * in the main thread (because toggling up is the result of
1451 : : * the JS object, previously visible only to JS code, becoming
1452 : : * visible to the refcounted C world), but because of weird
1453 : : * weak singletons like g_bus_get_sync() objects can see toggle-ups
1454 : : * from different threads too.
1455 : : */
1456 : 2529 : is_main_thread = gjs->is_owner_thread();
1457 : :
1458 : 2529 : auto toggle_queue = ToggleQueue::get_default();
1459 : 2529 : std::tie(toggle_down_queued, toggle_up_queued) =
1460 : 5058 : toggle_queue->is_queued(self);
1461 [ + + + + ]: 2529 : bool anything_queued = toggle_up_queued || toggle_down_queued;
1462 : :
1463 [ + + ]: 2529 : if (is_last_ref) {
1464 : : /* We've transitions from 2 -> 1 references,
1465 : : * The JSObject is rooted and we need to unroot it so it
1466 : : * can be garbage collected
1467 : : */
1468 [ + + + + ]: 1626 : if (is_main_thread && !anything_queued) {
1469 : 1599 : self->toggle_down();
1470 : : } else {
1471 : 27 : toggle_queue->enqueue(self, ToggleQueue::DOWN, toggle_handler);
1472 : : }
1473 : : } else {
1474 : : /* We've transitioned from 1 -> 2 references.
1475 : : *
1476 : : * The JSObject associated with the gobject is not rooted,
1477 : : * but it needs to be. We'll root it.
1478 : : */
1479 [ + + + + : 1783 : if (is_main_thread && !anything_queued &&
+ + ]
1480 [ + + ]: 880 : !JS::RuntimeHeapIsCollecting()) {
1481 : 873 : self->toggle_up();
1482 : : } else {
1483 : 30 : toggle_queue->enqueue(self, ToggleQueue::UP, toggle_handler);
1484 : : }
1485 : : }
1486 : 2529 : }
1487 : :
1488 : : void
1489 : 1545 : ObjectInstance::release_native_object(void)
1490 : : {
1491 : : static GType gdksurface_type = 0;
1492 : :
1493 : 1545 : discard_wrapper();
1494 : :
1495 [ + + ]: 1545 : if (m_gobj_finalized) {
1496 : 19 : g_critical(
1497 : : "Object %p of type %s has been finalized while it was still "
1498 : : "owned by gjs, this is due to invalid memory management.",
1499 : : m_ptr.get(), g_type_name(gtype()));
1500 : 19 : m_ptr.release();
1501 : 19 : return;
1502 : : }
1503 : :
1504 : 1526 : if (m_ptr)
1505 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Releasing native object %s %p",
1506 : : g_type_name(gtype()), m_ptr.get());
1507 : :
1508 [ + + ]: 1526 : if (m_gobj_disposed)
1509 : 162 : ignore_gobject_finalization();
1510 : :
1511 [ + + + - ]: 1526 : if (m_uses_toggle_ref && !m_gobj_disposed) {
1512 : 661 : g_object_remove_toggle_ref(m_ptr.release(), wrapped_gobj_toggle_notify,
1513 : : this);
1514 : 661 : return;
1515 : : }
1516 : :
1517 : : // Unref the object. Handle any special cases for destruction here
1518 [ + + ]: 865 : if (m_ptr->ref_count == 1) {
1519 : : // Quickest way to check for GdkSurface if Gdk has been loaded?
1520 : : // surface_type may be 0 if Gdk not loaded. The type may be a private
1521 : : // type and not have introspection info.
1522 [ + + ]: 788 : if (!gdksurface_type)
1523 : 715 : gdksurface_type = g_type_from_name("GdkSurface");
1524 [ + + + - : 788 : if (gdksurface_type && g_type_is_a(gtype(), gdksurface_type)) {
- + - + ]
1525 : 0 : GObject* ptr = m_ptr.release();
1526 : :
1527 : : // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/6289
1528 : : GjsAutoObjectInfo surface_info =
1529 : 0 : g_irepository_find_by_gtype(nullptr, gdksurface_type);
1530 : 0 : g_assert(surface_info && "Could not find introspected GdkSurface info");
1531 : : GjsAutoFunctionInfo destroy_func =
1532 : 0 : g_object_info_find_method(surface_info, "destroy");
1533 : 0 : GIArgument destroy_args[1] = {{.v_pointer = ptr}};
1534 : : GIArgument unused_return;
1535 : :
1536 : 0 : GjsAutoError err;
1537 [ # # ]: 0 : if (!g_function_info_invoke(destroy_func, destroy_args, 1, nullptr,
1538 : : 0, &unused_return, err.out()))
1539 : 0 : g_critical("Error destroying GdkSurface %p: %s", ptr,
1540 : : err->message);
1541 : 0 : }
1542 : : }
1543 : :
1544 : 865 : m_ptr = nullptr;
1545 : : }
1546 : :
1547 : : /* At shutdown, we need to ensure we've cleared the context of any
1548 : : * pending toggle references.
1549 : : */
1550 : : void
1551 : 723 : gjs_object_clear_toggles(void)
1552 : : {
1553 : 723 : ToggleQueue::get_default()->handle_all_toggles(toggle_handler);
1554 : 723 : }
1555 : :
1556 : : void
1557 : 240 : gjs_object_shutdown_toggle_queue(void)
1558 : : {
1559 : 240 : ToggleQueue::get_default()->shutdown();
1560 : 240 : }
1561 : :
1562 : : /*
1563 : : * ObjectInstance::prepare_shutdown:
1564 : : *
1565 : : * Called when the #GjsContext is disposed, in order to release all GC roots of
1566 : : * JSObjects that are held by GObjects.
1567 : : */
1568 : 240 : void ObjectInstance::prepare_shutdown(void) {
1569 : : /* We iterate over all of the objects, breaking the JS <-> C
1570 : : * association. We avoid the potential recursion implied in:
1571 : : * toggle ref removal -> gobj dispose -> toggle ref notify
1572 : : * by emptying the toggle queue earlier in the shutdown sequence. */
1573 : 480 : ObjectInstance::remove_wrapped_gobjects_if(
1574 : 480 : std::mem_fn(&ObjectInstance::wrapper_is_rooted),
1575 : 240 : std::mem_fn(&ObjectInstance::release_native_object));
1576 : 240 : }
1577 : :
1578 : 1563 : ObjectInstance::ObjectInstance(ObjectPrototype* prototype,
1579 : 1563 : JS::HandleObject object)
1580 : : : GIWrapperInstance(prototype, object),
1581 : 1563 : m_wrapper_finalized(false),
1582 : 1563 : m_gobj_disposed(false),
1583 : 1563 : m_gobj_finalized(false),
1584 : 1563 : m_uses_toggle_ref(false) {
1585 : : GTypeQuery query;
1586 : 1563 : type_query_dynamic_safe(&query);
1587 [ + - ]: 1563 : if (G_LIKELY(query.type))
1588 : 1563 : JS::AddAssociatedMemory(object, query.instance_size,
1589 : : MemoryUse::GObjectInstanceStruct);
1590 : :
1591 : 1563 : GJS_INC_COUNTER(object_instance);
1592 : 1563 : }
1593 : :
1594 : 672 : ObjectPrototype::ObjectPrototype(GIObjectInfo* info, GType gtype)
1595 : 672 : : GIWrapperPrototype(info, gtype) {
1596 : 672 : g_type_class_ref(gtype);
1597 : :
1598 : 672 : GJS_INC_COUNTER(object_prototype);
1599 : 672 : }
1600 : :
1601 : : /*
1602 : : * ObjectInstance::update_heap_wrapper_weak_pointers:
1603 : : *
1604 : : * Private callback, called after the JS engine finishes garbage collection, and
1605 : : * notifies when weak pointers need to be either moved or swept.
1606 : : */
1607 : 236 : void ObjectInstance::update_heap_wrapper_weak_pointers(JSTracer* trc,
1608 : : JS::Compartment*,
1609 : : void*) {
1610 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Weak pointer update callback, "
1611 : : "%zu wrapped GObject(s) to examine",
1612 : : ObjectInstance::num_wrapped_gobjects());
1613 : :
1614 : : // Take a lock on the queue till we're done with it, so that we don't
1615 : : // risk that another thread will queue something else while sweeping
1616 : 236 : auto locked_queue = ToggleQueue::get_default();
1617 : :
1618 : 472 : ObjectInstance::remove_wrapped_gobjects_if(
1619 : 472 : [&trc](ObjectInstance* instance) -> bool {
1620 : 2304 : return instance->weak_pointer_was_finalized(trc);
1621 : : },
1622 : 236 : std::mem_fn(&ObjectInstance::disassociate_js_gobject));
1623 : :
1624 : 236 : s_wrapped_gobject_list.shrink_to_fit();
1625 : 236 : }
1626 : :
1627 : 2304 : bool ObjectInstance::weak_pointer_was_finalized(JSTracer* trc) {
1628 [ + + + + : 2304 : if (has_wrapper() && !wrapper_is_rooted()) {
+ + ]
1629 : : bool toggle_down_queued, toggle_up_queued;
1630 : :
1631 : 1540 : auto toggle_queue = ToggleQueue::get_default();
1632 : 1540 : std::tie(toggle_down_queued, toggle_up_queued) =
1633 : 3080 : toggle_queue->is_queued(this);
1634 : :
1635 [ + - + + ]: 1540 : if (!toggle_down_queued && toggle_up_queued)
1636 : 2 : return false;
1637 : :
1638 [ + + ]: 1538 : if (!update_after_gc(trc))
1639 : 307 : return false;
1640 : :
1641 [ - + ]: 1231 : if (toggle_down_queued)
1642 : 0 : toggle_queue->cancel(this);
1643 : :
1644 : : /* Ouch, the JS object is dead already. Disassociate the
1645 : : * GObject and hope the GObject dies too. (Remove it from
1646 : : * the weak pointer list first, since the disassociation
1647 : : * may also cause it to be erased.)
1648 : : */
1649 : 1231 : debug_lifecycle("Found GObject weak pointer whose JS wrapper is about "
1650 : : "to be finalized");
1651 : 1231 : return true;
1652 : 1540 : }
1653 : 764 : return false;
1654 : : }
1655 : :
1656 : : /*
1657 : : * ObjectInstance::ensure_weak_pointer_callback:
1658 : : *
1659 : : * Private method called when adding a weak pointer for the first time.
1660 : : */
1661 : 1546 : void ObjectInstance::ensure_weak_pointer_callback(JSContext* cx) {
1662 [ + + ]: 1546 : if (!s_weak_pointer_callback) {
1663 : 29 : JS_AddWeakPointerCompartmentCallback(
1664 : : cx, &ObjectInstance::update_heap_wrapper_weak_pointers, nullptr);
1665 : 29 : s_weak_pointer_callback = true;
1666 : : }
1667 : 1546 : }
1668 : :
1669 : : void
1670 : 1546 : ObjectInstance::associate_js_gobject(JSContext *context,
1671 : : JS::HandleObject object,
1672 : : GObject *gobj)
1673 : : {
1674 : 1546 : g_assert(!wrapper_is_rooted());
1675 : :
1676 : 1546 : m_uses_toggle_ref = false;
1677 : 1546 : m_ptr = gobj;
1678 : 1546 : set_object_qdata();
1679 : 1546 : m_wrapper = object;
1680 : 1546 : m_gobj_disposed = !!g_object_get_qdata(gobj, ObjectBase::disposed_quark());
1681 : :
1682 : 1546 : ensure_weak_pointer_callback(context);
1683 : 1546 : link();
1684 : :
1685 [ + + ]: 1546 : if (!G_UNLIKELY(m_gobj_disposed))
1686 : 1522 : g_object_weak_ref(gobj, wrapped_gobj_dispose_notify, this);
1687 : 1546 : }
1688 : :
1689 : 1494 : void ObjectInstance::ensure_uses_toggle_ref(JSContext* cx) {
1690 [ + + ]: 1494 : if (m_uses_toggle_ref)
1691 : 760 : return;
1692 : :
1693 [ + + ]: 734 : if (!check_gobject_disposed_or_finalized("add toggle reference on"))
1694 : 2 : return;
1695 : :
1696 : 732 : debug_lifecycle("Switching object instance to toggle ref");
1697 : :
1698 : 732 : g_assert(!wrapper_is_rooted());
1699 : :
1700 : : /* OK, here is where things get complicated. We want the
1701 : : * wrapped gobj to keep the JSObject* wrapper alive, because
1702 : : * people might set properties on the JSObject* that they care
1703 : : * about. Therefore, whenever the refcount on the wrapped gobj
1704 : : * is >1, i.e. whenever something other than the wrapper is
1705 : : * referencing the wrapped gobj, the wrapped gobj has a strong
1706 : : * ref (gc-roots the wrapper). When the refcount on the
1707 : : * wrapped gobj is 1, then we change to a weak ref to allow
1708 : : * the wrapper to be garbage collected (and thus unref the
1709 : : * wrappee).
1710 : : */
1711 : 732 : m_uses_toggle_ref = true;
1712 : 732 : switch_to_rooted(cx);
1713 : 732 : g_object_add_toggle_ref(m_ptr, wrapped_gobj_toggle_notify, this);
1714 : :
1715 : : /* We now have both a ref and a toggle ref, we only want the toggle ref.
1716 : : * This may immediately remove the GC root we just added, since refcount
1717 : : * may drop to 1. */
1718 : 732 : g_object_unref(m_ptr);
1719 : : }
1720 : :
1721 : 3462 : static void invalidate_closure_vector(std::vector<GClosure*>* closures,
1722 : : void* data, GClosureNotify notify_func) {
1723 : 3462 : g_assert(closures);
1724 : 3462 : g_assert(notify_func);
1725 : :
1726 [ + + ]: 3734 : for (auto it = closures->begin(); it != closures->end();) {
1727 : : // This will also free the closure data, through the closure
1728 : : // invalidation mechanism, but adding a temporary reference to
1729 : : // ensure that the closure is still valid when calling invalidation
1730 : : // notify callbacks
1731 : 272 : GjsAutoGClosure closure(*it, GjsAutoTakeOwnership());
1732 : 272 : it = closures->erase(it);
1733 : :
1734 : : // Only call the invalidate notifiers that won't touch this vector
1735 : 272 : g_closure_remove_invalidate_notifier(closure, data, notify_func);
1736 : 272 : g_closure_invalidate(closure);
1737 : 272 : }
1738 : :
1739 : 3462 : g_assert(closures->empty());
1740 : 3462 : }
1741 : :
1742 : : // Note: m_wrapper (the JS object) may already be null when this is called, if
1743 : : // it was finalized while the GObject was toggled down.
1744 : : void
1745 : 1231 : ObjectInstance::disassociate_js_gobject(void)
1746 : : {
1747 : : bool had_toggle_down, had_toggle_up;
1748 : :
1749 : 1231 : std::tie(had_toggle_down, had_toggle_up) =
1750 : 2462 : ToggleQueue::get_default()->cancel(this);
1751 [ - + - - ]: 1231 : if (had_toggle_up && !had_toggle_down) {
1752 : 0 : g_error(
1753 : : "JS object wrapper for GObject %p (%s) is being released while "
1754 : : "toggle references are still pending.",
1755 : : m_ptr.get(), type_name());
1756 : : }
1757 : :
1758 [ + + ]: 1231 : if (!m_gobj_disposed)
1759 : 1204 : g_object_weak_unref(m_ptr.get(), wrapped_gobj_dispose_notify, this);
1760 : :
1761 [ + + ]: 1231 : if (!m_gobj_finalized) {
1762 : : /* Fist, remove the wrapper pointer from the wrapped GObject */
1763 : 1229 : unset_object_qdata();
1764 : : }
1765 : :
1766 : : /* Now release all the resources the current wrapper has */
1767 : 1231 : invalidate_closures();
1768 : 1231 : release_native_object();
1769 : :
1770 : : /* Mark that a JS object once existed, but it doesn't any more */
1771 : 1231 : m_wrapper_finalized = true;
1772 : 1231 : }
1773 : :
1774 : 906 : bool ObjectInstance::init_impl(JSContext* context, const JS::CallArgs& args,
1775 : : JS::HandleObject object) {
1776 : 906 : g_assert(gtype() != G_TYPE_NONE);
1777 : :
1778 [ + + - + ]: 907 : if (args.length() > 1 &&
1779 [ - + ]: 1 : !JS::WarnUTF8(context,
1780 : : "Too many arguments to the constructor of %s: expected "
1781 : : "1, got %u",
1782 : : name(), args.length()))
1783 : 0 : return false;
1784 : :
1785 : 906 : GjsAutoTypeClass<GObjectClass> object_class(gtype());
1786 : 906 : std::vector<const char *> names;
1787 : 906 : AutoGValueVector values;
1788 : :
1789 [ + + + + : 906 : if (args.length() > 0 && !args[0].isUndefined()) {
+ + ]
1790 [ + + ]: 264 : if (!args[0].isObject()) {
1791 : 1 : gjs_throw(context,
1792 : : "Argument to the constructor of %s should be a plain JS "
1793 : : "object with properties to set",
1794 : : name());
1795 : 11 : return false;
1796 : : }
1797 : :
1798 : 263 : JS::RootedObject props(context, &args[0].toObject());
1799 [ + + ]: 263 : if (ObjectInstance::typecheck(context, props, nullptr, G_TYPE_NONE,
1800 : : GjsTypecheckNoThrow{})) {
1801 : 1 : gjs_throw(context,
1802 : : "Argument to the constructor of %s should be a plain JS "
1803 : : "object with properties to set",
1804 : : name());
1805 : 1 : return false;
1806 : : }
1807 [ + + ]: 262 : if (!m_proto->props_to_g_parameters(context, object_class, props,
1808 : : &names, &values))
1809 : 9 : return false;
1810 [ + + ]: 263 : }
1811 : :
1812 [ + + ]: 895 : if (G_TYPE_IS_ABSTRACT(gtype())) {
1813 : 1 : gjs_throw(context,
1814 : : "Cannot instantiate abstract type %s", g_type_name(gtype()));
1815 : 1 : return false;
1816 : : }
1817 : :
1818 : : // Mark this object in the construction stack, it will be popped in
1819 : : // gjs_object_custom_init() in gi/gobject.cpp.
1820 [ + + ]: 894 : if (is_custom_js_class()) {
1821 : 481 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
1822 [ - + ]: 481 : if (!gjs->object_init_list().append(object)) {
1823 : 0 : JS_ReportOutOfMemory(context);
1824 : 0 : return false;
1825 : : }
1826 : : }
1827 : :
1828 : 894 : g_assert(names.size() == values.size());
1829 : 894 : GObject* gobj = g_object_new_with_properties(gtype(), values.size(),
1830 : 894 : names.data(), values.data());
1831 : :
1832 : 894 : ObjectInstance *other_priv = ObjectInstance::for_gobject(gobj);
1833 [ + + + + : 894 : if (other_priv && other_priv->m_wrapper != object.get()) {
+ + ]
1834 : : /* g_object_new_with_properties() returned an object that's already
1835 : : * tracked by a JS object.
1836 : : *
1837 : : * This typically occurs in one of two cases:
1838 : : * - This object is a singleton like IBus.IBus
1839 : : * - This object passed itself to JS before g_object_new_* returned
1840 : : *
1841 : : * In these cases, return the existing JS wrapper object instead
1842 : : * of creating a new one.
1843 : : *
1844 : : * 'object' has a value that was originally created by
1845 : : * JS_NewObjectForConstructor in GJS_NATIVE_CONSTRUCTOR_PRELUDE, but
1846 : : * we're not actually using it, so just let it get collected. Avoiding
1847 : : * this would require a non-trivial amount of work.
1848 : : * */
1849 : 1 : bool toggle_ref_added = false;
1850 [ + - ]: 1 : if (!m_uses_toggle_ref) {
1851 : 1 : other_priv->ensure_uses_toggle_ref(context);
1852 : 1 : toggle_ref_added = m_uses_toggle_ref;
1853 : : }
1854 : :
1855 : 1 : args.rval().setObject(*other_priv->m_wrapper);
1856 : :
1857 [ - + ]: 1 : if (toggle_ref_added)
1858 [ # # ]: 0 : g_clear_object(&gobj); /* We already own a reference */
1859 : 1 : return true;
1860 : : }
1861 : :
1862 [ - + + - : 1025 : if (G_IS_INITIALLY_UNOWNED(gobj) &&
- + + + +
+ + + ]
1863 : 132 : !g_object_is_floating(gobj)) {
1864 : : /* GtkWindow does not return a ref to caller of g_object_new.
1865 : : * Need a flag in gobject-introspection to tell us this.
1866 : : */
1867 : 75 : gjs_debug(GJS_DEBUG_GOBJECT,
1868 : : "Newly-created object is initially unowned but we did not get the "
1869 : : "floating ref, probably GtkWindow, using hacky workaround");
1870 : 75 : g_object_ref(gobj);
1871 [ + + ]: 818 : } else if (g_object_is_floating(gobj)) {
1872 : 57 : g_object_ref_sink(gobj);
1873 : : } else {
1874 : : /* we should already have a ref */
1875 : : }
1876 : :
1877 [ + + ]: 893 : if (!m_ptr)
1878 : 412 : associate_js_gobject(context, object, gobj);
1879 : :
1880 : : TRACE(GJS_OBJECT_WRAPPER_NEW(this, m_ptr, ns(), name()));
1881 : :
1882 : 893 : args.rval().setObject(*object);
1883 : 893 : return true;
1884 : 906 : }
1885 : :
1886 : : // See GIWrapperBase::constructor()
1887 : 910 : bool ObjectInstance::constructor_impl(JSContext* context,
1888 : : JS::HandleObject object,
1889 : : const JS::CallArgs& argv) {
1890 : 910 : JS::RootedValue initer(context);
1891 : 910 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
1892 : 910 : const auto& new_target = argv.newTarget();
1893 : : bool has_gtype;
1894 : :
1895 : 910 : g_assert(new_target.isObject() && "new.target needs to be an object");
1896 : 910 : JS::RootedObject rooted_target(context, &new_target.toObject());
1897 [ - + ]: 910 : if (!JS_HasOwnPropertyById(context, rooted_target, gjs->atoms().gtype(),
1898 : : &has_gtype))
1899 : 0 : return false;
1900 : :
1901 [ - + ]: 910 : if (!has_gtype) {
1902 : 0 : gjs_throw(context,
1903 : : "Tried to construct an object without a GType; are "
1904 : : "you using GObject.registerClass() when inheriting "
1905 : : "from a GObject type?");
1906 : 0 : return false;
1907 : : }
1908 : :
1909 : 1820 : return gjs_object_require_property(context, object, "GObject instance",
1910 [ + - + + ]: 2730 : gjs->atoms().init(), &initer) &&
1911 : 1820 : gjs->call_function(object, initer, argv, argv.rval());
1912 : 910 : }
1913 : :
1914 : 256 : void ObjectInstance::trace_impl(JSTracer* tracer) {
1915 [ + + ]: 332 : for (GClosure *closure : m_closures)
1916 : 76 : Gjs::Closure::for_gclosure(closure)->trace(tracer);
1917 : 256 : }
1918 : :
1919 : 2269 : void ObjectPrototype::trace_impl(JSTracer* tracer) {
1920 : 2269 : m_field_cache.trace(tracer);
1921 : 2269 : m_unresolvable_cache.trace(tracer);
1922 [ + + ]: 2369 : for (GClosure* closure : m_vfuncs)
1923 : 100 : Gjs::Closure::for_gclosure(closure)->trace(tracer);
1924 : 2269 : }
1925 : :
1926 : 1562 : void ObjectInstance::finalize_impl(JS::GCContext* gcx, JSObject* obj) {
1927 : : GTypeQuery query;
1928 : 1562 : type_query_dynamic_safe(&query);
1929 [ + - ]: 1562 : if (G_LIKELY(query.type))
1930 : 1562 : JS::RemoveAssociatedMemory(obj, query.instance_size,
1931 : : MemoryUse::GObjectInstanceStruct);
1932 : :
1933 : 1562 : GIWrapperInstance::finalize_impl(gcx, obj);
1934 : 1562 : }
1935 : :
1936 : 1562 : ObjectInstance::~ObjectInstance() {
1937 : : TRACE(GJS_OBJECT_WRAPPER_FINALIZE(this, m_ptr, ns(), name()));
1938 : :
1939 : 1562 : invalidate_closures();
1940 : :
1941 : : // Do not keep the queue locked here, as we may want to leave the other
1942 : : // threads to queue toggle events till we're owning the GObject so that
1943 : : // eventually (once the toggle reference is finally removed) we can be
1944 : : // sure that no other toggle event will target this (soon dead) wrapper.
1945 : : bool had_toggle_up;
1946 : : bool had_toggle_down;
1947 : 1562 : std::tie(had_toggle_down, had_toggle_up) =
1948 : 3124 : ToggleQueue::get_default()->cancel(this);
1949 : :
1950 : : /* GObject is not already freed */
1951 [ + + ]: 1562 : if (m_ptr) {
1952 [ + + - + ]: 314 : if (!had_toggle_up && had_toggle_down) {
1953 : 0 : g_error(
1954 : : "Finalizing wrapper for an object that's scheduled to be "
1955 : : "unrooted: %s.%s\n",
1956 : : ns(), name());
1957 : : }
1958 : :
1959 [ + + ]: 314 : if (!m_gobj_disposed)
1960 : 160 : g_object_weak_unref(m_ptr, wrapped_gobj_dispose_notify, this);
1961 : :
1962 [ + + ]: 314 : if (!m_gobj_finalized)
1963 : 297 : unset_object_qdata();
1964 : :
1965 : 314 : bool was_using_toggle_refs = m_uses_toggle_ref;
1966 : 314 : release_native_object();
1967 : :
1968 [ + + ]: 314 : if (was_using_toggle_refs) {
1969 : : // We need to cancel again, to be sure that no other thread added
1970 : : // another toggle reference before we were removing the last one.
1971 : 159 : ToggleQueue::get_default()->cancel(this);
1972 : : }
1973 : : }
1974 : :
1975 [ - + ]: 1562 : if (wrapper_is_rooted()) {
1976 : : /* This happens when the refcount on the object is still >1,
1977 : : * for example with global objects GDK never frees like GdkDisplay,
1978 : : * when we close down the JS runtime.
1979 : : */
1980 : 0 : gjs_debug(GJS_DEBUG_GOBJECT,
1981 : : "Wrapper was finalized despite being kept alive, has refcount >1");
1982 : :
1983 : 0 : debug_lifecycle("Unrooting object");
1984 : :
1985 : 0 : discard_wrapper();
1986 : : }
1987 : 1562 : unlink();
1988 : :
1989 : 1562 : GJS_DEC_COUNTER(object_instance);
1990 : 1562 : }
1991 : :
1992 : 669 : ObjectPrototype::~ObjectPrototype() {
1993 : 669 : invalidate_closure_vector(&m_vfuncs, this, &vfunc_invalidated_notify);
1994 : :
1995 : 669 : g_type_class_unref(g_type_class_peek(m_gtype));
1996 : :
1997 : 669 : GJS_DEC_COUNTER(object_prototype);
1998 : 669 : }
1999 : :
2000 : 2540 : static JSObject* gjs_lookup_object_constructor_from_info(JSContext* context,
2001 : : GIBaseInfo* info,
2002 : : GType gtype) {
2003 : 2540 : g_return_val_if_fail(
2004 : : !info || GI_IS_OBJECT_INFO(info) || GI_IS_INTERFACE_INFO(info), NULL);
2005 : :
2006 : 2540 : JS::RootedObject in_object(context);
2007 : : const char *constructor_name;
2008 : :
2009 [ + + ]: 2540 : if (info) {
2010 : 2367 : in_object = gjs_lookup_namespace_object(context, info);
2011 : 2367 : constructor_name = g_base_info_get_name(info);
2012 : : } else {
2013 : 173 : in_object = gjs_lookup_private_namespace(context);
2014 : 173 : constructor_name = g_type_name(gtype);
2015 : : }
2016 : :
2017 [ - + ]: 2540 : if (G_UNLIKELY (!in_object))
2018 : 0 : return NULL;
2019 : :
2020 : : bool found;
2021 [ - + ]: 2540 : if (!JS_HasProperty(context, in_object, constructor_name, &found))
2022 : 0 : return NULL;
2023 : :
2024 : 2540 : JS::RootedValue value(context);
2025 [ + + - + : 2540 : if (found && !JS_GetProperty(context, in_object, constructor_name, &value))
- + ]
2026 : 0 : return NULL;
2027 : :
2028 : 2540 : JS::RootedObject constructor(context);
2029 [ + + ]: 2540 : if (value.isUndefined()) {
2030 : : /* In case we're looking for a private type, and we don't find it,
2031 : : we need to define it first.
2032 : : */
2033 : 58 : JS::RootedObject ignored(context);
2034 [ - + ]: 58 : if (!ObjectPrototype::define_class(context, in_object, nullptr, gtype,
2035 : : nullptr, 0, &constructor, &ignored))
2036 : 0 : return nullptr;
2037 [ + - ]: 58 : } else {
2038 [ - + ]: 2482 : if (G_UNLIKELY (!value.isObject()))
2039 : 0 : return NULL;
2040 : :
2041 : 2482 : constructor = &value.toObject();
2042 : : }
2043 : :
2044 : 2540 : g_assert(constructor);
2045 : :
2046 : 2540 : return constructor;
2047 : 2540 : }
2048 : :
2049 : : GJS_JSAPI_RETURN_CONVENTION
2050 : 2020 : static JSObject* gjs_lookup_object_prototype_from_info(JSContext* context,
2051 : : GIBaseInfo* info,
2052 : : GType gtype) {
2053 : 2020 : g_return_val_if_fail(
2054 : : !info || GI_IS_OBJECT_INFO(info) || GI_IS_INTERFACE_INFO(info), NULL);
2055 : :
2056 : : JS::RootedObject constructor(context,
2057 : 2020 : gjs_lookup_object_constructor_from_info(context, info, gtype));
2058 : :
2059 [ - + ]: 2020 : if (G_UNLIKELY(!constructor))
2060 : 0 : return NULL;
2061 : :
2062 : 2020 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
2063 : 2020 : JS::RootedObject prototype(context);
2064 [ - + ]: 2020 : if (!gjs_object_require_property(context, constructor, "constructor object",
2065 : : atoms.prototype(), &prototype))
2066 : 0 : return NULL;
2067 : :
2068 : 2020 : return prototype;
2069 : 2020 : }
2070 : :
2071 : : GJS_JSAPI_RETURN_CONVENTION
2072 : : static JSObject *
2073 : 1248 : gjs_lookup_object_prototype(JSContext *context,
2074 : : GType gtype)
2075 : : {
2076 : 1248 : GjsAutoObjectInfo info = gjs_lookup_gtype(nullptr, gtype);
2077 : 1248 : return gjs_lookup_object_prototype_from_info(context, info, gtype);
2078 : 1248 : }
2079 : :
2080 : : // Retrieves a GIFieldInfo for a field named @key. This is for use in
2081 : : // field_getter_impl() and field_setter_not_impl(), where the field info *must*
2082 : : // have been cached previously in resolve_impl() on this ObjectPrototype or one
2083 : : // of its parent ObjectPrototypes. This will fail an assertion if there is no
2084 : : // cached field info.
2085 : : //
2086 : : // The caller does not own the return value, and it can never be null.
2087 : 12 : GIFieldInfo* ObjectPrototype::lookup_cached_field_info(JSContext* cx,
2088 : : JS::HandleString key) {
2089 [ - + ]: 12 : if (!info()) {
2090 : : // Custom JS classes can't have fields, and fields on internal classes
2091 : : // are not available. We must be looking up a field on a
2092 : : // GObject-introspected parent.
2093 : 0 : GType parent_gtype = g_type_parent(m_gtype);
2094 : 0 : g_assert(parent_gtype != G_TYPE_INVALID &&
2095 : : "Custom JS class must have parent");
2096 : : ObjectPrototype* parent_proto =
2097 : 0 : ObjectPrototype::for_gtype(parent_gtype);
2098 : :
2099 [ # # ]: 0 : if (!parent_proto) {
2100 : 0 : JS::RootedObject proto(cx, gjs_lookup_object_prototype(cx, parent_gtype));
2101 : 0 : parent_proto = ObjectPrototype::for_js(cx, proto);
2102 : 0 : }
2103 : :
2104 : 0 : g_assert(parent_proto &&
2105 : : "Custom JS class's parent must have been accessed in JS");
2106 : 0 : return parent_proto->lookup_cached_field_info(cx, key);
2107 : : }
2108 : :
2109 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
2110 : : "Looking up cached field info for %s in '%s' prototype",
2111 : : gjs_debug_string(key).c_str(), g_type_name(m_gtype));
2112 : 12 : auto entry = m_field_cache.lookupForAdd(key);
2113 [ + + ]: 12 : if (entry)
2114 : 9 : return entry->value().get();
2115 : :
2116 : : // We must be looking up a field defined on a parent. Look up the prototype
2117 : : // object via its GIObjectInfo.
2118 : 3 : GjsAutoObjectInfo parent_info = g_object_info_get_parent(m_info);
2119 : 3 : JS::RootedObject parent_proto(cx, gjs_lookup_object_prototype_from_info(
2120 : 3 : cx, parent_info, G_TYPE_INVALID));
2121 : 3 : ObjectPrototype* parent = ObjectPrototype::for_js(cx, parent_proto);
2122 : 3 : return parent->lookup_cached_field_info(cx, key);
2123 : 3 : }
2124 : :
2125 : 358 : bool ObjectInstance::associate_closure(JSContext* cx, GClosure* closure) {
2126 [ + - ]: 358 : if (!is_prototype())
2127 : 358 : to_instance()->ensure_uses_toggle_ref(cx);
2128 : :
2129 : 358 : g_assert(std::find(m_closures.begin(), m_closures.end(), closure) ==
2130 : : m_closures.end() &&
2131 : : "This closure was already associated with this object");
2132 : :
2133 : : /* This is a weak reference, and will be cleared when the closure is
2134 : : * invalidated */
2135 : 358 : m_closures.push_back(closure);
2136 : 358 : g_closure_add_invalidate_notifier(
2137 : : closure, this, &ObjectInstance::closure_invalidated_notify);
2138 : :
2139 : 358 : return true;
2140 : : }
2141 : :
2142 : 143 : void ObjectInstance::closure_invalidated_notify(void* data, GClosure* closure) {
2143 : : // This callback should *only* touch m_closures
2144 : 143 : auto* priv = static_cast<ObjectInstance*>(data);
2145 : 143 : Gjs::remove_one_from_unsorted_vector(&priv->m_closures, closure);
2146 : 143 : }
2147 : :
2148 : 2793 : void ObjectInstance::invalidate_closures() {
2149 : 2793 : invalidate_closure_vector(&m_closures, this, &closure_invalidated_notify);
2150 : 2793 : m_closures.shrink_to_fit();
2151 : 2793 : }
2152 : :
2153 : 318 : bool ObjectBase::connect(JSContext* cx, unsigned argc, JS::Value* vp) {
2154 [ - + - + ]: 318 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
2155 [ - + ]: 318 : if (!priv->check_is_instance(cx, "connect to signals"))
2156 : 0 : return false;
2157 : :
2158 : 318 : return priv->to_instance()->connect_impl(cx, args, false);
2159 : 318 : }
2160 : :
2161 : 2 : bool ObjectBase::connect_after(JSContext* cx, unsigned argc, JS::Value* vp) {
2162 [ - + - + ]: 2 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
2163 [ - + ]: 2 : if (!priv->check_is_instance(cx, "connect to signals"))
2164 : 0 : return false;
2165 : :
2166 : 2 : return priv->to_instance()->connect_impl(cx, args, true);
2167 : 2 : }
2168 : :
2169 : 1 : bool ObjectBase::connect_object(JSContext* cx, unsigned argc, JS::Value* vp) {
2170 [ - + - + ]: 1 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
2171 [ - + ]: 1 : if (!priv->check_is_instance(cx, "connect to signals"))
2172 : 0 : return false;
2173 : :
2174 : 1 : return priv->to_instance()->connect_impl(cx, args, false, true);
2175 : 1 : }
2176 : :
2177 : 321 : bool ObjectInstance::connect_impl(JSContext* context, const JS::CallArgs& args,
2178 : : bool after, bool object) {
2179 : : gulong id;
2180 : : guint signal_id;
2181 : : GQuark signal_detail;
2182 [ + + ]: 641 : const char* func_name = object ? "connect_object"
2183 [ + + ]: 320 : : after ? "connect_after"
2184 : : : "connect";
2185 : :
2186 : : gjs_debug_gsignal("connect obj %p priv %p", m_wrapper.get(), this);
2187 : :
2188 [ + + ]: 321 : if (!check_gobject_disposed_or_finalized("connect to any signal on")) {
2189 : 4 : args.rval().setInt32(0);
2190 : 4 : return true;
2191 : : }
2192 : :
2193 : 317 : JS::UniqueChars signal_name;
2194 : 317 : JS::RootedObject callback(context);
2195 : 317 : JS::RootedObject associate_obj(context);
2196 : : GConnectFlags flags;
2197 [ + + ]: 317 : if (object) {
2198 [ - + ]: 1 : if (!gjs_parse_call_args(context, func_name, args, "sooi",
2199 : : "signal name", &signal_name, "callback",
2200 : : &callback, "gobject", &associate_obj,
2201 : : "connect_flags", &flags))
2202 : 0 : return false;
2203 : :
2204 [ - + ]: 1 : if (flags & G_CONNECT_SWAPPED) {
2205 : 0 : gjs_throw(context, "Unsupported connect flag G_CONNECT_SWAPPED");
2206 : 0 : return false;
2207 : : }
2208 : :
2209 : 1 : after = flags & G_CONNECT_AFTER;
2210 : : } else {
2211 [ - + ]: 316 : if (!gjs_parse_call_args(context, func_name, args, "so", "signal name",
2212 : : &signal_name, "callback", &callback))
2213 : 0 : return false;
2214 : : }
2215 : :
2216 : : std::string dynamicString =
2217 : 317 : format_name() + '.' + func_name + "('" + signal_name.get() + "')";
2218 : 317 : AutoProfilerLabel label(context, "", dynamicString.c_str());
2219 : :
2220 [ - + ]: 317 : if (!JS::IsCallable(callback)) {
2221 : 0 : gjs_throw(context, "second arg must be a callback");
2222 : 0 : return false;
2223 : : }
2224 : :
2225 [ + + ]: 317 : if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id,
2226 : : &signal_detail, true)) {
2227 : 1 : gjs_throw(context, "No signal '%s' on object '%s'",
2228 : : signal_name.get(), type_name());
2229 : 1 : return false;
2230 : : }
2231 : :
2232 : 316 : GClosure* closure = Gjs::Closure::create_for_signal(
2233 : : context, callback, "signal callback", signal_id);
2234 [ - + ]: 316 : if (closure == NULL)
2235 : 0 : return false;
2236 : :
2237 [ + + ]: 316 : if (associate_obj.get() != nullptr) {
2238 : 1 : ObjectInstance* obj = ObjectInstance::for_js(context, associate_obj);
2239 [ - + ]: 1 : if (!obj)
2240 : 0 : return false;
2241 : :
2242 [ - + ]: 1 : if (!obj->associate_closure(context, closure))
2243 : 0 : return false;
2244 [ - + ]: 315 : } else if (!associate_closure(context, closure)) {
2245 : 0 : return false;
2246 : : }
2247 : :
2248 : 316 : id = g_signal_connect_closure_by_id(m_ptr, signal_id, signal_detail,
2249 : : closure, after);
2250 : :
2251 : 316 : args.rval().setDouble(id);
2252 : :
2253 : 316 : return true;
2254 : 317 : }
2255 : :
2256 : 172 : bool ObjectBase::emit(JSContext* cx, unsigned argc, JS::Value* vp) {
2257 [ - + - + ]: 172 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
2258 [ - + ]: 172 : if (!priv->check_is_instance(cx, "emit signal"))
2259 : 0 : return false;
2260 : :
2261 : 172 : return priv->to_instance()->emit_impl(cx, args);
2262 : 171 : }
2263 : :
2264 : : bool
2265 : 172 : ObjectInstance::emit_impl(JSContext *context,
2266 : : const JS::CallArgs& argv)
2267 : : {
2268 : : guint signal_id;
2269 : : GQuark signal_detail;
2270 : : GSignalQuery signal_query;
2271 : : unsigned int i;
2272 : :
2273 : : gjs_debug_gsignal("emit obj %p priv %p argc %d", m_wrapper.get(), this,
2274 : : argv.length());
2275 : :
2276 [ + + ]: 172 : if (!check_gobject_finalized("emit any signal on")) {
2277 : 1 : argv.rval().setUndefined();
2278 : 1 : return true;
2279 : : }
2280 : :
2281 : 171 : JS::UniqueChars signal_name;
2282 [ - + ]: 171 : if (!gjs_parse_call_args(context, "emit", argv, "!s",
2283 : : "signal name", &signal_name))
2284 : 0 : return false;
2285 : :
2286 : : std::string dynamicString =
2287 : 171 : format_name() + "emit('" + signal_name.get() + "')";
2288 : 171 : AutoProfilerLabel label(context, "", dynamicString.c_str());
2289 : :
2290 [ + + ]: 171 : if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id,
2291 : : &signal_detail, false)) {
2292 : 1 : gjs_throw(context, "No signal '%s' on object '%s'",
2293 : : signal_name.get(), type_name());
2294 : 1 : return false;
2295 : : }
2296 : :
2297 : 170 : g_signal_query(signal_id, &signal_query);
2298 : :
2299 [ - + ]: 170 : if ((argv.length() - 1) != signal_query.n_params) {
2300 : 0 : gjs_throw(context, "Signal '%s' on %s requires %d args got %d",
2301 : : signal_name.get(), type_name(), signal_query.n_params,
2302 : 0 : argv.length() - 1);
2303 : 0 : return false;
2304 : : }
2305 : :
2306 : 170 : AutoGValueVector instance_and_args;
2307 : 170 : instance_and_args.reserve(signal_query.n_params + 1);
2308 : 170 : std::vector<Gjs::AutoGValue*> args_to_steal;
2309 : 170 : Gjs::AutoGValue& instance = instance_and_args.emplace_back(gtype());
2310 : 170 : g_value_set_instance(&instance, m_ptr);
2311 : :
2312 [ + + ]: 233 : for (i = 0; i < signal_query.n_params; ++i) {
2313 : 75 : GType gtype = signal_query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
2314 : 75 : Gjs::AutoGValue& value = instance_and_args.emplace_back(gtype);
2315 [ + + ]: 75 : if ((signal_query.param_types[i] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0) {
2316 [ - + ]: 3 : if (!gjs_value_to_g_value_no_copy(context, argv[i + 1], &value))
2317 : 12 : return false;
2318 : : } else {
2319 [ + + ]: 72 : if (!gjs_value_to_g_value(context, argv[i + 1], &value))
2320 : 12 : return false;
2321 : : }
2322 : :
2323 [ + + ]: 63 : if (!ObjectBase::info())
2324 : 55 : continue;
2325 : :
2326 : : GjsAutoSignalInfo signal_info = g_object_info_find_signal(
2327 : 8 : ObjectBase::info(), signal_query.signal_name);
2328 [ - + ]: 8 : if (!signal_info)
2329 : 0 : continue;
2330 : :
2331 : 8 : GjsAutoArgInfo arg_info = g_callable_info_get_arg(signal_info, i);
2332 [ + + ]: 8 : if (g_arg_info_get_ownership_transfer(arg_info) !=
2333 : : GI_TRANSFER_NOTHING) {
2334 : : // FIXME(3v1n0): As it happens in many places in gjs, we can't track
2335 : : // (yet) containers content, so in case of transfer container we
2336 : : // can only leak.
2337 : 3 : args_to_steal.push_back(&value);
2338 : : }
2339 [ + - ]: 8 : }
2340 : :
2341 [ + + ]: 158 : if (signal_query.return_type == G_TYPE_NONE) {
2342 : 131 : g_signal_emitv(instance_and_args.data(), signal_id, signal_detail,
2343 : : nullptr);
2344 : 130 : argv.rval().setUndefined();
2345 : 130 : std::for_each(args_to_steal.begin(), args_to_steal.end(),
2346 : 3 : [](Gjs::AutoGValue* value) { value->steal(); });
2347 : 130 : return true;
2348 : : }
2349 : :
2350 : 27 : GType gtype = signal_query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE;
2351 : 27 : Gjs::AutoGValue rvalue(gtype);
2352 : 27 : g_signal_emitv(instance_and_args.data(), signal_id, signal_detail, &rvalue);
2353 : :
2354 : 27 : std::for_each(args_to_steal.begin(), args_to_steal.end(),
2355 : 0 : [](Gjs::AutoGValue* value) { value->steal(); });
2356 : :
2357 : 27 : return gjs_value_from_g_value(context, argv.rval(), &rvalue);
2358 : 170 : }
2359 : :
2360 : 27 : bool ObjectInstance::signal_match_arguments_from_object(
2361 : : JSContext* cx, JS::HandleObject match_obj, GSignalMatchType* mask_out,
2362 : : unsigned* signal_id_out, GQuark* detail_out,
2363 : : JS::MutableHandleObject callable_out) {
2364 : 27 : g_assert(mask_out && signal_id_out && detail_out && "forgot out parameter");
2365 : :
2366 : 27 : int mask = 0;
2367 : 27 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
2368 : :
2369 : : bool has_id;
2370 : 27 : unsigned signal_id = 0;
2371 [ - + ]: 27 : if (!JS_HasOwnPropertyById(cx, match_obj, atoms.signal_id(), &has_id))
2372 : 0 : return false;
2373 [ + + ]: 27 : if (has_id) {
2374 : 7 : mask |= G_SIGNAL_MATCH_ID;
2375 : :
2376 : 7 : JS::RootedValue value(cx);
2377 [ - + ]: 7 : if (!JS_GetPropertyById(cx, match_obj, atoms.signal_id(), &value))
2378 : 0 : return false;
2379 : :
2380 : 7 : JS::UniqueChars signal_name = gjs_string_to_utf8(cx, value);
2381 [ - + ]: 7 : if (!signal_name)
2382 : 0 : return false;
2383 : :
2384 : 7 : signal_id = g_signal_lookup(signal_name.get(), gtype());
2385 [ + - + - ]: 7 : }
2386 : :
2387 : : bool has_detail;
2388 : 27 : GQuark detail = 0;
2389 [ - + ]: 27 : if (!JS_HasOwnPropertyById(cx, match_obj, atoms.detail(), &has_detail))
2390 : 0 : return false;
2391 [ + + ]: 27 : if (has_detail) {
2392 : 3 : mask |= G_SIGNAL_MATCH_DETAIL;
2393 : :
2394 : 3 : JS::RootedValue value(cx);
2395 [ - + ]: 3 : if (!JS_GetPropertyById(cx, match_obj, atoms.detail(), &value))
2396 : 0 : return false;
2397 : :
2398 : 3 : JS::UniqueChars detail_string = gjs_string_to_utf8(cx, value);
2399 [ - + ]: 3 : if (!detail_string)
2400 : 0 : return false;
2401 : :
2402 : 3 : detail = g_quark_from_string(detail_string.get());
2403 [ + - + - ]: 3 : }
2404 : :
2405 : : bool has_func;
2406 : 27 : JS::RootedObject callable(cx);
2407 [ - + ]: 27 : if (!JS_HasOwnPropertyById(cx, match_obj, atoms.func(), &has_func))
2408 : 0 : return false;
2409 [ + + ]: 27 : if (has_func) {
2410 : 22 : mask |= G_SIGNAL_MATCH_CLOSURE;
2411 : :
2412 : 22 : JS::RootedValue value(cx);
2413 [ - + ]: 22 : if (!JS_GetPropertyById(cx, match_obj, atoms.func(), &value))
2414 : 0 : return false;
2415 : :
2416 [ + - - + : 22 : if (!value.isObject() || !JS::IsCallable(&value.toObject())) {
- + ]
2417 : 0 : gjs_throw(cx, "'func' property must be a function");
2418 : 0 : return false;
2419 : : }
2420 : :
2421 : 22 : callable = &value.toObject();
2422 [ + - ]: 22 : }
2423 : :
2424 [ + + + + : 27 : if (!has_id && !has_detail && !has_func) {
- + ]
2425 : 0 : gjs_throw(cx, "Must specify at least one of signalId, detail, or func");
2426 : 0 : return false;
2427 : : }
2428 : :
2429 : 27 : *mask_out = GSignalMatchType(mask);
2430 [ + + ]: 27 : if (has_id)
2431 : 7 : *signal_id_out = signal_id;
2432 [ + + ]: 27 : if (has_detail)
2433 : 3 : *detail_out = detail;
2434 [ + + ]: 27 : if (has_func)
2435 : 22 : callable_out.set(callable);
2436 : 27 : return true;
2437 : 27 : }
2438 : :
2439 : 12 : bool ObjectBase::signal_find(JSContext* cx, unsigned argc, JS::Value* vp) {
2440 [ - + - + ]: 12 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
2441 [ - + ]: 12 : if (!priv->check_is_instance(cx, "find signal"))
2442 : 0 : return false;
2443 : :
2444 : 12 : return priv->to_instance()->signal_find_impl(cx, args);
2445 : 12 : }
2446 : :
2447 : 12 : bool ObjectInstance::signal_find_impl(JSContext* cx, const JS::CallArgs& args) {
2448 : : gjs_debug_gsignal("[Gi.signal_find_symbol]() obj %p priv %p argc %d",
2449 : : m_wrapper.get(), this, args.length());
2450 : :
2451 [ - + ]: 12 : if (!check_gobject_finalized("find any signal on")) {
2452 : 0 : args.rval().setInt32(0);
2453 : 0 : return true;
2454 : : }
2455 : :
2456 : 12 : JS::RootedObject match(cx);
2457 [ - + ]: 12 : if (!gjs_parse_call_args(cx, "[Gi.signal_find_symbol]", args, "o", "match",
2458 : : &match))
2459 : 0 : return false;
2460 : :
2461 : : GSignalMatchType mask;
2462 : : unsigned signal_id;
2463 : : GQuark detail;
2464 : 12 : JS::RootedObject callable(cx);
2465 [ - + ]: 12 : if (!signal_match_arguments_from_object(cx, match, &mask, &signal_id,
2466 : : &detail, &callable))
2467 : 0 : return false;
2468 : :
2469 : 12 : uint64_t handler = 0;
2470 [ + + ]: 12 : if (!callable) {
2471 : 5 : handler = g_signal_handler_find(m_ptr, mask, signal_id, detail, nullptr,
2472 : : nullptr, nullptr);
2473 : : } else {
2474 [ + - ]: 23 : for (GClosure* candidate : m_closures) {
2475 [ + + ]: 23 : if (Gjs::Closure::for_gclosure(candidate)->callable() == callable) {
2476 : 7 : handler = g_signal_handler_find(m_ptr, mask, signal_id, detail,
2477 : : candidate, nullptr, nullptr);
2478 [ + - ]: 7 : if (handler != 0)
2479 : 7 : break;
2480 : : }
2481 : : }
2482 : : }
2483 : :
2484 : 12 : args.rval().setNumber(static_cast<double>(handler));
2485 : 12 : return true;
2486 : 12 : }
2487 : :
2488 : : template <ObjectBase::SignalMatchFunc(*MatchFunc)>
2489 : : static inline const char* signal_match_to_action_name();
2490 : :
2491 : : template <>
2492 : : inline const char*
2493 : 12 : signal_match_to_action_name<&g_signal_handlers_block_matched>() {
2494 : 12 : return "block";
2495 : : }
2496 : :
2497 : : template <>
2498 : : inline const char*
2499 : 12 : signal_match_to_action_name<&g_signal_handlers_unblock_matched>() {
2500 : 12 : return "unblock";
2501 : : }
2502 : :
2503 : : template <>
2504 : : inline const char*
2505 : 12 : signal_match_to_action_name<&g_signal_handlers_disconnect_matched>() {
2506 : 12 : return "disconnect";
2507 : : }
2508 : :
2509 : : template <ObjectBase::SignalMatchFunc(*MatchFunc)>
2510 : 18 : bool ObjectBase::signals_action(JSContext* cx, unsigned argc, JS::Value* vp) {
2511 [ - + - + ]: 18 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
2512 : 18 : const std::string action_name = signal_match_to_action_name<MatchFunc>();
2513 [ - + ]: 18 : if (!priv->check_is_instance(cx, (action_name + " signal").c_str()))
2514 : 0 : return false;
2515 : :
2516 : 18 : return priv->to_instance()->signals_action_impl<MatchFunc>(cx, args);
2517 : 18 : }
2518 : :
2519 : : template <ObjectBase::SignalMatchFunc(*MatchFunc)>
2520 : 18 : bool ObjectInstance::signals_action_impl(JSContext* cx,
2521 : : const JS::CallArgs& args) {
2522 : 18 : const std::string action_name = signal_match_to_action_name<MatchFunc>();
2523 : 18 : const std::string action_tag = "[Gi.signals_" + action_name + "_symbol]";
2524 : : gjs_debug_gsignal("[%s]() obj %p priv %p argc %d", action_tag.c_str(),
2525 : : m_wrapper.get(), this, args.length());
2526 : :
2527 [ + + ]: 18 : if (!check_gobject_finalized((action_name + " any signal on").c_str())) {
2528 : 3 : args.rval().setInt32(0);
2529 : 3 : return true;
2530 : : }
2531 : 15 : JS::RootedObject match(cx);
2532 [ - + ]: 15 : if (!gjs_parse_call_args(cx, action_tag.c_str(), args, "o", "match",
2533 : : &match)) {
2534 : 0 : return false;
2535 : : }
2536 : : GSignalMatchType mask;
2537 : : unsigned signal_id;
2538 : : GQuark detail;
2539 : 15 : JS::RootedObject callable(cx);
2540 [ - + ]: 15 : if (!signal_match_arguments_from_object(cx, match, &mask, &signal_id,
2541 : : &detail, &callable)) {
2542 : 0 : return false;
2543 : : }
2544 : 15 : unsigned n_matched = 0;
2545 [ - + ]: 15 : if (!callable) {
2546 : 0 : n_matched = MatchFunc(m_ptr, mask, signal_id, detail, nullptr, nullptr,
2547 : : nullptr);
2548 : : } else {
2549 : 15 : std::vector<GClosure*> candidates;
2550 [ + + ]: 99 : for (GClosure* candidate : m_closures) {
2551 [ + + ]: 84 : if (Gjs::Closure::for_gclosure(candidate)->callable() == callable)
2552 : 15 : candidates.push_back(candidate);
2553 : : }
2554 [ + + ]: 30 : for (GClosure* candidate : candidates) {
2555 : 15 : n_matched += MatchFunc(m_ptr, mask, signal_id, detail, candidate,
2556 : : nullptr, nullptr);
2557 : : }
2558 : 15 : }
2559 : :
2560 : 15 : args.rval().setNumber(n_matched);
2561 : 15 : return true;
2562 : 18 : }
2563 : :
2564 : 216 : bool ObjectBase::to_string(JSContext* cx, unsigned argc, JS::Value* vp) {
2565 [ - + - + ]: 216 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
2566 : 216 : const char* kind = ObjectBase::DEBUG_TAG;
2567 [ + + ]: 216 : if (!priv->is_prototype())
2568 : 214 : kind = priv->to_instance()->to_string_kind();
2569 [ + + ]: 646 : return gjs_wrapper_to_string_func(
2570 : : cx, obj, kind, priv->info(), priv->gtype(),
2571 : 430 : priv->is_prototype() ? nullptr : priv->to_instance()->ptr(),
2572 : 216 : args.rval());
2573 : 216 : }
2574 : :
2575 : : /*
2576 : : * ObjectInstance::to_string_kind:
2577 : : *
2578 : : * ObjectInstance shows a "disposed" marker in its toString() method if the
2579 : : * wrapped GObject has already been disposed.
2580 : : */
2581 : 214 : const char* ObjectInstance::to_string_kind(void) const {
2582 [ + + ]: 214 : if (m_gobj_finalized)
2583 : 3 : return "object (FINALIZED)";
2584 [ + + ]: 211 : return m_gobj_disposed ? "object (DISPOSED)" : "object";
2585 : : }
2586 : :
2587 : : /*
2588 : : * ObjectBase::init_gobject:
2589 : : *
2590 : : * This is named "init_gobject()" but corresponds to "_init()" in JS. The reason
2591 : : * for the name is that an "init()" method is used within SpiderMonkey to
2592 : : * indicate fallible initialization that must be done before an object can be
2593 : : * used, which is not the case here.
2594 : : */
2595 : 906 : bool ObjectBase::init_gobject(JSContext* context, unsigned argc,
2596 : : JS::Value* vp) {
2597 [ - + - + ]: 906 : GJS_CHECK_WRAPPER_PRIV(context, argc, vp, argv, obj, ObjectBase, priv);
2598 [ - + ]: 906 : if (!priv->check_is_instance(context, "initialize"))
2599 : 0 : return false;
2600 : :
2601 : 906 : std::string dynamicString = priv->format_name() + "._init";
2602 : 906 : AutoProfilerLabel label(context, "", dynamicString.c_str());
2603 : :
2604 : 906 : return priv->to_instance()->init_impl(context, argv, obj);
2605 : 906 : }
2606 : :
2607 : : // clang-format off
2608 : : const struct JSClassOps ObjectBase::class_ops = {
2609 : : &ObjectBase::add_property,
2610 : : nullptr, // deleteProperty
2611 : : nullptr, // enumerate
2612 : : &ObjectBase::new_enumerate,
2613 : : &ObjectBase::resolve,
2614 : : nullptr, // mayResolve
2615 : : &ObjectBase::finalize,
2616 : : NULL,
2617 : : NULL,
2618 : : &ObjectBase::trace,
2619 : : };
2620 : :
2621 : : const struct JSClass ObjectBase::klass = {
2622 : : "GObject_Object",
2623 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
2624 : : &ObjectBase::class_ops
2625 : : };
2626 : :
2627 : : JSFunctionSpec ObjectBase::proto_methods[] = {
2628 : : JS_FN("_init", &ObjectBase::init_gobject, 0, 0),
2629 : : JS_FN("connect", &ObjectBase::connect, 0, 0),
2630 : : JS_FN("connect_after", &ObjectBase::connect_after, 0, 0),
2631 : : JS_FN("connect_object", &ObjectBase::connect_object, 0, 0),
2632 : : JS_FN("emit", &ObjectBase::emit, 0, 0),
2633 : : JS_FS_END
2634 : : };
2635 : :
2636 : : JSPropertySpec ObjectBase::proto_properties[] = {
2637 : : JS_STRING_SYM_PS(toStringTag, "GObject_Object", JSPROP_READONLY),
2638 : : JS_PS_END};
2639 : : // clang-format on
2640 : :
2641 : : // Override of GIWrapperPrototype::get_parent_proto()
2642 : 672 : bool ObjectPrototype::get_parent_proto(JSContext* cx,
2643 : : JS::MutableHandleObject proto) const {
2644 : 672 : GType parent_type = g_type_parent(gtype());
2645 [ + + ]: 672 : if (parent_type == G_TYPE_INVALID) {
2646 : 77 : proto.set(nullptr);
2647 : 77 : return true;
2648 : : }
2649 : :
2650 : 595 : JSObject* prototype = gjs_lookup_object_prototype(cx, parent_type);
2651 [ - + ]: 595 : if (!prototype)
2652 : 0 : return false;
2653 : :
2654 : 595 : proto.set(prototype);
2655 : 595 : return true;
2656 : : }
2657 : :
2658 : 582 : bool ObjectPrototype::get_parent_constructor(
2659 : : JSContext* cx, JS::MutableHandleObject constructor) const {
2660 : 582 : GType parent_type = g_type_parent(gtype());
2661 : :
2662 [ + + ]: 582 : if (parent_type == G_TYPE_INVALID) {
2663 : 77 : constructor.set(nullptr);
2664 : 77 : return true;
2665 : : }
2666 : :
2667 : 505 : JS::RootedValue v_constructor(cx);
2668 [ - + ]: 505 : if (!gjs_lookup_object_constructor(cx, parent_type, &v_constructor))
2669 : 0 : return false;
2670 : :
2671 : 505 : g_assert(v_constructor.isObject() &&
2672 : : "gjs_lookup_object_constructor() should always produce an object");
2673 : 505 : constructor.set(&v_constructor.toObject());
2674 : 505 : return true;
2675 : 505 : }
2676 : :
2677 : 672 : void ObjectPrototype::set_interfaces(GType* interface_gtypes,
2678 : : uint32_t n_interface_gtypes) {
2679 [ + + ]: 672 : if (interface_gtypes) {
2680 [ + + ]: 73 : for (uint32_t n = 0; n < n_interface_gtypes; n++) {
2681 : 39 : m_interface_gtypes.push_back(interface_gtypes[n]);
2682 : : }
2683 : : }
2684 : 672 : }
2685 : :
2686 : : /*
2687 : : * ObjectPrototype::define_class:
2688 : : * @in_object: Object where the constructor is stored, typically a repo object.
2689 : : * @info: Introspection info for the GObject class.
2690 : : * @gtype: #GType for the GObject class.
2691 : : * @constructor: Return location for the constructor object.
2692 : : * @prototype: Return location for the prototype object.
2693 : : *
2694 : : * Define a GObject class constructor and prototype, including all the
2695 : : * necessary methods and properties that are not introspected. Provides the
2696 : : * constructor and prototype objects as out parameters, for convenience
2697 : : * elsewhere.
2698 : : */
2699 : 582 : bool ObjectPrototype::define_class(
2700 : : JSContext* context, JS::HandleObject in_object, GIObjectInfo* info,
2701 : : GType gtype, GType* interface_gtypes, uint32_t n_interface_gtypes,
2702 : : JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) {
2703 : 582 : ObjectPrototype* priv = ObjectPrototype::create_class(
2704 : : context, in_object, info, gtype, constructor, prototype);
2705 [ - + ]: 582 : if (!priv)
2706 : 0 : return false;
2707 : :
2708 : 582 : priv->set_interfaces(interface_gtypes, n_interface_gtypes);
2709 : :
2710 : 582 : JS::RootedObject parent_constructor(context);
2711 [ - + ]: 582 : if (!priv->get_parent_constructor(context, &parent_constructor))
2712 : 0 : return false;
2713 : : // If this is a fundamental constructor (e.g. GObject.Object) the
2714 : : // parent constructor may be null.
2715 [ + + ]: 582 : if (parent_constructor) {
2716 [ - + ]: 505 : if (!JS_SetPrototype(context, constructor, parent_constructor))
2717 : 0 : return false;
2718 : : }
2719 : :
2720 : : // hook_up_vfunc and the signal handler matcher functions can't be included
2721 : : // in gjs_object_instance_proto_funcs because they are custom symbols.
2722 : 582 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
2723 : 582 : return JS_DefineFunctionById(context, prototype, atoms.hook_up_vfunc(),
2724 : : &ObjectBase::hook_up_vfunc, 3,
2725 [ + - ]: 582 : GJS_MODULE_PROP_FLAGS) &&
2726 : 582 : JS_DefineFunctionById(context, prototype, atoms.signal_find(),
2727 : : &ObjectBase::signal_find, 1,
2728 [ + - ]: 582 : GJS_MODULE_PROP_FLAGS) &&
2729 : 582 : JS_DefineFunctionById(
2730 : : context, prototype, atoms.signals_block(),
2731 : 5 : &ObjectBase::signals_action<&g_signal_handlers_block_matched>, 1,
2732 [ + - ]: 582 : GJS_MODULE_PROP_FLAGS) &&
2733 : 582 : JS_DefineFunctionById(
2734 : : context, prototype, atoms.signals_unblock(),
2735 : 5 : &ObjectBase::signals_action<&g_signal_handlers_unblock_matched>,
2736 [ + - + - ]: 1164 : 1, GJS_MODULE_PROP_FLAGS) &&
2737 : 582 : JS_DefineFunctionById(context, prototype, atoms.signals_disconnect(),
2738 : : &ObjectBase::signals_action<
2739 : 5 : &g_signal_handlers_disconnect_matched>,
2740 : 582 : 1, GJS_MODULE_PROP_FLAGS);
2741 : 582 : }
2742 : :
2743 : : /*
2744 : : * ObjectInstance::init_custom_class_from_gobject:
2745 : : *
2746 : : * Does all the necessary initialization for an ObjectInstance and JSObject
2747 : : * wrapper, given a newly-created GObject pointer, of a GObject class that was
2748 : : * created in JS with GObject.registerClass(). This is called from the GObject's
2749 : : * instance init function in gobject.cpp, and that's the only reason it's a
2750 : : * public method.
2751 : : */
2752 : 481 : bool ObjectInstance::init_custom_class_from_gobject(JSContext* cx,
2753 : : JS::HandleObject wrapper,
2754 : : GObject* gobj) {
2755 : 481 : associate_js_gobject(cx, wrapper, gobj);
2756 : :
2757 : : // Custom JS objects will most likely have visible state, so just do this
2758 : : // from the start.
2759 : 481 : ensure_uses_toggle_ref(cx);
2760 [ - + ]: 481 : if (!m_uses_toggle_ref) {
2761 : 0 : gjs_throw(cx, "Impossible to set toggle references on %sobject %p",
2762 [ # # ]: 0 : m_gobj_disposed ? "disposed " : "", gobj);
2763 : 0 : return false;
2764 : : }
2765 : :
2766 : 481 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
2767 : 481 : JS::RootedValue v(cx);
2768 [ - + ]: 481 : if (!JS_GetPropertyById(cx, wrapper, atoms.instance_init(), &v))
2769 : 0 : return false;
2770 : :
2771 [ + + ]: 481 : if (v.isUndefined())
2772 : 434 : return true;
2773 [ + - - + : 47 : if (!v.isObject() || !JS::IsCallable(&v.toObject())) {
- + ]
2774 : 0 : gjs_throw(cx, "_instance_init property was not a function");
2775 : 0 : return false;
2776 : : }
2777 : :
2778 : 47 : JS::RootedValue ignored_rval(cx);
2779 : 47 : return JS_CallFunctionValue(cx, wrapper, v, JS::HandleValueArray::empty(),
2780 : 47 : &ignored_rval);
2781 : 481 : }
2782 : :
2783 : : /*
2784 : : * ObjectInstance::new_for_gobject:
2785 : : *
2786 : : * Creates a new JSObject wrapper for the GObject pointer @gobj, and an
2787 : : * ObjectInstance private structure to go along with it.
2788 : : */
2789 : 653 : ObjectInstance* ObjectInstance::new_for_gobject(JSContext* cx, GObject* gobj) {
2790 : 653 : g_assert(gobj && "Cannot create JSObject for null GObject pointer");
2791 : :
2792 : 653 : GType gtype = G_TYPE_FROM_INSTANCE(gobj);
2793 : :
2794 : : gjs_debug_marshal(GJS_DEBUG_GOBJECT, "Wrapping %s %p with JSObject",
2795 : : g_type_name(gtype), gobj);
2796 : :
2797 : 653 : JS::RootedObject proto(cx, gjs_lookup_object_prototype(cx, gtype));
2798 [ - + ]: 653 : if (!proto)
2799 : 0 : return nullptr;
2800 : :
2801 : : JS::RootedObject obj(
2802 : 653 : cx, JS_NewObjectWithGivenProto(cx, &ObjectBase::klass, proto));
2803 [ - + ]: 653 : if (!obj)
2804 : 0 : return nullptr;
2805 : :
2806 : 653 : ObjectPrototype* prototype = resolve_prototype(cx, proto);
2807 [ - + ]: 653 : if (!prototype)
2808 : 0 : return nullptr;
2809 : :
2810 : 653 : ObjectInstance* priv = new ObjectInstance(prototype, obj);
2811 : :
2812 : 653 : ObjectBase::init_private(obj, priv);
2813 : :
2814 : 653 : g_object_ref_sink(gobj);
2815 : 653 : priv->associate_js_gobject(cx, obj, gobj);
2816 : :
2817 : 653 : g_assert(priv->wrapper() == obj.get());
2818 : :
2819 : 653 : return priv;
2820 : 653 : }
2821 : :
2822 : : /*
2823 : : * ObjectInstance::wrapper_from_gobject:
2824 : : *
2825 : : * Gets a JSObject wrapper for the GObject pointer @gobj. If one already exists,
2826 : : * then it is returned. Otherwise a new one is created with
2827 : : * ObjectInstance::new_for_gobject().
2828 : : */
2829 : 1841 : JSObject* ObjectInstance::wrapper_from_gobject(JSContext* cx, GObject* gobj) {
2830 : 1841 : g_assert(gobj && "Cannot get JSObject for null GObject pointer");
2831 : :
2832 : 1841 : ObjectInstance* priv = ObjectInstance::for_gobject(gobj);
2833 : :
2834 [ + + ]: 1841 : if (!priv) {
2835 : : /* We have to create a wrapper */
2836 : 634 : priv = new_for_gobject(cx, gobj);
2837 [ - + ]: 634 : if (!priv)
2838 : 0 : return nullptr;
2839 : : }
2840 : :
2841 : 1841 : return priv->wrapper();
2842 : : }
2843 : :
2844 : 1735 : bool ObjectInstance::set_value_from_gobject(JSContext* cx, GObject* gobj,
2845 : : JS::MutableHandleValue value_p) {
2846 [ - + ]: 1735 : if (!gobj) {
2847 : 0 : value_p.setNull();
2848 : 0 : return true;
2849 : : }
2850 : :
2851 : 1735 : auto* wrapper = ObjectInstance::wrapper_from_gobject(cx, gobj);
2852 [ + - ]: 1735 : if (wrapper) {
2853 : 1735 : value_p.setObject(*wrapper);
2854 : 1735 : return true;
2855 : : }
2856 : :
2857 : 0 : gjs_throw(cx, "Failed to find JS object for GObject %p of type %s", gobj,
2858 : 0 : g_type_name(G_TYPE_FROM_INSTANCE(gobj)));
2859 : 0 : return false;
2860 : : }
2861 : :
2862 : : // Replaces GIWrapperBase::to_c_ptr(). The GIWrapperBase version is deleted.
2863 : 1986 : bool ObjectBase::to_c_ptr(JSContext* cx, JS::HandleObject obj, GObject** ptr) {
2864 : 1986 : g_assert(ptr);
2865 : :
2866 : 1986 : auto* priv = ObjectBase::for_js(cx, obj);
2867 [ + + - + : 1986 : if (!priv || priv->is_prototype())
+ + ]
2868 : 1 : return false;
2869 : :
2870 : 1985 : ObjectInstance* instance = priv->to_instance();
2871 [ + + ]: 1985 : if (!instance->check_gobject_finalized("access")) {
2872 : 2 : *ptr = nullptr;
2873 : 2 : return true;
2874 : : }
2875 : :
2876 : 1983 : *ptr = instance->ptr();
2877 : 1983 : return true;
2878 : : }
2879 : :
2880 : : // Overrides GIWrapperBase::transfer_to_gi_argument().
2881 : 1933 : bool ObjectBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj,
2882 : : GIArgument* arg,
2883 : : GIDirection transfer_direction,
2884 : : GITransfer transfer_ownership,
2885 : : GType expected_gtype,
2886 : : GIBaseInfo* expected_info) {
2887 : 1933 : g_assert(transfer_direction != GI_DIRECTION_INOUT &&
2888 : : "transfer_to_gi_argument() must choose between in or out");
2889 : :
2890 [ + + ]: 1933 : if (!ObjectBase::typecheck(cx, obj, expected_info, expected_gtype)) {
2891 : 5 : gjs_arg_unset<void*>(arg);
2892 : 5 : return false;
2893 : : }
2894 : :
2895 : : GObject* ptr;
2896 [ - + ]: 1928 : if (!ObjectBase::to_c_ptr(cx, obj, &ptr))
2897 : 0 : return false;
2898 : :
2899 : 1928 : gjs_arg_set(arg, ptr);
2900 : :
2901 : : // Pointer can be null if object was already disposed by C code
2902 [ + + ]: 1928 : if (!ptr)
2903 : 2 : return true;
2904 : :
2905 [ + - + + ]: 1926 : if ((transfer_direction == GI_DIRECTION_IN &&
2906 [ - + ]: 1868 : transfer_ownership != GI_TRANSFER_NOTHING) ||
2907 [ # # ]: 0 : (transfer_direction == GI_DIRECTION_OUT &&
2908 : : transfer_ownership == GI_TRANSFER_EVERYTHING)) {
2909 : 58 : gjs_arg_set(arg, ObjectInstance::copy_ptr(cx, expected_gtype,
2910 : : gjs_arg_get<void*>(arg)));
2911 [ - + ]: 58 : if (!gjs_arg_get<void*>(arg))
2912 : 0 : return false;
2913 : : }
2914 : :
2915 : 1926 : return true;
2916 : : }
2917 : :
2918 : : // Overrides GIWrapperInstance::typecheck_impl()
2919 : 1990 : bool ObjectInstance::typecheck_impl(JSContext* cx, GIBaseInfo* expected_info,
2920 : : GType expected_type) const {
2921 : 1990 : g_assert(m_gobj_disposed || !m_ptr ||
2922 : : gtype() == G_OBJECT_TYPE(m_ptr.as<GObject*>()));
2923 : 1990 : return GIWrapperInstance::typecheck_impl(cx, expected_info, expected_type);
2924 : : }
2925 : :
2926 : : GJS_JSAPI_RETURN_CONVENTION
2927 : 59 : static bool find_vfunc_info(JSContext* context, GType implementor_gtype,
2928 : : GIBaseInfo* vfunc_info, const char* vfunc_name,
2929 : : void** implementor_vtable_ret,
2930 : : GjsAutoFieldInfo* field_info_ret) {
2931 : : GType ancestor_gtype;
2932 : : int length, i;
2933 : : GIBaseInfo *ancestor_info;
2934 : 59 : GjsAutoStructInfo struct_info;
2935 : : bool is_interface;
2936 : :
2937 : 59 : field_info_ret->reset();
2938 : 59 : *implementor_vtable_ret = NULL;
2939 : :
2940 : 59 : ancestor_info = g_base_info_get_container(vfunc_info);
2941 : 59 : ancestor_gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)ancestor_info);
2942 : :
2943 : 59 : is_interface = g_base_info_get_type(ancestor_info) == GI_INFO_TYPE_INTERFACE;
2944 : :
2945 : 59 : GjsAutoTypeClass<GTypeClass> implementor_class(implementor_gtype);
2946 [ + + ]: 59 : if (is_interface) {
2947 : : GTypeInstance *implementor_iface_class;
2948 : 9 : implementor_iface_class = (GTypeInstance*) g_type_interface_peek(implementor_class,
2949 : : ancestor_gtype);
2950 [ - + ]: 9 : if (implementor_iface_class == NULL) {
2951 : 0 : gjs_throw (context, "Couldn't find GType of implementor of interface %s.",
2952 : : g_type_name(ancestor_gtype));
2953 : 0 : return false;
2954 : : }
2955 : :
2956 : 9 : *implementor_vtable_ret = implementor_iface_class;
2957 : :
2958 : 9 : struct_info = g_interface_info_get_iface_struct((GIInterfaceInfo*)ancestor_info);
2959 : : } else {
2960 : 50 : struct_info = g_object_info_get_class_struct((GIObjectInfo*)ancestor_info);
2961 : 50 : *implementor_vtable_ret = implementor_class;
2962 : : }
2963 : :
2964 : 59 : length = g_struct_info_get_n_fields(struct_info);
2965 [ + - ]: 873 : for (i = 0; i < length; i++) {
2966 : 873 : GjsAutoFieldInfo field_info = g_struct_info_get_field(struct_info, i);
2967 [ + + ]: 873 : if (strcmp(field_info.name(), vfunc_name) != 0)
2968 : 814 : continue;
2969 : :
2970 : 59 : GjsAutoTypeInfo type_info = g_field_info_get_type(field_info);
2971 [ - + ]: 59 : if (g_type_info_get_tag(type_info) != GI_TYPE_TAG_INTERFACE) {
2972 : : /* We have a field with the same name, but it's not a callback.
2973 : : * There's no hope of being another field with a correct name,
2974 : : * so just abort early. */
2975 : 0 : return true;
2976 : : } else {
2977 : 59 : *field_info_ret = std::move(field_info);
2978 : 59 : return true;
2979 : : }
2980 [ + + ]: 932 : }
2981 : 0 : return true;
2982 : 59 : }
2983 : :
2984 : 62 : bool ObjectBase::hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp) {
2985 [ - + - + ]: 62 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, prototype, ObjectBase, priv);
2986 : : /* Normally we wouldn't assert is_prototype(), but this method can only be
2987 : : * called internally so it's OK to crash if done wrongly */
2988 : 62 : return priv->to_prototype()->hook_up_vfunc_impl(cx, args);
2989 : 62 : }
2990 : :
2991 : 62 : bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx,
2992 : : const JS::CallArgs& args) {
2993 : 62 : JS::UniqueChars name;
2994 : 62 : JS::RootedObject callable(cx);
2995 [ - + ]: 62 : if (!gjs_parse_call_args(cx, "hook_up_vfunc", args, "so", "name", &name,
2996 : : "callable", &callable))
2997 : 0 : return false;
2998 : :
2999 : 62 : args.rval().setUndefined();
3000 : :
3001 : : /* find the first class that actually has repository information */
3002 : 62 : GIObjectInfo *info = m_info;
3003 : 62 : GType info_gtype = m_gtype;
3004 [ + + + - ]: 129 : while (!info && info_gtype != G_TYPE_OBJECT) {
3005 : 67 : info_gtype = g_type_parent(info_gtype);
3006 : :
3007 : 67 : info = g_irepository_find_by_gtype(nullptr, info_gtype);
3008 : : }
3009 : :
3010 : : /* If we don't have 'info', we don't have the base class (GObject).
3011 : : * This is awful, so abort now. */
3012 : 62 : g_assert(info != NULL);
3013 : :
3014 : 62 : GjsAutoVFuncInfo vfunc = g_object_info_find_vfunc(info, name.get());
3015 : : // Search the parent type chain
3016 [ + + + + : 68 : while (!vfunc && info_gtype != G_TYPE_OBJECT) {
+ + ]
3017 : 6 : info_gtype = g_type_parent(info_gtype);
3018 : :
3019 : 6 : info = g_irepository_find_by_gtype(nullptr, info_gtype);
3020 [ + - ]: 6 : if (info)
3021 : 6 : vfunc = g_object_info_find_vfunc(info, name.get());
3022 : : }
3023 : :
3024 : : // If the vfunc doesn't exist in the parent
3025 : : // type chain, loop through the explicitly
3026 : : // defined interfaces...
3027 [ + + ]: 62 : if (!vfunc) {
3028 [ + + ]: 12 : for (GType interface_gtype : m_interface_gtypes) {
3029 : : GjsAutoInterfaceInfo interface =
3030 : 9 : g_irepository_find_by_gtype(nullptr, interface_gtype);
3031 : :
3032 : : // Private and dynamic interfaces (defined in JS) do not have type
3033 : : // info.
3034 [ + - ]: 9 : if (interface) {
3035 : 9 : vfunc = g_interface_info_find_vfunc(interface, name.get());
3036 : :
3037 [ + - ]: 9 : if (vfunc)
3038 : 9 : break;
3039 : : }
3040 [ - + ]: 9 : }
3041 : : }
3042 : :
3043 : : // If the vfunc is still not found, it could exist on an interface
3044 : : // implemented by a parent. This is an error, as hooking up the vfunc
3045 : : // would create an implementation on the interface itself. In this
3046 : : // case, print a more helpful error than...
3047 : : // "Could not find definition of virtual function"
3048 : : //
3049 : : // See https://gitlab.gnome.org/GNOME/gjs/-/issues/89
3050 [ + + ]: 62 : if (!vfunc) {
3051 : : unsigned n_interfaces;
3052 : : GjsAutoPointer<GType> interface_list =
3053 : 3 : g_type_interfaces(m_gtype, &n_interfaces);
3054 : :
3055 [ + + ]: 3 : for (unsigned i = 0; i < n_interfaces; i++) {
3056 : : GjsAutoInterfaceInfo interface =
3057 : 2 : g_irepository_find_by_gtype(nullptr, interface_list[i]);
3058 : :
3059 [ + - ]: 2 : if (interface) {
3060 : : GjsAutoVFuncInfo parent_vfunc =
3061 : 2 : g_interface_info_find_vfunc(interface, name.get());
3062 : :
3063 [ + - ]: 2 : if (parent_vfunc) {
3064 : : GjsAutoChar identifier = g_strdup_printf(
3065 : 2 : "%s.%s", interface.ns(), interface.name());
3066 : 2 : gjs_throw(cx,
3067 : : "%s does not implement %s, add %s to your "
3068 : : "implements array",
3069 : : g_type_name(m_gtype), identifier.get(),
3070 : : identifier.get());
3071 : 2 : return false;
3072 : 2 : }
3073 [ - + ]: 2 : }
3074 [ - + ]: 2 : }
3075 : :
3076 : : // Fall back to less helpful error message
3077 : 1 : gjs_throw(cx, "Could not find definition of virtual function %s",
3078 : : name.get());
3079 : 1 : return false;
3080 : 3 : }
3081 : :
3082 : : void *implementor_vtable;
3083 : 59 : GjsAutoFieldInfo field_info;
3084 [ - + ]: 59 : if (!find_vfunc_info(cx, m_gtype, vfunc, name.get(), &implementor_vtable,
3085 : : &field_info))
3086 : 0 : return false;
3087 : :
3088 [ + - ]: 59 : if (field_info) {
3089 : : gint offset;
3090 : : void* method_ptr;
3091 : : GjsCallbackTrampoline *trampoline;
3092 : :
3093 : 59 : offset = g_field_info_get_offset(field_info);
3094 : 59 : method_ptr = G_STRUCT_MEMBER_P(implementor_vtable, offset);
3095 : :
3096 [ - + ]: 59 : if (!JS::IsCallable(callable)) {
3097 : 0 : gjs_throw(cx, "Tried to deal with a vfunc that wasn't callable");
3098 : 1 : return false;
3099 : : }
3100 : 59 : trampoline = GjsCallbackTrampoline::create(
3101 : : cx, callable, vfunc, GI_SCOPE_TYPE_NOTIFIED, true, true);
3102 [ + + ]: 59 : if (!trampoline)
3103 : 1 : return false;
3104 : :
3105 : : // This is traced, and will be cleared from the list when the closure is
3106 : : // invalidated
3107 : 58 : g_assert(std::find(m_vfuncs.begin(), m_vfuncs.end(), trampoline) ==
3108 : : m_vfuncs.end() &&
3109 : : "This vfunc was already associated with this class");
3110 : 58 : m_vfuncs.push_back(trampoline);
3111 : 58 : g_closure_add_invalidate_notifier(
3112 : : trampoline, this, &ObjectPrototype::vfunc_invalidated_notify);
3113 : 58 : g_closure_add_invalidate_notifier(
3114 : : trampoline, nullptr,
3115 : 58 : [](void*, GClosure* closure) { g_closure_unref(closure); });
3116 : :
3117 : 58 : *reinterpret_cast<void**>(method_ptr) = trampoline->closure();
3118 : : }
3119 : :
3120 : 58 : return true;
3121 : 62 : }
3122 : :
3123 : 0 : void ObjectPrototype::vfunc_invalidated_notify(void* data, GClosure* closure) {
3124 : : // This callback should *only* touch m_vfuncs
3125 : 0 : auto* priv = static_cast<ObjectPrototype*>(data);
3126 : 0 : Gjs::remove_one_from_unsorted_vector(&priv->m_vfuncs, closure);
3127 : 0 : }
3128 : :
3129 : : bool
3130 : 520 : gjs_lookup_object_constructor(JSContext *context,
3131 : : GType gtype,
3132 : : JS::MutableHandleValue value_p)
3133 : : {
3134 : : JSObject *constructor;
3135 : :
3136 : 520 : GjsAutoObjectInfo object_info = gjs_lookup_gtype(nullptr, gtype);
3137 : :
3138 : 520 : constructor = gjs_lookup_object_constructor_from_info(context, object_info, gtype);
3139 : :
3140 [ - + ]: 520 : if (G_UNLIKELY (constructor == NULL))
3141 : 0 : return false;
3142 : :
3143 : 520 : value_p.setObject(*constructor);
3144 : 520 : return true;
3145 : 520 : }
3146 : :
3147 : 2 : void ObjectInstance::associate_string(GObject* obj, char* str) {
3148 : : auto* instance_strings = static_cast<GPtrArray*>(
3149 : 2 : g_object_get_qdata(obj, ObjectBase::instance_strings_quark()));
3150 : :
3151 [ + + ]: 2 : if (!instance_strings) {
3152 : 1 : instance_strings = g_ptr_array_new_with_free_func(g_free);
3153 : 1 : g_object_set_qdata_full(
3154 : : obj, ObjectBase::instance_strings_quark(), instance_strings,
3155 : : reinterpret_cast<GDestroyNotify>(g_ptr_array_unref));
3156 : : }
3157 : 2 : g_ptr_array_add(instance_strings, str);
3158 : 2 : }
|