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