LCOV - code coverage report
Current view: top level - gjs - jsapi-dynamic-class.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 58.7 % 92 54
Test Date: 2024-09-12 04:39:42 Functions: 75.0 % 8 6
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 46.9 % 64 30

             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                 :        1482 : 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                 :        1482 :     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                 :        1482 :     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         [ +  + ]:        1482 :     if (parent_proto) {
      68                 :         502 :         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                 :         980 :         prototype.set(JS_NewObject(context, clasp));
      74                 :             :     }
      75         [ -  + ]:        1482 :     if (!prototype)
      76                 :           0 :         return false;
      77                 :             : 
      78   [ +  +  -  +  :        1482 :     if (proto_ps && !JS_DefineProperties(context, prototype, proto_ps))
                   -  + ]
      79                 :           0 :         return false;
      80   [ +  +  -  +  :        1482 :     if (proto_fs && !JS_DefineFunctions(context, prototype, proto_fs))
                   -  + ]
      81                 :           0 :         return false;
      82                 :             : 
      83                 :             :     GjsAutoChar full_function_name =
      84                 :        1482 :         g_strdup_printf("%s_%s", ns_name, class_name);
      85                 :             :     JSFunction* constructor_fun =
      86                 :        1482 :         JS_NewFunction(context, constructor_native, nargs, JSFUN_CONSTRUCTOR,
      87                 :             :                        full_function_name);
      88         [ -  + ]:        1482 :     if (!constructor_fun)
      89                 :           0 :         return false;
      90                 :             : 
      91                 :        1482 :     constructor.set(JS_GetFunctionObject(constructor_fun));
      92                 :             : 
      93   [ -  +  -  -  :        1482 :     if (static_ps && !JS_DefineProperties(context, constructor, static_ps))
                   -  + ]
      94                 :           0 :         return false;
      95   [ +  +  -  +  :        1482 :     if (static_fs && !JS_DefineFunctions(context, constructor, static_fs))
                   -  + ]
      96                 :           0 :         return false;
      97                 :             : 
      98         [ -  + ]:        1482 :     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                 :        2964 :     return JS_DefineProperty(context, in_object, class_name, constructor,
     104                 :        1482 :                              GJS_MODULE_PROP_FLAGS);
     105                 :        1482 : }
     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                 :         293 : gjs_typecheck_instance(JSContext       *context,
     116                 :             :                        JS::HandleObject obj,
     117                 :             :                        const JSClass   *static_clasp,
     118                 :             :                        bool             throw_error)
     119                 :             : {
     120         [ -  + ]:         293 :     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                 :         293 :     return true;
     134                 :             : }
     135                 :             : 
     136                 :             : JSObject*
     137                 :           0 : gjs_construct_object_dynamic(JSContext                  *context,
     138                 :             :                              JS::HandleObject            proto,
     139                 :             :                              const JS::HandleValueArray& args)
     140                 :             : {
     141                 :           0 :     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
     142                 :           0 :     JS::RootedObject constructor(context);
     143                 :             : 
     144         [ #  # ]:           0 :     if (!gjs_object_require_property(context, proto, "prototype",
     145                 :             :                                      atoms.constructor(), &constructor))
     146                 :           0 :         return NULL;
     147                 :             : 
     148                 :           0 :     JS::RootedValue v_constructor(context, JS::ObjectValue(*constructor));
     149                 :           0 :     JS::RootedObject object(context);
     150         [ #  # ]:           0 :     if (!JS::Construct(context, v_constructor, args, &object))
     151                 :           0 :         return nullptr;
     152                 :             : 
     153                 :           0 :     return object;
     154                 :           0 : }
     155                 :             : 
     156                 :             : GJS_JSAPI_RETURN_CONVENTION
     157                 :             : static JSObject *
     158                 :        3858 : define_native_accessor_wrapper(JSContext      *cx,
     159                 :             :                                JSNative        call,
     160                 :             :                                unsigned        nargs,
     161                 :             :                                const char     *func_name,
     162                 :             :                                JS::HandleValue private_slot)
     163                 :             : {
     164                 :        3858 :     JSFunction *func = js::NewFunctionWithReserved(cx, call, nargs, 0, func_name);
     165         [ -  + ]:        3858 :     if (!func)
     166                 :           0 :         return nullptr;
     167                 :             : 
     168                 :        3858 :     JSObject *func_obj = JS_GetFunctionObject(func);
     169                 :        3858 :     js::SetFunctionNativeReserved(func_obj, DYNAMIC_PROPERTY_PRIVATE_SLOT,
     170                 :             :                                   private_slot);
     171                 :        3858 :     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                 :        1929 : 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                 :        1929 :     GjsAutoChar getter_name = g_strconcat(func_namespace, "_get::", prop_name, nullptr);
     203                 :        1929 :     GjsAutoChar setter_name = g_strconcat(func_namespace, "_set::", prop_name, nullptr);
     204                 :             : 
     205                 :             :     JS::RootedObject getter_obj(
     206                 :        1929 :         cx, define_native_accessor_wrapper(cx, getter, 0, getter_name,
     207                 :        1929 :                                            getter_slot));
     208         [ -  + ]:        1929 :     if (!getter_obj)
     209                 :           0 :         return false;
     210                 :             : 
     211                 :             :     JS::RootedObject setter_obj(
     212                 :        1929 :         cx, define_native_accessor_wrapper(cx, setter, 1, setter_name,
     213                 :        1929 :                                            setter_slot));
     214         [ -  + ]:        1929 :     if (!setter_obj)
     215                 :           0 :         return false;
     216                 :             : 
     217         [ -  + ]:        1929 :     if (id.isVoid()) {
     218                 :           0 :         return JS_DefineProperty(cx, proto, prop_name, getter_obj, setter_obj,
     219                 :           0 :                                  flags);
     220                 :             :     }
     221                 :             : 
     222                 :        1929 :     return JS_DefinePropertyById(cx, proto, id, getter_obj, setter_obj, flags);
     223                 :        1929 : }
     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                 :        2390 : gjs_dynamic_property_private_slot(JSObject *accessor_obj)
     237                 :             : {
     238                 :        2390 :     return js::GetFunctionNativeReserved(accessor_obj,
     239                 :        2390 :                                          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                 :           1 : bool gjs_object_in_prototype_chain(JSContext* cx, JS::HandleObject proto,
     256                 :             :                                    JS::HandleObject check_obj,
     257                 :             :                                    bool* is_in_chain) {
     258                 :           1 :     JS::RootedObject object_prototype(cx, JS::GetRealmObjectPrototype(cx));
     259         [ -  + ]:           1 :     if (!object_prototype)
     260                 :           0 :         return false;
     261                 :             : 
     262                 :           1 :     JS::RootedObject proto_iter(cx);
     263         [ -  + ]:           1 :     if (!JS_GetPrototype(cx, check_obj, &proto_iter))
     264                 :           0 :         return false;
     265         [ +  - ]:           2 :     while (proto_iter != object_prototype) {
     266         [ +  + ]:           2 :         if (proto_iter == proto) {
     267                 :           1 :             *is_in_chain = true;
     268                 :           1 :             return true;
     269                 :             :         }
     270         [ -  + ]:           1 :         if (!JS_GetPrototype(cx, proto_iter, &proto_iter))
     271                 :           0 :             return false;
     272                 :             :     }
     273                 :           0 :     *is_in_chain = false;
     274                 :           0 :     return true;
     275                 :           1 : }
        

Generated by: LCOV version 2.0-1