2010-11-08 22:42:32 +01:00
|
|
|
|
#include <glib-object.h>
|
|
|
|
|
|
2011-02-14 05:47:42 +01:00
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2011-10-17 03:11:11 +02:00
|
|
|
|
static GType my_object_get_type (void);
|
2011-02-14 05:47:42 +01:00
|
|
|
|
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"));
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-08 22:42:32 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2011-02-14 05:47:42 +01:00
|
|
|
|
static void
|
|
|
|
|
test_clear_function (void)
|
|
|
|
|
{
|
2018-07-11 11:18:08 +02:00
|
|
|
|
GObject *o = NULL;
|
2011-02-14 05:47:42 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-16 12:29:03 +01:00
|
|
|
|
static void
|
|
|
|
|
test_set (void)
|
|
|
|
|
{
|
|
|
|
|
GObject *o = NULL;
|
|
|
|
|
GObject *tmp;
|
2018-06-27 10:59:57 +02:00
|
|
|
|
gpointer tmp_weak = NULL;
|
2014-12-16 12:29:03 +01:00
|
|
|
|
|
|
|
|
|
g_assert (!g_set_object (&o, NULL));
|
|
|
|
|
g_assert (o == NULL);
|
|
|
|
|
|
|
|
|
|
tmp = g_object_new (G_TYPE_OBJECT, NULL);
|
2018-06-27 10:59:57 +02:00
|
|
|
|
tmp_weak = tmp;
|
|
|
|
|
g_object_add_weak_pointer (tmp, &tmp_weak);
|
2014-12-16 12:29:03 +01:00
|
|
|
|
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 shouldn’t cause finalisation. */
|
|
|
|
|
g_assert (!g_set_object (&o, tmp));
|
|
|
|
|
g_assert (o == tmp);
|
|
|
|
|
g_assert_cmpint (tmp->ref_count, ==, 1);
|
2018-06-27 10:59:57 +02:00
|
|
|
|
g_assert_nonnull (tmp_weak);
|
2014-12-16 12:29:03 +01:00
|
|
|
|
|
|
|
|
|
g_assert (g_set_object (&o, NULL));
|
|
|
|
|
g_assert (o == NULL);
|
2018-06-27 10:59:57 +02:00
|
|
|
|
g_assert_null (tmp_weak);
|
2014-12-16 12:29:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_set_function (void)
|
|
|
|
|
{
|
|
|
|
|
GObject *o = NULL;
|
|
|
|
|
GObject *tmp;
|
2018-06-27 10:59:57 +02:00
|
|
|
|
gpointer tmp_weak = NULL;
|
2014-12-16 12:29:03 +01:00
|
|
|
|
|
|
|
|
|
g_assert (!(g_set_object) (&o, NULL));
|
|
|
|
|
g_assert (o == NULL);
|
|
|
|
|
|
|
|
|
|
tmp = g_object_new (G_TYPE_OBJECT, NULL);
|
2018-06-27 10:59:57 +02:00
|
|
|
|
tmp_weak = tmp;
|
|
|
|
|
g_object_add_weak_pointer (tmp, &tmp_weak);
|
2014-12-16 12:29:03 +01:00
|
|
|
|
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 shouldn’t cause finalisation. */
|
|
|
|
|
g_assert (!(g_set_object) (&o, tmp));
|
|
|
|
|
g_assert (o == tmp);
|
|
|
|
|
g_assert_cmpint (tmp->ref_count, ==, 1);
|
2018-06-27 10:59:57 +02:00
|
|
|
|
g_assert_nonnull (tmp_weak);
|
2014-12-16 12:29:03 +01:00
|
|
|
|
|
|
|
|
|
g_assert ((g_set_object) (&o, NULL));
|
|
|
|
|
g_assert (o == NULL);
|
2018-06-27 10:59:57 +02:00
|
|
|
|
g_assert_null (tmp_weak);
|
2014-12-16 12:29:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 13:10:07 +01:00
|
|
|
|
static void
|
|
|
|
|
test_set_derived_type (void)
|
|
|
|
|
{
|
|
|
|
|
GBinding *obj = NULL;
|
|
|
|
|
GObject *o = NULL;
|
|
|
|
|
GBinding *b = NULL;
|
|
|
|
|
|
|
|
|
|
g_test_summary ("Check that g_set_object() doesn’t 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);
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-23 14:19:06 +02:00
|
|
|
|
static void
|
|
|
|
|
toggle_cb (gpointer data, GObject *obj, gboolean is_last)
|
|
|
|
|
{
|
|
|
|
|
gboolean *b = data;
|
|
|
|
|
|
|
|
|
|
*b = TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2011-02-14 05:47:42 +01:00
|
|
|
|
static void
|
|
|
|
|
test_object_value (void)
|
|
|
|
|
{
|
|
|
|
|
GObject *v;
|
|
|
|
|
GObject *v2;
|
2011-09-30 18:19:50 +02:00
|
|
|
|
GValue value = G_VALUE_INIT;
|
2012-04-23 14:19:06 +02:00
|
|
|
|
gboolean toggled = FALSE;
|
2011-02-14 05:47:42 +01:00
|
|
|
|
|
|
|
|
|
g_value_init (&value, G_TYPE_OBJECT);
|
|
|
|
|
|
|
|
|
|
v = g_object_new (G_TYPE_OBJECT, NULL);
|
2012-04-23 14:19:06 +02:00
|
|
|
|
g_object_add_toggle_ref (v, toggle_cb, &toggled);
|
|
|
|
|
|
2011-02-14 05:47:42 +01:00
|
|
|
|
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);
|
|
|
|
|
|
2012-04-23 14:19:06 +02:00
|
|
|
|
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;
|
2011-02-14 05:47:42 +01:00
|
|
|
|
g_value_unset (&value);
|
2012-04-23 14:19:06 +02:00
|
|
|
|
g_assert (toggled);
|
|
|
|
|
|
|
|
|
|
g_object_remove_toggle_ref (v, toggle_cb, &toggled);
|
2011-02-14 05:47:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2021-06-10 06:56:41 +02:00
|
|
|
|
|
|
|
|
|
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);
|
2022-09-21 12:13:39 +02:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2011-02-14 05:47:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-20 23:05:53 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-08 18:35:19 +01:00
|
|
|
|
/* 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);
|
|
|
|
|
|
2021-09-15 22:13:28 +02:00
|
|
|
|
/* 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);
|
|
|
|
|
|
2011-12-08 18:35:19 +01:00
|
|
|
|
/* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-24 04:39:17 +02:00
|
|
|
|
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);
|
2021-09-15 22:02:09 +02:00
|
|
|
|
|
gobject: drop clearing quark_weak_locations from g_object_real_dispose()
In g_object_unref(), we call _object_unref_clear_weak_locations() before
and after dispose() already. At those places it is necessary to do.
Calling it a third time during g_object_real_dispose() seems not useful,
has unnecessary overhead and actually undesirable.
In particular, because g_object_real_dispose() is the implementation for
the virtual function GObject.dispose(). For subclasses that override dispose(),
it's not well defined at which point they should chain up the parent
implementation (for dispose(), I'd argue that usually they chain up at
the end of their own code). If they chain up at the end, this has no
effect.
This only really matters if you try to register GWeakRef during dipose
and/or resurrect the object.
static void dispose(GObject *object)
{
g_weak_ref_set(&global_weak_ref, object);
global_ref = g_object_ref(object);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
the object was resurrected, but g_object_real_dispose() would clear the
weak ref. That is not desirable, nor does it make sense.
Instead, the virtual function dispose() is called from two places, from
g_object_unref() and g_object_run_dispose(). In both cases, it is
ensured that weak locations are cleared *after* dispatching the virtual
function. Don't do it somewhere in the middle from
g_object_real_dispose().
2023-12-21 21:34:36 +01:00
|
|
|
|
g_assert_true (object == g_weak_ref_get (weak_reffed->weak_ref));
|
|
|
|
|
g_object_unref (object);
|
2021-04-24 04:39:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-15 22:02:09 +02:00
|
|
|
|
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));
|
|
|
|
|
|
gobject: drop clearing quark_weak_locations from g_object_real_dispose()
In g_object_unref(), we call _object_unref_clear_weak_locations() before
and after dispose() already. At those places it is necessary to do.
Calling it a third time during g_object_real_dispose() seems not useful,
has unnecessary overhead and actually undesirable.
In particular, because g_object_real_dispose() is the implementation for
the virtual function GObject.dispose(). For subclasses that override dispose(),
it's not well defined at which point they should chain up the parent
implementation (for dispose(), I'd argue that usually they chain up at
the end of their own code). If they chain up at the end, this has no
effect.
This only really matters if you try to register GWeakRef during dipose
and/or resurrect the object.
static void dispose(GObject *object)
{
g_weak_ref_set(&global_weak_ref, object);
global_ref = g_object_ref(object);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
the object was resurrected, but g_object_real_dispose() would clear the
weak ref. That is not desirable, nor does it make sense.
Instead, the virtual function dispose() is called from two places, from
g_object_unref() and g_object_run_dispose(). In both cases, it is
ensured that weak locations are cleared *after* dispatching the virtual
function. Don't do it somewhere in the middle from
g_object_real_dispose().
2023-12-21 21:34:36 +01:00
|
|
|
|
g_weak_ref_set (&weak, obj);
|
|
|
|
|
|
2021-09-15 22:02:09 +02:00
|
|
|
|
g_clear_object (&obj);
|
|
|
|
|
g_assert_null (g_weak_ref_get (&weak));
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-24 04:39:17 +02:00
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-23 19:55:27 +01:00
|
|
|
|
static void
|
|
|
|
|
weak_ref_in_toggle_notify_toggle_cb (gpointer data,
|
|
|
|
|
GObject *object,
|
|
|
|
|
gboolean is_last_ref)
|
|
|
|
|
{
|
|
|
|
|
GWeakRef weak2;
|
|
|
|
|
GObject *obj2;
|
|
|
|
|
|
|
|
|
|
if (is_last_ref)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/* We just got a second ref, while calling g_weak_ref_get().
|
|
|
|
|
*
|
2023-12-23 20:06:03 +01:00
|
|
|
|
* Test that taking another weak ref in this situation works.
|
2023-12-23 19:55:27 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
g_weak_ref_init (&weak2, object);
|
|
|
|
|
g_assert_true (object == g_weak_ref_get (&weak2));
|
|
|
|
|
g_object_unref (object);
|
|
|
|
|
|
|
|
|
|
obj2 = g_object_new (G_TYPE_OBJECT, NULL);
|
|
|
|
|
g_weak_ref_set (&weak2, obj2);
|
|
|
|
|
g_object_unref (obj2);
|
|
|
|
|
|
|
|
|
|
g_assert_null (g_weak_ref_get (&weak2));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_weak_ref_in_toggle_notify (void)
|
|
|
|
|
{
|
|
|
|
|
GObject *obj;
|
|
|
|
|
GWeakRef weak = { { GUINT_TO_POINTER (0xDEADBEEFU) } };
|
|
|
|
|
|
|
|
|
|
obj = g_object_new (G_TYPE_OBJECT, NULL);
|
|
|
|
|
g_object_add_toggle_ref (obj, weak_ref_in_toggle_notify_toggle_cb, NULL);
|
|
|
|
|
g_object_unref (obj);
|
|
|
|
|
|
|
|
|
|
g_weak_ref_init (&weak, obj);
|
|
|
|
|
|
|
|
|
|
/* We trigger a toggle notify via g_weak_ref_get(). */
|
|
|
|
|
g_assert_true (g_weak_ref_get (&weak) == obj);
|
|
|
|
|
|
|
|
|
|
g_object_remove_toggle_ref (obj, weak_ref_in_toggle_notify_toggle_cb, NULL);
|
|
|
|
|
g_object_unref (obj);
|
|
|
|
|
|
|
|
|
|
g_assert_null (g_weak_ref_get (&weak));
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-01 13:42:09 +01:00
|
|
|
|
static void
|
|
|
|
|
test_weak_ref_many (void)
|
|
|
|
|
{
|
|
|
|
|
const guint N = g_test_slow ()
|
|
|
|
|
? G_MAXUINT16
|
|
|
|
|
: 211;
|
|
|
|
|
const guint PRIME = 1048583;
|
|
|
|
|
GObject *obj;
|
|
|
|
|
GWeakRef *weak_refs;
|
|
|
|
|
GWeakRef weak_ref1;
|
|
|
|
|
guint j;
|
|
|
|
|
guint n;
|
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
|
|
obj = g_object_new (G_TYPE_OBJECT, NULL);
|
|
|
|
|
|
|
|
|
|
weak_refs = g_new (GWeakRef, N);
|
|
|
|
|
|
|
|
|
|
/* We register them in a somewhat juggled order. That's because below, we will clear them
|
|
|
|
|
* again, and we don't want to always clear them in the same order as they were registered.
|
|
|
|
|
* For that, we calculate the actual index by jumping around by adding a prime number. */
|
|
|
|
|
j = (g_test_rand_int () % (N + 1));
|
|
|
|
|
for (i = 0; i < N; i++)
|
|
|
|
|
{
|
|
|
|
|
j = (j + PRIME) % N;
|
|
|
|
|
g_weak_ref_init (&weak_refs[j], obj);
|
|
|
|
|
}
|
|
|
|
|
|
gobject: track GWeakRef in object's WeakRefData with an array
GSList doesn't seem the best choice here. It's benefits are that it's
relatively convenient to use (albeit not very efficient) and that an
empty list requires only the pointer to the list's head.
But for non-empty list, we need to allocate GSList elements. We can do
better, by writing more code.
I think it's worth optimizing GObject, at the expense of a bit(?) more
complicated code. The complicated code is still entirely self-contained,
so unless you review WeakRefData usage, it doesn't need to bother you.
Note that this can be easily measure to be a bit faster. But I think the
more important part is to safe some allocations. Often objects are
long-lived, and the GWeakRef will be tracked for a long time. It is
interesting, to optimize the memory usage of that.
- if the list only contains one weak reference, it's interned/embedded in
WeakRefData.list.one. Otherwise, an array is allocated and tracked
at WeakRefData.list.many.
- when the buffer grows, we double the size. When the buffer shrinks,
we reallocate to 50% when 75% are empty. When the buffer shrinks to
length 1, we free it (so that "list.one" is always used with a length
of 1).
That means, at worst case we waste 75% of the allocated buffer,
which is a choice in the hope that future weak references will be
registered, and that this is a suitable strategy.
- on architectures like x86_68, does this not increase the size of
WeakRefData.
Also, the number of weak-refs is now limited to 65535, and now an
assertion fails when you try to register more than that. But note that
the internal tracking just uses a linear search, so you really don't
want to register thousands of weak references on an object. If you do
that, the current implementation is not suitable anyway and you must
rethink your approach. Nor does it make sense to optimize the
implementation for such a use case. Instead, the implementation is
optimized for a few (one!) weak reference per object.
2024-02-01 12:47:53 +01:00
|
|
|
|
if (N == G_MAXUINT16)
|
|
|
|
|
{
|
|
|
|
|
g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_CRITICAL, "*Too many GWeakRef registered");
|
|
|
|
|
g_weak_ref_init (&weak_ref1, obj);
|
|
|
|
|
g_test_assert_expected_messages ();
|
|
|
|
|
g_assert_null (g_weak_ref_get (&weak_ref1));
|
|
|
|
|
}
|
2024-02-01 13:42:09 +01:00
|
|
|
|
|
|
|
|
|
n = g_test_rand_int () % (N + 1u);
|
|
|
|
|
for (i = 0; i < N; i++)
|
|
|
|
|
g_weak_ref_set (&weak_refs[i], i < n ? NULL : obj);
|
|
|
|
|
|
|
|
|
|
g_object_unref (obj);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < N; i++)
|
|
|
|
|
g_assert_null (g_weak_ref_get (&weak_refs[i]));
|
|
|
|
|
|
|
|
|
|
/* The API would expect us to also call g_weak_ref_clear() on all references
|
|
|
|
|
* to clean up. In practice, they are already all NULL, so we don't need
|
|
|
|
|
* that (it would have no effect, with the current implementation of
|
|
|
|
|
* GWeakRef). */
|
|
|
|
|
|
|
|
|
|
g_free (weak_refs);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-24 07:58:51 +01:00
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#define CONCURRENT_N_OBJS 5
|
|
|
|
|
#define CONCURRENT_N_THREADS 5
|
|
|
|
|
#define CONCURRENT_N_RACES 100
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
int TEST_IDX;
|
|
|
|
|
GObject *objs[CONCURRENT_N_OBJS];
|
|
|
|
|
int thread_done[CONCURRENT_N_THREADS];
|
|
|
|
|
} ConcurrentData;
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
const ConcurrentData *data;
|
|
|
|
|
int idx;
|
|
|
|
|
int race_count;
|
|
|
|
|
GWeakRef *weak_ref;
|
|
|
|
|
GRand *rnd;
|
|
|
|
|
} ConcurrentThreadData;
|
|
|
|
|
|
|
|
|
|
static gpointer
|
|
|
|
|
_test_weak_ref_concurrent_thread_cb (gpointer data)
|
|
|
|
|
{
|
|
|
|
|
ConcurrentThreadData *thread_data = data;
|
|
|
|
|
|
|
|
|
|
while (TRUE)
|
|
|
|
|
{
|
|
|
|
|
gboolean all_done;
|
|
|
|
|
int i;
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
for (r = 0; r < 15; r++)
|
|
|
|
|
{
|
|
|
|
|
GObject *obj_allocated = NULL;
|
|
|
|
|
GObject *obj;
|
|
|
|
|
GObject *obj2;
|
|
|
|
|
gboolean got_race;
|
|
|
|
|
|
|
|
|
|
/* Choose a random object */
|
|
|
|
|
obj = thread_data->data->objs[g_rand_int (thread_data->rnd) % CONCURRENT_N_OBJS];
|
|
|
|
|
if (thread_data->data->TEST_IDX > 0 && (g_rand_int (thread_data->rnd) % 4 == 0))
|
|
|
|
|
{
|
|
|
|
|
/* With TEST_IDX>0 also randomly choose NULL or a newly created
|
|
|
|
|
* object. */
|
|
|
|
|
if (g_rand_boolean (thread_data->rnd))
|
|
|
|
|
obj = NULL;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
obj_allocated = g_object_new (G_TYPE_OBJECT, NULL);
|
|
|
|
|
obj = obj_allocated;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_assert (!obj || G_IS_OBJECT (obj));
|
|
|
|
|
|
|
|
|
|
g_weak_ref_set (thread_data->weak_ref, obj);
|
|
|
|
|
|
|
|
|
|
/* get the weak-ref. If there is no race, we expect to get the same
|
|
|
|
|
* object back. */
|
|
|
|
|
obj2 = g_weak_ref_get (thread_data->weak_ref);
|
|
|
|
|
|
|
|
|
|
g_assert (!obj2 || G_IS_OBJECT (obj2));
|
|
|
|
|
if (!obj2)
|
|
|
|
|
{
|
|
|
|
|
g_assert (thread_data->data->TEST_IDX > 0);
|
|
|
|
|
}
|
|
|
|
|
if (obj != obj2)
|
|
|
|
|
{
|
|
|
|
|
int cnt;
|
|
|
|
|
|
|
|
|
|
cnt = 0;
|
|
|
|
|
for (i = 0; i < CONCURRENT_N_OBJS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (obj2 == thread_data->data->objs[i])
|
|
|
|
|
cnt++;
|
|
|
|
|
}
|
|
|
|
|
if (!obj2)
|
|
|
|
|
g_assert_cmpint (cnt, ==, 0);
|
|
|
|
|
else if (obj2 && obj2 == obj_allocated)
|
|
|
|
|
g_assert_cmpint (cnt, ==, 0);
|
|
|
|
|
else if (thread_data->data->TEST_IDX > 0)
|
|
|
|
|
g_assert_cmpint (cnt, <=, 1);
|
|
|
|
|
else
|
|
|
|
|
g_assert_cmpint (cnt, ==, 1);
|
|
|
|
|
got_race = TRUE;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
got_race = FALSE;
|
|
|
|
|
|
|
|
|
|
g_clear_object (&obj2);
|
|
|
|
|
g_clear_object (&obj_allocated);
|
|
|
|
|
|
|
|
|
|
if (got_race)
|
|
|
|
|
{
|
|
|
|
|
/* Each thread should see CONCURRENT_N_RACES before being done.
|
|
|
|
|
* Count them. */
|
|
|
|
|
if (g_atomic_int_get (&thread_data->race_count) > CONCURRENT_N_RACES)
|
|
|
|
|
g_atomic_int_set (&thread_data->data->thread_done[thread_data->idx], 1);
|
|
|
|
|
else
|
|
|
|
|
g_atomic_int_add (&thread_data->race_count, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Each thread runs, until all threads saw the expected number of races. */
|
|
|
|
|
all_done = TRUE;
|
|
|
|
|
for (i = 0; i < CONCURRENT_N_THREADS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (!g_atomic_int_get (&thread_data->data->thread_done[i]))
|
|
|
|
|
{
|
|
|
|
|
all_done = FALSE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (all_done)
|
|
|
|
|
return GINT_TO_POINTER (1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_weak_ref_concurrent (gconstpointer testdata)
|
|
|
|
|
{
|
|
|
|
|
const int TEST_IDX = GPOINTER_TO_INT (testdata);
|
|
|
|
|
GThread *threads[CONCURRENT_N_THREADS];
|
|
|
|
|
int i;
|
|
|
|
|
ConcurrentData data = {
|
|
|
|
|
.TEST_IDX = TEST_IDX,
|
|
|
|
|
};
|
|
|
|
|
ConcurrentThreadData thread_data[CONCURRENT_N_THREADS];
|
|
|
|
|
GWeakRef weak_ref = { 0 };
|
|
|
|
|
|
|
|
|
|
/* Let several threads call g_weak_ref_set() & g_weak_ref_get() in a loop. */
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < CONCURRENT_N_OBJS; i++)
|
|
|
|
|
data.objs[i] = g_object_new (G_TYPE_OBJECT, NULL);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < CONCURRENT_N_THREADS; i++)
|
|
|
|
|
{
|
|
|
|
|
const guint32 rnd_seed[] = {
|
|
|
|
|
g_test_rand_int (),
|
|
|
|
|
g_test_rand_int (),
|
|
|
|
|
g_test_rand_int (),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
thread_data[i] = (ConcurrentThreadData){
|
|
|
|
|
.idx = i,
|
|
|
|
|
.data = &data,
|
|
|
|
|
.weak_ref = &weak_ref,
|
|
|
|
|
.rnd = g_rand_new_with_seed_array (rnd_seed, G_N_ELEMENTS (rnd_seed)),
|
|
|
|
|
};
|
|
|
|
|
threads[i] = g_thread_new ("test-weak-ref-concurrent", _test_weak_ref_concurrent_thread_cb, &thread_data[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < CONCURRENT_N_THREADS; i++)
|
|
|
|
|
{
|
|
|
|
|
gpointer r;
|
|
|
|
|
|
|
|
|
|
r = g_thread_join (g_steal_pointer (&threads[i]));
|
|
|
|
|
g_assert_cmpint (GPOINTER_TO_INT (r), ==, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < CONCURRENT_N_OBJS; i++)
|
|
|
|
|
g_object_unref (g_steal_pointer (&data.objs[i]));
|
|
|
|
|
for (i = 0; i < CONCURRENT_N_THREADS; i++)
|
|
|
|
|
g_rand_free (g_steal_pointer (&thread_data[i].rnd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
2012-04-23 14:19:06 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2022-12-06 04:27:53 +01:00
|
|
|
|
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);
|
|
|
|
|
|
2012-04-23 14:19:06 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-29 23:12:17 +01:00
|
|
|
|
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;
|
2022-12-06 03:51:09 +01:00
|
|
|
|
gboolean disposing_refs_all_normal;
|
2022-11-29 23:12:17 +01:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2022-12-06 03:51:09 +01:00
|
|
|
|
if (i == 0 && !obj->disposing_refs_all_normal)
|
2022-11-29 23:12:17 +01:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-06 03:51:09 +01:00
|
|
|
|
static void
|
|
|
|
|
toggle_notify_counter (gpointer data,
|
|
|
|
|
GObject *obj,
|
|
|
|
|
gboolean is_last)
|
|
|
|
|
{
|
|
|
|
|
Count *c = data;
|
|
|
|
|
c->count++;
|
2022-12-06 04:27:53 +01:00
|
|
|
|
|
|
|
|
|
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);
|
2022-12-06 03:51:09 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-06 04:27:53 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-06 03:51:09 +01:00
|
|
|
|
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);
|
gobject: fix race in g_object_unref() related to toggle references
The previous g_object_unref() was racy. There were three places where we
decremented the ref count, but still accessed the object afterwards
(while assuming that somebody else might still hold a reference). For
example:
if (!g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
old_ref, old_ref - 1,
&old_ref))
continue;
TRACE (GOBJECT_OBJECT_UNREF (object, G_TYPE_FROM_INSTANCE (object), old_ref));
/* if we went from 2->1 we need to notify toggle refs if any */
if (old_ref == 2 && OBJECT_HAS_TOGGLE_REF (object))
{
/* The last ref being held in this case is owned by the toggle_ref */
toggle_refs_notify (object, TRUE);
}
After we decrement the reference count (and gave up our reference), we
are only allowed to access object if we know we have the only possible
reference to it. In particular, if old_ref is larger than 1, then
somebody else holds references and races against destroying object.
The object might be a dangling pointer already.
This is slightly complicated due to toggle references and clearing of
weak-locations.
For toggle references, we must take a lock on the mutex. Luckily, that
is only necessary, when the current reference count is exactly 2.
Note that we emit the TRACE() after the ref count was already decreased.
If another thread unrefs the object, inside the TRACE() we might have a
dangling pointer. That would only be fixable, by emitting the TRACE()
before the actual unref (which has its own problems). This problem
already existed previously.
The change to the test is necessary and correct. Before this patch,
g_object_unref() would call dispose() and decrement the reference count
right after.
In the test case at gobject/tests/reference.c:1108, the reference count
after dispose and decrement is 1. Then it thaws the queue notification,
which emits a property changed signal. The test then proceeds to
reference the object again and notifying the toggle reference.
Previously, the toggle reference was notified 3 times.
After this change, the property changed signal is emitted before
decreasing the reference count. Taking a reference then does not cause
an additional toggle on+off, so in total only one toggle happens.
That accounts for the change in the test. The new behavior is
correct.
2023-12-19 09:30:29 +01:00
|
|
|
|
g_assert_cmpint (obj->actual.count, ==, 2);
|
2022-12-06 03:51:09 +01:00
|
|
|
|
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;
|
gobject: fix race in g_object_unref() related to toggle references
The previous g_object_unref() was racy. There were three places where we
decremented the ref count, but still accessed the object afterwards
(while assuming that somebody else might still hold a reference). For
example:
if (!g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
old_ref, old_ref - 1,
&old_ref))
continue;
TRACE (GOBJECT_OBJECT_UNREF (object, G_TYPE_FROM_INSTANCE (object), old_ref));
/* if we went from 2->1 we need to notify toggle refs if any */
if (old_ref == 2 && OBJECT_HAS_TOGGLE_REF (object))
{
/* The last ref being held in this case is owned by the toggle_ref */
toggle_refs_notify (object, TRUE);
}
After we decrement the reference count (and gave up our reference), we
are only allowed to access object if we know we have the only possible
reference to it. In particular, if old_ref is larger than 1, then
somebody else holds references and races against destroying object.
The object might be a dangling pointer already.
This is slightly complicated due to toggle references and clearing of
weak-locations.
For toggle references, we must take a lock on the mutex. Luckily, that
is only necessary, when the current reference count is exactly 2.
Note that we emit the TRACE() after the ref count was already decreased.
If another thread unrefs the object, inside the TRACE() we might have a
dangling pointer. That would only be fixable, by emitting the TRACE()
before the actual unref (which has its own problems). This problem
already existed previously.
The change to the test is necessary and correct. Before this patch,
g_object_unref() would call dispose() and decrement the reference count
right after.
In the test case at gobject/tests/reference.c:1108, the reference count
after dispose and decrement is 1. Then it thaws the queue notification,
which emits a property changed signal. The test then proceeds to
reference the object again and notifying the toggle reference.
Previously, the toggle reference was notified 3 times.
After this change, the property changed signal is emitted before
decreasing the reference count. Taking a reference then does not cause
an additional toggle on+off, so in total only one toggle happens.
That accounts for the change in the test. The new behavior is
correct.
2023-12-19 09:30:29 +01:00
|
|
|
|
obj->expected.count = 2;
|
2022-12-06 03:51:09 +01:00
|
|
|
|
obj->notify_handler = G_CALLBACK (on_object_notify_switch_to_normal_ref);
|
|
|
|
|
g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
|
gobject: fix race in g_object_unref() related to toggle references
The previous g_object_unref() was racy. There were three places where we
decremented the ref count, but still accessed the object afterwards
(while assuming that somebody else might still hold a reference). For
example:
if (!g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
old_ref, old_ref - 1,
&old_ref))
continue;
TRACE (GOBJECT_OBJECT_UNREF (object, G_TYPE_FROM_INSTANCE (object), old_ref));
/* if we went from 2->1 we need to notify toggle refs if any */
if (old_ref == 2 && OBJECT_HAS_TOGGLE_REF (object))
{
/* The last ref being held in this case is owned by the toggle_ref */
toggle_refs_notify (object, TRUE);
}
After we decrement the reference count (and gave up our reference), we
are only allowed to access object if we know we have the only possible
reference to it. In particular, if old_ref is larger than 1, then
somebody else holds references and races against destroying object.
The object might be a dangling pointer already.
This is slightly complicated due to toggle references and clearing of
weak-locations.
For toggle references, we must take a lock on the mutex. Luckily, that
is only necessary, when the current reference count is exactly 2.
Note that we emit the TRACE() after the ref count was already decreased.
If another thread unrefs the object, inside the TRACE() we might have a
dangling pointer. That would only be fixable, by emitting the TRACE()
before the actual unref (which has its own problems). This problem
already existed previously.
The change to the test is necessary and correct. Before this patch,
g_object_unref() would call dispose() and decrement the reference count
right after.
In the test case at gobject/tests/reference.c:1108, the reference count
after dispose and decrement is 1. Then it thaws the queue notification,
which emits a property changed signal. The test then proceeds to
reference the object again and notifying the toggle reference.
Previously, the toggle reference was notified 3 times.
After this change, the property changed signal is emitted before
decreasing the reference count. Taking a reference then does not cause
an additional toggle on+off, so in total only one toggle happens.
That accounts for the change in the test. The new behavior is
correct.
2023-12-19 09:30:29 +01:00
|
|
|
|
g_assert_cmpint (obj->actual.count, ==, 2);
|
2022-12-06 03:51:09 +01:00
|
|
|
|
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;
|
gobject: fix race in g_object_unref() related to toggle references
The previous g_object_unref() was racy. There were three places where we
decremented the ref count, but still accessed the object afterwards
(while assuming that somebody else might still hold a reference). For
example:
if (!g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
old_ref, old_ref - 1,
&old_ref))
continue;
TRACE (GOBJECT_OBJECT_UNREF (object, G_TYPE_FROM_INSTANCE (object), old_ref));
/* if we went from 2->1 we need to notify toggle refs if any */
if (old_ref == 2 && OBJECT_HAS_TOGGLE_REF (object))
{
/* The last ref being held in this case is owned by the toggle_ref */
toggle_refs_notify (object, TRUE);
}
After we decrement the reference count (and gave up our reference), we
are only allowed to access object if we know we have the only possible
reference to it. In particular, if old_ref is larger than 1, then
somebody else holds references and races against destroying object.
The object might be a dangling pointer already.
This is slightly complicated due to toggle references and clearing of
weak-locations.
For toggle references, we must take a lock on the mutex. Luckily, that
is only necessary, when the current reference count is exactly 2.
Note that we emit the TRACE() after the ref count was already decreased.
If another thread unrefs the object, inside the TRACE() we might have a
dangling pointer. That would only be fixable, by emitting the TRACE()
before the actual unref (which has its own problems). This problem
already existed previously.
The change to the test is necessary and correct. Before this patch,
g_object_unref() would call dispose() and decrement the reference count
right after.
In the test case at gobject/tests/reference.c:1108, the reference count
after dispose and decrement is 1. Then it thaws the queue notification,
which emits a property changed signal. The test then proceeds to
reference the object again and notifying the toggle reference.
Previously, the toggle reference was notified 3 times.
After this change, the property changed signal is emitted before
decreasing the reference count. Taking a reference then does not cause
an additional toggle on+off, so in total only one toggle happens.
That accounts for the change in the test. The new behavior is
correct.
2023-12-19 09:30:29 +01:00
|
|
|
|
obj->expected.count = 2;
|
2022-12-06 03:51:09 +01:00
|
|
|
|
obj->notify_handler = G_CALLBACK (on_object_notify_switch_to_toggle_ref);
|
|
|
|
|
g_object_unref (obj);
|
gobject: fix race in g_object_unref() related to toggle references
The previous g_object_unref() was racy. There were three places where we
decremented the ref count, but still accessed the object afterwards
(while assuming that somebody else might still hold a reference). For
example:
if (!g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
old_ref, old_ref - 1,
&old_ref))
continue;
TRACE (GOBJECT_OBJECT_UNREF (object, G_TYPE_FROM_INSTANCE (object), old_ref));
/* if we went from 2->1 we need to notify toggle refs if any */
if (old_ref == 2 && OBJECT_HAS_TOGGLE_REF (object))
{
/* The last ref being held in this case is owned by the toggle_ref */
toggle_refs_notify (object, TRUE);
}
After we decrement the reference count (and gave up our reference), we
are only allowed to access object if we know we have the only possible
reference to it. In particular, if old_ref is larger than 1, then
somebody else holds references and races against destroying object.
The object might be a dangling pointer already.
This is slightly complicated due to toggle references and clearing of
weak-locations.
For toggle references, we must take a lock on the mutex. Luckily, that
is only necessary, when the current reference count is exactly 2.
Note that we emit the TRACE() after the ref count was already decreased.
If another thread unrefs the object, inside the TRACE() we might have a
dangling pointer. That would only be fixable, by emitting the TRACE()
before the actual unref (which has its own problems). This problem
already existed previously.
The change to the test is necessary and correct. Before this patch,
g_object_unref() would call dispose() and decrement the reference count
right after.
In the test case at gobject/tests/reference.c:1108, the reference count
after dispose and decrement is 1. Then it thaws the queue notification,
which emits a property changed signal. The test then proceeds to
reference the object again and notifying the toggle reference.
Previously, the toggle reference was notified 3 times.
After this change, the property changed signal is emitted before
decreasing the reference count. Taking a reference then does not cause
an additional toggle on+off, so in total only one toggle happens.
That accounts for the change in the test. The new behavior is
correct.
2023-12-19 09:30:29 +01:00
|
|
|
|
g_assert_cmpint (obj->actual.count, ==, 3);
|
2022-12-06 03:51:09 +01:00
|
|
|
|
g_assert_cmpuint (obj->notify_called, ==, 3);
|
|
|
|
|
|
|
|
|
|
disposed_checker = &obj;
|
|
|
|
|
g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);
|
|
|
|
|
|
2022-12-06 04:27:53 +01:00
|
|
|
|
/* 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;
|
gobject: fix race in g_object_unref() related to toggle references
The previous g_object_unref() was racy. There were three places where we
decremented the ref count, but still accessed the object afterwards
(while assuming that somebody else might still hold a reference). For
example:
if (!g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
old_ref, old_ref - 1,
&old_ref))
continue;
TRACE (GOBJECT_OBJECT_UNREF (object, G_TYPE_FROM_INSTANCE (object), old_ref));
/* if we went from 2->1 we need to notify toggle refs if any */
if (old_ref == 2 && OBJECT_HAS_TOGGLE_REF (object))
{
/* The last ref being held in this case is owned by the toggle_ref */
toggle_refs_notify (object, TRUE);
}
After we decrement the reference count (and gave up our reference), we
are only allowed to access object if we know we have the only possible
reference to it. In particular, if old_ref is larger than 1, then
somebody else holds references and races against destroying object.
The object might be a dangling pointer already.
This is slightly complicated due to toggle references and clearing of
weak-locations.
For toggle references, we must take a lock on the mutex. Luckily, that
is only necessary, when the current reference count is exactly 2.
Note that we emit the TRACE() after the ref count was already decreased.
If another thread unrefs the object, inside the TRACE() we might have a
dangling pointer. That would only be fixable, by emitting the TRACE()
before the actual unref (which has its own problems). This problem
already existed previously.
The change to the test is necessary and correct. Before this patch,
g_object_unref() would call dispose() and decrement the reference count
right after.
In the test case at gobject/tests/reference.c:1108, the reference count
after dispose and decrement is 1. Then it thaws the queue notification,
which emits a property changed signal. The test then proceeds to
reference the object again and notifying the toggle reference.
Previously, the toggle reference was notified 3 times.
After this change, the property changed signal is emitted before
decreasing the reference count. Taking a reference then does not cause
an additional toggle on+off, so in total only one toggle happens.
That accounts for the change in the test. The new behavior is
correct.
2023-12-19 09:30:29 +01:00
|
|
|
|
obj->expected.count = 3;
|
2022-12-06 04:27:53 +01:00
|
|
|
|
obj->notify_handler = G_CALLBACK (on_object_notify_add_ref);
|
|
|
|
|
g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
|
gobject: fix race in g_object_unref() related to toggle references
The previous g_object_unref() was racy. There were three places where we
decremented the ref count, but still accessed the object afterwards
(while assuming that somebody else might still hold a reference). For
example:
if (!g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
old_ref, old_ref - 1,
&old_ref))
continue;
TRACE (GOBJECT_OBJECT_UNREF (object, G_TYPE_FROM_INSTANCE (object), old_ref));
/* if we went from 2->1 we need to notify toggle refs if any */
if (old_ref == 2 && OBJECT_HAS_TOGGLE_REF (object))
{
/* The last ref being held in this case is owned by the toggle_ref */
toggle_refs_notify (object, TRUE);
}
After we decrement the reference count (and gave up our reference), we
are only allowed to access object if we know we have the only possible
reference to it. In particular, if old_ref is larger than 1, then
somebody else holds references and races against destroying object.
The object might be a dangling pointer already.
This is slightly complicated due to toggle references and clearing of
weak-locations.
For toggle references, we must take a lock on the mutex. Luckily, that
is only necessary, when the current reference count is exactly 2.
Note that we emit the TRACE() after the ref count was already decreased.
If another thread unrefs the object, inside the TRACE() we might have a
dangling pointer. That would only be fixable, by emitting the TRACE()
before the actual unref (which has its own problems). This problem
already existed previously.
The change to the test is necessary and correct. Before this patch,
g_object_unref() would call dispose() and decrement the reference count
right after.
In the test case at gobject/tests/reference.c:1108, the reference count
after dispose and decrement is 1. Then it thaws the queue notification,
which emits a property changed signal. The test then proceeds to
reference the object again and notifying the toggle reference.
Previously, the toggle reference was notified 3 times.
After this change, the property changed signal is emitted before
decreasing the reference count. Taking a reference then does not cause
an additional toggle on+off, so in total only one toggle happens.
That accounts for the change in the test. The new behavior is
correct.
2023-12-19 09:30:29 +01:00
|
|
|
|
g_assert_cmpint (obj->actual.count, ==, 3);
|
2022-12-06 04:27:53 +01:00
|
|
|
|
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;
|
gobject: fix race in g_object_unref() related to toggle references
The previous g_object_unref() was racy. There were three places where we
decremented the ref count, but still accessed the object afterwards
(while assuming that somebody else might still hold a reference). For
example:
if (!g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
old_ref, old_ref - 1,
&old_ref))
continue;
TRACE (GOBJECT_OBJECT_UNREF (object, G_TYPE_FROM_INSTANCE (object), old_ref));
/* if we went from 2->1 we need to notify toggle refs if any */
if (old_ref == 2 && OBJECT_HAS_TOGGLE_REF (object))
{
/* The last ref being held in this case is owned by the toggle_ref */
toggle_refs_notify (object, TRUE);
}
After we decrement the reference count (and gave up our reference), we
are only allowed to access object if we know we have the only possible
reference to it. In particular, if old_ref is larger than 1, then
somebody else holds references and races against destroying object.
The object might be a dangling pointer already.
This is slightly complicated due to toggle references and clearing of
weak-locations.
For toggle references, we must take a lock on the mutex. Luckily, that
is only necessary, when the current reference count is exactly 2.
Note that we emit the TRACE() after the ref count was already decreased.
If another thread unrefs the object, inside the TRACE() we might have a
dangling pointer. That would only be fixable, by emitting the TRACE()
before the actual unref (which has its own problems). This problem
already existed previously.
The change to the test is necessary and correct. Before this patch,
g_object_unref() would call dispose() and decrement the reference count
right after.
In the test case at gobject/tests/reference.c:1108, the reference count
after dispose and decrement is 1. Then it thaws the queue notification,
which emits a property changed signal. The test then proceeds to
reference the object again and notifying the toggle reference.
Previously, the toggle reference was notified 3 times.
After this change, the property changed signal is emitted before
decreasing the reference count. Taking a reference then does not cause
an additional toggle on+off, so in total only one toggle happens.
That accounts for the change in the test. The new behavior is
correct.
2023-12-19 09:30:29 +01:00
|
|
|
|
obj->expected.count = 4;
|
2022-12-06 03:51:09 +01:00
|
|
|
|
g_clear_object (&obj);
|
|
|
|
|
g_assert_null (disposed_checker);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-19 18:20:37 +01:00
|
|
|
|
static gboolean global_destroyed;
|
|
|
|
|
static gint global_value;
|
2012-04-23 14:19:06 +02:00
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
data_destroy (gpointer data)
|
|
|
|
|
{
|
2022-01-19 18:20:37 +01:00
|
|
|
|
g_assert_cmpint (GPOINTER_TO_INT (data), ==, global_value);
|
2012-04-23 14:19:06 +02:00
|
|
|
|
|
2022-01-19 18:20:37 +01:00
|
|
|
|
global_destroyed = TRUE;
|
2012-04-23 14:19:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_object_qdata (void)
|
|
|
|
|
{
|
|
|
|
|
GObject *obj;
|
|
|
|
|
gpointer v;
|
|
|
|
|
GQuark quark;
|
|
|
|
|
|
|
|
|
|
obj = g_object_new (G_TYPE_OBJECT, NULL);
|
|
|
|
|
|
2022-01-19 18:20:37 +01:00
|
|
|
|
global_value = 1;
|
|
|
|
|
global_destroyed = FALSE;
|
2012-04-23 14:19:06 +02:00
|
|
|
|
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);
|
2022-01-19 18:20:37 +01:00
|
|
|
|
g_assert (global_destroyed);
|
|
|
|
|
global_value = 2;
|
|
|
|
|
global_destroyed = FALSE;
|
2012-04-23 14:19:06 +02:00
|
|
|
|
v = g_object_steal_data (obj, "test");
|
|
|
|
|
g_assert_cmpint (GPOINTER_TO_INT (v), ==, 2);
|
2022-01-19 18:20:37 +01:00
|
|
|
|
g_assert (!global_destroyed);
|
2012-04-23 14:19:06 +02:00
|
|
|
|
|
2022-01-19 18:20:37 +01:00
|
|
|
|
global_value = 1;
|
|
|
|
|
global_destroyed = FALSE;
|
2012-04-23 14:19:06 +02:00
|
|
|
|
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);
|
2022-01-19 18:20:37 +01:00
|
|
|
|
g_assert (global_destroyed);
|
|
|
|
|
global_value = 2;
|
|
|
|
|
global_destroyed = FALSE;
|
2012-04-23 14:19:06 +02:00
|
|
|
|
v = g_object_steal_qdata (obj, quark);
|
|
|
|
|
g_assert_cmpint (GPOINTER_TO_INT (v), ==, 2);
|
2022-01-19 18:20:37 +01:00
|
|
|
|
g_assert (!global_destroyed);
|
2012-04-23 14:19:06 +02:00
|
|
|
|
|
|
|
|
|
g_object_set_qdata_full (obj, quark, GINT_TO_POINTER (3), data_destroy);
|
2022-01-19 18:20:37 +01:00
|
|
|
|
global_value = 3;
|
|
|
|
|
global_destroyed = FALSE;
|
2012-04-23 14:19:06 +02:00
|
|
|
|
g_object_unref (obj);
|
|
|
|
|
|
2022-01-19 18:20:37 +01:00
|
|
|
|
g_assert (global_destroyed);
|
2012-04-23 14:19:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2012-08-29 05:56:01 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-08 22:42:32 +01:00
|
|
|
|
int
|
|
|
|
|
main (int argc, char **argv)
|
|
|
|
|
{
|
|
|
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
|
|
2022-09-21 12:13:39 +02:00
|
|
|
|
g_setenv ("G_ENABLE_DIAGNOSTIC", "1", TRUE);
|
|
|
|
|
|
2011-02-14 05:47:42 +01:00
|
|
|
|
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);
|
2010-11-08 22:42:32 +01:00
|
|
|
|
g_test_add_func ("/object/clear", test_clear);
|
2011-02-14 05:47:42 +01:00
|
|
|
|
g_test_add_func ("/object/clear-function", test_clear_function);
|
2014-12-16 12:29:03 +01:00
|
|
|
|
g_test_add_func ("/object/set", test_set);
|
|
|
|
|
g_test_add_func ("/object/set-function", test_set_function);
|
2020-02-18 13:10:07 +01:00
|
|
|
|
g_test_add_func ("/object/set/derived-type", test_set_derived_type);
|
2011-02-14 05:47:42 +01:00
|
|
|
|
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);
|
2017-12-20 23:05:53 +01:00
|
|
|
|
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);
|
2011-12-08 18:35:19 +01:00
|
|
|
|
g_test_add_func ("/object/weak-ref", test_weak_ref);
|
2021-04-24 04:39:17 +02:00
|
|
|
|
g_test_add_func ("/object/weak-ref/on-dispose", test_weak_ref_on_dispose);
|
2021-09-15 22:02:09 +02:00
|
|
|
|
g_test_add_func ("/object/weak-ref/on-run-dispose", test_weak_ref_on_run_dispose);
|
2021-04-24 04:39:17 +02:00
|
|
|
|
g_test_add_func ("/object/weak-ref/on-toggle-notify", test_weak_ref_on_toggle_notify);
|
2023-12-23 19:55:27 +01:00
|
|
|
|
g_test_add_func ("/object/weak-ref/in-toggle-notify", test_weak_ref_in_toggle_notify);
|
2024-02-01 13:42:09 +01:00
|
|
|
|
g_test_add_func ("/object/weak-ref/many", test_weak_ref_many);
|
2023-12-24 07:58:51 +01:00
|
|
|
|
g_test_add_data_func ("/object/weak-ref/concurrent/0", GINT_TO_POINTER (0), test_weak_ref_concurrent);
|
|
|
|
|
g_test_add_data_func ("/object/weak-ref/concurrent/1", GINT_TO_POINTER (1), test_weak_ref_concurrent);
|
2012-04-23 14:19:06 +02:00
|
|
|
|
g_test_add_func ("/object/toggle-ref", test_toggle_ref);
|
2022-11-29 23:12:17 +01:00
|
|
|
|
g_test_add_func ("/object/toggle-ref/ref-on-dispose", test_toggle_ref_on_dispose);
|
2022-12-06 03:51:09 +01:00
|
|
|
|
g_test_add_func ("/object/toggle-ref/ref-and-notify-on-dispose", test_toggle_ref_and_notify_on_dispose);
|
2012-04-23 14:19:06 +02:00
|
|
|
|
g_test_add_func ("/object/qdata", test_object_qdata);
|
2012-08-29 05:56:01 +02:00
|
|
|
|
g_test_add_func ("/object/qdata2", test_object_qdata2);
|
2010-11-08 22:42:32 +01:00
|
|
|
|
|
|
|
|
|
return g_test_run ();
|
|
|
|
|
}
|