LCOV - code coverage report
Current view: top level - modules - system.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 75.8 % 161 122
Test Date: 2024-04-29 05:18:28 Functions: 83.3 % 12 10
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 51.4 % 72 37

             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 Red Hat, Inc.
       5                 :             : // SPDX-FileCopyrightText: 2019 Endless Mobile, Inc.
       6                 :             : // SPDX-FileCopyrightText: 2021 Philip Chimento <philip.chimento@gmail.com>
       7                 :             : 
       8                 :             : #include <config.h>  // for GJS_VERSION
       9                 :             : 
      10                 :             : #include <stdint.h>
      11                 :             : #include <stdio.h>
      12                 :             : #include <time.h>    // for tzset
      13                 :             : 
      14                 :             : #include <glib-object.h>
      15                 :             : #include <glib.h>
      16                 :             : 
      17                 :             : #include <js/CallArgs.h>
      18                 :             : #include <js/Date.h>                // for ResetTimeZone
      19                 :             : #include <js/GCAPI.h>               // for JS_GC
      20                 :             : #include <js/JSON.h>
      21                 :             : #include <js/PropertyAndElement.h>
      22                 :             : #include <js/PropertyDescriptor.h>  // for JSPROP_READONLY
      23                 :             : #include <js/PropertySpec.h>
      24                 :             : #include <js/RootingAPI.h>
      25                 :             : #include <js/TypeDecls.h>
      26                 :             : #include <js/Value.h>     // for NullValue
      27                 :             : #include <js/friend/DumpFunctions.h>
      28                 :             : #include <jsapi.h>        // for JS_GetFunctionObject, JS_NewPlainObject
      29                 :             : #include <jsfriendapi.h>  // for GetFunctionNativeReserved, NewFunctionByIdW...
      30                 :             : 
      31                 :             : #include "gi/object.h"
      32                 :             : #include "gjs/atoms.h"
      33                 :             : #include "gjs/context-private.h"
      34                 :             : #include "gjs/jsapi-util-args.h"
      35                 :             : #include "gjs/jsapi-util.h"
      36                 :             : #include "gjs/macros.h"
      37                 :             : #include "gjs/profiler-private.h"
      38                 :             : #include "modules/system.h"
      39                 :             : #include "util/log.h"
      40                 :             : #include "util/misc.h"  // for LogFile
      41                 :             : 
      42                 :             : /* Note that this cannot be relied on to test whether two objects are the same!
      43                 :             :  * SpiderMonkey can move objects around in memory during garbage collection,
      44                 :             :  * and it can also deduplicate identical instances of objects in memory. */
      45                 :             : static bool
      46                 :          10 : gjs_address_of(JSContext *context,
      47                 :             :                unsigned   argc,
      48                 :             :                JS::Value *vp)
      49                 :             : {
      50                 :          10 :     JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
      51                 :          10 :     JS::RootedObject target_obj(context);
      52                 :             : 
      53         [ -  + ]:          10 :     if (!gjs_parse_call_args(context, "addressOf", argv, "o",
      54                 :             :                              "object", &target_obj))
      55                 :           0 :         return false;
      56                 :             : 
      57                 :          10 :     GjsAutoChar pointer_string = g_strdup_printf("%p", target_obj.get());
      58                 :          10 :     return gjs_string_from_utf8(context, pointer_string, argv.rval());
      59                 :          10 : }
      60                 :             : 
      61                 :             : GJS_JSAPI_RETURN_CONVENTION
      62                 :          10 : static bool gjs_address_of_gobject(JSContext* cx, unsigned argc,
      63                 :             :                                    JS::Value* vp) {
      64                 :          10 :     JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
      65                 :          10 :     JS::RootedObject target_obj(cx);
      66                 :             :     GObject *obj;
      67                 :             : 
      68         [ -  + ]:          10 :     if (!gjs_parse_call_args(cx, "addressOfGObject", argv, "o", "object",
      69                 :             :                              &target_obj))
      70                 :           0 :         return false;
      71                 :             : 
      72         [ +  + ]:          10 :     if (!ObjectBase::to_c_ptr(cx, target_obj, &obj)) {
      73                 :           1 :         gjs_throw(cx, "Object %p is not a GObject", &target_obj);
      74                 :           1 :         return false;
      75                 :             :     }
      76                 :             : 
      77                 :           9 :     GjsAutoChar pointer_string = g_strdup_printf("%p", obj);
      78                 :           9 :     return gjs_string_from_utf8(cx, pointer_string, argv.rval());
      79                 :          10 : }
      80                 :             : 
      81                 :             : static bool
      82                 :           3 : gjs_refcount(JSContext *context,
      83                 :             :              unsigned   argc,
      84                 :             :              JS::Value *vp)
      85                 :             : {
      86                 :           3 :     JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
      87                 :           3 :     JS::RootedObject target_obj(context);
      88                 :             :     GObject *obj;
      89                 :             : 
      90         [ -  + ]:           3 :     if (!gjs_parse_call_args(context, "refcount", argv, "o",
      91                 :             :                              "object", &target_obj))
      92                 :           0 :         return false;
      93                 :             : 
      94         [ -  + ]:           3 :     if (!ObjectBase::to_c_ptr(context, target_obj, &obj))
      95                 :           0 :         return false;
      96         [ -  + ]:           3 :     if (!obj) {
      97                 :             :         // Object already disposed, treat as refcount 0
      98                 :           0 :         argv.rval().setInt32(0);
      99                 :           0 :         return true;
     100                 :             :     }
     101                 :             : 
     102                 :           3 :     argv.rval().setInt32(obj->ref_count);
     103                 :           3 :     return true;
     104                 :           3 : }
     105                 :             : 
     106                 :             : static bool
     107                 :           0 : gjs_breakpoint(JSContext *context,
     108                 :             :                unsigned   argc,
     109                 :             :                JS::Value *vp)
     110                 :             : {
     111                 :           0 :     JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
     112         [ #  # ]:           0 :     if (!gjs_parse_call_args(context, "breakpoint", argv, ""))
     113                 :           0 :         return false;
     114                 :           0 :     G_BREAKPOINT();
     115                 :           0 :     argv.rval().setUndefined();
     116                 :           0 :     return true;
     117                 :             : }
     118                 :             : 
     119                 :             : // This can reduce performance, so should be used for debugging only.
     120                 :             : // js::CollectNurseryBeforeDump promotes any live objects in the nursery to the
     121                 :             : // tenured heap. This is slow, but this way, we are certain to get an accurate
     122                 :             : // picture of the heap.
     123                 :             : static bool
     124                 :           1 : gjs_dump_heap(JSContext *cx,
     125                 :             :               unsigned   argc,
     126                 :             :               JS::Value *vp)
     127                 :             : {
     128                 :           1 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     129                 :           1 :     GjsAutoChar filename;
     130                 :             : 
     131         [ -  + ]:           1 :     if (!gjs_parse_call_args(cx, "dumpHeap", args, "|F", "filename", &filename))
     132                 :           0 :         return false;
     133                 :             : 
     134                 :           1 :     LogFile file(filename);
     135         [ +  - ]:           1 :     if (file.has_error()) {
     136                 :           1 :         gjs_throw(cx, "Cannot dump heap to %s: %s", filename.get(),
     137                 :             :                   file.errmsg());
     138                 :           1 :         return false;
     139                 :             :     }
     140                 :           0 :     js::DumpHeap(cx, file.fp(), js::CollectNurseryBeforeDump);
     141                 :             : 
     142         [ #  # ]:           0 :     gjs_debug(GJS_DEBUG_CONTEXT, "Heap dumped to %s",
     143                 :           0 :               filename ? filename.get() : "stdout");
     144                 :             : 
     145                 :           0 :     args.rval().setUndefined();
     146                 :           0 :     return true;
     147                 :           1 : }
     148                 :             : 
     149                 :             : static bool
     150                 :          73 : gjs_gc(JSContext *context,
     151                 :             :        unsigned   argc,
     152                 :             :        JS::Value *vp)
     153                 :             : {
     154                 :          73 :     JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
     155         [ -  + ]:          73 :     if (!gjs_parse_call_args(context, "gc", argv, ""))
     156                 :           0 :         return false;
     157                 :          73 :     JS_GC(context);
     158                 :          73 :     argv.rval().setUndefined();
     159                 :          73 :     return true;
     160                 :             : }
     161                 :             : 
     162                 :             : static bool
     163                 :          25 : gjs_exit(JSContext *context,
     164                 :             :          unsigned   argc,
     165                 :             :          JS::Value *vp)
     166                 :             : {
     167                 :          25 :     JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
     168                 :             :     gint32 ecode;
     169         [ -  + ]:          25 :     if (!gjs_parse_call_args(context, "exit", argv, "i",
     170                 :             :                              "ecode", &ecode))
     171                 :           0 :         return false;
     172                 :             : 
     173                 :          25 :     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
     174                 :          25 :     gjs->exit(ecode);
     175                 :          25 :     return false;  /* without gjs_throw() == "throw uncatchable exception" */
     176                 :             : }
     177                 :             : 
     178                 :           0 : static bool gjs_clear_date_caches(JSContext*, unsigned argc, JS::Value* vp) {
     179                 :           0 :     JS::CallArgs rec = JS::CallArgsFromVp(argc, vp);
     180                 :             : 
     181                 :             :     // Workaround for a bug in SpiderMonkey where tzset is not called before
     182                 :             :     // localtime_r, see https://bugzilla.mozilla.org/show_bug.cgi?id=1004706
     183                 :           0 :     tzset();
     184                 :             : 
     185                 :           0 :     JS::ResetTimeZone();
     186                 :             : 
     187                 :           0 :     rec.rval().setUndefined();
     188                 :           0 :     return true;
     189                 :             : }
     190                 :             : 
     191                 :           1 : static bool write_gc_info(const char16_t* buf, uint32_t len, void* data) {
     192                 :           1 :     auto* fp = static_cast<FILE*>(data);
     193                 :             : 
     194                 :             :     long bytes_written;  // NOLINT(runtime/int): the GLib API requires this type
     195                 :             :     GjsAutoChar utf8 = g_utf16_to_utf8(reinterpret_cast<const uint16_t*>(buf),
     196                 :             :                                        len, /* items_read = */ nullptr,
     197                 :           1 :                                        &bytes_written, /* error = */ nullptr);
     198         [ -  + ]:           1 :     if (!utf8)
     199                 :           0 :         utf8 = g_strdup("<invalid string>");
     200                 :             : 
     201                 :           1 :     fwrite(utf8, 1, bytes_written, fp);
     202                 :           2 :     return true;
     203                 :           1 : }
     204                 :             : 
     205                 :           2 : static bool gjs_dump_memory_info(JSContext* cx, unsigned argc, JS::Value* vp) {
     206                 :           2 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     207                 :             : 
     208                 :           2 :     GjsAutoChar filename;
     209         [ -  + ]:           2 :     if (!gjs_parse_call_args(cx, "dumpMemoryInfo", args, "|F", "filename",
     210                 :             :                              &filename))
     211                 :           0 :         return false;
     212                 :             : 
     213                 :             :     int64_t gc_counters[Gjs::GCCounters::N_COUNTERS];
     214                 :             : 
     215                 :             :     // The object returned from NewMemoryInfoObject has gcBytes and mallocBytes
     216                 :             :     // properties which are the sum (over all zones) of bytes used. gcBytes is
     217                 :             :     // the number of bytes in garbage-collectable things (GC things).
     218                 :             :     // mallocBytes is the number of bytes allocated with malloc (reported with
     219                 :             :     // JS::AddAssociatedMemory).
     220                 :             :     //
     221                 :             :     // This info leaks internal state of the JS engine, which is why it is not
     222                 :             :     // returned to the caller, only dumped to a file and piped to Sysprof.
     223                 :             :     //
     224                 :             :     // The object also has a zone property with its own gcBytes and mallocBytes
     225                 :             :     // properties, representing the bytes used in the zone that the memory
     226                 :             :     // object belongs to. We only have one zone in GJS's context, so
     227                 :             :     // zone.gcBytes and zone.mallocBytes are a good measure for how much memory
     228                 :             :     // the actual user program is occupying. These are the values that we expose
     229                 :             :     // as counters in Sysprof. The difference between these values and the sum
     230                 :             :     // values is due to the self-hosting zone and atoms zone, that represent
     231                 :             :     // overhead of the JS engine.
     232                 :             : 
     233                 :           2 :     JS::RootedObject gc_info(cx, js::gc::NewMemoryInfoObject(cx));
     234         [ -  + ]:           2 :     if (!gc_info)
     235                 :           0 :         return false;
     236                 :             : 
     237                 :           2 :     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
     238                 :             :     int32_t val;
     239                 :           2 :     JS::RootedObject zone_info(cx);
     240                 :           2 :     if (!gjs_object_require_property(cx, gc_info, "gc.zone", atoms.zone(),
     241         [ +  - ]:           4 :                                      &zone_info) ||
     242   [ -  +  -  + ]:           4 :         !gjs_object_require_property(cx, zone_info, "gc.zone.gcBytes",
     243                 :             :                                      atoms.gc_bytes(), &val))
     244                 :           0 :         return false;
     245                 :           2 :     gc_counters[Gjs::GCCounters::GC_HEAP_BYTES] = int64_t(val);
     246         [ -  + ]:           2 :     if (!gjs_object_require_property(cx, zone_info, "gc.zone.mallocBytes",
     247                 :             :                                      atoms.malloc_bytes(), &val))
     248                 :           0 :         return false;
     249                 :           2 :     gc_counters[Gjs::GCCounters::MALLOC_HEAP_BYTES] = int64_t(val);
     250                 :             : 
     251                 :           2 :     auto* gjs = GjsContextPrivate::from_cx(cx);
     252   [ -  +  -  + ]:           2 :     if (gjs->profiler() &&
     253         [ #  # ]:           0 :         !_gjs_profiler_sample_gc_memory_info(gjs->profiler(), gc_counters)) {
     254                 :           0 :         gjs_throw(cx, "Could not write GC counters to profiler");
     255                 :           0 :         return false;
     256                 :             :     }
     257                 :             : 
     258                 :           2 :     LogFile file(filename);
     259         [ +  + ]:           2 :     if (file.has_error()) {
     260                 :           1 :         gjs_throw(cx, "Cannot dump memory info to %s: %s", filename.get(),
     261                 :             :                   file.errmsg());
     262                 :           1 :         return false;
     263                 :             :     }
     264                 :             : 
     265                 :           1 :     fprintf(file.fp(), "# GC Memory Info Object #\n\n```json\n");
     266                 :           1 :     JS::RootedValue v_gc_info(cx, JS::ObjectValue(*gc_info));
     267                 :           1 :     JS::RootedValue spacing(cx, JS::Int32Value(2));
     268         [ -  + ]:           1 :     if (!JS_Stringify(cx, &v_gc_info, nullptr, spacing, write_gc_info,
     269                 :           1 :                       file.fp()))
     270                 :           0 :         return false;
     271                 :           1 :     fprintf(file.fp(), "\n```\n");
     272                 :             : 
     273                 :           1 :     args.rval().setUndefined();
     274                 :           1 :     return true;
     275                 :           2 : }
     276                 :             : 
     277                 :             : static JSFunctionSpec module_funcs[] = {
     278                 :             :     JS_FN("addressOf", gjs_address_of, 1, GJS_MODULE_PROP_FLAGS),
     279                 :             :     JS_FN("addressOfGObject", gjs_address_of_gobject, 1, GJS_MODULE_PROP_FLAGS),
     280                 :             :     JS_FN("refcount", gjs_refcount, 1, GJS_MODULE_PROP_FLAGS),
     281                 :             :     JS_FN("breakpoint", gjs_breakpoint, 0, GJS_MODULE_PROP_FLAGS),
     282                 :             :     JS_FN("dumpHeap", gjs_dump_heap, 1, GJS_MODULE_PROP_FLAGS),
     283                 :             :     JS_FN("dumpMemoryInfo", gjs_dump_memory_info, 0, GJS_MODULE_PROP_FLAGS),
     284                 :             :     JS_FN("gc", gjs_gc, 0, GJS_MODULE_PROP_FLAGS),
     285                 :             :     JS_FN("exit", gjs_exit, 0, GJS_MODULE_PROP_FLAGS),
     286                 :             :     JS_FN("clearDateCaches", gjs_clear_date_caches, 0, GJS_MODULE_PROP_FLAGS),
     287                 :             :     JS_FS_END};
     288                 :             : 
     289                 :          71 : static bool get_program_args(JSContext* cx, unsigned argc, JS::Value* vp) {
     290                 :             :     static const size_t SLOT_ARGV = 0;
     291                 :             : 
     292                 :          71 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     293                 :          71 :     GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx);
     294                 :             : 
     295                 :             :     JS::RootedValue v_argv(
     296                 :          71 :         cx, js::GetFunctionNativeReserved(&args.callee(), SLOT_ARGV));
     297                 :             : 
     298         [ +  + ]:          71 :     if (v_argv.isUndefined()) {
     299                 :             :         // First time this property is accessed, build the array
     300                 :          63 :         JS::RootedObject argv(cx, priv->build_args_array());
     301         [ -  + ]:          63 :         if (!argv)
     302                 :           0 :             return false;
     303                 :          63 :         js::SetFunctionNativeReserved(&args.callee(), SLOT_ARGV,
     304                 :          63 :                                       JS::ObjectValue(*argv));
     305                 :          63 :         args.rval().setObject(*argv);
     306         [ +  - ]:          63 :     } else {
     307                 :           8 :         args.rval().set(v_argv);
     308                 :             :     }
     309                 :             : 
     310                 :          71 :     return true;
     311                 :          71 : }
     312                 :             : 
     313                 :             : bool
     314                 :          77 : gjs_js_define_system_stuff(JSContext              *context,
     315                 :             :                            JS::MutableHandleObject module)
     316                 :             : {
     317                 :          77 :     module.set(JS_NewPlainObject(context));
     318                 :             : 
     319         [ -  + ]:          77 :     if (!JS_DefineFunctions(context, module, &module_funcs[0]))
     320                 :           0 :         return false;
     321                 :             : 
     322                 :          77 :     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
     323                 :          77 :     const char* program_name = gjs->program_name();
     324                 :          77 :     const char* program_path = gjs->program_path();
     325                 :             : 
     326                 :          77 :     JS::RootedValue v_program_invocation_name(context);
     327                 :          77 :     JS::RootedValue v_program_path(context, JS::NullValue());
     328         [ +  + ]:          77 :     if (program_path) {
     329         [ -  + ]:           9 :         if (!gjs_string_from_utf8(context, program_path, &v_program_path))
     330                 :           0 :             return false;
     331                 :             :     }
     332                 :             : 
     333                 :             :     JS::RootedObject program_args_getter(
     334                 :             :         context,
     335                 :          77 :         JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
     336                 :         154 :             context, get_program_args, 0, 0, gjs->atoms().program_args())));
     337                 :             : 
     338         [ +  - ]:          77 :     return program_args_getter &&
     339                 :          77 :            gjs_string_from_utf8(context, program_name,
     340         [ +  - ]:         154 :                                 &v_program_invocation_name) &&
     341                 :             :            /* The name is modeled after program_invocation_name, part of glibc
     342                 :             :             */
     343                 :         154 :            JS_DefinePropertyById(context, module,
     344                 :          77 :                                  gjs->atoms().program_invocation_name(),
     345                 :             :                                  v_program_invocation_name,
     346         [ +  - ]:          77 :                                  GJS_MODULE_PROP_FLAGS | JSPROP_READONLY) &&
     347                 :          77 :            JS_DefinePropertyById(context, module, gjs->atoms().program_path(),
     348                 :             :                                  v_program_path,
     349         [ +  - ]:          77 :                                  GJS_MODULE_PROP_FLAGS | JSPROP_READONLY) &&
     350                 :          77 :            JS_DefinePropertyById(context, module, gjs->atoms().program_args(),
     351                 :             :                                  program_args_getter, nullptr,
     352   [ +  -  +  - ]:         154 :                                  GJS_MODULE_PROP_FLAGS) &&
     353                 :          77 :            JS_DefinePropertyById(context, module, gjs->atoms().version(),
     354                 :             :                                  GJS_VERSION,
     355                 :          77 :                                  GJS_MODULE_PROP_FLAGS | JSPROP_READONLY);
     356                 :          77 : }
        

Generated by: LCOV version 2.0-1