From fc6d0ba3a80fb0931cf9f1fadc1f95d70c2e5d72 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 31 Jan 2022 14:45:44 +0000 Subject: [PATCH 1/7] gapplicationcommandline: Fix a typo in a documentation comment Signed-off-by: Philip Withnall --- gio/gapplicationcommandline.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gio/gapplicationcommandline.c b/gio/gapplicationcommandline.c index 741aa97f1..937b87d22 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 From 0d743e7ffbf1edff5e00e67a9bed7bfa507901f6 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 31 Jan 2022 14:46:08 +0000 Subject: [PATCH 2/7] gapplicationcommandline: Handle the possibility empty args in an example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While it’s not usual, it is possible for applications to be called with an empty `argv` by a process (ab)using `execve()`. Modify the `GApplication` code example to handle that possibility, so that anyone copying the example isn’t introducing a bug into their program immediately. Signed-off-by: Philip Withnall --- gio/gapplicationcommandline.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gio/gapplicationcommandline.c b/gio/gapplicationcommandline.c index 937b87d22..d7be108bf 100644 --- a/gio/gapplicationcommandline.c +++ b/gio/gapplicationcommandline.c @@ -119,6 +119,12 @@ * * argv = *arguments; * + * if (argv[0] == NULL) + * { + * *exit_status = 0; + * return FALSE; + * } + * * i = 1; * while (argv[i]) * { From 3ea4ba31a14a9c0790a5a71cdda1527930c250a6 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 31 Jan 2022 14:47:57 +0000 Subject: [PATCH 3/7] tools: Fix handling of empty argv in various minor GLib tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This won’t really affect anything, but we might as well fix them to not crash if called with an empty `argv` by someone (ab)using `execve()`. Signed-off-by: Philip Withnall --- gio/gdbus-tool.c | 3 ++- gio/gio-querymodules.c | 2 +- glib/deprecated/gcompletion.c | 3 ++- glib/win_iconv.c | 3 ++- gobject/gobject-query.c | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) 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/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/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) { From 11654811ef304a56cc35743b4cd6547a25338ed5 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 31 Jan 2022 14:48:58 +0000 Subject: [PATCH 4/7] gdesktopappinfo: Add checks for empty argv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There shouldn’t be any issues here with empty argv arrays since an empty `Exec=` line is already checked for. Encode that explicitly with an assertion. Signed-off-by: Philip Withnall --- gio/gdesktopappinfo.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 80f936ca1..0bc8e96c9 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); From 18fc711355248ddf5032e9fe568870bf3e19d1b3 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 31 Jan 2022 14:49:50 +0000 Subject: [PATCH 5/7] gshell: Mention handling of empty argv arrays in the documentation It is not possible for `g_shell_parse_argv()` to return an empty `argv` array. Make that clear in the documentation and add some assertions to encode it explicitly in the code. Signed-off-by: Philip Withnall --- glib/gshell.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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; From 44f4d551503005f1dad2ed91530b9bec1a7b47ed Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 31 Jan 2022 14:54:10 +0000 Subject: [PATCH 6/7] gspawn: Clarify that empty argv arrays are not allowed when spawning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While `execve()` might allow (probably malicious) users to execute a program with an empty `argv` array, gspawn does not. It’s not actually possible, as the path to the binary to execute is not specified separately from the argument array. Explicitly document and encode that in preconditions. Signed-off-by: Philip Withnall --- glib/gspawn-win32.c | 15 ++++++++++----- glib/gspawn.c | 24 ++++++++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/glib/gspawn-win32.c b/glib/gspawn-win32.c index 02eddccf1..254195c08 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; From a6311f81ee93b772a29ec9396e2853fcc72fb79a Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 31 Jan 2022 14:55:50 +0000 Subject: [PATCH 7/7] gtestutils: Handle empty argv array passed to g_test_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This can happen if a caller (ab)uses `execve()` to execute a gtest process with an empty `argv` array. `g_test_init()` has to gracefully handle such a situation. Fix a few problem areas in the code, and add a simple test which checks that `g_test_init()` doesn’t crash when called with an empty `argv`. Signed-off-by: Philip Withnall --- glib/gtester.c | 4 ++-- glib/gtestutils.c | 13 ++++++++----- glib/tests/testing-helper.c | 18 ++++++++++++++++++ glib/tests/testing.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 7 deletions(-) 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