diff options
Diffstat (limited to 'gio')
-rw-r--r-- | gio/gdbusauthmechanismsha1.c | 1 | ||||
-rw-r--r-- | gio/gdbusconnection.c | 175 | ||||
-rw-r--r-- | gio/gdbusmessage.c | 259 | ||||
-rw-r--r-- | gio/gdelayedsettingsbackend.c | 3 | ||||
-rw-r--r-- | gio/gnextstepsettingsbackend.m | 6 | ||||
-rw-r--r-- | gio/gsettings.c | 30 | ||||
-rw-r--r-- | gio/gtestdbus.c | 89 | ||||
-rw-r--r-- | gio/gwin32appinfo.c | 5 | ||||
-rw-r--r-- | gio/tests/gdbus-export.c | 180 | ||||
-rw-r--r-- | gio/tests/gdbus-serialization.c | 149 | ||||
-rw-r--r-- | gio/tests/gsettings.c | 191 | ||||
-rw-r--r-- | gio/tests/meson.build | 11 |
12 files changed, 815 insertions, 284 deletions
diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c index 94fe0bce8..066ef1ab7 100644 --- a/gio/gdbusauthmechanismsha1.c +++ b/gio/gdbusauthmechanismsha1.c @@ -908,6 +908,7 @@ keyring_generate_entry (const gchar *cookie_context, _("(Additionally, releasing the lock for “%s” also failed: %s) "), path, local_error->message); + g_error_free (local_error); } } else diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c index d730111f8..73b5b309a 100644 --- a/gio/gdbusconnection.c +++ b/gio/gdbusconnection.c @@ -466,7 +466,8 @@ typedef struct ExportedObject ExportedObject; static void exported_object_free (ExportedObject *eo); typedef struct ExportedSubtree ExportedSubtree; -static void exported_subtree_free (ExportedSubtree *es); +static ExportedSubtree *exported_subtree_ref (ExportedSubtree *es); +static void exported_subtree_unref (ExportedSubtree *es); enum { @@ -1096,7 +1097,7 @@ g_dbus_connection_init (GDBusConnection *connection) connection->map_object_path_to_es = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, - (GDestroyNotify) exported_subtree_free); + (GDestroyNotify) exported_subtree_unref); connection->map_id_to_es = g_hash_table_new (g_direct_hash, g_direct_equal); @@ -4085,23 +4086,39 @@ typedef struct { ExportedObject *eo; + gint refcount; /* (atomic) */ + guint id; - gchar *interface_name; - GDBusInterfaceVTable *vtable; - GDBusInterfaceInfo *interface_info; + gchar *interface_name; /* (owned) */ + GDBusInterfaceVTable *vtable; /* (owned) */ + GDBusInterfaceInfo *interface_info; /* (owned) */ - GMainContext *context; + GMainContext *context; /* (owned) */ gpointer user_data; GDestroyNotify user_data_free_func; } ExportedInterface; -/* called with lock held */ +static ExportedInterface * +exported_interface_ref (ExportedInterface *ei) +{ + g_atomic_int_inc (&ei->refcount); + + return ei; +} + +/* May be called with lock held */ static void -exported_interface_free (ExportedInterface *ei) +exported_interface_unref (ExportedInterface *ei) { + if (!g_atomic_int_dec_and_test (&ei->refcount)) + return; + g_dbus_interface_info_cache_release (ei->interface_info); g_dbus_interface_info_unref ((GDBusInterfaceInfo *) ei->interface_info); + /* All uses of ei->vtable from callbacks scheduled in idle functions must + * have completed by this call_destroy_notify() call, as language bindings + * may destroy function closures in this callback. */ call_destroy_notify (ei->context, ei->user_data_free_func, ei->user_data); @@ -4113,36 +4130,95 @@ exported_interface_free (ExportedInterface *ei) g_free (ei); } +struct ExportedSubtree +{ + gint refcount; /* (atomic) */ + + guint id; + gchar *object_path; /* (owned) */ + GDBusConnection *connection; /* (unowned) */ + GDBusSubtreeVTable *vtable; /* (owned) */ + GDBusSubtreeFlags flags; + + GMainContext *context; /* (owned) */ + gpointer user_data; + GDestroyNotify user_data_free_func; +}; + +static ExportedSubtree * +exported_subtree_ref (ExportedSubtree *es) +{ + g_atomic_int_inc (&es->refcount); + + return es; +} + +/* May be called with lock held */ +static void +exported_subtree_unref (ExportedSubtree *es) +{ + if (!g_atomic_int_dec_and_test (&es->refcount)) + return; + + /* All uses of es->vtable from callbacks scheduled in idle functions must + * have completed by this call_destroy_notify() call, as language bindings + * may destroy function closures in this callback. */ + call_destroy_notify (es->context, + es->user_data_free_func, + es->user_data); + + g_main_context_unref (es->context); + + _g_dbus_subtree_vtable_free (es->vtable); + g_free (es->object_path); + g_free (es); +} + /* ---------------------------------------------------------------------------------------------------- */ /* Convenience function to check if @registration_id (if not zero) or * @subtree_registration_id (if not zero) has been unregistered. If * so, returns %TRUE. * + * If not, sets @out_ei and/or @out_es to a strong reference to the relevant + * #ExportedInterface/#ExportedSubtree and returns %FALSE. + * * May be called by any thread. Caller must *not* hold lock. */ static gboolean -has_object_been_unregistered (GDBusConnection *connection, - guint registration_id, - guint subtree_registration_id) +has_object_been_unregistered (GDBusConnection *connection, + guint registration_id, + ExportedInterface **out_ei, + guint subtree_registration_id, + ExportedSubtree **out_es) { gboolean ret; + ExportedInterface *ei = NULL; + gpointer es = NULL; g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); ret = FALSE; CONNECTION_LOCK (connection); - if (registration_id != 0 && g_hash_table_lookup (connection->map_id_to_ei, - GUINT_TO_POINTER (registration_id)) == NULL) + + if (registration_id != 0) { - ret = TRUE; + ei = g_hash_table_lookup (connection->map_id_to_ei, GUINT_TO_POINTER (registration_id)); + if (ei == NULL) + ret = TRUE; + else if (out_ei != NULL) + *out_ei = exported_interface_ref (ei); } - else if (subtree_registration_id != 0 && g_hash_table_lookup (connection->map_id_to_es, - GUINT_TO_POINTER (subtree_registration_id)) == NULL) + if (subtree_registration_id != 0) { - ret = TRUE; + es = g_hash_table_lookup (connection->map_id_to_es, GUINT_TO_POINTER (subtree_registration_id)); + if (es == NULL) + ret = TRUE; + else if (out_es != NULL) + *out_es = exported_subtree_ref (es); } + CONNECTION_UNLOCK (connection); return ret; @@ -4179,10 +4255,14 @@ invoke_get_property_in_idle_cb (gpointer _data) GVariant *value; GError *error; GDBusMessage *reply; + ExportedInterface *ei = NULL; + ExportedSubtree *es = NULL; if (has_object_been_unregistered (data->connection, data->registration_id, - data->subtree_registration_id)) + &ei, + data->subtree_registration_id, + &es)) { reply = g_dbus_message_new_method_error (data->message, "org.freedesktop.DBus.Error.UnknownMethod", @@ -4229,6 +4309,9 @@ invoke_get_property_in_idle_cb (gpointer _data) } out: + g_clear_pointer (&ei, exported_interface_unref); + g_clear_pointer (&es, exported_subtree_unref); + return FALSE; } @@ -4526,10 +4609,14 @@ invoke_get_all_properties_in_idle_cb (gpointer _data) GVariantBuilder builder; GDBusMessage *reply; guint n; + ExportedInterface *ei = NULL; + ExportedSubtree *es = NULL; if (has_object_been_unregistered (data->connection, data->registration_id, - data->subtree_registration_id)) + &ei, + data->subtree_registration_id, + &es)) { reply = g_dbus_message_new_method_error (data->message, "org.freedesktop.DBus.Error.UnknownMethod", @@ -4582,6 +4669,9 @@ invoke_get_all_properties_in_idle_cb (gpointer _data) g_object_unref (reply); out: + g_clear_pointer (&ei, exported_interface_unref); + g_clear_pointer (&es, exported_subtree_unref); + return FALSE; } @@ -4891,13 +4981,17 @@ call_in_idle_cb (gpointer user_data) GDBusInterfaceVTable *vtable; guint registration_id; guint subtree_registration_id; + ExportedInterface *ei = NULL; + ExportedSubtree *es = NULL; registration_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (invocation), "g-dbus-registration-id")); subtree_registration_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (invocation), "g-dbus-subtree-registration-id")); if (has_object_been_unregistered (g_dbus_method_invocation_get_connection (invocation), registration_id, - subtree_registration_id)) + &ei, + subtree_registration_id, + &es)) { GDBusMessage *reply; reply = g_dbus_message_new_method_error (g_dbus_method_invocation_get_message (invocation), @@ -4923,6 +5017,9 @@ call_in_idle_cb (gpointer user_data) g_dbus_method_invocation_get_user_data (invocation)); out: + g_clear_pointer (&ei, exported_interface_unref); + g_clear_pointer (&es, exported_subtree_unref); + return FALSE; } @@ -5224,7 +5321,7 @@ g_dbus_connection_register_object (GDBusConnection *connection, eo->map_if_name_to_ei = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, - (GDestroyNotify) exported_interface_free); + (GDestroyNotify) exported_interface_unref); g_hash_table_insert (connection->map_object_path_to_eo, eo->object_path, eo); } @@ -5241,6 +5338,7 @@ g_dbus_connection_register_object (GDBusConnection *connection, } ei = g_new0 (ExportedInterface, 1); + ei->refcount = 1; ei->id = (guint) g_atomic_int_add (&_global_registration_id, 1); /* TODO: overflow etc. */ ei->eo = eo; ei->user_data = user_data; @@ -6401,33 +6499,6 @@ g_dbus_connection_call_with_unix_fd_list_sync (GDBusConnection *connection, /* ---------------------------------------------------------------------------------------------------- */ -struct ExportedSubtree -{ - guint id; - gchar *object_path; - GDBusConnection *connection; - GDBusSubtreeVTable *vtable; - GDBusSubtreeFlags flags; - - GMainContext *context; - gpointer user_data; - GDestroyNotify user_data_free_func; -}; - -static void -exported_subtree_free (ExportedSubtree *es) -{ - call_destroy_notify (es->context, - es->user_data_free_func, - es->user_data); - - g_main_context_unref (es->context); - - _g_dbus_subtree_vtable_free (es->vtable); - g_free (es->object_path); - g_free (es); -} - /* called without lock held in the thread where the caller registered * the subtree */ @@ -6753,14 +6824,15 @@ handle_subtree_method_invocation (GDBusConnection *connection, typedef struct { - GDBusMessage *message; - ExportedSubtree *es; + GDBusMessage *message; /* (owned) */ + ExportedSubtree *es; /* (owned) */ } SubtreeDeferredData; static void subtree_deferred_data_free (SubtreeDeferredData *data) { g_object_unref (data->message); + exported_subtree_unref (data->es); g_free (data); } @@ -6819,7 +6891,7 @@ subtree_message_func (GDBusConnection *connection, data = g_new0 (SubtreeDeferredData, 1); data->message = g_object_ref (message); - data->es = es; + data->es = exported_subtree_ref (es); /* defer this call to an idle handler in the right thread */ idle_source = g_idle_source_new (); @@ -6924,6 +6996,7 @@ g_dbus_connection_register_subtree (GDBusConnection *connection, } es = g_new0 (ExportedSubtree, 1); + es->refcount = 1; es->object_path = g_strdup (object_path); es->connection = connection; diff --git a/gio/gdbusmessage.c b/gio/gdbusmessage.c index cdc0b83e8..3415ed613 100644 --- a/gio/gdbusmessage.c +++ b/gio/gdbusmessage.c @@ -83,21 +83,36 @@ g_memory_buffer_is_byteswapped (GMemoryBuffer *mbuf) } static guchar -g_memory_buffer_read_byte (GMemoryBuffer *mbuf) +g_memory_buffer_read_byte (GMemoryBuffer *mbuf, + GError **error) { + g_return_val_if_fail (error == NULL || *error == NULL, 0); + if (mbuf->pos >= mbuf->valid_len) - return 0; + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Unexpected end of message while reading byte."); + return 0; + } return mbuf->data [mbuf->pos++]; } static gint16 -g_memory_buffer_read_int16 (GMemoryBuffer *mbuf) +g_memory_buffer_read_int16 (GMemoryBuffer *mbuf, + GError **error) { gint16 v; - + + g_return_val_if_fail (error == NULL || *error == NULL, -1); + if (mbuf->pos > mbuf->valid_len - 2) { - mbuf->pos = mbuf->valid_len; + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Unexpected end of message while reading int16."); return 0; } @@ -111,13 +126,19 @@ g_memory_buffer_read_int16 (GMemoryBuffer *mbuf) } static guint16 -g_memory_buffer_read_uint16 (GMemoryBuffer *mbuf) +g_memory_buffer_read_uint16 (GMemoryBuffer *mbuf, + GError **error) { guint16 v; - + + g_return_val_if_fail (error == NULL || *error == NULL, 0); + if (mbuf->pos > mbuf->valid_len - 2) { - mbuf->pos = mbuf->valid_len; + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Unexpected end of message while reading uint16."); return 0; } @@ -131,13 +152,19 @@ g_memory_buffer_read_uint16 (GMemoryBuffer *mbuf) } static gint32 -g_memory_buffer_read_int32 (GMemoryBuffer *mbuf) +g_memory_buffer_read_int32 (GMemoryBuffer *mbuf, + GError **error) { gint32 v; - + + g_return_val_if_fail (error == NULL || *error == NULL, -1); + if (mbuf->pos > mbuf->valid_len - 4) { - mbuf->pos = mbuf->valid_len; + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Unexpected end of message while reading int32."); return 0; } @@ -151,13 +178,19 @@ g_memory_buffer_read_int32 (GMemoryBuffer *mbuf) } static guint32 -g_memory_buffer_read_uint32 (GMemoryBuffer *mbuf) +g_memory_buffer_read_uint32 (GMemoryBuffer *mbuf, + GError **error) { guint32 v; - + + g_return_val_if_fail (error == NULL || *error == NULL, 0); + if (mbuf->pos > mbuf->valid_len - 4) { - mbuf->pos = mbuf->valid_len; + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Unexpected end of message while reading uint32."); return 0; } @@ -171,13 +204,19 @@ g_memory_buffer_read_uint32 (GMemoryBuffer *mbuf) } static gint64 -g_memory_buffer_read_int64 (GMemoryBuffer *mbuf) +g_memory_buffer_read_int64 (GMemoryBuffer *mbuf, + GError **error) { gint64 v; - + + g_return_val_if_fail (error == NULL || *error == NULL, -1); + if (mbuf->pos > mbuf->valid_len - 8) { - mbuf->pos = mbuf->valid_len; + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Unexpected end of message while reading int64."); return 0; } @@ -191,13 +230,19 @@ g_memory_buffer_read_int64 (GMemoryBuffer *mbuf) } static guint64 -g_memory_buffer_read_uint64 (GMemoryBuffer *mbuf) +g_memory_buffer_read_uint64 (GMemoryBuffer *mbuf, + GError **error) { guint64 v; - + + g_return_val_if_fail (error == NULL || *error == NULL, 0); + if (mbuf->pos > mbuf->valid_len - 8) { - mbuf->pos = mbuf->valid_len; + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Unexpected end of message while reading uint64."); return 0; } @@ -1500,7 +1545,9 @@ parse_value_from_blob (GMemoryBuffer *buf, if (!just_align) { gboolean v; - v = g_memory_buffer_read_uint32 (buf); + v = g_memory_buffer_read_uint32 (buf, &local_error); + if (local_error) + goto fail; ret = g_variant_new_boolean (v); } break; @@ -1509,7 +1556,9 @@ parse_value_from_blob (GMemoryBuffer *buf, if (!just_align) { guchar v; - v = g_memory_buffer_read_byte (buf); + v = g_memory_buffer_read_byte (buf, &local_error); + if (local_error) + goto fail; ret = g_variant_new_byte (v); } break; @@ -1519,7 +1568,9 @@ parse_value_from_blob (GMemoryBuffer *buf, if (!just_align) { gint16 v; - v = g_memory_buffer_read_int16 (buf); + v = g_memory_buffer_read_int16 (buf, &local_error); + if (local_error) + goto fail; ret = g_variant_new_int16 (v); } break; @@ -1529,7 +1580,9 @@ parse_value_from_blob (GMemoryBuffer *buf, if (!just_align) { guint16 v; - v = g_memory_buffer_read_uint16 (buf); + v = g_memory_buffer_read_uint16 (buf, &local_error); + if (local_error) + goto fail; ret = g_variant_new_uint16 (v); } break; @@ -1539,7 +1592,9 @@ parse_value_from_blob (GMemoryBuffer *buf, if (!just_align) { gint32 v; - v = g_memory_buffer_read_int32 (buf); + v = g_memory_buffer_read_int32 (buf, &local_error); + if (local_error) + goto fail; ret = g_variant_new_int32 (v); } break; @@ -1549,7 +1604,9 @@ parse_value_from_blob (GMemoryBuffer *buf, if (!just_align) { guint32 v; - v = g_memory_buffer_read_uint32 (buf); + v = g_memory_buffer_read_uint32 (buf, &local_error); + if (local_error) + goto fail; ret = g_variant_new_uint32 (v); } break; @@ -1559,7 +1616,9 @@ parse_value_from_blob (GMemoryBuffer *buf, if (!just_align) { gint64 v; - v = g_memory_buffer_read_int64 (buf); + v = g_memory_buffer_read_int64 (buf, &local_error); + if (local_error) + goto fail; ret = g_variant_new_int64 (v); } break; @@ -1569,7 +1628,9 @@ parse_value_from_blob (GMemoryBuffer *buf, if (!just_align) { guint64 v; - v = g_memory_buffer_read_uint64 (buf); + v = g_memory_buffer_read_uint64 (buf, &local_error); + if (local_error) + goto fail; ret = g_variant_new_uint64 (v); } break; @@ -1583,7 +1644,9 @@ parse_value_from_blob (GMemoryBuffer *buf, gdouble v_double; } u; G_STATIC_ASSERT (sizeof (gdouble) == sizeof (guint64)); - u.v_uint64 = g_memory_buffer_read_uint64 (buf); + u.v_uint64 = g_memory_buffer_read_uint64 (buf, &local_error); + if (local_error) + goto fail; ret = g_variant_new_double (u.v_double); } break; @@ -1594,7 +1657,9 @@ parse_value_from_blob (GMemoryBuffer *buf, { guint32 len; const gchar *v; - len = g_memory_buffer_read_uint32 (buf); + len = g_memory_buffer_read_uint32 (buf, &local_error); + if (local_error) + goto fail; v = read_string (buf, (gsize) len, &local_error); if (v == NULL) goto fail; @@ -1608,7 +1673,9 @@ parse_value_from_blob (GMemoryBuffer *buf, { guint32 len; const gchar *v; - len = g_memory_buffer_read_uint32 (buf); + len = g_memory_buffer_read_uint32 (buf, &local_error); + if (local_error) + goto fail; v = read_string (buf, (gsize) len, &local_error); if (v == NULL) goto fail; @@ -1630,7 +1697,9 @@ parse_value_from_blob (GMemoryBuffer *buf, { guchar len; const gchar *v; - len = g_memory_buffer_read_byte (buf); + len = g_memory_buffer_read_byte (buf, &local_error); + if (local_error) + goto fail; v = read_string (buf, (gsize) len, &local_error); if (v == NULL) goto fail; @@ -1652,7 +1721,9 @@ parse_value_from_blob (GMemoryBuffer *buf, if (!just_align) { gint32 v; - v = g_memory_buffer_read_int32 (buf); + v = g_memory_buffer_read_int32 (buf, &local_error); + if (local_error) + goto fail; ret = g_variant_new_handle (v); } break; @@ -1672,7 +1743,9 @@ parse_value_from_blob (GMemoryBuffer *buf, const GVariantType *element_type; guint fixed_size; - array_len = g_memory_buffer_read_uint32 (buf); + array_len = g_memory_buffer_read_uint32 (buf, &local_error); + if (local_error) + goto fail; #ifdef DEBUG_SERIALIZER is_leaf = FALSE; @@ -1776,6 +1849,16 @@ parse_value_from_blob (GMemoryBuffer *buf, } g_variant_builder_add_value (&builder, item); g_variant_unref (item); + + /* Array elements must not be zero-length. There are no + * valid zero-length serialisations of any types which + * can be array elements in the D-Bus wire format, so this + * assertion should always hold. + * + * See https://gitlab.gnome.org/GNOME/glib/-/issues/2557 + */ + g_assert (buf->pos > (gsize) offset); + offset = buf->pos; } } @@ -1844,6 +1927,16 @@ parse_value_from_blob (GMemoryBuffer *buf, g_variant_builder_init (&builder, type); element_type = g_variant_type_first (type); + if (!element_type) + { + g_variant_builder_clear (&builder); + g_set_error_literal (&local_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Empty structures (tuples) are not allowed in D-Bus")); + goto fail; + } + while (element_type != NULL) { GVariant *item; @@ -1880,7 +1973,9 @@ parse_value_from_blob (GMemoryBuffer *buf, GVariantType *variant_type; GVariant *value; - siglen = g_memory_buffer_read_byte (buf); + siglen = g_memory_buffer_read_byte (buf, &local_error); + if (local_error) + goto fail; sig = read_string (buf, (gsize) siglen, &local_error); if (sig == NULL) goto fail; @@ -2078,7 +2173,7 @@ g_dbus_message_new_from_blob (guchar *blob, GDBusCapabilityFlags capabilities, GError **error) { - gboolean ret; + GError *local_error = NULL; GMemoryBuffer mbuf; GDBusMessage *message; guchar endianness; @@ -2091,11 +2186,8 @@ g_dbus_message_new_from_blob (guchar *blob, /* TODO: check against @capabilities */ - ret = FALSE; - g_return_val_if_fail (blob != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - g_return_val_if_fail (blob_len >= 12, NULL); message = g_dbus_message_new (); @@ -2103,7 +2195,10 @@ g_dbus_message_new_from_blob (guchar *blob, mbuf.data = (gchar *)blob; mbuf.len = mbuf.valid_len = blob_len; - endianness = g_memory_buffer_read_byte (&mbuf); + endianness = g_memory_buffer_read_byte (&mbuf, &local_error); + if (local_error) + goto fail; + switch (endianness) { case 'l': @@ -2115,28 +2210,38 @@ g_dbus_message_new_from_blob (guchar *blob, message->byte_order = G_DBUS_MESSAGE_BYTE_ORDER_BIG_ENDIAN; break; default: - g_set_error (error, + g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid endianness value. Expected 0x6c (“l”) or 0x42 (“B”) but found value 0x%02x"), endianness); - goto out; + goto fail; } - message->type = g_memory_buffer_read_byte (&mbuf); - message->flags = g_memory_buffer_read_byte (&mbuf); - major_protocol_version = g_memory_buffer_read_byte (&mbuf); + message->type = g_memory_buffer_read_byte (&mbuf, &local_error); + if (local_error) + goto fail; + message->flags = g_memory_buffer_read_byte (&mbuf, &local_error); + if (local_error) + goto fail; + major_protocol_version = g_memory_buffer_read_byte (&mbuf, &local_error); + if (local_error) + goto fail; if (major_protocol_version != 1) { - g_set_error (error, + g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid major protocol version. Expected 1 but found %d"), major_protocol_version); - goto out; + goto fail; } - message_body_len = g_memory_buffer_read_uint32 (&mbuf); - message->serial = g_memory_buffer_read_uint32 (&mbuf); + message_body_len = g_memory_buffer_read_uint32 (&mbuf, &local_error); + if (local_error) + goto fail; + message->serial = g_memory_buffer_read_uint32 (&mbuf, &local_error); + if (local_error) + goto fail; #ifdef DEBUG_SERIALIZER g_print ("Parsing blob (blob_len = 0x%04x bytes)\n", (gint) blob_len); @@ -2156,9 +2261,9 @@ g_dbus_message_new_from_blob (guchar *blob, G_DBUS_MAX_TYPE_DEPTH + 2 /* for the a{yv} */, FALSE, 2, - error); + &local_error); if (headers == NULL) - goto out; + goto fail; g_variant_iter_init (&iter, headers); while ((item = g_variant_iter_next_value (&iter)) != NULL) { @@ -2182,11 +2287,11 @@ g_dbus_message_new_from_blob (guchar *blob, if (!g_variant_is_of_type (signature, G_VARIANT_TYPE_SIGNATURE)) { - g_set_error_literal (error, + g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Signature header found but is not of type signature")); - goto out; + goto fail; } signature_str = g_variant_get_string (signature, &signature_str_len); @@ -2194,12 +2299,12 @@ g_dbus_message_new_from_blob (guchar *blob, /* signature but no body */ if (message_body_len == 0 && signature_str_len > 0) { - g_set_error (error, + g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Signature header with signature “%s” found but message body is empty"), signature_str); - goto out; + goto fail; } else if (signature_str_len > 0) { @@ -2209,13 +2314,13 @@ g_dbus_message_new_from_blob (guchar *blob, if (!g_variant_is_signature (signature_str) || !g_variant_type_string_is_valid (tupled_signature_str)) { - g_set_error (error, + g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Parsed value “%s” is not a valid D-Bus signature (for body)"), signature_str); g_free (tupled_signature_str); - goto out; + goto fail; } variant_type = g_variant_type_new (tupled_signature_str); @@ -2228,10 +2333,10 @@ g_dbus_message_new_from_blob (guchar *blob, G_DBUS_MAX_TYPE_DEPTH + 1 /* for the surrounding tuple */, FALSE, 2, - error); + &local_error); g_variant_type_free (variant_type); if (message->body == NULL) - goto out; + goto fail; } } else @@ -2240,7 +2345,7 @@ g_dbus_message_new_from_blob (guchar *blob, if (message_body_len != 0) { /* G_GUINT32_FORMAT doesn't work with gettext, just use %u */ - g_set_error (error, + g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, g_dngettext (GETTEXT_PACKAGE, @@ -2248,29 +2353,22 @@ g_dbus_message_new_from_blob (guchar *blob, "No signature header in message but the message body is %u bytes", message_body_len), message_body_len); - goto out; + goto fail; } } - if (!validate_headers (message, error)) + if (!validate_headers (message, &local_error)) { - g_prefix_error (error, _("Cannot deserialize message: ")); - goto out; + g_prefix_error (&local_error, _("Cannot deserialize message: ")); + goto fail; } - ret = TRUE; + return message; - out: - if (ret) - { - return message; - } - else - { - if (message != NULL) - g_object_unref (message); - return NULL; - } +fail: + g_clear_object (&message); + g_propagate_error (error, local_error); + return NULL; } /* ---------------------------------------------------------------------------------------------------- */ @@ -2549,6 +2647,15 @@ append_value_to_blob (GVariant *value, default: if (g_variant_type_is_dict_entry (type) || g_variant_type_is_tuple (type)) { + if (!g_variant_type_first (type)) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Empty structures (tuples) are not allowed in D-Bus")); + goto fail; + } + padding_added = ensure_output_padding (mbuf, 8); if (value != NULL) { diff --git a/gio/gdelayedsettingsbackend.c b/gio/gdelayedsettingsbackend.c index 93ced25db..6fff6f71c 100644 --- a/gio/gdelayedsettingsbackend.c +++ b/gio/gdelayedsettingsbackend.c @@ -156,7 +156,8 @@ add_to_tree (gpointer key, gpointer value, gpointer user_data) { - g_tree_insert (user_data, g_strdup (key), g_variant_ref (value)); + /* A value may be %NULL if its key has been reset */ + g_tree_insert (user_data, g_strdup (key), (value != NULL) ? g_variant_ref (value) : NULL); return FALSE; } diff --git a/gio/gnextstepsettingsbackend.m b/gio/gnextstepsettingsbackend.m index 44ea845d8..c09d995b6 100644 --- a/gio/gnextstepsettingsbackend.m +++ b/gio/gnextstepsettingsbackend.m @@ -444,17 +444,17 @@ g_nextstep_settings_backend_get_ns_object (GVariant *variant) { NSMutableDictionary *dictionary; GVariantIter iter; - GVariant *name; + const gchar *name; GVariant *value; dictionary = [NSMutableDictionary dictionaryWithCapacity:g_variant_iter_init (&iter, variant)]; - while (g_variant_iter_loop (&iter, "{s*}", &name, &value)) + while (g_variant_iter_loop (&iter, "{&s*}", &name, &value)) { NSString *key; id object; - key = [NSString stringWithUTF8String:g_variant_get_string (name, NULL)]; + key = [NSString stringWithUTF8String:name]; object = g_nextstep_settings_backend_get_ns_object (value); [dictionary setObject:object forKey:key]; diff --git a/gio/gsettings.c b/gio/gsettings.c index f7d39c77e..9130dafd5 100644 --- a/gio/gsettings.c +++ b/gio/gsettings.c @@ -343,8 +343,6 @@ struct _GSettingsPrivate GSettingsBackend *backend; GSettingsSchema *schema; gchar *path; - - GDelayedSettingsBackend *delayed; }; enum @@ -642,7 +640,7 @@ g_settings_get_property (GObject *object, break; case PROP_DELAY_APPLY: - g_value_set_boolean (value, settings->priv->delayed != NULL); + g_value_set_boolean (value, G_IS_DELAYED_SETTINGS_BACKEND (settings->priv->backend)); break; default: @@ -2256,19 +2254,20 @@ g_settings_set_strv (GSettings *settings, void g_settings_delay (GSettings *settings) { + GDelayedSettingsBackend *delayed = NULL; + g_return_if_fail (G_IS_SETTINGS (settings)); - if (settings->priv->delayed) + if (G_IS_DELAYED_SETTINGS_BACKEND (settings->priv->backend)) return; - settings->priv->delayed = - g_delayed_settings_backend_new (settings->priv->backend, - settings, - settings->priv->main_context); + delayed = g_delayed_settings_backend_new (settings->priv->backend, + settings, + settings->priv->main_context); g_settings_backend_unwatch (settings->priv->backend, G_OBJECT (settings)); g_object_unref (settings->priv->backend); - settings->priv->backend = G_SETTINGS_BACKEND (settings->priv->delayed); + settings->priv->backend = G_SETTINGS_BACKEND (delayed); g_settings_backend_watch (settings->priv->backend, &listener_vtable, G_OBJECT (settings), settings->priv->main_context); @@ -2288,7 +2287,7 @@ g_settings_delay (GSettings *settings) void g_settings_apply (GSettings *settings) { - if (settings->priv->delayed) + if (G_IS_DELAYED_SETTINGS_BACKEND (settings->priv->backend)) { GDelayedSettingsBackend *delayed; @@ -2311,7 +2310,7 @@ g_settings_apply (GSettings *settings) void g_settings_revert (GSettings *settings) { - if (settings->priv->delayed) + if (G_IS_DELAYED_SETTINGS_BACKEND (settings->priv->backend)) { GDelayedSettingsBackend *delayed; @@ -2336,7 +2335,7 @@ g_settings_get_has_unapplied (GSettings *settings) { g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE); - return settings->priv->delayed && + return G_IS_DELAYED_SETTINGS_BACKEND (settings->priv->backend) && g_delayed_settings_backend_get_has_unapplied ( G_DELAYED_SETTINGS_BACKEND (settings->priv->backend)); } @@ -2424,9 +2423,12 @@ g_settings_is_writable (GSettings *settings, * @settings. * * The schema for the child settings object must have been declared - * in the schema of @settings using a <child> element. + * in the schema of @settings using a `<child>` element. + * + * The created child settings object will inherit the #GSettings:delay-apply + * mode from @settings. * - * Returns: (transfer full): a 'child' settings object + * Returns: (not nullable) (transfer full): a 'child' settings object * * Since: 2.26 */ diff --git a/gio/gtestdbus.c b/gio/gtestdbus.c index 703a0b3a5..992d29cef 100644 --- a/gio/gtestdbus.c +++ b/gio/gtestdbus.c @@ -32,6 +32,8 @@ #endif #ifdef G_OS_WIN32 #include <io.h> +#include <fcntl.h> +#include <windows.h> #endif #include <glib.h> @@ -44,8 +46,8 @@ #include "glibintl.h" -#ifdef G_OS_WIN32 -#include <windows.h> +#ifdef G_OS_UNIX +#include "glib-unix.h" #endif /* -------------------------------------------------------------------------- */ @@ -436,7 +438,6 @@ struct _GTestDBusPrivate GTestDBusFlags flags; GPtrArray *service_dirs; GPid bus_pid; - gint bus_stdout_fd; gchar *bus_address; gboolean up; }; @@ -596,58 +597,87 @@ write_config_file (GTestDBus *self) return path; } +static gboolean +make_pipe (gint pipe_fds[2], + GError **error) +{ +#if defined(G_OS_UNIX) + return g_unix_open_pipe (pipe_fds, FD_CLOEXEC, error); +#elif defined(G_OS_WIN32) + if (_pipe (pipe_fds, 4096, _O_BINARY) < 0) + { + int errsv = errno; + + g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, + _("Failed to create pipe for communicating with child process (%s)"), + g_strerror (errsv)); + return FALSE; + } + return TRUE; +#else + g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, + _("Pipes are not supported in this platform")); + return FALSE; +#endif +} + static void start_daemon (GTestDBus *self) { const gchar *argv[] = {"dbus-daemon", "--print-address", "--config-file=foo", NULL}; + gint pipe_fds[2] = {-1, -1}; gchar *config_path; gchar *config_arg; + gchar *print_address; GIOChannel *channel; - gint stdout_fd2; gsize termpos; GError *error = NULL; if (g_getenv ("G_TEST_DBUS_DAEMON") != NULL) argv[0] = (gchar *)g_getenv ("G_TEST_DBUS_DAEMON"); + make_pipe (pipe_fds, &error); + g_assert_no_error (error); + + print_address = g_strdup_printf ("--print-address=%d", pipe_fds[1]); + argv[1] = print_address; + g_assert_no_error (error); + /* Write config file and set its path in argv */ config_path = write_config_file (self); config_arg = g_strdup_printf ("--config-file=%s", config_path); argv[2] = config_arg; /* Spawn dbus-daemon */ - g_spawn_async_with_pipes (NULL, - (gchar **) argv, - NULL, - /* We Need this to get the pid returned on win32 */ - G_SPAWN_DO_NOT_REAP_CHILD | - G_SPAWN_SEARCH_PATH | - /* dbus-daemon will not abuse our descriptors, and - * passing this means we can use posix_spawn() for speed */ - G_SPAWN_LEAVE_DESCRIPTORS_OPEN, - NULL, - NULL, - &self->priv->bus_pid, - NULL, - &self->priv->bus_stdout_fd, - NULL, - &error); + g_spawn_async_with_pipes_and_fds (NULL, + argv, + NULL, + /* We Need this to get the pid returned on win32 */ + G_SPAWN_DO_NOT_REAP_CHILD | + G_SPAWN_SEARCH_PATH | + /* dbus-daemon will not abuse our descriptors, and + * passing this means we can use posix_spawn() for speed */ + G_SPAWN_LEAVE_DESCRIPTORS_OPEN, + NULL, NULL, + -1, -1, -1, + &pipe_fds[1], &pipe_fds[1], 1, + &self->priv->bus_pid, + NULL, NULL, NULL, + &error); g_assert_no_error (error); _g_test_watcher_add_pid (self->priv->bus_pid); - /* Read bus address from daemon' stdout. We have to be careful to avoid - * closing the FD, as it is passed to any D-Bus service activated processes, - * and if we close it, they will get a SIGPIPE and die when they try to write - * to their stdout. */ - stdout_fd2 = dup (self->priv->bus_stdout_fd); - g_assert_cmpint (stdout_fd2, >=, 0); - channel = g_io_channel_unix_new (stdout_fd2); - + /* Read bus address from pipe */ + channel = g_io_channel_unix_new (pipe_fds[0]); + pipe_fds[0] = -1; + g_io_channel_set_close_on_unref (channel, TRUE); g_io_channel_read_line (channel, &self->priv->bus_address, NULL, &termpos, &error); g_assert_no_error (error); self->priv->bus_address[termpos] = '\0'; + close (pipe_fds[1]); + pipe_fds[1] = -1; /* start dbus-monitor */ if (g_getenv ("G_DBUS_MONITOR") != NULL) @@ -671,6 +701,7 @@ start_daemon (GTestDBus *self) if (g_unlink (config_path) != 0) g_assert_not_reached (); + g_free (print_address); g_free (config_path); g_free (config_arg); } @@ -687,8 +718,6 @@ stop_daemon (GTestDBus *self) _g_test_watcher_remove_pid (self->priv->bus_pid); g_spawn_close_pid (self->priv->bus_pid); self->priv->bus_pid = 0; - close (self->priv->bus_stdout_fd); - self->priv->bus_stdout_fd = -1; g_free (self->priv->bus_address); self->priv->bus_address = NULL; diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index 3468dfee0..cafd053b4 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -983,7 +983,10 @@ get_verbs (GWin32RegistryKey *program_id_key, name, NULL); - g_assert (subkey != NULL); + /* We may not have the required access rights to open the child key */ + if (subkey == NULL) + continue; + /* The key we're looking at is "<some_root>/Shell/<this_key>", * where "Shell" is verbshell_prefix. * If it has a value named 'Subcommands' (doesn't matter what its data is), diff --git a/gio/tests/gdbus-export.c b/gio/tests/gdbus-export.c index aec21d7d0..4cdc24492 100644 --- a/gio/tests/gdbus-export.c +++ b/gio/tests/gdbus-export.c @@ -1792,6 +1792,184 @@ test_async_properties (void) g_object_unref (c); } +typedef struct +{ + GDBusConnection *connection; /* (owned) */ + guint registration_id; + guint subtree_registration_id; +} ThreadedUnregistrationData; + +static gpointer +unregister_thread_cb (gpointer user_data) +{ + ThreadedUnregistrationData *data = user_data; + + /* Sleeping here makes the race more likely to be hit, as it balances the + * time taken to set up the thread and unregister, with the time taken to + * make and handle the D-Bus call. This will likely change with future kernel + * versions, but there isn’t a more deterministic synchronisation point that + * I can think of to use instead. */ + usleep (330); + + if (data->registration_id > 0) + g_assert_true (g_dbus_connection_unregister_object (data->connection, data->registration_id)); + + if (data->subtree_registration_id > 0) + g_assert_true (g_dbus_connection_unregister_subtree (data->connection, data->subtree_registration_id)); + + return NULL; +} + +static void +async_result_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + + *result_out = g_object_ref (result); + g_main_context_wakeup (NULL); +} + +/* Returns %TRUE if this iteration resolved the race with the unregistration + * first, %FALSE if the call handler was invoked first. */ +static gboolean +test_threaded_unregistration_iteration (gboolean subtree) +{ + ThreadedUnregistrationData data = { NULL, 0, 0 }; + ObjectRegistrationData object_registration_data = { 0, 0, 2 }; + GError *local_error = NULL; + GThread *unregister_thread = NULL; + const gchar *object_path; + GVariant *value = NULL; + const gchar *value_str; + GAsyncResult *call_result = NULL; + gboolean unregistration_was_first; + + data.connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (data.connection); + + /* Register an object or a subtree */ + if (!subtree) + { + data.registration_id = g_dbus_connection_register_object (data.connection, + "/foo/boss", + (GDBusInterfaceInfo *) &foo_interface_info, + &foo_vtable, + &object_registration_data, + on_object_unregistered, + &local_error); + g_assert_no_error (local_error); + g_assert_cmpint (data.registration_id, >, 0); + + object_path = "/foo/boss"; + } + else + { + data.subtree_registration_id = g_dbus_connection_register_subtree (data.connection, + "/foo/boss/executives", + &subtree_vtable, + G_DBUS_SUBTREE_FLAGS_NONE, + &object_registration_data, + on_subtree_unregistered, + &local_error); + g_assert_no_error (local_error); + g_assert_cmpint (data.subtree_registration_id, >, 0); + + object_path = "/foo/boss/executives/vp0"; + } + + /* Allow the registrations to go through. */ + g_main_context_iteration (NULL, FALSE); + + /* Spawn a thread to unregister the object/subtree. This will race with + * the call we subsequently make. */ + unregister_thread = g_thread_new ("unregister-object", + unregister_thread_cb, &data); + + /* Call a method on the object (or an object in the subtree). The callback + * will be invoked in this main context. */ + g_dbus_connection_call (data.connection, + g_dbus_connection_get_unique_name (data.connection), + object_path, + "org.example.Foo", + "Method1", + g_variant_new ("(s)", "winwinwin"), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + async_result_cb, + &call_result); + + while (call_result == NULL) + g_main_context_iteration (NULL, TRUE); + + value = g_dbus_connection_call_finish (data.connection, call_result, &local_error); + + /* The result of the method could either be an error (that the object doesn’t + * exist) or a valid result, depending on how the thread was scheduled + * relative to the call. */ + unregistration_was_first = (value == NULL); + if (value != NULL) + { + g_assert_no_error (local_error); + g_assert_true (g_variant_is_of_type (value, G_VARIANT_TYPE ("(s)"))); + g_variant_get (value, "(&s)", &value_str); + g_assert_cmpstr (value_str, ==, "You passed the string 'winwinwin'. Jolly good!"); + } + else + { + g_assert_null (value); + g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD); + } + + g_clear_pointer (&value, g_variant_unref); + g_clear_error (&local_error); + + /* Tidy up. */ + g_thread_join (g_steal_pointer (&unregister_thread)); + + g_clear_object (&call_result); + g_clear_object (&data.connection); + + return unregistration_was_first; +} + +static void +test_threaded_unregistration (gconstpointer test_data) +{ + gboolean subtree = GPOINTER_TO_INT (test_data); + guint i; + guint n_iterations_unregistration_first = 0; + guint n_iterations_call_first = 0; + + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2400"); + g_test_summary ("Test that object/subtree unregistration from one thread " + "doesn’t cause problems when racing with method callbacks " + "in another thread for that object or subtree"); + + /* Run iterations of the test until it’s likely we’ve hit the race. Limit the + * number of iterations so the test doesn’t run forever if not. The choice of + * 100 is arbitrary. */ + for (i = 0; i < 1000 && (n_iterations_unregistration_first < 100 || n_iterations_call_first < 100); i++) + { + if (test_threaded_unregistration_iteration (subtree)) + n_iterations_unregistration_first++; + else + n_iterations_call_first++; + } + + /* If the condition below is met, we probably failed to reproduce the race. + * Don’t fail the test, though, as we can’t always control whether we hit the + * race, and spurious test failures are annoying. */ + if (n_iterations_unregistration_first < 100 || + n_iterations_call_first < 100) + g_test_skip_printf ("Failed to reproduce race (%u iterations with unregistration first, %u with call first); skipping test", + n_iterations_unregistration_first, n_iterations_call_first); +} + /* ---------------------------------------------------------------------------------------------------- */ int @@ -1809,6 +1987,8 @@ main (int argc, g_test_add_func ("/gdbus/object-registration-with-closures", test_object_registration_with_closures); g_test_add_func ("/gdbus/registered-interfaces", test_registered_interfaces); g_test_add_func ("/gdbus/async-properties", test_async_properties); + g_test_add_data_func ("/gdbus/threaded-unregistration/object", GINT_TO_POINTER (FALSE), test_threaded_unregistration); + g_test_add_data_func ("/gdbus/threaded-unregistration/subtree", GINT_TO_POINTER (TRUE), test_threaded_unregistration); /* TODO: check that we spit out correct introspection data */ /* TODO: check that registering a whole subtree works */ diff --git a/gio/tests/gdbus-serialization.c b/gio/tests/gdbus-serialization.c index a3b03c5e0..7cc46a4ae 100644 --- a/gio/tests/gdbus-serialization.c +++ b/gio/tests/gdbus-serialization.c @@ -1472,6 +1472,149 @@ test_message_parse_deep_body_nesting (void) /* ---------------------------------------------------------------------------------------------------- */ +static void +test_message_parse_truncated (void) +{ + GDBusMessage *message = NULL; + GDBusMessage *message2 = NULL; + GVariantBuilder builder; + guchar *blob = NULL; + gsize size = 0; + GError *error = NULL; + + g_test_summary ("Test that truncated messages are properly rejected."); + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2528"); + + message = g_dbus_message_new (); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("(asbynqiuxtd)")); + g_variant_builder_open (&builder, G_VARIANT_TYPE ("as")); + g_variant_builder_add (&builder, "s", "fourtytwo"); + g_variant_builder_close (&builder); + g_variant_builder_add (&builder, "b", TRUE); + g_variant_builder_add (&builder, "y", 42); + g_variant_builder_add (&builder, "n", 42); + g_variant_builder_add (&builder, "q", 42); + g_variant_builder_add (&builder, "i", 42); + g_variant_builder_add (&builder, "u", 42); + g_variant_builder_add (&builder, "x", 42); + g_variant_builder_add (&builder, "t", 42); + g_variant_builder_add (&builder, "d", (gdouble) 42); + + g_dbus_message_set_message_type (message, G_DBUS_MESSAGE_TYPE_METHOD_CALL); + g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, + g_variant_new_object_path ("/foo/bar")); + g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, + g_variant_new_string ("Member")); + g_dbus_message_set_body (message, g_variant_builder_end (&builder)); + + blob = g_dbus_message_to_blob (message, &size, G_DBUS_CAPABILITY_FLAGS_NONE, &error); + g_assert_no_error (error); + + g_clear_object (&message); + + /* Try parsing all possible prefixes of the full @blob. */ + for (gsize i = 0; i < size; i++) + { + message2 = g_dbus_message_new_from_blob (blob, i, G_DBUS_CAPABILITY_FLAGS_NONE, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_null (message2); + g_clear_error (&error); + } + + message2 = g_dbus_message_new_from_blob (blob, size, G_DBUS_CAPABILITY_FLAGS_NONE, &error); + g_assert_no_error (error); + g_assert_true (G_IS_DBUS_MESSAGE (message2)); + g_clear_object (&message2); + + g_free (blob); +} + +static void +test_message_parse_empty_structure (void) +{ + const guint8 data[] = + { + 'l', /* little-endian byte order */ + 0x02, /* message type (method return) */ + 0x00, /* message flags (none) */ + 0x01, /* major protocol version */ + 0x08, 0x00, 0x00, 0x00, /* body length (in bytes) */ + 0x00, 0x00, 0x00, 0x00, /* message serial */ + /* a{yv} of header fields */ + 0x20, 0x00, 0x00, 0x00, /* array length (in bytes), must be a multiple of 8 */ + 0x01, /* array key (PATH) */ + 0x01, /* signature length */ + 'o', /* type (OBJECT_PATH) */ + 0x00, /* nul terminator */ + 0x05, 0x00, 0x00, 0x00, /* length 5 */ + '/', 'p', 'a', 't', 'h', 0x00, 0x00, 0x00, /* string '/path' and padding */ + 0x03, /* array key (MEMBER) */ + 0x01, /* signature length */ + 's', /* type (STRING) */ + 0x00, /* nul terminator */ + 0x06, 0x00, 0x00, 0x00, /* length 6 */ + 'M', 'e', 'm', 'b', 'e', 'r', 0x00, 0x00, /* string 'Member' and padding */ + 0x08, /* array key (SIGNATURE) */ + 0x01, /* signature length */ + 'g', /* type (SIGNATURE) */ + 0x00, /* nul terminator */ + 0x03, /* length 3 */ + 'a', '(', ')', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* type 'a()' and padding */ + 0x08, 0x00, 0x00, 0x00, /* array length: 4 bytes */ + 0x00, 0x00, 0x00, 0x00, /* padding to 8 bytes */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* array data */ + 0x00 + }; + gsize size = sizeof (data); + GDBusMessage *message = NULL; + GError *local_error = NULL; + + g_test_summary ("Test that empty structures are rejected when parsing."); + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2557"); + + message = g_dbus_message_new_from_blob ((guchar *) data, size, + G_DBUS_CAPABILITY_FLAGS_NONE, + &local_error); + g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_cmpstr (local_error->message, ==, "Empty structures (tuples) are not allowed in D-Bus"); + g_assert_null (message); + + g_clear_error (&local_error); +} + +static void +test_message_serialize_empty_structure (void) +{ + GDBusMessage *message; + GVariantBuilder builder; + gsize size = 0; + GError *local_error = NULL; + + g_test_summary ("Test that empty structures are rejected when serializing."); + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2557"); + + message = g_dbus_message_new (); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a())")); + g_variant_builder_open (&builder, G_VARIANT_TYPE ("a()")); + g_variant_builder_add (&builder, "()"); + g_variant_builder_close (&builder); + g_dbus_message_set_message_type (message, G_DBUS_MESSAGE_TYPE_METHOD_CALL); + g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, + g_variant_new_object_path ("/path")); + g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, + g_variant_new_string ("Member")); + g_dbus_message_set_body (message, g_variant_builder_end (&builder)); + + g_dbus_message_to_blob (message, &size, G_DBUS_CAPABILITY_FLAGS_NONE, &local_error); + g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_cmpstr (local_error->message, ==, "Empty structures (tuples) are not allowed in D-Bus"); + + g_clear_error (&local_error); + g_clear_object (&message); +} + +/* ---------------------------------------------------------------------------------------------------- */ + int main (int argc, char *argv[]) @@ -1491,6 +1634,8 @@ main (int argc, test_message_serialize_header_checks); g_test_add_func ("/gdbus/message-serialize/double-array", test_message_serialize_double_array); + g_test_add_func ("/gdbus/message-serialize/empty-structure", + test_message_serialize_empty_structure); g_test_add_func ("/gdbus/message-parse/empty-arrays-of-arrays", test_message_parse_empty_arrays_of_arrays); @@ -1506,6 +1651,10 @@ main (int argc, test_message_parse_deep_header_nesting); g_test_add_func ("/gdbus/message-parse/deep-body-nesting", test_message_parse_deep_body_nesting); + g_test_add_func ("/gdbus/message-parse/truncated", + test_message_parse_truncated); + g_test_add_func ("/gdbus/message-parse/empty-structure", + test_message_parse_empty_structure); return g_test_run(); } diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index 42eeccd7a..dad1623b7 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -56,6 +56,15 @@ check_and_free (GVariant *value, g_variant_unref (value); } +/* Wrapper around g_assert_cmpstr() which gets a setting from a #GSettings + * using g_settings_get(). */ +#define settings_assert_cmpstr(settings, key, op, expected_value) G_STMT_START { \ + gchar *__str; \ + g_settings_get ((settings), (key), "s", &__str); \ + g_assert_cmpstr (__str, op, (expected_value)); \ + g_free (__str); \ +} G_STMT_END + /* Just to get warmed up: Read and set a string, and * verify that can read the changed string back @@ -88,15 +97,10 @@ test_basic (void) g_object_unref (b); g_free (path); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "Hello, earthlings"); - g_free (str); + settings_assert_cmpstr (settings, "greeting", ==, "Hello, earthlings"); g_settings_set (settings, "greeting", "s", "goodbye world"); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "goodbye world"); - g_free (str); - str = NULL; + settings_assert_cmpstr (settings, "greeting", ==, "goodbye world"); if (!backend_set && g_test_undefined ()) { @@ -110,10 +114,7 @@ test_basic (void) g_object_unref (tmp_settings); } - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "goodbye world"); - g_free (str); - str = NULL; + settings_assert_cmpstr (settings, "greeting", ==, "goodbye world"); g_settings_reset (settings, "greeting"); str = g_settings_get_string (settings, "greeting"); @@ -342,10 +343,7 @@ test_basic_types (void) g_settings_get (settings, "test-double", "d", &d); g_assert_cmpfloat (d, ==, G_MAXDOUBLE); - g_settings_get (settings, "test-string", "s", &str); - g_assert_cmpstr (str, ==, "a string, it seems"); - g_free (str); - str = NULL; + settings_assert_cmpstr (settings, "test-string", ==, "a string, it seems"); g_settings_get (settings, "test-objectpath", "o", &str); g_assert_cmpstr (str, ==, "/a/object/path"); @@ -484,7 +482,6 @@ test_delay_apply (void) { GSettings *settings; GSettings *settings2; - gchar *str; gboolean writable; GVariant *v; const gchar *s; @@ -530,20 +527,14 @@ test_delay_apply (void) writable = g_settings_is_writable (settings, "greeting"); g_assert_true (writable); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "greetings from test_delay_apply"); - g_free (str); - str = NULL; + settings_assert_cmpstr (settings, "greeting", ==, "greetings from test_delay_apply"); v = g_settings_get_user_value (settings, "greeting"); s = g_variant_get_string (v, NULL); g_assert_cmpstr (s, ==, "greetings from test_delay_apply"); g_variant_unref (v); - g_settings_get (settings2, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "top o' the morning"); - g_free (str); - str = NULL; + settings_assert_cmpstr (settings2, "greeting", ==, "top o' the morning"); g_assert_true (g_settings_get_has_unapplied (settings)); g_assert_false (g_settings_get_has_unapplied (settings2)); @@ -556,15 +547,8 @@ test_delay_apply (void) g_assert_false (changed_cb_called); g_assert_true (changed_cb_called2); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "greetings from test_delay_apply"); - g_free (str); - str = NULL; - - g_settings_get (settings2, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "greetings from test_delay_apply"); - g_free (str); - str = NULL; + settings_assert_cmpstr (settings, "greeting", ==, "greetings from test_delay_apply"); + settings_assert_cmpstr (settings2, "greeting", ==, "greetings from test_delay_apply"); g_assert_false (g_settings_get_has_unapplied (settings)); g_assert_false (g_settings_get_has_unapplied (settings2)); @@ -572,9 +556,7 @@ test_delay_apply (void) g_settings_reset (settings, "greeting"); g_settings_apply (settings); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "Hello, earthlings"); - g_free (str); + settings_assert_cmpstr (settings, "greeting", ==, "Hello, earthlings"); g_object_unref (settings2); g_object_unref (settings); @@ -588,30 +570,20 @@ test_delay_revert (void) { GSettings *settings; GSettings *settings2; - gchar *str; settings = g_settings_new ("org.gtk.test"); settings2 = g_settings_new ("org.gtk.test"); g_settings_set (settings2, "greeting", "s", "top o' the morning"); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "top o' the morning"); - g_free (str); + settings_assert_cmpstr (settings, "greeting", ==, "top o' the morning"); g_settings_delay (settings); g_settings_set (settings, "greeting", "s", "greetings from test_delay_revert"); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "greetings from test_delay_revert"); - g_free (str); - str = NULL; - - g_settings_get (settings2, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "top o' the morning"); - g_free (str); - str = NULL; + settings_assert_cmpstr (settings, "greeting", ==, "greetings from test_delay_revert"); + settings_assert_cmpstr (settings2, "greeting", ==, "top o' the morning"); g_assert_true (g_settings_get_has_unapplied (settings)); @@ -619,15 +591,8 @@ test_delay_revert (void) g_assert_false (g_settings_get_has_unapplied (settings)); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "top o' the morning"); - g_free (str); - str = NULL; - - g_settings_get (settings2, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "top o' the morning"); - g_free (str); - str = NULL; + settings_assert_cmpstr (settings, "greeting", ==, "top o' the morning"); + settings_assert_cmpstr (settings2, "greeting", ==, "top o' the morning"); g_object_unref (settings2); g_object_unref (settings); @@ -654,7 +619,7 @@ test_delay_child (void) g_assert_nonnull (child); g_object_get (child, "delay-apply", &delay, NULL); - g_assert_false (delay); + g_assert_true (delay); g_settings_get (child, "test-byte", "y", &byte); g_assert_cmpuint (byte, ==, 36); @@ -665,18 +630,66 @@ test_delay_child (void) g_settings_get (base, "test-byte", "y", &byte); g_assert_cmpuint (byte, ==, 36); + /* apply the child and the changes should be saved */ + g_settings_apply (child); + g_settings_get (base, "test-byte", "y", &byte); + g_assert_cmpuint (byte, ==, 42); + g_object_unref (child); g_object_unref (settings); g_object_unref (base); } static void +test_delay_reset_key (void) +{ + GSettings *direct_settings = NULL, *delayed_settings = NULL; + + g_test_summary ("Test that resetting a key on a delayed settings instance works"); + + delayed_settings = g_settings_new ("org.gtk.test"); + direct_settings = g_settings_new ("org.gtk.test"); + + g_settings_set (direct_settings, "greeting", "s", "ey up"); + + settings_assert_cmpstr (delayed_settings, "greeting", ==, "ey up"); + + /* Set up a delayed settings backend. */ + g_settings_delay (delayed_settings); + + g_settings_set (delayed_settings, "greeting", "s", "how do"); + + settings_assert_cmpstr (delayed_settings, "greeting", ==, "how do"); + settings_assert_cmpstr (direct_settings, "greeting", ==, "ey up"); + + g_assert_true (g_settings_get_has_unapplied (delayed_settings)); + + g_settings_reset (delayed_settings, "greeting"); + + /* There are still unapplied settings, because the reset is resetting to the + * value from the schema, not the value from @direct_settings. */ + g_assert_true (g_settings_get_has_unapplied (delayed_settings)); + + settings_assert_cmpstr (delayed_settings, "greeting", ==, "Hello, earthlings"); + settings_assert_cmpstr (direct_settings, "greeting", ==, "ey up"); + + /* Apply the settings changes (i.e. the reset). */ + g_settings_apply (delayed_settings); + + g_assert_false (g_settings_get_has_unapplied (delayed_settings)); + + settings_assert_cmpstr (delayed_settings, "greeting", ==, "Hello, earthlings"); + settings_assert_cmpstr (direct_settings, "greeting", ==, "Hello, earthlings"); + + g_object_unref (direct_settings); + g_object_unref (delayed_settings); +} + +static void keys_changed_cb (GSettings *settings, const GQuark *keys, gint n_keys) { - gchar *str; - g_assert_cmpint (n_keys, ==, 2); g_assert_true ((keys[0] == g_quark_from_static_string ("greeting") && @@ -684,15 +697,8 @@ keys_changed_cb (GSettings *settings, (keys[1] == g_quark_from_static_string ("greeting") && keys[0] == g_quark_from_static_string ("farewell"))); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "greetings from test_atomic"); - g_free (str); - str = NULL; - - g_settings_get (settings, "farewell", "s", &str); - g_assert_cmpstr (str, ==, "atomic bye-bye"); - g_free (str); - str = NULL; + settings_assert_cmpstr (settings, "greeting", ==, "greetings from test_atomic"); + settings_assert_cmpstr (settings, "farewell", ==, "atomic bye-bye"); } /* Check that delay-applied changes appear atomically. @@ -704,7 +710,6 @@ test_atomic (void) { GSettings *settings; GSettings *settings2; - gchar *str; settings = g_settings_new ("org.gtk.test"); settings2 = g_settings_new ("org.gtk.test"); @@ -724,25 +729,10 @@ test_atomic (void) g_settings_apply (settings); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "greetings from test_atomic"); - g_free (str); - str = NULL; - - g_settings_get (settings, "farewell", "s", &str); - g_assert_cmpstr (str, ==, "atomic bye-bye"); - g_free (str); - str = NULL; - - g_settings_get (settings2, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "greetings from test_atomic"); - g_free (str); - str = NULL; - - g_settings_get (settings2, "farewell", "s", &str); - g_assert_cmpstr (str, ==, "atomic bye-bye"); - g_free (str); - str = NULL; + settings_assert_cmpstr (settings, "greeting", ==, "greetings from test_atomic"); + settings_assert_cmpstr (settings, "farewell", ==, "atomic bye-bye"); + settings_assert_cmpstr (settings2, "greeting", ==, "greetings from test_atomic"); + settings_assert_cmpstr (settings2, "farewell", ==, "atomic bye-bye"); g_object_unref (settings2); g_object_unref (settings); @@ -851,13 +841,7 @@ test_l10n_context (void) setlocale (LC_MESSAGES, "de_DE.UTF-8"); /* Only do the test if translation is actually working... */ if (g_str_equal (dgettext ("test", "\"Unnamed\""), "\"Unbenannt\"")) - { - g_settings_get (settings, "backspace", "s", &str); - - g_assert_cmpstr (str, ==, "Löschen"); - g_free (str); - str = NULL; - } + settings_assert_cmpstr (settings, "backspace", ==, "Löschen"); else g_printerr ("warning: translation is not working... skipping test. "); @@ -2767,14 +2751,10 @@ test_null_backend (void) g_assert_cmpstr (str, ==, "org.gtk.test"); g_free (str); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "Hello, earthlings"); - g_free (str); + settings_assert_cmpstr (settings, "greeting", ==, "Hello, earthlings"); g_settings_set (settings, "greeting", "s", "goodbye world"); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "Hello, earthlings"); - g_free (str); + settings_assert_cmpstr (settings, "greeting", ==, "Hello, earthlings"); writable = g_settings_is_writable (settings, "greeting"); g_assert_false (writable); @@ -2784,9 +2764,7 @@ test_null_backend (void) g_settings_delay (settings); g_settings_set (settings, "greeting", "s", "goodbye world"); g_settings_apply (settings); - g_settings_get (settings, "greeting", "s", &str); - g_assert_cmpstr (str, ==, "Hello, earthlings"); - g_free (str); + settings_assert_cmpstr (settings, "greeting", ==, "Hello, earthlings"); g_object_unref (settings); g_object_unref (backend); @@ -3114,6 +3092,7 @@ main (int argc, char *argv[]) g_test_add_func ("/gsettings/delay-apply", test_delay_apply); g_test_add_func ("/gsettings/delay-revert", test_delay_revert); g_test_add_func ("/gsettings/delay-child", test_delay_child); + g_test_add_func ("/gsettings/delay-reset-key", test_delay_reset_key); g_test_add_func ("/gsettings/atomic", test_atomic); g_test_add_func ("/gsettings/simple-binding", test_simple_binding); diff --git a/gio/tests/meson.build b/gio/tests/meson.build index 5dbfb8e60..b563e8dde 100644 --- a/gio/tests/meson.build +++ b/gio/tests/meson.build @@ -17,7 +17,7 @@ if build_machine.system() == 'linux' libutil_name = 'libutil' libutil = run_command('sh', '-c', '''ldconfig -p | grep -o "[[:space:]]@0@\.so\(\.[0-9]\+\)\?\b"''' - .format(libutil_name)).stdout().strip().split('\n') + .format(libutil_name), check: false).stdout().strip().split('\n') if libutil.length() > 0 message('Found libutil as @0@'.format(libutil[0])) @@ -683,7 +683,14 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() objcopy_supports_add_symbol = false objcopy = find_program('objcopy', required : false) if objcopy.found() - objcopy_supports_add_symbol = run_command(objcopy, '--help').stdout().contains('--add-symbol') + # FIXME: This should be `check: true` because we never really expect + # `objcopy --help` to fail, given that `objcopy` exists. However, it does + # fail on FreeBSD because ELF Tool Chain has + # [a bug](https://sourceforge.net/p/elftoolchain/code/3950/). + # This can be changed back to `check: true` once our CI uses a FreeBSD + # version which includes the fix. + # See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2360#note_1318608 + objcopy_supports_add_symbol = run_command(objcopy, '--help', check: false).stdout().contains('--add-symbol') endif ld = find_program('ld', required : false) |