glib/gobject/tests/reference.c
Marco Trevisan (Treviño) 0918ce013a gobject: Do not call toggle down notifications if current refcount is not 1
When an object is revitalized and a notify callbacks increased the reference
counter of the object, we are calling the toggle notifier twice, while it
should only happen if also the actual reference count value is 1 (after
having been decremented from 2).
2022-12-06 04:38:26 +01:00

1374 lines
37 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.

#include <glib-object.h>
static void
test_fundamentals (void)
{
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_NONE));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_INTERFACE));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_CHAR));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_UCHAR));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_BOOLEAN));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_INT));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_UINT));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_LONG));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_ULONG));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_INT64));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_UINT64));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_ENUM));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_FLAGS));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_FLOAT));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_DOUBLE));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_STRING));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_POINTER));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_BOXED));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_PARAM));
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_OBJECT));
g_assert (G_TYPE_OBJECT == g_object_get_type ());
g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_VARIANT));
g_assert (G_TYPE_IS_DERIVED (G_TYPE_INITIALLY_UNOWNED));
g_assert (g_type_fundamental_next () == G_TYPE_MAKE_FUNDAMENTAL (G_TYPE_RESERVED_USER_FIRST));
}
static void
test_type_qdata (void)
{
gchar *data;
g_type_set_qdata (G_TYPE_ENUM, g_quark_from_string ("bla"), "bla");
data = g_type_get_qdata (G_TYPE_ENUM, g_quark_from_string ("bla"));
g_assert_cmpstr (data, ==, "bla");
}
static void
test_type_query (void)
{
GTypeQuery query;
g_type_query (G_TYPE_ENUM, &query);
g_assert_cmpint (query.type, ==, G_TYPE_ENUM);
g_assert_cmpstr (query.type_name, ==, "GEnum");
g_assert_cmpint (query.class_size, ==, sizeof (GEnumClass));
g_assert_cmpint (query.instance_size, ==, 0);
}
typedef struct _MyObject MyObject;
typedef struct _MyObjectClass MyObjectClass;
typedef struct _MyObjectClassPrivate MyObjectClassPrivate;
struct _MyObject
{
GObject parent_instance;
gint count;
};
struct _MyObjectClass
{
GObjectClass parent_class;
};
struct _MyObjectClassPrivate
{
gint secret_class_count;
};
static GType my_object_get_type (void);
G_DEFINE_TYPE_WITH_CODE (MyObject, my_object, G_TYPE_OBJECT,
g_type_add_class_private (g_define_type_id, sizeof (MyObjectClassPrivate)) );
static void
my_object_init (MyObject *obj)
{
obj->count = 42;
}
static void
my_object_class_init (MyObjectClass *klass)
{
}
static void
test_class_private (void)
{
GObject *obj;
MyObjectClass *class;
MyObjectClassPrivate *priv;
obj = g_object_new (my_object_get_type (), NULL);
class = g_type_class_ref (my_object_get_type ());
priv = G_TYPE_CLASS_GET_PRIVATE (class, my_object_get_type (), MyObjectClassPrivate);
priv->secret_class_count = 13;
g_type_class_unref (class);
g_object_unref (obj);
g_assert_cmpint (g_type_qname (my_object_get_type ()), ==, g_quark_from_string ("MyObject"));
}
static void
test_clear (void)
{
GObject *o = NULL;
GObject *tmp;
g_clear_object (&o);
g_assert (o == NULL);
tmp = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (tmp->ref_count, ==, 1);
o = g_object_ref (tmp);
g_assert (o != NULL);
g_assert_cmpint (tmp->ref_count, ==, 2);
g_clear_object (&o);
g_assert_cmpint (tmp->ref_count, ==, 1);
g_assert (o == NULL);
g_object_unref (tmp);
}
static void
test_clear_function (void)
{
GObject *o = NULL;
GObject *tmp;
(g_clear_object) (&o);
g_assert (o == NULL);
tmp = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (tmp->ref_count, ==, 1);
o = g_object_ref (tmp);
g_assert (o != NULL);
g_assert_cmpint (tmp->ref_count, ==, 2);
(g_clear_object) (&o);
g_assert_cmpint (tmp->ref_count, ==, 1);
g_assert (o == NULL);
g_object_unref (tmp);
}
static void
test_set (void)
{
GObject *o = NULL;
GObject *tmp;
gpointer tmp_weak = NULL;
g_assert (!g_set_object (&o, NULL));
g_assert (o == NULL);
tmp = g_object_new (G_TYPE_OBJECT, NULL);
tmp_weak = tmp;
g_object_add_weak_pointer (tmp, &tmp_weak);
g_assert_cmpint (tmp->ref_count, ==, 1);
g_assert (g_set_object (&o, tmp));
g_assert (o == tmp);
g_assert_cmpint (tmp->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (tmp->ref_count, ==, 1);
/* Setting it again shouldnt cause finalisation. */
g_assert (!g_set_object (&o, tmp));
g_assert (o == tmp);
g_assert_cmpint (tmp->ref_count, ==, 1);
g_assert_nonnull (tmp_weak);
g_assert (g_set_object (&o, NULL));
g_assert (o == NULL);
g_assert_null (tmp_weak);
}
static void
test_set_function (void)
{
GObject *o = NULL;
GObject *tmp;
gpointer tmp_weak = NULL;
g_assert (!(g_set_object) (&o, NULL));
g_assert (o == NULL);
tmp = g_object_new (G_TYPE_OBJECT, NULL);
tmp_weak = tmp;
g_object_add_weak_pointer (tmp, &tmp_weak);
g_assert_cmpint (tmp->ref_count, ==, 1);
g_assert ((g_set_object) (&o, tmp));
g_assert (o == tmp);
g_assert_cmpint (tmp->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (tmp->ref_count, ==, 1);
/* Setting it again shouldnt cause finalisation. */
g_assert (!(g_set_object) (&o, tmp));
g_assert (o == tmp);
g_assert_cmpint (tmp->ref_count, ==, 1);
g_assert_nonnull (tmp_weak);
g_assert ((g_set_object) (&o, NULL));
g_assert (o == NULL);
g_assert_null (tmp_weak);
}
static void
test_set_derived_type (void)
{
GBinding *obj = NULL;
GObject *o = NULL;
GBinding *b = NULL;
g_test_summary ("Check that g_set_object() doesnt give strict aliasing "
"warnings when used on types derived from GObject");
g_assert_false (g_set_object (&o, NULL));
g_assert_null (o);
g_assert_false (g_set_object (&b, NULL));
g_assert_null (b);
obj = g_object_new (my_object_get_type (), NULL);
g_assert_true (g_set_object (&o, G_OBJECT (obj)));
g_assert_true (o == G_OBJECT (obj));
g_assert_true (g_set_object (&b, obj));
g_assert_true (b == obj);
g_object_unref (obj);
g_clear_object (&b);
g_clear_object (&o);
}
static void
toggle_cb (gpointer data, GObject *obj, gboolean is_last)
{
gboolean *b = data;
*b = TRUE;
}
static void
test_object_value (void)
{
GObject *v;
GObject *v2;
GValue value = G_VALUE_INIT;
gboolean toggled = FALSE;
g_value_init (&value, G_TYPE_OBJECT);
v = g_object_new (G_TYPE_OBJECT, NULL);
g_object_add_toggle_ref (v, toggle_cb, &toggled);
g_value_take_object (&value, v);
v2 = g_value_get_object (&value);
g_assert (v2 == v);
v2 = g_value_dup_object (&value);
g_assert (v2 == v); /* objects use ref/unref for copy/free */
g_object_unref (v2);
g_assert (!toggled);
g_value_unset (&value);
g_assert (toggled);
/* test the deprecated variant too */
g_value_init (&value, G_TYPE_OBJECT);
/* get a new reference */
g_object_ref (v);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
g_value_set_object_take_ownership (&value, v);
G_GNUC_END_IGNORE_DEPRECATIONS
toggled = FALSE;
g_value_unset (&value);
g_assert (toggled);
g_object_remove_toggle_ref (v, toggle_cb, &toggled);
}
static void
test_initially_unowned (void)
{
GObject *obj;
obj = g_object_new (G_TYPE_INITIALLY_UNOWNED, NULL);
g_assert (g_object_is_floating (obj));
g_assert_cmpint (obj->ref_count, ==, 1);
g_object_ref_sink (obj);
g_assert (!g_object_is_floating (obj));
g_assert_cmpint (obj->ref_count, ==, 1);
g_object_ref_sink (obj);
g_assert (!g_object_is_floating (obj));
g_assert_cmpint (obj->ref_count, ==, 2);
g_object_unref (obj);
g_assert_cmpint (obj->ref_count, ==, 1);
g_object_force_floating (obj);
g_assert (g_object_is_floating (obj));
g_assert_cmpint (obj->ref_count, ==, 1);
g_object_ref_sink (obj);
g_object_unref (obj);
obj = g_object_new (G_TYPE_INITIALLY_UNOWNED, NULL);
g_assert_true (g_object_is_floating (obj));
g_assert_cmpint (obj->ref_count, ==, 1);
g_object_take_ref (obj);
g_assert_false (g_object_is_floating (obj));
g_assert_cmpint (obj->ref_count, ==, 1);
g_object_take_ref (obj);
g_assert_false (g_object_is_floating (obj));
g_assert_cmpint (obj->ref_count, ==, 1);
g_object_unref (obj);
if (g_test_undefined ())
{
obj = g_object_new (G_TYPE_INITIALLY_UNOWNED, NULL);
#ifdef G_ENABLE_DEBUG
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"A floating object GInitiallyUnowned * was finalized*");
#endif
g_object_unref (obj);
#ifdef G_ENABLE_DEBUG
g_test_assert_expected_messages ();
#endif
}
}
static void
test_weak_pointer (void)
{
GObject *obj;
gpointer weak;
gpointer weak2;
weak = weak2 = obj = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (obj->ref_count, ==, 1);
g_object_add_weak_pointer (obj, &weak);
g_object_add_weak_pointer (obj, &weak2);
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert (weak == obj);
g_assert (weak2 == obj);
g_object_remove_weak_pointer (obj, &weak2);
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert (weak == obj);
g_assert (weak2 == obj);
g_object_unref (obj);
g_assert (weak == NULL);
g_assert (weak2 == obj);
}
static void
test_weak_pointer_clear (void)
{
GObject *obj;
gpointer weak = NULL;
g_clear_weak_pointer (&weak);
g_assert_null (weak);
weak = obj = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (obj->ref_count, ==, 1);
g_object_add_weak_pointer (obj, &weak);
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_true (weak == obj);
g_clear_weak_pointer (&weak);
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_null (weak);
g_object_unref (obj);
}
static void
test_weak_pointer_clear_function (void)
{
GObject *obj;
gpointer weak = NULL;
(g_clear_weak_pointer) (&weak);
g_assert_null (weak);
weak = obj = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (obj->ref_count, ==, 1);
g_object_add_weak_pointer (obj, &weak);
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_true (weak == obj);
(g_clear_weak_pointer) (&weak);
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_null (weak);
g_object_unref (obj);
}
static void
test_weak_pointer_set (void)
{
GObject *obj;
gpointer weak = NULL;
g_assert_false (g_set_weak_pointer (&weak, NULL));
g_assert_null (weak);
obj = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_true (g_set_weak_pointer (&weak, obj));
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_true (weak == obj);
g_assert_true (g_set_weak_pointer (&weak, NULL));
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_null (weak);
g_assert_true (g_set_weak_pointer (&weak, obj));
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_true (weak == obj);
g_object_unref (obj);
g_assert_null (weak);
}
static void
test_weak_pointer_set_function (void)
{
GObject *obj;
gpointer weak = NULL;
g_assert_false ((g_set_weak_pointer) (&weak, NULL));
g_assert_null (weak);
obj = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_true ((g_set_weak_pointer) (&weak, obj));
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_true (weak == obj);
g_assert_true ((g_set_weak_pointer) (&weak, NULL));
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_null (weak);
g_assert_true ((g_set_weak_pointer) (&weak, obj));
g_assert_cmpint (obj->ref_count, ==, 1);
g_assert_true (weak == obj);
g_object_unref (obj);
g_assert_null (weak);
}
/* See gobject/tests/threadtests.c for the threaded version */
static void
test_weak_ref (void)
{
GObject *obj;
GObject *obj2;
GObject *tmp;
GWeakRef weak = { { GUINT_TO_POINTER (0xDEADBEEFU) } };
GWeakRef weak2 = { { GUINT_TO_POINTER (0xDEADBEEFU) } };
GWeakRef weak3 = { { GUINT_TO_POINTER (0xDEADBEEFU) } };
GWeakRef *dynamic_weak = g_new (GWeakRef, 1);
/* you can initialize to empty like this... */
g_weak_ref_init (&weak2, NULL);
g_assert (g_weak_ref_get (&weak2) == NULL);
/* ... or via an initializer */
g_weak_ref_init (&weak3, NULL);
g_assert (g_weak_ref_get (&weak3) == NULL);
obj = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (obj->ref_count, ==, 1);
obj2 = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (obj2->ref_count, ==, 1);
/* you can init with an object (even if uninitialized) */
g_weak_ref_init (&weak, obj);
g_weak_ref_init (dynamic_weak, obj);
/* or set to point at an object, if initialized (maybe to 0) */
g_weak_ref_set (&weak2, obj);
g_weak_ref_set (&weak3, obj);
/* none of this affects its refcount */
g_assert_cmpint (obj->ref_count, ==, 1);
/* getting the value takes a ref */
tmp = g_weak_ref_get (&weak);
g_assert (tmp == obj);
g_assert_cmpint (obj->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (obj->ref_count, ==, 1);
tmp = g_weak_ref_get (&weak2);
g_assert (tmp == obj);
g_assert_cmpint (obj->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (obj->ref_count, ==, 1);
tmp = g_weak_ref_get (&weak3);
g_assert (tmp == obj);
g_assert_cmpint (obj->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (obj->ref_count, ==, 1);
tmp = g_weak_ref_get (dynamic_weak);
g_assert (tmp == obj);
g_assert_cmpint (obj->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (obj->ref_count, ==, 1);
/* clearing a weak ref stops tracking */
g_weak_ref_clear (&weak);
/* setting a weak ref to NULL stops tracking too */
g_weak_ref_set (&weak2, NULL);
g_assert (g_weak_ref_get (&weak2) == NULL);
g_weak_ref_clear (&weak2);
/* setting a weak ref to a new object stops tracking the old one */
g_weak_ref_set (dynamic_weak, obj2);
tmp = g_weak_ref_get (dynamic_weak);
g_assert (tmp == obj2);
g_assert_cmpint (obj2->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (obj2->ref_count, ==, 1);
g_assert_cmpint (obj->ref_count, ==, 1);
/* free the object: weak3 is the only one left pointing there */
g_object_unref (obj);
g_assert (g_weak_ref_get (&weak3) == NULL);
/* setting a weak ref to a new object stops tracking the old one */
g_weak_ref_set (dynamic_weak, obj2);
tmp = g_weak_ref_get (dynamic_weak);
g_assert (tmp == obj2);
g_assert_cmpint (obj2->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (obj2->ref_count, ==, 1);
g_weak_ref_clear (&weak3);
/* unset dynamic_weak... */
g_weak_ref_set (dynamic_weak, NULL);
g_assert_null (g_weak_ref_get (dynamic_weak));
/* initializing a weak reference to an object that had before works */
g_weak_ref_set (dynamic_weak, obj2);
tmp = g_weak_ref_get (dynamic_weak);
g_assert_true (tmp == obj2);
g_assert_cmpint (obj2->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (obj2->ref_count, ==, 1);
/* clear and free dynamic_weak... */
g_weak_ref_clear (dynamic_weak);
/* ... to prove that doing so stops this from being a use-after-free */
g_object_unref (obj2);
g_free (dynamic_weak);
}
G_DECLARE_FINAL_TYPE (WeakReffedObject, weak_reffed_object,
WEAK, REFFED_OBJECT, GObject)
struct _WeakReffedObject
{
GObject parent;
GWeakRef *weak_ref;
};
G_DEFINE_TYPE (WeakReffedObject, weak_reffed_object, G_TYPE_OBJECT)
static void
weak_reffed_object_dispose (GObject *object)
{
WeakReffedObject *weak_reffed = WEAK_REFFED_OBJECT (object);
g_assert_cmpint (object->ref_count, ==, 1);
g_weak_ref_set (weak_reffed->weak_ref, object);
G_OBJECT_CLASS (weak_reffed_object_parent_class)->dispose (object);
g_assert_null (g_weak_ref_get (weak_reffed->weak_ref));
}
static void
weak_reffed_object_init (WeakReffedObject *connector)
{
}
static void
weak_reffed_object_class_init (WeakReffedObjectClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = weak_reffed_object_dispose;
}
static void
test_weak_ref_on_dispose (void)
{
WeakReffedObject *obj;
GWeakRef weak = { { GUINT_TO_POINTER (0xDEADBEEFU) } };
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2390");
g_test_summary ("Test that a weak ref set during dispose vfunc is cleared");
g_weak_ref_init (&weak, NULL);
obj = g_object_new (weak_reffed_object_get_type (), NULL);
obj->weak_ref = &weak;
g_assert_cmpint (G_OBJECT (obj)->ref_count, ==, 1);
g_clear_object (&obj);
g_assert_null (g_weak_ref_get (&weak));
}
static void
test_weak_ref_on_run_dispose (void)
{
GObject *obj;
GWeakRef weak = { { GUINT_TO_POINTER (0xDEADBEEFU) } };
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/865");
g_test_summary ("Test that a weak ref is cleared on g_object_run_dispose()");
obj = g_object_new (G_TYPE_OBJECT, NULL);
g_weak_ref_init (&weak, obj);
g_assert_true (obj == g_weak_ref_get (&weak));
g_object_unref (obj);
g_object_run_dispose (obj);
g_assert_null (g_weak_ref_get (&weak));
g_clear_object (&obj);
g_assert_null (g_weak_ref_get (&weak));
}
static void
on_weak_ref_toggle_notify (gpointer data,
GObject *object,
gboolean is_last_ref)
{
GWeakRef *weak = data;
if (is_last_ref)
g_weak_ref_set (weak, object);
}
static void
on_weak_ref_toggle_notify_disposed (gpointer data,
GObject *object)
{
g_assert_cmpint (object->ref_count, ==, 1);
g_object_ref (object);
g_object_unref (object);
}
static void
test_weak_ref_on_toggle_notify (void)
{
GObject *obj;
GWeakRef weak = { { GUINT_TO_POINTER (0xDEADBEEFU) } };
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2390");
g_test_summary ("Test that a weak ref set on toggle notify is cleared");
g_weak_ref_init (&weak, NULL);
obj = g_object_new (G_TYPE_OBJECT, NULL);
g_object_add_toggle_ref (obj, on_weak_ref_toggle_notify, &weak);
g_object_weak_ref (obj, on_weak_ref_toggle_notify_disposed, NULL);
g_object_unref (obj);
g_assert_cmpint (obj->ref_count, ==, 1);
g_clear_object (&obj);
g_assert_null (g_weak_ref_get (&weak));
}
typedef struct
{
gboolean should_be_last;
gint count;
} Count;
static void
toggle_notify (gpointer data,
GObject *obj,
gboolean is_last)
{
Count *c = data;
g_assert (is_last == c->should_be_last);
if (is_last)
g_assert_cmpint (g_atomic_int_get (&obj->ref_count), ==, 1);
else
g_assert_cmpint (g_atomic_int_get (&obj->ref_count), ==, 2);
c->count++;
}
static void
test_toggle_ref (void)
{
GObject *obj;
Count c, c2;
obj = g_object_new (G_TYPE_OBJECT, NULL);
g_object_add_toggle_ref (obj, toggle_notify, &c);
g_object_add_toggle_ref (obj, toggle_notify, &c2);
c.should_be_last = c2.should_be_last = TRUE;
c.count = c2.count = 0;
g_object_unref (obj);
g_assert_cmpint (c.count, ==, 0);
g_assert_cmpint (c2.count, ==, 0);
g_object_ref (obj);
g_assert_cmpint (c.count, ==, 0);
g_assert_cmpint (c2.count, ==, 0);
g_object_remove_toggle_ref (obj, toggle_notify, &c2);
g_object_unref (obj);
g_assert_cmpint (c.count, ==, 1);
c.should_be_last = FALSE;
g_object_ref (obj);
g_assert_cmpint (c.count, ==, 2);
c.should_be_last = TRUE;
g_object_unref (obj);
g_assert_cmpint (c.count, ==, 3);
g_object_remove_toggle_ref (obj, toggle_notify, &c);
}
G_DECLARE_FINAL_TYPE (DisposeReffingObject, dispose_reffing_object,
DISPOSE, REFFING_OBJECT, GObject)
typedef enum
{
PROP_INT_PROP = 1,
N_PROPS,
} DisposeReffingObjectProperty;
static GParamSpec *dispose_reffing_object_properties[N_PROPS] = {0};
struct _DisposeReffingObject
{
GObject parent;
GToggleNotify toggle_notify;
Count actual;
Count expected;
unsigned disposing_refs;
gboolean disposing_refs_all_normal;
GCallback notify_handler;
unsigned notify_called;
int int_prop;
GWeakRef *weak_ref;
};
G_DEFINE_TYPE (DisposeReffingObject, dispose_reffing_object, G_TYPE_OBJECT)
static void
on_object_notify (GObject *object,
GParamSpec *pspec,
void *data)
{
DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);
obj->notify_called++;
}
static void
dispose_reffing_object_dispose (GObject *object)
{
DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);
g_assert_cmpint (object->ref_count, ==, 1);
g_assert_cmpint (obj->actual.count, ==, obj->expected.count);
for (unsigned i = 0; i < obj->disposing_refs; ++i)
{
if (i == 0 && !obj->disposing_refs_all_normal)
{
g_object_add_toggle_ref (object, obj->toggle_notify, &obj->actual);
}
else
{
obj->actual.should_be_last = FALSE;
g_object_ref (obj);
g_assert_cmpint (obj->actual.count, ==, obj->expected.count);
}
obj->actual.should_be_last = TRUE;
}
G_OBJECT_CLASS (dispose_reffing_object_parent_class)->dispose (object);
if (obj->notify_handler)
{
unsigned old_notify_called = obj->notify_called;
g_assert_cmpuint (g_signal_handler_find (object, G_SIGNAL_MATCH_FUNC,
0, 0, NULL, obj->notify_handler, NULL), ==, 0);
g_signal_connect (object, "notify", G_CALLBACK (obj->notify_handler), NULL);
/* This would trigger a toggle notification, but is not something we may
* want with https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2377
* so, we only test this in case we have more than one ref
*/
if (obj->toggle_notify == toggle_notify)
g_assert_cmpint (obj->disposing_refs, >, 1);
g_object_notify (object, "int-prop");
g_assert_cmpuint (obj->notify_called, ==, old_notify_called);
}
g_assert_cmpint (obj->actual.count, ==, obj->expected.count);
}
static void
dispose_reffing_object_init (DisposeReffingObject *connector)
{
}
static void
dispose_reffing_object_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);
switch ((DisposeReffingObjectProperty) property_id)
{
case PROP_INT_PROP:
obj->int_prop = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
dispose_reffing_object_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);
switch ((DisposeReffingObjectProperty) property_id)
{
case PROP_INT_PROP:
g_value_set_int (value, obj->int_prop);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
dispose_reffing_object_class_init (DisposeReffingObjectClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
dispose_reffing_object_properties[PROP_INT_PROP] =
g_param_spec_int ("int-prop", "int-prop", "int-prop",
G_MININT, G_MAXINT,
0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
object_class->dispose = dispose_reffing_object_dispose;
object_class->set_property = dispose_reffing_object_set_property;
object_class->get_property = dispose_reffing_object_get_property;
g_object_class_install_properties (object_class, N_PROPS,
dispose_reffing_object_properties);
}
static void
test_toggle_ref_on_dispose (void)
{
DisposeReffingObject *obj;
gpointer disposed_checker = &obj;
/* This tests wants to ensure that an object that gets re-referenced
* (one or multiple times) during its dispose virtual function:
* - Notifies all the queued "notify" signal handlers
* - Notifies toggle notifications if any
* - It does not get finalized
*/
obj = g_object_new (dispose_reffing_object_get_type (), NULL);
obj->toggle_notify = toggle_notify;
obj->notify_handler = G_CALLBACK (on_object_notify);
g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);
/* Convert to toggle notification */
g_object_add_toggle_ref (G_OBJECT (obj), obj->toggle_notify, &obj->actual);
g_assert_cmpint (obj->actual.count, ==, 0);
obj->actual.should_be_last = TRUE;
obj->notify_handler = G_CALLBACK (on_object_notify);
g_object_unref (obj);
g_assert_cmpint (obj->actual.count, ==, 1);
g_assert_cmpuint (obj->notify_called, ==, 0);
/* Remove the toggle reference, making it to dispose and resurrect again */
obj->disposing_refs = 1;
obj->expected.count = 1;
obj->notify_handler = NULL; /* FIXME: enable it when !2377 is in */
g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
g_assert_cmpint (obj->actual.count, ==, 2);
g_assert_cmpuint (obj->notify_called, ==, 0);
g_assert_null (disposed_checker);
g_assert_cmpint (g_atomic_int_get (&G_OBJECT (obj)->ref_count), ==,
obj->disposing_refs);
/* Object has been disposed, but is still alive, so add another weak pointer */
disposed_checker = &obj;
g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);
/* Remove the toggle reference, making it to dispose and resurrect with
* more references than before, so that no toggle notify is called
*/
obj->disposing_refs = 3;
obj->expected.count = 2;
obj->notify_handler = G_CALLBACK (on_object_notify);
g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
g_assert_cmpint (obj->actual.count, ==, 2);
g_assert_cmpint (obj->notify_called, ==, 1);
obj->expected.count = obj->actual.count;
g_assert_null (disposed_checker);
g_assert_cmpint (g_atomic_int_get (&G_OBJECT (obj)->ref_count), ==,
obj->disposing_refs);
disposed_checker = &obj;
g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);
/* Now remove the first added reference */
obj->disposing_refs = 0;
g_object_unref (obj);
g_assert_nonnull (disposed_checker);
g_assert_cmpint (g_atomic_int_get (&G_OBJECT (obj)->ref_count), ==, 2);
g_assert_cmpint (obj->actual.count, ==, 2);
g_assert_cmpint (obj->notify_called, ==, 1);
/* And the toggle one */
obj->actual.should_be_last = TRUE;
obj->notify_handler = NULL;
g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
g_assert_nonnull (disposed_checker);
g_assert_cmpint (g_atomic_int_get (&G_OBJECT (obj)->ref_count), ==, 1);
g_assert_cmpint (obj->actual.count, ==, 2);
obj->expected.count = obj->actual.count;
g_clear_object (&obj);
g_assert_null (disposed_checker);
}
static void
toggle_notify_counter (gpointer data,
GObject *obj,
gboolean is_last)
{
Count *c = data;
c->count++;
if (is_last)
g_assert_cmpint (g_atomic_int_get (&obj->ref_count), ==, 1);
else
g_assert_cmpint (g_atomic_int_get (&obj->ref_count), ==, 2);
}
static void
on_object_notify_switch_to_normal_ref (GObject *object,
GParamSpec *pspec,
void *data)
{
DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);
obj->notify_called++;
g_object_ref (object);
g_object_remove_toggle_ref (object, obj->toggle_notify, NULL);
}
static void
on_object_notify_switch_to_toggle_ref (GObject *object,
GParamSpec *pspec,
void *data)
{
DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);
obj->notify_called++;
g_object_add_toggle_ref (object, obj->toggle_notify, &obj->actual);
g_object_unref (object);
}
static void
on_object_notify_add_ref (GObject *object,
GParamSpec *pspec,
void *data)
{
DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);
int old_toggle_cout = obj->actual.count;
obj->notify_called++;
g_object_ref (object);
g_assert_cmpint (obj->actual.count, ==, old_toggle_cout);
}
static void
test_toggle_ref_and_notify_on_dispose (void)
{
DisposeReffingObject *obj;
gpointer disposed_checker = &obj;
/* This tests wants to ensure that toggle signal emission during dispose
* is properly working if the object is revitalized by adding new references.
* It also wants to check that toggle notifications are not happening if a
* notify handler is removing them at this phase.
*/
obj = g_object_new (dispose_reffing_object_get_type (), NULL);
obj->toggle_notify = toggle_notify_counter;
g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);
/* Convert to toggle notification */
g_object_add_toggle_ref (G_OBJECT (obj), obj->toggle_notify, &obj->actual);
g_assert_cmpint (obj->actual.count, ==, 0);
obj->notify_handler = G_CALLBACK (on_object_notify);
g_object_unref (obj);
g_assert_cmpint (obj->actual.count, ==, 1);
g_assert_cmpuint (obj->notify_called, ==, 0);
disposed_checker = &obj;
g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);
/* Check that notification is triggered after being queued */
obj->disposing_refs = 1;
obj->expected.count = 1;
obj->notify_handler = G_CALLBACK (on_object_notify);
g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
/* FIXME: adjust the count to 1 when !2377 is in */
g_assert_cmpint (obj->actual.count, ==, 4);
g_assert_cmpuint (obj->notify_called, ==, 1);
disposed_checker = &obj;
g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);
/* Check that notification is triggered after being queued, but no toggle
* notification is happening if notify handler switches to normal reference
*/
obj->disposing_refs = 1;
obj->expected.count = 4;
obj->notify_handler = G_CALLBACK (on_object_notify_switch_to_normal_ref);
g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
g_assert_cmpint (obj->actual.count, ==, 5);
g_assert_cmpuint (obj->notify_called, ==, 2);
disposed_checker = &obj;
g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);
/* Check that notification is triggered after being queued, but that toggle
* is happening if notify handler switched to toggle reference
*/
obj->disposing_refs = 1;
obj->disposing_refs_all_normal = TRUE;
obj->expected.count = 5;
obj->notify_handler = G_CALLBACK (on_object_notify_switch_to_toggle_ref);
g_object_unref (obj);
g_assert_cmpint (obj->actual.count, ==, 7);
g_assert_cmpuint (obj->notify_called, ==, 3);
disposed_checker = &obj;
g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);
/* Check that notification is triggered after being queued, but that toggle
* is not happening if current refcount changed.
*/
obj->disposing_refs = 1;
obj->disposing_refs_all_normal = FALSE;
obj->expected.count = 7;
obj->notify_handler = G_CALLBACK (on_object_notify_add_ref);
g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
g_assert_cmpint (obj->actual.count, ==, 8);
g_assert_cmpuint (obj->notify_called, ==, 4);
g_object_unref (obj);
disposed_checker = &obj;
g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);
obj->disposing_refs = 0;
obj->expected.count = 9;
g_clear_object (&obj);
g_assert_null (disposed_checker);
}
static gboolean global_destroyed;
static gint global_value;
static void
data_destroy (gpointer data)
{
g_assert_cmpint (GPOINTER_TO_INT (data), ==, global_value);
global_destroyed = TRUE;
}
static void
test_object_qdata (void)
{
GObject *obj;
gpointer v;
GQuark quark;
obj = g_object_new (G_TYPE_OBJECT, NULL);
global_value = 1;
global_destroyed = FALSE;
g_object_set_data_full (obj, "test", GINT_TO_POINTER (1), data_destroy);
v = g_object_get_data (obj, "test");
g_assert_cmpint (GPOINTER_TO_INT (v), ==, 1);
g_object_set_data_full (obj, "test", GINT_TO_POINTER (2), data_destroy);
g_assert (global_destroyed);
global_value = 2;
global_destroyed = FALSE;
v = g_object_steal_data (obj, "test");
g_assert_cmpint (GPOINTER_TO_INT (v), ==, 2);
g_assert (!global_destroyed);
global_value = 1;
global_destroyed = FALSE;
quark = g_quark_from_string ("test");
g_object_set_qdata_full (obj, quark, GINT_TO_POINTER (1), data_destroy);
v = g_object_get_qdata (obj, quark);
g_assert_cmpint (GPOINTER_TO_INT (v), ==, 1);
g_object_set_qdata_full (obj, quark, GINT_TO_POINTER (2), data_destroy);
g_assert (global_destroyed);
global_value = 2;
global_destroyed = FALSE;
v = g_object_steal_qdata (obj, quark);
g_assert_cmpint (GPOINTER_TO_INT (v), ==, 2);
g_assert (!global_destroyed);
g_object_set_qdata_full (obj, quark, GINT_TO_POINTER (3), data_destroy);
global_value = 3;
global_destroyed = FALSE;
g_object_unref (obj);
g_assert (global_destroyed);
}
typedef struct {
const gchar *value;
gint refcount;
} Value;
static gpointer
ref_value (gpointer value, gpointer user_data)
{
Value *v = value;
Value **old_value_p = user_data;
if (old_value_p)
*old_value_p = v;
if (v)
v->refcount += 1;
return value;
}
static void
unref_value (gpointer value)
{
Value *v = value;
v->refcount -= 1;
if (v->refcount == 0)
g_free (value);
}
static
Value *
new_value (const gchar *s)
{
Value *v;
v = g_new (Value, 1);
v->value = s;
v->refcount = 1;
return v;
}
static void
test_object_qdata2 (void)
{
GObject *obj;
Value *v, *v1, *v2, *v3, *old_val;
GDestroyNotify old_destroy;
gboolean res;
obj = g_object_new (G_TYPE_OBJECT, NULL);
v1 = new_value ("bla");
g_object_set_data_full (obj, "test", v1, unref_value);
v = g_object_get_data (obj, "test");
g_assert_cmpstr (v->value, ==, "bla");
g_assert_cmpint (v->refcount, ==, 1);
v = g_object_dup_data (obj, "test", ref_value, &old_val);
g_assert (old_val == v1);
g_assert_cmpstr (v->value, ==, "bla");
g_assert_cmpint (v->refcount, ==, 2);
unref_value (v);
v = g_object_dup_data (obj, "nono", ref_value, &old_val);
g_assert (old_val == NULL);
g_assert (v == NULL);
v2 = new_value ("not");
res = g_object_replace_data (obj, "test", v1, v2, unref_value, &old_destroy);
g_assert (res == TRUE);
g_assert (old_destroy == unref_value);
g_assert_cmpstr (v1->value, ==, "bla");
g_assert_cmpint (v1->refcount, ==, 1);
v = g_object_get_data (obj, "test");
g_assert_cmpstr (v->value, ==, "not");
g_assert_cmpint (v->refcount, ==, 1);
v3 = new_value ("xyz");
res = g_object_replace_data (obj, "test", v1, v3, unref_value, &old_destroy);
g_assert (res == FALSE);
g_assert_cmpstr (v2->value, ==, "not");
g_assert_cmpint (v2->refcount, ==, 1);
unref_value (v1);
res = g_object_replace_data (obj, "test", NULL, v3, unref_value, &old_destroy);
g_assert (res == FALSE);
g_assert_cmpstr (v2->value, ==, "not");
g_assert_cmpint (v2->refcount, ==, 1);
res = g_object_replace_data (obj, "test", v2, NULL, unref_value, &old_destroy);
g_assert (res == TRUE);
g_assert (old_destroy == unref_value);
g_assert_cmpstr (v2->value, ==, "not");
g_assert_cmpint (v2->refcount, ==, 1);
unref_value (v2);
v = g_object_get_data (obj, "test");
g_assert (v == NULL);
res = g_object_replace_data (obj, "test", NULL, v3, unref_value, &old_destroy);
g_assert (res == TRUE);
v = g_object_get_data (obj, "test");
g_assert (v == v3);
ref_value (v3, NULL);
g_assert_cmpint (v3->refcount, ==, 2);
g_object_unref (obj);
g_assert_cmpint (v3->refcount, ==, 1);
unref_value (v3);
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
g_setenv ("G_ENABLE_DIAGNOSTIC", "1", TRUE);
g_test_add_func ("/type/fundamentals", test_fundamentals);
g_test_add_func ("/type/qdata", test_type_qdata);
g_test_add_func ("/type/query", test_type_query);
g_test_add_func ("/type/class-private", test_class_private);
g_test_add_func ("/object/clear", test_clear);
g_test_add_func ("/object/clear-function", test_clear_function);
g_test_add_func ("/object/set", test_set);
g_test_add_func ("/object/set-function", test_set_function);
g_test_add_func ("/object/set/derived-type", test_set_derived_type);
g_test_add_func ("/object/value", test_object_value);
g_test_add_func ("/object/initially-unowned", test_initially_unowned);
g_test_add_func ("/object/weak-pointer", test_weak_pointer);
g_test_add_func ("/object/weak-pointer/clear", test_weak_pointer_clear);
g_test_add_func ("/object/weak-pointer/clear-function", test_weak_pointer_clear_function);
g_test_add_func ("/object/weak-pointer/set", test_weak_pointer_set);
g_test_add_func ("/object/weak-pointer/set-function", test_weak_pointer_set_function);
g_test_add_func ("/object/weak-ref", test_weak_ref);
g_test_add_func ("/object/weak-ref/on-dispose", test_weak_ref_on_dispose);
g_test_add_func ("/object/weak-ref/on-run-dispose", test_weak_ref_on_run_dispose);
g_test_add_func ("/object/weak-ref/on-toggle-notify", test_weak_ref_on_toggle_notify);
g_test_add_func ("/object/toggle-ref", test_toggle_ref);
g_test_add_func ("/object/toggle-ref/ref-on-dispose", test_toggle_ref_on_dispose);
g_test_add_func ("/object/toggle-ref/ref-and-notify-on-dispose", test_toggle_ref_and_notify_on_dispose);
g_test_add_func ("/object/qdata", test_object_qdata);
g_test_add_func ("/object/qdata2", test_object_qdata2);
return g_test_run ();
}