LCOV - code coverage report
Current view: top level - gi - boxed.cpp (source / functions) Coverage Total Hit
Test: gjs-1.87.2 Code Coverage Lines: 87.7 % 486 426
Test Date: 2026-02-11 15:09:51 Functions: 94.0 % 83 78
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 79.9 % 364 291

             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*);
        

Generated by: LCOV version 2.0-1