LCOV - code coverage report
Current view: top level - gi - wrapperutils.h (source / functions) Coverage Total Hit
Test: gjs-1.89.90 Code Coverage Lines: 90.9 % 341 310
Test Date: 2026-09-10 04:38:53 Functions: 96.2 % 319 307
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 72.8 % 158 115

             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: 2018 Philip Chimento <philip.chimento@gmail.com>
       5                 :             : 
       6                 :             : #pragma once
       7                 :             : 
       8                 :             : #include <config.h>
       9                 :             : 
      10                 :             : #include <stdint.h>
      11                 :             : 
      12                 :             : #include <new>  // for operator new
      13                 :             : #include <string>
      14                 :             : #include <type_traits>
      15                 :             : 
      16                 :             : #include <girepository/girepository.h>
      17                 :             : #include <glib-object.h>
      18                 :             : #include <glib.h>
      19                 :             : 
      20                 :             : #include <js/CallArgs.h>
      21                 :             : #include <js/ComparisonOperators.h>
      22                 :             : #include <js/ErrorReport.h>  // for JSEXN_TYPEERR
      23                 :             : #include <js/Id.h>
      24                 :             : #include <js/MemoryFunctions.h>
      25                 :             : #include <js/Object.h>
      26                 :             : #include <js/PropertyAndElement.h>  // for JS_DefineFunctionById
      27                 :             : #include <js/RootingAPI.h>
      28                 :             : #include <js/TypeDecls.h>
      29                 :             : #include <js/Value.h>
      30                 :             : #include <jsapi.h>  // for JS_GetPrototype
      31                 :             : #include <mozilla/Maybe.h>
      32                 :             : 
      33                 :             : #include "gi/arg-inl.h"
      34                 :             : #include "gi/cwrapper.h"
      35                 :             : #include "gi/info.h"
      36                 :             : #include "gjs/atoms.h"
      37                 :             : #include "gjs/auto.h"
      38                 :             : #include "gjs/context-private.h"
      39                 :             : #include "gjs/jsapi-class.h"
      40                 :             : #include "gjs/jsapi-util.h"
      41                 :             : #include "gjs/macros.h"
      42                 :             : #include "gjs/mem-private.h"
      43                 :             : #include "gjs/profiler-private.h"
      44                 :             : #include "util/log.h"
      45                 :             : 
      46                 :             : struct JSFunctionSpec;
      47                 :             : struct JSPropertySpec;
      48                 :             : class JSTracer;
      49                 :             : 
      50                 :             : GJS_JSAPI_RETURN_CONVENTION
      51                 :             : bool gjs_wrapper_to_string_func(JSContext*, JSObject* this_obj,
      52                 :             :                                 const char* objtype,
      53                 :             :                                 const mozilla::Maybe<const GI::BaseInfo>&,
      54                 :             :                                 GType, const void* native_address,
      55                 :             :                                 JS::MutableHandleValue rval);
      56                 :             : 
      57                 :             : // Needed because some of the templates don't have Maybe as their info() type
      58                 :             : GJS_JSAPI_RETURN_CONVENTION
      59                 :          55 : static inline bool gjs_wrapper_to_string_func(JSContext* cx, JSObject* this_obj,
      60                 :             :                                               const char* objtype,
      61                 :             :                                               const GI::BaseInfo& info,
      62                 :             :                                               GType gtype,
      63                 :             :                                               const void* native_address,
      64                 :             :                                               JS::MutableHandleValue ret) {
      65                 :          55 :     return gjs_wrapper_to_string_func(
      66                 :         110 :         cx, this_obj, objtype, mozilla::Some(info), gtype, native_address, ret);
      67                 :             : }
      68                 :             : 
      69                 :             : bool gjs_wrapper_throw_nonexistent_field(JSContext*, GType,
      70                 :             :                                          const char* field_name);
      71                 :             : 
      72                 :             : bool gjs_wrapper_throw_readonly_field(JSContext*, GType,
      73                 :             :                                       const char* field_name);
      74                 :             : 
      75                 :             : struct GjsTypecheckNoThrow {};
      76                 :             : 
      77                 :             : // Some types of introspected wrapper permit creating a new type from JS (e.g.,
      78                 :             : // objects, interfaces.) These JS-created types do not have introspection info
      79                 :             : // and so their GIWrapperPrototype::info() methods return Maybe<const FooInfo>.
      80                 :             : // Others do not permit creating a new type from JS (e.g., enums, boxeds.) These
      81                 :             : // have GIWrapperPrototype::info() methods that return const FooInfo directly.
      82                 :             : // Sometimes we need to have different code for the two cases.
      83                 :             : template <typename>
      84                 :             : struct is_maybe : std::false_type {};
      85                 :             : template <typename T>
      86                 :             : struct is_maybe<mozilla::Maybe<T>> : std::true_type {};
      87                 :             : 
      88                 :             : // Add associated memory for `prototype` according to the `class_size`
      89                 :             : // associated with `gtype`.
      90                 :        2420 : static inline void add_prototype_associated_memory(GType gtype, JS::MutableHandleObject prototype) {
      91         [ +  + ]:        2420 :     if (!G_TYPE_IS_CLASSED(gtype))
      92                 :        1435 :         return;
      93                 :             : 
      94                 :             :     GTypeQuery query;
      95                 :         985 :     g_type_query(gtype, &query);
      96                 :         985 :     g_assert(query.type);
      97                 :             : 
      98                 :             :     // Associate the size of the class structure with the prototype
      99                 :         985 :     JS::AddAssociatedMemory(prototype, query.class_size,
     100                 :             :                             MemoryUse::GObjectClassStruct);
     101                 :             : }
     102                 :             : 
     103                 :             : // Remove associated memory for `prototype` according to the `class_size`
     104                 :             : // associated with `gtype`.
     105                 :        2397 : static inline void remove_prototype_associated_memory(GType gtype, JSObject* obj) {
     106         [ +  + ]:        2397 :     if (!G_TYPE_IS_CLASSED(gtype))
     107                 :        1415 :         return;
     108                 :             : 
     109                 :             :     GTypeQuery query;
     110                 :         982 :     g_type_query(gtype, &query);
     111                 :         982 :     g_assert(query.type);
     112                 :             : 
     113                 :             :     // Remove the memory associated with the prototype
     114                 :         982 :     JS::RemoveAssociatedMemory(obj, query.class_size,
     115                 :             :                                MemoryUse::GObjectClassStruct);
     116                 :             : }
     117                 :             : 
     118                 :             : /**
     119                 :             :  * gjs_define_static_methods:
     120                 :             :  *
     121                 :             :  * Defines all static methods from @info on @constructor. Also includes class
     122                 :             :  * methods for GI::ObjectInfo, and interface methods for GI::InterfaceInfo.
     123                 :             :  */
     124                 :             : template <GI::InfoTag TAG>
     125                 :             : GJS_JSAPI_RETURN_CONVENTION
     126                 :             : bool gjs_define_static_methods(JSContext*, JS::HandleObject constructor, GType,
     127                 :             :                                const GI::UnownedInfo<TAG>&);
     128                 :             : 
     129                 :             : template <GI::InfoTag TAG>
     130                 :             : GJS_JSAPI_RETURN_CONVENTION
     131                 :        1225 : inline bool gjs_define_static_methods(JSContext* cx,
     132                 :             :                                       JS::HandleObject constructor, GType gtype,
     133                 :             :                                       const GI::OwnedInfo<TAG>& info) {
     134                 :        2450 :     return gjs_define_static_methods(cx, constructor, gtype,
     135                 :        1225 :                                      GI::UnownedInfo<TAG>{info});
     136                 :             : }
     137                 :             : 
     138                 :             : /**
     139                 :             :  * GIWrapperBase:
     140                 :             :  *
     141                 :             :  * In most different kinds of C pointer that we expose to JS through GObject
     142                 :             :  * Introspection (boxed, fundamental, gerror, interface, object, union), we want
     143                 :             :  * to have different private structures for the prototype JS object and the JS
     144                 :             :  * objects representing instances. Both should inherit from a base structure for
     145                 :             :  * their common functionality.
     146                 :             :  *
     147                 :             :  * This is mainly for memory reasons. We need to keep track of the GIBaseInfo*
     148                 :             :  * and GType for each dynamically created class, but we don't need to duplicate
     149                 :             :  * that information (16 bytes on x64 systems) for every instance. In some cases
     150                 :             :  * there can also be other information that's only used on the prototype.
     151                 :             :  *
     152                 :             :  * So, to conserve memory, we split the private structures in FooInstance and
     153                 :             :  * FooPrototype, which both inherit from FooBase. All the repeated code in these
     154                 :             :  * structures lives in GIWrapperBase, GIWrapperPrototype, and GIWrapperInstance.
     155                 :             :  *
     156                 :             :  * The m_proto member needs a bit of explanation, as this is used to implement
     157                 :             :  * an unusual form of polymorphism. Sadly, we cannot have virtual methods in
     158                 :             :  * FooBase, because SpiderMonkey can be compiled with or without RTTI, so we
     159                 :             :  * cannot count on being able to cast FooBase to FooInstance or FooPrototype
     160                 :             :  * with dynamic_cast<>, and the vtable would take up just as much space anyway.
     161                 :             :  * Instead, we use the CRTP technique, and distinguish between FooInstance and
     162                 :             :  * FooPrototype using the m_proto member, which will be null for FooPrototype.
     163                 :             :  * Instead of casting, we have the to_prototype() and to_instance() methods
     164                 :             :  * which will give you a pointer if the FooBase is of the correct type (and
     165                 :             :  * assert if not.)
     166                 :             :  *
     167                 :             :  * The CRTP requires inheriting classes to declare themselves friends of the
     168                 :             :  * parent class, so that the parent class can call their private methods.
     169                 :             :  *
     170                 :             :  * For more information about the CRTP, the Wikipedia article is informative:
     171                 :             :  * https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
     172                 :             :  */
     173                 :             : template <class Base, class Prototype, class Instance>
     174                 :             : class GIWrapperBase : public CWrapperPointerOps<Base> {
     175                 :             :  protected:
     176                 :             :     // nullptr if this Base is a Prototype; points to the corresponding
     177                 :             :     // Prototype if this Base is an Instance.
     178                 :             :     Prototype* m_proto;
     179                 :             : 
     180                 :       27267 :     explicit GIWrapperBase(Prototype* proto = nullptr) : m_proto(proto) {}
     181                 :             : 
     182                 :             :     // These three can be overridden in subclasses. See define_jsclass().
     183                 :             :     static constexpr JSPropertySpec* proto_properties = nullptr;
     184                 :             :     static constexpr JSPropertySpec* static_properties = nullptr;
     185                 :             :     static constexpr JSFunctionSpec* proto_methods = nullptr;
     186                 :             :     static constexpr JSFunctionSpec* static_methods = nullptr;
     187                 :             : 
     188                 :             :  public:
     189                 :             :     // Methods implementing our CRTP polymorphism scheme follow below. We don't
     190                 :             :     // use standard C++ polymorphism because that would occupy another 8 bytes
     191                 :             :     // for a vtable.
     192                 :             : 
     193                 :             :     /**
     194                 :             :      * GIWrapperBase::is_prototype:
     195                 :             :      *
     196                 :             :      * Returns whether this Base is actually a Prototype (true) or an Instance
     197                 :             :      * (false).
     198                 :             :      */
     199                 :      785634 :     [[nodiscard]] bool is_prototype() const { return !m_proto; }
     200                 :             : 
     201                 :             :     /**
     202                 :             :      * GIWrapperBase::to_prototype:
     203                 :             :      * GIWrapperBase::to_instance:
     204                 :             :      *
     205                 :             :      * These methods assert that this Base is of the correct subclass. If you
     206                 :             :      * don't want to assert, then either check beforehand with is_prototype(),
     207                 :             :      * or use get_prototype().
     208                 :             :      */
     209                 :             :     [[nodiscard]]
     210                 :       62598 :     Prototype* to_prototype() {
     211                 :       62598 :         g_assert(is_prototype());
     212                 :       62598 :         return reinterpret_cast<Prototype*>(this);
     213                 :             :     }
     214                 :             :     [[nodiscard]]
     215                 :       13776 :     const Prototype* to_prototype() const {
     216                 :       13776 :         g_assert(is_prototype());
     217                 :       13776 :         return reinterpret_cast<const Prototype*>(this);
     218                 :             :     }
     219                 :             :     [[nodiscard]]
     220                 :      158156 :     Instance* to_instance() {
     221                 :      158156 :         g_assert(!is_prototype());
     222                 :      158156 :         return reinterpret_cast<Instance*>(this);
     223                 :             :     }
     224                 :             :     [[nodiscard]]
     225                 :        7894 :     const Instance* to_instance() const {
     226                 :        7894 :         g_assert(!is_prototype());
     227                 :        7894 :         return reinterpret_cast<const Instance*>(this);
     228                 :             :     }
     229                 :             : 
     230                 :             :     /**
     231                 :             :      * GIWrapperBase::get_prototype:
     232                 :             :      *
     233                 :             :      * get_prototype() doesn't assert. If you call it on a Prototype, it returns
     234                 :             :      * you the same object cast to the correct type; if you call it on an
     235                 :             :      * Instance, it returns you the Prototype belonging to the corresponding JS
     236                 :             :      * prototype.
     237                 :             :      */
     238                 :             :     [[nodiscard]] [[gnu::const]]
     239                 :        2441 :     Prototype* get_prototype() {
     240         [ -  + ]:        2441 :         return is_prototype() ? to_prototype() : m_proto;
     241                 :             :     }
     242                 :             :     [[nodiscard]]
     243                 :      264824 :     const Prototype* get_prototype() const {
     244         [ +  + ]:      264824 :         return is_prototype() ? to_prototype() : m_proto;
     245                 :             :     }
     246                 :             : 
     247                 :             :     // Accessors for Prototype members follow below. Both Instance and Prototype
     248                 :             :     // should be able to access the GIFooInfo and the GType, but for space
     249                 :             :     // reasons we store them only on Prototype.
     250                 :             : 
     251                 :       12568 :     [[nodiscard]] auto info() const { return get_prototype()->info(); }
     252                 :      252206 :     [[nodiscard]] GType gtype() const { return get_prototype()->gtype(); }
     253                 :             : 
     254                 :             :     // The next three methods are operations derived from the GIFooInfo.
     255                 :             : 
     256                 :        5081 :     [[nodiscard]] const char* type_name() const { return g_type_name(gtype()); }
     257                 :             :     [[nodiscard]]
     258                 :        2214 :     const char* ns() const {
     259                 :             :         if constexpr (Prototype::may_not_have_info) {
     260                 :        1050 :             const auto i = info();
     261         [ +  - ]:        1050 :             return i ? i->ns() : "";
     262                 :        1050 :         } else {
     263                 :        1164 :             return info().ns();
     264                 :             :         }
     265                 :             :     }
     266                 :             :     [[nodiscard]]
     267                 :        2428 :     const char* name() const {
     268                 :             :         if constexpr (Prototype::may_not_have_info) {
     269                 :        1259 :             const auto i = info();
     270         [ +  + ]:        1259 :             return i ? i->name() : type_name();
     271                 :        1259 :         } else {
     272                 :        1169 :             return info().name();
     273                 :             :         }
     274                 :             :     }
     275                 :             : 
     276                 :             :     // This exists for genericity, but use the default formatters for info() if
     277                 :             :     // info() is always present (i.e. prototypes can't be JS-only)
     278                 :             :     [[nodiscard]]
     279                 :        2363 :     std::string format_name() const {
     280                 :             :         if constexpr (Prototype::may_not_have_info) {
     281                 :        1197 :             const auto i = info();
     282   [ +  +  +  + ]:        2495 :             return i ? i->display_string() : type_name();
     283                 :        1197 :         } else {
     284                 :        1166 :             return info().display_string();
     285                 :             :         }
     286                 :             :     }
     287                 :             : 
     288                 :             :  private:
     289                 :             :     // Accessor for Instance member. Used only in debug methods and toString().
     290                 :             :     [[nodiscard]]
     291                 :          55 :     const void* ptr_addr() const {
     292         [ -  + ]:          55 :         return is_prototype() ? nullptr : to_instance()->ptr();
     293                 :             :     }
     294                 :             : 
     295                 :             :     // Debug methods
     296                 :             : 
     297                 :             :  protected:
     298                 :       24261 :     void debug_lifecycle(const char* message GJS_USED_VERBOSE_LIFECYCLE) const {
     299                 :             :         gjs_debug_lifecycle(Base::DEBUG_TOPIC,
     300                 :             :                             "[{}: {} pointer {} - {} ({})] {}",
     301                 :             :                             static_cast<const void*>(this), Base::DEBUG_TAG,
     302                 :             :                             ptr_addr(), format_name(), type_name(), message);
     303                 :       24261 :     }
     304                 :       64200 :     void debug_lifecycle(const void* obj GJS_USED_VERBOSE_LIFECYCLE,
     305                 :             :                          const char* message GJS_USED_VERBOSE_LIFECYCLE) const {
     306                 :             :         gjs_debug_lifecycle(Base::DEBUG_TOPIC,
     307                 :             :                             "[{}: {} pointer {} - JS wrapper {} - {} ({})] {}",
     308                 :             :                             static_cast<const void*>(this), Base::DEBUG_TAG,
     309                 :             :                             ptr_addr(), obj, format_name(), type_name(),
     310                 :             :                             message);
     311                 :       64200 :     }
     312                 :        1161 :     void debug_jsprop(const char* message GJS_USED_VERBOSE_PROPS,
     313                 :             :                       const char* id GJS_USED_VERBOSE_PROPS,
     314                 :             :                       const void* obj GJS_USED_VERBOSE_PROPS) const {
     315                 :             :         gjs_debug_jsprop(
     316                 :             :             Base::DEBUG_TOPIC,
     317                 :             :             "[{}: {} pointer {} - JS wrapper {} - {} ({})] {} '{}'",
     318                 :             :             static_cast<const void*>(this), Base::DEBUG_TAG, ptr_addr(), obj,
     319                 :             :             format_name(), type_name(), message, id);
     320                 :        1161 :     }
     321                 :       94096 :     void debug_jsprop(const char* message, jsid id, const void* obj) const {
     322                 :             :         if constexpr (GJS_VERBOSE_ENABLE_PROPS)
     323                 :             :             debug_jsprop(message, gjs_debug_id(id).c_str(), obj);
     324                 :       94096 :     }
     325                 :             :     void debug_jsprop(const char* message, JSString* id,
     326                 :             :                       const void* obj) const {
     327                 :             :         if constexpr (GJS_VERBOSE_ENABLE_PROPS)
     328                 :             :             debug_jsprop(message, gjs_debug_string(id).c_str(), obj);
     329                 :             :     }
     330                 :        5716 :     static void debug_jsprop_static(const char* message GJS_USED_VERBOSE_PROPS,
     331                 :             :                                     jsid id GJS_USED_VERBOSE_PROPS,
     332                 :             :                                     const void* obj GJS_USED_VERBOSE_PROPS) {
     333                 :             :         gjs_debug_jsprop(Base::DEBUG_TOPIC,
     334                 :             :                          "[{} JS wrapper {}] {} '{}', no instance associated",
     335                 :             :                          Base::DEBUG_TAG, obj, message, id);
     336                 :        5716 :     }
     337                 :             : 
     338                 :             :     // JS class operations, used only in the JSClassOps struct
     339                 :             : 
     340                 :             :     /**
     341                 :             :      * GIWrapperBase::new_enumerate:
     342                 :             :      *
     343                 :             :      * Include this in the Base::klass vtable if the class should support lazy
     344                 :             :      * enumeration (listing all of the lazy properties that can be defined in
     345                 :             :      * resolve().) If it is included, then there must be a corresponding
     346                 :             :      * Prototype::new_enumerate_impl() method.
     347                 :             :      */
     348                 :             :     GJS_JSAPI_RETURN_CONVENTION
     349                 :         262 :     static bool new_enumerate(JSContext* cx, JS::HandleObject obj,
     350                 :             :                               JS::MutableHandleIdVector properties,
     351                 :             :                               bool only_enumerable) {
     352                 :         262 :         Base* priv = Base::for_js(cx, obj);
     353                 :             : 
     354                 :         262 :         priv->debug_jsprop("Enumerate hook", "(all)", obj);
     355                 :             : 
     356         [ +  + ]:         262 :         if (!priv->is_prototype()) {
     357                 :             :             // Instances don't have any methods or properties. Spidermonkey will
     358                 :             :             // call new_enumerate on the prototype next.
     359                 :         154 :             return true;
     360                 :             :         }
     361                 :             : 
     362                 :         108 :         return priv->to_prototype()->new_enumerate_impl(cx, obj, properties,
     363                 :         108 :                                                         only_enumerable);
     364                 :             :     }
     365                 :             : 
     366                 :             :  private:
     367                 :             :     /**
     368                 :             :      * GIWrapperBase::id_is_never_lazy:
     369                 :             :      *
     370                 :             :      * Returns true if @id should never be treated as a lazy property. The
     371                 :             :      * JSResolveOp for an instance is called for every property not defined,
     372                 :             :      * even if it's one of the functions or properties we're adding to the
     373                 :             :      * prototype manually, such as toString().
     374                 :             :      *
     375                 :             :      * Override this and chain up if you have Base::resolve in your JSClassOps
     376                 :             :      * vtable, and have overridden Base::proto_properties or
     377                 :             :      * Base::proto_methods. You should add any identifiers in the override that
     378                 :             :      * you have added to the prototype object.
     379                 :             :      */
     380                 :             :     [[nodiscard]]
     381                 :       29971 :     static bool id_is_never_lazy(jsid id, const GjsAtoms& atoms) {
     382                 :             :         // toString() is always defined somewhere on the prototype chain, so it
     383                 :             :         // is never a lazy property.
     384                 :       29971 :         return id == atoms.to_string();
     385                 :             :     }
     386                 :             : 
     387                 :             :  protected:
     388                 :             :     /**
     389                 :             :      * GIWrapperBase::resolve_prototype:
     390                 :             :      */
     391                 :             :     [[nodiscard]]
     392                 :        4155 :     static Prototype* resolve_prototype(JSContext* cx, JS::HandleObject proto) {
     393         [ +  + ]:        4155 :         if (JS::GetClass(proto) == &Base::klass)
     394                 :        3641 :             return Prototype::for_js(cx, proto);
     395                 :             : 
     396                 :         514 :         const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
     397                 :             : 
     398                 :         514 :         bool has_property = false;
     399         [ -  + ]:         514 :         if (!JS_HasOwnPropertyById(cx, proto, atoms.gobject_prototype(),
     400                 :             :                                    &has_property))
     401                 :           0 :             return nullptr;
     402                 :             : 
     403         [ +  + ]:         514 :         if (!has_property) {
     404                 :           2 :             gjs_throw(cx, "Tried to construct an object without a GType");
     405                 :           2 :             return nullptr;
     406                 :             :         }
     407                 :             : 
     408                 :         512 :         JS::RootedValue gobject_proto(cx);
     409         [ -  + ]:         512 :         if (!JS_GetPropertyById(cx, proto, atoms.gobject_prototype(),
     410                 :         512 :                                 &gobject_proto))
     411                 :           0 :             return nullptr;
     412                 :             : 
     413         [ -  + ]:         512 :         if (!gobject_proto.isObject()) {
     414                 :           0 :             gjs_throw(cx, "Tried to construct an object without a GType");
     415                 :           0 :             return nullptr;
     416                 :             :         }
     417                 :             : 
     418                 :         512 :         JS::RootedObject obj(cx, &gobject_proto.toObject());
     419                 :             :         // gobject_prototype is an internal symbol so we can assert that it is
     420                 :             :         // only assigned to objects with &Base::klass definitions
     421                 :         512 :         g_assert(JS::GetClass(obj) == &Base::klass);
     422                 :             : 
     423                 :         512 :         return Prototype::for_js(cx, obj);
     424                 :         512 :     }
     425                 :             : 
     426                 :             :     /**
     427                 :             :      * GIWrapperBase::resolve:
     428                 :             :      *
     429                 :             :      * Include this in the Base::klass vtable if the class should support lazy
     430                 :             :      * properties. If it is included, then there must be a corresponding
     431                 :             :      * Prototype::resolve_impl() method.
     432                 :             :      *
     433                 :             :      * The *resolved out parameter, on success, should be false to indicate that
     434                 :             :      * id was not resolved; and true if id was resolved.
     435                 :             :      */
     436                 :             :     GJS_JSAPI_RETURN_CONVENTION
     437                 :       95224 :     static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
     438                 :             :                         bool* resolved) {
     439                 :       95224 :         Base* priv = Base::for_js(cx, obj);
     440                 :             : 
     441         [ +  + ]:       95224 :         if (!priv) {
     442                 :             :             // This catches a case in Object where the private struct isn't set
     443                 :             :             // until the initializer is called, so just defer to prototype
     444                 :             :             // chains in this case.
     445                 :             :             //
     446                 :             :             // This isn't too bad: either you get undefined if the field doesn't
     447                 :             :             // exist on any of the prototype chains, or whatever code will run
     448                 :             :             // afterwards will fail because of the "!priv" check there.
     449                 :        3572 :             debug_jsprop_static("Resolve hook", id, obj);
     450                 :        3572 :             *resolved = false;
     451                 :        3572 :             return true;
     452                 :             :         }
     453                 :             : 
     454                 :       91652 :         priv->debug_jsprop("Resolve hook", id, obj);
     455                 :             : 
     456         [ +  + ]:       91652 :         if (!priv->is_prototype()) {
     457                 :             :             // We are an instance, not a prototype, so look for per-instance
     458                 :             :             // props that we want to define on the JSObject. Generally we do not
     459                 :             :             // want to cache these in JS, we want to always pull them from the C
     460                 :             :             // object, or JS would not see any changes made from C. So we use
     461                 :             :             // the property accessors, not this resolve hook.
     462                 :       61681 :             *resolved = false;
     463                 :       61681 :             return true;
     464                 :             :         }
     465                 :             : 
     466                 :       29971 :         const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
     467         [ +  + ]:       29971 :         if (id_is_never_lazy(id, atoms)) {
     468                 :        3702 :             *resolved = false;
     469                 :        3702 :             return true;
     470                 :             :         }
     471                 :             : 
     472                 :       26269 :         return priv->to_prototype()->resolve_impl(cx, obj, id, resolved);
     473                 :             :     }
     474                 :             : 
     475                 :             :     /**
     476                 :             :      * GIWrapperBase::finalize:
     477                 :             :      *
     478                 :             :      * This should always be included in the Base::klass vtable. The destructors
     479                 :             :      * of Prototype and Instance will be called in the finalize hook. It is not
     480                 :             :      * necessary to include a finalize_impl() function in Prototype or Instance.
     481                 :             :      * Any needed finalization should be done in ~Prototype() and ~Instance().
     482                 :             :      */
     483                 :       27242 :     static void finalize(JS::GCContext* gcx, JSObject* obj) {
     484                 :       27242 :         Base* priv = Base::for_js_nocheck(obj);
     485         [ +  + ]:       27242 :         if (!priv)
     486                 :           2 :             return;  // construction didn't finish
     487                 :             : 
     488                 :             :         // Call only GIWrapperBase's original method here, not any overrides;
     489                 :             :         // e.g., we don't want to deal with a read barrier in ObjectInstance.
     490                 :       27240 :         static_cast<GIWrapperBase*>(priv)->debug_lifecycle(obj, "Finalize");
     491                 :             : 
     492         [ +  + ]:       27240 :         if (priv->is_prototype()) {
     493                 :        2397 :             GType gtype = priv->to_prototype()->gtype();
     494                 :        2397 :             remove_prototype_associated_memory(gtype, obj);
     495                 :        2397 :             priv->to_prototype()->finalize_impl(gcx, obj);
     496                 :             :         } else {
     497                 :       24843 :             priv->to_instance()->finalize_impl(gcx, obj);
     498                 :             :         }
     499                 :             : 
     500                 :       27240 :         Base::unset_private(obj);
     501                 :             :     }
     502                 :             : 
     503                 :             :     /**
     504                 :             :      * GIWrapperBase::trace:
     505                 :             :      *
     506                 :             :      * This should be included in the Base::klass vtable if any of the Base,
     507                 :             :      * Prototype or Instance structures contain any members that the JS garbage
     508                 :             :      * collector must trace. Each struct containing such members must override
     509                 :             :      * GIWrapperBase::trace_impl(), GIWrapperPrototype::trace_impl(), and/or
     510                 :             :      * GIWrapperInstance::trace_impl() in order to perform the trace.
     511                 :             :      */
     512                 :        7311 :     static void trace(JSTracer* trc, JSObject* obj) {
     513                 :        7311 :         Base* priv = Base::for_js_nocheck(obj);
     514         [ -  + ]:        7311 :         if (!priv)
     515                 :           0 :             return;
     516                 :             : 
     517                 :             :         // Don't log in trace(). That would overrun even the most verbose logs.
     518                 :             : 
     519         [ +  + ]:        7311 :         if (priv->is_prototype())
     520                 :        6424 :             priv->to_prototype()->trace_impl(trc);
     521                 :             :         else
     522                 :         887 :             priv->to_instance()->trace_impl(trc);
     523                 :             : 
     524                 :        7311 :         priv->trace_impl(trc);
     525                 :             :     }
     526                 :             : 
     527                 :             :     /**
     528                 :             :      * GIWrapperBase::trace_impl:
     529                 :             :      * Override if necessary. See trace().
     530                 :             :      */
     531                 :        7311 :     void trace_impl(JSTracer*) {}
     532                 :             : 
     533                 :             :     // JSNative methods
     534                 :             : 
     535                 :             :     /**
     536                 :             :      * GIWrapperBase::constructor:
     537                 :             :      *
     538                 :             :      * C++ implementation of the JS constructor passed to JS_InitClass(). Only
     539                 :             :      * called on instances, never on prototypes. This method contains the
     540                 :             :      * functionality common to all GI wrapper classes. There must be a
     541                 :             :      * corresponding Instance::constructor_impl method containing the rest of
     542                 :             :      * the functionality.
     543                 :             :      */
     544                 :             :     GJS_JSAPI_RETURN_CONVENTION
     545                 :        3325 :     static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
     546                 :        3325 :         JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     547                 :             : 
     548         [ +  + ]:        3325 :         if (!args.isConstructing()) {
     549                 :           1 :             gjs_throw_constructor_error(cx);
     550                 :           1 :             return false;
     551                 :             :         }
     552                 :        6648 :         JS::RootedObject obj(
     553                 :        3324 :             cx, JS_NewObjectForConstructor(cx, &Base::klass, args));
     554         [ -  + ]:        3324 :         if (!obj)
     555                 :           0 :             return false;
     556                 :             : 
     557                 :        3324 :         JS::RootedObject proto(cx);
     558         [ -  + ]:        3324 :         if (!JS_GetPrototype(cx, obj, &proto))
     559                 :           0 :             return false;
     560                 :             : 
     561                 :        3324 :         Prototype* prototype = resolve_prototype(cx, proto);
     562         [ +  + ]:        3324 :         if (!prototype)
     563                 :           2 :             return false;
     564                 :             : 
     565                 :        3322 :         args.rval().setUndefined();
     566                 :             : 
     567                 :        3322 :         Instance* priv = Instance::new_for_js_object(prototype, obj);
     568                 :             : 
     569                 :             :         {
     570                 :        3322 :             std::string full_name{
     571   [ -  +  +  - ]:        6644 :                 GJS_PROFILER_DYNAMIC_STRING(cx, priv->format_name())};
     572                 :        3322 :             AutoProfilerLabel label{cx, "constructor", full_name};
     573                 :             : 
     574         [ +  + ]:        3322 :             if (!priv->constructor_impl(cx, obj, args))
     575                 :          47 :                 return false;
     576   [ +  +  +  + ]:        6644 :         }
     577                 :             : 
     578                 :        3275 :         static_cast<GIWrapperBase*>(priv)->debug_lifecycle(obj,
     579                 :             :                                                            "JSObject created");
     580                 :             :         gjs_debug_lifecycle(Base::DEBUG_TOPIC, "m_proto is {}",
     581                 :             :                             static_cast<void*>(priv->get_prototype()));
     582                 :             : 
     583                 :             :         // We may need to return a value different from obj (for example because
     584                 :             :         // we delegate to another constructor)
     585         [ +  + ]:        3275 :         if (args.rval().isUndefined())
     586                 :         778 :             args.rval().setObject(*obj);
     587                 :        3275 :         return true;
     588                 :        3324 :     }
     589                 :             : 
     590                 :             :     /**
     591                 :             :      * GIWrapperBase::to_string:
     592                 :             :      *
     593                 :             :      * JSNative method connected to the toString() method in JS.
     594                 :             :      */
     595                 :             :     GJS_JSAPI_RETURN_CONVENTION
     596                 :          55 :     static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp) {
     597   [ -  +  -  + ]:          55 :         GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, Base, priv);
     598                 :         110 :         return gjs_wrapper_to_string_func(cx, obj, Base::DEBUG_TAG,
     599                 :         110 :                                           priv->info(), priv->gtype(),
     600                 :          55 :                                           priv->ptr_addr(), args.rval());
     601                 :          55 :     }
     602                 :             : 
     603                 :             :     // Helper methods
     604                 :             : 
     605                 :             :  public:
     606                 :             :     /**
     607                 :             :      * GIWrapperBase::check_is_instance:
     608                 :             :      * @for_what: string used in the exception message if an exception is thrown
     609                 :             :      *
     610                 :             :      * Used in JSNative methods to ensure the passed-in JS object is an instance
     611                 :             :      * and not the prototype. Throws a JS exception if the prototype is passed
     612                 :             :      * in.
     613                 :             :      */
     614                 :             :     GJS_JSAPI_RETURN_CONVENTION
     615                 :      125150 :     bool check_is_instance(JSContext* cx, const char* for_what) const {
     616         [ +  - ]:      125150 :         if (!is_prototype())
     617                 :      125150 :             return true;
     618                 :           0 :         gjs_throw(cx, "Can't {} on {}.prototype; only on instances", for_what,
     619                 :           0 :                   format_name());
     620                 :           0 :         return false;
     621                 :             :     }
     622                 :             : 
     623                 :             :     /**
     624                 :             :      * GIWrapperBase::to_c_ptr:
     625                 :             :      *
     626                 :             :      * Returns the underlying C pointer of the wrapped object, or throws a JS
     627                 :             :      * exception if that is not possible (for example, the passed-in JS object
     628                 :             :      * is the prototype.)
     629                 :             :      *
     630                 :             :      * Includes a JS typecheck (but without any extra typecheck of the GType or
     631                 :             :      * introspection info that you would get from GIWrapperBase::typecheck(), so
     632                 :             :      * if you want that you still have to do the typecheck before calling this
     633                 :             :      * method.)
     634                 :             :      */
     635                 :             :     template <typename T = void>
     636                 :             :     GJS_JSAPI_RETURN_CONVENTION
     637                 :       58945 :     static T* to_c_ptr(JSContext* cx, JS::HandleObject obj) {
     638                 :             :         Base* priv;
     639   [ +  -  -  + ]:      117890 :         if (!Base::for_js_typecheck(cx, obj, &priv) ||
     640         [ -  + ]:       58945 :             !priv->check_is_instance(cx, "get a C pointer"))
     641                 :           0 :             return nullptr;
     642                 :             : 
     643                 :       58945 :         return static_cast<T*>(priv->to_instance()->ptr());
     644                 :             :     }
     645                 :             : 
     646                 :             :     /**
     647                 :             :      * GIWrapperBase::transfer_to_gi_argument:
     648                 :             :      * @arg: #GIArgument to fill with the value from @obj
     649                 :             :      * @transfer_direction: Either %GI_DIRECTION_IN or %GI_DIRECTION_OUT
     650                 :             :      * @transfer_ownership: #GITransfer value specifying whether @arg should
     651                 :             :      *   copy or acquire a reference to the value or not
     652                 :             :      * @expected_gtype: #GType to perform a typecheck with
     653                 :             :      * @expected_info: Introspection info to perform a typecheck with
     654                 :             :      *
     655                 :             :      * Prepares @arg for passing the value from @obj into C code. It will get a
     656                 :             :      * C pointer from @obj and assign it to @arg's pointer field, taking a
     657                 :             :      * reference with GIWrapperInstance::copy_ptr() if @transfer_direction and
     658                 :             :      * @transfer_ownership indicate that it should.
     659                 :             :      *
     660                 :             :      * Includes a typecheck using GIWrapperBase::typecheck(), to which
     661                 :             :      * @expected_gtype and @expected_info are passed.
     662                 :             :      *
     663                 :             :      * If returning false, then @arg's pointer field is null.
     664                 :             :      */
     665                 :             :     GJS_JSAPI_RETURN_CONVENTION
     666                 :       58312 :     static bool transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj,
     667                 :             :                                         GIArgument* arg,
     668                 :             :                                         GIDirection transfer_direction,
     669                 :             :                                         GITransfer transfer_ownership,
     670                 :             :                                         GType expected_gtype) {
     671                 :       58312 :         g_assert(transfer_direction != GI_DIRECTION_INOUT &&
     672                 :             :                  "transfer_to_gi_argument() must choose between in or out");
     673                 :             : 
     674   [ +  +  +  + ]:      116604 :         if (expected_gtype != G_TYPE_NONE &&
     675         [ +  + ]:       58292 :             !Base::typecheck(cx, obj, expected_gtype)) {
     676                 :           4 :             gjs_arg_unset(arg);
     677                 :           4 :             return false;
     678                 :             :         }
     679                 :             : 
     680                 :       58308 :         gjs_arg_set(arg, Base::to_c_ptr(cx, obj));
     681         [ -  + ]:       58308 :         if (!gjs_arg_get<void*>(arg))
     682                 :           0 :             return false;
     683                 :             : 
     684   [ +  -  +  + ]:       58308 :         if ((transfer_direction == GI_DIRECTION_IN &&
     685         [ -  + ]:       58184 :              transfer_ownership != GI_TRANSFER_NOTHING) ||
     686         [ #  # ]:           0 :             (transfer_direction == GI_DIRECTION_OUT &&
     687                 :             :              transfer_ownership == GI_TRANSFER_EVERYTHING)) {
     688                 :         124 :             gjs_arg_set(arg, Instance::copy_ptr(cx, expected_gtype,
     689                 :             :                                                 gjs_arg_get<void*>(arg)));
     690         [ -  + ]:         124 :             if (!gjs_arg_get<void*>(arg))
     691                 :           0 :                 return false;
     692                 :             :         }
     693                 :             : 
     694                 :       58308 :         return true;
     695                 :             :     }
     696                 :             : 
     697                 :             :     // Public typecheck API
     698                 :             : 
     699                 :             :     /**
     700                 :             :      * GIWrapperBase::typecheck:
     701                 :             :      * @expected_info: (nullable): GI info to check
     702                 :             :      * @expected_type: (nullable): GType to check
     703                 :             :      *
     704                 :             :      * Checks not only that the JS object is of the correct JSClass (like
     705                 :             :      * CWrapperPointerOps::typecheck() does); but also that the object is an
     706                 :             :      * instance, not the prototype; and that the instance's wrapped pointer is
     707                 :             :      * of the correct GType or GI info.
     708                 :             :      *
     709                 :             :      * The overload with a GjsTypecheckNoThrow parameter will not throw a JS
     710                 :             :      * exception if the prototype is passed in or the typecheck fails.
     711                 :             :      */
     712                 :             :     GJS_JSAPI_RETURN_CONVENTION
     713                 :         440 :     static bool typecheck(JSContext* cx, JS::HandleObject object,
     714                 :             :                           const GI::BaseInfo& expected_info) {
     715                 :             :         Base* priv;
     716   [ +  -  -  + ]:         880 :         if (!Base::for_js_typecheck(cx, object, &priv) ||
     717         [ -  + ]:         440 :             !priv->check_is_instance(cx, "convert to pointer"))
     718                 :           0 :             return false;
     719                 :             : 
     720         [ +  - ]:         440 :         if (priv->to_instance()->typecheck_impl(expected_info))
     721                 :         440 :             return true;
     722                 :             : 
     723                 :           0 :         gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr,
     724                 :             :                          "Object is of type {} - cannot convert to {}",
     725                 :           0 :                          priv->format_name(), expected_info);
     726                 :           0 :         return false;
     727                 :             :     }
     728                 :             :     GJS_JSAPI_RETURN_CONVENTION
     729                 :       61299 :     static bool typecheck(JSContext* cx, JS::HandleObject object,
     730                 :             :                           GType expected_gtype) {
     731                 :             :         Base* priv;
     732   [ +  +  +  + ]:      122594 :         if (!Base::for_js_typecheck(cx, object, &priv) ||
     733         [ -  + ]:       61295 :             !priv->check_is_instance(cx, "convert to pointer"))
     734                 :           4 :             return false;
     735                 :             : 
     736         [ +  + ]:       61295 :         if (priv->to_instance()->typecheck_impl(expected_gtype))
     737                 :       61290 :             return true;
     738                 :             : 
     739                 :           5 :         gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr,
     740                 :             :                          "Object is of type {} - cannot convert to {}",
     741                 :          10 :                          priv->format_name(), g_type_name(expected_gtype));
     742                 :           5 :         return false;
     743                 :             :     }
     744                 :             :     [[nodiscard]]
     745                 :         241 :     static bool typecheck(JSContext* cx, JS::HandleObject object,
     746                 :             :                           const auto& expected, GjsTypecheckNoThrow) {
     747                 :         241 :         Base* priv = Base::for_js(cx, object);
     748   [ +  +  -  +  :         241 :         if (!priv || priv->is_prototype())
                   +  + ]
     749                 :         109 :             return false;
     750                 :             : 
     751                 :         132 :         return priv->to_instance()->typecheck_impl(expected);
     752                 :             :     }
     753                 :             : 
     754                 :             :     // Deleting these constructors and assignment operators will also delete
     755                 :             :     // them from derived classes.
     756                 :             :     GIWrapperBase(const GIWrapperBase& other) = delete;
     757                 :             :     GIWrapperBase(GIWrapperBase&& other) = delete;
     758                 :             :     GIWrapperBase& operator=(const GIWrapperBase& other) = delete;
     759                 :             :     GIWrapperBase& operator=(GIWrapperBase&& other) = delete;
     760                 :             : };
     761                 :             : 
     762                 :             : /**
     763                 :             :  * GIWrapperPrototype:
     764                 :             :  *
     765                 :             :  * The specialization of GIWrapperBase which becomes the private data of JS
     766                 :             :  * prototype objects. For example, it is the parent class of BoxedPrototype.
     767                 :             :  *
     768                 :             :  * Classes inheriting from GIWrapperPrototype must declare "friend class
     769                 :             :  * GIWrapperBase" as well as the normal CRTP requirement of "friend class
     770                 :             :  * GIWrapperPrototype", because of the unusual polymorphism scheme, in order for
     771                 :             :  * Base to call methods such as trace_impl().
     772                 :             :  */
     773                 :             : template <class Base, class Prototype, class Instance, typename OwnedInfo,
     774                 :             :           typename UnownedInfo>
     775                 :             : class GIWrapperPrototype : public Base {
     776                 :             :     using GjsAutoPrototype =
     777                 :           0 :         Gjs::AutoPointer<Prototype, void, g_atomic_rc_box_release>;
     778                 :             : 
     779                 :             :  protected:
     780                 :             :     // m_info may be null in the case of JS-defined types, or internal types not
     781                 :             :     // exposed through introspection, such as GLocalFile. Not all subclasses of
     782                 :             :     // GIWrapperPrototype support this. Object and Interface support it in any
     783                 :             :     // case.
     784                 :             :     OwnedInfo m_info;
     785                 :             :     GType m_gtype;
     786                 :             : 
     787                 :        2420 :     explicit GIWrapperPrototype(const UnownedInfo& info, GType gtype)
     788                 :        2420 :         : Base(), m_info(info), m_gtype(gtype) {
     789                 :        2420 :         Base::debug_lifecycle("Prototype constructor");
     790                 :        2420 :     }
     791                 :             : 
     792                 :             :     /**
     793                 :             :      * GIWrapperPrototype::init:
     794                 :             :      *
     795                 :             :      * Performs any initialization that cannot be done in the constructor of
     796                 :             :      * GIWrapperPrototype, either because it can fail, or because it can cause a
     797                 :             :      * garbage collection.
     798                 :             :      *
     799                 :             :      * This default implementation does nothing. Override in a subclass if
     800                 :             :      * necessary.
     801                 :             :      */
     802                 :             :     GJS_JSAPI_RETURN_CONVENTION
     803                 :        2420 :     bool init(JSContext*) { return true; }
     804                 :             : 
     805                 :             :     // The following four methods are private because they are used only in
     806                 :             :     // create_class().
     807                 :             : 
     808                 :             :  private:
     809                 :             :     /**
     810                 :             :      * GIWrapperPrototype::parent_proto:
     811                 :             :      *
     812                 :             :      * Returns in @proto the parent class's prototype object, or nullptr if
     813                 :             :      * there is none.
     814                 :             :      *
     815                 :             :      * This default implementation is for GObject introspection types that can't
     816                 :             :      * inherit in JS, like Boxed and Union. Override this if the type can
     817                 :             :      * inherit in JS.
     818                 :             :      */
     819                 :             :     GJS_JSAPI_RETURN_CONVENTION
     820                 :        1421 :     static bool get_parent_proto(JSContext*, JS::MutableHandleObject proto) {
     821                 :        1421 :         proto.set(nullptr);
     822                 :        1421 :         return true;
     823                 :             :     }
     824                 :             : 
     825                 :             :     /**
     826                 :             :      * GIWrapperPrototype::constructor_nargs:
     827                 :             :      *
     828                 :             :      * Override this if the type's constructor takes other than 1 argument.
     829                 :             :      */
     830                 :        2304 :     [[nodiscard]] unsigned constructor_nargs() const { return 1; }
     831                 :             : 
     832                 :             :     /**
     833                 :             :      * GIWrapperPrototype::define_jsclass:
     834                 :             :      * @in_object: JSObject on which to define the class constructor as a
     835                 :             :      *   property
     836                 :             :      * @parent_proto: (nullable): prototype of the prototype
     837                 :             :      * @constructor: return location for the constructor function object
     838                 :             :      * @prototype: return location for the prototype object
     839                 :             :      *
     840                 :             :      * Defines a JS class with constructor and prototype, and optionally defines
     841                 :             :      * properties and methods on the prototype object, and methods on the
     842                 :             :      * constructor object.
     843                 :             :      *
     844                 :             :      * By default no properties or methods are defined, but derived classes can
     845                 :             :      * override the GIWrapperBase::proto_properties,
     846                 :             :      * GIWrapperBase::proto_methods, and GIWrapperBase::static_methods members.
     847                 :             :      * Static properties would also be possible but are not used anywhere in GJS
     848                 :             :      * so are not implemented yet.
     849                 :             :      *
     850                 :             :      * Note: no prototype methods are defined if @parent_proto is null.
     851                 :             :      *
     852                 :             :      * Here is a refresher comment on the difference between __proto__ and
     853                 :             :      * prototype that has been in the GJS codebase since forever:
     854                 :             :      *
     855                 :             :      * https://web.archive.org/web/20100716231157/http://egachine.berlios.de/embedding-sm-best-practice/apa.html
     856                 :             :      * https://www.sitepoint.com/javascript-inheritance/
     857                 :             :      * http://www.cs.rit.edu/~atk/JavaScript/manuals/jsobj/
     858                 :             :      *
     859                 :             :      * What we want is: repoobj.Gtk.Window is constructor for a GtkWindow
     860                 :             :      * wrapper JSObject (gjs_define_object_class() is supposed to define Window
     861                 :             :      * in Gtk.)
     862                 :             :      *
     863                 :             :      * Window.prototype contains the methods on Window, e.g. set_default_size()
     864                 :             :      * mywindow.__proto__ is Window.prototype
     865                 :             :      * mywindow.__proto__.__proto__ is Bin.prototype
     866                 :             :      * mywindow.__proto__.__proto__.__proto__ is Container.prototype
     867                 :             :      *
     868                 :             :      * Because Window.prototype is an instance of Window in a sense,
     869                 :             :      * Window.prototype.__proto__ is Window.prototype, just as
     870                 :             :      * mywindow.__proto__ is Window.prototype
     871                 :             :      *
     872                 :             :      * If we do "mywindow = new Window()" then we should get:
     873                 :             :      *     mywindow.__proto__ == Window.prototype
     874                 :             :      * which means "mywindow instanceof Window" is true.
     875                 :             :      *
     876                 :             :      * Remember "Window.prototype" is "the __proto__ of stuff constructed with
     877                 :             :      * new Window()"
     878                 :             :      *
     879                 :             :      * __proto__ is used to search for properties if you do "this.foo", while
     880                 :             :      * .prototype is only relevant for constructors and is used to set __proto__
     881                 :             :      * on new'd objects. So .prototype only makes sense on constructors.
     882                 :             :      *
     883                 :             :      * JS_SetPrototype() and JS_GetPrototype() are for __proto__. To set/get
     884                 :             :      * .prototype, just use the normal property accessors, or JS_InitClass()
     885                 :             :      * sets it up automatically.
     886                 :             :      */
     887                 :             :     GJS_JSAPI_RETURN_CONVENTION
     888                 :        2314 :     bool define_jsclass(JSContext* cx, JS::HandleObject in_object,
     889                 :             :                         JS::HandleObject parent_proto,
     890                 :             :                         JS::MutableHandleObject constructor,
     891                 :             :                         JS::MutableHandleObject prototype) {
     892                 :             :         // The GI namespace is only used to set the JSClass->name field (exposed
     893                 :             :         // by Object.prototype.toString, for example). We can safely set
     894                 :             :         // "unknown" if this is a custom or internal JS class with no GI
     895                 :             :         // namespace, as in that case the name is already globally unique (it's
     896                 :             :         // a GType name).
     897                 :             :         const char* gi_namespace;
     898                 :             :         if constexpr (may_not_have_info)
     899         [ +  + ]:        1150 :             gi_namespace = Base::info() ? Base::ns() : "unknown";
     900                 :             :         else
     901                 :        1164 :             gi_namespace = Base::ns();
     902                 :             : 
     903                 :        2314 :         unsigned nargs = static_cast<Prototype*>(this)->constructor_nargs();
     904                 :             : 
     905         [ -  + ]:        2314 :         if (!gjs_init_class_dynamic(
     906                 :             :                 cx, in_object, parent_proto, gi_namespace, Base::name(),
     907                 :             :                 &Base::klass, &Base::constructor, nargs, Base::proto_properties,
     908         [ +  + ]:        2314 :                 parent_proto ? nullptr : Base::proto_methods,
     909                 :             :                 Base::static_properties, Base::static_methods, prototype,
     910                 :             :                 constructor))
     911                 :           0 :             return false;
     912                 :             : 
     913                 :        2314 :         gjs_debug(
     914                 :             :             Base::DEBUG_TOPIC,
     915                 :             :             "Defined class for {} ({}), prototype {:?}, JSClass {}, in {:?}",
     916                 :        4628 :             Base::format_name(), Base::type_name(), prototype,
     917                 :        2314 :             JS::GetClass(prototype)->name, in_object);
     918                 :             : 
     919                 :        2314 :         return true;
     920                 :             :     }
     921                 :             : 
     922                 :             :     /**
     923                 :             :      * GIWrapperPrototype::define_static_methods:
     924                 :             :      *
     925                 :             :      * Defines all introspectable static methods on @constructor, including
     926                 :             :      * class methods for objects, and interface methods for interfaces. See
     927                 :             :      * gjs_define_static_methods() for details.
     928                 :             :      */
     929                 :             :     GJS_JSAPI_RETURN_CONVENTION
     930                 :        2420 :     bool define_static_methods(JSContext* cx, JS::HandleObject constructor) {
     931                 :             :         if constexpr (may_not_have_info) {
     932         [ +  + ]:        1256 :             if (!info())
     933                 :         206 :                 return true;  // no introspection means no methods to define
     934                 :        1050 :             return gjs_define_static_methods(cx, constructor, m_gtype,
     935                 :        2100 :                                              info().value());
     936                 :             :         } else {
     937                 :        1164 :             return gjs_define_static_methods(cx, constructor, m_gtype, m_info);
     938                 :             :         }
     939                 :             :     }
     940                 :             : 
     941                 :             :     GJS_JSAPI_RETURN_CONVENTION
     942                 :        2420 :     static Prototype* create_prototype(const UnownedInfo& info, GType gtype) {
     943                 :        2420 :         g_assert(gtype != G_TYPE_INVALID);
     944                 :             : 
     945                 :             :         // We have to keep the Prototype in an arcbox because some of its
     946                 :             :         // members are needed in some Instance destructors, e.g. m_gtype to
     947                 :             :         // figure out how to free the Instance's m_ptr, and m_info to figure out
     948                 :             :         // how many bytes to free if it is allocated directly. Storing a
     949                 :             :         // refcount on the prototype is cheaper than storing pointers to m_info
     950                 :             :         // and m_gtype on each instance.
     951                 :        2420 :         Prototype* priv = g_atomic_rc_box_new0(Prototype);
     952                 :        2420 :         new (priv) Prototype(info, gtype);
     953                 :             : 
     954                 :        2420 :         return priv;
     955                 :             :     }
     956                 :             : 
     957                 :             :  public:
     958                 :             :     /**
     959                 :             :      * GIWrapperPrototype::create_class:
     960                 :             :      * @in_object: JSObject on which to define the class constructor as a
     961                 :             :      *   property
     962                 :             :      * @info: (nullable): Introspection info for the class, or null if the class
     963                 :             :      *   has been defined in JS
     964                 :             :      * @gtype: GType for the class
     965                 :             :      * @constructor: return location for the constructor function object
     966                 :             :      * @prototype: return location for the prototype object
     967                 :             :      *
     968                 :             :      * Creates a JS class that wraps a GI pointer, by defining its constructor
     969                 :             :      * function and prototype object. The prototype object is given an instance
     970                 :             :      * of GIWrapperPrototype as its private data, which is also returned.
     971                 :             :      * Basically treat this method as the public constructor.
     972                 :             :      *
     973                 :             :      * Also defines all the requested methods and properties on the prototype
     974                 :             :      * and constructor objects (see define_jsclass()), as well as a `$gtype`
     975                 :             :      * property and a toString() method.
     976                 :             :      *
     977                 :             :      * This method can be overridden and chained up to if the derived class
     978                 :             :      * needs to define more properties on the constructor or prototype objects,
     979                 :             :      * e.g. eager GI properties.
     980                 :             :      */
     981                 :             :     GJS_JSAPI_RETURN_CONVENTION
     982                 :        2314 :     static Prototype* create_class(JSContext* cx, JS::HandleObject in_object,
     983                 :             :                                    const UnownedInfo& info, GType gtype,
     984                 :             :                                    JS::MutableHandleObject constructor,
     985                 :             :                                    JS::MutableHandleObject prototype) {
     986                 :        2314 :         g_assert(in_object);
     987                 :             : 
     988                 :        2314 :         GjsAutoPrototype priv = create_prototype(info, gtype);
     989         [ -  + ]:        2314 :         if (!priv->init(cx))
     990                 :           0 :             return nullptr;
     991                 :             : 
     992                 :        2314 :         JS::RootedObject parent_proto(cx);
     993         [ +  - ]:        4628 :         if (!priv->get_parent_proto(cx, &parent_proto) ||
     994   [ -  +  -  + ]:        4628 :             !priv->define_jsclass(cx, in_object, parent_proto, constructor,
     995                 :             :                                   prototype))
     996                 :           0 :             return nullptr;
     997                 :             : 
     998                 :             :         // Init the private variable of @private before we do anything else. If
     999                 :             :         // a garbage collection or error happens subsequently, then this object
    1000                 :             :         // might be traced and we would end up dereferencing a null pointer.
    1001                 :        2314 :         Prototype* proto = priv.release();
    1002                 :        2314 :         Prototype::init_private(prototype, proto);
    1003                 :             : 
    1004         [ -  + ]:        2314 :         if (!gjs_wrapper_define_gtype_prop(cx, constructor, gtype))
    1005                 :           0 :             return nullptr;
    1006                 :             : 
    1007                 :             :         // Every class has a toString() with C++ implementation, so define that
    1008                 :             :         // without requiring it to be listed in Base::proto_methods
    1009         [ +  + ]:        2314 :         if (!parent_proto) {
    1010                 :        1504 :             const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
    1011         [ -  + ]:        1504 :             if (!JS_DefineFunctionById(cx, prototype, atoms.to_string(),
    1012                 :             :                                        &Base::to_string, 0,
    1013                 :             :                                        GJS_MODULE_PROP_FLAGS))
    1014                 :           0 :                 return nullptr;
    1015                 :             :         }
    1016                 :             : 
    1017         [ -  + ]:        2314 :         if (!proto->define_static_methods(cx, constructor))
    1018                 :           0 :             return nullptr;
    1019                 :             : 
    1020                 :        2314 :         add_prototype_associated_memory(gtype, prototype);
    1021                 :             : 
    1022                 :        2314 :         return proto;
    1023                 :        2314 :     }
    1024                 :             : 
    1025                 :             :     GJS_JSAPI_RETURN_CONVENTION
    1026                 :         106 :     static Prototype* wrap_class(JSContext* cx, JS::HandleObject in_object,
    1027                 :             :                                  const UnownedInfo& info, GType gtype,
    1028                 :             :                                  JS::HandleObject constructor,
    1029                 :             :                                  JS::MutableHandleObject prototype) {
    1030                 :         106 :         g_assert(in_object);
    1031                 :             : 
    1032                 :         106 :         GjsAutoPrototype priv = create_prototype(info, gtype);
    1033         [ -  + ]:         106 :         if (!priv->init(cx))
    1034                 :           0 :             return nullptr;
    1035                 :             : 
    1036                 :         106 :         JS::RootedObject parent_proto(cx);
    1037         [ -  + ]:         106 :         if (!priv->get_parent_proto(cx, &parent_proto))
    1038                 :           0 :             return nullptr;
    1039                 :             : 
    1040         [ +  + ]:         106 :         if (parent_proto) {
    1041                 :         103 :             prototype.set(
    1042                 :         103 :                 JS_NewObjectWithGivenProto(cx, &Base::klass, parent_proto));
    1043                 :             :         } else {
    1044                 :           3 :             prototype.set(JS_NewObject(cx, &Base::klass));
    1045                 :             :         }
    1046                 :             : 
    1047         [ -  + ]:         106 :         if (!prototype)
    1048                 :           0 :             return nullptr;
    1049                 :             : 
    1050                 :         106 :         Prototype* proto = priv.release();
    1051                 :         106 :         Prototype::init_private(prototype, proto);
    1052                 :             : 
    1053         [ -  + ]:         106 :         if (!proto->define_static_methods(cx, constructor))
    1054                 :           0 :             return nullptr;
    1055                 :             : 
    1056         [ -  + ]:         106 :         if (!JS_DefineProperty(cx, in_object, proto->name(), constructor,
    1057                 :             :                                GJS_MODULE_PROP_FLAGS))
    1058                 :           0 :             return nullptr;
    1059                 :             : 
    1060                 :         106 :         add_prototype_associated_memory(gtype, prototype);
    1061                 :             : 
    1062                 :         106 :         return proto;
    1063                 :         106 :     }
    1064                 :             : 
    1065                 :             :     // Methods to get an existing Prototype
    1066                 :             : 
    1067                 :             :     /**
    1068                 :             :      * GIWrapperPrototype::for_js:
    1069                 :             :      *
    1070                 :             :      * Like Base::for_js(), but asserts that the returned private struct is a
    1071                 :             :      * Prototype and not an Instance.
    1072                 :             :      */
    1073                 :             :     [[nodiscard]]
    1074                 :        4217 :     static Prototype* for_js(JSContext* cx, JS::HandleObject wrapper) {
    1075                 :        4217 :         return Base::for_js(cx, wrapper)->to_prototype();
    1076                 :             :     }
    1077                 :             : 
    1078                 :             :     /**
    1079                 :             :      * GIWrapperPrototype::for_js_prototype:
    1080                 :             :      *
    1081                 :             :      * Gets the Prototype private data from to @wrapper.prototype. Cannot return
    1082                 :             :      * null, and asserts so.
    1083                 :             :      */
    1084                 :             :     [[nodiscard]]
    1085                 :       20694 :     static Prototype* for_js_prototype(JSContext* cx,
    1086                 :             :                                        JS::HandleObject wrapper) {
    1087                 :       20694 :         JS::RootedObject proto(cx);
    1088                 :       20694 :         JS_GetPrototype(cx, wrapper, &proto);
    1089                 :       20694 :         Base* retval = Base::for_js(cx, proto);
    1090                 :       20694 :         g_assert(retval);
    1091                 :       20694 :         return retval->to_prototype();
    1092                 :       20694 :     }
    1093                 :             : 
    1094                 :             :     // Accessors
    1095                 :             : 
    1096                 :             :     static constexpr bool may_not_have_info = is_maybe<UnownedInfo>::value;
    1097                 :       33194 :     [[nodiscard]] UnownedInfo info() const { return m_info; }
    1098                 :      257486 :     [[nodiscard]] GType gtype() const { return m_gtype; }
    1099                 :             : 
    1100                 :             :     // Helper methods
    1101                 :             : 
    1102                 :             :  private:
    1103                 :        2397 :     static void destroy_notify(void* ptr) {
    1104                 :        2397 :         static_cast<Prototype*>(ptr)->~Prototype();
    1105                 :        2397 :     }
    1106                 :             : 
    1107                 :             :  public:
    1108                 :       24847 :     Prototype* acquire() {
    1109                 :       24847 :         g_atomic_rc_box_acquire(this);
    1110                 :       24847 :         return static_cast<Prototype*>(this);
    1111                 :             :     }
    1112                 :             : 
    1113                 :       27240 :     void release() { g_atomic_rc_box_release_full(this, &destroy_notify); }
    1114                 :             : 
    1115                 :             :     // JSClass operations
    1116                 :             : 
    1117                 :             :  protected:
    1118                 :        2397 :     void finalize_impl(JS::GCContext*, JSObject*) { release(); }
    1119                 :             : 
    1120                 :             :     // Override if necessary
    1121                 :          14 :     void trace_impl(JSTracer*) {}
    1122                 :             : };
    1123                 :             : 
    1124                 :             : using GIWrappedUnowned = void;
    1125                 :             : namespace Gjs {
    1126                 :             : template <>
    1127                 :             : struct SmartPointer<GIWrappedUnowned>
    1128                 :             :     : AutoPointer<GIWrappedUnowned, void, nullptr> {
    1129                 :             :     using AutoPointer::AutoPointer;
    1130                 :             : };
    1131                 :             : }  // namespace Gjs
    1132                 :             : 
    1133                 :             : /**
    1134                 :             :  * GIWrapperInstance:
    1135                 :             :  *
    1136                 :             :  * The specialization of GIWrapperBase which becomes the private data of JS
    1137                 :             :  * instance objects. For example, it is the parent class of BoxedInstance.
    1138                 :             :  *
    1139                 :             :  * Classes inheriting from GIWrapperInstance must declare "friend class
    1140                 :             :  * GIWrapperBase" as well as the normal CRTP requirement of "friend class
    1141                 :             :  * GIWrapperInstance", because of the unusual polymorphism scheme, in order for
    1142                 :             :  * Base to call methods such as trace_impl().
    1143                 :             :  */
    1144                 :             : template <class Base, class Prototype, class Instance,
    1145                 :             :           typename Wrapped = GIWrappedUnowned>
    1146                 :             : class GIWrapperInstance : public Base {
    1147                 :             :  protected:
    1148                 :             :     Gjs::SmartPointer<Wrapped> m_ptr;
    1149                 :             : 
    1150                 :       24847 :     explicit GIWrapperInstance(Prototype* prototype, JS::HandleObject obj)
    1151                 :       24847 :         : Base(prototype), m_ptr(nullptr) {
    1152                 :       24847 :         Base::m_proto->acquire();
    1153                 :       24847 :         Base::GIWrapperBase::debug_lifecycle(obj, "Instance constructor");
    1154                 :       24847 :     }
    1155                 :             : 
    1156                 :       24843 :     ~GIWrapperInstance() { Base::m_proto->release(); }
    1157                 :             : 
    1158                 :             :  public:
    1159                 :             :     /**
    1160                 :             :      * GIWrapperInstance::new_for_js_object:
    1161                 :             :      *
    1162                 :             :      * Creates a GIWrapperInstance and associates it with @obj as its private
    1163                 :             :      * data. This is called by the JS constructor.
    1164                 :             :      */
    1165                 :             :     [[nodiscard]]
    1166                 :       20694 :     static Instance* new_for_js_object(JSContext* cx, JS::HandleObject obj) {
    1167                 :       20694 :         Prototype* prototype = Prototype::for_js_prototype(cx, obj);
    1168                 :       20694 :         auto* priv = new Instance(prototype, obj);
    1169                 :             : 
    1170                 :             :         // Init the private variable before we do anything else. If a garbage
    1171                 :             :         // collection happens when calling the constructor, then this object
    1172                 :             :         // might be traced and we would end up dereferencing a null pointer.
    1173                 :       20694 :         Instance::init_private(obj, priv);
    1174                 :             : 
    1175                 :       20694 :         return priv;
    1176                 :             :     }
    1177                 :             : 
    1178                 :             :     [[nodiscard]]
    1179                 :        3322 :     static Instance* new_for_js_object(Prototype* prototype,
    1180                 :             :                                        JS::HandleObject obj) {
    1181                 :        3322 :         auto* priv = new Instance(prototype, obj);
    1182                 :             : 
    1183                 :        3322 :         Instance::init_private(obj, priv);
    1184                 :             : 
    1185                 :        3322 :         return priv;
    1186                 :             :     }
    1187                 :             : 
    1188                 :             :     // Method to get an existing Instance
    1189                 :             : 
    1190                 :             :     /**
    1191                 :             :      * GIWrapperInstance::for_js:
    1192                 :             :      *
    1193                 :             :      * Like Base::for_js(), but asserts that the returned private struct is an
    1194                 :             :      * Instance and not a Prototype.
    1195                 :             :      */
    1196                 :             :     [[nodiscard]]
    1197                 :          60 :     static Instance* for_js(JSContext* cx, JS::HandleObject wrapper) {
    1198                 :          60 :         return Base::for_js(cx, wrapper)->to_instance();
    1199                 :             :     }
    1200                 :             : 
    1201                 :             :     // Accessors
    1202                 :             : 
    1203                 :       62151 :     [[nodiscard]] Wrapped* ptr() const { return m_ptr; }
    1204                 :             :     /**
    1205                 :             :      * GIWrapperInstance::raw_ptr:
    1206                 :             :      *
    1207                 :             :      * Like ptr(), but returns a byte pointer for use in byte arithmetic.
    1208                 :             :      */
    1209                 :             :     [[nodiscard]]
    1210                 :         122 :     uint8_t* raw_ptr() const {
    1211                 :         122 :         return reinterpret_cast<uint8_t*>(ptr());
    1212                 :             :     }
    1213                 :             : 
    1214                 :             :     // JSClass operations
    1215                 :             : 
    1216                 :             :  protected:
    1217                 :       24843 :     void finalize_impl(JS::GCContext*, JSObject*) {
    1218         [ +  - ]:       24843 :         delete static_cast<Instance*>(this);
    1219                 :       24843 :     }
    1220                 :             : 
    1221                 :             :     // Override if necessary
    1222                 :           0 :     void trace_impl(JSTracer*) {}
    1223                 :             : 
    1224                 :             :     // Helper methods
    1225                 :             : 
    1226                 :             :     /**
    1227                 :             :      * GIWrapperInstance::typecheck_impl:
    1228                 :             :      *
    1229                 :             :      * See GIWrapperBase::typecheck(). Checks that the instance's wrapped
    1230                 :             :      * pointer is of the correct GType or GI info. Does not throw a JS
    1231                 :             :      * exception.
    1232                 :             :      *
    1233                 :             :      * It's possible to override typecheck_impl() if you need an extra step in
    1234                 :             :      * the check.
    1235                 :             :      */
    1236                 :             :     [[nodiscard]]
    1237                 :         495 :     bool typecheck_impl(const GI::BaseInfo& expected_info) const {
    1238                 :             :         if constexpr (Prototype::may_not_have_info) {
    1239                 :             :             if (Base::info())
    1240                 :             :                 return Base::info().ref() == expected_info;
    1241                 :             :         } else {
    1242                 :         495 :             return Base::info() == expected_info;
    1243                 :             :         }
    1244                 :             :         return true;
    1245                 :             :     }
    1246                 :             :     [[nodiscard]]
    1247                 :       61372 :     bool typecheck_impl(GType expected_gtype) const {
    1248                 :       61372 :         g_assert(expected_gtype != G_TYPE_NONE &&
    1249                 :             :                  "should not call typecheck_impl() without a real GType");
    1250   [ +  +  +  + ]:       61372 :         return g_type_is_a(Base::gtype(), expected_gtype);
    1251                 :             :     }
    1252                 :             : };
        

Generated by: LCOV version 2.0-1