glib/glib/tests/bytes.c
Philip Withnall b63a3189e7 tests: Add a test for GBytes memory transfer with an odd free func
This is basically a contrived test to trigger the `bytes->user_data !=
bytes->data` condition (and none of the earlier short-circuiting
conditions in that statement) in `try_steal_and_unref()`. This gives
100% line and branch coverage for `gbytes.c`.

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
2021-06-16 12:14:58 +01:00

513 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright 2011 Collabora Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* See the included COPYING file for more information.
*/
#undef G_DISABLE_ASSERT
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "glib.h"
/* Keep in sync with glib/gbytes.c */
struct _GBytes
{
gconstpointer data;
gsize size;
gint ref_count;
GDestroyNotify free_func;
gpointer user_data;
};
static const gchar *NYAN = "nyannyan";
static const gsize N_NYAN = 8;
static void
test_new (void)
{
const gchar *data;
GBytes *bytes;
gsize size;
data = "test";
bytes = g_bytes_new (data, 4);
g_assert_nonnull (bytes);
g_assert_true (g_bytes_get_data (bytes, &size) != data);
g_assert_cmpuint (size, ==, 4);
g_assert_cmpuint (g_bytes_get_size (bytes), ==, 4);
g_assert_cmpmem (data, 4, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes));
g_bytes_unref (bytes);
}
static void
test_new_take (void)
{
gchar *data;
GBytes *bytes;
gsize size;
data = g_strdup ("test");
bytes = g_bytes_new_take (data, 4);
g_assert_nonnull (bytes);
g_assert_true (g_bytes_get_data (bytes, &size) == data);
g_assert_cmpuint (size, ==, 4);
g_assert_cmpuint (g_bytes_get_size (bytes), ==, 4);
g_bytes_unref (bytes);
}
static void
test_new_static (void)
{
const gchar *data;
GBytes *bytes;
gsize size;
data = "test";
bytes = g_bytes_new_static (data, 4);
g_assert_nonnull (bytes);
g_assert_true (g_bytes_get_data (bytes, &size) == data);
g_assert_cmpuint (size, ==, 4);
g_assert_cmpuint (g_bytes_get_size (bytes), ==, 4);
g_bytes_unref (bytes);
}
static void
test_new_from_bytes (void)
{
const gchar *data = "smile and wave";
GBytes *bytes;
GBytes *sub;
bytes = g_bytes_new (data, 14);
sub = g_bytes_new_from_bytes (bytes, 10, 4);
g_assert_nonnull (sub);
g_assert_true (g_bytes_get_data (sub, NULL) == ((gchar *)g_bytes_get_data (bytes, NULL)) + 10);
g_bytes_unref (bytes);
g_assert_cmpmem (g_bytes_get_data (sub, NULL), g_bytes_get_size (sub), "wave", 4);
g_bytes_unref (sub);
}
/* Verify that creating slices of GBytes reference the top-most bytes
* at the correct offset. Ensure that intermediate GBytes are not referenced.
*/
static void
test_new_from_bytes_slice (void)
{
GBytes *bytes = g_bytes_new_static ("Some stupid data", strlen ("Some stupid data") + 1);
GBytes *bytes1 = g_bytes_new_from_bytes (bytes, 4, 13);
GBytes *bytes2 = g_bytes_new_from_bytes (bytes1, 1, 12);
GBytes *bytes3 = g_bytes_new_from_bytes (bytes2, 0, 6);
g_assert_cmpint (bytes->ref_count, ==, 4);
g_assert_cmpint (bytes1->ref_count, ==, 1);
g_assert_cmpint (bytes2->ref_count, ==, 1);
g_assert_cmpint (bytes3->ref_count, ==, 1);
g_assert_null (bytes->user_data);
g_assert_true (bytes1->user_data == bytes);
g_assert_true (bytes2->user_data == bytes);
g_assert_true (bytes3->user_data == bytes);
g_assert_cmpint (17, ==, g_bytes_get_size (bytes));
g_assert_cmpint (13, ==, g_bytes_get_size (bytes1));
g_assert_cmpint (12, ==, g_bytes_get_size (bytes2));
g_assert_cmpint (6, ==, g_bytes_get_size (bytes3));
g_assert_cmpint (0, ==, strncmp ("Some stupid data", (gchar *)bytes->data, 17));
g_assert_cmpint (0, ==, strncmp (" stupid data", (gchar *)bytes1->data, 13));
g_assert_cmpint (0, ==, strncmp ("stupid data", (gchar *)bytes2->data, 12));
g_assert_cmpint (0, ==, strncmp ("stupid", (gchar *)bytes3->data, 6));
g_bytes_unref (bytes);
g_bytes_unref (bytes1);
g_bytes_unref (bytes2);
g_bytes_unref (bytes3);
}
/* Ensure that referencing an entire GBytes just returns the same bytes
* instance (with incremented reference count) instead of a new instance.
*/
static void
test_new_from_bytes_shared_ref (void)
{
GBytes *bytes = g_bytes_new_static ("Some data", strlen ("Some data") + 1);
GBytes *other = g_bytes_new_from_bytes (bytes, 0, g_bytes_get_size (bytes));
g_assert_true (bytes == other);
g_assert_cmpint (bytes->ref_count, ==, 2);
g_bytes_unref (bytes);
g_bytes_unref (other);
}
static void
on_destroy_increment (gpointer data)
{
gint *count = data;
g_assert_nonnull (count);
(*count)++;
}
static void
test_new_with_free_func (void)
{
GBytes *bytes;
gchar *data;
gint count = 0;
gsize size;
data = "test";
bytes = g_bytes_new_with_free_func (data, 4, on_destroy_increment, &count);
g_assert_nonnull (bytes);
g_assert_cmpint (count, ==, 0);
g_assert_true (g_bytes_get_data (bytes, &size) == data);
g_assert_cmpuint (size, ==, 4);
g_assert_cmpuint (g_bytes_get_size (bytes), ==, 4);
g_bytes_unref (bytes);
g_assert_cmpuint (count, ==, 1);
}
static void
test_hash (void)
{
GBytes *bytes1;
GBytes *bytes2;
guint hash1;
guint hash2;
bytes1 = g_bytes_new ("blah", 4);
bytes2 = g_bytes_new ("blah", 4);
hash1 = g_bytes_hash (bytes1);
hash2 = g_bytes_hash (bytes2);
g_assert_cmpuint (hash1, ==, hash2);
g_bytes_unref (bytes1);
g_bytes_unref (bytes2);
}
static void
test_equal (void)
{
GBytes *bytes;
GBytes *bytes2;
bytes = g_bytes_new ("blah", 4);
bytes2 = g_bytes_new ("blah", 4);
g_assert_true (g_bytes_equal (bytes, bytes2));
g_assert_true (g_bytes_equal (bytes2, bytes));
g_bytes_unref (bytes2);
bytes2 = g_bytes_new ("bla", 3);
g_assert_false (g_bytes_equal (bytes, bytes2));
g_assert_false (g_bytes_equal (bytes2, bytes));
g_bytes_unref (bytes2);
bytes2 = g_bytes_new ("true", 4);
g_assert_false (g_bytes_equal (bytes, bytes2));
g_assert_false (g_bytes_equal (bytes2, bytes));
g_bytes_unref (bytes2);
g_bytes_unref (bytes);
}
static void
test_compare (void)
{
GBytes *bytes;
GBytes *bytes2;
bytes = g_bytes_new ("blah", 4);
bytes2 = g_bytes_new ("blah", 4);
g_assert_cmpint (g_bytes_compare (bytes, bytes2), ==, 0);
g_bytes_unref (bytes2);
bytes2 = g_bytes_new ("bla", 3);
g_assert_cmpint (g_bytes_compare (bytes, bytes2), >, 0);
g_bytes_unref (bytes2);
bytes2 = g_bytes_new ("abcd", 4);
g_assert_cmpint (g_bytes_compare (bytes, bytes2), >, 0);
g_bytes_unref (bytes2);
bytes2 = g_bytes_new ("blahblah", 8);
g_assert_cmpint (g_bytes_compare (bytes, bytes2), <, 0);
g_bytes_unref (bytes2);
bytes2 = g_bytes_new ("zyx", 3);
g_assert_cmpint (g_bytes_compare (bytes, bytes2), <, 0);
g_bytes_unref (bytes2);
bytes2 = g_bytes_new ("zyxw", 4);
g_assert_cmpint (g_bytes_compare (bytes, bytes2), <, 0);
g_bytes_unref (bytes2);
g_bytes_unref (bytes);
}
static void
test_to_data_transferred (void)
{
gconstpointer memory;
gpointer data;
gsize size;
GBytes *bytes;
/* Memory transferred: one reference, and allocated with g_malloc */
bytes = g_bytes_new (NYAN, N_NYAN);
memory = g_bytes_get_data (bytes, NULL);
data = g_bytes_unref_to_data (bytes, &size);
g_assert_true (data == memory);
g_assert_cmpmem (data, size, NYAN, N_NYAN);
g_free (data);
}
static void
test_to_data_two_refs (void)
{
gconstpointer memory;
gpointer data;
gsize size;
GBytes *bytes;
/* Memory copied: two references */
bytes = g_bytes_new (NYAN, N_NYAN);
bytes = g_bytes_ref (bytes);
memory = g_bytes_get_data (bytes, NULL);
data = g_bytes_unref_to_data (bytes, &size);
g_assert_true (data != memory);
g_assert_cmpmem (data, size, NYAN, N_NYAN);
g_free (data);
g_assert_true (g_bytes_get_data (bytes, &size) == memory);
g_assert_cmpuint (size, ==, N_NYAN);
g_assert_cmpuint (g_bytes_get_size (bytes), ==, N_NYAN);
g_bytes_unref (bytes);
}
static void
test_to_data_non_malloc (void)
{
gpointer data;
gsize size;
GBytes *bytes;
/* Memory copied: non malloc memory */
bytes = g_bytes_new_static (NYAN, N_NYAN);
g_assert_true (g_bytes_get_data (bytes, NULL) == NYAN);
data = g_bytes_unref_to_data (bytes, &size);
g_assert_true (data != (gpointer)NYAN);
g_assert_cmpmem (data, size, NYAN, N_NYAN);
g_free (data);
}
static void
test_to_data_different_free_func (void)
{
gpointer data;
gsize size;
GBytes *bytes;
gchar *sentinel = g_strdup ("hello");
/* Memory copied: free func and user_data dont point to the bytes data */
bytes = g_bytes_new_with_free_func (NYAN, N_NYAN, g_free, sentinel);
g_assert_true (g_bytes_get_data (bytes, NULL) == NYAN);
data = g_bytes_unref_to_data (bytes, &size);
g_assert_true (data != (gpointer)NYAN);
g_assert_cmpmem (data, size, NYAN, N_NYAN);
g_free (data);
/* @sentinel should not be leaked; testing that requires this test to be run
* under valgrind. We cant use a custom free func to check it isnt leaked,
* as the point of this test is to hit a condition in `try_steal_and_unref()`
* which is short-circuited if the free func isnt g_free().
* See discussion in https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2152 */
}
static void
test_to_array_transferred (void)
{
gconstpointer memory;
GByteArray *array;
GBytes *bytes;
/* Memory transferred: one reference, and allocated with g_malloc */
bytes = g_bytes_new (NYAN, N_NYAN);
memory = g_bytes_get_data (bytes, NULL);
array = g_bytes_unref_to_array (bytes);
g_assert_nonnull (array);
g_assert_true (array->data == memory);
g_assert_cmpmem (array->data, array->len, NYAN, N_NYAN);
g_byte_array_unref (array);
}
static void
test_to_array_transferred_oversize (void)
{
g_test_message ("g_bytes_unref_to_array() can only take GBytes up to "
"G_MAXUINT in length; test that longer ones are rejected");
if (sizeof (guint) >= sizeof (gsize))
{
g_test_skip ("Skipping test as guint is not smaller than gsize");
}
else if (g_test_undefined ())
{
GByteArray *array = NULL;
GBytes *bytes = NULL;
gpointer data = g_memdup2 (NYAN, N_NYAN);
gsize len = ((gsize) G_MAXUINT) + 1;
bytes = g_bytes_new_take (data, len);
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"g_byte_array_new_take: assertion 'len <= G_MAXUINT' failed");
array = g_bytes_unref_to_array (g_steal_pointer (&bytes));
g_test_assert_expected_messages ();
g_assert_null (array);
g_free (data);
}
else
{
g_test_skip ("Skipping test as testing undefined behaviour is disabled");
}
}
static void
test_to_array_two_refs (void)
{
gconstpointer memory;
GByteArray *array;
GBytes *bytes;
gsize size;
/* Memory copied: two references */
bytes = g_bytes_new (NYAN, N_NYAN);
bytes = g_bytes_ref (bytes);
memory = g_bytes_get_data (bytes, NULL);
array = g_bytes_unref_to_array (bytes);
g_assert_nonnull (array);
g_assert_true (array->data != memory);
g_assert_cmpmem (array->data, array->len, NYAN, N_NYAN);
g_byte_array_unref (array);
g_assert_true (g_bytes_get_data (bytes, &size) == memory);
g_assert_cmpuint (size, ==, N_NYAN);
g_assert_cmpuint (g_bytes_get_size (bytes), ==, N_NYAN);
g_bytes_unref (bytes);
}
static void
test_to_array_non_malloc (void)
{
GByteArray *array;
GBytes *bytes;
/* Memory copied: non malloc memory */
bytes = g_bytes_new_static (NYAN, N_NYAN);
g_assert_true (g_bytes_get_data (bytes, NULL) == NYAN);
array = g_bytes_unref_to_array (bytes);
g_assert_nonnull (array);
g_assert_true (array->data != (gpointer)NYAN);
g_assert_cmpmem (array->data, array->len, NYAN, N_NYAN);
g_byte_array_unref (array);
}
static void
test_null (void)
{
GBytes *bytes;
gpointer data;
gsize size;
bytes = g_bytes_new (NULL, 0);
data = g_bytes_unref_to_data (bytes, &size);
g_assert_null (data);
g_assert_cmpuint (size, ==, 0);
}
static void
test_get_region (void)
{
GBytes *bytes;
bytes = g_bytes_new_static (NYAN, N_NYAN);
/* simple valid gets at the start */
g_assert_true (g_bytes_get_region (bytes, 1, 0, 1) == NYAN);
g_assert_true (g_bytes_get_region (bytes, 1, 0, N_NYAN) == NYAN);
/* an invalid get because the range is too wide */
g_assert_true (g_bytes_get_region (bytes, 1, 0, N_NYAN + 1) == NULL);
/* an valid get, but of a zero-byte range at the end */
g_assert_true (g_bytes_get_region (bytes, 1, N_NYAN, 0) == NYAN + N_NYAN);
/* not a valid get because it overlap ones byte */
g_assert_true (g_bytes_get_region (bytes, 1, N_NYAN, 1) == NULL);
/* let's try some multiplication overflow now */
g_assert_true (g_bytes_get_region (bytes, 32, 0, G_MAXSIZE / 32 + 1) == NULL);
g_assert_true (g_bytes_get_region (bytes, G_MAXSIZE / 32 + 1, 0, 32) == NULL);
/* and some addition overflow */
g_assert_true (g_bytes_get_region (bytes, 1, G_MAXSIZE, -G_MAXSIZE) == NULL);
g_assert_true (g_bytes_get_region (bytes, 1, G_MAXSSIZE, ((gsize) G_MAXSSIZE) + 1) == NULL);
g_assert_true (g_bytes_get_region (bytes, 1, G_MAXSIZE, 1) == NULL);
g_bytes_unref (bytes);
}
static void
test_unref_null (void)
{
g_test_summary ("Test that calling g_bytes_unref() on NULL is a no-op");
g_bytes_unref (NULL);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/bytes/new", test_new);
g_test_add_func ("/bytes/new-take", test_new_take);
g_test_add_func ("/bytes/new-static", test_new_static);
g_test_add_func ("/bytes/new-with-free-func", test_new_with_free_func);
g_test_add_func ("/bytes/new-from-bytes", test_new_from_bytes);
g_test_add_func ("/bytes/new-from-bytes-slice", test_new_from_bytes_slice);
g_test_add_func ("/bytes/new-from-bytes-shared-ref", test_new_from_bytes_shared_ref);
g_test_add_func ("/bytes/hash", test_hash);
g_test_add_func ("/bytes/equal", test_equal);
g_test_add_func ("/bytes/compare", test_compare);
g_test_add_func ("/bytes/to-data/transferred", test_to_data_transferred);
g_test_add_func ("/bytes/to-data/two-refs", test_to_data_two_refs);
g_test_add_func ("/bytes/to-data/non-malloc", test_to_data_non_malloc);
g_test_add_func ("/bytes/to-data/different-free-func", test_to_data_different_free_func);
g_test_add_func ("/bytes/to-array/transferred", test_to_array_transferred);
g_test_add_func ("/bytes/to-array/transferred/oversize", test_to_array_transferred_oversize);
g_test_add_func ("/bytes/to-array/two-refs", test_to_array_two_refs);
g_test_add_func ("/bytes/to-array/non-malloc", test_to_array_non_malloc);
g_test_add_func ("/bytes/null", test_null);
g_test_add_func ("/bytes/get-region", test_get_region);
g_test_add_func ("/bytes/unref-null", test_unref_null);
return g_test_run ();
}