mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-25 15:06:14 +01:00
a412ffe4d3
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
173 lines
4.8 KiB
C
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);
|
|
}
|