diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 74f7b2149..765c48aa5 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -3028,6 +3028,7 @@ g_bytes_new_static g_bytes_new_with_free_func g_bytes_new_from_bytes g_bytes_get_data +g_bytes_get_region g_bytes_get_size g_bytes_hash g_bytes_equal diff --git a/glib/gbytes.c b/glib/gbytes.c index aaadf451b..a6ca0e300 100644 --- a/glib/gbytes.c +++ b/glib/gbytes.c @@ -538,3 +538,75 @@ g_bytes_unref_to_array (GBytes *bytes) data = g_bytes_unref_to_data (bytes, &size); return g_byte_array_new_take (data, size); } + +/** + * g_bytes_get_region: + * @bytes: a #GBytes + * @element_size: a non-zero element size + * @offset: an offset to the start of the region within the @bytes + * @n_elements: the number of elements in the region + * + * Gets a pointer to a region in @bytes. + * + * The region starts at @offset many bytes from the start of the data + * and contains @n_elements many elements of @element_size size. + * + * @n_elements may be zero, but @element_size must always be non-zero. + * Ideally, @element_size is a static constant (eg: sizeof a struct). + * + * This function does careful bounds checking (including checking for + * arithmetic overflows) and returns a non-%NULL pointer if the + * specified region lies entirely within the @bytes. If the region is + * in some way out of range, or if an overflow has occurred, then %NULL + * is returned. + * + * Note: it is possible to have a valid zero-size region. In this case, + * the returned pointer will be equal to the base pointer of the data of + * @bytes, plus @offset. This will be non-%NULL except for the case + * where @bytes itself was a zero-sized region. Since it is unlikely + * that you will be using this function to check for a zero-sized region + * in a zero-sized @bytes, %NULL effectively always means "error". + * + * Returns: (nullable): the requested region, or %NULL in case of an error + * + * Since: 2.70 + */ +gconstpointer +g_bytes_get_region (GBytes *bytes, + gsize element_size, + gsize offset, + gsize n_elements) +{ + gsize total_size; + gsize end_offset; + + g_return_val_if_fail (element_size > 0, NULL); + + /* No other assertion checks here. If something is wrong then we will + * simply crash (via NULL dereference or divide-by-zero). + */ + + if (!g_size_checked_mul (&total_size, element_size, n_elements)) + return NULL; + + if (!g_size_checked_add (&end_offset, offset, total_size)) + return NULL; + + /* We now have: + * + * 0 <= offset <= end_offset + * + * So we need only check that end_offset is within the range of the + * size of @bytes and we're good to go. + */ + + if (end_offset > bytes->size) + return NULL; + + /* We now have: + * + * 0 <= offset <= end_offset <= bytes->size + */ + + return ((guchar *) bytes->data) + offset; +} \ No newline at end of file diff --git a/glib/gbytes.h b/glib/gbytes.h index 0bb1517b9..37cad861d 100644 --- a/glib/gbytes.h +++ b/glib/gbytes.h @@ -85,6 +85,13 @@ GLIB_AVAILABLE_IN_ALL gint g_bytes_compare (gconstpointer bytes1, gconstpointer bytes2); +GLIB_AVAILABLE_IN_2_70 +gconstpointer g_bytes_get_region (GBytes *bytes, + gsize element_size, + gsize offset, + gsize n_elements); + + G_END_DECLS #endif /* __G_BYTES_H__ */ diff --git a/glib/tests/bytes.c b/glib/tests/bytes.c index fc02a211c..e4be70aad 100644 --- a/glib/tests/bytes.c +++ b/glib/tests/bytes.c @@ -418,6 +418,38 @@ test_null (void) g_assert (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); +} + int main (int argc, char *argv[]) { @@ -441,6 +473,7 @@ main (int argc, char *argv[]) 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); return g_test_run (); }