diff --git a/gio/gapplicationcommandline.c b/gio/gapplicationcommandline.c index 741aa97f1..d7be108bf 100644 --- a/gio/gapplicationcommandline.c +++ b/gio/gapplicationcommandline.c @@ -106,7 +106,7 @@ * The complete example can be found here: * [gapplication-example-cmdline.c](https://gitlab.gnome.org/GNOME/glib/-/blob/HEAD/gio/tests/gapplication-example-cmdline.c) * - * In more complicated cases, the handling of the comandline can be + * In more complicated cases, the handling of the commandline can be * split between the launcher and the primary instance. * |[ * static gboolean @@ -119,6 +119,12 @@ * * argv = *arguments; * + * if (argv[0] == NULL) + * { + * *exit_status = 0; + * return FALSE; + * } + * * i = 1; * while (argv[i]) * { diff --git a/gio/gdbus-tool.c b/gio/gdbus-tool.c index 42991886a..476056b9f 100644 --- a/gio/gdbus-tool.c +++ b/gio/gdbus-tool.c @@ -107,7 +107,7 @@ usage (gint *argc, gchar **argv[], gboolean use_stdout) g_option_context_set_help_enabled (o, FALSE); /* Ignore parsing result */ g_option_context_parse (o, argc, argv, NULL); - program_name = g_path_get_basename ((*argv)[0]); + program_name = (*argc > 0) ? g_path_get_basename ((*argv)[0]) : g_strdup ("gdbus-tool"); s = g_strdup_printf (_("Commands:\n" " help Shows this information\n" " introspect Introspect a remote object\n" @@ -141,6 +141,7 @@ modify_argv0_for_command (gint *argc, gchar **argv[], const gchar *command) * 2. save old argv[0] and restore later */ + g_assert (*argc > 1); g_assert (g_strcmp0 ((*argv)[1], command) == 0); remove_arg (1, argc, argv); diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 058ddca2a..0d0b2c6cc 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -1880,6 +1880,10 @@ g_desktop_app_info_load_from_keyfile (GDesktopAppInfo *info, else { char *t; + + /* Since @exec is not an empty string, there must be at least one + * argument, so dereferencing argv[0] should return non-NULL. */ + g_assert (argc > 0); t = g_find_program_in_path (argv[0]); g_strfreev (argv); diff --git a/gio/gio-querymodules.c b/gio/gio-querymodules.c index cbeb9758e..aff194b73 100644 --- a/gio/gio-querymodules.c +++ b/gio/gio-querymodules.c @@ -161,7 +161,7 @@ main (gint argc, { int i; - if (argc == 1) + if (argc <= 1) { g_print ("Usage: gio-querymodules [ ...]\n"); g_print ("Will update giomodule.cache in the listed directories\n"); diff --git a/glib/deprecated/gcompletion.c b/glib/deprecated/gcompletion.c index d09c1f1fa..5f0979bb0 100644 --- a/glib/deprecated/gcompletion.c +++ b/glib/deprecated/gcompletion.c @@ -463,7 +463,8 @@ main (int argc, if (argc < 3) { - g_warning ("Usage: %s filename prefix1 [prefix2 ...]", argv[0]); + g_warning ("Usage: %s filename prefix1 [prefix2 ...]", + (argc > 0) ? argv[0] : "gcompletion"); return 1; } diff --git a/glib/gshell.c b/glib/gshell.c index 33c53497a..ed893905e 100644 --- a/glib/gshell.c +++ b/glib/gshell.c @@ -646,6 +646,11 @@ tokenize_command_line (const gchar *command_line, * * Possible errors are those from the %G_SHELL_ERROR domain. * + * In particular, if @command_line is an empty string (or a string containing + * only whitespace), %G_SHELL_ERROR_EMPTY_STRING will be returned. It’s + * guaranteed that @argvp will be a non-empty array if this function returns + * successfully. + * * Free the returned vector with g_strfreev(). * * Returns: %TRUE on success, %FALSE if error set @@ -702,7 +707,10 @@ g_shell_parse_argv (const gchar *command_line, } g_slist_free_full (tokens, g_free); - + + g_assert (argc > 0); + g_assert (argv != NULL && argv[0] != NULL); + if (argcp) *argcp = argc; diff --git a/glib/gspawn-win32.c b/glib/gspawn-win32.c index d3864d54d..30812dba6 100644 --- a/glib/gspawn-win32.c +++ b/glib/gspawn-win32.c @@ -236,7 +236,7 @@ g_spawn_async (const gchar *working_directory, GPid *child_pid, GError **error) { - g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (argv != NULL && argv[0] != NULL, FALSE); return g_spawn_async_with_pipes (working_directory, argv, envp, @@ -460,6 +460,8 @@ do_spawn_directly (gint *exit_status, gint conv_error_index; wchar_t *wargv0, **wargv, **wenvp; + g_assert (argv != NULL && argv[0] != NULL); + new_argv = (flags & G_SPAWN_FILE_AND_ARGV_ZERO) ? protected_argv + 1 : protected_argv; wargv0 = g_utf8_to_utf16 (argv[0], -1, NULL, NULL, &conv_error); @@ -601,6 +603,7 @@ fork_exec (gint *exit_status, int stdout_pipe[2] = { -1, -1 }; int stderr_pipe[2] = { -1, -1 }; + g_assert (argv != NULL && argv[0] != NULL); g_assert (stdin_pipe_out == NULL || stdin_fd < 0); g_assert (stdout_pipe_out == NULL || stdout_fd < 0); g_assert (stderr_pipe_out == NULL || stderr_fd < 0); @@ -989,7 +992,7 @@ g_spawn_sync (const gchar *working_directory, gboolean failed; gint status; - g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (argv != NULL && argv[0] != NULL, FALSE); g_return_val_if_fail (!(flags & G_SPAWN_DO_NOT_REAP_CHILD), FALSE); g_return_val_if_fail (standard_output == NULL || !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); @@ -1221,7 +1224,7 @@ g_spawn_async_with_pipes (const gchar *working_directory, gint *standard_error, GError **error) { - g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (argv != NULL && argv[0] != NULL, FALSE); g_return_val_if_fail (standard_output == NULL || !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); g_return_val_if_fail (standard_error == NULL || @@ -1263,7 +1266,7 @@ g_spawn_async_with_fds (const gchar *working_directory, gint stderr_fd, GError **error) { - g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (argv != NULL && argv[0] != NULL, FALSE); g_return_val_if_fail (stdin_fd == -1 || !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); g_return_val_if_fail (stderr_fd == -1 || @@ -1312,7 +1315,7 @@ g_spawn_async_with_pipes_and_fds (const gchar *working_directory, gint *stderr_pipe_out, GError **error) { - g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (argv != NULL && argv[0] != NULL, FALSE); g_return_val_if_fail (stdout_pipe_out == NULL || !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); g_return_val_if_fail (stderr_pipe_out == NULL || @@ -1359,6 +1362,7 @@ g_spawn_command_line_sync (const gchar *command_line, g_return_val_if_fail (command_line != NULL, FALSE); + /* This will return a runtime error if @command_line is the empty string. */ if (!g_shell_parse_argv (command_line, NULL, &argv, error)) @@ -1388,6 +1392,7 @@ g_spawn_command_line_async (const gchar *command_line, g_return_val_if_fail (command_line != NULL, FALSE); + /* This will return a runtime error if @command_line is the empty string. */ if (!g_shell_parse_argv (command_line, NULL, &argv, error)) diff --git a/glib/gspawn.c b/glib/gspawn.c index 1128221cc..cce03f81d 100644 --- a/glib/gspawn.c +++ b/glib/gspawn.c @@ -319,7 +319,7 @@ read_data (GString *str, * @working_directory: (type filename) (nullable): child's current working * directory, or %NULL to inherit parent's * @argv: (array zero-terminated=1) (element-type filename): - * child's argument vector + * child's argument vector, which must be non-empty and %NULL-terminated * @envp: (array zero-terminated=1) (element-type filename) (nullable): * child's environment, or %NULL to inherit parent's * @flags: flags from #GSpawnFlags @@ -378,6 +378,7 @@ g_spawn_sync (const gchar *working_directory, gint status; g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (argv[0] != NULL, FALSE); g_return_val_if_fail (!(flags & G_SPAWN_DO_NOT_REAP_CHILD), FALSE); g_return_val_if_fail (standard_output == NULL || !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); @@ -578,7 +579,7 @@ g_spawn_sync (const gchar *working_directory, * @working_directory: (type filename) (nullable): child's current working * directory, or %NULL to inherit parent's, in the GLib file name encoding * @argv: (array zero-terminated=1) (element-type filename): child's argument - * vector, in the GLib file name encoding + * vector, in the GLib file name encoding; it must be non-empty and %NULL-terminated * @envp: (array zero-terminated=1) (element-type filename) (nullable): * child's environment, or %NULL to inherit parent's, in the GLib file * name encoding @@ -610,6 +611,7 @@ g_spawn_async_with_pipes (const gchar *working_directory, GError **error) { g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (argv[0] != NULL, FALSE); g_return_val_if_fail (standard_output == NULL || !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); g_return_val_if_fail (standard_error == NULL || @@ -646,7 +648,7 @@ g_spawn_async_with_pipes (const gchar *working_directory, * @working_directory: (type filename) (nullable): child's current working * directory, or %NULL to inherit parent's, in the GLib file name encoding * @argv: (array zero-terminated=1) (element-type filename): child's argument - * vector, in the GLib file name encoding + * vector, in the GLib file name encoding; it must be non-empty and %NULL-terminated * @envp: (array zero-terminated=1) (element-type filename) (nullable): * child's environment, or %NULL to inherit parent's, in the GLib file * name encoding @@ -880,6 +882,7 @@ g_spawn_async_with_pipes_and_fds (const gchar *working_directory, GError **error) { g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (argv[0] != NULL, FALSE); g_return_val_if_fail (stdout_pipe_out == NULL || !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); g_return_val_if_fail (stderr_pipe_out == NULL || @@ -922,7 +925,8 @@ g_spawn_async_with_pipes_and_fds (const gchar *working_directory, /** * g_spawn_async_with_fds: * @working_directory: (type filename) (nullable): child's current working directory, or %NULL to inherit parent's, in the GLib file name encoding - * @argv: (array zero-terminated=1): child's argument vector, in the GLib file name encoding + * @argv: (array zero-terminated=1): child's argument vector, in the GLib file name encoding; + * it must be non-empty and %NULL-terminated * @envp: (array zero-terminated=1) (nullable): child's environment, or %NULL to inherit parent's, in the GLib file name encoding * @flags: flags from #GSpawnFlags * @child_setup: (scope async) (nullable): function to run in the child just before exec() @@ -956,6 +960,7 @@ g_spawn_async_with_fds (const gchar *working_directory, GError **error) { g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (argv[0] != NULL, FALSE); g_return_val_if_fail (stdout_fd < 0 || !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); g_return_val_if_fail (stderr_fd < 0 || @@ -1039,6 +1044,7 @@ g_spawn_command_line_sync (const gchar *command_line, g_return_val_if_fail (command_line != NULL, FALSE); + /* This will return a runtime error if @command_line is the empty string. */ if (!g_shell_parse_argv (command_line, NULL, &argv, error)) @@ -1086,6 +1092,7 @@ g_spawn_command_line_async (const gchar *command_line, g_return_val_if_fail (command_line != NULL, FALSE); + /* This will return a runtime error if @command_line is the empty string. */ if (!g_shell_parse_argv (command_line, NULL, &argv, error)) @@ -1636,7 +1643,9 @@ enum }; /* This function is called between fork() and exec() and hence must be - * async-signal-safe (see signal-safety(7)) until it calls exec(). */ + * async-signal-safe (see signal-safety(7)) until it calls exec(). + * + * All callers must guarantee that @argv and @argv[0] are non-NULL. */ static void do_exec (gint child_err_report_fd, gint stdin_fd, @@ -1926,6 +1935,8 @@ do_posix_spawn (const gchar * const *argv, gsize i; int r; + g_assert (argv != NULL && argv[0] != NULL); + if (*argv[0] == '\0') { /* We check the simple case first. */ @@ -2176,6 +2187,7 @@ fork_exec (gboolean intermediate_child, gint n_child_close_fds = 0; gint *source_fds_copy = NULL; + g_assert (argv != NULL && argv[0] != NULL); g_assert (stdin_pipe_out == NULL || stdin_fd < 0); g_assert (stdout_pipe_out == NULL || stdout_fd < 0); g_assert (stderr_pipe_out == NULL || stderr_fd < 0); @@ -2734,7 +2746,7 @@ g_execute (const gchar *file, gchar *search_path_buffer, gsize search_path_buffer_len) { - if (*file == '\0') + if (file == NULL || *file == '\0') { /* We check the simple case first. */ errno = ENOENT; diff --git a/glib/gtester.c b/glib/gtester.c index 94cfba641..c48ecaf94 100644 --- a/glib/gtester.c +++ b/glib/gtester.c @@ -668,8 +668,8 @@ parse_args (gint *argc_p, } } /* collapse argv */ - e = 1; - for (i = 1; i < argc; i++) + e = 0; + for (i = 0; i < argc; i++) if (argv[i]) { argv[e++] = argv[i]; diff --git a/glib/gtestutils.c b/glib/gtestutils.c index 27c72a416..dca4badcf 100644 --- a/glib/gtestutils.c +++ b/glib/gtestutils.c @@ -890,7 +890,7 @@ static gboolean test_debug_log = FALSE; static gboolean test_tap_log = TRUE; /* default to TAP as of GLib 2.62; see #1619; the non-TAP output mode is deprecated */ static gboolean test_nonfatal_assertions = FALSE; static DestroyEntry *test_destroy_queue = NULL; -static const char *test_argv0 = NULL; /* points into global argv */ +static const char *test_argv0 = NULL; /* (nullable), points into global argv */ static char *test_argv0_dirname = NULL; /* owned by GLib */ static const char *test_disted_files_dir; /* points into test_argv0_dirname or an environment variable */ static const char *test_built_files_dir; /* points into test_argv0_dirname or an environment variable */ @@ -1138,7 +1138,7 @@ parse_args (gint *argc_p, gchar **argv = *argv_p; guint i, e; - test_argv0 = argv[0]; + test_argv0 = argv[0]; /* will be NULL iff argc == 0 */ test_initial_cwd = g_get_current_dir (); /* parse known args */ @@ -1382,8 +1382,8 @@ parse_args (gint *argc_p, test_paths = g_slist_reverse (test_paths); /* collapse argv */ - e = 1; - for (i = 1; i < argc; i++) + e = 0; + for (i = 0; i < argc; i++) if (argv[i]) { argv[e++] = argv[i]; @@ -1732,7 +1732,7 @@ void g_log_set_default_handler (gtest_default_log_handler, NULL); g_test_log (G_TEST_LOG_START_BINARY, g_get_prgname(), test_run_seedstr, 0, NULL); - test_argv0_dirname = g_path_get_dirname (test_argv0); + test_argv0_dirname = (test_argv0 != NULL) ? g_path_get_dirname (test_argv0) : g_strdup ("."); /* Make sure we get the real dirname that the test was run from */ if (g_str_has_suffix (test_argv0_dirname, "/.libs")) @@ -3828,6 +3828,9 @@ g_test_trap_subprocess (const char *test_path, test_trap_clear (); test_trap_last_subprocess = g_strdup (test_path); + if (test_argv0 == NULL) + g_error ("g_test_trap_subprocess() requires argv0 to be passed to g_test_init()"); + argv = g_ptr_array_new (); g_ptr_array_add (argv, (char *) test_argv0); g_ptr_array_add (argv, "-q"); diff --git a/glib/tests/testing-helper.c b/glib/tests/testing-helper.c index 7b61e8337..7731538a0 100644 --- a/glib/tests/testing-helper.c +++ b/glib/tests/testing-helper.c @@ -102,6 +102,24 @@ main (int argc, argc -= 1; argv[argc] = NULL; + if (g_strcmp0 (argv1, "init-null-argv0") == 0) + { + int test_argc = 0; + char *test_argva[1] = { NULL }; + char **test_argv = test_argva; + + /* Test that `g_test_init()` can handle being called with an empty argv + * and argc == 0. While this isn’t recommended, it is possible for another + * process to use execve() to call a gtest process this way, so we’d + * better handle it gracefully. + * + * This test can’t be run after `g_test_init()` has been called normally, + * as it isn’t allowed to be called more than once in a process. */ + g_test_init (&test_argc, &test_argv, NULL); + + return 0; + } + g_test_init (&argc, &argv, NULL); g_test_set_nonfatal_assertions (); diff --git a/glib/tests/testing.c b/glib/tests/testing.c index c1fcd8c67..accd5dbfe 100644 --- a/glib/tests/testing.c +++ b/glib/tests/testing.c @@ -1585,6 +1585,40 @@ test_tap_summary (void) g_ptr_array_unref (argv); } +static void +test_init_no_argv0 (void) +{ + const char *testing_helper; + GPtrArray *argv; + GError *error = NULL; + int status; + gchar *output; + + g_test_summary ("Test that g_test_init() can be called safely with argc == 0."); + + testing_helper = g_test_get_filename (G_TEST_BUILT, "testing-helper" EXEEXT, NULL); + + argv = g_ptr_array_new (); + g_ptr_array_add (argv, (char *) testing_helper); + g_ptr_array_add (argv, "init-null-argv0"); + g_ptr_array_add (argv, NULL); + + /* This has to be spawned manually and can’t be run with g_test_subprocess() + * because the test helper can’t be run after `g_test_init()` has been called + * in the process. */ + g_spawn_sync (NULL, (char **) argv->pdata, NULL, + G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, &output, NULL, &status, + &error); + g_assert_no_error (error); + + g_spawn_check_wait_status (status, &error); + g_assert_no_error (error); + g_assert_nonnull (strstr (output, "# random seed:")); + g_free (output); + g_ptr_array_unref (argv); +} + int main (int argc, char *argv[]) @@ -1682,6 +1716,8 @@ main (int argc, g_test_add_func ("/tap", test_tap); g_test_add_func ("/tap/summary", test_tap_summary); + g_test_add_func ("/init/no_argv0", test_init_no_argv0); + ret = g_test_run (); /* We can't test for https://gitlab.gnome.org/GNOME/glib/-/issues/2563 diff --git a/glib/win_iconv.c b/glib/win_iconv.c index cc2ce53b6..7c78e78fc 100644 --- a/glib/win_iconv.c +++ b/glib/win_iconv.c @@ -2034,7 +2034,8 @@ main(int argc, char **argv) if (fromcode == NULL || tocode == NULL) { - printf("usage: %s [-c] -f from-enc -t to-enc [file]\n", argv[0]); + printf("usage: %s [-c] -f from-enc -t to-enc [file]\n", + (argc > 0) ? argv[0] : "win_iconv"); return 0; } diff --git a/gobject/gobject-query.c b/gobject/gobject-query.c index fd45fba82..5c3c7dc42 100644 --- a/gobject/gobject-query.c +++ b/gobject/gobject-query.c @@ -196,7 +196,7 @@ main (gint argc, } if (!gen_froots && !gen_tree) - return help (argv[i-1]); + return help ((argc > 0) ? argv[i-1] : NULL); if (!indent_inc) {