LCOV - code coverage report
Current view: top level - gjs - jsapi-dynamic-class.cpp (source / functions) Hit Total Coverage
Test: gjs- Code Coverage Lines: 65 92 70.7 %
Date: 2024-02-27 17:05:05 Functions: 7 8 87.5 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 33 64 51.6 %

           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: 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
       5                 :            : 
       6                 :            : #include <config.h>
       7                 :            : 
       8                 :            : #include <string.h>  // for strlen
       9                 :            : 
      10                 :            : #include <glib.h>
      11                 :            : 
      12                 :            : #include <js/CallAndConstruct.h>
      13                 :            : #include <js/CallArgs.h>  // for JSNative
      14                 :            : #include <js/Class.h>
      15                 :            : #include <js/ComparisonOperators.h>
      16                 :            : #include <js/ErrorReport.h>         // for JSEXN_TYPEERR
      17                 :            : #include <js/Object.h>              // for GetClass
      18                 :            : #include <js/PropertyAndElement.h>  // for JS_DefineFunctions, JS_DefinePro...
      19                 :            : #include <js/Realm.h>  // for GetRealmObjectPrototype
      20                 :            : #include <js/RootingAPI.h>
      21                 :            : #include <js/TypeDecls.h>
      22                 :            : #include <js/Value.h>
      23                 :            : #include <jsapi.h>        // for JS_GetFunctionObject, JS_GetPrototype
      24                 :            : #include <jsfriendapi.h>  // for GetFunctionNativeReserved, NewFun...
      25                 :            : 
      26                 :            : #include "gjs/atoms.h"
      27                 :            : #include "gjs/context-private.h"
      28                 :            : #include "gjs/jsapi-util.h"
      29                 :            : #include "gjs/macros.h"
      30                 :            : 
      31                 :            : struct JSFunctionSpec;
      32                 :            : struct JSPropertySpec;
      33                 :            : namespace JS {
      34                 :            : class HandleValueArray;
      35                 :            : }
      36                 :            : 
      37                 :            : /* Reserved slots of JSNative accessor wrappers */
      38                 :            : enum {
      39                 :            :     DYNAMIC_PROPERTY_PRIVATE_SLOT,
      40                 :            : };
      41                 :            : 
      42                 :       1389 : bool gjs_init_class_dynamic(JSContext* context, JS::HandleObject in_object,
      43                 :            :                             JS::HandleObject parent_proto, const char* ns_name,
      44                 :            :                             const char* class_name, const JSClass* clasp,
      45                 :            :                             JSNative constructor_native, unsigned nargs,
      46                 :            :                             JSPropertySpec* proto_ps, JSFunctionSpec* proto_fs,
      47                 :            :                             JSPropertySpec* static_ps,
      48                 :            :                             JSFunctionSpec* static_fs,
      49                 :            :                             JS::MutableHandleObject prototype,
      50                 :            :                             JS::MutableHandleObject constructor) {
      51                 :            :     /* Without a name, JS_NewObject fails */
      52                 :       1389 :     g_assert (clasp->name != NULL);
      53                 :            : 
      54                 :            :     /* gjs_init_class_dynamic only makes sense for instantiable classes,
      55                 :            :        use JS_InitClass for static classes like Math */
      56                 :       1389 :     g_assert (constructor_native != NULL);
      57                 :            : 
      58                 :            :     /* Class initialization consists of five parts:
      59                 :            :        - building a prototype
      60                 :            :        - defining prototype properties and functions
      61                 :            :        - building a constructor and defining it on the right object
      62                 :            :        - defining constructor properties and functions
      63                 :            :        - linking the constructor and the prototype, so that
      64                 :            :          JS_NewObjectForConstructor can find it
      65                 :            :     */
      66                 :            : 
      67         [ +  + ]:       1389 :     if (parent_proto) {
      68                 :        512 :         prototype.set(JS_NewObjectWithGivenProto(context, clasp, parent_proto));
      69                 :            :     } else {
      70                 :            :         /* JS_NewObject will use Object.prototype as the prototype if the
      71                 :            :          * clasp's constructor is not a built-in class.
      72                 :            :          */
      73                 :        877 :         prototype.set(JS_NewObject(context, clasp));
      74                 :            :     }
      75         [ -  + ]:       1389 :     if (!prototype)
      76                 :          0 :         return false;
      77                 :            : 
      78   [ +  +  -  +  :       1389 :     if (proto_ps && !JS_DefineProperties(context, prototype, proto_ps))
                   -  + ]
      79                 :          0 :         return false;
      80   [ +  +  -  +  :       1389 :     if (proto_fs && !JS_DefineFunctions(context, prototype, proto_fs))
                   -  + ]
      81                 :          0 :         return false;
      82                 :            : 
      83                 :            :     GjsAutoChar full_function_name =
      84                 :       1389 :         g_strdup_printf("%s_%s", ns_name, class_name);
      85                 :            :     JSFunction* constructor_fun =
      86                 :       1389 :         JS_NewFunction(context, constructor_native, nargs, JSFUN_CONSTRUCTOR,
      87                 :            :                        full_function_name);
      88         [ -  + ]:       1389 :     if (!constructor_fun)
      89                 :          0 :         return false;
      90                 :            : 
      91                 :       1389 :     constructor.set(JS_GetFunctionObject(constructor_fun));
      92                 :            : 
      93   [ -  +  -  -  :       1389 :     if (static_ps && !JS_DefineProperties(context, constructor, static_ps))
                   -  + ]
      94                 :          0 :         return false;
      95   [ +  +  -  +  :       1389 :     if (static_fs && !JS_DefineFunctions(context, constructor, static_fs))
                   -  + ]
      96                 :          0 :         return false;
      97                 :            : 
      98         [ -  + ]:       1389 :     if (!JS_LinkConstructorAndPrototype(context, constructor, prototype))
      99                 :          0 :         return false;
     100                 :            : 
     101                 :            :     /* The constructor defined by JS_InitClass has no property attributes, but this
     102                 :            :        is a more useful default for gjs */
     103                 :       2778 :     return JS_DefineProperty(context, in_object, class_name, constructor,
     104                 :       1389 :                              GJS_MODULE_PROP_FLAGS);
     105                 :       1389 : }
     106                 :            : 
     107                 :          0 : [[nodiscard]] static const char* format_dynamic_class_name(const char* name) {
     108   [ #  #  #  #  :          0 :     if (g_str_has_prefix(name, "_private_"))
                   #  # ]
     109                 :          0 :         return name + strlen("_private_");
     110                 :            :     else
     111                 :          0 :         return name;
     112                 :            : }
     113                 :            : 
     114                 :            : bool
     115                 :        347 : gjs_typecheck_instance(JSContext       *context,
     116                 :            :                        JS::HandleObject obj,
     117                 :            :                        const JSClass   *static_clasp,
     118                 :            :                        bool             throw_error)
     119                 :            : {
     120         [ -  + ]:        347 :     if (!JS_InstanceOf(context, obj, static_clasp, NULL)) {
     121         [ #  # ]:          0 :         if (throw_error) {
     122                 :          0 :             const JSClass* obj_class = JS::GetClass(obj);
     123                 :            : 
     124                 :          0 :             gjs_throw_custom(context, JSEXN_TYPEERR, nullptr,
     125                 :            :                              "Object %p is not a subclass of %s, it's a %s",
     126                 :          0 :                              obj.get(), static_clasp->name,
     127                 :          0 :                              format_dynamic_class_name(obj_class->name));
     128                 :            :         }
     129                 :            : 
     130                 :          0 :         return false;
     131                 :            :     }
     132                 :            : 
     133                 :        347 :     return true;
     134                 :            : }
     135                 :            : 
     136                 :            : JSObject*
     137                 :          3 : gjs_construct_object_dynamic(JSContext                  *context,
     138                 :            :                              JS::HandleObject            proto,
     139                 :            :                              const JS::HandleValueArray& args)
     140                 :            : {
     141                 :          3 :     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
     142                 :          3 :     JS::RootedObject constructor(context);
     143                 :            : 
     144         [ -  + ]:          3 :     if (!gjs_object_require_property(context, proto, "prototype",
     145                 :            :                                      atoms.constructor(), &constructor))
     146                 :          0 :         return NULL;
     147                 :            : 
     148                 :          3 :     JS::RootedValue v_constructor(context, JS::ObjectValue(*constructor));
     149                 :          3 :     JS::RootedObject object(context);
     150         [ -  + ]:          3 :     if (!JS::Construct(context, v_constructor, args, &object))
     151                 :          0 :         return nullptr;
     152                 :            : 
     153                 :          3 :     return object;
     154                 :          3 : }
     155                 :            : 
     156                 :            : GJS_JSAPI_RETURN_CONVENTION
     157                 :            : static JSObject *
     158                 :       3308 : define_native_accessor_wrapper(JSContext      *cx,
     159                 :            :                                JSNative        call,
     160                 :            :                                unsigned        nargs,
     161                 :            :                                const char     *func_name,
     162                 :            :                                JS::HandleValue private_slot)
     163                 :            : {
     164                 :       3308 :     JSFunction *func = js::NewFunctionWithReserved(cx, call, nargs, 0, func_name);
     165         [ -  + ]:       3308 :     if (!func)
     166                 :          0 :         return nullptr;
     167                 :            : 
     168                 :       3308 :     JSObject *func_obj = JS_GetFunctionObject(func);
     169                 :       3308 :     js::SetFunctionNativeReserved(func_obj, DYNAMIC_PROPERTY_PRIVATE_SLOT,
     170                 :            :                                   private_slot);
     171                 :       3308 :     return func_obj;
     172                 :            : }
     173                 :            : 
     174                 :            : /**
     175                 :            :  * gjs_define_property_dynamic:
     176                 :            :  * @cx: the #JSContext
     177                 :            :  * @proto: the prototype of the object, on which to define the property
     178                 :            :  * @prop_name: name of the property or field in GObject, visible to JS code
     179                 :            :  * @func_namespace: string from which the internal names for the getter and
     180                 :            :  *   setter functions are built, not visible to JS code
     181                 :            :  * @getter: getter function
     182                 :            :  * @setter: setter function
     183                 :            :  * @private_slot: private data in the form of a #JS::Value that the getter and
     184                 :            :  *   setter will have access to
     185                 :            :  * @flags: additional flags to define the property with (other than the ones
     186                 :            :  *   required for a property with native getter/setter)
     187                 :            :  *
     188                 :            :  * When defining properties in a GBoxed or GObject, we can't have a separate
     189                 :            :  * getter and setter for each one, since the properties are defined dynamically.
     190                 :            :  * Therefore we must have one getter and setter for all the properties we define
     191                 :            :  * on all the types. In order to have that, we must provide the getter and
     192                 :            :  * setter with private data, e.g. the field index for GBoxed, in a "reserved
     193                 :            :  * slot" for which we must unfortunately use the jsfriendapi.
     194                 :            :  *
     195                 :            :  * Returns: %true on success, %false if an exception is pending on @cx.
     196                 :            :  */
     197                 :       1654 : bool gjs_define_property_dynamic(JSContext* cx, JS::HandleObject proto,
     198                 :            :                                  const char* prop_name, JS::HandleId id,
     199                 :            :                                  const char* func_namespace, JSNative getter,
     200                 :            :                                  JS::HandleValue getter_slot, JSNative setter,
     201                 :            :                                  JS::HandleValue setter_slot, unsigned flags) {
     202                 :       1654 :     GjsAutoChar getter_name = g_strconcat(func_namespace, "_get::", prop_name, nullptr);
     203                 :       1654 :     GjsAutoChar setter_name = g_strconcat(func_namespace, "_set::", prop_name, nullptr);
     204                 :            : 
     205                 :            :     JS::RootedObject getter_obj(
     206                 :       1654 :         cx, define_native_accessor_wrapper(cx, getter, 0, getter_name,
     207                 :       1654 :                                            getter_slot));
     208         [ -  + ]:       1654 :     if (!getter_obj)
     209                 :          0 :         return false;
     210                 :            : 
     211                 :            :     JS::RootedObject setter_obj(
     212                 :       1654 :         cx, define_native_accessor_wrapper(cx, setter, 1, setter_name,
     213                 :       1654 :                                            setter_slot));
     214         [ -  + ]:       1654 :     if (!setter_obj)
     215                 :          0 :         return false;
     216                 :            : 
     217         [ -  + ]:       1654 :     if (id.isVoid()) {
     218                 :          0 :         return JS_DefineProperty(cx, proto, prop_name, getter_obj, setter_obj,
     219                 :          0 :                                  flags);
     220                 :            :     }
     221                 :            : 
     222                 :       1654 :     return JS_DefinePropertyById(cx, proto, id, getter_obj, setter_obj, flags);
     223                 :       1654 : }
     224                 :            : 
     225                 :            : /**
     226                 :            :  * gjs_dynamic_property_private_slot:
     227                 :            :  * @accessor_obj: the getter or setter as a function object, i.e.
     228                 :            :  *   `&args.callee()` in the #JSNative function
     229                 :            :  *
     230                 :            :  * For use in dynamic property getters and setters (see
     231                 :            :  * gjs_define_property_dynamic()) to retrieve the private data passed there.
     232                 :            :  *
     233                 :            :  * Returns: the JS::Value that was passed to gjs_define_property_dynamic().
     234                 :            :  */
     235                 :            : JS::Value
     236                 :       2620 : gjs_dynamic_property_private_slot(JSObject *accessor_obj)
     237                 :            : {
     238                 :       2620 :     return js::GetFunctionNativeReserved(accessor_obj,
     239                 :       2620 :                                          DYNAMIC_PROPERTY_PRIVATE_SLOT);
     240                 :            : }
     241                 :            : 
     242                 :            : /**
     243                 :            :  * gjs_object_in_prototype_chain:
     244                 :            :  * @cx:
     245                 :            :  * @proto: The prototype which we are checking if @check_obj has in its chain
     246                 :            :  * @check_obj: The object to check
     247                 :            :  * @is_in_chain: (out): Whether @check_obj has @proto in its prototype chain
     248                 :            :  *
     249                 :            :  * Similar to JS_HasInstance() but takes into account abstract classes defined
     250                 :            :  * with JS_InitClass(), which JS_HasInstance() does not. Abstract classes don't
     251                 :            :  * have constructors, and JS_HasInstance() requires a constructor.
     252                 :            :  *
     253                 :            :  * Returns: false if an exception was thrown, true otherwise.
     254                 :            :  */
     255                 :         82 : bool gjs_object_in_prototype_chain(JSContext* cx, JS::HandleObject proto,
     256                 :            :                                    JS::HandleObject check_obj,
     257                 :            :                                    bool* is_in_chain) {
     258                 :         82 :     JS::RootedObject object_prototype(cx, JS::GetRealmObjectPrototype(cx));
     259         [ -  + ]:         82 :     if (!object_prototype)
     260                 :          0 :         return false;
     261                 :            : 
     262                 :         82 :     JS::RootedObject proto_iter(cx);
     263         [ -  + ]:         82 :     if (!JS_GetPrototype(cx, check_obj, &proto_iter))
     264                 :          0 :         return false;
     265         [ +  + ]:        166 :     while (proto_iter != object_prototype) {
     266         [ +  + ]:        162 :         if (proto_iter == proto) {
     267                 :         78 :             *is_in_chain = true;
     268                 :         78 :             return true;
     269                 :            :         }
     270         [ -  + ]:         84 :         if (!JS_GetPrototype(cx, proto_iter, &proto_iter))
     271                 :          0 :             return false;
     272                 :            :     }
     273                 :          4 :     *is_in_chain = false;
     274                 :          4 :     return true;
     275                 :         82 : }

Generated by: LCOV version 1.14