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