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 <array>
13 : : #include <functional> // for mem_fn
14 : : #include <limits>
15 : : #include <string>
16 : : #include <tuple> // for tie
17 : : #include <unordered_set>
18 : : #include <utility> // for move
19 : : #include <vector>
20 : :
21 : : #include <girepository.h>
22 : : #include <girffi.h>
23 : : #include <glib-object.h>
24 : : #include <glib.h>
25 : :
26 : : #include <js/CallAndConstruct.h> // for IsCallable, JS_CallFunctionValue
27 : : #include <js/CallArgs.h>
28 : : #include <js/CharacterEncoding.h>
29 : : #include <js/Class.h>
30 : : #include <js/ComparisonOperators.h>
31 : : #include <js/ErrorReport.h> // for JS_ReportOutOfMemory
32 : : #include <js/Exception.h> // for JS_ClearPendingException
33 : : #include <js/GCAPI.h> // for JS_AddWeakPointerCompartmentCallback
34 : : #include <js/GCVector.h> // for MutableWrappedPtrOperations
35 : : #include <js/HeapAPI.h>
36 : : #include <js/MemoryFunctions.h> // for AddAssociatedMemory, RemoveAssoci...
37 : : #include <js/PropertyAndElement.h>
38 : : #include <js/PropertyDescriptor.h> // for JSPROP_PERMANENT, JSPROP_READONLY
39 : : #include <js/String.h>
40 : : #include <js/Symbol.h>
41 : : #include <js/TypeDecls.h>
42 : : #include <js/Utility.h> // for UniqueChars
43 : : #include <js/Value.h>
44 : : #include <js/ValueArray.h>
45 : : #include <js/Warnings.h>
46 : : #include <jsapi.h> // for JS_GetFunctionObject, IdVector
47 : : #include <jsfriendapi.h> // for JS_GetObjectFunction, GetFunctionNativeReserved
48 : : #include <mozilla/Maybe.h>
49 : : #include <mozilla/Result.h>
50 : : #include <mozilla/Unused.h>
51 : :
52 : : #include "gi/arg-inl.h"
53 : : #include "gi/arg-types-inl.h"
54 : : #include "gi/arg.h"
55 : : #include "gi/closure.h"
56 : : #include "gi/cwrapper.h"
57 : : #include "gi/function.h"
58 : : #include "gi/gjs_gi_trace.h"
59 : : #include "gi/info.h"
60 : : #include "gi/js-value-inl.h" // for Relaxed, c_value_to_js_checked
61 : : #include "gi/object.h"
62 : : #include "gi/repo.h"
63 : : #include "gi/toggle.h"
64 : : #include "gi/utils-inl.h" // for gjs_int_to_pointer
65 : : #include "gi/value.h"
66 : : #include "gi/wrapperutils.h"
67 : : #include "gjs/atoms.h"
68 : : #include "gjs/auto.h"
69 : : #include "gjs/context-private.h"
70 : : #include "gjs/deprecation.h"
71 : : #include "gjs/gerror-result.h"
72 : : #include "gjs/jsapi-class.h"
73 : : #include "gjs/jsapi-simple-wrapper.h"
74 : : #include "gjs/jsapi-util-args.h"
75 : : #include "gjs/jsapi-util-root.h"
76 : : #include "gjs/jsapi-util.h"
77 : : #include "gjs/macros.h"
78 : : #include "gjs/mem-private.h"
79 : : #include "gjs/profiler-private.h"
80 : : #include "util/log.h"
81 : :
82 : : class JSTracer;
83 : :
84 : : using mozilla::Err, mozilla::Maybe, mozilla::Ok, mozilla::Some;
85 : :
86 : : /* This is a trick to print out the sizes of the structs at compile time, in
87 : : * an error message. */
88 : : // template <int s> struct Measure;
89 : : // Measure<sizeof(ObjectInstance)> instance_size;
90 : : // Measure<sizeof(ObjectPrototype)> prototype_size;
91 : :
92 : : #if defined(__x86_64__) && defined(__clang__)
93 : : /* This isn't meant to be comprehensive, but should trip on at least one CI job
94 : : * if sizeof(ObjectInstance) is increased. */
95 : : static_assert(sizeof(ObjectInstance) <= 64,
96 : : "Think very hard before increasing the size of ObjectInstance. "
97 : : "There can be tens of thousands of them alive in a typical "
98 : : "gnome-shell run.");
99 : : #endif // x86-64 clang
100 : :
101 : : bool ObjectInstance::s_weak_pointer_callback = false;
102 : : decltype(ObjectInstance::s_wrapped_gobject_list)
103 : : ObjectInstance::s_wrapped_gobject_list;
104 : :
105 : : static const auto DISPOSED_OBJECT = std::numeric_limits<uintptr_t>::max();
106 : :
107 : : GJS_JSAPI_RETURN_CONVENTION
108 : : static JSObject* gjs_lookup_object_prototype_from_info(JSContext*, GIBaseInfo*,
109 : : GType);
110 : :
111 : : // clang-format off
112 [ + + ]: 3023 : G_DEFINE_QUARK(gjs::custom-type, ObjectBase::custom_type)
113 [ + + ]: 492 : G_DEFINE_QUARK(gjs::custom-property, ObjectBase::custom_property)
114 [ + + ]: 3 : G_DEFINE_QUARK(gjs::instance-strings, ObjectBase::instance_strings)
115 [ + + ]: 2015 : G_DEFINE_QUARK(gjs::disposed, ObjectBase::disposed)
116 : : // clang-format on
117 : :
118 : 6977 : [[nodiscard]] static GQuark gjs_object_priv_quark() {
119 : : static GQuark val = 0;
120 [ + + ]: 6977 : if (G_UNLIKELY (!val))
121 : 31 : val = g_quark_from_static_string ("gjs::private");
122 : :
123 : 6977 : return val;
124 : : }
125 : :
126 : 2887 : bool ObjectBase::is_custom_js_class() {
127 : 2887 : return !!g_type_get_qdata(gtype(), ObjectBase::custom_type_quark());
128 : : }
129 : :
130 : 1698 : void ObjectInstance::link() {
131 : 1698 : auto [_, done] = s_wrapped_gobject_list.insert(this);
132 : 1698 : g_assert(done);
133 : : mozilla::Unused << done;
134 : 1698 : }
135 : :
136 : 1714 : void ObjectInstance::unlink() { s_wrapped_gobject_list.erase(this); }
137 : :
138 : 7916 : const void* ObjectBase::jsobj_addr(void) const {
139 [ + + ]: 7916 : if (is_prototype())
140 : 735 : return nullptr;
141 : 7181 : return to_instance()->m_wrapper.debug_addr();
142 : : }
143 : :
144 : 4236 : bool ObjectInstance::check_gobject_disposed_or_finalized(
145 : : const char* for_what) const {
146 [ + + ]: 4236 : if (!m_gobj_disposed)
147 : 4196 : return true;
148 : :
149 [ + + ]: 40 : g_critical(
150 : : "Object %s (%p), has been already %s — impossible to %s it. This might "
151 : : "be caused by the object having been destroyed from C code using "
152 : : "something such as destroy(), dispose(), or remove() vfuncs.\n%s",
153 : : format_name().c_str(), m_ptr.get(),
154 : : m_gobj_finalized ? "finalized" : "disposed", for_what,
155 : : gjs_dumpstack_string().c_str());
156 : 40 : return false;
157 : : }
158 : :
159 : 3158 : bool ObjectInstance::check_gobject_finalized(const char* for_what) const {
160 [ + + ]: 3158 : if (check_gobject_disposed_or_finalized(for_what))
161 : 3124 : return true;
162 : :
163 : 34 : return !m_gobj_finalized;
164 : : }
165 : :
166 : : ObjectInstance *
167 : 3170 : ObjectInstance::for_gobject(GObject *gobj)
168 : : {
169 : 3170 : auto priv = static_cast<ObjectInstance *>(g_object_get_qdata(gobj,
170 : : gjs_object_priv_quark()));
171 : :
172 [ + + ]: 3170 : if (priv)
173 : 1981 : priv->check_js_object_finalized();
174 : :
175 : 3170 : return priv;
176 : : }
177 : :
178 : : void
179 : 1981 : ObjectInstance::check_js_object_finalized(void)
180 : : {
181 [ + + ]: 1981 : if (!m_uses_toggle_ref)
182 : 313 : return;
183 [ - + ]: 1668 : if (G_UNLIKELY(m_wrapper_finalized)) {
184 : 0 : g_critical(
185 : : "Object %p (a %s) resurfaced after the JS wrapper was finalized. "
186 : : "This is some library doing dubious memory management inside "
187 : : "dispose()",
188 : : m_ptr.get(), type_name());
189 : 0 : m_wrapper_finalized = false;
190 : 0 : g_assert(!m_wrapper); /* should associate again with a new wrapper */
191 : : }
192 : : }
193 : :
194 : 148 : ObjectPrototype* ObjectPrototype::for_gtype(GType gtype) {
195 : : return static_cast<ObjectPrototype*>(
196 : 148 : g_type_get_qdata(gtype, gjs_object_priv_quark()));
197 : : }
198 : :
199 : 127 : void ObjectPrototype::set_type_qdata(void) {
200 : 127 : g_type_set_qdata(m_gtype, gjs_object_priv_quark(), this);
201 : 127 : }
202 : :
203 : : void
204 : 1698 : ObjectInstance::set_object_qdata(void)
205 : : {
206 : 1698 : g_object_set_qdata_full(
207 : 1699 : m_ptr, gjs_object_priv_quark(), this, [](void* object) {
208 : 1 : auto* self = static_cast<ObjectInstance*>(object);
209 [ - + ]: 1 : if (G_UNLIKELY(!self->m_gobj_disposed)) {
210 : 0 : g_warning(
211 : : "Object %p (a %s) was finalized but we didn't track "
212 : : "its disposal",
213 : : self->m_ptr.get(), g_type_name(self->gtype()));
214 : 0 : self->m_gobj_disposed = true;
215 : : }
216 : 1 : self->m_gobj_finalized = true;
217 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
218 : : "Wrapped GObject %p finalized",
219 : : self->m_ptr.get());
220 : 1 : });
221 : 1698 : }
222 : :
223 : : void
224 : 1834 : ObjectInstance::unset_object_qdata(void)
225 : : {
226 : 1834 : auto priv_quark = gjs_object_priv_quark();
227 [ + + ]: 1834 : if (g_object_get_qdata(m_ptr, priv_quark) == this)
228 : 1696 : g_object_steal_qdata(m_ptr, priv_quark);
229 : 1834 : }
230 : :
231 : 749 : GParamSpec* ObjectPrototype::find_param_spec_from_id(
232 : : JSContext* cx, Gjs::AutoTypeClass<GObjectClass> const& object_class,
233 : : JS::HandleString key) {
234 : : /* First check for the ID in the cache */
235 : :
236 : 749 : JS::UniqueChars js_prop_name(JS_EncodeStringToUTF8(cx, key));
237 [ - + ]: 749 : if (!js_prop_name)
238 : 0 : return nullptr;
239 : :
240 : 749 : Gjs::AutoChar gname{gjs_hyphen_from_camel(js_prop_name.get())};
241 : 749 : GParamSpec* pspec = g_object_class_find_property(object_class, gname);
242 : :
243 [ + + ]: 749 : if (!pspec) {
244 : 2 : gjs_wrapper_throw_nonexistent_field(cx, m_gtype, js_prop_name.get());
245 : 2 : return nullptr;
246 : : }
247 : :
248 : 747 : return pspec;
249 : 749 : }
250 : :
251 : : /* A hook on adding a property to an object. This is called during a set
252 : : * property operation after all the resolve hooks on the prototype chain have
253 : : * failed to resolve. We use this to mark an object as needing toggle refs when
254 : : * custom state is set on it, because we need to keep the JS GObject wrapper
255 : : * alive in order not to lose custom "expando" properties.
256 : : */
257 : 10072 : bool ObjectBase::add_property(JSContext* cx, JS::HandleObject obj,
258 : : JS::HandleId id, JS::HandleValue value) {
259 : 10072 : auto* priv = ObjectBase::for_js(cx, obj);
260 : :
261 : : /* priv is null during init: property is not being added from JS */
262 [ + + ]: 10072 : if (!priv) {
263 : 1655 : debug_jsprop_static("Add property hook", id, obj);
264 : 1655 : return true;
265 : : }
266 [ + + ]: 8417 : if (priv->is_prototype())
267 : 6736 : return true;
268 : :
269 : 1681 : return priv->to_instance()->add_property_impl(cx, obj, id, value);
270 : : }
271 : :
272 : 1681 : bool ObjectInstance::add_property_impl(JSContext* cx, JS::HandleObject obj,
273 : : JS::HandleId id, JS::HandleValue) {
274 : 1681 : debug_jsprop("Add property hook", id, obj);
275 : :
276 [ + + ]: 1681 : if (is_custom_js_class())
277 : 1046 : return true;
278 : :
279 : 635 : ensure_uses_toggle_ref(cx);
280 : 635 : return true;
281 : : }
282 : :
283 : : template <typename TAG>
284 : 460 : bool ObjectBase::prop_getter(JSContext* cx, unsigned argc, JS::Value* vp) {
285 [ - + - + ]: 460 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
286 : :
287 : : auto* pspec = static_cast<GParamSpec*>(
288 : 460 : gjs_dynamic_property_private_slot(&args.callee()).toPrivate());
289 : :
290 [ - + + - : 1380 : std::string full_name{GJS_PROFILER_DYNAMIC_STRING(
- + - + -
+ ]
291 : : cx, priv->format_name() + "[\"" + pspec->name + "\"]")};
292 : 460 : AutoProfilerLabel label{cx, "property getter", full_name};
293 : :
294 : 460 : priv->debug_jsprop("Property getter", pspec->name, obj);
295 : :
296 [ + + ]: 460 : if (priv->is_prototype())
297 : 139 : return true;
298 : : /* Ignore silently; note that this is different from what we do for
299 : : * boxed types, for historical reasons */
300 : :
301 : 321 : return priv->to_instance()->prop_getter_impl<TAG>(cx, pspec, args.rval());
302 : 460 : }
303 : :
304 : : template <typename TAG>
305 : 321 : bool ObjectInstance::prop_getter_impl(JSContext* cx, GParamSpec* param,
306 : : JS::MutableHandleValue rval) {
307 [ - + ]: 321 : if (!check_gobject_finalized("get any property from")) {
308 : 0 : rval.setUndefined();
309 : 0 : return true;
310 : : }
311 : :
312 [ - + ]: 321 : if (param->flags & G_PARAM_DEPRECATED) {
313 [ # # ]: 0 : _gjs_warn_deprecated_once_per_callsite(cx, DeprecatedGObjectProperty,
314 : : {format_name(), param->name});
315 : : }
316 : :
317 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s",
318 : : param->name);
319 : :
320 : 321 : Gjs::AutoGValue gvalue(G_PARAM_SPEC_VALUE_TYPE(param));
321 : 321 : g_object_get_property(m_ptr, param->name, &gvalue);
322 : :
323 : : if constexpr (!std::is_same_v<TAG, void>) {
324 [ + - ]: 178 : if (Gjs::c_value_to_js_checked<TAG>(cx, Gjs::gvalue_get<TAG>(&gvalue),
325 : : rval))
326 : 178 : return true;
327 : :
328 : 0 : gjs_throw(cx, "Can't convert value %s got from %s::%s property",
329 : 0 : Gjs::gvalue_to_string<TAG>(&gvalue).c_str(),
330 : 0 : format_name().c_str(), param->name);
331 : 0 : return false;
332 : : } else {
333 : 143 : return gjs_value_from_g_value(cx, rval, &gvalue);
334 : : }
335 : 321 : }
336 : :
337 : 1 : bool ObjectBase::prop_getter_write_only(JSContext*, unsigned argc,
338 : : JS::Value* vp) {
339 : 1 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
340 : 1 : args.rval().setUndefined();
341 : 1 : return true;
342 : : }
343 : :
344 : : class ObjectPropertyInfoCaller {
345 : : public:
346 : : GI::AutoFunctionInfo func_info;
347 : : void* native_address;
348 : :
349 : 20 : explicit ObjectPropertyInfoCaller(GIFunctionInfo* info)
350 : 20 : : func_info(info, Gjs::TakeOwnership{}), native_address(nullptr) {}
351 : :
352 : 20 : Gjs::GErrorResult<> init() {
353 : : GIFunctionInvoker invoker;
354 : 20 : Gjs::AutoError error;
355 [ - + ]: 20 : if (!g_function_info_prep_invoker(func_info, &invoker, &error))
356 : 0 : return Err(std::move(error));
357 : 20 : native_address = invoker.native_address;
358 : 20 : g_function_invoker_destroy(&invoker);
359 : 20 : return Ok{};
360 : 20 : }
361 : : };
362 : :
363 : 60 : bool ObjectBase::prop_getter_func(JSContext* cx, unsigned argc, JS::Value* vp) {
364 [ - + - + ]: 60 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
365 : :
366 : : JS::RootedObject pspec_obj{
367 : 60 : cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()};
368 : : auto* info_caller =
369 : 60 : Gjs::SimpleWrapper::get<ObjectPropertyInfoCaller>(cx, pspec_obj);
370 : :
371 : 60 : const GI::AutoFunctionInfo& func_info = info_caller->func_info;
372 : 60 : GI::AutoPropertyInfo property_info{g_function_info_get_property(func_info)};
373 [ + - - + : 180 : std::string full_name{GJS_PROFILER_DYNAMIC_STRING(
- + - + ]
374 [ - + ]: 120 : cx, priv->format_name() + "[\"" + property_info.name() + "\"]")};
375 : 60 : AutoProfilerLabel label{cx, "property getter", full_name};
376 : :
377 : 60 : priv->debug_jsprop("Property getter", property_info.name(), obj);
378 : :
379 : : // Ignore silently; note that this is different from what we do for
380 : : // boxed types, for historical reasons
381 [ - + ]: 60 : if (priv->is_prototype())
382 : 0 : return true;
383 : :
384 : 60 : return priv->to_instance()->prop_getter_impl(cx, info_caller, args);
385 : 60 : }
386 : :
387 : : template <typename TAG>
388 : : [[nodiscard]]
389 : 60 : static bool simple_getter_caller(GObject* obj, void* native_address,
390 : : GIArgument* out_arg) {
391 : : using T = Gjs::Tag::RealT<TAG>;
392 : : using FuncType = T (*)(GObject*);
393 : 60 : FuncType func = reinterpret_cast<FuncType>(native_address);
394 : :
395 : 60 : gjs_arg_set<TAG>(out_arg, func(obj));
396 : 60 : return true;
397 : : }
398 : :
399 : : [[nodiscard]]
400 : 60 : static bool simple_getters_caller(GITypeInfo* type_info, GObject* obj,
401 : : void* native_address, GIArgument* out_arg) {
402 [ - - - - : 60 : switch (g_type_info_get_tag(type_info)) {
- - - - -
- - - - -
+ + - ]
403 : 0 : case GI_TYPE_TAG_VOID:
404 [ # # ]: 0 : if (g_type_info_is_pointer(type_info))
405 : 0 : return simple_getter_caller<void*>(obj, native_address,
406 : 0 : out_arg);
407 : 0 : return false;
408 : 0 : case GI_TYPE_TAG_BOOLEAN:
409 : 0 : return simple_getter_caller<Gjs::Tag::GBoolean>(obj, native_address,
410 : 0 : out_arg);
411 : 0 : case GI_TYPE_TAG_INT8:
412 : 0 : return simple_getter_caller<int8_t>(obj, native_address, out_arg);
413 : 0 : case GI_TYPE_TAG_UINT8:
414 : 0 : return simple_getter_caller<uint8_t>(obj, native_address, out_arg);
415 : 0 : case GI_TYPE_TAG_INT16:
416 : 0 : return simple_getter_caller<int16_t>(obj, native_address, out_arg);
417 : 0 : case GI_TYPE_TAG_UINT16:
418 : 0 : return simple_getter_caller<uint16_t>(obj, native_address, out_arg);
419 : 0 : case GI_TYPE_TAG_INT32:
420 : 0 : return simple_getter_caller<int32_t>(obj, native_address, out_arg);
421 : 0 : case GI_TYPE_TAG_UINT32:
422 : 0 : return simple_getter_caller<uint32_t>(obj, native_address, out_arg);
423 : 0 : case GI_TYPE_TAG_INT64:
424 : 0 : return simple_getter_caller<int64_t>(obj, native_address, out_arg);
425 : 0 : case GI_TYPE_TAG_UINT64:
426 : 0 : return simple_getter_caller<uint64_t>(obj, native_address, out_arg);
427 : 0 : case GI_TYPE_TAG_FLOAT:
428 : 0 : return simple_getter_caller<float>(obj, native_address, out_arg);
429 : 0 : case GI_TYPE_TAG_DOUBLE:
430 : 0 : return simple_getter_caller<double>(obj, native_address, out_arg);
431 : 0 : case GI_TYPE_TAG_GTYPE:
432 : 0 : return simple_getter_caller<Gjs::Tag::GType>(obj, native_address,
433 : 0 : out_arg);
434 : 0 : case GI_TYPE_TAG_UNICHAR:
435 : 0 : return simple_getter_caller<gunichar>(obj, native_address, out_arg);
436 : :
437 : 52 : case GI_TYPE_TAG_INTERFACE:
438 : : {
439 : : GI::AutoBaseInfo interface_info{
440 : 52 : g_type_info_get_interface(type_info)};
441 : :
442 [ + + + + : 52 : if (GI_IS_ENUM_INFO(interface_info)) {
+ + ]
443 : 12 : return simple_getter_caller<Gjs::Tag::Enum>(obj, native_address, out_arg);
444 : : }
445 : 40 : return simple_getter_caller<void*>(obj, native_address, out_arg);
446 : 52 : }
447 : :
448 : 8 : case GI_TYPE_TAG_UTF8:
449 : : case GI_TYPE_TAG_FILENAME:
450 : : case GI_TYPE_TAG_ARRAY:
451 : : case GI_TYPE_TAG_GLIST:
452 : : case GI_TYPE_TAG_GSLIST:
453 : : case GI_TYPE_TAG_GHASH:
454 : : case GI_TYPE_TAG_ERROR:
455 : 8 : return simple_getter_caller<void*>(obj, native_address, out_arg);
456 : : }
457 : :
458 : 0 : return false;
459 : : }
460 : :
461 : 60 : bool ObjectInstance::prop_getter_impl(JSContext* cx,
462 : : ObjectPropertyInfoCaller* info_caller,
463 : : JS::CallArgs const& args) {
464 [ - + ]: 60 : if (!check_gobject_finalized("get any property from")) {
465 : 0 : args.rval().setUndefined();
466 : 0 : return true;
467 : : }
468 : :
469 : 60 : const GI::AutoFunctionInfo& getter = info_caller->func_info;
470 : 60 : GI::AutoPropertyInfo property_info{g_function_info_get_property(getter)};
471 : 60 : GParamFlags flags = g_property_info_get_flags(property_info);
472 : :
473 [ + - ]: 60 : if (flags & G_PARAM_DEPRECATED ||
474 [ + - - + : 120 : g_base_info_is_deprecated(property_info) ||
- + ]
475 : 60 : g_base_info_is_deprecated(getter)) {
476 [ # # ]: 0 : _gjs_warn_deprecated_once_per_callsite(
477 : : cx, DeprecatedGObjectProperty,
478 : : {format_name(), property_info.name()});
479 : : }
480 : :
481 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s",
482 : : property_info.name());
483 : :
484 : : GIArgument ret;
485 : : std::array<GIArgument, 1> gi_args;
486 : 60 : gjs_arg_set(&gi_args[0], m_ptr.get());
487 : :
488 : : GITypeInfo type_info;
489 : 60 : g_callable_info_load_return_type(getter, &type_info);
490 [ - + ]: 60 : if (!simple_getters_caller(&type_info, m_ptr, info_caller->native_address,
491 : : &ret)) {
492 : 0 : const std::string& class_name = format_name();
493 : 0 : gjs_throw(cx, "Wrong type for %s::%s getter", class_name.c_str(),
494 : : property_info.name());
495 : 0 : return false;
496 : 0 : }
497 : :
498 : 60 : GITransfer transfer = g_callable_info_get_caller_owns(getter);
499 : :
500 [ - + ]: 60 : if (!gjs_value_from_gi_argument(cx, args.rval(), &type_info,
501 : : GJS_ARGUMENT_RETURN_VALUE, transfer,
502 : : &ret)) {
503 : : // Unlikely to happen, but we fallback to gvalue mode, just in case
504 : 0 : JS_ClearPendingException(cx);
505 : 0 : Gjs::AutoTypeClass<GObjectClass> klass{gtype()};
506 : : GParamSpec* pspec =
507 : 0 : g_object_class_find_property(klass, property_info.name());
508 [ # # ]: 0 : if (!pspec) {
509 : 0 : const std::string& class_name = format_name();
510 : 0 : gjs_throw(cx, "Error converting value got from %s::%s getter",
511 : : class_name.c_str(), property_info.name());
512 : 0 : return false;
513 : 0 : }
514 : 0 : return prop_getter_impl<void>(cx, pspec, args[0]);
515 : 0 : }
516 : :
517 : 60 : return gjs_gi_argument_release(cx, transfer, &type_info,
518 : 60 : GjsArgumentFlags::ARG_OUT, &ret);
519 : 60 : }
520 : :
521 : : class ObjectPropertyPspecCaller {
522 : : public:
523 : : GParamSpec* pspec;
524 : : void* native_address;
525 : :
526 : 66 : explicit ObjectPropertyPspecCaller(GParamSpec* param)
527 : 66 : : pspec(param), native_address(nullptr) {}
528 : :
529 : 66 : Gjs::GErrorResult<> init(GIFunctionInfo* info) {
530 : : GIFunctionInvoker invoker;
531 : 66 : Gjs::AutoError error;
532 [ - + ]: 66 : if (!g_function_info_prep_invoker(info, &invoker, &error))
533 : 0 : return Err(std::move(error));
534 : 66 : native_address = invoker.native_address;
535 : 66 : g_function_invoker_destroy(&invoker);
536 : 66 : return Ok{};
537 : 66 : }
538 : : };
539 : :
540 : : template <typename TAG, GITransfer TRANSFER>
541 : 115 : bool ObjectBase::prop_getter_simple_type_func(JSContext* cx, unsigned argc,
542 : : JS::Value* vp) {
543 [ - + - + ]: 115 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
544 : :
545 : 115 : JS::RootedObject pspec_obj(
546 : 115 : cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject());
547 : : auto* caller =
548 : 115 : Gjs::SimpleWrapper::get<ObjectPropertyPspecCaller>(cx, pspec_obj);
549 : :
550 [ - + + - : 345 : std::string full_name{GJS_PROFILER_DYNAMIC_STRING(
- + - + -
+ ]
551 : : cx, priv->format_name() + "[\"" + caller->pspec->name + "\"]")};
552 : 115 : AutoProfilerLabel label{cx, "property getter", full_name};
553 : :
554 : 230 : priv->debug_jsprop("Property getter",
555 : 115 : gjs_intern_string_to_id(cx, caller->pspec->name), obj);
556 : :
557 : : // Ignore silently; note that this is different from what we do for
558 : : // boxed types, for historical reasons
559 [ + + ]: 115 : if (priv->is_prototype())
560 : 23 : return true;
561 : :
562 : 92 : return priv->to_instance()->prop_getter_impl<TAG, TRANSFER>(cx, caller,
563 : 92 : args);
564 : 115 : }
565 : :
566 : : template <typename TAG, GITransfer TRANSFER>
567 : 92 : bool ObjectInstance::prop_getter_impl(JSContext* cx,
568 : : ObjectPropertyPspecCaller* pspec_caller,
569 : : JS::CallArgs const& args) {
570 [ + + ]: 92 : if (!check_gobject_finalized("get any property from")) {
571 : 2 : args.rval().setUndefined();
572 : 2 : return true;
573 : : }
574 : :
575 [ + + ]: 90 : if (pspec_caller->pspec->flags & G_PARAM_DEPRECATED) {
576 [ + + ]: 7 : _gjs_warn_deprecated_once_per_callsite(
577 : : cx, DeprecatedGObjectProperty,
578 : 1 : {format_name(), pspec_caller->pspec->name});
579 : : }
580 : :
581 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s",
582 : : pspec_caller->pspec->name);
583 : :
584 : : using T = Gjs::Tag::RealT<TAG>;
585 : : using FuncType = T (*)(GObject*);
586 : 90 : FuncType func = reinterpret_cast<FuncType>(pspec_caller->native_address);
587 : 90 : T retval = func(m_ptr);
588 [ - + ]: 90 : if (!Gjs::c_value_to_js_checked<TAG>(cx, retval, args.rval()))
589 : 0 : return false;
590 : :
591 : : if constexpr (TRANSFER != GI_TRANSFER_NOTHING) {
592 : : static_assert(std::is_same_v<T, char*>, "Unexpected type to release");
593 : 0 : g_free(retval);
594 : : }
595 : :
596 : 90 : return true;
597 : : }
598 : :
599 : 3815 : [[nodiscard]] static GI::AutoFieldInfo lookup_field_info(GIObjectInfo* info,
600 : : const char* name) {
601 : 3815 : int n_fields = g_object_info_get_n_fields(info);
602 : : int ix;
603 : 3815 : GI::AutoFieldInfo retval;
604 : :
605 [ + + ]: 13229 : for (ix = 0; ix < n_fields; ix++) {
606 : 9419 : retval = g_object_info_get_field(info, ix);
607 [ + + ]: 9419 : if (strcmp(name, retval.name()) == 0)
608 : 5 : break;
609 : 9414 : retval.reset();
610 : : }
611 : :
612 [ + + - + : 3815 : if (!retval || !(g_field_info_get_flags(retval) & GI_FIELD_IS_READABLE))
+ + ]
613 : 3810 : return nullptr;
614 : :
615 : 5 : return retval;
616 : 3815 : }
617 : :
618 : 8 : bool ObjectBase::field_getter(JSContext* cx, unsigned argc, JS::Value* vp) {
619 [ - + - + ]: 8 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
620 : :
621 : : JS::RootedObject field_info_obj{
622 : 8 : cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()};
623 : : auto const& field_info =
624 : 8 : *Gjs::SimpleWrapper::get<GI::AutoFieldInfo>(cx, field_info_obj);
625 : :
626 [ + - - + : 24 : std::string full_name{GJS_PROFILER_DYNAMIC_STRING(
- + - + ]
627 [ - + ]: 16 : cx, priv->format_name() + "[\"" + field_info.name() + "\"]")};
628 : 8 : AutoProfilerLabel label{cx, "field getter", full_name};
629 : :
630 : 8 : priv->debug_jsprop("Field getter", field_info.name(), obj);
631 : :
632 [ - + ]: 8 : if (priv->is_prototype())
633 : 0 : return true;
634 : : /* Ignore silently; note that this is different from what we do for
635 : : * boxed types, for historical reasons */
636 : :
637 : 8 : return priv->to_instance()->field_getter_impl(cx, field_info, args.rval());
638 : 8 : }
639 : :
640 : 8 : bool ObjectInstance::field_getter_impl(JSContext* cx,
641 : : GI::AutoFieldInfo const& field,
642 : : JS::MutableHandleValue rval) {
643 [ - + ]: 8 : if (!check_gobject_finalized("get any property from"))
644 : 0 : return true;
645 : :
646 : : GITypeTag tag;
647 : 8 : GIArgument arg = { 0 };
648 : :
649 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Overriding %s with GObject field",
650 : : field.name());
651 : :
652 : 8 : GI::AutoTypeInfo type{g_field_info_get_type(field)};
653 : 8 : tag = g_type_info_get_tag(type);
654 : :
655 [ + + ]: 8 : switch (tag) {
656 : 2 : case GI_TYPE_TAG_ARRAY:
657 : : case GI_TYPE_TAG_ERROR:
658 : : case GI_TYPE_TAG_GHASH:
659 : : case GI_TYPE_TAG_GLIST:
660 : : case GI_TYPE_TAG_GSLIST:
661 : : case GI_TYPE_TAG_INTERFACE:
662 : 2 : gjs_throw(cx,
663 : : "Can't get field %s; GObject introspection supports only "
664 : : "fields with simple types, not %s",
665 : : field.name(), g_type_tag_to_string(tag));
666 : 2 : return false;
667 : :
668 : 6 : default:
669 : 6 : break;
670 : : }
671 : :
672 [ - + ]: 6 : if (!g_field_info_get_field(field, m_ptr, &arg)) {
673 : 0 : gjs_throw(cx, "Error getting field %s from object", field.name());
674 : 0 : return false;
675 : : }
676 : :
677 : 6 : return gjs_value_from_gi_argument(cx, rval, type, GJS_ARGUMENT_FIELD,
678 : 6 : GI_TRANSFER_EVERYTHING, &arg);
679 : : /* transfer is irrelevant because g_field_info_get_field() doesn't
680 : : * handle boxed types */
681 : 8 : }
682 : :
683 : : /* Dynamic setter for GObject properties. Returns false on OOM/exception.
684 : : * args.rval() becomes the "stored value" for the property. */
685 : : template <typename TAG>
686 : 199 : bool ObjectBase::prop_setter(JSContext* cx, unsigned argc, JS::Value* vp) {
687 [ - + - + ]: 199 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
688 : :
689 : : auto* pspec = static_cast<GParamSpec*>(
690 : 199 : gjs_dynamic_property_private_slot(&args.callee()).toPrivate());
691 : :
692 [ - + + - : 597 : std::string full_name{GJS_PROFILER_DYNAMIC_STRING(
- + - + -
+ ]
693 : : cx, priv->format_name() + "[\"" + pspec->name + "\"]")};
694 : 199 : AutoProfilerLabel label{cx, "property setter", full_name};
695 : :
696 : 199 : priv->debug_jsprop("Property setter", pspec->name, obj);
697 : :
698 [ - + ]: 199 : if (priv->is_prototype())
699 : 0 : return true;
700 : : /* Ignore silently; note that this is different from what we do for
701 : : * boxed types, for historical reasons */
702 : :
703 : : /* Clear the JS stored value, to avoid keeping additional references */
704 : 199 : args.rval().setUndefined();
705 : :
706 : 199 : return priv->to_instance()->prop_setter_impl<TAG>(cx, pspec, args[0]);
707 : 199 : }
708 : :
709 : : template <typename TAG>
710 : 199 : bool ObjectInstance::prop_setter_impl(JSContext* cx, GParamSpec* param_spec,
711 : : JS::HandleValue value) {
712 [ - + ]: 199 : if (!check_gobject_finalized("set any property on"))
713 : 0 : return true;
714 : :
715 [ - + ]: 199 : if (param_spec->flags & G_PARAM_DEPRECATED) {
716 [ # # ]: 0 : _gjs_warn_deprecated_once_per_callsite(
717 : : cx, DeprecatedGObjectProperty, {format_name(), param_spec->name});
718 : : }
719 : :
720 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop %s",
721 : : param_spec->name);
722 : :
723 : 199 : Gjs::AutoGValue gvalue(G_PARAM_SPEC_VALUE_TYPE(param_spec));
724 : :
725 : : using T = Gjs::Tag::RealT<TAG>;
726 : : if constexpr (std::is_same_v<T, void>) {
727 [ - + ]: 93 : if (!gjs_value_to_g_value(cx, value, &gvalue))
728 : 0 : return false;
729 : : } else if constexpr (std::is_arithmetic_v< // NOLINT(readability/braces)
730 : : T> &&
731 : : !Gjs::type_has_js_getter<TAG>()) {
732 : 64 : bool out_of_range = false;
733 : :
734 : 64 : Gjs::Tag::JSValuePackT<TAG> val{};
735 : : using HolderTag = Gjs::Tag::JSValuePackTag<TAG>;
736 [ - + ]: 64 : if (!Gjs::js_value_to_c_checked<T, HolderTag>(cx, value, &val,
737 : : &out_of_range)) {
738 : 0 : gjs_throw(cx, "Can't convert value %s to set %s::%s property",
739 : 0 : gjs_debug_value(value).c_str(), format_name().c_str(),
740 : : param_spec->name);
741 : 7 : return false;
742 : : }
743 : :
744 [ + + ]: 64 : if (out_of_range) {
745 : 14 : gjs_throw(cx, "value %s is out of range for %s (type %s)",
746 : 14 : std::to_string(val).c_str(), param_spec->name,
747 : : Gjs::static_type_name<TAG>());
748 : 7 : return false;
749 : : }
750 : :
751 : 57 : Gjs::gvalue_set<TAG>(&gvalue, val);
752 : : } else {
753 : : T native_value;
754 [ - + ]: 42 : if (!Gjs::js_value_to_c<TAG>(cx, value, &native_value)) {
755 : 0 : gjs_throw(cx, "Can't convert %s value to set %s::%s property",
756 : 0 : gjs_debug_value(value).c_str(), format_name().c_str(),
757 : : param_spec->name);
758 : 0 : return false;
759 : : }
760 : :
761 : : if constexpr (std::is_pointer_v<T>) {
762 : 16 : Gjs::gvalue_take<TAG>(&gvalue, g_steal_pointer(&native_value));
763 : : } else {
764 : 26 : Gjs::gvalue_set<TAG>(&gvalue, native_value);
765 : : }
766 : : }
767 : :
768 : 192 : g_object_set_property(m_ptr, param_spec->name, &gvalue);
769 : :
770 : 192 : return true;
771 : 199 : }
772 : :
773 : 2 : bool ObjectBase::prop_setter_read_only(JSContext* cx, unsigned argc,
774 : : JS::Value* vp) {
775 [ - + - + ]: 2 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
776 : : auto* pspec = static_cast<GParamSpec*>(
777 : 2 : gjs_dynamic_property_private_slot(&args.callee()).toPrivate());
778 : : // Prevent setting the property even in JS
779 : 2 : return gjs_wrapper_throw_readonly_field(cx, priv->to_instance()->gtype(),
780 : 2 : pspec->name);
781 : 2 : }
782 : :
783 : 60 : bool ObjectBase::prop_setter_func(JSContext* cx, unsigned argc, JS::Value* vp) {
784 [ - + - + ]: 60 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
785 : :
786 : : JS::RootedObject func_obj{
787 : 60 : cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()};
788 : : auto* info_caller =
789 : 60 : Gjs::SimpleWrapper::get<ObjectPropertyInfoCaller>(cx, func_obj);
790 : :
791 : 60 : const GI::AutoFunctionInfo& func_info = info_caller->func_info;
792 : 60 : GI::AutoPropertyInfo property_info{g_function_info_get_property(func_info)};
793 [ + - - + : 180 : std::string full_name{GJS_PROFILER_DYNAMIC_STRING(
- + - + ]
794 [ - + ]: 120 : cx, priv->format_name() + "[\"" + property_info.name() + "\"]")};
795 : 60 : AutoProfilerLabel label{cx, "property setter", full_name};
796 : :
797 : 60 : priv->debug_jsprop("Property setter", property_info.name(), obj);
798 : :
799 : : // Ignore silently; note that this is different from what we do for
800 : : // boxed types, for historical reasons
801 [ - + ]: 60 : if (priv->is_prototype())
802 : 0 : return true;
803 : :
804 : 60 : return priv->to_instance()->prop_setter_impl(cx, info_caller, args);
805 : 60 : }
806 : :
807 : : template <typename TAG>
808 : : [[nodiscard]]
809 : 60 : static bool simple_setter_caller(GIArgument* arg, GObject* obj,
810 : : void* native_address) {
811 : : using FuncType = void (*)(GObject*, Gjs::Tag::RealT<TAG>);
812 : 60 : FuncType func = reinterpret_cast<FuncType>(native_address);
813 : :
814 : 60 : func(obj, gjs_arg_get<TAG>(arg));
815 : 60 : return true;
816 : : }
817 : :
818 : : [[nodiscard]]
819 : 60 : static bool simple_setters_caller(GITypeInfo* type_info, GIArgument* arg,
820 : : GObject* obj, void* native_address) {
821 [ - - - - : 60 : switch (g_type_info_get_tag(type_info)) {
- - - - -
- - - - -
+ + - ]
822 : 0 : case GI_TYPE_TAG_VOID:
823 [ # # ]: 0 : if (g_type_info_is_pointer(type_info))
824 : 0 : return simple_setter_caller<void*>(arg, obj, native_address);
825 : 0 : return false;
826 : 0 : case GI_TYPE_TAG_BOOLEAN:
827 : 0 : return simple_setter_caller<Gjs::Tag::GBoolean>(arg, obj,
828 : 0 : native_address);
829 : 0 : case GI_TYPE_TAG_INT8:
830 : 0 : return simple_setter_caller<int8_t>(arg, obj, native_address);
831 : 0 : case GI_TYPE_TAG_UINT8:
832 : 0 : return simple_setter_caller<uint8_t>(arg, obj, native_address);
833 : 0 : case GI_TYPE_TAG_INT16:
834 : 0 : return simple_setter_caller<int16_t>(arg, obj, native_address);
835 : 0 : case GI_TYPE_TAG_UINT16:
836 : 0 : return simple_setter_caller<uint16_t>(arg, obj, native_address);
837 : 0 : case GI_TYPE_TAG_INT32:
838 : 0 : return simple_setter_caller<int32_t>(arg, obj, native_address);
839 : 0 : case GI_TYPE_TAG_UINT32:
840 : 0 : return simple_setter_caller<uint32_t>(arg, obj, native_address);
841 : 0 : case GI_TYPE_TAG_INT64:
842 : 0 : return simple_setter_caller<int64_t>(arg, obj, native_address);
843 : 0 : case GI_TYPE_TAG_UINT64:
844 : 0 : return simple_setter_caller<uint64_t>(arg, obj, native_address);
845 : 0 : case GI_TYPE_TAG_FLOAT:
846 : 0 : return simple_setter_caller<float>(arg, obj, native_address);
847 : 0 : case GI_TYPE_TAG_DOUBLE:
848 : 0 : return simple_setter_caller<double>(arg, obj, native_address);
849 : 0 : case GI_TYPE_TAG_GTYPE:
850 : 0 : return simple_setter_caller<Gjs::Tag::GType>(arg, obj,
851 : 0 : native_address);
852 : 0 : case GI_TYPE_TAG_UNICHAR:
853 : 0 : return simple_setter_caller<gunichar>(arg, obj, native_address);
854 : :
855 : 54 : case GI_TYPE_TAG_INTERFACE:
856 : : {
857 : : GI::AutoBaseInfo interface_info{
858 : 54 : g_type_info_get_interface(type_info)};
859 : :
860 [ + + + + : 54 : if (GI_IS_ENUM_INFO(interface_info)) {
+ + ]
861 : 12 : return simple_setter_caller<Gjs::Tag::Enum>(arg, obj, native_address);
862 : : }
863 : 42 : return simple_setter_caller<void*>(arg, obj, native_address);
864 : 54 : }
865 : :
866 : 6 : case GI_TYPE_TAG_UTF8:
867 : : case GI_TYPE_TAG_FILENAME:
868 : : case GI_TYPE_TAG_ARRAY:
869 : : case GI_TYPE_TAG_GLIST:
870 : : case GI_TYPE_TAG_GSLIST:
871 : : case GI_TYPE_TAG_GHASH:
872 : : case GI_TYPE_TAG_ERROR:
873 : 6 : return simple_setter_caller<void*>(arg, obj, native_address);
874 : : }
875 : :
876 : 0 : return false;
877 : : }
878 : :
879 : 60 : bool ObjectInstance::prop_setter_impl(JSContext* cx,
880 : : ObjectPropertyInfoCaller* info_caller,
881 : : JS::CallArgs const& args) {
882 [ - + ]: 60 : if (!check_gobject_finalized("set any property on"))
883 : 0 : return true;
884 : :
885 : 60 : const GI::AutoFunctionInfo& setter = info_caller->func_info;
886 : 60 : GI::AutoPropertyInfo property_info{g_function_info_get_property(setter)};
887 : 60 : GParamFlags flags = g_property_info_get_flags(property_info);
888 : :
889 [ + - ]: 60 : if (flags & G_PARAM_DEPRECATED ||
890 [ + - - + : 120 : g_base_info_is_deprecated(property_info) ||
- + ]
891 : 60 : g_base_info_is_deprecated(setter)) {
892 [ # # ]: 0 : _gjs_warn_deprecated_once_per_callsite(
893 : : cx, DeprecatedGObjectProperty,
894 : : {format_name(), property_info.name()});
895 : : }
896 : :
897 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop via setter %s",
898 : : property_info.name());
899 : :
900 : : GIArgInfo arg_info;
901 : 60 : g_callable_info_load_arg(setter, 0, &arg_info);
902 : : GITypeInfo type_info;
903 : 60 : g_arg_info_load_type(&arg_info, &type_info);
904 : 60 : GITransfer transfer = g_arg_info_get_ownership_transfer(&arg_info);
905 : 60 : JS::RootedValue value{cx, args[0]};
906 : : GIArgument arg;
907 : :
908 [ - + ]: 60 : if (!gjs_value_to_gi_argument(cx, value, &type_info, property_info.name(),
909 : : GJS_ARGUMENT_ARGUMENT, transfer,
910 : : GjsArgumentFlags::ARG_IN, &arg)) {
911 : : // Unlikely to happen, but we fallback to gvalue mode, just in case
912 : 0 : JS_ClearPendingException(cx);
913 : 0 : Gjs::AutoTypeClass<GObjectClass> klass{gtype()};
914 : : GParamSpec* pspec =
915 : 0 : g_object_class_find_property(klass, property_info.name());
916 [ # # ]: 0 : if (!pspec) {
917 : 0 : const std::string& class_name = format_name();
918 : 0 : gjs_throw(cx, "Error converting value to call %s::%s setter",
919 : : class_name.c_str(), property_info.name());
920 : 0 : return false;
921 : 0 : }
922 : 0 : return prop_setter_impl<void>(cx, pspec, value);
923 : 0 : }
924 : :
925 [ - + ]: 60 : if (!simple_setters_caller(&type_info, &arg, m_ptr,
926 : : info_caller->native_address)) {
927 : 0 : const std::string& class_name = format_name();
928 : 0 : gjs_throw(cx, "Wrong type for %s::%s setter", class_name.c_str(),
929 : : property_info.name());
930 : 0 : return false;
931 : 0 : }
932 : :
933 : 60 : return gjs_gi_argument_release_in_arg(cx, transfer, &type_info, &arg);
934 : 60 : }
935 : :
936 : : template <typename TAG, GITransfer TRANSFER>
937 : 86 : bool ObjectBase::prop_setter_simple_type_func(JSContext* cx, unsigned argc,
938 : : JS::Value* vp) {
939 [ - + - + ]: 86 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
940 : :
941 : 86 : JS::RootedObject pspec_obj(
942 : 86 : cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject());
943 : : auto* caller =
944 : 86 : Gjs::SimpleWrapper::get<ObjectPropertyPspecCaller>(cx, pspec_obj);
945 : :
946 [ - + + - : 258 : std::string full_name{GJS_PROFILER_DYNAMIC_STRING(
- + - + -
+ ]
947 : : cx, priv->format_name() + "[" + caller->pspec->name + "]")};
948 : 86 : AutoProfilerLabel label{cx, "property setter", full_name};
949 : :
950 : 86 : priv->debug_jsprop("Property setter", caller->pspec->name, obj);
951 : :
952 : : // Ignore silently; note that this is different from what we do for
953 : : // boxed types, for historical reasons
954 [ - + ]: 86 : if (priv->is_prototype())
955 : 0 : return true;
956 : :
957 : 86 : return priv->to_instance()->prop_setter_impl<TAG, TRANSFER>(cx, caller,
958 : 86 : args);
959 : 86 : }
960 : :
961 : : template <typename TAG, GITransfer TRANSFER>
962 : 86 : bool ObjectInstance::prop_setter_impl(JSContext* cx,
963 : : ObjectPropertyPspecCaller* pspec_caller,
964 : : JS::CallArgs const& args) {
965 [ + + ]: 86 : if (!check_gobject_finalized("set any property on"))
966 : 1 : return true;
967 : :
968 : : gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop via setter %s",
969 : : pspec_caller->pspec->name);
970 : :
971 [ + + ]: 85 : if (pspec_caller->pspec->flags & G_PARAM_DEPRECATED) {
972 [ + + ]: 7 : _gjs_warn_deprecated_once_per_callsite(
973 : : cx, DeprecatedGObjectProperty,
974 : 1 : {format_name(), pspec_caller->pspec->name});
975 : : }
976 : :
977 : : using T = Gjs::Tag::RealT<TAG>;
978 : : using FuncType = void (*)(GObject*, T);
979 : 85 : FuncType func = reinterpret_cast<FuncType>(pspec_caller->native_address);
980 : :
981 : : if constexpr (std::is_arithmetic_v<T> && !Gjs::type_has_js_getter<TAG>()) {
982 : 61 : bool out_of_range = false;
983 : :
984 : 61 : Gjs::Tag::JSValuePackT<TAG> native_value{};
985 : : using HolderTag = Gjs::Tag::JSValuePackTag<TAG>;
986 [ - + ]: 61 : if (!Gjs::js_value_to_c_checked<T, HolderTag>(
987 : : cx, args[0], &native_value, &out_of_range))
988 : 7 : return false;
989 : :
990 [ + + ]: 61 : if (out_of_range) {
991 : 14 : gjs_throw(cx, "value %s is out of range for %s (type %s)",
992 : 14 : std::to_string(native_value).c_str(),
993 : 7 : pspec_caller->pspec->name, Gjs::static_type_name<TAG>());
994 : 7 : return false;
995 : : }
996 : :
997 : 54 : func(m_ptr, native_value);
998 : : } else {
999 : : T native_value;
1000 [ - + ]: 24 : if (!Gjs::js_value_to_c<TAG>(cx, args[0], &native_value))
1001 : 0 : return false;
1002 : :
1003 : 24 : func(m_ptr, native_value);
1004 : :
1005 : : if constexpr (TRANSFER == GI_TRANSFER_NOTHING && std::is_pointer_v<T>) {
1006 : : static_assert(std::is_same_v<T, char*>,
1007 : : "Unexpected type to release");
1008 : 8 : g_free(native_value);
1009 : : }
1010 : : }
1011 : :
1012 : 78 : return true;
1013 : : }
1014 : :
1015 : 1 : bool ObjectBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) {
1016 [ - + - + ]: 1 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
1017 : :
1018 : : JS::RootedObject field_info_obj{
1019 : 1 : cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()};
1020 : : auto const& field_info =
1021 : 1 : *Gjs::SimpleWrapper::get<GI::AutoFieldInfo>(cx, field_info_obj);
1022 : :
1023 [ + - - + : 3 : std::string full_name{GJS_PROFILER_DYNAMIC_STRING(
- + - + ]
1024 [ - + ]: 2 : cx, priv->format_name() + "[\"" + field_info.name() + "\"]")};
1025 : 1 : AutoProfilerLabel label{cx, "field setter", full_name};
1026 : :
1027 : 1 : priv->debug_jsprop("Field setter", field_info.name(), obj);
1028 : :
1029 [ - + ]: 1 : if (priv->is_prototype())
1030 : 0 : return true;
1031 : : /* Ignore silently; note that this is different from what we do for
1032 : : * boxed types, for historical reasons */
1033 : :
1034 : : /* We have to update args.rval(), because JS caches it as the property's "stored
1035 : : * value" (https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/Stored_value)
1036 : : * and so subsequent gets would get the stored value instead of accessing
1037 : : * the field */
1038 : 1 : args.rval().setUndefined();
1039 : :
1040 : 1 : return priv->to_instance()->field_setter_not_impl(cx, field_info);
1041 : 1 : }
1042 : :
1043 : 1 : bool ObjectInstance::field_setter_not_impl(JSContext* cx,
1044 : : GI::AutoFieldInfo const& field) {
1045 [ - + ]: 1 : if (!check_gobject_finalized("set GObject field on"))
1046 : 0 : return true;
1047 : :
1048 : : /* As far as I know, GI never exposes GObject instance struct fields as
1049 : : * writable, so no need to implement this for the time being */
1050 [ - + ]: 1 : if (g_field_info_get_flags(field) & GI_FIELD_IS_WRITABLE) {
1051 : 0 : g_message(
1052 : : "Field %s of a GObject is writable, but setting it is not "
1053 : : "implemented",
1054 : : field.name());
1055 : 0 : return true;
1056 : : }
1057 : :
1058 : 1 : return gjs_wrapper_throw_readonly_field(cx, gtype(),
1059 : 1 : g_base_info_get_name(field));
1060 : : }
1061 : :
1062 : 2 : bool ObjectPrototype::is_vfunc_unchanged(GIVFuncInfo* info) {
1063 : 2 : Gjs::AutoError error;
1064 : 2 : GType ptype = g_type_parent(m_gtype);
1065 : : gpointer addr1, addr2;
1066 : :
1067 : 2 : addr1 = g_vfunc_info_get_address(info, m_gtype, &error);
1068 [ - + ]: 2 : if (error)
1069 : 0 : return false;
1070 : :
1071 : 2 : addr2 = g_vfunc_info_get_address(info, ptype, &error);
1072 [ + + ]: 2 : if (error)
1073 : 1 : return false;
1074 : :
1075 : 1 : return addr1 == addr2;
1076 : 2 : }
1077 : :
1078 : 20 : [[nodiscard]] static GI::AutoVFuncInfo find_vfunc_on_parents(
1079 : : GIObjectInfo* info, const char* name, bool* out_defined_by_parent) {
1080 : 20 : bool defined_by_parent = false;
1081 : :
1082 : : /* ref the first info so that we don't destroy
1083 : : * it when unrefing parents later */
1084 : 20 : GI::AutoObjectInfo parent{info, Gjs::TakeOwnership{}};
1085 : :
1086 : : /* Since it isn't possible to override a vfunc on
1087 : : * an interface without reimplementing it, we don't need
1088 : : * to search the parent types when looking for a vfunc. */
1089 : : GI::AutoVFuncInfo vfunc{
1090 : 20 : g_object_info_find_vfunc_using_interfaces(parent, name, nullptr)};
1091 [ + + + + : 56 : while (!vfunc && parent) {
+ + ]
1092 : 36 : parent = g_object_info_get_parent(parent);
1093 [ + + ]: 36 : if (parent)
1094 : 19 : vfunc = g_object_info_find_vfunc(parent, name);
1095 : :
1096 : 36 : defined_by_parent = true;
1097 : : }
1098 : :
1099 [ + - ]: 20 : if (out_defined_by_parent)
1100 : 20 : *out_defined_by_parent = defined_by_parent;
1101 : :
1102 : 40 : return vfunc;
1103 : 20 : }
1104 : :
1105 : : /* Taken from GLib */
1106 : 3768 : static void canonicalize_key(const Gjs::AutoChar& key) {
1107 [ + + ]: 46738 : for (char* p = key; *p != 0; p++) {
1108 : 42970 : char c = *p;
1109 : :
1110 [ + + + + : 42970 : if (c != '-' && (c < '0' || c > '9') && (c < 'A' || c > 'Z') &&
+ + + + +
- + + ]
1111 [ - + ]: 38588 : (c < 'a' || c > 'z'))
1112 : 2991 : *p = '-';
1113 : : }
1114 : 3768 : }
1115 : :
1116 : : /* @name must already be canonicalized */
1117 : : [[nodiscard]]
1118 : 1486 : static Maybe<GI::AutoPropertyInfo> get_ginterface_property_by_name(
1119 : : GIInterfaceInfo* info, const char* name) {
1120 : 1486 : int n_props = g_interface_info_get_n_properties(info);
1121 : :
1122 [ + + ]: 1637 : for (int ix = 0; ix < n_props; ix++) {
1123 : 157 : GI::AutoPropertyInfo prop_info{g_interface_info_get_property(info, ix)};
1124 [ + + ]: 157 : if (strcmp(name, prop_info.name()) == 0)
1125 : 6 : return Some(prop_info);
1126 [ + + ]: 157 : }
1127 : :
1128 : 1480 : return {};
1129 : : }
1130 : :
1131 : : [[nodiscard]]
1132 : 3577 : static Maybe<GI::AutoPropertyInfo> get_gobject_property_info(GIObjectInfo* info,
1133 : : const char* name) {
1134 : 3577 : int n_props = g_object_info_get_n_properties(info);
1135 [ + + ]: 19657 : for (int ix = 0; ix < n_props; ix++) {
1136 : 16496 : GI::AutoPropertyInfo prop_info{g_object_info_get_property(info, ix)};
1137 [ + + ]: 16496 : if (strcmp(name, prop_info.name()) == 0)
1138 : 416 : return Some(prop_info);
1139 [ + + ]: 16496 : }
1140 : :
1141 : 3161 : int n_ifaces = g_object_info_get_n_interfaces(info);
1142 [ + + ]: 4543 : for (int ix = 0; ix < n_ifaces; ix++) {
1143 : 1385 : GI::AutoInterfaceInfo iface_info{g_object_info_get_interface(info, ix)};
1144 [ + + ]: 1385 : if (Maybe<GI::AutoPropertyInfo> prop_info =
1145 : 1385 : get_ginterface_property_by_name(iface_info, name))
1146 [ + + ]: 1385 : return prop_info;
1147 [ + + ]: 1385 : }
1148 : 3158 : return {};
1149 : : }
1150 : :
1151 : : [[nodiscard]]
1152 : 55 : static JSNative get_getter_for_type(GITypeInfo* type_info,
1153 : : GITransfer transfer) {
1154 [ + + + - : 55 : switch (g_type_info_get_tag(type_info)) {
- + + + +
+ + - - +
+ ]
1155 : 27 : case GI_TYPE_TAG_BOOLEAN:
1156 : 27 : return ObjectBase::prop_getter_simple_type_func<Gjs::Tag::GBoolean>;
1157 : 1 : case GI_TYPE_TAG_INT8:
1158 : 1 : return ObjectBase::prop_getter_simple_type_func<int8_t>;
1159 : 1 : case GI_TYPE_TAG_UINT8:
1160 : 1 : return ObjectBase::prop_getter_simple_type_func<uint8_t>;
1161 : 0 : case GI_TYPE_TAG_INT16:
1162 : 0 : return ObjectBase::prop_getter_simple_type_func<int16_t>;
1163 : 0 : case GI_TYPE_TAG_UINT16:
1164 : 0 : return ObjectBase::prop_getter_simple_type_func<uint16_t>;
1165 : 3 : case GI_TYPE_TAG_INT32:
1166 : 3 : return ObjectBase::prop_getter_simple_type_func<int32_t>;
1167 : 1 : case GI_TYPE_TAG_UINT32:
1168 : 1 : return ObjectBase::prop_getter_simple_type_func<uint32_t>;
1169 : 2 : case GI_TYPE_TAG_INT64:
1170 : 2 : return ObjectBase::prop_getter_simple_type_func<int64_t>;
1171 : 2 : case GI_TYPE_TAG_UINT64:
1172 : 2 : return ObjectBase::prop_getter_simple_type_func<uint64_t>;
1173 : 1 : case GI_TYPE_TAG_FLOAT:
1174 : 1 : return ObjectBase::prop_getter_simple_type_func<float>;
1175 : 1 : case GI_TYPE_TAG_DOUBLE:
1176 : 1 : return ObjectBase::prop_getter_simple_type_func<double>;
1177 : 0 : case GI_TYPE_TAG_GTYPE:
1178 : 0 : return ObjectBase::prop_getter_simple_type_func<Gjs::Tag::GType>;
1179 : 0 : case GI_TYPE_TAG_UNICHAR:
1180 : 0 : return ObjectBase::prop_getter_simple_type_func<gunichar>;
1181 : 6 : case GI_TYPE_TAG_FILENAME:
1182 : : case GI_TYPE_TAG_UTF8:
1183 [ + - ]: 6 : if (transfer == GI_TRANSFER_NOTHING) {
1184 : 6 : return ObjectBase::prop_getter_simple_type_func<
1185 : : const char*, GI_TRANSFER_NOTHING>;
1186 : : } else {
1187 : 0 : return ObjectBase::prop_getter_simple_type_func<
1188 : : char*, GI_TRANSFER_EVERYTHING>;
1189 : : }
1190 : 10 : default:
1191 : 10 : return nullptr;
1192 : : }
1193 : : }
1194 : :
1195 : 31 : [[nodiscard]] static JSNative get_setter_for_type(GITypeInfo* type_info,
1196 : : GITransfer transfer) {
1197 [ + + + - : 31 : switch (g_type_info_get_tag(type_info)) {
- + + + +
+ + - - +
+ ]
1198 : 4 : case GI_TYPE_TAG_BOOLEAN:
1199 : 4 : return ObjectBase::prop_setter_simple_type_func<Gjs::Tag::GBoolean>;
1200 : 1 : case GI_TYPE_TAG_INT8:
1201 : 1 : return ObjectBase::prop_setter_simple_type_func<int8_t>;
1202 : 1 : case GI_TYPE_TAG_UINT8:
1203 : 1 : return ObjectBase::prop_setter_simple_type_func<uint8_t>;
1204 : 0 : case GI_TYPE_TAG_INT16:
1205 : 0 : return ObjectBase::prop_setter_simple_type_func<int16_t>;
1206 : 0 : case GI_TYPE_TAG_UINT16:
1207 : 0 : return ObjectBase::prop_setter_simple_type_func<uint16_t>;
1208 : 2 : case GI_TYPE_TAG_INT32:
1209 : 2 : return ObjectBase::prop_setter_simple_type_func<int32_t>;
1210 : 1 : case GI_TYPE_TAG_UINT32:
1211 : 1 : return ObjectBase::prop_setter_simple_type_func<uint32_t>;
1212 : 2 : case GI_TYPE_TAG_INT64:
1213 : 2 : return ObjectBase::prop_setter_simple_type_func<int64_t>;
1214 : 2 : case GI_TYPE_TAG_UINT64:
1215 : 2 : return ObjectBase::prop_setter_simple_type_func<uint64_t>;
1216 : 1 : case GI_TYPE_TAG_FLOAT:
1217 : 1 : return ObjectBase::prop_setter_simple_type_func<float>;
1218 : 1 : case GI_TYPE_TAG_DOUBLE:
1219 : 1 : return ObjectBase::prop_setter_simple_type_func<double>;
1220 : 0 : case GI_TYPE_TAG_GTYPE:
1221 : 0 : return ObjectBase::prop_setter_simple_type_func<Gjs::Tag::GType>;
1222 : 0 : case GI_TYPE_TAG_UNICHAR:
1223 : 0 : return ObjectBase::prop_setter_simple_type_func<gunichar>;
1224 : 6 : case GI_TYPE_TAG_FILENAME:
1225 : : case GI_TYPE_TAG_UTF8:
1226 [ + - ]: 6 : if (transfer == GI_TRANSFER_NOTHING) {
1227 : 6 : return ObjectBase::prop_setter_simple_type_func<
1228 : : char*, GI_TRANSFER_NOTHING>;
1229 : : } else {
1230 : 0 : return ObjectBase::prop_setter_simple_type_func<
1231 : : char*, GI_TRANSFER_EVERYTHING>;
1232 : : }
1233 : 10 : default:
1234 : 10 : return nullptr;
1235 : : }
1236 : : }
1237 : :
1238 : : GJS_JSAPI_RETURN_CONVENTION
1239 : 55 : static JSNative create_getter_invoker(JSContext* cx, GParamSpec* pspec,
1240 : : GIFunctionInfo* getter, GITypeInfo* type,
1241 : : JS::MutableHandleValue wrapper_out) {
1242 : 55 : JS::RootedObject wrapper{cx};
1243 : :
1244 : 55 : GITransfer transfer = g_callable_info_get_caller_owns(getter);
1245 : 55 : JSNative js_getter = get_getter_for_type(type, transfer);
1246 : :
1247 : 55 : Gjs::GErrorResult<> init_result{Ok{}};
1248 [ + + ]: 55 : if (js_getter) {
1249 : 45 : wrapper = Gjs::SimpleWrapper::new_for_type<ObjectPropertyPspecCaller>(
1250 : 45 : cx, pspec);
1251 [ - + ]: 45 : if (!wrapper)
1252 : 0 : return nullptr;
1253 : : auto* caller =
1254 : 45 : Gjs::SimpleWrapper::get<ObjectPropertyPspecCaller>(cx, wrapper);
1255 : 45 : init_result = caller->init(getter);
1256 : : } else {
1257 : 10 : wrapper = Gjs::SimpleWrapper::new_for_type<ObjectPropertyInfoCaller>(
1258 : 10 : cx, getter);
1259 [ - + ]: 10 : if (!wrapper)
1260 : 0 : return nullptr;
1261 : 10 : js_getter = &ObjectBase::prop_getter_func;
1262 : : auto* caller =
1263 : 10 : Gjs::SimpleWrapper::get<ObjectPropertyInfoCaller>(cx, wrapper);
1264 : 10 : init_result = caller->init();
1265 : : }
1266 : :
1267 [ - + ]: 55 : if (init_result.isErr()) {
1268 : 0 : gjs_throw(cx, "Impossible to create invoker for %s: %s",
1269 : : g_base_info_get_name(getter),
1270 : 0 : init_result.inspectErr()->message);
1271 : 0 : return nullptr;
1272 : : }
1273 : :
1274 : 55 : wrapper_out.setObject(*wrapper);
1275 : 55 : return js_getter;
1276 : 55 : }
1277 : :
1278 : : // We cannot use g_base_info_equal because the GITypeInfo of properties is
1279 : : // not marked as a pointer in GIR files, while it is marked as a pointer in the
1280 : : // return type of the associated getter, or the argument type of the associated
1281 : : // setter. Also, there isn't a GParamSpec for integers of specific widths, there
1282 : : // is only int and long, whereas the corresponding getter may return a specific
1283 : : // width of integer.
1284 : : [[nodiscard]]
1285 : 86 : static bool type_info_compatible(GITypeInfo* func_type, GITypeInfo* prop_type) {
1286 : 86 : GITypeTag tag = g_type_info_get_tag(prop_type);
1287 : 86 : GITypeTag func_tag = g_type_info_get_tag(func_type);
1288 : :
1289 [ + + - + ]: 86 : if (GI_TYPE_TAG_IS_BASIC(tag)) {
1290 [ - + ]: 132 : if (g_type_info_is_pointer(func_type) !=
1291 : 66 : g_type_info_is_pointer(prop_type))
1292 : 0 : return false;
1293 : : }
1294 [ + + + + : 86 : switch (tag) {
- - + - +
- ]
1295 : 39 : case GI_TYPE_TAG_VOID: // g_param_spec_param
1296 : : case GI_TYPE_TAG_BOOLEAN: // g_param_spec_boolean
1297 : : case GI_TYPE_TAG_INT8: // g_param_spec_char
1298 : : case GI_TYPE_TAG_DOUBLE: // g_param_spec_double
1299 : : case GI_TYPE_TAG_FLOAT: // g_param_spec_float
1300 : : case GI_TYPE_TAG_GTYPE: // g_param_spec_gtype
1301 : : case GI_TYPE_TAG_UINT8: // g_param_spec_uchar
1302 : : case GI_TYPE_TAG_UNICHAR: // g_param_spec_unichar
1303 : : case GI_TYPE_TAG_ERROR: // would be g_param_spec_boxed?
1304 : 39 : return func_tag == tag;
1305 : 9 : case GI_TYPE_TAG_INT32:
1306 : : case GI_TYPE_TAG_INT64:
1307 : : // g_param_spec_int, g_param_spec_long, or g_param_spec_int64
1308 [ + - ]: 9 : return func_tag == GI_TYPE_TAG_INT8 ||
1309 [ + + ]: 9 : func_tag == GI_TYPE_TAG_INT16 ||
1310 [ + - + - ]: 18 : func_tag == GI_TYPE_TAG_INT32 ||
1311 : 9 : func_tag == GI_TYPE_TAG_INT64;
1312 : 6 : case GI_TYPE_TAG_UINT32:
1313 : : case GI_TYPE_TAG_UINT64:
1314 : : // g_param_spec_uint, g_param_spec_ulong, or g_param_spec_uint64
1315 [ + - ]: 6 : return func_tag == GI_TYPE_TAG_UINT8 ||
1316 [ + + ]: 6 : func_tag == GI_TYPE_TAG_UINT16 ||
1317 [ + - + - ]: 12 : func_tag == GI_TYPE_TAG_UINT32 ||
1318 : 6 : func_tag == GI_TYPE_TAG_UINT64;
1319 : 12 : case GI_TYPE_TAG_UTF8: // g_param_spec_string
1320 [ - + - - ]: 12 : return func_tag == tag || func_tag == GI_TYPE_TAG_FILENAME;
1321 : 0 : case GI_TYPE_TAG_INT16:
1322 : : case GI_TYPE_TAG_UINT16:
1323 : : case GI_TYPE_TAG_FILENAME:
1324 : : g_return_val_if_reached(false); // never occurs as GParamSpec type
1325 : : // everything else
1326 : 0 : case GI_TYPE_TAG_GLIST:
1327 : : case GI_TYPE_TAG_GSLIST: {
1328 [ # # ]: 0 : if (func_tag != tag)
1329 : 0 : return false;
1330 : : GI::AutoBaseInfo func_elem{
1331 : 0 : g_type_info_get_param_type(func_type, 0)};
1332 : : GI::AutoBaseInfo prop_elem{
1333 : 0 : g_type_info_get_param_type(func_type, 0)};
1334 : 0 : return g_base_info_equal(func_elem, prop_elem);
1335 : 0 : }
1336 : 3 : case GI_TYPE_TAG_ARRAY: {
1337 [ - + ]: 3 : if (func_tag != tag)
1338 : 0 : return false;
1339 : : GI::AutoBaseInfo func_elem{
1340 : 3 : g_type_info_get_param_type(func_type, 0)};
1341 : : GI::AutoBaseInfo prop_elem{
1342 : 3 : g_type_info_get_param_type(func_type, 0)};
1343 [ + - ]: 6 : return g_base_info_equal(func_elem, prop_elem) &&
1344 : 3 : g_type_info_is_zero_terminated(func_type) ==
1345 [ + - ]: 6 : g_type_info_is_zero_terminated(prop_type) &&
1346 : 3 : g_type_info_get_array_fixed_size(func_type) ==
1347 [ + - ]: 9 : g_type_info_get_array_fixed_size(prop_type) &&
1348 : 3 : g_type_info_get_array_type(func_type) ==
1349 [ + - ]: 3 : g_type_info_get_array_type(prop_type);
1350 : 3 : }
1351 : 0 : case GI_TYPE_TAG_GHASH: {
1352 [ # # ]: 0 : if (func_tag != tag)
1353 : 0 : return false;
1354 : 0 : GI::AutoBaseInfo func_key{g_type_info_get_param_type(func_type, 0)};
1355 : 0 : GI::AutoBaseInfo prop_key{g_type_info_get_param_type(func_type, 0)};
1356 : 0 : GI::AutoBaseInfo func_val{g_type_info_get_param_type(func_type, 0)};
1357 : 0 : GI::AutoBaseInfo prop_val{g_type_info_get_param_type(func_type, 0)};
1358 [ # # # # ]: 0 : return g_base_info_equal(func_key, prop_key) &&
1359 : 0 : g_base_info_equal(func_val, prop_val);
1360 : 0 : }
1361 : 17 : case GI_TYPE_TAG_INTERFACE: {
1362 [ - + ]: 17 : if (func_tag != tag)
1363 : 0 : return false;
1364 : 17 : GI::AutoBaseInfo func_iface{g_type_info_get_interface(func_type)};
1365 : 17 : GI::AutoBaseInfo prop_iface{g_type_info_get_interface(prop_type)};
1366 : 17 : return g_base_info_equal(func_iface, prop_iface);
1367 : 17 : }
1368 : : }
1369 : : g_return_val_if_reached(false);
1370 : : }
1371 : :
1372 : : GJS_JSAPI_RETURN_CONVENTION
1373 : 239 : static JSNative get_getter_for_property(
1374 : : JSContext* cx, GParamSpec* pspec, Maybe<GI::AutoPropertyInfo> property_info,
1375 : : JS::MutableHandleValue priv_out) {
1376 [ + + ]: 239 : if (!(pspec->flags & G_PARAM_READABLE)) {
1377 : 1 : priv_out.setUndefined();
1378 : 1 : return &ObjectBase::prop_getter_write_only;
1379 : : }
1380 : :
1381 [ + + ]: 238 : if (property_info) {
1382 : : GI::AutoFunctionInfo prop_getter{
1383 : 236 : g_property_info_get_getter(*property_info)};
1384 : :
1385 [ + - + - ]: 346 : if (prop_getter && g_callable_info_is_method(prop_getter) &&
1386 [ + + + - : 346 : g_callable_info_get_n_args(prop_getter) == 0 &&
+ + ]
1387 : 55 : !g_callable_info_skip_return(prop_getter)) {
1388 : : GI::AutoTypeInfo return_type{
1389 : 55 : g_callable_info_get_return_type(prop_getter)};
1390 : : GI::AutoTypeInfo prop_type{
1391 : 55 : g_property_info_get_type(*property_info)};
1392 : :
1393 [ + - ]: 55 : if (G_LIKELY(type_info_compatible(return_type, prop_type))) {
1394 : 55 : return create_getter_invoker(cx, pspec, prop_getter,
1395 : 55 : return_type, priv_out);
1396 : : } else {
1397 : 0 : GIBaseInfo* container = g_base_info_get_container(prop_getter);
1398 : 0 : g_warning(
1399 : : "Type %s of property %s.%s::%s does not match return type "
1400 : : "%s of getter %s. Falling back to slow path",
1401 : : g_type_tag_to_string(g_type_info_get_tag(prop_type)),
1402 : : g_base_info_get_namespace(container),
1403 : : g_base_info_get_name(container), property_info->name(),
1404 : : g_type_tag_to_string(g_type_info_get_tag(return_type)),
1405 : : prop_getter.name());
1406 : : // fall back to GValue below
1407 : : }
1408 [ - + - + ]: 110 : }
1409 [ + + ]: 236 : }
1410 : :
1411 : 183 : priv_out.setPrivate(pspec);
1412 [ + + + + : 183 : switch (pspec->value_type) {
+ + + + +
+ + + + ]
1413 : 26 : case G_TYPE_BOOLEAN:
1414 : 26 : return &ObjectBase::prop_getter<Gjs::Tag::GBoolean>;
1415 : 7 : case G_TYPE_INT:
1416 : 7 : return &ObjectBase::prop_getter<int>;
1417 : 1 : case G_TYPE_UINT:
1418 : 1 : return &ObjectBase::prop_getter<unsigned int>;
1419 : 1 : case G_TYPE_CHAR:
1420 : 1 : return &ObjectBase::prop_getter<signed char>;
1421 : 1 : case G_TYPE_UCHAR:
1422 : 1 : return &ObjectBase::prop_getter<unsigned char>;
1423 : 1 : case G_TYPE_INT64:
1424 : 1 : return &ObjectBase::prop_getter<int64_t>;
1425 : 1 : case G_TYPE_UINT64:
1426 : 1 : return &ObjectBase::prop_getter<uint64_t>;
1427 : 2 : case G_TYPE_FLOAT:
1428 : 2 : return &ObjectBase::prop_getter<float>;
1429 : 2 : case G_TYPE_DOUBLE:
1430 : 2 : return &ObjectBase::prop_getter<double>;
1431 : 76 : case G_TYPE_STRING:
1432 : 76 : return &ObjectBase::prop_getter<char*>;
1433 : 1 : case G_TYPE_LONG:
1434 : 1 : return &ObjectBase::prop_getter<Gjs::Tag::Long>;
1435 : 1 : case G_TYPE_ULONG:
1436 : 1 : return &ObjectBase::prop_getter<Gjs::Tag::UnsignedLong>;
1437 : 63 : default:
1438 : 63 : return &ObjectBase::prop_getter<>;
1439 : : }
1440 : : }
1441 : :
1442 : : GJS_JSAPI_RETURN_CONVENTION
1443 : 31 : static JSNative create_setter_invoker(JSContext* cx, GParamSpec* pspec,
1444 : : GIFunctionInfo* setter,
1445 : : GIArgInfo* value_arg, GITypeInfo* type,
1446 : : JS::MutableHandleValue wrapper_out) {
1447 : 31 : JS::RootedObject wrapper{cx};
1448 : :
1449 : 31 : GITransfer transfer = g_arg_info_get_ownership_transfer(value_arg);
1450 : 31 : JSNative js_setter = get_setter_for_type(type, transfer);
1451 : :
1452 : 31 : Gjs::GErrorResult<> init_result{Ok{}};
1453 [ + + ]: 31 : if (js_setter) {
1454 : 21 : wrapper = Gjs::SimpleWrapper::new_for_type<ObjectPropertyPspecCaller>(
1455 : 21 : cx, pspec);
1456 [ - + ]: 21 : if (!wrapper)
1457 : 0 : return nullptr;
1458 : : auto* caller =
1459 : 21 : Gjs::SimpleWrapper::get<ObjectPropertyPspecCaller>(cx, wrapper);
1460 : 21 : init_result = caller->init(setter);
1461 : : } else {
1462 : 10 : wrapper = Gjs::SimpleWrapper::new_for_type<ObjectPropertyInfoCaller>(
1463 : 10 : cx, setter);
1464 [ - + ]: 10 : if (!wrapper)
1465 : 0 : return nullptr;
1466 : 10 : js_setter = &ObjectBase::prop_setter_func;
1467 : : auto* caller =
1468 : 10 : Gjs::SimpleWrapper::get<ObjectPropertyInfoCaller>(cx, wrapper);
1469 : 10 : init_result = caller->init();
1470 : : }
1471 : :
1472 [ - + ]: 31 : if (init_result.isErr()) {
1473 : 0 : gjs_throw(cx, "Impossible to create invoker for %s: %s",
1474 : : g_base_info_get_name(setter),
1475 : 0 : init_result.inspectErr()->message);
1476 : 0 : return nullptr;
1477 : : }
1478 : :
1479 : 31 : wrapper_out.setObject(*wrapper);
1480 : 31 : return js_setter;
1481 : 31 : }
1482 : :
1483 : : GJS_JSAPI_RETURN_CONVENTION
1484 : 239 : static JSNative get_setter_for_property(
1485 : : JSContext* cx, GParamSpec* pspec, Maybe<GI::AutoPropertyInfo> property_info,
1486 : : JS::MutableHandleValue priv_out) {
1487 [ + + ]: 239 : if (!(pspec->flags & G_PARAM_WRITABLE)) {
1488 : 51 : priv_out.setPrivate(pspec);
1489 : 51 : return &ObjectBase::prop_setter_read_only;
1490 : : }
1491 : :
1492 [ + + ]: 188 : if (property_info) {
1493 : : GI::AutoFunctionInfo prop_setter{
1494 : 187 : g_property_info_get_setter(*property_info)};
1495 : :
1496 [ + + + - : 218 : if (prop_setter && g_callable_info_is_method(prop_setter) &&
+ - + + ]
1497 : 31 : g_callable_info_get_n_args(prop_setter) == 1) {
1498 : : GIArgInfo value_arg;
1499 : 31 : g_callable_info_load_arg(prop_setter, 0, &value_arg);
1500 : : GITypeInfo type_info;
1501 : 31 : g_arg_info_load_type(&value_arg, &type_info);
1502 : : GI::AutoTypeInfo prop_type{
1503 : 31 : g_property_info_get_type(*property_info)};
1504 : :
1505 [ + - ]: 31 : if (G_LIKELY(type_info_compatible(&type_info, prop_type))) {
1506 : 31 : return create_setter_invoker(cx, pspec, prop_setter, &value_arg,
1507 : 31 : &type_info, priv_out);
1508 : : } else {
1509 : 0 : GIBaseInfo* container = g_base_info_get_container(prop_setter);
1510 : 0 : g_warning(
1511 : : "Type %s of property %s.%s::%s does not match type %s of "
1512 : : "first argument of setter %s. Falling back to slow path",
1513 : : g_type_tag_to_string(g_type_info_get_tag(prop_type)),
1514 : : g_base_info_get_namespace(container),
1515 : : g_base_info_get_name(container), property_info->name(),
1516 : : g_type_tag_to_string(g_type_info_get_tag(&type_info)),
1517 : : prop_setter.name());
1518 : : // fall back to GValue below
1519 : : }
1520 [ - + ]: 31 : }
1521 [ + + ]: 187 : }
1522 : :
1523 : 157 : priv_out.setPrivate(pspec);
1524 [ + + + + : 157 : switch (pspec->value_type) {
+ + + + +
+ + + + ]
1525 : 3 : case G_TYPE_BOOLEAN:
1526 : 3 : return &ObjectBase::prop_setter<Gjs::Tag::GBoolean>;
1527 : 6 : case G_TYPE_INT:
1528 : 6 : return &ObjectBase::prop_setter<int>;
1529 : 1 : case G_TYPE_UINT:
1530 : 1 : return &ObjectBase::prop_setter<unsigned int>;
1531 : 1 : case G_TYPE_CHAR:
1532 : 1 : return &ObjectBase::prop_setter<signed char>;
1533 : 1 : case G_TYPE_UCHAR:
1534 : 1 : return &ObjectBase::prop_setter<unsigned char>;
1535 : 1 : case G_TYPE_INT64:
1536 : 1 : return &ObjectBase::prop_setter<int64_t>;
1537 : 1 : case G_TYPE_UINT64:
1538 : 1 : return &ObjectBase::prop_setter<uint64_t>;
1539 : 2 : case G_TYPE_FLOAT:
1540 : 2 : return &ObjectBase::prop_setter<float>;
1541 : 2 : case G_TYPE_DOUBLE:
1542 : 2 : return &ObjectBase::prop_setter<double>;
1543 : 76 : case G_TYPE_STRING:
1544 : 76 : return &ObjectBase::prop_setter<char*>;
1545 : 1 : case G_TYPE_LONG:
1546 : 1 : return &ObjectBase::prop_setter<Gjs::Tag::Long>;
1547 : 1 : case G_TYPE_ULONG:
1548 : 1 : return &ObjectBase::prop_setter<Gjs::Tag::UnsignedLong>;
1549 : 61 : default:
1550 : 61 : return &ObjectBase::prop_setter<>;
1551 : : }
1552 : : }
1553 : :
1554 : 424 : bool ObjectPrototype::lazy_define_gobject_property(
1555 : : JSContext* cx, JS::HandleObject obj, JS::HandleId id, GParamSpec* pspec,
1556 : : bool* resolved, const char* name,
1557 : : Maybe<const GI::AutoPropertyInfo> property_info) {
1558 : 424 : JS::RootedId canonical_id{cx};
1559 : 424 : JS::Rooted<JS::PropertyDescriptor> canonical_desc{cx};
1560 : :
1561 : : // Make property configurable so that interface properties can be
1562 : : // overridden by GObject.ParamSpec.override in the class that
1563 : : // implements them
1564 : 424 : unsigned flags = GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT;
1565 : :
1566 [ + + ]: 424 : if (!g_str_equal(pspec->name, name)) {
1567 : 185 : canonical_id = gjs_intern_string_to_id(cx, pspec->name);
1568 : :
1569 : 185 : JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc{cx};
1570 [ - + ]: 185 : if (!JS_GetOwnPropertyDescriptorById(cx, obj, canonical_id, &desc))
1571 : 0 : return false;
1572 : :
1573 [ + - ]: 185 : if (desc.isSome()) {
1574 : 185 : debug_jsprop("Defining alias GObject property", id, obj);
1575 : 185 : canonical_desc = *desc;
1576 [ - + ]: 185 : if (!JS_DefinePropertyById(cx, obj, id, canonical_desc))
1577 : 0 : return false;
1578 : :
1579 : 185 : *resolved = true;
1580 : 185 : return true;
1581 : : }
1582 [ - + ]: 185 : }
1583 : :
1584 : 239 : debug_jsprop("Defining lazy GObject property", id, obj);
1585 : :
1586 [ - + ]: 239 : if (!(pspec->flags & (G_PARAM_WRITABLE | G_PARAM_READABLE))) {
1587 [ # # ]: 0 : if (!JS_DefinePropertyById(cx, obj, id, JS::UndefinedHandleValue,
1588 : : flags))
1589 : 0 : return false;
1590 : :
1591 [ # # ]: 0 : if (!canonical_id.isVoid() &&
1592 [ # # # # ]: 0 : !JS_DefinePropertyById(cx, obj, canonical_id,
1593 : : JS::UndefinedHandleValue, flags))
1594 : 0 : return false;
1595 : :
1596 : 0 : *resolved = true;
1597 : 0 : return true;
1598 : : }
1599 : :
1600 : : // Do not fetch JS overridden properties from GObject, to avoid
1601 : : // infinite recursion.
1602 [ - + ]: 239 : if (g_param_spec_get_qdata(pspec, ObjectBase::custom_property_quark())) {
1603 : 0 : *resolved = false;
1604 : 0 : return true;
1605 : : }
1606 : :
1607 : 239 : JS::RootedValue getter_priv{cx};
1608 : : JSNative js_getter =
1609 : 239 : get_getter_for_property(cx, pspec, property_info, &getter_priv);
1610 [ - + ]: 239 : if (!js_getter)
1611 : 0 : return false;
1612 : :
1613 : 239 : JS::RootedValue setter_priv{cx};
1614 : : JSNative js_setter =
1615 : 239 : get_setter_for_property(cx, pspec, property_info, &setter_priv);
1616 [ - + ]: 239 : if (!js_setter)
1617 : 0 : return false;
1618 : :
1619 [ - + ]: 239 : if (!gjs_define_property_dynamic(cx, obj, name, id, "gobject_prop",
1620 : : js_getter, getter_priv, js_setter,
1621 : : setter_priv, flags))
1622 : 0 : return false;
1623 : :
1624 [ - + ]: 239 : if G_UNLIKELY (!canonical_id.isVoid()) {
1625 : 0 : debug_jsprop("Defining alias GObject property", canonical_id, obj);
1626 : :
1627 [ # # ]: 0 : if (!JS_DefinePropertyById(cx, obj, canonical_id, canonical_desc))
1628 : 0 : return false;
1629 : : }
1630 : :
1631 : 239 : *resolved = true;
1632 : 239 : return true;
1633 : 424 : }
1634 : :
1635 : : // An object shared by the getter and setter to store the interface' prototype
1636 : : // and overrides.
1637 : : static constexpr size_t ACCESSOR_SLOT = 0;
1638 : :
1639 : 373 : static bool interface_getter(JSContext* cx, unsigned argc, JS::Value* vp) {
1640 : 373 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
1641 : :
1642 : : JS::RootedValue v_accessor(
1643 : 373 : cx, js::GetFunctionNativeReserved(&args.callee(), ACCESSOR_SLOT));
1644 : 373 : g_assert(v_accessor.isObject() && "accessor must be an object");
1645 : 373 : JS::RootedObject accessor(cx, &v_accessor.toObject());
1646 : :
1647 : 373 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
1648 : :
1649 : : // Check if an override value has been set
1650 : 373 : bool has_override_symbol = false;
1651 [ - + ]: 373 : if (!JS_HasPropertyById(cx, accessor, atoms.override(),
1652 : : &has_override_symbol))
1653 : 0 : return false;
1654 : :
1655 [ + + ]: 373 : if (has_override_symbol) {
1656 : 46 : JS::RootedValue v_override_symbol(cx);
1657 [ - + ]: 46 : if (!JS_GetPropertyById(cx, accessor, atoms.override(),
1658 : : &v_override_symbol))
1659 : 0 : return false;
1660 : 46 : g_assert(v_override_symbol.isSymbol() &&
1661 : : "override symbol must be a symbol");
1662 : 46 : JS::RootedSymbol override_symbol(cx, v_override_symbol.toSymbol());
1663 : 46 : JS::RootedId override_id(cx, JS::PropertyKey::Symbol(override_symbol));
1664 : :
1665 : 46 : JS::RootedObject this_obj(cx);
1666 [ - + ]: 46 : if (!args.computeThis(cx, &this_obj))
1667 : 0 : return false;
1668 : :
1669 : 46 : bool has_override = false;
1670 [ - + ]: 46 : if (!JS_HasPropertyById(cx, this_obj, override_id, &has_override))
1671 : 0 : return false;
1672 : :
1673 [ + + ]: 46 : if (has_override)
1674 : 41 : return JS_GetPropertyById(cx, this_obj, override_id, args.rval());
1675 [ + + + + : 169 : }
+ + + + ]
1676 : :
1677 : 332 : JS::RootedValue v_prototype(cx);
1678 [ - + ]: 332 : if (!JS_GetPropertyById(cx, accessor, atoms.prototype(), &v_prototype))
1679 : 0 : return false;
1680 : 332 : g_assert(v_prototype.isObject() && "prototype must be an object");
1681 : :
1682 : 332 : JS::RootedObject prototype(cx, &v_prototype.toObject());
1683 : 332 : JS::RootedFunction fn_obj{cx, JS_GetObjectFunction(&args.callee())};
1684 : 332 : JS::RootedString fn_name{cx};
1685 [ - + ]: 332 : if (!JS_GetFunctionId(cx, fn_obj, &fn_name))
1686 : 0 : return false;
1687 : 332 : JS::RootedId id{cx, JS::PropertyKey::NonIntAtom(fn_name)};
1688 : 332 : return JS_GetPropertyById(cx, prototype, id, args.rval());
1689 : 373 : }
1690 : :
1691 : 77 : static bool interface_setter(JSContext* cx, unsigned argc, JS::Value* vp) {
1692 : 77 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
1693 : : JS::RootedValue v_accessor(
1694 : 77 : cx, js::GetFunctionNativeReserved(&args.callee(), ACCESSOR_SLOT));
1695 : 77 : JS::RootedObject accessor(cx, &v_accessor.toObject());
1696 : : JS::RootedString description(
1697 : 77 : cx, JS_AtomizeAndPinString(cx, "Private interface function setter"));
1698 : 77 : JS::RootedSymbol symbol(cx, JS::NewSymbol(cx, description));
1699 : 77 : JS::RootedValue v_symbol(cx, JS::SymbolValue(symbol));
1700 : :
1701 : 77 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
1702 [ - + ]: 77 : if (!JS_SetPropertyById(cx, accessor, atoms.override(), v_symbol))
1703 : 0 : return false;
1704 : :
1705 : 77 : args.rval().setUndefined();
1706 : :
1707 : 77 : JS::RootedObject this_obj(cx);
1708 [ - + ]: 77 : if (!args.computeThis(cx, &this_obj))
1709 : 0 : return false;
1710 : 77 : JS::RootedId override_id(cx, JS::PropertyKey::Symbol(symbol));
1711 : :
1712 : 77 : return JS_SetPropertyById(cx, this_obj, override_id, args[0]);
1713 : 77 : }
1714 : :
1715 : 1674 : static bool resolve_on_interface_prototype(JSContext* cx,
1716 : : GIInterfaceInfo* iface_info,
1717 : : JS::HandleId identifier,
1718 : : JS::HandleObject class_prototype,
1719 : : bool* found) {
1720 : 1674 : GType gtype = g_base_info_get_type(iface_info);
1721 : : JS::RootedObject interface_prototype(
1722 : 1674 : cx, gjs_lookup_object_prototype_from_info(cx, iface_info, gtype));
1723 [ - + ]: 1674 : if (!interface_prototype)
1724 : 0 : return false;
1725 : :
1726 : 1674 : bool exists = false;
1727 [ - + ]: 1674 : if (!JS_HasPropertyById(cx, interface_prototype, identifier, &exists))
1728 : 0 : return false;
1729 : :
1730 : : // If the property doesn't exist on the interface prototype, we don't need
1731 : : // to perform this trick.
1732 [ + + ]: 1674 : if (!exists) {
1733 : 1555 : *found = false;
1734 : 1555 : return true;
1735 : : }
1736 : :
1737 : : // Lazily define a property on the class prototype if a property
1738 : : // of that name is present on an interface prototype that the class
1739 : : // implements.
1740 : : //
1741 : : // Define a property of the same name on the class prototype, with a
1742 : : // getter and setter. This is so that e.g. file.dup() calls the _current_
1743 : : // value of Gio.File.prototype.dup(), not the original, so that it can be
1744 : : // overridden (or monkeypatched).
1745 : : //
1746 : : // The setter (interface_setter() above) marks the property as overridden if
1747 : : // it is set from user code. The getter (interface_getter() above) proxies
1748 : : // the interface prototype's property, unless it was marked as overridden.
1749 : : //
1750 : : // Store the identifier in the getter and setter function's ID slots for
1751 : : // to enable looking up the original value on the interface prototype.
1752 : : JS::RootedObject getter(
1753 : 119 : cx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
1754 : 119 : cx, interface_getter, 0, 0, identifier)));
1755 [ - + ]: 119 : if (!getter)
1756 : 0 : return false;
1757 : :
1758 : : JS::RootedObject setter(
1759 : 119 : cx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
1760 : 119 : cx, interface_setter, 1, 0, identifier)));
1761 [ - + ]: 119 : if (!setter)
1762 : 0 : return false;
1763 : :
1764 : 119 : JS::RootedObject accessor(cx, JS_NewPlainObject(cx));
1765 [ - + ]: 119 : if (!accessor)
1766 : 0 : return false;
1767 : :
1768 : 119 : js::SetFunctionNativeReserved(setter, ACCESSOR_SLOT,
1769 : 119 : JS::ObjectValue(*accessor));
1770 : 119 : js::SetFunctionNativeReserved(getter, ACCESSOR_SLOT,
1771 : 119 : JS::ObjectValue(*accessor));
1772 : :
1773 : 119 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
1774 : 119 : JS::RootedValue v_prototype(cx, JS::ObjectValue(*interface_prototype));
1775 [ - + ]: 119 : if (!JS_SetPropertyById(cx, accessor, atoms.prototype(), v_prototype))
1776 : 0 : return false;
1777 : :
1778 : : // Create a new descriptor with our getter and setter, that is configurable
1779 : : // and enumerable, because GObject may need to redefine it later.
1780 : 119 : JS::PropertyAttributes attrs{JS::PropertyAttribute::Configurable,
1781 : : JS::PropertyAttribute::Enumerable};
1782 : : JS::Rooted<JS::PropertyDescriptor> desc(
1783 : 119 : cx, JS::PropertyDescriptor::Accessor(getter, setter, attrs));
1784 : :
1785 [ - + ]: 119 : if (!JS_DefinePropertyById(cx, class_prototype, identifier, desc))
1786 : 0 : return false;
1787 : :
1788 : 119 : *found = true;
1789 : 119 : return true;
1790 : 1674 : }
1791 : :
1792 : 2261 : bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
1793 : : JS::HandleId id, bool* resolved,
1794 : : const char* name,
1795 : : ResolveWhat resolve_props) {
1796 : : guint n_interfaces;
1797 : : guint i;
1798 : :
1799 : 2261 : Gjs::AutoChar canonical_name;
1800 [ + + ]: 2261 : if (resolve_props == ConsiderMethodsAndProperties) {
1801 : : // Optimization: GObject property names must start with a letter
1802 [ + + ]: 365 : if (g_ascii_isalpha(name[0])) {
1803 : 191 : canonical_name = gjs_hyphen_from_camel(name);
1804 : 191 : canonicalize_key(canonical_name);
1805 : : }
1806 : : }
1807 : :
1808 : : GIInterfaceInfo** interfaces;
1809 : 2261 : g_irepository_get_object_gtype_interfaces(nullptr, m_gtype, &n_interfaces,
1810 : : &interfaces);
1811 : :
1812 : : /* Fallback to GType system for non custom GObjects with no GI information
1813 : : */
1814 [ + + + - : 2261 : if (canonical_name && G_TYPE_IS_CLASSED(m_gtype) && !is_custom_js_class()) {
+ + + + ]
1815 : 59 : Gjs::AutoTypeClass<GObjectClass> oclass{m_gtype};
1816 : :
1817 [ + + ]: 59 : if (GParamSpec* pspec =
1818 : 59 : g_object_class_find_property(oclass, canonical_name))
1819 : 10 : return lazy_define_gobject_property(cx, obj, id, pspec, resolved,
1820 : 5 : name);
1821 : :
1822 [ + + ]: 109 : for (i = 0; i < n_interfaces; i++) {
1823 : : GType iface_gtype =
1824 : 55 : g_registered_type_info_get_g_type(interfaces[i]);
1825 [ + - ]: 55 : if (!G_TYPE_IS_CLASSED(iface_gtype))
1826 : 55 : continue;
1827 : :
1828 : 0 : Gjs::AutoTypeClass<GObjectClass> iclass{iface_gtype};
1829 : :
1830 [ # # ]: 0 : if (GParamSpec* pspec =
1831 : 0 : g_object_class_find_property(iclass, canonical_name)) {
1832 : 0 : return lazy_define_gobject_property(
1833 : : cx, obj, id, pspec, resolved, name,
1834 : 0 : get_ginterface_property_by_name(interfaces[i],
1835 : 0 : canonical_name));
1836 : : }
1837 [ # # ]: 0 : }
1838 [ + + ]: 59 : }
1839 : :
1840 [ + + ]: 3811 : for (i = 0; i < n_interfaces; i++) {
1841 : 1592 : GIInterfaceInfo* iface_info = interfaces[i];
1842 : : GI::AutoFunctionInfo method_info{
1843 : 1592 : g_interface_info_find_method(iface_info, name)};
1844 [ + + ]: 1592 : if (method_info) {
1845 [ + - ]: 35 : if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) {
1846 : 35 : bool found = false;
1847 [ - + ]: 35 : if (!resolve_on_interface_prototype(cx, iface_info, id, obj,
1848 : : &found))
1849 : 0 : return false;
1850 : :
1851 : : // Fallback to defining the function from type info...
1852 [ - + - - : 35 : if (!found &&
- + ]
1853 : 0 : !gjs_define_function(cx, obj, m_gtype, method_info))
1854 : 0 : return false;
1855 : :
1856 : 35 : *resolved = true;
1857 : 35 : return true;
1858 : : }
1859 : : }
1860 : :
1861 : :
1862 : : /* If the name refers to a GObject property, lazily define the property
1863 : : * in JS as we do below in the real resolve hook. We ignore fields here
1864 : : * because I don't think interfaces can have fields */
1865 [ + + ]: 1557 : if (canonical_name) {
1866 : : Maybe<const GI::AutoPropertyInfo> prop_info{
1867 : 101 : get_ginterface_property_by_name(iface_info, canonical_name)};
1868 : :
1869 [ + + ]: 101 : if (prop_info) {
1870 : 3 : Gjs::AutoTypeClass<GObjectClass> oclass{m_gtype};
1871 : : // unowned
1872 : 3 : GParamSpec* pspec = g_object_class_find_property(
1873 : : oclass, canonical_name); // unowned
1874 [ + + - + ]: 3 : if (pspec && pspec->owner_type == m_gtype) {
1875 : 0 : return lazy_define_gobject_property(
1876 : 0 : cx, obj, id, pspec, resolved, name, prop_info);
1877 : : }
1878 [ + - ]: 3 : }
1879 [ + - ]: 101 : }
1880 : :
1881 [ - + ]: 1557 : if (!resolve_on_interface_prototype(cx, iface_info, id, obj, resolved))
1882 : 0 : return false;
1883 [ + + ]: 1557 : if (*resolved)
1884 : 2 : return true;
1885 [ + + ]: 1592 : }
1886 : :
1887 : 2219 : *resolved = false;
1888 : 2219 : return true;
1889 : 2261 : }
1890 : :
1891 : : [[nodiscard]]
1892 : 4234 : static Maybe<GI::AutoPropertyInfo> find_gobject_property_info(
1893 : : GIObjectInfo* info, const char* name) {
1894 : : // Optimization: GObject property names must start with a letter
1895 [ + + ]: 4234 : if (!g_ascii_isalpha(name[0]))
1896 : 657 : return {};
1897 : :
1898 : 3577 : Gjs::AutoChar canonical_name{gjs_hyphen_from_camel(name)};
1899 : 3577 : canonicalize_key(canonical_name);
1900 : :
1901 : 3577 : return get_gobject_property_info(info, canonical_name);
1902 : 3577 : }
1903 : :
1904 : : // Override of GIWrapperBase::id_is_never_lazy()
1905 : 0 : bool ObjectBase::id_is_never_lazy(jsid name, const GjsAtoms& atoms) {
1906 : : // Keep this list in sync with ObjectBase::proto_properties and
1907 : : // ObjectBase::proto_methods. However, explicitly do not include
1908 : : // connect() in it, because there are a few cases where the lazy property
1909 : : // should override the predefined one, such as Gio.Cancellable.connect().
1910 [ # # # # : 0 : return name == atoms.init() || name == atoms.connect_after() ||
# # ]
1911 : 0 : name == atoms.emit();
1912 : : }
1913 : :
1914 : 15374 : bool ObjectPrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
1915 : : JS::HandleId id, bool* resolved) {
1916 [ + + ]: 15374 : if (m_unresolvable_cache.has(id)) {
1917 : 6882 : *resolved = false;
1918 : 6882 : return true;
1919 : : }
1920 : :
1921 : 8492 : JS::UniqueChars prop_name;
1922 [ - + ]: 8492 : if (!gjs_get_string_id(context, id, &prop_name))
1923 : 0 : return false;
1924 [ + + ]: 8492 : if (!prop_name) {
1925 : 3890 : *resolved = false;
1926 : 3890 : return true; // not resolved, but no error
1927 : : }
1928 : :
1929 [ - + ]: 4602 : if (!uncached_resolve(context, obj, id, prop_name.get(), resolved))
1930 : 0 : return false;
1931 : :
1932 [ + + - + : 4602 : if (!*resolved && !m_unresolvable_cache.putNew(id)) {
- + ]
1933 : 0 : JS_ReportOutOfMemory(context);
1934 : 0 : return false;
1935 : : }
1936 : :
1937 : 4602 : return true;
1938 : 8492 : }
1939 : :
1940 : 4602 : bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj,
1941 : : JS::HandleId id, const char* name,
1942 : : bool* resolved) {
1943 : 4602 : bool found = false;
1944 [ - + ]: 4602 : if (!JS_AlreadyHasOwnPropertyById(context, obj, id, &found))
1945 : 0 : return false;
1946 : :
1947 [ - + ]: 4602 : if (found) {
1948 : : // Already defined, so *resolved = false because we didn't just define
1949 : : // it
1950 : 0 : *resolved = false;
1951 : 0 : return true;
1952 : : }
1953 : :
1954 : : // If we have no GIRepository information (we're a JS GObject subclass or an
1955 : : // internal non-introspected class such as GLocalFile), we need to look at
1956 : : // exposing interfaces. Look up our interfaces through GType data, and then
1957 : : // hope that *those* are introspectable.
1958 [ + + ]: 4602 : if (!info())
1959 : 365 : return resolve_no_info(context, obj, id, resolved, name,
1960 : 365 : ConsiderMethodsAndProperties);
1961 : :
1962 [ - + + + : 4237 : if (g_str_has_prefix(name, "vfunc_")) {
+ + ]
1963 : : /* The only time we find a vfunc info is when we're the base
1964 : : * class that defined the vfunc. If we let regular prototype
1965 : : * chaining resolve this, we'd have the implementation for the base's
1966 : : * vfunc on the base class, without any other "real" implementations
1967 : : * in the way. If we want to expose a "real" vfunc implementation,
1968 : : * we need to go down to the parent infos and look at their VFuncInfos.
1969 : : *
1970 : : * This is good, but it's memory-hungry -- we would define every
1971 : : * possible vfunc on every possible object, even if it's the same
1972 : : * "real" vfunc underneath. Instead, only expose vfuncs that are
1973 : : * different from their parent, and let prototype chaining do the
1974 : : * rest.
1975 : : */
1976 : :
1977 : 20 : const char *name_without_vfunc_ = &(name[6]); /* lifetime tied to name */
1978 : : bool defined_by_parent;
1979 : : GI::AutoVFuncInfo vfunc{find_vfunc_on_parents(
1980 : 20 : m_info, name_without_vfunc_, &defined_by_parent)};
1981 [ + + ]: 20 : if (vfunc) {
1982 : : /* In the event that the vfunc is unchanged, let regular
1983 : : * prototypal inheritance take over. */
1984 [ + + + + : 3 : if (defined_by_parent && is_vfunc_unchanged(vfunc)) {
+ + ]
1985 : 1 : *resolved = false;
1986 : 1 : return true;
1987 : : }
1988 : :
1989 [ - + ]: 2 : if (!gjs_define_function(context, obj, m_gtype, vfunc))
1990 : 0 : return false;
1991 : :
1992 : 2 : *resolved = true;
1993 : 2 : return true;
1994 : : }
1995 : :
1996 : : /* If the vfunc wasn't found, fall through, back to normal
1997 : : * method resolution. */
1998 [ + + ]: 20 : }
1999 : :
2000 [ + + ]: 4234 : if (Maybe<GI::AutoPropertyInfo> property_info =
2001 : 4234 : find_gobject_property_info(m_info, name)) {
2002 : 419 : Gjs::AutoTypeClass<GObjectClass> gobj_class{m_gtype};
2003 [ + - ]: 419 : if (GParamSpec* pspec =
2004 : 419 : g_object_class_find_property(gobj_class, property_info->name()))
2005 : 838 : return lazy_define_gobject_property(context, obj, id, pspec,
2006 : 419 : resolved, name, property_info);
2007 [ - + + + ]: 4653 : }
2008 : :
2009 : 3815 : GI::AutoFieldInfo field_info{lookup_field_info(m_info, name)};
2010 [ + + ]: 3815 : if (field_info) {
2011 : 5 : debug_jsprop("Defining lazy GObject field", id, obj);
2012 : :
2013 : 5 : unsigned flags = GJS_MODULE_PROP_FLAGS;
2014 [ + - ]: 5 : if (!(g_field_info_get_flags(field_info) & GI_FIELD_IS_WRITABLE))
2015 : 5 : flags |= JSPROP_READONLY;
2016 : :
2017 : : JS::RootedObject rooted_field{
2018 : 10 : context, Gjs::SimpleWrapper::new_for_type<GI::AutoFieldInfo>(
2019 : 5 : context, field_info)};
2020 : 5 : JS::RootedValue private_value{context, JS::ObjectValue(*rooted_field)};
2021 [ - + ]: 5 : if (!gjs_define_property_dynamic(
2022 : : context, obj, name, id, "gobject_field",
2023 : : &ObjectBase::field_getter, &ObjectBase::field_setter,
2024 : : private_value, flags))
2025 : 0 : return false;
2026 : :
2027 : 5 : *resolved = true;
2028 : 5 : return true;
2029 : 5 : }
2030 : :
2031 : : /* find_method does not look at methods on parent classes,
2032 : : * we rely on javascript to walk up the __proto__ chain
2033 : : * and find those and define them in the right prototype.
2034 : : *
2035 : : * Note that if it isn't a method on the object, since JS
2036 : : * lacks multiple inheritance, we're sticking the iface
2037 : : * methods in the object prototype, which means there are many
2038 : : * copies of the iface methods (one per object class node that
2039 : : * introduces the iface)
2040 : : */
2041 : :
2042 : 3810 : GI::AutoBaseInfo implementor_info;
2043 : : GI::AutoFunctionInfo method_info{g_object_info_find_method_using_interfaces(
2044 : 3810 : m_info, name, implementor_info.out())};
2045 : :
2046 : : /**
2047 : : * Search through any interfaces implemented by the GType;
2048 : : * See https://bugzilla.gnome.org/show_bug.cgi?id=632922
2049 : : * for background on why we need to do this.
2050 : : */
2051 [ + + ]: 3810 : if (!method_info)
2052 : 1896 : return resolve_no_info(context, obj, id, resolved, name,
2053 : 1896 : ConsiderOnlyMethods);
2054 : :
2055 : : #if GJS_VERBOSE_ENABLE_GI_USAGE
2056 : : _gjs_log_info_usage(method_info);
2057 : : #endif
2058 : :
2059 [ + - ]: 1914 : if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) {
2060 : 1914 : gjs_debug(GJS_DEBUG_GOBJECT,
2061 : : "Defining method %s in prototype for %s (%s)",
2062 : 3828 : method_info.name(), type_name(), format_name().c_str());
2063 [ + + ]: 1914 : if (GI_IS_INTERFACE_INFO(implementor_info)) {
2064 : 82 : bool found = false;
2065 [ - + ]: 82 : if (!resolve_on_interface_prototype(context, implementor_info, id,
2066 : : obj, &found))
2067 : 0 : return false;
2068 : :
2069 : : // If the method was not found fallback to defining the function
2070 : : // from type info...
2071 [ - + - - : 82 : if (!found &&
- + ]
2072 : 0 : !gjs_define_function(context, obj, m_gtype, method_info)) {
2073 : 0 : return false;
2074 : : }
2075 [ - + ]: 1832 : } else if (!gjs_define_function(context, obj, m_gtype, method_info)) {
2076 : 0 : return false;
2077 : : }
2078 : :
2079 : 1914 : *resolved = true; /* we defined the prop in obj */
2080 : : }
2081 : :
2082 : 1914 : return true;
2083 : 3815 : }
2084 : :
2085 : 23 : bool ObjectPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject,
2086 : : JS::MutableHandleIdVector properties,
2087 : : bool only_enumerable
2088 : : [[maybe_unused]]) {
2089 : : unsigned n_interfaces;
2090 : 23 : GType* interfaces = g_type_interfaces(gtype(), &n_interfaces);
2091 : :
2092 [ - + ]: 23 : for (unsigned k = 0; k < n_interfaces; k++) {
2093 : : GI::AutoInterfaceInfo iface_info{
2094 : 0 : g_irepository_find_by_gtype(nullptr, interfaces[k])};
2095 : :
2096 [ # # ]: 0 : if (!iface_info) {
2097 : 0 : continue;
2098 : : }
2099 : :
2100 : 0 : int n_methods = g_interface_info_get_n_methods(iface_info);
2101 : 0 : int n_properties = g_interface_info_get_n_properties(iface_info);
2102 [ # # ]: 0 : if (!properties.reserve(properties.length() + n_methods +
2103 : 0 : n_properties)) {
2104 : 0 : JS_ReportOutOfMemory(cx);
2105 : 0 : return false;
2106 : : }
2107 : :
2108 : : // Methods
2109 [ # # ]: 0 : for (int i = 0; i < n_methods; i++) {
2110 : : GI::AutoFunctionInfo meth_info{
2111 : 0 : g_interface_info_get_method(iface_info, i)};
2112 : 0 : GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info);
2113 : :
2114 [ # # ]: 0 : if (flags & GI_FUNCTION_IS_METHOD) {
2115 : 0 : const char* name = meth_info.name();
2116 : 0 : jsid id = gjs_intern_string_to_id(cx, name);
2117 [ # # ]: 0 : if (id.isVoid())
2118 : 0 : return false;
2119 : 0 : properties.infallibleAppend(id);
2120 : : }
2121 [ # # ]: 0 : }
2122 : :
2123 : : // Properties
2124 [ # # ]: 0 : for (int i = 0; i < n_properties; i++) {
2125 : : GI::AutoPropertyInfo prop_info{
2126 : 0 : g_interface_info_get_property(iface_info, i)};
2127 : :
2128 : 0 : Gjs::AutoChar js_name{gjs_hyphen_to_underscore(prop_info.name())};
2129 : :
2130 : 0 : jsid id = gjs_intern_string_to_id(cx, js_name);
2131 [ # # ]: 0 : if (id.isVoid())
2132 : 0 : return false;
2133 : 0 : properties.infallibleAppend(id);
2134 [ # # # # ]: 0 : }
2135 [ # # # ]: 0 : }
2136 : :
2137 : 23 : g_free(interfaces);
2138 : :
2139 [ + - ]: 23 : if (info()) {
2140 : 23 : int n_methods = g_object_info_get_n_methods(info());
2141 : 23 : int n_properties = g_object_info_get_n_properties(info());
2142 [ - + ]: 46 : if (!properties.reserve(properties.length() + n_methods +
2143 : 23 : n_properties)) {
2144 : 0 : JS_ReportOutOfMemory(cx);
2145 : 0 : return false;
2146 : : }
2147 : :
2148 : : // Methods
2149 [ + + ]: 1127 : for (int i = 0; i < n_methods; i++) {
2150 : 1104 : GI::AutoFunctionInfo meth_info{g_object_info_get_method(info(), i)};
2151 : 1104 : GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info);
2152 : :
2153 [ + + ]: 1104 : if (flags & GI_FUNCTION_IS_METHOD) {
2154 : 897 : const char* name = meth_info.name();
2155 : 897 : jsid id = gjs_intern_string_to_id(cx, name);
2156 [ - + ]: 897 : if (id.isVoid())
2157 : 0 : return false;
2158 : 897 : properties.infallibleAppend(id);
2159 : : }
2160 [ + - ]: 1104 : }
2161 : :
2162 : : // Properties
2163 [ + + ]: 184 : for (int i = 0; i < n_properties; i++) {
2164 : : GI::AutoPropertyInfo prop_info{
2165 : 161 : g_object_info_get_property(info(), i)};
2166 : :
2167 : 161 : Gjs::AutoChar js_name{gjs_hyphen_to_underscore(prop_info.name())};
2168 : 161 : jsid id = gjs_intern_string_to_id(cx, js_name);
2169 [ - + ]: 161 : if (id.isVoid())
2170 : 0 : return false;
2171 : 161 : properties.infallibleAppend(id);
2172 [ + - + - ]: 161 : }
2173 : : }
2174 : :
2175 : 23 : return true;
2176 : : }
2177 : :
2178 : : /* Set properties from args to constructor (args[0] is supposed to be
2179 : : * a hash) */
2180 : 282 : bool ObjectPrototype::props_to_g_parameters(
2181 : : JSContext* context, Gjs::AutoTypeClass<GObjectClass> const& object_class,
2182 : : JS::HandleObject props, std::vector<const char*>* names,
2183 : : AutoGValueVector* values) {
2184 : : size_t ix, length;
2185 : 282 : JS::RootedId prop_id(context);
2186 : 282 : JS::RootedValue value(context);
2187 : 282 : JS::Rooted<JS::IdVector> ids(context, context);
2188 : 282 : std::unordered_set<GParamSpec*> visited_params;
2189 [ - + ]: 282 : if (!JS_Enumerate(context, props, &ids)) {
2190 : 0 : gjs_throw(context, "Failed to create property iterator for object props hash");
2191 : 0 : return false;
2192 : : }
2193 : :
2194 : 282 : values->reserve(ids.length());
2195 [ + + ]: 1022 : for (ix = 0, length = ids.length(); ix < length; ix++) {
2196 : : /* ids[ix] is reachable because props is rooted, but require_property
2197 : : * doesn't know that */
2198 : 749 : prop_id = ids[ix];
2199 : :
2200 [ - + ]: 749 : if (!prop_id.isString())
2201 : 0 : return gjs_wrapper_throw_nonexistent_field(
2202 : 0 : context, m_gtype, gjs_debug_id(prop_id).c_str());
2203 : :
2204 : 749 : JS::RootedString js_prop_name(context, prop_id.toString());
2205 : : GParamSpec* param_spec =
2206 : 749 : find_param_spec_from_id(context, object_class, js_prop_name);
2207 [ + + ]: 749 : if (!param_spec)
2208 : 2 : return false;
2209 : :
2210 [ - + ]: 747 : if (visited_params.find(param_spec) != visited_params.end())
2211 : 0 : continue;
2212 : 747 : visited_params.insert(param_spec);
2213 : :
2214 [ - + ]: 747 : if (!JS_GetPropertyById(context, props, prop_id, &value))
2215 : 0 : return false;
2216 [ + + ]: 747 : if (value.isUndefined()) {
2217 : 1 : gjs_throw(context, "Invalid value 'undefined' for property %s in "
2218 : : "object initializer.", param_spec->name);
2219 : 1 : return false;
2220 : : }
2221 : :
2222 [ - + ]: 746 : if (!(param_spec->flags & G_PARAM_WRITABLE))
2223 : 0 : return gjs_wrapper_throw_readonly_field(context, m_gtype,
2224 : 0 : param_spec->name);
2225 : : /* prevent setting the prop even in JS */
2226 : :
2227 : : Gjs::AutoGValue& gvalue =
2228 : 746 : values->emplace_back(G_PARAM_SPEC_VALUE_TYPE(param_spec));
2229 [ + + ]: 746 : if (!gjs_value_to_g_value(context, value, &gvalue))
2230 : 6 : return false;
2231 : :
2232 : 740 : names->push_back(param_spec->name); // owned by GParamSpec
2233 [ + + - ]: 749 : }
2234 : :
2235 : 273 : return true;
2236 : 282 : }
2237 : :
2238 : 156 : void ObjectInstance::wrapped_gobj_dispose_notify(
2239 : : void* data, GObject* where_the_object_was GJS_USED_VERBOSE_LIFECYCLE) {
2240 : 156 : auto *priv = static_cast<ObjectInstance *>(data);
2241 : 156 : priv->gobj_dispose_notify();
2242 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Wrapped GObject %p disposed",
2243 : : where_the_object_was);
2244 : 156 : }
2245 : :
2246 : 156 : void ObjectInstance::track_gobject_finalization() {
2247 : 156 : auto quark = ObjectBase::disposed_quark();
2248 : 156 : g_object_steal_qdata(m_ptr, quark);
2249 : 156 : g_object_set_qdata_full(m_ptr, quark, this, [](void* data) {
2250 : 18 : auto* self = static_cast<ObjectInstance*>(data);
2251 : 18 : self->m_gobj_finalized = true;
2252 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Wrapped GObject %p finalized",
2253 : : self->m_ptr.get());
2254 : 18 : });
2255 : 156 : }
2256 : :
2257 : 161 : void ObjectInstance::ignore_gobject_finalization() {
2258 : 161 : auto quark = ObjectBase::disposed_quark();
2259 [ + + ]: 161 : if (g_object_get_qdata(m_ptr, quark) == this) {
2260 : 138 : g_object_steal_qdata(m_ptr, quark);
2261 : 138 : g_object_set_qdata(m_ptr, quark, gjs_int_to_pointer(DISPOSED_OBJECT));
2262 : : }
2263 : 161 : }
2264 : :
2265 : : void
2266 : 156 : ObjectInstance::gobj_dispose_notify(void)
2267 : : {
2268 : 156 : m_gobj_disposed = true;
2269 : :
2270 : 156 : unset_object_qdata();
2271 : 156 : track_gobject_finalization();
2272 : :
2273 [ + + ]: 156 : if (m_uses_toggle_ref) {
2274 : 70 : g_object_ref(m_ptr.get());
2275 : 70 : g_object_remove_toggle_ref(m_ptr, wrapped_gobj_toggle_notify, this);
2276 : 70 : ToggleQueue::get_default()->cancel(this);
2277 : 70 : wrapped_gobj_toggle_notify(this, m_ptr, TRUE);
2278 : 70 : m_uses_toggle_ref = false;
2279 : : }
2280 : :
2281 [ + + ]: 156 : if (GjsContextPrivate::from_current_context()->is_owner_thread())
2282 : 153 : discard_wrapper();
2283 : 156 : }
2284 : :
2285 : 503 : void ObjectInstance::remove_wrapped_gobjects_if(
2286 : : const ObjectInstance::Predicate& predicate,
2287 : : const ObjectInstance::Action& action) {
2288 : 1006 : for (auto link = s_wrapped_gobject_list.begin(),
2289 : 503 : last = s_wrapped_gobject_list.end();
2290 [ + + ]: 2989 : link != last;) {
2291 [ + + ]: 2486 : if (predicate(*link)) {
2292 : 1384 : action(*link);
2293 : 1384 : link = s_wrapped_gobject_list.erase(link);
2294 : 1384 : continue;
2295 : : }
2296 : 1102 : ++link;
2297 : : }
2298 : 503 : }
2299 : :
2300 : : /*
2301 : : * ObjectInstance::context_dispose_notify:
2302 : : *
2303 : : * Callback called when the #GjsContext is disposed. It just calls
2304 : : * handle_context_dispose() on every ObjectInstance.
2305 : : */
2306 : 255 : void ObjectInstance::context_dispose_notify(void*, GObject* where_the_object_was
2307 : : [[maybe_unused]]) {
2308 : 255 : std::for_each(s_wrapped_gobject_list.begin(), s_wrapped_gobject_list.end(),
2309 : : std::mem_fn(&ObjectInstance::handle_context_dispose));
2310 : 255 : }
2311 : :
2312 : : /*
2313 : : * ObjectInstance::handle_context_dispose:
2314 : : *
2315 : : * Called on each existing ObjectInstance when the #GjsContext is disposed.
2316 : : */
2317 : 1173 : void ObjectInstance::handle_context_dispose(void) {
2318 [ + + ]: 1173 : if (wrapper_is_rooted()) {
2319 : 140 : debug_lifecycle("Was rooted, but unrooting due to GjsContext dispose");
2320 : 140 : discard_wrapper();
2321 : : }
2322 : 1173 : }
2323 : :
2324 : : void
2325 : 1634 : ObjectInstance::toggle_down(void)
2326 : : {
2327 : 1634 : debug_lifecycle("Toggle notify DOWN");
2328 : :
2329 : : /* Change to weak ref so the wrapper-wrappee pair can be
2330 : : * collected by the GC
2331 : : */
2332 [ + + ]: 1634 : if (wrapper_is_rooted()) {
2333 : 1496 : debug_lifecycle("Unrooting wrapper");
2334 : 1496 : GjsContextPrivate* gjs = GjsContextPrivate::from_current_context();
2335 : 1496 : switch_to_unrooted(gjs->context());
2336 : :
2337 : : /* During a GC, the collector asks each object which other
2338 : : * objects that it wants to hold on to so if there's an entire
2339 : : * section of the heap graph that's not connected to anything
2340 : : * else, and not reachable from the root set, then it can be
2341 : : * trashed all at once.
2342 : : *
2343 : : * GObjects, however, don't work like that, there's only a
2344 : : * reference count but no notion of who owns the reference so,
2345 : : * a JS object that's wrapping a GObject is unconditionally held
2346 : : * alive as long as the GObject has >1 references.
2347 : : *
2348 : : * Since we cannot know how many more wrapped GObjects are going
2349 : : * be marked for garbage collection after the owner is destroyed,
2350 : : * always queue a garbage collection when a toggle reference goes
2351 : : * down.
2352 : : */
2353 [ + - ]: 1496 : if (!gjs->destroying())
2354 : 1496 : gjs->schedule_gc();
2355 : : }
2356 : 1634 : }
2357 : :
2358 : : void
2359 : 891 : ObjectInstance::toggle_up(void)
2360 : : {
2361 [ + - + + : 891 : if (G_UNLIKELY(!m_ptr || m_gobj_disposed || m_gobj_finalized)) {
- + + + ]
2362 : 1 : if (m_ptr) {
2363 : : gjs_debug_lifecycle(
2364 : : GJS_DEBUG_GOBJECT,
2365 : : "Avoid to toggle up a wrapper for a %s object: %p (%s)",
2366 : : m_gobj_finalized ? "finalized" : "disposed", m_ptr.get(),
2367 : : g_type_name(gtype()));
2368 : : } else {
2369 : : gjs_debug_lifecycle(
2370 : : GJS_DEBUG_GOBJECT,
2371 : : "Avoid to toggle up a wrapper for a released %s object (%p)",
2372 : : g_type_name(gtype()), this);
2373 : : }
2374 : 1 : return;
2375 : : }
2376 : :
2377 : : /* We need to root the JSObject associated with the passed in GObject so it
2378 : : * doesn't get garbage collected (and lose any associated javascript state
2379 : : * such as custom properties).
2380 : : */
2381 [ - + ]: 890 : if (!has_wrapper()) /* Object already GC'd */
2382 : 0 : return;
2383 : :
2384 : 890 : debug_lifecycle("Toggle notify UP");
2385 : :
2386 : : /* Change to strong ref so the wrappee keeps the wrapper alive
2387 : : * in case the wrapper has data in it that the app cares about
2388 : : */
2389 [ + + ]: 890 : if (!wrapper_is_rooted()) {
2390 : : // FIXME: thread the context through somehow. Maybe by looking up the
2391 : : // realm that obj belongs to.
2392 : 889 : debug_lifecycle("Rooting wrapper");
2393 : 889 : auto* cx = GjsContextPrivate::from_current_context()->context();
2394 : 889 : switch_to_rooted(cx);
2395 : : }
2396 : : }
2397 : :
2398 : 9 : static void toggle_handler(ObjectInstance* self,
2399 : : ToggleQueue::Direction direction) {
2400 [ + + - ]: 9 : switch (direction) {
2401 : 4 : case ToggleQueue::UP:
2402 : 4 : self->toggle_up();
2403 : 4 : break;
2404 : 5 : case ToggleQueue::DOWN:
2405 : 5 : self->toggle_down();
2406 : 5 : break;
2407 : 0 : default:
2408 : : g_assert_not_reached();
2409 : : }
2410 : 9 : }
2411 : :
2412 : 2575 : void ObjectInstance::wrapped_gobj_toggle_notify(void* instance, GObject*,
2413 : : gboolean is_last_ref) {
2414 : : bool is_main_thread;
2415 : : bool toggle_up_queued, toggle_down_queued;
2416 : 2575 : auto* self = static_cast<ObjectInstance*>(instance);
2417 : :
2418 : 2575 : GjsContextPrivate* gjs = GjsContextPrivate::from_current_context();
2419 [ - + ]: 2575 : if (gjs->destroying()) {
2420 : : /* Do nothing here - we're in the process of disassociating
2421 : : * the objects.
2422 : : */
2423 : 0 : return;
2424 : : }
2425 : :
2426 : : /* We only want to touch javascript from one thread.
2427 : : * If we're not in that thread, then we need to defer processing
2428 : : * to it.
2429 : : * In case we're toggling up (and thus rooting the JS object) we
2430 : : * also need to take care if GC is running. The marking side
2431 : : * of it is taken care by JS::Heap, which we use in GjsMaybeOwned,
2432 : : * so we're safe. As for sweeping, it is too late: the JS object
2433 : : * is dead, and attempting to keep it alive would soon crash
2434 : : * the process. Plus, if we touch the JSAPI from another thread, libmozjs
2435 : : * aborts in most cases when in debug mode.
2436 : : * Thus, we drain the toggle queue when GC starts, in order to
2437 : : * prevent this from happening.
2438 : : * In practice, a toggle up during JS finalize can only happen
2439 : : * for temporary refs/unrefs of objects that are garbage anyway,
2440 : : * because JS code is never invoked while the finalizers run
2441 : : * and C code needs to clean after itself before it returns
2442 : : * from dispose()/finalize().
2443 : : * On the other hand, toggling down is a lot simpler, because
2444 : : * we're creating more garbage. So we just unroot the object, make it a
2445 : : * weak pointer, and wait for the next GC cycle.
2446 : : *
2447 : : * Note that one would think that toggling up only happens
2448 : : * in the main thread (because toggling up is the result of
2449 : : * the JS object, previously visible only to JS code, becoming
2450 : : * visible to the refcounted C world), but because of weird
2451 : : * weak singletons like g_bus_get_sync() objects can see toggle-ups
2452 : : * from different threads too.
2453 : : */
2454 : 2575 : is_main_thread = gjs->is_owner_thread();
2455 : :
2456 : 2575 : auto toggle_queue = ToggleQueue::get_default();
2457 : 2575 : std::tie(toggle_down_queued, toggle_up_queued) =
2458 : 5150 : toggle_queue->is_queued(self);
2459 [ + + + + ]: 2575 : bool anything_queued = toggle_up_queued || toggle_down_queued;
2460 : :
2461 [ + + ]: 2575 : if (is_last_ref) {
2462 : : /* We've transitions from 2 -> 1 references,
2463 : : * The JSObject is rooted and we need to unroot it so it
2464 : : * can be garbage collected
2465 : : */
2466 [ + + + + ]: 1657 : if (is_main_thread && !anything_queued) {
2467 : 1629 : self->toggle_down();
2468 : : } else {
2469 : 28 : toggle_queue->enqueue(self, ToggleQueue::DOWN, toggle_handler);
2470 : : }
2471 : : } else {
2472 : : /* We've transitioned from 1 -> 2 references.
2473 : : *
2474 : : * The JSObject associated with the gobject is not rooted,
2475 : : * but it needs to be. We'll root it.
2476 : : */
2477 [ + + + + : 1812 : if (is_main_thread && !anything_queued &&
+ + ]
2478 [ + + ]: 894 : !JS::RuntimeHeapIsCollecting()) {
2479 : 887 : self->toggle_up();
2480 : : } else {
2481 : 31 : toggle_queue->enqueue(self, ToggleQueue::UP, toggle_handler);
2482 : : }
2483 : : }
2484 : 2575 : }
2485 : :
2486 : : void
2487 : 1697 : ObjectInstance::release_native_object(void)
2488 : : {
2489 : : static GType gdksurface_type = 0;
2490 : :
2491 : 1697 : discard_wrapper();
2492 : :
2493 [ + + ]: 1697 : if (m_gobj_finalized) {
2494 : 19 : g_critical(
2495 : : "Object %p of type %s has been finalized while it was still "
2496 : : "owned by gjs, this is due to invalid memory management.",
2497 : : m_ptr.get(), g_type_name(gtype()));
2498 : 19 : m_ptr.release();
2499 : 19 : return;
2500 : : }
2501 : :
2502 : 1678 : if (m_ptr)
2503 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Releasing native object %s %p",
2504 : : g_type_name(gtype()), m_ptr.get());
2505 : :
2506 [ + + ]: 1678 : if (m_gobj_disposed)
2507 : 161 : ignore_gobject_finalization();
2508 : :
2509 [ + + + - ]: 1678 : if (m_uses_toggle_ref && !m_gobj_disposed) {
2510 : 677 : g_object_remove_toggle_ref(m_ptr.release(), wrapped_gobj_toggle_notify,
2511 : : this);
2512 : 677 : return;
2513 : : }
2514 : :
2515 : : // Unref the object. Handle any special cases for destruction here
2516 [ + + ]: 1001 : if (m_ptr->ref_count == 1) {
2517 : : // Quickest way to check for GdkSurface if Gdk has been loaded?
2518 : : // surface_type may be 0 if Gdk not loaded. The type may be a private
2519 : : // type and not have introspection info.
2520 [ + + ]: 891 : if (!gdksurface_type)
2521 : 823 : gdksurface_type = g_type_from_name("GdkSurface");
2522 [ + + + - : 891 : if (gdksurface_type && g_type_is_a(gtype(), gdksurface_type)) {
- + - + ]
2523 : 0 : GObject* ptr = m_ptr.release();
2524 : :
2525 : : // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/6289
2526 : : GI::AutoObjectInfo surface_info{
2527 : 0 : g_irepository_find_by_gtype(nullptr, gdksurface_type)};
2528 : 0 : g_assert(surface_info && "Could not find introspected GdkSurface info");
2529 : : GI::AutoFunctionInfo destroy_func{
2530 : 0 : g_object_info_find_method(surface_info, "destroy")};
2531 : : GIArgument destroy_args;
2532 : 0 : gjs_arg_set(&destroy_args, ptr);
2533 : : GIArgument unused_return;
2534 : :
2535 : 0 : Gjs::AutoError err;
2536 [ # # ]: 0 : if (!g_function_info_invoke(destroy_func, &destroy_args, 1, nullptr,
2537 : : 0, &unused_return, err.out()))
2538 : 0 : g_critical("Error destroying GdkSurface %p: %s", ptr,
2539 : : err->message);
2540 : 0 : }
2541 : : }
2542 : :
2543 : 1001 : m_ptr = nullptr;
2544 : : }
2545 : :
2546 : : /* At shutdown, we need to ensure we've cleared the context of any
2547 : : * pending toggle references.
2548 : : */
2549 : : void
2550 : 762 : gjs_object_clear_toggles(void)
2551 : : {
2552 : 762 : ToggleQueue::get_default()->handle_all_toggles(toggle_handler);
2553 : 762 : }
2554 : :
2555 : : void
2556 : 255 : gjs_object_shutdown_toggle_queue(void)
2557 : : {
2558 : 255 : ToggleQueue::get_default()->shutdown();
2559 : 255 : }
2560 : :
2561 : : /*
2562 : : * ObjectInstance::prepare_shutdown:
2563 : : *
2564 : : * Called when the #GjsContext is disposed, in order to release all GC roots of
2565 : : * JSObjects that are held by GObjects.
2566 : : */
2567 : 255 : void ObjectInstance::prepare_shutdown(void) {
2568 : : /* We iterate over all of the objects, breaking the JS <-> C
2569 : : * association. We avoid the potential recursion implied in:
2570 : : * toggle ref removal -> gobj dispose -> toggle ref notify
2571 : : * by emptying the toggle queue earlier in the shutdown sequence. */
2572 : 510 : ObjectInstance::remove_wrapped_gobjects_if(
2573 : 510 : std::mem_fn(&ObjectInstance::wrapper_is_rooted),
2574 : 255 : std::mem_fn(&ObjectInstance::release_native_object));
2575 : 255 : }
2576 : :
2577 : 1715 : ObjectInstance::ObjectInstance(ObjectPrototype* prototype,
2578 : 1715 : JS::HandleObject object)
2579 : : : GIWrapperInstance(prototype, object),
2580 : 1715 : m_wrapper_finalized(false),
2581 : 1715 : m_gobj_disposed(false),
2582 : 1715 : m_gobj_finalized(false),
2583 : 1715 : m_uses_toggle_ref(false) {
2584 : : GTypeQuery query;
2585 : 1715 : g_type_query(gtype(), &query);
2586 [ + - ]: 1715 : if (G_LIKELY(query.type))
2587 : 1715 : JS::AddAssociatedMemory(object, query.instance_size,
2588 : : MemoryUse::GObjectInstanceStruct);
2589 : :
2590 : 1715 : GJS_INC_COUNTER(object_instance);
2591 : 1715 : }
2592 : :
2593 : 735 : ObjectPrototype::ObjectPrototype(GIObjectInfo* info, GType gtype)
2594 : 735 : : GIWrapperPrototype(info, gtype) {
2595 : 735 : g_type_class_ref(gtype);
2596 : :
2597 : 735 : GJS_INC_COUNTER(object_prototype);
2598 : 735 : }
2599 : :
2600 : : /*
2601 : : * ObjectInstance::update_heap_wrapper_weak_pointers:
2602 : : *
2603 : : * Private callback, called after the JS engine finishes garbage collection, and
2604 : : * notifies when weak pointers need to be either moved or swept.
2605 : : */
2606 : 248 : void ObjectInstance::update_heap_wrapper_weak_pointers(JSTracer* trc,
2607 : : JS::Compartment*,
2608 : : void*) {
2609 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Weak pointer update callback, "
2610 : : "%zu wrapped GObject(s) to examine",
2611 : : ObjectInstance::num_wrapped_gobjects());
2612 : :
2613 : : // Take a lock on the queue till we're done with it, so that we don't
2614 : : // risk that another thread will queue something else while sweeping
2615 : 248 : auto locked_queue = ToggleQueue::get_default();
2616 : :
2617 : 496 : ObjectInstance::remove_wrapped_gobjects_if(
2618 : 496 : [&trc](ObjectInstance* instance) -> bool {
2619 : 2463 : return instance->weak_pointer_was_finalized(trc);
2620 : : },
2621 : 248 : std::mem_fn(&ObjectInstance::disassociate_js_gobject));
2622 : 248 : }
2623 : :
2624 : 2463 : bool ObjectInstance::weak_pointer_was_finalized(JSTracer* trc) {
2625 [ + + + + : 2463 : if (has_wrapper() && !wrapper_is_rooted()) {
+ + ]
2626 : : bool toggle_down_queued, toggle_up_queued;
2627 : :
2628 : 1700 : auto toggle_queue = ToggleQueue::get_default();
2629 : 1700 : std::tie(toggle_down_queued, toggle_up_queued) =
2630 : 3400 : toggle_queue->is_queued(this);
2631 : :
2632 [ + - + + ]: 1700 : if (!toggle_down_queued && toggle_up_queued)
2633 : 2 : return false;
2634 : :
2635 [ + + ]: 1698 : if (!update_after_gc(trc))
2636 : 314 : return false;
2637 : :
2638 [ - + ]: 1384 : if (toggle_down_queued)
2639 : 0 : toggle_queue->cancel(this);
2640 : :
2641 : : /* Ouch, the JS object is dead already. Disassociate the
2642 : : * GObject and hope the GObject dies too. (Remove it from
2643 : : * the weak pointer list first, since the disassociation
2644 : : * may also cause it to be erased.)
2645 : : */
2646 : 1384 : debug_lifecycle("Found GObject weak pointer whose JS wrapper is about "
2647 : : "to be finalized");
2648 : 1384 : return true;
2649 : 1700 : }
2650 : 763 : return false;
2651 : : }
2652 : :
2653 : : /*
2654 : : * ObjectInstance::ensure_weak_pointer_callback:
2655 : : *
2656 : : * Private method called when adding a weak pointer for the first time.
2657 : : */
2658 : 1698 : void ObjectInstance::ensure_weak_pointer_callback(JSContext* cx) {
2659 [ + + ]: 1698 : if (!s_weak_pointer_callback) {
2660 : 31 : JS_AddWeakPointerCompartmentCallback(
2661 : : cx, &ObjectInstance::update_heap_wrapper_weak_pointers, nullptr);
2662 : 31 : s_weak_pointer_callback = true;
2663 : : }
2664 : 1698 : }
2665 : :
2666 : : void
2667 : 1698 : ObjectInstance::associate_js_gobject(JSContext *context,
2668 : : JS::HandleObject object,
2669 : : GObject *gobj)
2670 : : {
2671 : 1698 : g_assert(!wrapper_is_rooted());
2672 : :
2673 : 1698 : m_uses_toggle_ref = false;
2674 : 1698 : m_ptr = gobj;
2675 : 1698 : set_object_qdata();
2676 : 1698 : m_wrapper = object;
2677 : 1698 : m_gobj_disposed = !!g_object_get_qdata(gobj, ObjectBase::disposed_quark());
2678 : :
2679 : 1698 : ensure_weak_pointer_callback(context);
2680 : 1698 : link();
2681 : :
2682 [ + + ]: 1698 : if (!G_UNLIKELY(m_gobj_disposed))
2683 : 1674 : g_object_weak_ref(gobj, wrapped_gobj_dispose_notify, this);
2684 : 1698 : }
2685 : :
2686 : 1513 : void ObjectInstance::ensure_uses_toggle_ref(JSContext* cx) {
2687 [ + + ]: 1513 : if (m_uses_toggle_ref)
2688 : 763 : return;
2689 : :
2690 [ + + ]: 750 : if (!check_gobject_disposed_or_finalized("add toggle reference on"))
2691 : 2 : return;
2692 : :
2693 : 748 : debug_lifecycle("Switching object instance to toggle ref");
2694 : :
2695 : 748 : g_assert(!wrapper_is_rooted());
2696 : :
2697 : : /* OK, here is where things get complicated. We want the
2698 : : * wrapped gobj to keep the JSObject* wrapper alive, because
2699 : : * people might set properties on the JSObject* that they care
2700 : : * about. Therefore, whenever the refcount on the wrapped gobj
2701 : : * is >1, i.e. whenever something other than the wrapper is
2702 : : * referencing the wrapped gobj, the wrapped gobj has a strong
2703 : : * ref (gc-roots the wrapper). When the refcount on the
2704 : : * wrapped gobj is 1, then we change to a weak ref to allow
2705 : : * the wrapper to be garbage collected (and thus unref the
2706 : : * wrappee).
2707 : : */
2708 : 748 : m_uses_toggle_ref = true;
2709 : 748 : switch_to_rooted(cx);
2710 : 748 : g_object_add_toggle_ref(m_ptr, wrapped_gobj_toggle_notify, this);
2711 : :
2712 : : /* We now have both a ref and a toggle ref, we only want the toggle ref.
2713 : : * This may immediately remove the GC root we just added, since refcount
2714 : : * may drop to 1. */
2715 : 748 : g_object_unref(m_ptr);
2716 : : }
2717 : :
2718 : : template <typename T>
2719 : 3830 : static void invalidate_closure_collection(T* closures, void* data,
2720 : : GClosureNotify notify_func) {
2721 : 3830 : g_assert(closures);
2722 : 3830 : g_assert(notify_func);
2723 : :
2724 [ + + ]: 4392 : for (auto it = closures->begin(); it != closures->end();) {
2725 : : // This will also free the closure data, through the closure
2726 : : // invalidation mechanism, but adding a temporary reference to
2727 : : // ensure that the closure is still valid when calling invalidation
2728 : : // notify callbacks
2729 : 281 : Gjs::AutoGClosure closure{*it, Gjs::TakeOwnership{}};
2730 : 281 : it = closures->erase(it);
2731 : :
2732 : : // Only call the invalidate notifiers that won't touch this vector
2733 : 281 : g_closure_remove_invalidate_notifier(closure, data, notify_func);
2734 : 281 : g_closure_invalidate(closure);
2735 : : }
2736 : :
2737 : 3830 : g_assert(closures->empty());
2738 : 3830 : }
2739 : :
2740 : : // Note: m_wrapper (the JS object) may already be null when this is called, if
2741 : : // it was finalized while the GObject was toggled down.
2742 : : void
2743 : 1384 : ObjectInstance::disassociate_js_gobject(void)
2744 : : {
2745 : : bool had_toggle_down, had_toggle_up;
2746 : :
2747 : 1384 : std::tie(had_toggle_down, had_toggle_up) =
2748 : 2768 : ToggleQueue::get_default()->cancel(this);
2749 [ - + - - ]: 1384 : if (had_toggle_up && !had_toggle_down) {
2750 : 0 : g_error(
2751 : : "JS object wrapper for GObject %p (%s) is being released while "
2752 : : "toggle references are still pending.",
2753 : : m_ptr.get(), type_name());
2754 : : }
2755 : :
2756 [ + + ]: 1384 : if (!m_gobj_disposed)
2757 : 1357 : g_object_weak_unref(m_ptr.get(), wrapped_gobj_dispose_notify, this);
2758 : :
2759 [ + + ]: 1384 : if (!m_gobj_finalized) {
2760 : : /* Fist, remove the wrapper pointer from the wrapped GObject */
2761 : 1382 : unset_object_qdata();
2762 : : }
2763 : :
2764 : : /* Now release all the resources the current wrapper has */
2765 : 1384 : invalidate_closures();
2766 : 1384 : release_native_object();
2767 : :
2768 : : /* Mark that a JS object once existed, but it doesn't any more */
2769 : 1384 : m_wrapper_finalized = true;
2770 : 1384 : }
2771 : :
2772 : 1027 : bool ObjectInstance::init_impl(JSContext* context, const JS::CallArgs& args,
2773 : : JS::HandleObject object) {
2774 : 1027 : g_assert(gtype() != G_TYPE_NONE);
2775 : :
2776 [ + + - + ]: 1028 : if (args.length() > 1 &&
2777 [ - + ]: 1 : !JS::WarnUTF8(context,
2778 : : "Too many arguments to the constructor of %s: expected "
2779 : : "1, got %u",
2780 : : name(), args.length()))
2781 : 0 : return false;
2782 : :
2783 : 1027 : Gjs::AutoTypeClass<GObjectClass> object_class{gtype()};
2784 : 1027 : std::vector<const char *> names;
2785 : 1027 : AutoGValueVector values;
2786 : :
2787 [ + + + + : 1027 : if (args.length() > 0 && !args[0].isUndefined()) {
+ + ]
2788 [ + + ]: 284 : if (!args[0].isObject()) {
2789 : 1 : gjs_throw(context,
2790 : : "Argument to the constructor of %s should be a plain JS "
2791 : : "object with properties to set",
2792 : : name());
2793 : 11 : return false;
2794 : : }
2795 : :
2796 : 283 : JS::RootedObject props(context, &args[0].toObject());
2797 [ + + ]: 283 : if (ObjectBase::for_js(context, props)) {
2798 : 1 : gjs_throw(context,
2799 : : "Argument to the constructor of %s should be a plain JS "
2800 : : "object with properties to set",
2801 : : name());
2802 : 1 : return false;
2803 : : }
2804 [ + + ]: 282 : if (!m_proto->props_to_g_parameters(context, object_class, props,
2805 : : &names, &values))
2806 : 9 : return false;
2807 [ + + ]: 283 : }
2808 : :
2809 [ + + ]: 1016 : if (G_TYPE_IS_ABSTRACT(gtype())) {
2810 : 1 : gjs_throw(context,
2811 : : "Cannot instantiate abstract type %s", g_type_name(gtype()));
2812 : 1 : return false;
2813 : : }
2814 : :
2815 : : // Mark this object in the construction stack, it will be popped in
2816 : : // gjs_object_custom_init() in gi/gobject.cpp.
2817 [ + + ]: 1015 : if (is_custom_js_class()) {
2818 : 491 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
2819 [ - + ]: 491 : if (!gjs->object_init_list().append(object)) {
2820 : 0 : JS_ReportOutOfMemory(context);
2821 : 0 : return false;
2822 : : }
2823 : : }
2824 : :
2825 : 1015 : g_assert(names.size() == values.size());
2826 : 1015 : GObject* gobj = g_object_new_with_properties(gtype(), values.size(),
2827 : 1015 : names.data(), values.data());
2828 : :
2829 : 1015 : ObjectInstance *other_priv = ObjectInstance::for_gobject(gobj);
2830 [ + + + + : 1015 : if (other_priv && other_priv->m_wrapper != object.get()) {
+ + ]
2831 : : /* g_object_new_with_properties() returned an object that's already
2832 : : * tracked by a JS object.
2833 : : *
2834 : : * This typically occurs in one of two cases:
2835 : : * - This object is a singleton like IBus.IBus
2836 : : * - This object passed itself to JS before g_object_new_* returned
2837 : : *
2838 : : * In these cases, return the existing JS wrapper object instead
2839 : : * of creating a new one.
2840 : : *
2841 : : * 'object' has a value that was originally created by
2842 : : * JS_NewObjectForConstructor in GJS_NATIVE_CONSTRUCTOR_PRELUDE, but
2843 : : * we're not actually using it, so just let it get collected. Avoiding
2844 : : * this would require a non-trivial amount of work.
2845 : : * */
2846 : 1 : bool toggle_ref_added = false;
2847 [ + - ]: 1 : if (!m_uses_toggle_ref) {
2848 : 1 : other_priv->ensure_uses_toggle_ref(context);
2849 : 1 : toggle_ref_added = m_uses_toggle_ref;
2850 : : }
2851 : :
2852 : 1 : args.rval().setObject(*other_priv->m_wrapper.get());
2853 : :
2854 [ - + ]: 1 : if (toggle_ref_added)
2855 [ # # ]: 0 : g_clear_object(&gobj); /* We already own a reference */
2856 : 1 : return true;
2857 : : }
2858 : :
2859 [ - + + - : 1149 : if (G_IS_INITIALLY_UNOWNED(gobj) &&
- + + + +
+ + + ]
2860 : 135 : !g_object_is_floating(gobj)) {
2861 : : /* GtkWindow does not return a ref to caller of g_object_new.
2862 : : * Need a flag in gobject-introspection to tell us this.
2863 : : */
2864 : 75 : gjs_debug(GJS_DEBUG_GOBJECT,
2865 : : "Newly-created object is initially unowned but we did not get the "
2866 : : "floating ref, probably GtkWindow, using hacky workaround");
2867 : 75 : g_object_ref(gobj);
2868 [ + + ]: 939 : } else if (g_object_is_floating(gobj)) {
2869 : 60 : g_object_ref_sink(gobj);
2870 : : } else {
2871 : : /* we should already have a ref */
2872 : : }
2873 : :
2874 [ + + ]: 1014 : if (!m_ptr)
2875 : 523 : associate_js_gobject(context, object, gobj);
2876 : :
2877 : : TRACE(GJS_OBJECT_WRAPPER_NEW(this, m_ptr, ns(), name()));
2878 : :
2879 : 1014 : args.rval().setObject(*object);
2880 : 1014 : return true;
2881 : 1027 : }
2882 : :
2883 : : // See GIWrapperBase::constructor()
2884 : 1031 : bool ObjectInstance::constructor_impl(JSContext* context,
2885 : : JS::HandleObject object,
2886 : : const JS::CallArgs& argv) {
2887 : 1031 : JS::RootedValue initer(context);
2888 : 1031 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
2889 : 1031 : const auto& new_target = argv.newTarget();
2890 : : bool has_gtype;
2891 : :
2892 : 1031 : g_assert(new_target.isObject() && "new.target needs to be an object");
2893 : 1031 : JS::RootedObject rooted_target(context, &new_target.toObject());
2894 [ - + ]: 1031 : if (!JS_HasOwnPropertyById(context, rooted_target, gjs->atoms().gtype(),
2895 : : &has_gtype))
2896 : 0 : return false;
2897 : :
2898 [ - + ]: 1031 : if (!has_gtype) {
2899 : 0 : gjs_throw(context,
2900 : : "Tried to construct an object without a GType; are "
2901 : : "you using GObject.registerClass() when inheriting "
2902 : : "from a GObject type?");
2903 : 0 : return false;
2904 : : }
2905 : :
2906 : 2062 : return gjs_object_require_property(context, object, "GObject instance",
2907 [ + - + + ]: 3093 : gjs->atoms().init(), &initer) &&
2908 : 2062 : gjs->call_function(object, initer, argv, argv.rval());
2909 : 1031 : }
2910 : :
2911 : 260 : void ObjectInstance::trace_impl(JSTracer* tracer) {
2912 [ + + ]: 334 : for (GClosure *closure : m_closures)
2913 : 74 : Gjs::Closure::for_gclosure(closure)->trace(tracer);
2914 : 260 : }
2915 : :
2916 : 2412 : void ObjectPrototype::trace_impl(JSTracer* tracer) {
2917 : 2412 : m_unresolvable_cache.trace(tracer);
2918 [ + + ]: 2523 : for (GClosure* closure : m_vfuncs)
2919 : 111 : Gjs::Closure::for_gclosure(closure)->trace(tracer);
2920 : 2412 : }
2921 : :
2922 : 1714 : void ObjectInstance::finalize_impl(JS::GCContext* gcx, JSObject* obj) {
2923 : : GTypeQuery query;
2924 : 1714 : g_type_query(gtype(), &query);
2925 [ + - ]: 1714 : if (G_LIKELY(query.type))
2926 : 1714 : JS::RemoveAssociatedMemory(obj, query.instance_size,
2927 : : MemoryUse::GObjectInstanceStruct);
2928 : :
2929 : 1714 : GIWrapperInstance::finalize_impl(gcx, obj);
2930 : 1714 : }
2931 : :
2932 : 1714 : ObjectInstance::~ObjectInstance() {
2933 : : TRACE(GJS_OBJECT_WRAPPER_FINALIZE(this, m_ptr, ns(), name()));
2934 : :
2935 : 1714 : invalidate_closures();
2936 : :
2937 : : // Do not keep the queue locked here, as we may want to leave the other
2938 : : // threads to queue toggle events till we're owning the GObject so that
2939 : : // eventually (once the toggle reference is finally removed) we can be
2940 : : // sure that no other toggle event will target this (soon dead) wrapper.
2941 : : bool had_toggle_up;
2942 : : bool had_toggle_down;
2943 : 1714 : std::tie(had_toggle_down, had_toggle_up) =
2944 : 3428 : ToggleQueue::get_default()->cancel(this);
2945 : :
2946 : : /* GObject is not already freed */
2947 [ + + ]: 1714 : if (m_ptr) {
2948 [ + + - + ]: 313 : if (!had_toggle_up && had_toggle_down) {
2949 : 0 : g_error(
2950 : : "Finalizing wrapper for an object that's scheduled to be "
2951 : : "unrooted: %s",
2952 : : format_name().c_str());
2953 : : }
2954 : :
2955 [ + + ]: 313 : if (!m_gobj_disposed)
2956 : 160 : g_object_weak_unref(m_ptr, wrapped_gobj_dispose_notify, this);
2957 : :
2958 [ + + ]: 313 : if (!m_gobj_finalized)
2959 : 296 : unset_object_qdata();
2960 : :
2961 : 313 : bool was_using_toggle_refs = m_uses_toggle_ref;
2962 : 313 : release_native_object();
2963 : :
2964 [ + + ]: 313 : if (was_using_toggle_refs) {
2965 : : // We need to cancel again, to be sure that no other thread added
2966 : : // another toggle reference before we were removing the last one.
2967 : 159 : ToggleQueue::get_default()->cancel(this);
2968 : : }
2969 : : }
2970 : :
2971 [ - + ]: 1714 : if (wrapper_is_rooted()) {
2972 : : /* This happens when the refcount on the object is still >1,
2973 : : * for example with global objects GDK never frees like GdkDisplay,
2974 : : * when we close down the JS runtime.
2975 : : */
2976 : 0 : gjs_debug(GJS_DEBUG_GOBJECT,
2977 : : "Wrapper was finalized despite being kept alive, has refcount >1");
2978 : :
2979 : 0 : debug_lifecycle("Unrooting object");
2980 : :
2981 : 0 : discard_wrapper();
2982 : : }
2983 : 1714 : unlink();
2984 : :
2985 : 1714 : GJS_DEC_COUNTER(object_instance);
2986 : 1714 : }
2987 : :
2988 : 732 : ObjectPrototype::~ObjectPrototype() {
2989 : 732 : invalidate_closure_collection(&m_vfuncs, this, &vfunc_invalidated_notify);
2990 : :
2991 : 732 : g_type_class_unref(g_type_class_peek(m_gtype));
2992 : :
2993 : 732 : GJS_DEC_COUNTER(object_prototype);
2994 : 732 : }
2995 : :
2996 : 3589 : static JSObject* gjs_lookup_object_constructor_from_info(JSContext* context,
2997 : : GIBaseInfo* info,
2998 : : GType gtype) {
2999 : 3589 : g_return_val_if_fail(
3000 : : !info || GI_IS_OBJECT_INFO(info) || GI_IS_INTERFACE_INFO(info), NULL);
3001 : :
3002 : 3589 : JS::RootedObject in_object(context);
3003 : : const char *constructor_name;
3004 : :
3005 [ + + ]: 3589 : if (info) {
3006 : 3406 : in_object = gjs_lookup_namespace_object(context, info);
3007 : 3406 : constructor_name = g_base_info_get_name(info);
3008 : : } else {
3009 : 183 : in_object = gjs_lookup_private_namespace(context);
3010 : 183 : constructor_name = g_type_name(gtype);
3011 : : }
3012 : :
3013 [ - + ]: 3589 : if (G_UNLIKELY (!in_object))
3014 : 0 : return NULL;
3015 : :
3016 : : bool found;
3017 [ - + ]: 3589 : if (!JS_HasProperty(context, in_object, constructor_name, &found))
3018 : 0 : return NULL;
3019 : :
3020 : 3589 : JS::RootedValue value(context);
3021 [ + + - + : 3589 : if (found && !JS_GetProperty(context, in_object, constructor_name, &value))
- + ]
3022 : 0 : return NULL;
3023 : :
3024 : 3589 : JS::RootedObject constructor(context);
3025 [ + + ]: 3589 : if (value.isUndefined()) {
3026 : : /* In case we're looking for a private type, and we don't find it,
3027 : : we need to define it first.
3028 : : */
3029 : 62 : JS::RootedObject ignored(context);
3030 [ - + ]: 62 : if (!ObjectPrototype::define_class(context, in_object, nullptr, gtype,
3031 : : nullptr, 0, &constructor, &ignored))
3032 : 0 : return nullptr;
3033 [ + - ]: 62 : } else {
3034 [ - + ]: 3527 : if (G_UNLIKELY (!value.isObject()))
3035 : 0 : return NULL;
3036 : :
3037 : 3527 : constructor = &value.toObject();
3038 : : }
3039 : :
3040 : 3589 : g_assert(constructor);
3041 : :
3042 : 3589 : return constructor;
3043 : 3589 : }
3044 : :
3045 : : GJS_JSAPI_RETURN_CONVENTION
3046 : 3016 : static JSObject* gjs_lookup_object_prototype_from_info(JSContext* context,
3047 : : GIBaseInfo* info,
3048 : : GType gtype) {
3049 : 3016 : g_return_val_if_fail(
3050 : : !info || GI_IS_OBJECT_INFO(info) || GI_IS_INTERFACE_INFO(info), NULL);
3051 : :
3052 : : JS::RootedObject constructor(context,
3053 : 3016 : gjs_lookup_object_constructor_from_info(context, info, gtype));
3054 : :
3055 [ - + ]: 3016 : if (G_UNLIKELY(!constructor))
3056 : 0 : return NULL;
3057 : :
3058 : 3016 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
3059 : 3016 : JS::RootedObject prototype(context);
3060 [ - + ]: 3016 : if (!gjs_object_require_property(context, constructor, "constructor object",
3061 : : atoms.prototype(), &prototype))
3062 : 0 : return NULL;
3063 : :
3064 : 3016 : return prototype;
3065 : 3016 : }
3066 : :
3067 : : GJS_JSAPI_RETURN_CONVENTION
3068 : : static JSObject *
3069 : 1342 : gjs_lookup_object_prototype(JSContext *context,
3070 : : GType gtype)
3071 : : {
3072 : 1342 : GI::AutoObjectInfo info{gjs_lookup_gtype(nullptr, gtype)};
3073 : 1342 : return gjs_lookup_object_prototype_from_info(context, info, gtype);
3074 : 1342 : }
3075 : :
3076 : 367 : bool ObjectInstance::associate_closure(JSContext* cx, GClosure* closure) {
3077 [ + - ]: 367 : if (!is_prototype())
3078 : 367 : to_instance()->ensure_uses_toggle_ref(cx);
3079 : :
3080 : 367 : g_assert(std::find(m_closures.begin(), m_closures.end(), closure) ==
3081 : : m_closures.end() &&
3082 : : "This closure was already associated with this object");
3083 : :
3084 : : /* This is a weak reference, and will be cleared when the closure is
3085 : : * invalidated */
3086 : 367 : m_closures.push_back(closure);
3087 : 367 : g_closure_add_invalidate_notifier(
3088 : : closure, this, &ObjectInstance::closure_invalidated_notify);
3089 : :
3090 : 367 : return true;
3091 : : }
3092 : :
3093 : 150 : void ObjectInstance::closure_invalidated_notify(void* data, GClosure* closure) {
3094 : : // This callback should *only* touch m_closures
3095 : 150 : auto* priv = static_cast<ObjectInstance*>(data);
3096 : 150 : Gjs::remove_one_from_unsorted_vector(&priv->m_closures, closure);
3097 : 150 : }
3098 : :
3099 : 3098 : void ObjectInstance::invalidate_closures() {
3100 : 3098 : invalidate_closure_collection(&m_closures, this,
3101 : : &closure_invalidated_notify);
3102 : 3098 : m_closures.shrink_to_fit();
3103 : 3098 : }
3104 : :
3105 : 325 : bool ObjectBase::connect(JSContext* cx, unsigned argc, JS::Value* vp) {
3106 [ - + - + ]: 325 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
3107 [ - + ]: 325 : if (!priv->check_is_instance(cx, "connect to signals"))
3108 : 0 : return false;
3109 : :
3110 : 325 : return priv->to_instance()->connect_impl(cx, args, false);
3111 : 325 : }
3112 : :
3113 : 2 : bool ObjectBase::connect_after(JSContext* cx, unsigned argc, JS::Value* vp) {
3114 [ - + - + ]: 2 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
3115 [ - + ]: 2 : if (!priv->check_is_instance(cx, "connect to signals"))
3116 : 0 : return false;
3117 : :
3118 : 2 : return priv->to_instance()->connect_impl(cx, args, true);
3119 : 2 : }
3120 : :
3121 : 1 : bool ObjectBase::connect_object(JSContext* cx, unsigned argc, JS::Value* vp) {
3122 [ - + - + ]: 1 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
3123 [ - + ]: 1 : if (!priv->check_is_instance(cx, "connect to signals"))
3124 : 0 : return false;
3125 : :
3126 : 1 : return priv->to_instance()->connect_impl(cx, args, false, true);
3127 : 1 : }
3128 : :
3129 : 328 : bool ObjectInstance::connect_impl(JSContext* context, const JS::CallArgs& args,
3130 : : bool after, bool object) {
3131 : : gulong id;
3132 : : guint signal_id;
3133 : : GQuark signal_detail;
3134 [ + + ]: 655 : const char* func_name = object ? "connect_object"
3135 [ + + ]: 327 : : after ? "connect_after"
3136 : : : "connect";
3137 : :
3138 : : gjs_debug_gsignal("connect obj %p priv %p", m_wrapper.get(), this);
3139 : :
3140 [ + + ]: 328 : if (!check_gobject_disposed_or_finalized("connect to any signal on")) {
3141 : 4 : args.rval().setInt32(0);
3142 : 4 : return true;
3143 : : }
3144 : :
3145 : 324 : JS::UniqueChars signal_name;
3146 : 324 : JS::RootedObject callback(context);
3147 : 324 : JS::RootedObject associate_obj(context);
3148 : : GConnectFlags flags;
3149 [ + + ]: 324 : if (object) {
3150 [ - + ]: 1 : if (!gjs_parse_call_args(context, func_name, args, "sooi",
3151 : : "signal name", &signal_name, "callback",
3152 : : &callback, "gobject", &associate_obj,
3153 : : "connect_flags", &flags))
3154 : 0 : return false;
3155 : :
3156 [ - + ]: 1 : if (flags & G_CONNECT_SWAPPED) {
3157 : 0 : gjs_throw(context, "Unsupported connect flag G_CONNECT_SWAPPED");
3158 : 0 : return false;
3159 : : }
3160 : :
3161 : 1 : after = flags & G_CONNECT_AFTER;
3162 : : } else {
3163 [ - + ]: 323 : if (!gjs_parse_call_args(context, func_name, args, "so", "signal name",
3164 : : &signal_name, "callback", &callback))
3165 : 0 : return false;
3166 : : }
3167 : :
3168 [ + - - + : 972 : std::string dynamic_string{GJS_PROFILER_DYNAMIC_STRING(
- + - + -
+ - + ]
3169 : : context,
3170 [ - + ]: 648 : format_name() + '.' + func_name + "('" + signal_name.get() + "')")};
3171 : 324 : AutoProfilerLabel label{context, "", dynamic_string};
3172 : :
3173 [ - + ]: 324 : if (!JS::IsCallable(callback)) {
3174 : 0 : gjs_throw(context, "second arg must be a callback");
3175 : 0 : return false;
3176 : : }
3177 : :
3178 [ + + ]: 324 : if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id,
3179 : : &signal_detail, true)) {
3180 : 1 : gjs_throw(context, "No signal '%s' on object '%s'",
3181 : : signal_name.get(), type_name());
3182 : 1 : return false;
3183 : : }
3184 : :
3185 : 323 : GClosure* closure = Gjs::Closure::create_for_signal(
3186 : : context, callback, "signal callback", signal_id);
3187 [ - + ]: 323 : if (closure == NULL)
3188 : 0 : return false;
3189 : :
3190 [ + + ]: 323 : if (associate_obj.get() != nullptr) {
3191 : 1 : ObjectInstance* obj = ObjectInstance::for_js(context, associate_obj);
3192 [ - + ]: 1 : if (!obj)
3193 : 0 : return false;
3194 : :
3195 [ - + ]: 1 : if (!obj->associate_closure(context, closure))
3196 : 0 : return false;
3197 [ - + ]: 322 : } else if (!associate_closure(context, closure)) {
3198 : 0 : return false;
3199 : : }
3200 : :
3201 : 323 : id = g_signal_connect_closure_by_id(m_ptr, signal_id, signal_detail,
3202 : : closure, after);
3203 : :
3204 : 323 : args.rval().setDouble(id);
3205 : :
3206 : 323 : return true;
3207 : 324 : }
3208 : :
3209 : 177 : bool ObjectBase::emit(JSContext* cx, unsigned argc, JS::Value* vp) {
3210 [ - + - + ]: 177 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
3211 [ - + ]: 177 : if (!priv->check_is_instance(cx, "emit signal"))
3212 : 0 : return false;
3213 : :
3214 : 177 : return priv->to_instance()->emit_impl(cx, args);
3215 : 176 : }
3216 : :
3217 : : bool
3218 : 177 : ObjectInstance::emit_impl(JSContext *context,
3219 : : const JS::CallArgs& argv)
3220 : : {
3221 : : guint signal_id;
3222 : : GQuark signal_detail;
3223 : : GSignalQuery signal_query;
3224 : : unsigned int i;
3225 : :
3226 : : gjs_debug_gsignal("emit obj %p priv %p argc %d", m_wrapper.get(), this,
3227 : : argv.length());
3228 : :
3229 [ + + ]: 177 : if (!check_gobject_finalized("emit any signal on")) {
3230 : 1 : argv.rval().setUndefined();
3231 : 1 : return true;
3232 : : }
3233 : :
3234 : 176 : JS::UniqueChars signal_name;
3235 [ - + ]: 176 : if (!gjs_parse_call_args(context, "emit", argv, "!s",
3236 : : "signal name", &signal_name))
3237 : 0 : return false;
3238 : :
3239 [ + - - + : 528 : std::string full_name{GJS_PROFILER_DYNAMIC_STRING(
- + - + ]
3240 [ - + ]: 352 : context, format_name() + " emit('" + signal_name.get() + "')")};
3241 : 176 : AutoProfilerLabel label{context, "", full_name};
3242 : :
3243 [ + + ]: 176 : if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id,
3244 : : &signal_detail, false)) {
3245 : 1 : gjs_throw(context, "No signal '%s' on object '%s'",
3246 : : signal_name.get(), type_name());
3247 : 1 : return false;
3248 : : }
3249 : :
3250 : 175 : g_signal_query(signal_id, &signal_query);
3251 : :
3252 [ - + ]: 175 : if ((argv.length() - 1) != signal_query.n_params) {
3253 : 0 : gjs_throw(context, "Signal '%s' on %s requires %d args got %d",
3254 : : signal_name.get(), type_name(), signal_query.n_params,
3255 : 0 : argv.length() - 1);
3256 : 0 : return false;
3257 : : }
3258 : :
3259 : 175 : AutoGValueVector instance_and_args;
3260 : 175 : instance_and_args.reserve(signal_query.n_params + 1);
3261 : 175 : std::vector<Gjs::AutoGValue*> args_to_steal;
3262 : 175 : Gjs::AutoGValue& instance = instance_and_args.emplace_back(gtype());
3263 : 175 : g_value_set_instance(&instance, m_ptr);
3264 : :
3265 [ + + ]: 241 : for (i = 0; i < signal_query.n_params; ++i) {
3266 : 78 : GType gtype = signal_query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
3267 : 78 : Gjs::AutoGValue& value = instance_and_args.emplace_back(gtype);
3268 [ + + ]: 78 : if ((signal_query.param_types[i] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0) {
3269 [ - + ]: 3 : if (!gjs_value_to_g_value_no_copy(context, argv[i + 1], &value))
3270 : 12 : return false;
3271 : : } else {
3272 [ + + ]: 75 : if (!gjs_value_to_g_value(context, argv[i + 1], &value))
3273 : 12 : return false;
3274 : : }
3275 : :
3276 [ + + ]: 66 : if (!ObjectBase::info())
3277 : 55 : continue;
3278 : :
3279 : : GI::AutoSignalInfo signal_info{g_object_info_find_signal(
3280 : 11 : ObjectBase::info(), signal_query.signal_name)};
3281 [ - + ]: 11 : if (!signal_info)
3282 : 0 : continue;
3283 : :
3284 : 11 : GI::AutoArgInfo arg_info{g_callable_info_get_arg(signal_info, i)};
3285 [ + + ]: 11 : if (g_arg_info_get_ownership_transfer(arg_info) !=
3286 : : GI_TRANSFER_NOTHING) {
3287 : : // FIXME(3v1n0): As it happens in many places in gjs, we can't track
3288 : : // (yet) containers content, so in case of transfer container we
3289 : : // can only leak.
3290 : 3 : args_to_steal.push_back(&value);
3291 : : }
3292 [ + - ]: 11 : }
3293 : :
3294 [ + + ]: 163 : if (signal_query.return_type == G_TYPE_NONE) {
3295 : 134 : g_signal_emitv(instance_and_args.data(), signal_id, signal_detail,
3296 : : nullptr);
3297 : 133 : argv.rval().setUndefined();
3298 : 133 : std::for_each(args_to_steal.begin(), args_to_steal.end(),
3299 : 3 : [](Gjs::AutoGValue* value) { value->steal(); });
3300 : 133 : return true;
3301 : : }
3302 : :
3303 : 29 : GType gtype = signal_query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE;
3304 : 29 : Gjs::AutoGValue rvalue(gtype);
3305 : 29 : g_signal_emitv(instance_and_args.data(), signal_id, signal_detail, &rvalue);
3306 : :
3307 : 29 : std::for_each(args_to_steal.begin(), args_to_steal.end(),
3308 : 0 : [](Gjs::AutoGValue* value) { value->steal(); });
3309 : :
3310 : 29 : return gjs_value_from_g_value(context, argv.rval(), &rvalue);
3311 : 175 : }
3312 : :
3313 : 27 : bool ObjectInstance::signal_match_arguments_from_object(
3314 : : JSContext* cx, JS::HandleObject match_obj, GSignalMatchType* mask_out,
3315 : : unsigned* signal_id_out, GQuark* detail_out,
3316 : : JS::MutableHandleObject callable_out) {
3317 : 27 : g_assert(mask_out && signal_id_out && detail_out && "forgot out parameter");
3318 : :
3319 : 27 : int mask = 0;
3320 : 27 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
3321 : :
3322 : : bool has_id;
3323 : 27 : unsigned signal_id = 0;
3324 [ - + ]: 27 : if (!JS_HasOwnPropertyById(cx, match_obj, atoms.signal_id(), &has_id))
3325 : 0 : return false;
3326 [ + + ]: 27 : if (has_id) {
3327 : 7 : mask |= G_SIGNAL_MATCH_ID;
3328 : :
3329 : 7 : JS::RootedValue value(cx);
3330 [ - + ]: 7 : if (!JS_GetPropertyById(cx, match_obj, atoms.signal_id(), &value))
3331 : 0 : return false;
3332 : :
3333 : 7 : JS::UniqueChars signal_name = gjs_string_to_utf8(cx, value);
3334 [ - + ]: 7 : if (!signal_name)
3335 : 0 : return false;
3336 : :
3337 : 7 : signal_id = g_signal_lookup(signal_name.get(), gtype());
3338 [ + - + - ]: 7 : }
3339 : :
3340 : : bool has_detail;
3341 : 27 : GQuark detail = 0;
3342 [ - + ]: 27 : if (!JS_HasOwnPropertyById(cx, match_obj, atoms.detail(), &has_detail))
3343 : 0 : return false;
3344 [ + + ]: 27 : if (has_detail) {
3345 : 3 : mask |= G_SIGNAL_MATCH_DETAIL;
3346 : :
3347 : 3 : JS::RootedValue value(cx);
3348 [ - + ]: 3 : if (!JS_GetPropertyById(cx, match_obj, atoms.detail(), &value))
3349 : 0 : return false;
3350 : :
3351 : 3 : JS::UniqueChars detail_string = gjs_string_to_utf8(cx, value);
3352 [ - + ]: 3 : if (!detail_string)
3353 : 0 : return false;
3354 : :
3355 : 3 : detail = g_quark_from_string(detail_string.get());
3356 [ + - + - ]: 3 : }
3357 : :
3358 : : bool has_func;
3359 : 27 : JS::RootedObject callable(cx);
3360 [ - + ]: 27 : if (!JS_HasOwnPropertyById(cx, match_obj, atoms.func(), &has_func))
3361 : 0 : return false;
3362 [ + + ]: 27 : if (has_func) {
3363 : 22 : mask |= G_SIGNAL_MATCH_CLOSURE;
3364 : :
3365 : 22 : JS::RootedValue value(cx);
3366 [ - + ]: 22 : if (!JS_GetPropertyById(cx, match_obj, atoms.func(), &value))
3367 : 0 : return false;
3368 : :
3369 [ + - - + : 22 : if (!value.isObject() || !JS::IsCallable(&value.toObject())) {
- + ]
3370 : 0 : gjs_throw(cx, "'func' property must be a function");
3371 : 0 : return false;
3372 : : }
3373 : :
3374 : 22 : callable = &value.toObject();
3375 [ + - ]: 22 : }
3376 : :
3377 [ + + + + : 27 : if (!has_id && !has_detail && !has_func) {
- + ]
3378 : 0 : gjs_throw(cx, "Must specify at least one of signalId, detail, or func");
3379 : 0 : return false;
3380 : : }
3381 : :
3382 : 27 : *mask_out = GSignalMatchType(mask);
3383 [ + + ]: 27 : if (has_id)
3384 : 7 : *signal_id_out = signal_id;
3385 [ + + ]: 27 : if (has_detail)
3386 : 3 : *detail_out = detail;
3387 [ + + ]: 27 : if (has_func)
3388 : 22 : callable_out.set(callable);
3389 : 27 : return true;
3390 : 27 : }
3391 : :
3392 : 12 : bool ObjectBase::signal_find(JSContext* cx, unsigned argc, JS::Value* vp) {
3393 [ - + - + ]: 12 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
3394 [ - + ]: 12 : if (!priv->check_is_instance(cx, "find signal"))
3395 : 0 : return false;
3396 : :
3397 : 12 : return priv->to_instance()->signal_find_impl(cx, args);
3398 : 12 : }
3399 : :
3400 : 12 : bool ObjectInstance::signal_find_impl(JSContext* cx, const JS::CallArgs& args) {
3401 : : gjs_debug_gsignal("[Gi.signal_find_symbol]() obj %p priv %p argc %d",
3402 : : m_wrapper.get(), this, args.length());
3403 : :
3404 [ - + ]: 12 : if (!check_gobject_finalized("find any signal on")) {
3405 : 0 : args.rval().setInt32(0);
3406 : 0 : return true;
3407 : : }
3408 : :
3409 : 12 : JS::RootedObject match(cx);
3410 [ - + ]: 12 : if (!gjs_parse_call_args(cx, "[Gi.signal_find_symbol]", args, "o", "match",
3411 : : &match))
3412 : 0 : return false;
3413 : :
3414 : : GSignalMatchType mask;
3415 : : unsigned signal_id;
3416 : : GQuark detail;
3417 : 12 : JS::RootedObject callable(cx);
3418 [ - + ]: 12 : if (!signal_match_arguments_from_object(cx, match, &mask, &signal_id,
3419 : : &detail, &callable))
3420 : 0 : return false;
3421 : :
3422 : 12 : uint64_t handler = 0;
3423 [ + + ]: 12 : if (!callable) {
3424 : 5 : handler = g_signal_handler_find(m_ptr, mask, signal_id, detail, nullptr,
3425 : : nullptr, nullptr);
3426 : : } else {
3427 [ + - ]: 23 : for (GClosure* candidate : m_closures) {
3428 [ + + ]: 23 : if (Gjs::Closure::for_gclosure(candidate)->callable() == callable) {
3429 : 7 : handler = g_signal_handler_find(m_ptr, mask, signal_id, detail,
3430 : : candidate, nullptr, nullptr);
3431 [ + - ]: 7 : if (handler != 0)
3432 : 7 : break;
3433 : : }
3434 : : }
3435 : : }
3436 : :
3437 : 12 : args.rval().setNumber(static_cast<double>(handler));
3438 : 12 : return true;
3439 : 12 : }
3440 : :
3441 : : template <ObjectBase::SignalMatchFunc(*MatchFunc)>
3442 : : static inline const char* signal_match_to_action_name();
3443 : :
3444 : : template <>
3445 : : inline const char*
3446 : 12 : signal_match_to_action_name<&g_signal_handlers_block_matched>() {
3447 : 12 : return "block";
3448 : : }
3449 : :
3450 : : template <>
3451 : : inline const char*
3452 : 12 : signal_match_to_action_name<&g_signal_handlers_unblock_matched>() {
3453 : 12 : return "unblock";
3454 : : }
3455 : :
3456 : : template <>
3457 : : inline const char*
3458 : 12 : signal_match_to_action_name<&g_signal_handlers_disconnect_matched>() {
3459 : 12 : return "disconnect";
3460 : : }
3461 : :
3462 : : template <ObjectBase::SignalMatchFunc(*MatchFunc)>
3463 : 18 : bool ObjectBase::signals_action(JSContext* cx, unsigned argc, JS::Value* vp) {
3464 [ - + - + ]: 18 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
3465 : 18 : const std::string action_name = signal_match_to_action_name<MatchFunc>();
3466 [ - + ]: 18 : if (!priv->check_is_instance(cx, (action_name + " signal").c_str()))
3467 : 0 : return false;
3468 : :
3469 : 18 : return priv->to_instance()->signals_action_impl<MatchFunc>(cx, args);
3470 : 18 : }
3471 : :
3472 : : template <ObjectBase::SignalMatchFunc(*MatchFunc)>
3473 : 18 : bool ObjectInstance::signals_action_impl(JSContext* cx,
3474 : : const JS::CallArgs& args) {
3475 : 18 : const std::string action_name = signal_match_to_action_name<MatchFunc>();
3476 : 18 : const std::string action_tag = "[Gi.signals_" + action_name + "_symbol]";
3477 : : gjs_debug_gsignal("[%s]() obj %p priv %p argc %d", action_tag.c_str(),
3478 : : m_wrapper.get(), this, args.length());
3479 : :
3480 [ + + ]: 18 : if (!check_gobject_finalized((action_name + " any signal on").c_str())) {
3481 : 3 : args.rval().setInt32(0);
3482 : 3 : return true;
3483 : : }
3484 : 15 : JS::RootedObject match(cx);
3485 [ - + ]: 15 : if (!gjs_parse_call_args(cx, action_tag.c_str(), args, "o", "match",
3486 : : &match)) {
3487 : 0 : return false;
3488 : : }
3489 : : GSignalMatchType mask;
3490 : : unsigned signal_id;
3491 : : GQuark detail;
3492 : 15 : JS::RootedObject callable(cx);
3493 [ - + ]: 15 : if (!signal_match_arguments_from_object(cx, match, &mask, &signal_id,
3494 : : &detail, &callable)) {
3495 : 0 : return false;
3496 : : }
3497 : 15 : unsigned n_matched = 0;
3498 [ - + ]: 15 : if (!callable) {
3499 : 0 : n_matched = MatchFunc(m_ptr, mask, signal_id, detail, nullptr, nullptr,
3500 : : nullptr);
3501 : : } else {
3502 : 15 : std::vector<GClosure*> candidates;
3503 [ + + ]: 99 : for (GClosure* candidate : m_closures) {
3504 [ + + ]: 84 : if (Gjs::Closure::for_gclosure(candidate)->callable() == callable)
3505 : 15 : candidates.push_back(candidate);
3506 : : }
3507 [ + + ]: 30 : for (GClosure* candidate : candidates) {
3508 : 15 : n_matched += MatchFunc(m_ptr, mask, signal_id, detail, candidate,
3509 : : nullptr, nullptr);
3510 : : }
3511 : 15 : }
3512 : :
3513 : 15 : args.rval().setNumber(n_matched);
3514 : 15 : return true;
3515 : 18 : }
3516 : :
3517 : 238 : bool ObjectBase::to_string(JSContext* cx, unsigned argc, JS::Value* vp) {
3518 [ - + - + ]: 238 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
3519 : 238 : const char* kind = ObjectBase::DEBUG_TAG;
3520 [ + + ]: 238 : if (!priv->is_prototype())
3521 : 236 : kind = priv->to_instance()->to_string_kind();
3522 [ + + ]: 712 : return gjs_wrapper_to_string_func(
3523 : : cx, obj, kind, priv->info(), priv->gtype(),
3524 : 474 : priv->is_prototype() ? nullptr : priv->to_instance()->ptr(),
3525 : 238 : args.rval());
3526 : 238 : }
3527 : :
3528 : : /*
3529 : : * ObjectInstance::to_string_kind:
3530 : : *
3531 : : * ObjectInstance shows a "disposed" marker in its toString() method if the
3532 : : * wrapped GObject has already been disposed.
3533 : : */
3534 : 236 : const char* ObjectInstance::to_string_kind(void) const {
3535 [ + + ]: 236 : if (m_gobj_finalized)
3536 : 3 : return "object (FINALIZED)";
3537 [ + + ]: 233 : return m_gobj_disposed ? "object (DISPOSED)" : "object";
3538 : : }
3539 : :
3540 : : /*
3541 : : * ObjectBase::init_gobject:
3542 : : *
3543 : : * This is named "init_gobject()" but corresponds to "_init()" in JS. The reason
3544 : : * for the name is that an "init()" method is used within SpiderMonkey to
3545 : : * indicate fallible initialization that must be done before an object can be
3546 : : * used, which is not the case here.
3547 : : */
3548 : 1027 : bool ObjectBase::init_gobject(JSContext* context, unsigned argc,
3549 : : JS::Value* vp) {
3550 [ - + - + ]: 1027 : GJS_CHECK_WRAPPER_PRIV(context, argc, vp, argv, obj, ObjectBase, priv);
3551 [ - + ]: 1027 : if (!priv->check_is_instance(context, "initialize"))
3552 : 0 : return false;
3553 : :
3554 : : std::string full_name{
3555 [ - + + - : 3081 : GJS_PROFILER_DYNAMIC_STRING(context, priv->format_name() + "._init")};
- + ]
3556 : 1027 : AutoProfilerLabel label{context, "", full_name};
3557 : :
3558 : 1027 : return priv->to_instance()->init_impl(context, argv, obj);
3559 : 1027 : }
3560 : :
3561 : : // clang-format off
3562 : : const struct JSClassOps ObjectBase::class_ops = {
3563 : : &ObjectBase::add_property,
3564 : : nullptr, // deleteProperty
3565 : : nullptr, // enumerate
3566 : : &ObjectBase::new_enumerate,
3567 : : &ObjectBase::resolve,
3568 : : nullptr, // mayResolve
3569 : : &ObjectBase::finalize,
3570 : : NULL,
3571 : : NULL,
3572 : : &ObjectBase::trace,
3573 : : };
3574 : :
3575 : : const struct JSClass ObjectBase::klass = {
3576 : : "GObject_Object",
3577 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
3578 : : &ObjectBase::class_ops
3579 : : };
3580 : :
3581 : : JSFunctionSpec ObjectBase::proto_methods[] = {
3582 : : JS_FN("_init", &ObjectBase::init_gobject, 0, 0),
3583 : : JS_FN("connect", &ObjectBase::connect, 0, 0),
3584 : : JS_FN("connect_after", &ObjectBase::connect_after, 0, 0),
3585 : : JS_FN("connect_object", &ObjectBase::connect_object, 0, 0),
3586 : : JS_FN("emit", &ObjectBase::emit, 0, 0),
3587 : : JS_FS_END
3588 : : };
3589 : :
3590 : : JSPropertySpec ObjectBase::proto_properties[] = {
3591 : : JS_STRING_SYM_PS(toStringTag, "GObject_Object", JSPROP_READONLY),
3592 : : JS_PS_END};
3593 : : // clang-format on
3594 : :
3595 : : // Override of GIWrapperPrototype::get_parent_proto()
3596 : 735 : bool ObjectPrototype::get_parent_proto(JSContext* cx,
3597 : : JS::MutableHandleObject proto) const {
3598 : 735 : GType parent_type = g_type_parent(gtype());
3599 [ + + ]: 735 : if (parent_type == G_TYPE_INVALID) {
3600 : 77 : proto.set(nullptr);
3601 : 77 : return true;
3602 : : }
3603 : :
3604 : 658 : JSObject* prototype = gjs_lookup_object_prototype(cx, parent_type);
3605 [ - + ]: 658 : if (!prototype)
3606 : 0 : return false;
3607 : :
3608 : 658 : proto.set(prototype);
3609 : 658 : return true;
3610 : : }
3611 : :
3612 : 635 : bool ObjectPrototype::get_parent_constructor(
3613 : : JSContext* cx, JS::MutableHandleObject constructor) const {
3614 : 635 : GType parent_type = g_type_parent(gtype());
3615 : :
3616 [ + + ]: 635 : if (parent_type == G_TYPE_INVALID) {
3617 : 77 : constructor.set(nullptr);
3618 : 77 : return true;
3619 : : }
3620 : :
3621 : 558 : JS::RootedValue v_constructor(cx);
3622 [ - + ]: 558 : if (!gjs_lookup_object_constructor(cx, parent_type, &v_constructor))
3623 : 0 : return false;
3624 : :
3625 : 558 : g_assert(v_constructor.isObject() &&
3626 : : "gjs_lookup_object_constructor() should always produce an object");
3627 : 558 : constructor.set(&v_constructor.toObject());
3628 : 558 : return true;
3629 : 558 : }
3630 : :
3631 : 735 : void ObjectPrototype::set_interfaces(GType* interface_gtypes,
3632 : : uint32_t n_interface_gtypes) {
3633 [ + + ]: 735 : if (interface_gtypes) {
3634 [ + + ]: 82 : for (uint32_t n = 0; n < n_interface_gtypes; n++) {
3635 : 44 : m_interface_gtypes.push_back(interface_gtypes[n]);
3636 : : }
3637 : : }
3638 : 735 : }
3639 : :
3640 : : /*
3641 : : * ObjectPrototype::define_class:
3642 : : * @in_object: Object where the constructor is stored, typically a repo object.
3643 : : * @info: Introspection info for the GObject class.
3644 : : * @gtype: #GType for the GObject class.
3645 : : * @constructor: Return location for the constructor object.
3646 : : * @prototype: Return location for the prototype object.
3647 : : *
3648 : : * Define a GObject class constructor and prototype, including all the
3649 : : * necessary methods and properties that are not introspected. Provides the
3650 : : * constructor and prototype objects as out parameters, for convenience
3651 : : * elsewhere.
3652 : : */
3653 : 635 : bool ObjectPrototype::define_class(
3654 : : JSContext* context, JS::HandleObject in_object, GIObjectInfo* info,
3655 : : GType gtype, GType* interface_gtypes, uint32_t n_interface_gtypes,
3656 : : JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) {
3657 : 635 : ObjectPrototype* priv = ObjectPrototype::create_class(
3658 : : context, in_object, info, gtype, constructor, prototype);
3659 [ - + ]: 635 : if (!priv)
3660 : 0 : return false;
3661 : :
3662 : 635 : priv->set_interfaces(interface_gtypes, n_interface_gtypes);
3663 : :
3664 : 635 : JS::RootedObject parent_constructor(context);
3665 [ - + ]: 635 : if (!priv->get_parent_constructor(context, &parent_constructor))
3666 : 0 : return false;
3667 : : // If this is a fundamental constructor (e.g. GObject.Object) the
3668 : : // parent constructor may be null.
3669 [ + + ]: 635 : if (parent_constructor) {
3670 [ - + ]: 558 : if (!JS_SetPrototype(context, constructor, parent_constructor))
3671 : 0 : return false;
3672 : : }
3673 : :
3674 : : // hook_up_vfunc and the signal handler matcher functions can't be included
3675 : : // in gjs_object_instance_proto_funcs because they are custom symbols.
3676 : 635 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
3677 : 635 : return JS_DefineFunctionById(context, prototype, atoms.hook_up_vfunc(),
3678 : : &ObjectBase::hook_up_vfunc, 3,
3679 [ + - ]: 635 : GJS_MODULE_PROP_FLAGS) &&
3680 : 635 : JS_DefineFunctionById(context, prototype, atoms.signal_find(),
3681 : : &ObjectBase::signal_find, 1,
3682 [ + - ]: 635 : GJS_MODULE_PROP_FLAGS) &&
3683 : 635 : JS_DefineFunctionById(
3684 : : context, prototype, atoms.signals_block(),
3685 : 5 : &ObjectBase::signals_action<&g_signal_handlers_block_matched>, 1,
3686 [ + - ]: 635 : GJS_MODULE_PROP_FLAGS) &&
3687 : 635 : JS_DefineFunctionById(
3688 : : context, prototype, atoms.signals_unblock(),
3689 : 5 : &ObjectBase::signals_action<&g_signal_handlers_unblock_matched>,
3690 [ + - + - ]: 1270 : 1, GJS_MODULE_PROP_FLAGS) &&
3691 : 635 : JS_DefineFunctionById(context, prototype, atoms.signals_disconnect(),
3692 : : &ObjectBase::signals_action<
3693 : 5 : &g_signal_handlers_disconnect_matched>,
3694 : 635 : 1, GJS_MODULE_PROP_FLAGS);
3695 : 635 : }
3696 : :
3697 : : /*
3698 : : * ObjectInstance::init_custom_class_from_gobject:
3699 : : *
3700 : : * Does all the necessary initialization for an ObjectInstance and JSObject
3701 : : * wrapper, given a newly-created GObject pointer, of a GObject class that was
3702 : : * created in JS with GObject.registerClass(). This is called from the GObject's
3703 : : * instance init function in gobject.cpp, and that's the only reason it's a
3704 : : * public method.
3705 : : */
3706 : 491 : bool ObjectInstance::init_custom_class_from_gobject(JSContext* cx,
3707 : : JS::HandleObject wrapper,
3708 : : GObject* gobj) {
3709 : 491 : associate_js_gobject(cx, wrapper, gobj);
3710 : :
3711 : : // Custom JS objects will most likely have visible state, so just do this
3712 : : // from the start.
3713 : 491 : ensure_uses_toggle_ref(cx);
3714 [ - + ]: 491 : if (!m_uses_toggle_ref) {
3715 : 0 : gjs_throw(cx, "Impossible to set toggle references on %sobject %p",
3716 [ # # ]: 0 : m_gobj_disposed ? "disposed " : "", gobj);
3717 : 0 : return false;
3718 : : }
3719 : :
3720 : 491 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
3721 : 491 : JS::RootedValue v(cx);
3722 [ - + ]: 491 : if (!JS_GetPropertyById(cx, wrapper, atoms.instance_init(), &v))
3723 : 0 : return false;
3724 : :
3725 [ + + ]: 491 : if (v.isUndefined())
3726 : 444 : return true;
3727 [ + - - + : 47 : if (!v.isObject() || !JS::IsCallable(&v.toObject())) {
- + ]
3728 : 0 : gjs_throw(cx, "_instance_init property was not a function");
3729 : 0 : return false;
3730 : : }
3731 : :
3732 : 47 : JS::RootedValue ignored_rval(cx);
3733 : 47 : return JS_CallFunctionValue(cx, wrapper, v, JS::HandleValueArray::empty(),
3734 : 47 : &ignored_rval);
3735 : 491 : }
3736 : :
3737 : : /*
3738 : : * ObjectInstance::new_for_gobject:
3739 : : *
3740 : : * Creates a new JSObject wrapper for the GObject pointer @gobj, and an
3741 : : * ObjectInstance private structure to go along with it.
3742 : : */
3743 : 684 : ObjectInstance* ObjectInstance::new_for_gobject(JSContext* cx, GObject* gobj) {
3744 : 684 : g_assert(gobj && "Cannot create JSObject for null GObject pointer");
3745 : :
3746 : 684 : GType gtype = G_TYPE_FROM_INSTANCE(gobj);
3747 : :
3748 : : gjs_debug_marshal(GJS_DEBUG_GOBJECT, "Wrapping %s %p with JSObject",
3749 : : g_type_name(gtype), gobj);
3750 : :
3751 : 684 : JS::RootedObject proto(cx, gjs_lookup_object_prototype(cx, gtype));
3752 [ - + ]: 684 : if (!proto)
3753 : 0 : return nullptr;
3754 : :
3755 : : JS::RootedObject obj(
3756 : 684 : cx, JS_NewObjectWithGivenProto(cx, &ObjectBase::klass, proto));
3757 [ - + ]: 684 : if (!obj)
3758 : 0 : return nullptr;
3759 : :
3760 : 684 : ObjectPrototype* prototype = resolve_prototype(cx, proto);
3761 [ - + ]: 684 : if (!prototype)
3762 : 0 : return nullptr;
3763 : :
3764 : 684 : ObjectInstance* priv = new ObjectInstance(prototype, obj);
3765 : :
3766 : 684 : ObjectBase::init_private(obj, priv);
3767 : :
3768 : 684 : g_object_ref_sink(gobj);
3769 : 684 : priv->associate_js_gobject(cx, obj, gobj);
3770 : :
3771 : 684 : g_assert(priv->wrapper() == obj.get());
3772 : :
3773 : 684 : return priv;
3774 : 684 : }
3775 : :
3776 : : /*
3777 : : * ObjectInstance::wrapper_from_gobject:
3778 : : *
3779 : : * Gets a JSObject wrapper for the GObject pointer @gobj. If one already exists,
3780 : : * then it is returned. Otherwise a new one is created with
3781 : : * ObjectInstance::new_for_gobject().
3782 : : */
3783 : 1932 : JSObject* ObjectInstance::wrapper_from_gobject(JSContext* cx, GObject* gobj) {
3784 : 1932 : g_assert(gobj && "Cannot get JSObject for null GObject pointer");
3785 : :
3786 : 1932 : ObjectInstance* priv = ObjectInstance::for_gobject(gobj);
3787 : :
3788 [ + + ]: 1932 : if (!priv) {
3789 : : /* We have to create a wrapper */
3790 : 665 : priv = new_for_gobject(cx, gobj);
3791 [ - + ]: 665 : if (!priv)
3792 : 0 : return nullptr;
3793 : : }
3794 : :
3795 : 1932 : return priv->wrapper();
3796 : : }
3797 : :
3798 : 1820 : bool ObjectInstance::set_value_from_gobject(JSContext* cx, GObject* gobj,
3799 : : JS::MutableHandleValue value_p) {
3800 [ - + ]: 1820 : if (!gobj) {
3801 : 0 : value_p.setNull();
3802 : 0 : return true;
3803 : : }
3804 : :
3805 : 1820 : auto* wrapper = ObjectInstance::wrapper_from_gobject(cx, gobj);
3806 [ + - ]: 1820 : if (wrapper) {
3807 : 1820 : value_p.setObject(*wrapper);
3808 : 1820 : return true;
3809 : : }
3810 : :
3811 : 0 : gjs_throw(cx, "Failed to find JS object for GObject %p of type %s", gobj,
3812 : 0 : g_type_name(G_TYPE_FROM_INSTANCE(gobj)));
3813 : 0 : return false;
3814 : : }
3815 : :
3816 : : // Replaces GIWrapperBase::to_c_ptr(). The GIWrapperBase version is deleted.
3817 : 2125 : bool ObjectBase::to_c_ptr(JSContext* cx, JS::HandleObject obj, GObject** ptr) {
3818 : 2125 : g_assert(ptr);
3819 : :
3820 : 2125 : auto* priv = ObjectBase::for_js(cx, obj);
3821 [ + + - + : 2125 : if (!priv || priv->is_prototype())
+ + ]
3822 : 1 : return false;
3823 : :
3824 : 2124 : ObjectInstance* instance = priv->to_instance();
3825 [ + + ]: 2124 : if (!instance->check_gobject_finalized("access")) {
3826 : 2 : *ptr = nullptr;
3827 : 2 : return true;
3828 : : }
3829 : :
3830 : 2122 : *ptr = instance->ptr();
3831 : 2122 : return true;
3832 : : }
3833 : :
3834 : : // Overrides GIWrapperBase::transfer_to_gi_argument().
3835 : 2071 : bool ObjectBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj,
3836 : : GIArgument* arg,
3837 : : GIDirection transfer_direction,
3838 : : GITransfer transfer_ownership,
3839 : : GType expected_gtype) {
3840 : 2071 : g_assert(transfer_direction != GI_DIRECTION_INOUT &&
3841 : : "transfer_to_gi_argument() must choose between in or out");
3842 : :
3843 [ + + ]: 2071 : if (!ObjectBase::typecheck(cx, obj, expected_gtype)) {
3844 : 5 : gjs_arg_unset(arg);
3845 : 5 : return false;
3846 : : }
3847 : :
3848 : : GObject* ptr;
3849 [ - + ]: 2066 : if (!ObjectBase::to_c_ptr(cx, obj, &ptr))
3850 : 0 : return false;
3851 : :
3852 : 2066 : gjs_arg_set(arg, ptr);
3853 : :
3854 : : // Pointer can be null if object was already disposed by C code
3855 [ + + ]: 2066 : if (!ptr)
3856 : 2 : return true;
3857 : :
3858 [ + - + + ]: 2064 : if ((transfer_direction == GI_DIRECTION_IN &&
3859 [ - + ]: 2006 : transfer_ownership != GI_TRANSFER_NOTHING) ||
3860 [ # # ]: 0 : (transfer_direction == GI_DIRECTION_OUT &&
3861 : : transfer_ownership == GI_TRANSFER_EVERYTHING)) {
3862 : 58 : gjs_arg_set(arg, ObjectInstance::copy_ptr(cx, expected_gtype,
3863 : : gjs_arg_get<void*>(arg)));
3864 [ - + ]: 58 : if (!gjs_arg_get<void*>(arg))
3865 : 0 : return false;
3866 : : }
3867 : :
3868 : 2064 : return true;
3869 : : }
3870 : :
3871 : : GJS_JSAPI_RETURN_CONVENTION
3872 : 66 : static bool find_vfunc_info(JSContext* context, GType implementor_gtype,
3873 : : GIBaseInfo* vfunc_info, const char* vfunc_name,
3874 : : void** implementor_vtable_ret,
3875 : : GI::AutoFieldInfo* field_info_ret) {
3876 : : GType ancestor_gtype;
3877 : : int length, i;
3878 : : GIBaseInfo *ancestor_info;
3879 : 66 : GI::AutoStructInfo struct_info;
3880 : :
3881 : 66 : field_info_ret->reset();
3882 : 66 : *implementor_vtable_ret = NULL;
3883 : :
3884 : 66 : ancestor_info = g_base_info_get_container(vfunc_info);
3885 : 66 : ancestor_gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)ancestor_info);
3886 : :
3887 : 66 : Gjs::AutoTypeClass<GTypeClass> implementor_class{implementor_gtype};
3888 [ + + ]: 66 : if (GI_IS_INTERFACE_INFO(ancestor_info)) {
3889 : : GTypeInstance *implementor_iface_class;
3890 : 12 : implementor_iface_class = (GTypeInstance*) g_type_interface_peek(implementor_class,
3891 : : ancestor_gtype);
3892 [ - + ]: 12 : if (implementor_iface_class == NULL) {
3893 : 0 : gjs_throw (context, "Couldn't find GType of implementor of interface %s.",
3894 : : g_type_name(ancestor_gtype));
3895 : 0 : return false;
3896 : : }
3897 : :
3898 : 12 : *implementor_vtable_ret = implementor_iface_class;
3899 : :
3900 : 12 : struct_info = g_interface_info_get_iface_struct((GIInterfaceInfo*)ancestor_info);
3901 : : } else {
3902 : 54 : struct_info = g_object_info_get_class_struct((GIObjectInfo*)ancestor_info);
3903 : 54 : *implementor_vtable_ret = implementor_class;
3904 : : }
3905 : :
3906 : 66 : length = g_struct_info_get_n_fields(struct_info);
3907 [ + - ]: 901 : for (i = 0; i < length; i++) {
3908 : 901 : GI::AutoFieldInfo field_info{g_struct_info_get_field(struct_info, i)};
3909 [ + + ]: 901 : if (strcmp(field_info.name(), vfunc_name) != 0)
3910 : 835 : continue;
3911 : :
3912 : 66 : GI::AutoTypeInfo type_info{g_field_info_get_type(field_info)};
3913 [ - + ]: 66 : if (g_type_info_get_tag(type_info) != GI_TYPE_TAG_INTERFACE) {
3914 : : /* We have a field with the same name, but it's not a callback.
3915 : : * There's no hope of being another field with a correct name,
3916 : : * so just abort early. */
3917 : 0 : return true;
3918 : : } else {
3919 : 66 : *field_info_ret = std::move(field_info);
3920 : 66 : return true;
3921 : : }
3922 [ + + ]: 967 : }
3923 : 0 : return true;
3924 : 66 : }
3925 : :
3926 : 72 : bool ObjectBase::hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp) {
3927 [ - + - + ]: 72 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, prototype, ObjectBase, priv);
3928 : : /* Normally we wouldn't assert is_prototype(), but this method can only be
3929 : : * called internally so it's OK to crash if done wrongly */
3930 : 72 : return priv->to_prototype()->hook_up_vfunc_impl(cx, args);
3931 : 72 : }
3932 : :
3933 : 72 : bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx,
3934 : : const JS::CallArgs& args) {
3935 : 72 : JS::UniqueChars name;
3936 : 72 : JS::RootedObject callable(cx);
3937 : 72 : bool is_static = false;
3938 [ - + ]: 72 : if (!gjs_parse_call_args(cx, "hook_up_vfunc", args, "so|b", "name", &name,
3939 : : "function", &callable, "is_static", &is_static))
3940 : 0 : return false;
3941 : :
3942 : 72 : args.rval().setUndefined();
3943 : :
3944 : : /* find the first class that actually has repository information */
3945 : 72 : GIObjectInfo *info = m_info;
3946 : 72 : GType info_gtype = m_gtype;
3947 [ + + + - ]: 151 : while (!info && info_gtype != G_TYPE_OBJECT) {
3948 : 79 : info_gtype = g_type_parent(info_gtype);
3949 : :
3950 : 79 : info = g_irepository_find_by_gtype(nullptr, info_gtype);
3951 : : }
3952 : :
3953 : : /* If we don't have 'info', we don't have the base class (GObject).
3954 : : * This is awful, so abort now. */
3955 : 72 : g_assert(info != NULL);
3956 : :
3957 : 72 : GI::AutoVFuncInfo vfunc{g_object_info_find_vfunc(info, name.get())};
3958 : : // Search the parent type chain
3959 [ + + + + : 84 : while (!vfunc && info_gtype != G_TYPE_OBJECT) {
+ + ]
3960 : 12 : info_gtype = g_type_parent(info_gtype);
3961 : :
3962 : 12 : info = g_irepository_find_by_gtype(nullptr, info_gtype);
3963 [ + - ]: 12 : if (info)
3964 : 12 : vfunc = g_object_info_find_vfunc(info, name.get());
3965 : : }
3966 : :
3967 : : // If the vfunc doesn't exist in the parent
3968 : : // type chain, loop through the explicitly
3969 : : // defined interfaces...
3970 [ + + ]: 72 : if (!vfunc) {
3971 [ + + ]: 20 : for (GType interface_gtype : m_interface_gtypes) {
3972 : : GI::AutoInterfaceInfo interface{
3973 : 15 : g_irepository_find_by_gtype(nullptr, interface_gtype)};
3974 : :
3975 : : // Private and dynamic interfaces (defined in JS) do not have type
3976 : : // info.
3977 [ + - ]: 15 : if (interface) {
3978 : 15 : vfunc = g_interface_info_find_vfunc(interface, name.get());
3979 : :
3980 [ + + ]: 15 : if (vfunc)
3981 : 13 : break;
3982 : : }
3983 [ + + ]: 15 : }
3984 : : }
3985 : :
3986 : : // If the vfunc is still not found, it could exist on an interface
3987 : : // implemented by a parent. This is an error, as hooking up the vfunc
3988 : : // would create an implementation on the interface itself. In this
3989 : : // case, print a more helpful error than...
3990 : : // "Could not find definition of virtual function"
3991 : : //
3992 : : // See https://gitlab.gnome.org/GNOME/gjs/-/issues/89
3993 [ + + ]: 72 : if (!vfunc) {
3994 : : unsigned n_interfaces;
3995 : 0 : Gjs::AutoPointer<GType> interface_list{
3996 : 5 : g_type_interfaces(m_gtype, &n_interfaces)};
3997 : :
3998 [ + + ]: 6 : for (unsigned i = 0; i < n_interfaces; i++) {
3999 : : GI::AutoInterfaceInfo interface{
4000 : 3 : g_irepository_find_by_gtype(nullptr, interface_list[i])};
4001 : :
4002 [ + - ]: 3 : if (interface) {
4003 : : GI::AutoVFuncInfo parent_vfunc{
4004 : 3 : g_interface_info_find_vfunc(interface, name.get())};
4005 : :
4006 [ + + ]: 3 : if (parent_vfunc) {
4007 : : Gjs::AutoChar identifier{g_strdup_printf(
4008 : 2 : "%s.%s", interface.ns(), interface.name())};
4009 : 2 : gjs_throw(cx,
4010 : : "%s does not implement %s, add %s to your "
4011 : : "implements array",
4012 : : g_type_name(m_gtype), identifier.get(),
4013 : : identifier.get());
4014 : 2 : return false;
4015 : 2 : }
4016 [ + + ]: 3 : }
4017 [ + + ]: 3 : }
4018 : :
4019 : : // Fall back to less helpful error message
4020 : 3 : gjs_throw(cx, "Could not find definition of virtual function %s",
4021 : : name.get());
4022 : 3 : return false;
4023 : 5 : }
4024 : :
4025 [ + + ]: 67 : if (g_callable_info_is_method(vfunc) != !is_static) {
4026 [ + - + - ]: 1 : gjs_throw(cx, "Invalid %s definition of %s virtual function %s",
4027 : : is_static ? "static" : "non-static",
4028 : : is_static ? "non-static" : "static", name.get());
4029 : 1 : return false;
4030 : : }
4031 : :
4032 : : void *implementor_vtable;
4033 : 66 : GI::AutoFieldInfo field_info;
4034 [ - + ]: 66 : if (!find_vfunc_info(cx, m_gtype, vfunc, name.get(), &implementor_vtable,
4035 : : &field_info))
4036 : 0 : return false;
4037 : :
4038 [ + - ]: 66 : if (field_info) {
4039 : : gint offset;
4040 : : void* method_ptr;
4041 : : GjsCallbackTrampoline *trampoline;
4042 : :
4043 : 66 : offset = g_field_info_get_offset(field_info);
4044 : 66 : method_ptr = G_STRUCT_MEMBER_P(implementor_vtable, offset);
4045 : :
4046 [ - + ]: 66 : if (!JS::IsCallable(callable)) {
4047 : 0 : gjs_throw(cx, "Tried to deal with a vfunc that wasn't callable");
4048 : 1 : return false;
4049 : : }
4050 : 66 : trampoline = GjsCallbackTrampoline::create(
4051 : 66 : cx, callable, vfunc, GI_SCOPE_TYPE_NOTIFIED, true, !is_static);
4052 [ + + ]: 66 : if (!trampoline)
4053 : 1 : return false;
4054 : :
4055 : : // This is traced, and will be cleared from the list when the closure is
4056 : : // invalidated
4057 : 65 : g_assert(std::find(m_vfuncs.begin(), m_vfuncs.end(), trampoline) ==
4058 : : m_vfuncs.end() &&
4059 : : "This vfunc was already associated with this class");
4060 : 65 : m_vfuncs.insert(trampoline);
4061 : 65 : g_closure_add_invalidate_notifier(
4062 : : trampoline, this, &ObjectPrototype::vfunc_invalidated_notify);
4063 : 65 : g_closure_add_invalidate_notifier(
4064 : : trampoline, nullptr,
4065 : 65 : [](void*, GClosure* closure) { g_closure_unref(closure); });
4066 : :
4067 : 65 : *reinterpret_cast<void**>(method_ptr) = trampoline->closure();
4068 : : }
4069 : :
4070 : 65 : return true;
4071 : 72 : }
4072 : :
4073 : 0 : void ObjectPrototype::vfunc_invalidated_notify(void* data, GClosure* closure) {
4074 : : // This callback should *only* touch m_vfuncs
4075 : 0 : auto* priv = static_cast<ObjectPrototype*>(data);
4076 : 0 : priv->m_vfuncs.erase(closure);
4077 : 0 : }
4078 : :
4079 : : bool
4080 : 573 : gjs_lookup_object_constructor(JSContext *context,
4081 : : GType gtype,
4082 : : JS::MutableHandleValue value_p)
4083 : : {
4084 : : JSObject *constructor;
4085 : :
4086 : 573 : GI::AutoObjectInfo object_info{gjs_lookup_gtype(nullptr, gtype)};
4087 : :
4088 : 573 : constructor = gjs_lookup_object_constructor_from_info(context, object_info, gtype);
4089 : :
4090 [ - + ]: 573 : if (G_UNLIKELY (constructor == NULL))
4091 : 0 : return false;
4092 : :
4093 : 573 : value_p.setObject(*constructor);
4094 : 573 : return true;
4095 : 573 : }
4096 : :
4097 : 2 : void ObjectInstance::associate_string(GObject* obj, char* str) {
4098 : : auto* instance_strings = static_cast<GPtrArray*>(
4099 : 2 : g_object_get_qdata(obj, ObjectBase::instance_strings_quark()));
4100 : :
4101 [ + + ]: 2 : if (!instance_strings) {
4102 : 1 : instance_strings = g_ptr_array_new_with_free_func(g_free);
4103 : 1 : g_object_set_qdata_full(
4104 : : obj, ObjectBase::instance_strings_quark(), instance_strings,
4105 : : reinterpret_cast<GDestroyNotify>(g_ptr_array_unref));
4106 : : }
4107 : 2 : g_ptr_array_add(instance_strings, str);
4108 : 2 : }
|