LCOV - code coverage report
Current view: top level - gi - wrapperutils.h (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 91.4 % 304 278
Test Date: 2024-04-16 04:37:39 Functions: 94.0 % 301 283
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 73.9 % 142 105

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

Generated by: LCOV version 2.0-1