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