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