LCOV - code coverage report
Current view: top level - gjs - console.cpp (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 78.7 % 197 155
Test Date: 2024-04-16 04:37:39 Functions: 100.0 % 6 6
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 69.2 % 120 83

             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                 :             : 
       5                 :             : #include <config.h>  // for PACKAGE_STRING
       6                 :             : 
       7                 :             : #include <locale.h>  // for setlocale, LC_ALL
       8                 :             : #include <stdint.h>
       9                 :             : #include <stdlib.h>  // for EXIT_SUCCESS / EXIT_FAILURE
      10                 :             : #include <string.h>  // for strcmp, strlen
      11                 :             : 
      12                 :             : #ifdef HAVE_UNISTD_H
      13                 :             : #    include <unistd.h>  // for close
      14                 :             : #elif defined (_WIN32)
      15                 :             : #    include <io.h>
      16                 :             : #endif
      17                 :             : 
      18                 :             : #include <gio/gio.h>
      19                 :             : #include <glib-object.h>
      20                 :             : #include <glib.h>
      21                 :             : 
      22                 :             : #include <gjs/gjs.h>
      23                 :             : #include <gjs/jsapi-util.h>
      24                 :             : 
      25                 :             : static GjsAutoStrv include_path;
      26                 :             : static GjsAutoStrv coverage_prefixes;
      27                 :             : static GjsAutoChar coverage_output_path;
      28                 :             : static GjsAutoChar profile_output_path;
      29                 :             : static GjsAutoChar command;
      30                 :             : static gboolean print_version = false;
      31                 :             : static gboolean print_js_version = false;
      32                 :             : static gboolean debugging = false;
      33                 :             : static gboolean exec_as_module = false;
      34                 :             : static bool enable_profiler = false;
      35                 :             : 
      36                 :             : static gboolean parse_profile_arg(const char *, const char *, void *, GError **);
      37                 :             : 
      38                 :             : using GjsAutoGOptionContext =
      39                 :             :     GjsAutoPointer<GOptionContext, GOptionContext, g_option_context_free>;
      40                 :             : 
      41                 :             : // clang-format off
      42                 :             : static GOptionEntry entries[] = {
      43                 :             :     { "version", 0, 0, G_OPTION_ARG_NONE, &print_version, "Print GJS version and exit" },
      44                 :             :     { "jsversion", 0, 0, G_OPTION_ARG_NONE, &print_js_version,
      45                 :             :         "Print version of the JS engine and exit" },
      46                 :             :     { "command", 'c', 0, G_OPTION_ARG_STRING, command.out(), "Program passed in as a string", "COMMAND" },
      47                 :             :     { "coverage-prefix", 'C', 0, G_OPTION_ARG_STRING_ARRAY, coverage_prefixes.out(),
      48                 :             :       "Add the prefix PREFIX to the list of files to generate coverage info for", "PREFIX" },
      49                 :             :     { "coverage-output", 0, 0, G_OPTION_ARG_STRING, coverage_output_path.out(),
      50                 :             :       "Write coverage output to a directory DIR. This option is mandatory when using --coverage-prefix", "DIR", },
      51                 :             :     { "include-path", 'I', 0, G_OPTION_ARG_STRING_ARRAY, include_path.out(), "Add the directory DIR to the list of directories to search for js files.", "DIR" },
      52                 :             :     { "module", 'm', 0, G_OPTION_ARG_NONE, &exec_as_module, "Execute the file as a module." },
      53                 :             :     { "profile", 0, G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME,
      54                 :             :         G_OPTION_ARG_CALLBACK, reinterpret_cast<void *>(&parse_profile_arg),
      55                 :             :         "Enable the profiler and write output to FILE (default: gjs-$PID.syscap)",
      56                 :             :         "FILE" },
      57                 :             :     { "debugger", 'd', 0, G_OPTION_ARG_NONE, &debugging, "Start in debug mode" },
      58                 :             :     { NULL }
      59                 :             : };
      60                 :             : // clang-format on
      61                 :             : 
      62                 :          67 : [[nodiscard]] static GjsAutoStrv strndupv(int n, char* const* strv) {
      63                 :             : #if GLIB_CHECK_VERSION(2, 68, 0)
      64                 :             :     GjsAutoPointer<GStrvBuilder, GStrvBuilder, g_strv_builder_unref> builder(
      65                 :          67 :         g_strv_builder_new());
      66                 :             : 
      67         [ +  + ]:         257 :     for (int i = 0; i < n; ++i)
      68                 :         190 :         g_strv_builder_add(builder, strv[i]);
      69                 :             : 
      70                 :          67 :     return g_strv_builder_end(builder);
      71                 :             : 
      72                 :             : #else
      73                 :             : 
      74                 :             :     int ix;
      75                 :             :     if (n == 0)
      76                 :             :         return NULL;
      77                 :             :     char **retval = g_new(char *, n + 1);
      78                 :             :     for (ix = 0; ix < n; ix++)
      79                 :             :         retval[ix] = g_strdup(strv[ix]);
      80                 :             :     retval[n] = NULL;
      81                 :             :     return retval;
      82                 :             : #endif  // GLIB_CHECK_VERSION(2, 68, 0)
      83                 :          67 : }
      84                 :             : 
      85                 :           3 : [[nodiscard]] static GjsAutoStrv strcatv(char** strv1, char** strv2) {
      86   [ +  -  -  + ]:           3 :     if (strv1 == NULL && strv2 == NULL)
      87                 :           0 :         return NULL;
      88         [ +  - ]:           3 :     if (strv1 == NULL)
      89                 :           3 :         return g_strdupv(strv2);
      90         [ #  # ]:           0 :     if (strv2 == NULL)
      91                 :           0 :         return g_strdupv(strv1);
      92                 :             : 
      93                 :             : #if GLIB_CHECK_VERSION(2, 70, 0)
      94                 :             :     GjsAutoPointer<GStrvBuilder, GStrvBuilder, g_strv_builder_unref> builder(
      95                 :           0 :         g_strv_builder_new());
      96                 :             : 
      97                 :           0 :     g_strv_builder_addv(builder, const_cast<const char**>(strv1));
      98                 :           0 :     g_strv_builder_addv(builder, const_cast<const char**>(strv2));
      99                 :             : 
     100                 :           0 :     return g_strv_builder_end(builder);
     101                 :             : 
     102                 :             : #else
     103                 :             : 
     104                 :             :     unsigned len1 = g_strv_length(strv1);
     105                 :             :     unsigned len2 = g_strv_length(strv2);
     106                 :             :     char **retval = g_new(char *, len1 + len2 + 1);
     107                 :             :     unsigned ix;
     108                 :             : 
     109                 :             :     for (ix = 0; ix < len1; ix++)
     110                 :             :         retval[ix] = g_strdup(strv1[ix]);
     111                 :             :     for (ix = 0; ix < len2; ix++)
     112                 :             :         retval[len1 + ix] = g_strdup(strv2[ix]);
     113                 :             :     retval[len1 + len2] = NULL;
     114                 :             : 
     115                 :             :     return retval;
     116                 :             : #endif  // GLIB_CHECK_VERSION(2, 70, 0)
     117                 :           0 : }
     118                 :             : 
     119                 :           6 : static gboolean parse_profile_arg(const char* option_name [[maybe_unused]],
     120                 :             :                                   const char* value, void*, GError**) {
     121                 :           6 :     enable_profiler = true;
     122                 :           6 :     profile_output_path = GjsAutoChar(value, GjsAutoTakeOwnership());
     123                 :           6 :     return true;
     124                 :             : }
     125                 :             : 
     126                 :             : static void
     127                 :          53 : check_script_args_for_stray_gjs_args(int           argc,
     128                 :             :                                      char * const *argv)
     129                 :             : {
     130                 :          53 :     GjsAutoError error;
     131                 :          53 :     GjsAutoStrv new_coverage_prefixes;
     132                 :          53 :     GjsAutoChar new_coverage_output_path;
     133                 :          53 :     GjsAutoStrv new_include_paths;
     134                 :             :     // Don't add new entries here. This is only for arguments that were
     135                 :             :     // previously accepted after the script name on the command line, for
     136                 :             :     // backwards compatibility.
     137                 :             :     // clang-format off
     138                 :          53 :     GOptionEntry script_check_entries[] = {
     139                 :          53 :         { "coverage-prefix", 'C', 0, G_OPTION_ARG_STRING_ARRAY, new_coverage_prefixes.out() },
     140                 :          53 :         { "coverage-output", 0, 0, G_OPTION_ARG_STRING, new_coverage_output_path.out() },
     141                 :          53 :         { "include-path", 'I', 0, G_OPTION_ARG_STRING_ARRAY, new_include_paths.out() },
     142                 :             :         { NULL }
     143                 :          53 :     };
     144                 :             :     // clang-format on
     145                 :             : 
     146                 :          53 :     GjsAutoStrv argv_copy = g_new(char*, argc + 2);
     147                 :             :     int ix;
     148                 :             : 
     149                 :          53 :     argv_copy[0] = g_strdup("dummy"); /* Fake argv[0] for GOptionContext */
     150         [ +  + ]:          70 :     for (ix = 0; ix < argc; ix++)
     151                 :          34 :         argv_copy[ix + 1] = g_strdup(argv[ix]);
     152                 :          53 :     argv_copy[argc + 1] = NULL;
     153                 :             : 
     154                 :          53 :     GjsAutoGOptionContext script_options = g_option_context_new(NULL);
     155                 :          53 :     g_option_context_set_ignore_unknown_options(script_options, true);
     156                 :          53 :     g_option_context_set_help_enabled(script_options, false);
     157                 :          53 :     g_option_context_add_main_entries(script_options, script_check_entries, NULL);
     158         [ -  + ]:          53 :     if (!g_option_context_parse_strv(script_options, argv_copy.out(), &error)) {
     159                 :           0 :         g_warning("Scanning script arguments failed: %s", error->message);
     160                 :           0 :         return;
     161                 :             :     }
     162                 :             : 
     163         [ +  + ]:          53 :     if (new_coverage_prefixes) {
     164                 :           2 :         g_warning("You used the --coverage-prefix option after the script on "
     165                 :             :                   "the GJS command line. Support for this will be removed in a "
     166                 :             :                   "future version. Place the option before the script or use "
     167                 :             :                   "the GJS_COVERAGE_PREFIXES environment variable.");
     168                 :           2 :         coverage_prefixes = strcatv(coverage_prefixes, new_coverage_prefixes);
     169                 :             :     }
     170         [ +  + ]:          53 :     if (new_include_paths) {
     171                 :           1 :         g_warning("You used the --include-path option after the script on the "
     172                 :             :                   "GJS command line. Support for this will be removed in a "
     173                 :             :                   "future version. Place the option before the script or use "
     174                 :             :                   "the GJS_PATH environment variable.");
     175                 :           1 :         include_path = strcatv(include_path, new_include_paths);
     176                 :             :     }
     177         [ +  + ]:          53 :     if (new_coverage_output_path) {
     178                 :           2 :         g_warning(
     179                 :             :             "You used the --coverage-output option after the script on "
     180                 :             :             "the GJS command line. Support for this will be removed in a "
     181                 :             :             "future version. Place the option before the script or use "
     182                 :             :             "the GJS_COVERAGE_OUTPUT environment variable.");
     183                 :           2 :         coverage_output_path = new_coverage_output_path;
     184                 :             :     }
     185   [ +  -  +  -  :          53 : }
          +  -  +  -  +  
                -  +  - ]
     186                 :             : 
     187                 :          53 : int define_argv_and_eval_script(GjsContext* js_context, int argc,
     188                 :             :                                 char* const* argv, const char* script,
     189                 :             :                                 size_t len, const char* filename) {
     190                 :          53 :     gjs_context_set_argv(js_context, argc, const_cast<const char**>(argv));
     191                 :             : 
     192                 :          53 :     GjsAutoError error;
     193                 :             :     /* evaluate the script */
     194                 :          53 :     int code = 0;
     195         [ +  + ]:          53 :     if (exec_as_module) {
     196                 :           2 :         GjsAutoUnref<GFile> output = g_file_new_for_commandline_arg(filename);
     197                 :           2 :         GjsAutoChar uri = g_file_get_uri(output);
     198         [ -  + ]:           2 :         if (!gjs_context_register_module(js_context, uri, uri, &error)) {
     199                 :           0 :             g_critical("%s", error->message);
     200                 :           0 :             code = 1;
     201                 :             :         }
     202                 :             : 
     203                 :           2 :         uint8_t code_u8 = 0;
     204   [ +  -  +  - ]:           3 :         if (!code &&
     205         [ +  - ]:           2 :             !gjs_context_eval_module(js_context, uri, &code_u8, &error)) {
     206                 :           1 :             code = code_u8;
     207                 :             : 
     208         [ -  + ]:           1 :             if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT))
     209                 :           0 :                 g_critical("%s", error->message);
     210                 :             :         }
     211         [ +  + ]:          52 :     } else if (!gjs_context_eval(js_context, script, len, filename, &code,
     212                 :             :                                  &error)) {
     213         [ -  + ]:          21 :         if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT))
     214                 :           0 :             g_critical("%s", error->message);
     215                 :             :     }
     216                 :         102 :     return code;
     217                 :          51 : }
     218                 :             : 
     219                 :          67 : int main(int argc, char** argv) {
     220                 :          67 :     GjsAutoError error;
     221                 :             :     const char *filename;
     222                 :             :     const char *program_name;
     223                 :             :     gsize len;
     224                 :          67 :     int gjs_argc = argc, script_argc, ix;
     225                 :             :     char * const *script_argv;
     226                 :             :     const char *env_coverage_output_path;
     227                 :          67 :     bool interactive_mode = false;
     228                 :             : 
     229                 :          67 :     setlocale(LC_ALL, "");
     230                 :             : 
     231                 :          67 :     GjsAutoGOptionContext context = g_option_context_new(NULL);
     232                 :          67 :     g_option_context_set_ignore_unknown_options(context, true);
     233                 :          67 :     g_option_context_set_help_enabled(context, false);
     234                 :             : 
     235                 :          67 :     GjsAutoStrv argv_copy_addr(g_strdupv(argv));
     236                 :          67 :     char** argv_copy = argv_copy_addr;
     237                 :             : 
     238                 :          67 :     g_option_context_add_main_entries(context, entries, NULL);
     239         [ -  + ]:          67 :     if (!g_option_context_parse_strv(context, &argv_copy, &error))
     240                 :           0 :         g_error("option parsing failed: %s", error->message);
     241                 :             : 
     242                 :             :     /* Split options so we pass unknown ones through to the JS script */
     243                 :          67 :     int argc_copy = g_strv_length(argv_copy);
     244         [ +  + ]:         106 :     for (ix = 1; ix < argc; ix++) {
     245                 :             :         /* Check if a file was given and split after it */
     246   [ +  +  +  + ]:         102 :         if (argc_copy >= 2 && strcmp(argv[ix], argv_copy[1]) == 0) {
     247                 :             :             /* Filename given; split after this argument */
     248                 :          42 :             gjs_argc = ix + 1;
     249                 :          42 :             break;
     250                 :             :         }
     251                 :             : 
     252                 :             :         /* Check if -c or --command was given and split after following arg */
     253   [ +  +  +  +  :          63 :         if (command && (strcmp(argv[ix], "-c") == 0 ||
                   +  + ]
     254         [ -  + ]:          63 :                         strcmp(argv[ix], "--command") == 0)) {
     255                 :          21 :             gjs_argc = ix + 2;
     256                 :          21 :             break;
     257                 :             :         }
     258                 :             :     }
     259                 :          67 :     GjsAutoStrv gjs_argv_addr = strndupv(gjs_argc, argv);
     260                 :          67 :     char** gjs_argv = gjs_argv_addr;
     261                 :          67 :     script_argc = argc - gjs_argc;
     262                 :          67 :     script_argv = argv + gjs_argc;
     263                 :             : 
     264                 :             :     /* Parse again, only the GJS options this time */
     265                 :          67 :     include_path.release();
     266                 :          67 :     coverage_prefixes.release();
     267                 :          67 :     coverage_output_path.release();
     268                 :          67 :     command.release();
     269                 :          67 :     print_version = false;
     270                 :          67 :     print_js_version = false;
     271                 :          67 :     debugging = false;
     272                 :          67 :     exec_as_module = false;
     273                 :          67 :     g_option_context_set_ignore_unknown_options(context, false);
     274                 :          67 :     g_option_context_set_help_enabled(context, true);
     275         [ +  + ]:          67 :     if (!g_option_context_parse_strv(context, &gjs_argv, &error)) {
     276                 :             :         GjsAutoChar help_text =
     277                 :           2 :             g_option_context_get_help(context, true, nullptr);
     278                 :           2 :         g_printerr("%s\n\n%s\n", error->message, help_text.get());
     279                 :           2 :         return EXIT_FAILURE;
     280                 :           2 :     }
     281                 :             : 
     282         [ +  + ]:          57 :     if (print_version) {
     283                 :           2 :         g_print("%s\n", PACKAGE_STRING);
     284                 :           2 :         return EXIT_SUCCESS;
     285                 :             :     }
     286                 :             : 
     287         [ +  + ]:          55 :     if (print_js_version) {
     288                 :           2 :         g_print("%s\n", gjs_get_js_version());
     289                 :           2 :         return EXIT_SUCCESS;
     290                 :             :     }
     291                 :             : 
     292                 :          53 :     GjsAutoChar program_path = nullptr;
     293                 :          53 :     gjs_argc = g_strv_length(gjs_argv);
     294                 :          53 :     GjsAutoChar script;
     295         [ +  + ]:          53 :     if (command) {
     296                 :          21 :         script = command;
     297                 :          21 :         len = strlen(script);
     298                 :          21 :         filename = "<command line>";
     299                 :          21 :         program_name = gjs_argv[0];
     300         [ -  + ]:          32 :     } else if (gjs_argc == 1) {
     301         [ #  # ]:           0 :         if (exec_as_module) {
     302                 :           0 :             g_warning(
     303                 :             :                 "'-m' requires a file argument.\nExample: gjs -m main.js");
     304                 :           0 :             return EXIT_FAILURE;
     305                 :             :         }
     306                 :             : 
     307                 :           0 :         script = g_strdup("const Console = imports.console; Console.interact();");
     308                 :           0 :         len = strlen(script);
     309                 :           0 :         filename = "<stdin>";
     310                 :           0 :         program_name = gjs_argv[0];
     311                 :           0 :         interactive_mode = true;
     312                 :             :     } else {
     313                 :             :         /* All unprocessed options should be in script_argv */
     314                 :          32 :         g_assert(gjs_argc == 2);
     315                 :          32 :         GjsAutoUnref<GFile> input = g_file_new_for_commandline_arg(gjs_argv[1]);
     316         [ -  + ]:          32 :         if (!g_file_load_contents(input, nullptr, script.out(), &len, nullptr,
     317                 :             :                                   &error)) {
     318                 :           0 :             g_printerr("%s\n", error->message);
     319                 :           0 :             return EXIT_FAILURE;
     320                 :             :         }
     321                 :          32 :         program_path = g_file_get_path(input);
     322                 :          32 :         filename = gjs_argv[1];
     323                 :          32 :         program_name = gjs_argv[1];
     324         [ +  - ]:          32 :     }
     325                 :             : 
     326                 :             :     /* This should be removed after a suitable time has passed */
     327                 :          53 :     check_script_args_for_stray_gjs_args(script_argc, script_argv);
     328                 :             : 
     329                 :             :     /* Check for GJS_TRACE_FD for sysprof profiling */
     330                 :          53 :     const char* env_tracefd = g_getenv("GJS_TRACE_FD");
     331                 :          53 :     int tracefd = -1;
     332         [ -  + ]:          53 :     if (env_tracefd) {
     333                 :           0 :         tracefd = g_ascii_strtoll(env_tracefd, nullptr, 10);
     334                 :           0 :         g_setenv("GJS_TRACE_FD", "", true);
     335         [ #  # ]:           0 :         if (tracefd > 0)
     336                 :           0 :             enable_profiler = true;
     337                 :             :     }
     338                 :             : 
     339   [ -  +  -  - ]:          53 :     if (interactive_mode && enable_profiler) {
     340                 :           0 :         g_message("Profiler disabled in interactive mode.");
     341                 :           0 :         enable_profiler = false;
     342                 :           0 :         g_unsetenv("GJS_ENABLE_PROFILER");  /* ignore env var in eval() */
     343                 :           0 :         g_unsetenv("GJS_TRACE_FD");         /* ignore env var in eval() */
     344                 :             :     }
     345                 :             : 
     346                 :          53 :     const char* env_coverage_prefixes = g_getenv("GJS_COVERAGE_PREFIXES");
     347         [ -  + ]:          53 :     if (env_coverage_prefixes)
     348                 :           0 :         coverage_prefixes = g_strsplit(env_coverage_prefixes, ":", -1);
     349                 :             : 
     350         [ +  + ]:          53 :     if (coverage_prefixes)
     351                 :           3 :         gjs_coverage_enable();
     352                 :             : 
     353                 :             :     GjsAutoUnref<GjsContext> js_context(GJS_CONTEXT(g_object_new(
     354                 :             :         GJS_TYPE_CONTEXT, "search-path", include_path.get(), "program-name",
     355                 :             :         program_name, "program-path", program_path.get(), "profiler-enabled",
     356                 :          53 :         enable_profiler, "exec-as-module", exec_as_module, nullptr)));
     357                 :             : 
     358                 :          53 :     env_coverage_output_path = g_getenv("GJS_COVERAGE_OUTPUT");
     359         [ -  + ]:          53 :     if (env_coverage_output_path != NULL) {
     360                 :           0 :         g_free(coverage_output_path);
     361                 :           0 :         coverage_output_path = g_strdup(env_coverage_output_path);
     362                 :             :     }
     363                 :             : 
     364                 :          53 :     GjsAutoUnref<GjsCoverage> coverage;
     365         [ +  + ]:          53 :     if (coverage_prefixes) {
     366         [ -  + ]:           3 :         if (!coverage_output_path)
     367                 :           0 :             g_error("--coverage-output is required when taking coverage statistics");
     368                 :             : 
     369                 :             :         GjsAutoUnref<GFile> output =
     370                 :           3 :             g_file_new_for_commandline_arg(coverage_output_path);
     371                 :           3 :         coverage = gjs_coverage_new(coverage_prefixes, js_context, output);
     372                 :           3 :     }
     373                 :             : 
     374   [ +  +  +  +  :          53 :     if (enable_profiler && profile_output_path) {
                   +  + ]
     375                 :           1 :         GjsProfiler *profiler = gjs_context_get_profiler(js_context);
     376                 :           1 :         gjs_profiler_set_filename(profiler, profile_output_path);
     377   [ +  +  -  + ]:          52 :     } else if (enable_profiler && tracefd > -1) {
     378                 :           0 :         GjsProfiler* profiler = gjs_context_get_profiler(js_context);
     379                 :           0 :         gjs_profiler_set_fd(profiler, tracefd);
     380                 :           0 :         tracefd = -1;
     381                 :             :     }
     382                 :             : 
     383         [ -  + ]:          53 :     if (tracefd != -1) {
     384                 :           0 :         close(tracefd);
     385                 :           0 :         tracefd = -1;
     386                 :             :     }
     387                 :             : 
     388                 :             :     /* If we're debugging, set up the debugger. It will break on the first
     389                 :             :      * frame. */
     390         [ +  + ]:          53 :     if (debugging)
     391                 :          20 :         gjs_context_setup_debugger_console(js_context);
     392                 :             : 
     393                 :          53 :     int code = define_argv_and_eval_script(js_context, script_argc, script_argv,
     394                 :             :                                            script, len, filename);
     395                 :             : 
     396                 :             :     /* Probably doesn't make sense to write statistics on failure */
     397   [ +  +  +  -  :          51 :     if (coverage && code == 0)
                   +  + ]
     398                 :           3 :         gjs_coverage_write_statistics(coverage);
     399                 :             : 
     400         [ +  + ]:          51 :     if (debugging)
     401                 :          20 :         g_print("Program exited with code %d\n", code);
     402                 :             : 
     403                 :          51 :     return code;
     404                 :          57 : }
        

Generated by: LCOV version 2.0-1