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

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

Generated by: LCOV version 2.0-1