Reputation: 413
I'm trying to serialize and deserialize a GHashTable
in and out of JSON and valgrind
reports memory that is definitely lost as a result of doing this. There are suppressions for g_hash_table_new
in the glib.suppressions
file that they distribute, but none for g_hash_table_new_full
which is what I see a bunch of. My hash table is a GObject
property that has been set using g_param_spec_pointer
, and the function to serialize it is:
static JsonNode *
foo_obj_serialize_property (JsonSerializable *serializable,
const gchar *name,
const GValue *value,
GParamSpec *pspec)
{
JsonNode *retval = NULL;
if (g_strcmp0 (name, "list") == 0)
{
GHashTable *list = NULL;
GHashTableIter iter;
JsonArray *arr = NULL;
gpointer key, val;
retval = json_node_new (JSON_NODE_ARRAY);
g_return_val_if_fail (value != NULL, retval);
g_return_val_if_fail (G_VALUE_HOLDS_POINTER (value), retval);
list = g_value_get_pointer (value);
arr = json_array_new ();
if (list != NULL)
{
g_hash_table_iter_init (&iter, list);
while (g_hash_table_iter_next (&iter, &key, &val))
{
JsonNode *node = NULL;
JsonObject *obj = NULL;
FooItem *item;
item = FOO_ITEM (val);
node = json_gobject_serialize (G_OBJECT (item));
if (JSON_NODE_HOLDS_OBJECT (node))
{
obj = json_node_get_object (node);
json_array_add_object_element (arr, obj);
}
}
}
json_node_take_array (retval, arr);
}
return retval;
}
the deserialize is:
static gboolean
foo_obj_deserialize_property (JsonSerializable *serializable,
const gchar *name,
GValue *value,
GParamSpec *pspec,
JsonNode *property_node)
{
gboolean retval = FALSE;
if (g_strcmp0 (name, "list") == 0)
{
GHashTable *list;
JsonArray *arr;
arr = json_node_get_array (property_node);
list = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
NULL);
for (gint i = 0; i < json_array_get_length (arr); i++)
{
g_autoptr (FooItem) item = NULL;
JsonNode *node = NULL;
node = json_array_get_element (arr, i);
item = FOO_ITEM (json_gobject_deserialize (FOO_TYPE_ITEM, node));
g_return_val_if_fail (FOO_IS_ITEM (item), FALSE);
g_object_ref (item);
g_hash_table_insert (list,
g_strdup (foo_item_get_key (item)),
item);
}
g_value_set_pointer (value, list);
retval = TRUE;
}
return retval;
}
my GObject
class property get/set functions are:
static void
foo_obj_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
FooObj *self = FOO_OBJ (object);
switch (prop_id)
{
case PROP_LIST:
g_value_set_pointer (value, self->list);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
foo_obj_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
FooObj *self = FOO_OBJ (object);
switch (prop_id)
{
case PROP_LIST:
foo_obj_set_list (self, g_value_get_pointer (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
and finally the class getter/setter:
GHashTable *
foo_obj_get_list (FooObj *self)
{
GHashTable *list;
g_return_val_if_fail (FOO_IS_OBJ (self), NULL);
g_object_get (self, "list", &list, NULL);
return list;
}
void
foo_obj_set_list (FooObj *self,
GHashTable *list)
{
g_return_if_fail (FOO_IS_OBJ (self));
if (self->list == list)
return;
if (list)
g_hash_table_ref (list);
if (self->list)
g_hash_table_unref (self->list);
self->list = list;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LIST]);
}
I'm running a memory check using valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --leak-resolution=high --num-callers=20 --suppressions=../tests/glib.supp foo
, and I get messages for lost memory for serialize showing:
==24303== 32 bytes in 1 blocks are definitely lost in loss record 1,029 of 1,852
==24303== at 0x483877F: malloc (vg_replace_malloc.c:299)
==24303== by 0x494CAB1: g_malloc (in /usr/lib/libglib-2.0.so.0.6000.4)
==24303== by 0x492C8E4: g_slice_alloc (in /usr/lib/libglib-2.0.so.0.6000.4)
==24303== by 0x4933839: g_slice_alloc0 (in /usr/lib/libglib-2.0.so.0.6000.4)
==24303== by 0x4D72CEE: json_node_alloc (in /usr/lib/libjson-glib-1.0.so.0.400.4)
==24303== by 0x4D72D37: json_node_new (in /usr/lib/libjson-glib-1.0.so.0.400.4)
==24303== by 0x4D77B37: json_gobject_serialize (in /usr/lib/libjson-glib-1.0.so.0.400.4)
==24303== by 0x4867AC9: foo_item_serialize_property (foo-item.c:71)
==24303== by 0x4D779C1: ??? (in /usr/lib/libjson-glib-1.0.so.0.400.4)
==24303== by 0x4D77B42: json_gobject_serialize (in /usr/lib/libjson-glib-1.0.so.0.400.4)
==24303== by 0x4861681: foo_obj_serialize_property (foo-obj.c:146)
==24303== by 0x4D779C1: ??? (in /usr/lib/libjson-glib-1.0.so.0.400.4)
==24303== by 0x4D77B42: json_gobject_serialize (in /usr/lib/libjson-glib-1.0.so.0.400.4)
==24303== by 0x4D77B82: json_gobject_to_data (in /usr/lib/libjson-glib-1.0.so.0.400.4)
and for deserialize:
==24303== 184 (88 direct, 96 indirect) bytes in 1 blocks are definitely lost in loss record 1,774 of 1,852
==24303== at 0x483877F: malloc (vg_replace_malloc.c:299)
==24303== by 0x494CAB1: g_malloc (in /usr/lib/libglib-2.0.so.0.6000.4)
==24303== by 0x492C8E4: g_slice_alloc (in /usr/lib/libglib-2.0.so.0.6000.4)
==24303== by 0x4966C5E: g_hash_table_new_full (in /usr/lib/libglib-2.0.so.0.6000.4)
==24303== by 0x4861AB2: foo_obj_deserialize_property (foo-obj.c:264)
==24303== by 0x4D77F00: ??? (in /usr/lib/libjson-glib-1.0.so.0.400.4)
==24303== by 0x4D7826B: json_gobject_from_data (in /usr/lib/libjson-glib-1.0.so.0.400.4)
==24303== by 0x4862BEE: foo_obj_deserialize (foo-obj.c:472)
I'm having a hard time seeing what I've missed, and various attempts to clear pointers result in a double free or error about ref count == 0. I know there's a lot of GLib
suppressions because of how it handles memory but I don't know if these fall under that.
I could probably box the hash table and simplify serialization by registering a function with json-glib
, but I don't want to go that direction unless I need to.
Upvotes: 1
Views: 472
Reputation: 2525
node = json_gobject_serialize (G_OBJECT (item));
After this line you do nothing with node
, neither store it somewhere, nor free. This results in leaking memory. Also note, that JsonNode
is a GBoxed
, not GObject
. Use json-node-free to free it.
I'd also suggest you read about reference counting and analysing valgrind output.
The stack trace tells you where the leaked memory was allocated. Memcheck cannot tell you why the memory leaked, unfortunately.
For deserialize
method it was allocated at (foo-obj.c:264), that's g_hash_table_new_full
. You create a hash table, but don't destroy it.
Upvotes: 2