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