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.
This commit is contained in:
Tobias Stoeckmann
2025-07-08 19:53:04 +02:00
parent 25a176869d
commit 591e39dbe9
2 changed files with 64 additions and 7 deletions

View File

@@ -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;

View File

@@ -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++)
{