Merge branch 'gsignal-emission-locks-cleanups' into 'main'

gsignal: Reduce lock/unlock pairs and ensure we always work on valid signal nodes

See merge request GNOME/glib!2824
This commit is contained in:
Philip Withnall 2023-05-30 13:57:27 +00:00
commit fe458559d8
2 changed files with 490 additions and 102 deletions

View File

@ -2305,7 +2305,6 @@ g_signal_chain_from_overridden_handler (gpointer instance,
}
SIGNAL_UNLOCK ();
instance_and_params->g_type = 0;
g_value_init_from_instance (instance_and_params, instance);
SIGNAL_LOCK ();
@ -3172,6 +3171,12 @@ g_signal_has_handler_pending (gpointer instance,
return has_pending;
}
static void
signal_emitv_unlocked (const GValue *instance_and_params,
guint signal_id,
GQuark detail,
GValue *return_value);
/**
* g_signal_emitv:
* @instance_and_params: (array): argument list for the signal emission.
@ -3194,6 +3199,17 @@ g_signal_emitv (const GValue *instance_and_params,
guint signal_id,
GQuark detail,
GValue *return_value)
{
SIGNAL_LOCK ();
signal_emitv_unlocked (instance_and_params, signal_id, detail, return_value);
SIGNAL_UNLOCK ();
}
static void
signal_emitv_unlocked (const GValue *instance_and_params,
guint signal_id,
GQuark detail,
GValue *return_value)
{
gpointer instance;
SignalNode *node;
@ -3211,19 +3227,16 @@ g_signal_emitv (const GValue *instance_and_params,
param_values = instance_and_params + 1;
#endif
SIGNAL_LOCK ();
node = LOOKUP_SIGNAL_NODE (signal_id);
if (!node || !g_type_is_a (G_TYPE_FROM_INSTANCE (instance), node->itype))
{
g_critical ("%s: signal id '%u' is invalid for instance '%p'", G_STRLOC, signal_id, instance);
SIGNAL_UNLOCK ();
return;
}
#ifdef G_ENABLE_DEBUG
if (detail && !(node->flags & G_SIGNAL_DETAILED))
{
g_critical ("%s: signal id '%u' does not support detail (%u)", G_STRLOC, signal_id, detail);
SIGNAL_UNLOCK ();
return;
}
for (i = 0; i < node->n_params; i++)
@ -3235,7 +3248,6 @@ g_signal_emitv (const GValue *instance_and_params,
i,
node->name,
G_VALUE_TYPE_NAME (param_values + i));
SIGNAL_UNLOCK ();
return;
}
if (node->return_type != G_TYPE_NONE)
@ -3246,7 +3258,6 @@ g_signal_emitv (const GValue *instance_and_params,
G_STRLOC,
type_debug_name (node->return_type),
node->name);
SIGNAL_UNLOCK ();
return;
}
else if (!node->accumulator && !G_TYPE_CHECK_VALUE_TYPE (return_value, node->return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE))
@ -3256,7 +3267,6 @@ g_signal_emitv (const GValue *instance_and_params,
type_debug_name (node->return_type),
node->name,
G_VALUE_TYPE_NAME (return_value));
SIGNAL_UNLOCK ();
return;
}
}
@ -3283,14 +3293,15 @@ g_signal_emitv (const GValue *instance_and_params,
if (hlist == NULL || hlist->handlers == NULL)
{
/* nothing to do to emit this signal */
SIGNAL_UNLOCK ();
/* g_printerr ("omitting emission of \"%s\"\n", node->name); */
return;
}
}
SIGNAL_UNLOCK ();
signal_emit_unlocked_R (node, detail, instance, return_value, instance_and_params);
/* Pass a stable node pointer, whose address can't change even if the
* g_signal_nodes array gets reallocated. */
SignalNode node_copy = *node;
signal_emit_unlocked_R (&node_copy, detail, instance, return_value, instance_and_params);
}
static inline gboolean
@ -3312,6 +3323,12 @@ accumulate (GSignalInvocationHint *ihint,
return continue_emission;
}
static gboolean
signal_emit_valist_unlocked (gpointer instance,
guint signal_id,
GQuark detail,
va_list var_args);
/**
* g_signal_emit_valist: (skip)
* @instance: (type GObject.TypeInstance): the instance the signal is being
@ -3333,36 +3350,60 @@ g_signal_emit_valist (gpointer instance,
guint signal_id,
GQuark detail,
va_list var_args)
{
SIGNAL_LOCK ();
if (signal_emit_valist_unlocked (instance, signal_id, detail, var_args))
SIGNAL_UNLOCK ();
}
/*<private>
* signal_emit_valist_unlocked:
* @instance: The instance to emit from
* @signal_id: Signal id to emit
* @detail: Signal detail
* @var_args: Call arguments
*
* Returns: %TRUE if the signal mutex has been left locked
*/
static gboolean
signal_emit_valist_unlocked (gpointer instance,
guint signal_id,
GQuark detail,
va_list var_args)
{
GValue *instance_and_params;
GType signal_return_type;
GValue *param_values;
SignalNode *node;
guint i, n_params;
guint i;
g_return_if_fail (G_TYPE_CHECK_INSTANCE (instance));
g_return_if_fail (signal_id > 0);
g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (instance), TRUE);
g_return_val_if_fail (signal_id > 0, TRUE);
SIGNAL_LOCK ();
node = LOOKUP_SIGNAL_NODE (signal_id);
if (!node || !g_type_is_a (G_TYPE_FROM_INSTANCE (instance), node->itype))
{
g_critical ("%s: signal id '%u' is invalid for instance '%p'", G_STRLOC, signal_id, instance);
SIGNAL_UNLOCK ();
return;
return TRUE;
}
#ifndef G_DISABLE_CHECKS
if (detail && !(node->flags & G_SIGNAL_DETAILED))
{
g_critical ("%s: signal id '%u' does not support detail (%u)", G_STRLOC, signal_id, detail);
SIGNAL_UNLOCK ();
return;
return TRUE;
}
#endif /* !G_DISABLE_CHECKS */
if (!node->single_va_closure_is_valid)
node_update_single_va_closure (node);
/* There's no need to deep copy this, because a SignalNode instance won't
* ever be destroyed, given that _g_signals_destroy() is not called in any
* real program, however the SignalNode pointer could change, so just store
* the struct contents references, so that we won't try to deference a
* potentially invalid (or changed) pointer;
*/
SignalNode node_copy = *node;
if (node->single_va_closure != NULL)
{
HandlerList* hlist;
@ -3415,32 +3456,26 @@ g_signal_emit_valist (gpointer instance,
}
}
if (fastpath && closure == NULL && node->return_type == G_TYPE_NONE)
{
SIGNAL_UNLOCK ();
return;
}
if (fastpath && closure == NULL && node_copy.return_type == G_TYPE_NONE)
return TRUE;
/* Don't allow no-recurse emission as we might have to restart, which means
we will run multiple handlers and thus must ref all arguments */
if (closure != NULL && (node->flags & (G_SIGNAL_NO_RECURSE)) != 0)
if (closure != NULL && (node_copy.flags & (G_SIGNAL_NO_RECURSE)) != 0)
fastpath = FALSE;
if (fastpath)
{
SignalAccumulator *accumulator;
Emission emission;
GValue *return_accu, accu = G_VALUE_INIT;
GType instance_type = G_TYPE_FROM_INSTANCE (instance);
GValue emission_return = G_VALUE_INIT;
GType rtype = node->return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE;
gboolean static_scope = node->return_type & G_SIGNAL_TYPE_STATIC_SCOPE;
GType rtype = node_copy.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE;
gboolean static_scope = node_copy.return_type & G_SIGNAL_TYPE_STATIC_SCOPE;
signal_id = node->signal_id;
accumulator = node->accumulator;
if (rtype == G_TYPE_NONE)
return_accu = NULL;
else if (accumulator)
else if (node_copy.accumulator)
return_accu = &accu;
else
return_accu = &emission_return;
@ -3456,18 +3491,18 @@ g_signal_emit_valist (gpointer instance,
if (fastpath_handler)
handler_ref (fastpath_handler);
SIGNAL_UNLOCK ();
if (closure != NULL)
{
TRACE(GOBJECT_SIGNAL_EMIT(signal_id, detail, instance, instance_type));
SIGNAL_UNLOCK ();
if (rtype != G_TYPE_NONE)
g_value_init (&emission_return, rtype);
if (accumulator)
g_value_init (&accu, rtype);
if (node_copy.accumulator)
g_value_init (&accu, rtype);
if (closure != NULL)
{
/*
* Coverity doesnt understand the paired ref/unref here and seems
* to ignore the ref, thus reports every call to g_signal_emit()
@ -3482,12 +3517,15 @@ g_signal_emit_valist (gpointer instance,
return_accu,
instance,
var_args,
node->n_params,
node->param_types);
accumulate (&emission.ihint, &emission_return, &accu, accumulator);
}
node_copy.n_params,
node_copy.param_types);
accumulate (&emission.ihint, &emission_return, &accu, node_copy.accumulator);
SIGNAL_LOCK ();
if (node_copy.accumulator)
g_value_unset (&accu);
SIGNAL_LOCK ();
}
emission.chain_type = G_TYPE_NONE;
emission_pop (&emission);
@ -3495,20 +3533,20 @@ g_signal_emit_valist (gpointer instance,
if (fastpath_handler)
handler_unref_R (signal_id, instance, fastpath_handler);
SIGNAL_UNLOCK ();
if (accumulator)
g_value_unset (&accu);
SIGNAL_UNLOCK ();
if (rtype != G_TYPE_NONE)
{
gchar *error = NULL;
for (i = 0; i < node->n_params; i++)
for (i = 0; i < node_copy.n_params; i++)
{
GType ptype = node->param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
GType ptype = node_copy.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
G_VALUE_COLLECT_SKIP (ptype, var_args);
}
if (closure == NULL)
g_value_init (&emission_return, rtype);
G_VALUE_LCOPY (&emission_return,
var_args,
static_scope ? G_VALUE_NOCOPY_CONTENTS : 0,
@ -3533,21 +3571,20 @@ g_signal_emit_valist (gpointer instance,
g_object_unref (instance);
#endif
return;
return FALSE;
}
}
SIGNAL_UNLOCK ();
n_params = node->n_params;
signal_return_type = node->return_type;
instance_and_params = g_newa0 (GValue, n_params + 1);
instance_and_params = g_newa0 (GValue, node_copy.n_params + 1);
param_values = instance_and_params + 1;
for (i = 0; i < node->n_params; i++)
for (i = 0; i < node_copy.n_params; i++)
{
gchar *error;
GType ptype = node->param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
gboolean static_scope = node->param_types[i] & G_SIGNAL_TYPE_STATIC_SCOPE;
GType ptype = node_copy.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
gboolean static_scope = node_copy.param_types[i] & G_SIGNAL_TYPE_STATIC_SCOPE;
G_VALUE_COLLECT_INIT (param_values + i, ptype,
var_args,
@ -3564,24 +3601,29 @@ g_signal_emit_valist (gpointer instance,
while (i--)
g_value_unset (param_values + i);
return;
return FALSE;
}
}
instance_and_params->g_type = 0;
g_value_init_from_instance (instance_and_params, instance);
if (signal_return_type == G_TYPE_NONE)
signal_emit_unlocked_R (node, detail, instance, NULL, instance_and_params);
if (node_copy.return_type == G_TYPE_NONE)
{
SIGNAL_LOCK ();
signal_emit_unlocked_R (&node_copy, detail, instance, NULL, instance_and_params);
SIGNAL_UNLOCK ();
}
else
{
GValue return_value = G_VALUE_INIT;
gchar *error = NULL;
GType rtype = signal_return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE;
gboolean static_scope = signal_return_type & G_SIGNAL_TYPE_STATIC_SCOPE;
GType rtype = node_copy.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE;
gboolean static_scope = node_copy.return_type & G_SIGNAL_TYPE_STATIC_SCOPE;
g_value_init (&return_value, rtype);
signal_emit_unlocked_R (node, detail, instance, &return_value, instance_and_params);
SIGNAL_LOCK ();
signal_emit_unlocked_R (&node_copy, detail, instance, &return_value, instance_and_params);
SIGNAL_UNLOCK ();
G_VALUE_LCOPY (&return_value,
var_args,
@ -3599,9 +3641,11 @@ g_signal_emit_valist (gpointer instance,
*/
}
}
for (i = 0; i < n_params; i++)
for (i = 0; i < node_copy.n_params; i++)
g_value_unset (param_values + i);
g_value_unset (instance_and_params);
return FALSE;
}
/**
@ -3663,19 +3707,41 @@ g_signal_emit_by_name (gpointer instance,
SIGNAL_LOCK ();
signal_id = signal_parse_name (detailed_signal, itype, &detail, TRUE);
SIGNAL_UNLOCK ();
if (signal_id)
{
va_list var_args;
va_start (var_args, detailed_signal);
g_signal_emit_valist (instance, signal_id, detail, var_args);
if (signal_emit_valist_unlocked (instance, signal_id, detail, var_args))
SIGNAL_UNLOCK ();
va_end (var_args);
}
else
g_critical ("%s: signal name '%s' is invalid for instance '%p' of type '%s'",
G_STRLOC, detailed_signal, instance, g_type_name (itype));
{
SIGNAL_UNLOCK ();
g_critical ("%s: signal name '%s' is invalid for instance '%p' of type '%s'",
G_STRLOC, detailed_signal, instance, g_type_name (itype));
}
}
G_ALWAYS_INLINE static inline GValue *
maybe_init_accumulator_unlocked (SignalNode *node,
GValue *emission_return,
GValue *accumulator_value)
{
if (node->accumulator)
{
if (accumulator_value->g_type)
return accumulator_value;
g_value_init (accumulator_value,
node->return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE);
return accumulator_value;
}
return emission_return;
}
static gboolean
@ -3694,11 +3760,16 @@ signal_emit_unlocked_R (SignalNode *node,
guint signal_id;
gulong max_sequential_handler_number;
gboolean return_value_altered = FALSE;
guint n_params;
TRACE(GOBJECT_SIGNAL_EMIT(node->signal_id, detail, instance, G_TYPE_FROM_INSTANCE (instance)));
SIGNAL_LOCK ();
/* We expect this function to be called with a stable SignalNode pointer
* that cannot change location, so accessing its stable members should
* always work even after a lock/unlock.
*/
signal_id = node->signal_id;
n_params = node->n_params + 1;
if (node->flags & G_SIGNAL_NO_RECURSE)
{
@ -3707,20 +3778,10 @@ signal_emit_unlocked_R (SignalNode *node,
if (emission_node)
{
emission_node->state = EMISSION_RESTART;
SIGNAL_UNLOCK ();
return return_value_altered;
}
}
accumulator = node->accumulator;
if (accumulator)
{
SIGNAL_UNLOCK ();
g_value_init (&accu, node->return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE);
return_accu = &accu;
SIGNAL_LOCK ();
}
else
return_accu = emission_return;
emission.instance = instance;
emission.ihint.signal_id = node->signal_id;
emission.ihint.detail = detail;
@ -3748,9 +3809,10 @@ signal_emit_unlocked_R (SignalNode *node,
emission.chain_type = G_TYPE_FROM_INSTANCE (instance);
SIGNAL_UNLOCK ();
return_accu = maybe_init_accumulator_unlocked (node, emission_return, &accu);
g_closure_invoke (class_closure,
return_accu,
node->n_params + 1,
n_params,
instance_and_params,
&emission.ihint);
if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) &&
@ -3768,32 +3830,128 @@ signal_emit_unlocked_R (SignalNode *node,
if (node->emission_hooks)
{
gboolean need_destroy, was_in_call, may_recurse = TRUE;
GHook *hook;
GHook *static_emission_hooks[3];
size_t n_emission_hooks = 0;
const gboolean may_recurse = TRUE;
guint i;
emission.state = EMISSION_HOOK;
/* Quick check to determine whether any hooks match this emission,
* before committing to the more complex work of calling those hooks.
* We save a few of them into a static array, to try to avoid further
* allocations.
*/
hook = g_hook_first_valid (node->emission_hooks, may_recurse);
while (hook)
{
SignalHook *signal_hook = SIGNAL_HOOK (hook);
if (!signal_hook->detail || signal_hook->detail == detail)
{
GSignalEmissionHook hook_func = (GSignalEmissionHook) hook->func;
{
if (n_emission_hooks < G_N_ELEMENTS (static_emission_hooks))
{
static_emission_hooks[n_emission_hooks] =
g_hook_ref (node->emission_hooks, hook);
}
n_emission_hooks += 1;
}
was_in_call = G_HOOK_IN_CALL (hook);
hook->flags |= G_HOOK_FLAG_IN_CALL;
SIGNAL_UNLOCK ();
need_destroy = !hook_func (&emission.ihint, node->n_params + 1, instance_and_params, hook->data);
SIGNAL_LOCK ();
if (!was_in_call)
hook->flags &= ~G_HOOK_FLAG_IN_CALL;
if (need_destroy)
g_hook_destroy_link (node->emission_hooks, hook);
}
hook = g_hook_next_valid (node->emission_hooks, hook, may_recurse);
}
/* Re-iterate back through the matching hooks and copy them into
* an array which wont change when we unlock to call the
* user-provided hook functions.
* These functions may change hook configuration for this signal,
* add / remove signal handlers, etc.
*/
if G_UNLIKELY (n_emission_hooks > 0)
{
guint8 static_hook_returns[G_N_ELEMENTS (static_emission_hooks)];
GHook **emission_hooks = NULL;
guint8 *hook_returns = NULL;
if G_LIKELY (n_emission_hooks <= G_N_ELEMENTS (static_emission_hooks))
{
emission_hooks = static_emission_hooks;
hook_returns = static_hook_returns;
}
else
{
emission_hooks = g_newa (GHook *, n_emission_hooks);
hook_returns = g_newa (guint8, n_emission_hooks);
/* We can't just memcpy the ones we have in the static array,
* to the alloca()'d one because otherwise we'd get an invalid
* ID assertion during unref
*/
i = 0;
for (hook = g_hook_first_valid (node->emission_hooks, may_recurse);
hook != NULL;
hook = g_hook_next_valid (node->emission_hooks, hook, may_recurse))
{
SignalHook *signal_hook = SIGNAL_HOOK (hook);
if (!signal_hook->detail || signal_hook->detail == detail)
{
if (i < G_N_ELEMENTS (static_emission_hooks))
{
emission_hooks[i] = g_steal_pointer (&static_emission_hooks[i]);
g_assert (emission_hooks[i] == hook);
}
else
{
emission_hooks[i] = g_hook_ref (node->emission_hooks, hook);
}
i += 1;
}
}
g_assert (i == n_emission_hooks);
}
SIGNAL_UNLOCK ();
for (i = 0; i < n_emission_hooks; ++i)
{
GSignalEmissionHook hook_func;
gboolean need_destroy;
guint old_flags;
hook = emission_hooks[i];
hook_func = (GSignalEmissionHook) hook->func;
old_flags = g_atomic_int_or (&hook->flags, G_HOOK_FLAG_IN_CALL);
need_destroy = !hook_func (&emission.ihint, n_params,
instance_and_params, hook->data);
if (!(old_flags & G_HOOK_FLAG_IN_CALL))
{
g_atomic_int_compare_and_exchange (&hook->flags,
old_flags | G_HOOK_FLAG_IN_CALL,
old_flags);
}
hook_returns[i] = !!need_destroy;
}
SIGNAL_LOCK ();
for (i = 0; i < n_emission_hooks; i++)
{
hook = emission_hooks[i];
g_hook_unref (node->emission_hooks, hook);
if (hook_returns[i])
g_hook_destroy_link (node->emission_hooks, hook);
}
}
if (emission.state == EMISSION_RESTART)
goto EMIT_RESTART;
}
@ -3818,9 +3976,10 @@ signal_emit_unlocked_R (SignalNode *node,
handler->sequential_number < max_sequential_handler_number)
{
SIGNAL_UNLOCK ();
return_accu = maybe_init_accumulator_unlocked (node, emission_return, &accu);
g_closure_invoke (handler->closure,
return_accu,
node->n_params + 1,
n_params,
instance_and_params,
&emission.ihint);
if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) &&
@ -3857,9 +4016,10 @@ signal_emit_unlocked_R (SignalNode *node,
emission.chain_type = G_TYPE_FROM_INSTANCE (instance);
SIGNAL_UNLOCK ();
return_accu = maybe_init_accumulator_unlocked (node, emission_return, &accu);
g_closure_invoke (class_closure,
return_accu,
node->n_params + 1,
n_params,
instance_and_params,
&emission.ihint);
if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) &&
@ -3889,9 +4049,10 @@ signal_emit_unlocked_R (SignalNode *node,
handler->sequential_number < max_sequential_handler_number)
{
SIGNAL_UNLOCK ();
return_accu = maybe_init_accumulator_unlocked (node, emission_return, &accu);
g_closure_invoke (handler->closure,
return_accu,
node->n_params + 1,
n_params,
instance_and_params,
&emission.ihint);
if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) &&
@ -3938,7 +4099,7 @@ signal_emit_unlocked_R (SignalNode *node,
}
g_closure_invoke (class_closure,
node->return_type != G_TYPE_NONE ? &accu : NULL,
node->n_params + 1,
n_params,
instance_and_params,
&emission.ihint);
if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) &&
@ -3959,7 +4120,6 @@ signal_emit_unlocked_R (SignalNode *node,
handler_unref_R (signal_id, instance, handler_list);
emission_pop (&emission);
SIGNAL_UNLOCK ();
if (accumulator)
g_value_unset (&accu);

View File

@ -1130,12 +1130,35 @@ hook_func (GSignalInvocationHint *ihint,
return TRUE;
}
static gboolean
hook_func_removal (GSignalInvocationHint *ihint,
guint n_params,
const GValue *params,
gpointer data)
{
gint *count = data;
(*count)++;
return FALSE;
}
static void
simple_handler_remove_hook (GObject *sender,
gpointer data)
{
gulong *hook = data;
g_signal_remove_emission_hook (simple_id, *hook);
}
static void
test_emission_hook (void)
{
GObject *test1, *test2;
gint count = 0;
gulong hook;
gulong connection_id;
test1 = g_object_new (test_get_type (), NULL);
test2 = g_object_new (test_get_type (), NULL);
@ -1150,6 +1173,73 @@ test_emission_hook (void)
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 2);
count = 0;
hook = g_signal_add_emission_hook (simple_id, 0, hook_func_removal, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 1);
g_signal_emit_by_name (test2, "simple");
g_assert_cmpint (count, ==, 1);
g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_CRITICAL,
"*simple* had no hook * to remove");
g_signal_remove_emission_hook (simple_id, hook);
g_test_assert_expected_messages ();
count = 0;
hook = g_signal_add_emission_hook (simple_id, 0, hook_func, &count, NULL);
connection_id = g_signal_connect (test1, "simple",
G_CALLBACK (simple_handler_remove_hook), &hook);
g_assert_cmpint (count, ==, 0);
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 1);
g_signal_emit_by_name (test2, "simple");
g_assert_cmpint (count, ==, 1);
g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_CRITICAL,
"*simple* had no hook * to remove");
g_signal_remove_emission_hook (simple_id, hook);
g_test_assert_expected_messages ();
g_clear_signal_handler (&connection_id, test1);
gulong hooks[10];
count = 0;
for (size_t i = 0; i < G_N_ELEMENTS (hooks); ++i)
hooks[i] = g_signal_add_emission_hook (simple_id, 0, hook_func, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 10);
g_signal_emit_by_name (test2, "simple");
g_assert_cmpint (count, ==, 20);
for (size_t i = 0; i < G_N_ELEMENTS (hooks); ++i)
g_signal_remove_emission_hook (simple_id, hooks[i]);
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 20);
count = 0;
for (size_t i = 0; i < G_N_ELEMENTS (hooks); ++i)
hooks[i] = g_signal_add_emission_hook (simple_id, 0, hook_func_removal, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 10);
g_signal_emit_by_name (test2, "simple");
g_assert_cmpint (count, ==, 10);
for (size_t i = 0; i < G_N_ELEMENTS (hooks); ++i)
{
g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_CRITICAL,
"*simple* had no hook * to remove");
g_signal_remove_emission_hook (simple_id, hooks[i]);
g_test_assert_expected_messages ();
}
g_object_unref (test1);
g_object_unref (test2);
}
@ -1818,6 +1908,143 @@ test_signal_is_valid_name (void)
g_assert_false (g_signal_is_valid_name (invalid_names[i]));
}
static void
test_emitv (void)
{
GArray *values;
GObject *test;
GValue return_value = G_VALUE_INIT;
gint count = 0;
guint signal_id;
gulong hook;
gulong id;
test = g_object_new (test_get_type (), NULL);
values = g_array_new (TRUE, TRUE, sizeof (GValue));
g_array_set_clear_func (values, (GDestroyNotify) g_value_unset);
g_array_set_size (values, 1);
g_value_init (&g_array_index (values, GValue, 0), G_TYPE_OBJECT);
g_value_set_object (&g_array_index (values, GValue, 0), test);
hook = g_signal_add_emission_hook (simple_id, 0, hook_func, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_signal_emitv ((GValue *) values->data, simple_id, 0, NULL);
g_assert_cmpint (count, ==, 1);
g_signal_remove_emission_hook (simple_id, hook);
g_array_set_size (values, 20);
g_value_init (&g_array_index (values, GValue, 1), G_TYPE_INT);
g_value_set_int (&g_array_index (values, GValue, 1), 42);
g_value_init (&g_array_index (values, GValue, 2), G_TYPE_BOOLEAN);
g_value_set_boolean (&g_array_index (values, GValue, 2), TRUE);
g_value_init (&g_array_index (values, GValue, 3), G_TYPE_CHAR);
g_value_set_schar (&g_array_index (values, GValue, 3), 17);
g_value_init (&g_array_index (values, GValue, 4), G_TYPE_UCHAR);
g_value_set_uchar (&g_array_index (values, GValue, 4), 140);
g_value_init (&g_array_index (values, GValue, 5), G_TYPE_UINT);
g_value_set_uint (&g_array_index (values, GValue, 5), G_MAXUINT - 42);
g_value_init (&g_array_index (values, GValue, 6), G_TYPE_LONG);
g_value_set_long (&g_array_index (values, GValue, 6), -1117);
g_value_init (&g_array_index (values, GValue, 7), G_TYPE_ULONG);
g_value_set_ulong (&g_array_index (values, GValue, 7), G_MAXULONG - 999);
g_value_init (&g_array_index (values, GValue, 8), enum_type);
g_value_set_enum (&g_array_index (values, GValue, 8), MY_ENUM_VALUE);
g_value_init (&g_array_index (values, GValue, 9), flags_type);
g_value_set_flags (&g_array_index (values, GValue, 9),
MY_FLAGS_FIRST_BIT | MY_FLAGS_THIRD_BIT | MY_FLAGS_LAST_BIT);
g_value_init (&g_array_index (values, GValue, 10), G_TYPE_FLOAT);
g_value_set_float (&g_array_index (values, GValue, 10), 0.25);
g_value_init (&g_array_index (values, GValue, 11), G_TYPE_DOUBLE);
g_value_set_double (&g_array_index (values, GValue, 11), 1.5);
g_value_init (&g_array_index (values, GValue, 12), G_TYPE_STRING);
g_value_set_string (&g_array_index (values, GValue, 12), "Test");
g_value_init (&g_array_index (values, GValue, 13), G_TYPE_PARAM_LONG);
g_value_take_param (&g_array_index (values, GValue, 13),
g_param_spec_long ("param", "nick", "blurb", 0, 10, 4, 0));
g_value_init (&g_array_index (values, GValue, 14), G_TYPE_BYTES);
g_value_take_boxed (&g_array_index (values, GValue, 14),
g_bytes_new_static ("Blah", 5));
g_value_init (&g_array_index (values, GValue, 15), G_TYPE_POINTER);
g_value_set_pointer (&g_array_index (values, GValue, 15), &enum_type);
g_value_init (&g_array_index (values, GValue, 16), test_get_type ());
g_value_set_object (&g_array_index (values, GValue, 16), test);
g_value_init (&g_array_index (values, GValue, 17), G_TYPE_VARIANT);
g_value_take_variant (&g_array_index (values, GValue, 17),
g_variant_ref_sink (g_variant_new_uint16 (99)));
g_value_init (&g_array_index (values, GValue, 18), G_TYPE_INT64);
g_value_set_int64 (&g_array_index (values, GValue, 18), G_MAXINT64 - 1234);
g_value_init (&g_array_index (values, GValue, 19), G_TYPE_UINT64);
g_value_set_uint64 (&g_array_index (values, GValue, 19), G_MAXUINT64 - 123456);
id = g_signal_connect (test, "all-types", G_CALLBACK (all_types_handler_cb), &flags_type);
signal_id = g_signal_lookup ("all-types", test_get_type ());
g_assert_cmpuint (signal_id, >, 0);
count = 0;
hook = g_signal_add_emission_hook (signal_id, 0, hook_func, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_signal_emitv ((GValue *) values->data, signal_id, 0, NULL);
g_assert_cmpint (count, ==, 1);
g_signal_remove_emission_hook (signal_id, hook);
g_clear_signal_handler (&id, test);
signal_id = g_signal_lookup ("generic-marshaller-int-return", test_get_type ());
g_assert_cmpuint (signal_id, >, 0);
g_array_set_size (values, 1);
id = g_signal_connect (test,
"generic-marshaller-int-return",
G_CALLBACK (on_generic_marshaller_int_return_signed_1),
NULL);
count = 0;
hook = g_signal_add_emission_hook (signal_id, 0, hook_func, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_value_init (&return_value, G_TYPE_INT);
g_signal_emitv ((GValue *) values->data, signal_id, 0, &return_value);
g_assert_cmpint (count, ==, 1);
g_assert_cmpint (g_value_get_int (&return_value), ==, -30);
g_signal_remove_emission_hook (signal_id, hook);
g_clear_signal_handler (&id, test);
#ifdef G_ENABLE_DEBUG
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*return*value*generic-marshaller-int-return*NULL*");
g_signal_emitv ((GValue *) values->data, signal_id, 0, NULL);
g_test_assert_expected_messages ();
g_value_unset (&return_value);
g_value_init (&return_value, G_TYPE_FLOAT);
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*return*value*generic-marshaller-int-return*gfloat*");
g_signal_emitv ((GValue *) values->data, signal_id, 0, &return_value);
g_test_assert_expected_messages ();
#endif
g_object_unref (test);
g_array_unref (values);
}
/* --- */
int
@ -1839,6 +2066,7 @@ main (int argc,
g_test_add_func ("/gobject/signals/custom-marshaller", test_custom_marshaller);
g_test_add_func ("/gobject/signals/connect", test_connect);
g_test_add_func ("/gobject/signals/emission-hook", test_emission_hook);
g_test_add_func ("/gobject/signals/emitv", test_emitv);
g_test_add_func ("/gobject/signals/accumulator", test_accumulator);
g_test_add_func ("/gobject/signals/accumulator-class", test_accumulator_class);
g_test_add_func ("/gobject/signals/introspection", test_introspection);