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 : :
5 : : #include <config.h>
6 : :
7 : : #include <stdint.h>
8 : : #include <string.h> // for memcpy, size_t, strcmp
9 : :
10 : : #include <algorithm> // for one_of, any_of
11 : : #include <utility> // for move, forward
12 : :
13 : : #include <girepository.h>
14 : : #include <glib-object.h>
15 : :
16 : : #include <js/CallArgs.h>
17 : : #include <js/Class.h>
18 : : #include <js/ErrorReport.h> // for JS_ReportOutOfMemory
19 : : #include <js/Exception.h>
20 : : #include <js/GCHashTable.h> // for GCHashMap
21 : : #include <js/GCVector.h> // for MutableWrappedPtrOperations
22 : : #include <js/Object.h> // for SetReservedSlot
23 : : #include <js/PropertyAndElement.h> // for JS_DefineFunction, JS_Enumerate
24 : : #include <js/String.h>
25 : : #include <js/TracingAPI.h>
26 : : #include <js/TypeDecls.h>
27 : : #include <js/Utility.h> // for UniqueChars
28 : : #include <js/Value.h>
29 : : #include <js/ValueArray.h>
30 : : #include <jsapi.h> // for IdVector
31 : : #include <mozilla/HashTable.h>
32 : : #include <mozilla/Span.h>
33 : :
34 : : #include "gi/arg-inl.h"
35 : : #include "gi/arg.h"
36 : : #include "gi/boxed.h"
37 : : #include "gi/function.h"
38 : : #include "gi/gerror.h"
39 : : #include "gi/repo.h"
40 : : #include "gi/wrapperutils.h"
41 : : #include "gjs/atoms.h"
42 : : #include "gjs/context-private.h"
43 : : #include "gjs/gerror-result.h"
44 : : #include "gjs/jsapi-class.h"
45 : : #include "gjs/jsapi-util.h"
46 : : #include "gjs/macros.h"
47 : : #include "gjs/mem-private.h"
48 : : #include "util/log.h"
49 : :
50 : : using mozilla::Maybe, mozilla::Some;
51 : :
52 : : [[nodiscard]] static bool struct_is_simple(const GI::StructInfo& info);
53 : :
54 : 21042 : BoxedInstance::BoxedInstance(BoxedPrototype* prototype, JS::HandleObject obj)
55 : : : GIWrapperInstance(prototype, obj),
56 : 21042 : m_allocated_directly(false),
57 : 21042 : m_owning_ptr(false) {
58 : 21042 : GJS_INC_COUNTER(boxed_instance);
59 : 21042 : }
60 : :
61 : : // See GIWrapperBase::resolve().
62 : 6550 : bool BoxedPrototype::resolve_impl(JSContext* cx, JS::HandleObject obj,
63 : : JS::HandleId id, bool* resolved) {
64 : 6550 : JS::UniqueChars prop_name;
65 [ - + ]: 6550 : if (!gjs_get_string_id(cx, id, &prop_name))
66 : 0 : return false;
67 [ + + ]: 6550 : if (!prop_name) {
68 : 411 : *resolved = false;
69 : 411 : return true; // not resolved, but no error
70 : : }
71 : :
72 : : // Look for methods and other class properties
73 : 6139 : Maybe<GI::AutoFunctionInfo> method_info{info().method(prop_name.get())};
74 [ + + ]: 6139 : if (!method_info) {
75 : 5283 : *resolved = false;
76 : 5283 : return true;
77 : : }
78 : 856 : method_info->log_usage();
79 : :
80 [ + - ]: 856 : if (method_info->is_method()) {
81 : 1712 : gjs_debug(GJS_DEBUG_GBOXED, "Defining method %s in prototype for %s",
82 : 2568 : method_info->name(), format_name().c_str());
83 : :
84 : : /* obj is the Boxed prototype */
85 [ - + ]: 856 : if (!gjs_define_function(cx, obj, gtype(), *method_info))
86 : 0 : return false;
87 : :
88 : 856 : *resolved = true;
89 : : } else {
90 : 0 : *resolved = false;
91 : : }
92 : :
93 : 856 : return true;
94 : 6550 : }
95 : :
96 : : // See GIWrapperBase::new_enumerate().
97 : 3 : bool BoxedPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject,
98 : : JS::MutableHandleIdVector properties,
99 : : bool only_enumerable [[maybe_unused]]) {
100 [ + + ]: 36 : for (GI::AutoFunctionInfo meth_info : info().methods()) {
101 [ + - ]: 33 : if (meth_info.is_method()) {
102 : 33 : jsid id = gjs_intern_string_to_id(cx, meth_info.name());
103 [ - + ]: 33 : if (id.isVoid())
104 : 0 : return false;
105 [ - + ]: 33 : if (!properties.append(id)) {
106 : 0 : JS_ReportOutOfMemory(cx);
107 : 0 : return false;
108 : : }
109 : : }
110 [ + - ]: 33 : }
111 : :
112 : 3 : return true;
113 : : }
114 : :
115 : : /*
116 : : * BoxedBase::get_copy_source():
117 : : *
118 : : * Check to see if JS::Value passed in is another Boxed instance object of the
119 : : * same type, and if so, retrieve the BoxedInstance private structure for it.
120 : : * This function does not throw any JS exceptions.
121 : : */
122 : 1008 : BoxedBase* BoxedBase::get_copy_source(JSContext* context,
123 : : JS::Value value) const {
124 [ + + ]: 1008 : if (!value.isObject())
125 : 902 : return nullptr;
126 : :
127 : 106 : JS::RootedObject object(context, &value.toObject());
128 : 106 : BoxedBase* source_priv = BoxedBase::for_js(context, object);
129 [ + + + + : 106 : if (!source_priv || info() != source_priv->info())
+ + ]
130 : 94 : return nullptr;
131 : :
132 : 12 : return source_priv;
133 : 106 : }
134 : :
135 : : /*
136 : : * BoxedInstance::allocate_directly:
137 : : *
138 : : * Allocate a boxed object of the correct size, set all the bytes to 0, and set
139 : : * m_ptr to point to it. This is used when constructing a boxed object that can
140 : : * be allocated directly (i.e., does not need to be created by a constructor
141 : : * function.)
142 : : */
143 : 429 : void BoxedInstance::allocate_directly(void) {
144 : 429 : g_assert(get_prototype()->can_allocate_directly());
145 : :
146 : 429 : own_ptr(g_malloc0(info().size()));
147 : 429 : m_allocated_directly = true;
148 : :
149 : 429 : debug_lifecycle("Boxed pointer directly allocated");
150 : 429 : }
151 : :
152 : : // When initializing a boxed object from a hash of properties, we don't want to
153 : : // do n O(n) lookups, so put put the fields into a hash table and store it on
154 : : // proto->priv for fast lookup.
155 : 15 : std::unique_ptr<BoxedPrototype::FieldMap> BoxedPrototype::create_field_map(
156 : : JSContext* cx, const GI::StructInfo struct_info) {
157 : 15 : auto result = std::make_unique<BoxedPrototype::FieldMap>();
158 : 15 : GI::StructInfo::FieldsIterator fields = struct_info.fields();
159 [ - + ]: 15 : if (!result->reserve(fields.size())) {
160 : 0 : JS_ReportOutOfMemory(cx);
161 : 0 : return nullptr;
162 : : }
163 : :
164 [ + + ]: 57 : for (GI::AutoFieldInfo field_info : fields) {
165 : : // We get the string as a jsid later, which is interned. We intern the
166 : : // string here as well, so it will be the same string pointer
167 : 42 : JSString* atom = JS_AtomizeAndPinString(cx, field_info.name());
168 : :
169 : 42 : result->putNewInfallible(atom, std::move(field_info));
170 : 42 : }
171 : :
172 : 15 : return result;
173 : 15 : }
174 : :
175 : : /*
176 : : * BoxedPrototype::ensure_field_map:
177 : : *
178 : : * BoxedPrototype keeps a cache of field names to introspection info.
179 : : * We only create the field cache the first time it is needed. An alternative
180 : : * would be to create it when the prototype is created, in BoxedPrototype::init.
181 : : */
182 : 117 : bool BoxedPrototype::ensure_field_map(JSContext* cx) {
183 [ + + ]: 117 : if (!m_field_map)
184 : 15 : m_field_map = create_field_map(cx, info());
185 : 117 : return !!m_field_map;
186 : : }
187 : :
188 : : /*
189 : : * BoxedPrototype::lookup_field:
190 : : *
191 : : * Look up the introspection info corresponding to the field name @prop_name,
192 : : * creating the field cache if necessary.
193 : : */
194 : 117 : Maybe<const GI::FieldInfo> BoxedPrototype::lookup_field(JSContext* cx,
195 : : JSString* prop_name) {
196 [ - + ]: 117 : if (!ensure_field_map(cx))
197 : 0 : return {};
198 : :
199 : 117 : auto entry = m_field_map->lookup(prop_name);
200 [ + + ]: 117 : if (!entry) {
201 : 4 : gjs_throw(cx, "No field %s on boxed type %s",
202 : 4 : gjs_debug_string(prop_name).c_str(), name());
203 : 2 : return {};
204 : : }
205 : :
206 : 115 : return Some(entry->value());
207 : : }
208 : :
209 : : /* Initialize a newly created Boxed from an object that is a "hash" of
210 : : * properties to set as fields of the object. We don't require that every field
211 : : * of the object be set.
212 : : */
213 : 79 : bool BoxedInstance::init_from_props(JSContext* context, JS::Value props_value) {
214 : : size_t ix, length;
215 : :
216 [ - + ]: 79 : if (!props_value.isObject()) {
217 : 0 : gjs_throw(context, "argument should be a hash with fields to set");
218 : 0 : return false;
219 : : }
220 : :
221 : 79 : JS::RootedObject props(context, &props_value.toObject());
222 : 79 : JS::Rooted<JS::IdVector> ids(context, context);
223 [ - + ]: 79 : if (!JS_Enumerate(context, props, &ids)) {
224 : 0 : gjs_throw(context, "Failed to enumerate fields hash");
225 : 0 : return false;
226 : : }
227 : :
228 : 79 : JS::RootedValue value(context);
229 [ + + ]: 194 : for (ix = 0, length = ids.length(); ix < length; ix++) {
230 [ - + ]: 117 : if (!ids[ix].isString()) {
231 : 0 : gjs_throw(context, "Fields hash contained a non-string field");
232 : 2 : return false;
233 : : }
234 : :
235 : : Maybe<const GI::FieldInfo> field_info =
236 : 117 : get_prototype()->lookup_field(context, ids[ix].toString());
237 [ + + ]: 117 : if (!field_info)
238 : 2 : return false;
239 : :
240 : : /* ids[ix] is reachable because props is rooted, but require_property
241 : : * doesn't know that */
242 : 230 : if (!gjs_object_require_property(context, props, "property list",
243 [ - + ]: 230 : JS::HandleId::fromMarkedLocation(ids[ix].address()),
244 : : &value))
245 : 0 : return false;
246 : :
247 [ - + ]: 115 : if (!field_setter_impl(context, *field_info, value))
248 : 0 : return false;
249 [ + + ]: 117 : }
250 : :
251 : 77 : return true;
252 : 79 : }
253 : :
254 : : GJS_JSAPI_RETURN_CONVENTION
255 : 1493 : static bool boxed_invoke_constructor(JSContext* context, JS::HandleObject obj,
256 : : JS::HandleId constructor_name,
257 : : const JS::CallArgs& args) {
258 : 1493 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
259 : 1493 : JS::RootedObject js_constructor(context);
260 : :
261 [ - + ]: 2986 : if (!gjs_object_require_property(
262 : 1493 : context, obj, nullptr, gjs->atoms().constructor(), &js_constructor))
263 : 0 : return false;
264 : :
265 : 1493 : JS::RootedValue js_constructor_func(context);
266 [ - + ]: 1493 : if (!gjs_object_require_property(context, js_constructor, NULL,
267 : : constructor_name, &js_constructor_func))
268 : 0 : return false;
269 : :
270 : 1493 : return gjs->call_function(nullptr, js_constructor_func, args, args.rval());
271 : 1493 : }
272 : :
273 : : /*
274 : : * BoxedInstance::copy_boxed:
275 : : *
276 : : * Allocate a new boxed pointer using g_boxed_copy(), either from a raw boxed
277 : : * pointer or another BoxedInstance.
278 : : */
279 : 15671 : void BoxedInstance::copy_boxed(void* boxed_ptr) {
280 : 15671 : own_ptr(g_boxed_copy(gtype(), boxed_ptr));
281 : 15671 : debug_lifecycle("Boxed pointer created with g_boxed_copy()");
282 : 15671 : }
283 : :
284 : 11 : void BoxedInstance::copy_boxed(BoxedInstance* source) {
285 : 11 : copy_boxed(source->ptr());
286 : 11 : }
287 : :
288 : : /*
289 : : * BoxedInstance::copy_memory:
290 : : *
291 : : * Allocate a new boxed pointer by copying the contents of another boxed pointer
292 : : * or another BoxedInstance.
293 : : */
294 : 26 : void BoxedInstance::copy_memory(void* boxed_ptr) {
295 : 26 : allocate_directly();
296 : 26 : memcpy(m_ptr, boxed_ptr, info().size());
297 : 26 : }
298 : :
299 : 1 : void BoxedInstance::copy_memory(BoxedInstance* source) {
300 : 1 : copy_memory(source->ptr());
301 : 1 : }
302 : :
303 : : // See GIWrapperBase::constructor().
304 : 1995 : bool BoxedInstance::constructor_impl(JSContext* context, JS::HandleObject obj,
305 : : const JS::CallArgs& args) {
306 : : // Short-circuit copy-construction in the case where we can use copy_boxed()
307 : : // or copy_memory()
308 : : BoxedBase* source_priv;
309 [ + + + + ]: 3000 : if (args.length() == 1 &&
310 [ + + ]: 3000 : (source_priv = get_copy_source(context, args[0]))) {
311 [ - + ]: 12 : if (!source_priv->check_is_instance(context, "construct boxed object"))
312 : 0 : return false;
313 : :
314 [ + - + + : 12 : if (g_type_is_a(gtype(), G_TYPE_BOXED)) {
+ + ]
315 : 11 : copy_boxed(source_priv->to_instance());
316 : 11 : return true;
317 [ + - ]: 1 : } else if (get_prototype()->can_allocate_directly()) {
318 : 1 : copy_memory(source_priv->to_instance());
319 : 1 : return true;
320 : : }
321 : : }
322 : :
323 [ + + ]: 1983 : if (gtype() == G_TYPE_VARIANT) {
324 : : /* Short-circuit construction for GVariants by calling into the JS packing
325 : : function */
326 : 485 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
327 [ + + ]: 485 : if (!boxed_invoke_constructor(context, obj, atoms.new_internal(), args))
328 : 2 : return false;
329 : :
330 : : // The return value of GLib.Variant.new_internal() gets its own
331 : : // BoxedInstance, and the one we're setting up in this constructor is
332 : : // discarded.
333 : 483 : debug_lifecycle(
334 : : "Boxed construction delegated to GVariant constructor, "
335 : : "boxed object discarded");
336 : :
337 : 483 : return true;
338 : : }
339 : :
340 : 1498 : BoxedPrototype* proto = get_prototype();
341 : :
342 : : // If the structure is registered as a boxed, we can create a new instance
343 : : // by looking for a zero-args constructor and calling it.
344 : : // Constructors don't really make sense for non-boxed types, since there is
345 : : // no memory management for the return value, and zero_args_constructor and
346 : : // default_constructor are always -1 for them.
347 : : //
348 : : // For backward compatibility, we choose the zero args constructor if one
349 : : // exists, otherwise we malloc the correct amount of space if possible;
350 : : // finally, we fallback on the default constructor.
351 [ + + ]: 1498 : if (proto->has_zero_args_constructor()) {
352 : 85 : GI::AutoFunctionInfo func_info{proto->zero_args_constructor_info()};
353 : :
354 : : GIArgument rval_arg;
355 : 85 : Gjs::GErrorResult<> result = func_info.invoke({}, {}, &rval_arg);
356 [ - + ]: 85 : if (result.isErr()) {
357 : 0 : gjs_throw(context, "Failed to invoke boxed constructor: %s",
358 : 0 : result.inspectErr()->message);
359 : 0 : return false;
360 : : }
361 : :
362 : 85 : own_ptr(gjs_arg_steal<void*>(&rval_arg));
363 : :
364 : 85 : debug_lifecycle("Boxed pointer created from zero-args constructor");
365 : :
366 [ + - + - : 1498 : } else if (proto->can_allocate_directly_without_pointers()) {
+ + ]
367 : 403 : allocate_directly();
368 [ + + ]: 1010 : } else if (proto->has_default_constructor()) {
369 : : /* for simplicity, we simply delegate all the work to the actual JS
370 : : * constructor function (which we retrieve from the JS constructor,
371 : : * that is, Namespace.BoxedType, or object.constructor, given that
372 : : * object was created with the right prototype. */
373 [ + + ]: 1008 : if (!boxed_invoke_constructor(context, obj,
374 : : proto->default_constructor_name(), args))
375 : 1 : return false;
376 : :
377 : : // Define the expected Error properties
378 [ + + ]: 1007 : if (gtype() == G_TYPE_ERROR) {
379 : 3 : JS::RootedObject gerror(context, &args.rval().toObject());
380 [ - + ]: 3 : if (!gjs_define_error_properties(context, gerror))
381 : 0 : return false;
382 [ + - ]: 3 : }
383 : :
384 : : // The return value of the JS constructor gets its own BoxedInstance,
385 : : // and this one is discarded.
386 : 1007 : debug_lifecycle(
387 : : "Boxed construction delegated to JS constructor, "
388 : : "boxed object discarded");
389 : :
390 : 1007 : return true;
391 [ - + ]: 2 : } else if (proto->can_allocate_directly()) {
392 : 0 : allocate_directly();
393 : : } else {
394 : 2 : gjs_throw(context,
395 : : "Unable to construct struct type %s since it has no default "
396 : : "constructor and cannot be allocated directly",
397 : : name());
398 : 2 : return false;
399 : : }
400 : :
401 : : /* If we reach this code, we need to init from a map of fields */
402 : :
403 [ + + ]: 488 : if (args.length() == 0)
404 : 409 : return true;
405 : :
406 [ - + ]: 79 : if (args.length() > 1) {
407 : 0 : gjs_throw(context,
408 : : "Constructor with multiple arguments not supported for %s",
409 : : name());
410 : 0 : return false;
411 : : }
412 : :
413 : 79 : return init_from_props(context, args[0]);
414 : : }
415 : :
416 : 21039 : BoxedInstance::~BoxedInstance() {
417 [ + + ]: 21039 : if (m_owning_ptr) {
418 [ + + ]: 19515 : if (m_allocated_directly) {
419 [ + + ]: 429 : if (gtype() == G_TYPE_VALUE)
420 : 337 : g_value_unset(m_ptr.as<GValue>());
421 : 429 : g_free(m_ptr.release());
422 : : } else {
423 [ + - + + : 19086 : if (g_type_is_a(gtype(), G_TYPE_BOXED))
+ + ]
424 : 15754 : g_boxed_free(gtype(), m_ptr.release());
425 [ - + - - : 3332 : else if (g_type_is_a(gtype(), G_TYPE_VARIANT))
+ - ]
426 : 3332 : g_variant_unref(static_cast<GVariant*>(m_ptr.release()));
427 : : else
428 : : g_assert_not_reached ();
429 : : }
430 : : }
431 : :
432 : 21039 : GJS_DEC_COUNTER(boxed_instance);
433 : 21039 : }
434 : :
435 : 861 : BoxedPrototype::~BoxedPrototype(void) {
436 : 861 : GJS_DEC_COUNTER(boxed_prototype);
437 : 861 : }
438 : :
439 : : /*
440 : : * BoxedBase::get_field_info:
441 : : *
442 : : * Does the same thing as g_struct_info_get_field(), but throws a JS exception
443 : : * if there is no such field.
444 : : */
445 : 2097 : Maybe<GI::AutoFieldInfo> BoxedBase::get_field_info(JSContext* cx,
446 : : uint32_t id) const {
447 : 2097 : Maybe<GI::AutoFieldInfo> field_info = info().fields()[id];
448 [ - + ]: 2097 : if (!field_info)
449 : 0 : gjs_throw(cx, "No field %d on boxed type %s", id, name());
450 : :
451 : 2097 : return field_info;
452 : : }
453 : :
454 : : /*
455 : : * BoxedInstance::get_nested_interface_object:
456 : : * @parent_obj: the BoxedInstance JS object that owns `this`
457 : : * @field_info: introspection info for the field of the parent boxed type that
458 : : * is another boxed type
459 : : * @interface_info: introspection info for the nested boxed type
460 : : * @value: return location for a new BoxedInstance JS object
461 : : *
462 : : * Some boxed types have a field that consists of another boxed type. We want to
463 : : * be able to expose these nested boxed types without copying them, because
464 : : * changing fields of the nested boxed struct should affect the enclosing boxed
465 : : * struct.
466 : : *
467 : : * This method creates a new BoxedInstance and JS object for a nested boxed
468 : : * struct. Since both the nested JS object and the parent boxed's JS object
469 : : * refer to the same memory, the parent JS object will be prevented from being
470 : : * garbage collected while the nested JS object is active.
471 : : */
472 : 16 : bool BoxedInstance::get_nested_interface_object(
473 : : JSContext* context, JSObject* parent_obj, const GI::FieldInfo field_info,
474 : : const GI::StructInfo struct_info, JS::MutableHandleValue value) const {
475 [ - + ]: 16 : if (!struct_is_simple(struct_info)) {
476 : 0 : gjs_throw(context, "Reading field %s.%s is not supported",
477 : 0 : format_name().c_str(), field_info.name());
478 : :
479 : 0 : return false;
480 : : }
481 : :
482 : : JS::RootedObject obj{
483 : 16 : context, gjs_new_object_with_generic_prototype(context, struct_info)};
484 [ - + ]: 16 : if (!obj)
485 : 0 : return false;
486 : :
487 : 16 : BoxedInstance* priv = BoxedInstance::new_for_js_object(context, obj);
488 : :
489 : : /* A structure nested inside a parent object; doesn't have an independent allocation */
490 : 16 : priv->share_ptr(raw_ptr() + field_info.offset());
491 : 16 : priv->debug_lifecycle(
492 : : "Boxed pointer created, pointing inside memory owned by parent");
493 : :
494 : : /* We never actually read the reserved slot, but we put the parent object
495 : : * into it to hold onto the parent object.
496 : : */
497 : 16 : JS::SetReservedSlot(obj, BoxedInstance::PARENT_OBJECT,
498 : 16 : JS::ObjectValue(*parent_obj));
499 : :
500 : 16 : value.setObject(*obj);
501 : 16 : return true;
502 : 16 : }
503 : :
504 : : /*
505 : : * BoxedBase::field_getter:
506 : : *
507 : : * JSNative property getter that is called when accessing a field defined on a
508 : : * boxed type. Delegates to BoxedInstance::field_getter_impl() if the minimal
509 : : * conditions have been met.
510 : : */
511 : 2011 : bool BoxedBase::field_getter(JSContext* context, unsigned argc, JS::Value* vp) {
512 [ - + - + ]: 2011 : GJS_CHECK_WRAPPER_PRIV(context, argc, vp, args, obj, BoxedBase, priv);
513 [ - + ]: 2011 : if (!priv->check_is_instance(context, "get a field"))
514 : 0 : return false;
515 : :
516 : 2011 : uint32_t field_ix = gjs_dynamic_property_private_slot(&args.callee())
517 : 2011 : .toPrivateUint32();
518 : : Maybe<GI::AutoFieldInfo> field_info{
519 : 2011 : priv->get_field_info(context, field_ix)};
520 [ - + ]: 2011 : if (!field_info)
521 : 0 : return false;
522 : :
523 : 6033 : return priv->to_instance()->field_getter_impl(
524 : 4022 : context, obj, field_info.ref(), args.rval());
525 : 2011 : }
526 : :
527 : : // See BoxedBase::field_getter().
528 : 2011 : bool BoxedInstance::field_getter_impl(JSContext* cx, JSObject* obj,
529 : : const GI::FieldInfo field_info,
530 : : JS::MutableHandleValue rval) const {
531 : 2011 : GI::AutoTypeInfo type_info{field_info.type_info()};
532 : :
533 [ + + + + : 2011 : if (!type_info.is_pointer() && type_info.tag() == GI_TYPE_TAG_INTERFACE) {
+ + ]
534 : 55 : GI::AutoBaseInfo interface{type_info.interface()};
535 [ + + ]: 55 : if (auto struct_info = interface.as<GI::InfoTag::STRUCT>()) {
536 : 16 : return get_nested_interface_object(cx, obj, field_info,
537 : 32 : struct_info.value(), rval);
538 [ + + ]: 55 : }
539 [ + + ]: 55 : }
540 : :
541 : : GIArgument arg;
542 [ - + ]: 1995 : if (field_info.read(m_ptr, &arg).isErr()) {
543 : 0 : gjs_throw(cx, "Reading field %s.%s is not supported",
544 : 0 : format_name().c_str(), field_info.name());
545 : 0 : return false;
546 : : }
547 : :
548 [ + + + + ]: 2415 : if (type_info.tag() == GI_TYPE_TAG_ARRAY &&
549 [ + + ]: 2415 : type_info.array_length_index()) {
550 : 2 : unsigned length_field_ix = type_info.array_length_index().value();
551 : : Maybe<GI::AutoFieldInfo> length_field_info{
552 : 2 : get_field_info(cx, length_field_ix)};
553 [ - + ]: 2 : if (!length_field_info) {
554 : 0 : gjs_throw(cx, "Reading field %s.%s is not supported",
555 : 0 : format_name().c_str(), field_info.name());
556 : 0 : return false;
557 : : }
558 : :
559 : : GIArgument length_arg;
560 [ - + ]: 2 : if (length_field_info->read(m_ptr, &length_arg).isErr()) {
561 : 0 : gjs_throw(cx, "Reading field %s.%s is not supported",
562 : 0 : format_name().c_str(), length_field_info->name());
563 : 0 : return false;
564 : : }
565 : :
566 : 2 : size_t length = gjs_gi_argument_get_array_length(
567 : 4 : length_field_info->type_info().tag(), &length_arg);
568 : 2 : return gjs_value_from_explicit_array(cx, rval, type_info, &arg, length);
569 : 2 : }
570 : :
571 : 3986 : return gjs_value_from_gi_argument(cx, rval, type_info, GJS_ARGUMENT_FIELD,
572 : 1993 : GI_TRANSFER_EVERYTHING, &arg);
573 : 2011 : }
574 : :
575 : : /*
576 : : * BoxedInstance::set_nested_interface_object:
577 : : * @field_info: introspection info for the field of the parent boxed type that
578 : : * is another boxed type
579 : : * @interface_info: introspection info for the nested boxed type
580 : : * @value: holds a BoxedInstance JS object of type @interface_info
581 : : *
582 : : * Some boxed types have a field that consists of another boxed type. This
583 : : * method is called from BoxedInstance::field_setter_impl() when any such field
584 : : * is being set. The contents of the BoxedInstance JS object in @value are
585 : : * copied into the correct place in this BoxedInstance's memory.
586 : : */
587 : 3 : bool BoxedInstance::set_nested_interface_object(
588 : : JSContext* context, const GI::FieldInfo field_info,
589 : : const GI::StructInfo struct_info, JS::HandleValue value) {
590 [ - + ]: 3 : if (!struct_is_simple(struct_info)) {
591 : 0 : gjs_throw(context, "Writing field %s.%s is not supported",
592 : 0 : format_name().c_str(), field_info.name());
593 : :
594 : 0 : return false;
595 : : }
596 : :
597 : : JS::RootedObject proto{context,
598 : 3 : gjs_lookup_generic_prototype(context, struct_info)};
599 : :
600 [ - + ]: 3 : if (!proto)
601 : 0 : return false;
602 : :
603 : : /* If we can't directly copy from the source object we need
604 : : * to construct a new temporary object.
605 : : */
606 : 3 : BoxedBase* source_priv = get_copy_source(context, value);
607 [ + - ]: 3 : if (!source_priv) {
608 : 3 : JS::RootedValueArray<1> args(context);
609 : 3 : args[0].set(value);
610 : : JS::RootedObject tmp_object(context,
611 : 3 : gjs_construct_object_dynamic(context, proto, args));
612 [ + - - + : 3 : if (!tmp_object || !for_js_typecheck(context, tmp_object, &source_priv))
- + ]
613 : 0 : return false;
614 [ + - + - ]: 3 : }
615 : :
616 [ - + ]: 3 : if (!source_priv->check_is_instance(context, "copy"))
617 : 0 : return false;
618 : :
619 : 3 : memcpy(raw_ptr() + field_info.offset(), source_priv->to_instance()->ptr(),
620 : 3 : source_priv->info().size());
621 : :
622 : 3 : return true;
623 : 3 : }
624 : :
625 : : // See BoxedBase::field_setter().
626 : 199 : bool BoxedInstance::field_setter_impl(JSContext* context,
627 : : const GI::FieldInfo field_info,
628 : : JS::HandleValue value) {
629 : 199 : GI::AutoTypeInfo type_info{field_info.type_info()};
630 : :
631 [ + + + + : 199 : if (!type_info.is_pointer() && type_info.tag() == GI_TYPE_TAG_INTERFACE) {
+ + ]
632 : 22 : GI::AutoBaseInfo interface_info{type_info.interface()};
633 [ + + ]: 22 : if (auto struct_info = interface_info.as<GI::InfoTag::STRUCT>()) {
634 : 3 : return set_nested_interface_object(context, field_info,
635 : 6 : struct_info.value(), value);
636 [ + + ]: 22 : }
637 [ + + ]: 22 : }
638 : :
639 : : GIArgument arg;
640 [ - + ]: 196 : if (!gjs_value_to_gi_argument(context, value, type_info, field_info.name(),
641 : : GJS_ARGUMENT_FIELD, GI_TRANSFER_NOTHING,
642 : : GjsArgumentFlags::MAY_BE_NULL, &arg))
643 : 0 : return false;
644 : :
645 : 196 : bool success = true;
646 [ + + ]: 196 : if (field_info.write(m_ptr, &arg).isErr()) {
647 : 2 : gjs_throw(context, "Writing field %s.%s is not supported",
648 : 2 : format_name().c_str(), field_info.name());
649 : 1 : success = false;
650 : : }
651 : :
652 : 196 : JS::AutoSaveExceptionState saved_exc(context);
653 [ - + ]: 196 : if (!gjs_gi_argument_release(context, GI_TRANSFER_NOTHING, type_info,
654 : : GjsArgumentFlags::ARG_IN, &arg))
655 : 0 : gjs_log_exception(context);
656 : 196 : saved_exc.restore();
657 : :
658 : 196 : return success;
659 : 199 : }
660 : :
661 : : /*
662 : : * BoxedBase::field_setter:
663 : : *
664 : : * JSNative property setter that is called when writing to a field defined on a
665 : : * boxed type. Delegates to BoxedInstance::field_setter_impl() if the minimal
666 : : * conditions have been met.
667 : : */
668 : 84 : bool BoxedBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) {
669 [ - + - + ]: 84 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, BoxedBase, priv);
670 [ - + ]: 84 : if (!priv->check_is_instance(cx, "set a field"))
671 : 0 : return false;
672 : :
673 : 84 : uint32_t field_ix = gjs_dynamic_property_private_slot(&args.callee())
674 : 84 : .toPrivateUint32();
675 : 84 : Maybe<GI::AutoFieldInfo> field_info{priv->get_field_info(cx, field_ix)};
676 [ - + ]: 84 : if (!field_info)
677 : 0 : return false;
678 : :
679 [ + + ]: 84 : if (!priv->to_instance()->field_setter_impl(cx, *field_info, args[0]))
680 : 1 : return false;
681 : :
682 : 83 : args.rval().setUndefined(); /* No stored value */
683 : 83 : return true;
684 : 84 : }
685 : :
686 : : /*
687 : : * BoxedPrototype::define_boxed_class_fields:
688 : : *
689 : : * Defines properties on the JS prototype object, with JSNative getters and
690 : : * setters, for all the fields exposed by GObject introspection.
691 : : */
692 : 879 : bool BoxedPrototype::define_boxed_class_fields(JSContext* cx,
693 : : JS::HandleObject proto) {
694 : 879 : uint32_t count = 0;
695 : :
696 : : // We define all fields as read/write so that the user gets an error
697 : : // message. If we omitted fields or defined them read-only we'd:
698 : : //
699 : : // - Store a new property for a non-accessible field
700 : : // - Silently do nothing when writing a read-only field
701 : : //
702 : : // Which is pretty confusing if the only reason a field isn't writable is
703 : : // language binding or memory-management restrictions.
704 : : //
705 : : // We just go ahead and define the fields immediately for the class; doing
706 : : // it lazily in boxed_resolve() would be possible as well if doing it ahead
707 : : // of time caused too much start-up memory overhead.
708 : : //
709 : : // At this point methods have already been defined on the prototype, so we
710 : : // may get name conflicts which we need to check for.
711 [ + + ]: 2909 : for (GI::AutoFieldInfo field : info().fields()) {
712 : 2030 : JS::RootedValue private_id{cx, JS::PrivateUint32Value(count++)};
713 : 2030 : JS::RootedId id{cx, gjs_intern_string_to_id(cx, field.name())};
714 : :
715 : : bool already_defined;
716 [ - + ]: 2030 : if (!JS_HasOwnPropertyById(cx, proto, id, &already_defined))
717 : 0 : return false;
718 [ + + ]: 2030 : if (already_defined) {
719 : 2 : gjs_debug(GJS_DEBUG_GBOXED,
720 : : "Field %s.%s overlaps with method of the same name; "
721 : : "skipping",
722 : 2 : format_name().c_str(), field.name());
723 : 1 : continue;
724 : : }
725 : :
726 [ - + ]: 2029 : if (!gjs_define_property_dynamic(
727 : : cx, proto, field.name(), id, "boxed_field",
728 : : &BoxedBase::field_getter, &BoxedBase::field_setter, private_id,
729 : : GJS_MODULE_PROP_FLAGS))
730 : 0 : return false;
731 [ + - + + : 2032 : }
- + + -
+ ]
732 : :
733 : 879 : return true;
734 : : }
735 : :
736 : : // Overrides GIWrapperPrototype::trace_impl().
737 : 1798 : void BoxedPrototype::trace_impl(JSTracer* trc) {
738 : 1798 : JS::TraceEdge<jsid>(trc, &m_default_constructor_name,
739 : : "Boxed::default_constructor_name");
740 [ + + ]: 1798 : if (m_field_map)
741 : 15 : m_field_map->trace(trc);
742 : 1798 : }
743 : :
744 : : // clang-format off
745 : : const struct JSClassOps BoxedBase::class_ops = {
746 : : nullptr, // addProperty
747 : : nullptr, // deleteProperty
748 : : nullptr, // enumerate
749 : : &BoxedBase::new_enumerate,
750 : : &BoxedBase::resolve,
751 : : nullptr, // mayResolve
752 : : &BoxedBase::finalize,
753 : : nullptr, // call
754 : : nullptr, // construct
755 : : &BoxedBase::trace
756 : : };
757 : :
758 : : /* We allocate 1 extra reserved slot; this is typically unused, but if the
759 : : * boxed is for a nested structure inside a parent structure, the
760 : : * reserved slot is used to hold onto the parent Javascript object and
761 : : * make sure it doesn't get freed.
762 : : */
763 : : const struct JSClass BoxedBase::klass = {
764 : : "GObject_Boxed",
765 : : JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_FOREGROUND_FINALIZE,
766 : : &BoxedBase::class_ops
767 : : };
768 : : // clang-format on
769 : :
770 : : [[nodiscard]]
771 : 4076 : static bool type_can_be_allocated_directly(const GI::TypeInfo& type_info) {
772 [ + + ]: 4076 : if (type_info.is_pointer()) {
773 [ + + + - : 2348 : if (type_info.tag() == GI_TYPE_TAG_ARRAY &&
+ + ]
774 : 344 : type_info.array_type() == GI_ARRAY_TYPE_C)
775 : 344 : return type_can_be_allocated_directly(type_info.element_type());
776 : :
777 : 1660 : return true;
778 : : }
779 : :
780 [ + + ]: 2072 : if (type_info.tag() != GI_TYPE_TAG_INTERFACE)
781 : 1782 : return true;
782 : :
783 : 290 : GI::AutoBaseInfo interface_info{type_info.interface()};
784 [ + + ]: 290 : if (auto struct_info = interface_info.as<GI::InfoTag::STRUCT>())
785 [ + + ]: 290 : return struct_is_simple(struct_info.value());
786 [ - + ]: 280 : if (interface_info.is_union())
787 : 0 : return false; // FIXME: Need to implement
788 [ + + ]: 280 : if (interface_info.is_enum_or_flags())
789 : 216 : return true;
790 [ - + ]: 64 : if (interface_info.type() == GI_INFO_TYPE_INVALID_0)
791 : : g_assert_not_reached();
792 : 64 : return false;
793 : 290 : }
794 : :
795 : : [[nodiscard]]
796 : : static bool simple_struct_has_pointers(const GI::StructInfo);
797 : :
798 : : [[nodiscard]]
799 : 746 : static bool direct_allocation_has_pointers(const GI::TypeInfo type_info) {
800 [ + + ]: 746 : if (type_info.is_pointer()) {
801 [ + + + - : 272 : if (type_info.tag() == GI_TYPE_TAG_ARRAY &&
+ + ]
802 : 1 : type_info.array_type() == GI_ARRAY_TYPE_C) {
803 : 1 : return direct_allocation_has_pointers(type_info.element_type());
804 : : }
805 : :
806 : 270 : return type_info.tag() != GI_TYPE_TAG_VOID;
807 : : }
808 : :
809 [ + + ]: 475 : if (type_info.tag() != GI_TYPE_TAG_INTERFACE)
810 : 417 : return false;
811 : :
812 : 58 : GI::AutoBaseInfo interface{type_info.interface()};
813 [ + + ]: 58 : if (auto struct_info = interface.as<GI::InfoTag::STRUCT>())
814 [ + + ]: 58 : return simple_struct_has_pointers(struct_info.value());
815 : :
816 : 55 : return false;
817 : 58 : }
818 : :
819 : : /* Check if the type of the boxed is "simple" - every field is a non-pointer
820 : : * type that we know how to assign to. If so, then we can allocate and free
821 : : * instances without needing a constructor.
822 : : */
823 : : [[nodiscard]]
824 : 1221 : bool struct_is_simple(const GI::StructInfo& info) {
825 : 1221 : GI::StructInfo::FieldsIterator iter = info.fields();
826 : :
827 : : // If it's opaque, it's not simple
828 [ + + ]: 1221 : if (iter.size() == 0)
829 : 505 : return false;
830 : :
831 : 716 : return std::all_of(
832 : 3732 : iter.begin(), iter.end(), [](GI::AutoFieldInfo field_info) {
833 : 3732 : return type_can_be_allocated_directly(field_info.type_info());
834 : 716 : });
835 : : }
836 : :
837 : : [[nodiscard]]
838 : 313 : static bool simple_struct_has_pointers(const GI::StructInfo info) {
839 : 313 : g_assert(struct_is_simple(info) &&
840 : : "Don't call simple_struct_has_pointers() on a non-simple struct");
841 : :
842 : 313 : GI::StructInfo::FieldsIterator fields = info.fields();
843 : 313 : return std::any_of(
844 : 745 : fields.begin(), fields.end(), [](GI::AutoFieldInfo field) {
845 : 745 : return direct_allocation_has_pointers(field.type_info());
846 : 313 : });
847 : : }
848 : :
849 : 879 : BoxedPrototype::BoxedPrototype(const GI::StructInfo info, GType gtype)
850 : : : GIWrapperPrototype(info, gtype),
851 : 879 : m_zero_args_constructor(-1),
852 : 879 : m_default_constructor(-1),
853 : 879 : m_default_constructor_name(JS::PropertyKey::Void()),
854 : 1758 : m_can_allocate_directly(struct_is_simple(info)) {
855 [ + + ]: 879 : if (!m_can_allocate_directly) {
856 : 569 : m_can_allocate_directly_without_pointers = false;
857 : : } else {
858 : 310 : m_can_allocate_directly_without_pointers =
859 : 310 : !simple_struct_has_pointers(info);
860 : : }
861 : 879 : GJS_INC_COUNTER(boxed_prototype);
862 : 879 : }
863 : :
864 : : // Overrides GIWrapperPrototype::init().
865 : 879 : bool BoxedPrototype::init(JSContext* context) {
866 : 879 : int i = 0;
867 : 879 : int first_constructor = -1;
868 : 879 : jsid first_constructor_name = JS::PropertyKey::Void();
869 : 879 : jsid zero_args_constructor_name = JS::PropertyKey::Void();
870 : :
871 [ + + ]: 879 : if (m_gtype != G_TYPE_NONE) {
872 : : /* If the structure is registered as a boxed, we can create a new instance by
873 : : * looking for a zero-args constructor and calling it; constructors don't
874 : : * really make sense for non-boxed types, since there is no memory management
875 : : * for the return value.
876 : : */
877 [ + + ]: 17748 : for (GI::AutoFunctionInfo func_info : m_info.methods()) {
878 [ + + ]: 16948 : if (func_info.is_constructor()) {
879 [ + + ]: 2230 : if (first_constructor < 0) {
880 : 542 : first_constructor = i;
881 : : first_constructor_name =
882 : 542 : gjs_intern_string_to_id(context, func_info.name());
883 [ - + ]: 542 : if (first_constructor_name.isVoid())
884 : 0 : return false;
885 : : }
886 : :
887 [ + + + + : 2230 : if (m_zero_args_constructor < 0 && func_info.n_args() == 0) {
+ + ]
888 : 14 : m_zero_args_constructor = i;
889 : : zero_args_constructor_name =
890 : 14 : gjs_intern_string_to_id(context, func_info.name());
891 [ - + ]: 14 : if (zero_args_constructor_name.isVoid())
892 : 0 : return false;
893 : : }
894 : :
895 [ + + + + ]: 4286 : if (m_default_constructor < 0 &&
896 [ + + ]: 2056 : strcmp(func_info.name(), "new") == 0) {
897 : 389 : m_default_constructor = i;
898 : 389 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
899 : 389 : m_default_constructor_name = atoms.new_();
900 : : }
901 : : }
902 : 16948 : i++;
903 [ + - ]: 16948 : }
904 : :
905 [ + + ]: 800 : if (m_default_constructor < 0) {
906 : 411 : m_default_constructor = m_zero_args_constructor;
907 : 411 : m_default_constructor_name = zero_args_constructor_name;
908 : : }
909 [ + + ]: 800 : if (m_default_constructor < 0) {
910 : 410 : m_default_constructor = first_constructor;
911 : 410 : m_default_constructor_name = first_constructor_name;
912 : : }
913 : : }
914 : :
915 : 879 : return true;
916 : : }
917 : :
918 : : /*
919 : : * BoxedPrototype::define_class:
920 : : * @in_object: Object where the constructor is stored, typically a repo object.
921 : : * @info: Introspection info for the boxed class.
922 : : *
923 : : * Define a boxed class constructor and prototype, including all the necessary
924 : : * methods and properties.
925 : : */
926 : 879 : bool BoxedPrototype::define_class(JSContext* context,
927 : : JS::HandleObject in_object,
928 : : const GI::StructInfo info) {
929 : 879 : JS::RootedObject prototype(context), unused_constructor(context);
930 : 879 : GType gtype = info.gtype();
931 : 879 : BoxedPrototype* priv = BoxedPrototype::create_class(
932 : : context, in_object, info, gtype, &unused_constructor, &prototype);
933 [ + - - + : 879 : if (!priv || !priv->define_boxed_class_fields(context, prototype))
- + ]
934 : 0 : return false;
935 : :
936 [ + + - + ]: 942 : if (gtype == G_TYPE_ERROR &&
937 [ - + ]: 942 : !JS_DefineFunction(context, prototype, "toString",
938 : : &ErrorBase::to_string, 0, GJS_MODULE_PROP_FLAGS))
939 : 0 : return false;
940 : :
941 : 879 : return true;
942 : 879 : }
943 : :
944 : : /* Helper function to make the public API more readable. The overloads are
945 : : * specified explicitly in the public API, but the implementation uses
946 : : * std::forward in order to avoid duplicating code. */
947 : : template <typename... Args>
948 : 19031 : JSObject* BoxedInstance::new_for_c_struct_impl(JSContext* cx,
949 : : const GI::StructInfo info,
950 : : void* gboxed, Args&&... args) {
951 [ - + ]: 19031 : if (gboxed == NULL)
952 : 0 : return NULL;
953 : :
954 : : gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping struct %s %p with JSObject",
955 : : info.name(), gboxed);
956 : :
957 : 19031 : JS::RootedObject obj(cx, gjs_new_object_with_generic_prototype(cx, info));
958 [ - + ]: 19031 : if (!obj)
959 : 0 : return nullptr;
960 : :
961 : 19031 : BoxedInstance* priv = BoxedInstance::new_for_js_object(cx, obj);
962 [ - + ]: 19031 : if (!priv)
963 : 0 : return nullptr;
964 : :
965 [ - + ]: 19031 : if (!priv->init_from_c_struct(cx, gboxed, std::forward<Args>(args)...))
966 : 0 : return nullptr;
967 : :
968 [ + + - + : 19031 : if (priv->gtype() == G_TYPE_ERROR && !gjs_define_error_properties(cx, obj))
- + ]
969 : 0 : return nullptr;
970 : :
971 : 19031 : return obj;
972 : 19031 : }
973 : :
974 : : /*
975 : : * BoxedInstance::new_for_c_struct:
976 : : *
977 : : * Creates a new BoxedInstance JS object from a C boxed struct pointer.
978 : : *
979 : : * There are two overloads of this method; the NoCopy overload will simply take
980 : : * the passed-in pointer but not own it, while the normal method will take a
981 : : * reference, or if the boxed type can be directly allocated, copy the memory.
982 : : */
983 : 19017 : JSObject* BoxedInstance::new_for_c_struct(JSContext* cx,
984 : : const GI::StructInfo info,
985 : : void* gboxed) {
986 : 19017 : return new_for_c_struct_impl(cx, info, gboxed);
987 : : }
988 : :
989 : 14 : JSObject* BoxedInstance::new_for_c_struct(JSContext* cx,
990 : : const GI::StructInfo info,
991 : : void* gboxed, NoCopy no_copy) {
992 : 14 : return new_for_c_struct_impl(cx, info, gboxed, no_copy);
993 : : }
994 : :
995 : : /*
996 : : * BoxedInstance::init_from_c_struct:
997 : : *
998 : : * Do the necessary initialization when creating a BoxedInstance JS object from
999 : : * a C boxed struct pointer.
1000 : : *
1001 : : * There are two overloads of this method; the NoCopy overload will simply take
1002 : : * the passed-in pointer, while the normal method will take a reference, or if
1003 : : * the boxed type can be directly allocated, copy the memory.
1004 : : */
1005 : 14 : bool BoxedInstance::init_from_c_struct(JSContext*, void* gboxed, NoCopy) {
1006 : : // We need to create a JS Boxed which references the original C struct, not
1007 : : // a copy of it. Used for G_SIGNAL_TYPE_STATIC_SCOPE.
1008 : 14 : share_ptr(gboxed);
1009 : 14 : debug_lifecycle("Boxed pointer acquired, memory not owned");
1010 : 14 : return true;
1011 : : }
1012 : :
1013 : 19017 : bool BoxedInstance::init_from_c_struct(JSContext* cx, void* gboxed) {
1014 [ + + + - : 19017 : if (gtype() != G_TYPE_NONE && g_type_is_a(gtype(), G_TYPE_BOXED)) {
+ + + + ]
1015 : 15660 : copy_boxed(gboxed);
1016 : 15660 : return true;
1017 [ + + ]: 3357 : } else if (gtype() == G_TYPE_VARIANT) {
1018 : 3332 : own_ptr(g_variant_ref_sink(static_cast<GVariant*>(gboxed)));
1019 : 3332 : debug_lifecycle("Boxed pointer created by sinking GVariant ref");
1020 : 3332 : return true;
1021 [ + - ]: 25 : } else if (get_prototype()->can_allocate_directly()) {
1022 : 25 : copy_memory(gboxed);
1023 : 25 : return true;
1024 : : }
1025 : :
1026 : 0 : gjs_throw(cx, "Can't create a Javascript object for %s; no way to copy",
1027 : : name());
1028 : 0 : return false;
1029 : : }
1030 : :
1031 : 109 : void* BoxedInstance::copy_ptr(JSContext* cx, GType gtype, void* ptr) {
1032 [ + - + + : 109 : if (g_type_is_a(gtype, G_TYPE_BOXED))
+ + ]
1033 : 107 : return g_boxed_copy(gtype, ptr);
1034 [ - + - - : 2 : if (g_type_is_a(gtype, G_TYPE_VARIANT))
+ - ]
1035 : 2 : return g_variant_ref(static_cast<GVariant*>(ptr));
1036 : :
1037 : 0 : gjs_throw(cx,
1038 : : "Can't transfer ownership of a structure type not registered as "
1039 : : "boxed");
1040 : 0 : return nullptr;
1041 : : }
|