From b15040215acba0b4f18ad830cfaa53ad9184f28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 14 Dec 2022 00:58:13 +0100 Subject: [PATCH] garray: Add g_array_new_take() and g_array_new_take_zero_terminated() Make it easy to handle C arrays using GArray API stealing data from other sources. --- docs/reference/glib/glib-sections.txt.in | 2 + glib/garray.c | 114 +++++++++++++ glib/garray.h | 9 ++ glib/tests/array-test.c | 194 +++++++++++++++++++++++ 4 files changed, 319 insertions(+) diff --git a/docs/reference/glib/glib-sections.txt.in b/docs/reference/glib/glib-sections.txt.in index ac9fab639..162fe6c2b 100644 --- a/docs/reference/glib/glib-sections.txt.in +++ b/docs/reference/glib/glib-sections.txt.in @@ -2680,6 +2680,8 @@ g_string_chunk_free arrays GArray g_array_new +g_array_new_take +g_array_new_take_zero_terminated g_array_steal g_array_sized_new g_array_copy diff --git a/glib/garray.c b/glib/garray.c index 82b17503a..925573b7f 100644 --- a/glib/garray.c +++ b/glib/garray.c @@ -35,6 +35,7 @@ #include "garray.h" +#include "galloca.h" #include "gbytes.h" #include "ghash.h" #include "gslice.h" @@ -190,6 +191,119 @@ g_array_new (gboolean zero_terminated, return g_array_sized_new (zero_terminated, clear, elt_size, 0); } +/** + * g_array_new_take: (skip) + * @data: (array length=len) (transfer full) (nullable): an array of + * elements of @element_size, or %NULL for an empty array + * @len: the number of elements in @data + * @clear: %TRUE if #GArray elements should be automatically cleared + * to 0 when they are allocated + * @element_size: the size of each element in bytes + * + * Creates a new #GArray with @data as array data, @len as length and a + * reference count of 1. + * + * This avoids having to copy the data manually, when it can just be + * inherited. @data will eventually be freed using g_free(), so must + * have been allocated with a suitable allocator. + * + * In case the elements need to be cleared when the array is freed, use + * g_array_set_clear_func() to set a #GDestroyNotify function to perform + * such task. + * + * Do not use it if @len or @element_size are greater than %G_MAXUINT. + * #GArray stores the length of its data in #guint, which may be shorter + * than #gsize. + * + * Returns: (transfer full): A new #GArray + * + * Since: 2.76 + */ +GArray * +g_array_new_take (gpointer data, + gsize len, + gboolean clear, + gsize element_size) +{ + GRealArray *rarray; + GArray *array; + + g_return_val_if_fail (data != NULL || len == 0, NULL); + g_return_val_if_fail (len <= G_MAXUINT, NULL); + g_return_val_if_fail (element_size <= G_MAXUINT, NULL); + + array = g_array_sized_new (FALSE, clear, element_size, 0); + rarray = (GRealArray *) array; + rarray->data = (guint8 *) g_steal_pointer (&data); + rarray->len = len; + rarray->elt_capacity = len; + + return array; +} + +/** + * g_array_new_take_zero_terminated: (skip) + * @data: (array zero-terminated=1): an array of elements of @element_size + * @clear: %TRUE if #GArray elements should be automatically cleared + * to 0 when they are allocated + * @element_size: the size of each element in bytes + * + * Creates a new #GArray with @data as array data, computing the length of it + * and setting the reference count to 1. + * + * This avoids having to copy the data manually, when it can just be + * inherited. @data will eventually be freed using g_free(), so must + * have been allocated with a suitable allocator. + * + * The length is calculated by iterating through @data until the first %NULL + * element is found. + * + * In case the elements need to be cleared when the array is freed, use + * g_array_set_clear_func() to set a #GDestroyNotify function to perform + * such task. + * + * Do not use it if @data length or @element_size are greater than %G_MAXUINT. + * #GArray stores the length of its data in #guint, which may be shorter + * than #gsize. + * + * Returns: (transfer full): A new #GArray + * + * Since: 2.76 + */ +GArray * +g_array_new_take_zero_terminated (gpointer data, + gboolean clear, + gsize element_size) +{ + GArray *array; + gsize len = 0; + + g_return_val_if_fail (element_size <= G_MAXUINT, NULL); + + if (data != NULL) + { + guint8 *array_data = data; + + for (gsize i = 0; ; ++i) + { + const guint8 *element_start = array_data + (i * element_size); + + if (*element_start == 0 && + memcmp (element_start, element_start + 1, element_size - 1) == 0) + break; + + len += 1; + } + } + + g_return_val_if_fail (len <= G_MAXUINT, NULL); + + array = g_array_new_take (data, len, clear, element_size); + ((GRealArray *)array)->zero_terminated = TRUE; + + return array; +} + /** * g_array_steal: * @array: a #GArray. diff --git a/glib/garray.h b/glib/garray.h index bec7bf6d2..2ec10793b 100644 --- a/glib/garray.h +++ b/glib/garray.h @@ -72,6 +72,15 @@ GLIB_AVAILABLE_IN_ALL GArray* g_array_new (gboolean zero_terminated, gboolean clear_, guint element_size); +GLIB_AVAILABLE_IN_2_76 +GArray* g_array_new_take (gpointer data, + gsize len, + gboolean clear, + gsize element_size); +GLIB_AVAILABLE_IN_2_76 +GArray* g_array_new_take_zero_terminated (gpointer data, + gboolean clear, + gsize element_size); GLIB_AVAILABLE_IN_2_64 gpointer g_array_steal (GArray *array, gsize *len); diff --git a/glib/tests/array-test.c b/glib/tests/array-test.c index f47103497..72d6924ff 100644 --- a/glib/tests/array-test.c +++ b/glib/tests/array-test.c @@ -142,6 +142,196 @@ array_new_zero_terminated (void) g_free (out_str); } +static void +array_new_take (void) +{ + const size_t array_size = 10000; + GArray *garray; + gpointer *data; + gpointer *old_data_copy; + gsize len; + + garray = g_array_new (FALSE, FALSE, sizeof (size_t)); + for (size_t i = 0; i < array_size; i++) + g_array_append_val (garray, i); + + data = g_array_steal (garray, &len); + g_assert_cmpuint (array_size, ==, len); + g_assert_nonnull (data); + g_clear_pointer (&garray, g_array_unref); + + old_data_copy = g_memdup2 (data, len * sizeof (size_t)); + garray = g_array_new_take (g_steal_pointer (&data), len, FALSE, sizeof (size_t)); + g_assert_cmpuint (garray->len, ==, array_size); + + g_assert_cmpuint (g_array_index (garray, size_t, 0), ==, 0); + g_assert_cmpuint (g_array_index (garray, size_t, 10), ==, 10); + + g_assert_cmpmem (old_data_copy, array_size * sizeof (size_t), + garray->data, array_size * sizeof (size_t)); + + size_t val = 55; + g_array_append_val (garray, val); + val = 33; + g_array_prepend_val (garray, val); + + g_assert_cmpuint (garray->len, ==, array_size + 2); + g_assert_cmpuint (g_array_index (garray, size_t, 0), ==, 33); + g_assert_cmpuint (g_array_index (garray, size_t, garray->len - 1), ==, 55); + + g_array_remove_index (garray, 0); + g_assert_cmpuint (garray->len, ==, array_size + 1); + g_array_remove_index (garray, garray->len - 1); + g_assert_cmpuint (garray->len, ==, array_size); + + g_assert_cmpmem (old_data_copy, array_size * sizeof (size_t), + garray->data, array_size * sizeof (size_t)); + + g_array_unref (garray); + g_free (old_data_copy); +} + +static void +array_new_take_empty (void) +{ + GArray *garray; + size_t empty_array[] = {0}; + + garray = g_array_new_take ( + g_memdup2 (&empty_array, sizeof (size_t)), 0, FALSE, sizeof (size_t)); + g_assert_cmpuint (garray->len, ==, 0); + + g_clear_pointer (&garray, g_array_unref); + + garray = g_array_new_take (NULL, 0, FALSE, sizeof (size_t)); + g_assert_cmpuint (garray->len, ==, 0); + + g_clear_pointer (&garray, g_array_unref); +} + +static void +array_new_take_zero_terminated (void) +{ + size_t array_size = 10000; + GArray *garray; + gpointer *data; + gpointer *old_data_copy; + gsize len; + + garray = g_array_new (TRUE, FALSE, sizeof (size_t)); + for (size_t i = 1; i <= array_size; i++) + g_array_append_val (garray, i); + + data = g_array_steal (garray, &len); + g_assert_cmpuint (array_size, ==, len); + g_assert_nonnull (data); + g_clear_pointer (&garray, g_array_unref); + + old_data_copy = g_memdup2 (data, len * sizeof (size_t)); + garray = g_array_new_take_zero_terminated ( + g_steal_pointer (&data), FALSE, sizeof (size_t)); + g_assert_cmpuint (garray->len, ==, array_size); + g_assert_cmpuint (g_array_index (garray, size_t, garray->len), ==, 0); + + g_assert_cmpuint (g_array_index (garray, size_t, 0), ==, 1); + g_assert_cmpuint (g_array_index (garray, size_t, 10), ==, 11); + + g_assert_cmpmem (old_data_copy, array_size * sizeof (size_t), + garray->data, array_size * sizeof (size_t)); + + size_t val = 55; + g_array_append_val (garray, val); + val = 33; + g_array_prepend_val (garray, val); + + g_assert_cmpuint (garray->len, ==, array_size + 2); + g_assert_cmpuint (g_array_index (garray, size_t, 0), ==, 33); + g_assert_cmpuint (g_array_index (garray, size_t, garray->len - 1), ==, 55); + + g_array_remove_index (garray, 0); + g_assert_cmpuint (garray->len, ==, array_size + 1); + g_array_remove_index (garray, garray->len - 1); + g_assert_cmpuint (garray->len, ==, array_size); + g_assert_cmpuint (g_array_index (garray, size_t, garray->len), ==, 0); + + g_assert_cmpmem (old_data_copy, array_size * sizeof (size_t), + garray->data, array_size * sizeof (size_t)); + + g_clear_pointer (&garray, g_array_unref); + g_clear_pointer (&old_data_copy, g_free); + + array_size = G_MAXUINT8; + garray = g_array_new (TRUE, FALSE, sizeof (guint8)); + for (guint8 i = 1; i < array_size; i++) + g_array_append_val (garray, i); + + val = G_MAXUINT8 / 2; + g_array_append_val (garray, val); + + data = g_array_steal (garray, &len); + g_assert_cmpuint (array_size, ==, len); + g_assert_nonnull (data); + g_clear_pointer (&garray, g_array_unref); + + old_data_copy = g_memdup2 (data, len * sizeof (guint8)); + garray = g_array_new_take_zero_terminated ( + g_steal_pointer (&data), FALSE, sizeof (guint8)); + g_assert_cmpuint (garray->len, ==, array_size); + g_assert_cmpuint (g_array_index (garray, guint8, garray->len), ==, 0); + + g_assert_cmpuint (g_array_index (garray, guint8, 0), ==, 1); + g_assert_cmpuint (g_array_index (garray, guint8, 10), ==, 11); + + g_assert_cmpmem (old_data_copy, array_size * sizeof (guint8), + garray->data, array_size * sizeof (guint8)); + + val = 55; + g_array_append_val (garray, val); + val = 33; + g_array_prepend_val (garray, val); + + g_assert_cmpuint (garray->len, ==, array_size + 2); + g_assert_cmpuint (g_array_index (garray, guint8, 0), ==, 33); + g_assert_cmpuint (g_array_index (garray, guint8, garray->len - 1), ==, 55); + + g_array_remove_index (garray, 0); + g_assert_cmpuint (garray->len, ==, array_size + 1); + g_array_remove_index (garray, garray->len - 1); + g_assert_cmpuint (garray->len, ==, array_size); + g_assert_cmpuint (g_array_index (garray, guint8, garray->len), ==, 0); + + g_assert_cmpmem (old_data_copy, array_size * sizeof (guint8), + garray->data, array_size * sizeof (guint8)); + + g_clear_pointer (&garray, g_array_unref); + g_clear_pointer (&old_data_copy, g_free); +} + +static void +array_new_take_overflow (void) +{ +#if SIZE_WIDTH <= UINT_WIDTH + g_test_skip ("Overflow test requires UINT_WIDTH > SIZE_WIDTH."); +#else + if (!g_test_undefined ()) + return; + + /* Check for overflow should happen before data is accessed. */ + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*assertion 'len <= G_MAXUINT' failed"); + g_assert_null ( + g_array_new_take ( + (gpointer) (int []) { 0 }, (gsize) G_MAXUINT + 1, FALSE, sizeof (int))); + g_test_assert_expected_messages (); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*assertion 'element_size <= G_MAXUINT' failed"); + g_assert_null ( + g_array_new_take (NULL, 0, FALSE, (gsize) G_MAXUINT + 1)); + g_test_assert_expected_messages (); +#endif +} + /* Check g_array_steal() function */ static void array_steal (void) @@ -2790,6 +2980,10 @@ main (int argc, char *argv[]) /* array tests */ g_test_add_func ("/array/new/zero-terminated", array_new_zero_terminated); + g_test_add_func ("/array/new/take", array_new_take); + 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/ref-count", array_ref_count); g_test_add_func ("/array/steal", array_steal); g_test_add_func ("/array/clear-func", array_clear_func);