glib/gobject/gatomicarray.c
Michael Catanzaro a412ffe4d3 gatomicarray: suppress valgrind memory leak warnings
The problem occurs because we keep a pointer inside the allocated block,
instead of a pointer to the start of the block:

```
==180238== 16 bytes in 1 blocks are possibly lost in loss record 3,086 of 16,075
==180238==    at 0x483980B: malloc (vg_replace_malloc.c:309)
==180238==    by 0x548942C: g_malloc (gmem.c:102)
==180238==    by 0x54A4748: g_slice_alloc (gslice.c:1025)
==180238==    by 0x53D0AAF: freelist_alloc (gatomicarray.c:77)
==180238==    by 0x53D0B85: _g_atomic_array_copy (gatomicarray.c:133)
==180238==    by 0x53F8E6D: iface_node_set_offset_L (gtype.c:1347)
==180238==    by 0x53F91F1: type_node_add_iface_entry_W (gtype.c:1444)
==180238==    by 0x53F93DF: type_add_interface_Wm (gtype.c:1477)
==180238==    by 0x53FC946: g_type_add_interface_static (gtype.c:2852)
==180238==    by 0x4A3D53A: gtk_menu_shell_accessible_get_type_once (gtkmenushellaccessible.c:26)
==180238==    by 0x4A3D495: gtk_menu_shell_accessible_get_type (gtkmenushellaccessible.c:26)
==180238==    by 0x4C8AC44: gtk_menu_shell_class_init (gtkmenushell.c:424)
```

Note we cannot use VALGRIND_FREELIKE_BLOCK() in freelist_free() because we
have not actually freed the FreeListNode and need to dereference it in
freelist_alloc() to decide whether to reuse the block. That would result
in a use-after-free warning before we would get a chance to call
VALGRIND_MALLOCLIKE_BLOCK() in the reuse path.

Also note that this free list only ever grows: it never shrinks for the
lifetime of the application, so nothing here will ever be truely freed,
although unused elements are eligible for reuse.

Fix suggested by Philip Withnall

Related: #2076
2020-11-24 08:10:58 -06:00

173 lines
4.8 KiB
C

/* GObject - GLib Type, Object, Parameter and Signal Library
* Copyright (C) 2009 Benjamin Otte <otte@gnome.org>
*
* This library 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.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "../glib/gvalgrind.h"
#include <string.h>
#include "gatomicarray.h"
/* A GAtomicArray is a growable, mutable array of data
* generally of the form of a header of a specific size and
* then an array of items of a fixed size.
*
* It is possible to do lock-less read transactions from the
* array without any protection against other reads or writes,
* but such read operation must be aware that the data in the
* atomic array can change at any time during the transaction,
* and only at the end can we verify if the transaction succeeded
* or not. Thus the reading transaction cannot for instance
* dereference a pointer in the array inside the transaction.
*
* The size of an array however cannot change during a read
* transaction.
*
* Writes to the array is done in a copy-update style, but there
* is no real protection against multiple writers overwriting each
* others updates, so writes must be protected by an external lock.
*/
G_LOCK_DEFINE_STATIC (array);
typedef struct _FreeListNode FreeListNode;
struct _FreeListNode {
FreeListNode *next;
};
/* This is really a list of array memory blocks, using the
* first item as the next pointer to chain them together.
* Protected by array lock */
static FreeListNode *freelist = NULL;
/* must hold array lock */
static gpointer
freelist_alloc (gsize size, gboolean reuse)
{
gpointer mem;
FreeListNode *free, **prev;
gsize real_size;
if (reuse)
{
for (free = freelist, prev = &freelist; free != NULL; prev = &free->next, free = free->next)
{
if (G_ATOMIC_ARRAY_DATA_SIZE (free) == size)
{
*prev = free->next;
return (gpointer)free;
}
}
}
real_size = sizeof (gsize) + MAX (size, sizeof (FreeListNode));
mem = g_slice_alloc (real_size);
mem = ((char *) mem) + sizeof (gsize);
G_ATOMIC_ARRAY_DATA_SIZE (mem) = size;
#if ENABLE_VALGRIND
VALGRIND_MALLOCLIKE_BLOCK (mem, real_size - sizeof (gsize), FALSE, FALSE);
#endif
return mem;
}
/* must hold array lock */
static void
freelist_free (gpointer mem)
{
FreeListNode *free;
free = mem;
free->next = freelist;
freelist = free;
}
void
_g_atomic_array_init (GAtomicArray *array)
{
array->data = NULL;
}
/* Get a copy of the data (if non-NULL) that
* can be changed and then re-applied with
* g_atomic_array_update().
*
* If additional_element_size is > 0 then
* then the new memory chunk is that much
* larger, or there were no data we return
* a chunk of header_size + additional_element_size.
* This means you can use this to grow the
* array part and it handles the first element
* being added automatically.
*
* We don't support shrinking arrays, as if
* we then re-grow we may reuse an old pointer
* value and confuse the transaction check.
*/
gpointer
_g_atomic_array_copy (GAtomicArray *array,
gsize header_size,
gsize additional_element_size)
{
guint8 *new, *old;
gsize old_size, new_size;
G_LOCK (array);
old = g_atomic_pointer_get (&array->data);
if (old)
{
old_size = G_ATOMIC_ARRAY_DATA_SIZE (old);
new_size = old_size + additional_element_size;
/* Don't reuse if copying to same size, as this may end
up reusing the same pointer for the same array thus
confusing the transaction check */
new = freelist_alloc (new_size, additional_element_size != 0);
memcpy (new, old, old_size);
}
else if (additional_element_size != 0)
{
new_size = header_size + additional_element_size;
new = freelist_alloc (new_size, TRUE);
}
else
new = NULL;
G_UNLOCK (array);
return new;
}
/* Replace the data in the array with the new data,
* freeing the old data (for reuse). The new data may
* not be smaller than the current data.
*/
void
_g_atomic_array_update (GAtomicArray *array,
gpointer new_data)
{
guint8 *old;
G_LOCK (array);
old = g_atomic_pointer_get (&array->data);
g_assert (old == NULL || G_ATOMIC_ARRAY_DATA_SIZE (old) <= G_ATOMIC_ARRAY_DATA_SIZE (new_data));
g_atomic_pointer_set (&array->data, new_data);
if (old)
freelist_free (old);
G_UNLOCK (array);
}