From 591e39dbe9de8bc02bdb16833caa1856a52a95e2 Mon Sep 17 00:00:00 2001 From: Tobias Stoeckmann Date: Tue, 8 Jul 2025 19:53:04 +0200 Subject: [PATCH] garray: Support unallocated zero terminated arrays The g_array_new_take_zero_terminated function could lead to NULL data pointer if it is called with (NULL, FALSE, x), i.e. with a NULL pointer and no clear request. This in turn means that g_array_steal could behave like g_ptr_array_steal, i.e. it would return NULL instead of a zero terminated array, which does not match its description. Also, g_array_remove_range and g_array_set_size could lead to NULL pointer dereferences with such arrays. Support all these cases and adjust the API description to reflect current behavior. It brings GArray and GPtrArray functionality closer to each other without breaking existing API/ABI for programs. --- glib/garray.c | 21 +++++++++++------ glib/tests/array-test.c | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/glib/garray.c b/glib/garray.c index bdbc5c173..b8fc5c36e 100644 --- a/glib/garray.c +++ b/glib/garray.c @@ -198,7 +198,9 @@ g_array_new_take (gpointer data, /** * g_array_new_take_zero_terminated: (skip) - * @data: (array zero-terminated=1): an array of elements of @element_size + * @data: (array zero-terminated=1) (transfer full) (nullable): an array + * of elements of @element_size, %NULL terminated, + * or %NULL for an empty array * @clear: %TRUE if #GArray elements should be automatically cleared * to 0 when they are allocated * @element_size: the size of each element in bytes @@ -271,8 +273,9 @@ g_array_new_take_zero_terminated (gpointer data, * the underlying array is preserved for use elsewhere and returned * to the caller. * - * If the array was created with the @zero_terminate property - * set to %TRUE, the returned data is zero terminated too. + * Note that if the array was created with the @zero_terminate + * property set to %TRUE, this may still return %NULL if the length + * of the array was zero and data was not yet allocated. * * If array elements contain dynamically-allocated memory, * the array elements should also be freed by the caller. @@ -770,11 +773,12 @@ g_array_set_size (GArray *farray, } else if (length < array->len) g_array_remove_range (farray, length, array->len - length); - + array->len = length; - - g_array_zero_terminate (array); - + + if (G_LIKELY (array->data != NULL)) + g_array_zero_terminate (array); + return farray; } @@ -881,6 +885,9 @@ g_array_remove_range (GArray *farray, g_return_val_if_fail (index_ <= G_MAXUINT - length, NULL); g_return_val_if_fail (index_ + length <= array->len, NULL); + if (length == 0) + return farray; + if (array->clear_func != NULL) { guint i; diff --git a/glib/tests/array-test.c b/glib/tests/array-test.c index df9d15bdc..4bcbae705 100644 --- a/glib/tests/array-test.c +++ b/glib/tests/array-test.c @@ -100,6 +100,20 @@ array_set_size (gconstpointer test_data) g_array_unref (garray); } +/* Check that unallocated zero terminated arrays can be set to size 0. */ +static void +array_set_size_zero_terminated_null (void) +{ + GArray *garray; + + garray = g_array_new_take_zero_terminated(NULL, FALSE, sizeof (gchar)); + + g_array_set_size (garray, 0); + g_assert_cmpuint (garray->len, ==, 0); + + g_array_free (garray, TRUE); +} + /* As with array_set_size(), but with a sized array. */ static void array_set_size_sized (gconstpointer test_data) @@ -307,6 +321,24 @@ array_new_take_zero_terminated (void) g_clear_pointer (&old_data_copy, g_free); } +/* Check that a non-existing array becomes a zero-terminated one when requested. */ +static void +array_new_take_zero_terminated_null (void) +{ + GArray *garray; + gchar *out_str = NULL; + gsize len; + + garray = g_array_new_take_zero_terminated (NULL, FALSE, sizeof (gchar)); + g_assert_cmpuint (garray->len, ==, 0); + + out_str = g_array_steal (garray, &len); + g_assert_cmpstr (out_str, ==, NULL); + g_assert_cmpuint (len, ==, 0); + + g_free (out_str); +} + static void array_new_take_overflow (void) { @@ -673,6 +705,21 @@ array_remove_range (gconstpointer test_data) g_array_free (garray, TRUE); } +/* Check that g_array_remove_range() works with a zero terminated array + * without any data. */ +static void +array_remove_range_zero_terminated_null (void) +{ + GArray *garray; + + garray = g_array_new_take_zero_terminated(NULL, FALSE, sizeof (gchar)); + + g_array_remove_range (garray, 0, 0); + g_assert_cmpuint (garray->len, ==, 0); + + g_array_free (garray, TRUE); +} + static void array_ref_count (void) { @@ -3194,6 +3241,7 @@ main (int argc, char *argv[]) g_test_add_func ("/array/new/take/empty", array_new_take_empty); g_test_add_func ("/array/new/take/overflow", array_new_take_overflow); g_test_add_func ("/array/new/take-zero-terminated", array_new_take_zero_terminated); + g_test_add_func ("/array/new/take-zero-terminated/null", array_new_take_zero_terminated_null); g_test_add_func ("/array/ref-count", array_ref_count); g_test_add_func ("/array/steal", array_steal); g_test_add_func ("/array/clear-func", array_clear_func); @@ -3201,6 +3249,8 @@ main (int argc, char *argv[]) g_test_add_func ("/array/copy-sized", test_array_copy_sized); g_test_add_func ("/array/overflow-append-vals", array_overflow_append_vals); g_test_add_func ("/array/overflow-set-size", array_overflow_set_size); + g_test_add_func ("/array/remove-range/zero-terminated-null", array_remove_range_zero_terminated_null); + g_test_add_func ("/array/set-size/zero-terminated-null", array_set_size_zero_terminated_null); for (i = 0; i < G_N_ELEMENTS (array_configurations); i++) {