diff --git a/girepository/gdump.c b/girepository/gdump.c index 883cb5dfb..78f4c3cd3 100644 --- a/girepository/gdump.c +++ b/girepository/gdump.c @@ -33,16 +33,73 @@ #include #include -#include +#include #include +#include #include -static void -escaped_printf (GOutputStream *out, const char *fmt, ...) G_GNUC_PRINTF (2, 3); +/* Analogue of g_output_stream_write_all(). */ +static gboolean +write_all (FILE *out, + const void *buffer, + gsize count, + gsize *bytes_written, + GError **error) +{ + size_t ret; + + ret = fwrite (buffer, 1, count, out); + + if (bytes_written != NULL) + *bytes_written = ret; + + if (ret < count) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Failed to write to file"); + return FALSE; + } + + return TRUE; +} + +/* Analogue of g_data_input_stream_read_line(). */ +static char * +read_line (FILE *input, + size_t *len_out) +{ + GByteArray *buffer = g_byte_array_new (); + const guint8 nul = '\0'; + + while (TRUE) + { + size_t ret; + guint8 byte; + + ret = fread (&byte, 1, 1, input); + if (ret == 0) + break; + + if (byte == '\n') + break; + + g_byte_array_append (buffer, &byte, 1); + } + + g_byte_array_append (buffer, &nul, 1); + + if (len_out != NULL) + *len_out = buffer->len - 1; /* don’t include terminating nul */ + + return (char *) g_byte_array_free (buffer, FALSE); +} static void -escaped_printf (GOutputStream *out, const char *fmt, ...) +escaped_printf (FILE *out, const char *fmt, ...) G_GNUC_PRINTF (2, 3); + +static void +escaped_printf (FILE *out, const char *fmt, ...) { char *str; va_list args; @@ -52,7 +109,7 @@ escaped_printf (GOutputStream *out, const char *fmt, ...) va_start (args, fmt); str = g_markup_vprintf_escaped (fmt, args); - if (!g_output_stream_write_all (out, str, strlen (str), &written, NULL, &error)) + if (!write_all (out, str, strlen (str), &written, &error)) { g_critical ("failed to write to iochannel: %s", error->message); g_clear_error (&error); @@ -63,11 +120,11 @@ escaped_printf (GOutputStream *out, const char *fmt, ...) } static void -goutput_write (GOutputStream *out, const char *str) +goutput_write (FILE *out, const char *str) { gsize written; GError *error = NULL; - if (!g_output_stream_write_all (out, str, strlen (str), &written, NULL, &error)) + if (!write_all (out, str, strlen (str), &written, &error)) { g_critical ("failed to write to iochannel: %s", error->message); g_clear_error (&error); @@ -86,8 +143,8 @@ invoke_get_type (GModule *self, const char *symbol, GError **error) if (!g_module_symbol (self, symbol, (void**)&sym)) { g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, "Failed to find symbol '%s'", symbol); return G_TYPE_INVALID; } @@ -96,8 +153,8 @@ invoke_get_type (GModule *self, const char *symbol, GError **error) if (ret == G_TYPE_INVALID) { g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, "Function '%s' returned G_TYPE_INVALID", symbol); } return ret; @@ -111,8 +168,8 @@ invoke_error_quark (GModule *self, const char *symbol, GError **error) if (!g_module_symbol (self, symbol, (void**)&sym)) { g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, "Failed to find symbol '%s'", symbol); return G_TYPE_INVALID; } @@ -191,10 +248,10 @@ value_to_string (const GValue *value) } static void -dump_properties (GType type, GOutputStream *out) +dump_properties (GType type, FILE *out) { guint i; - guint n_properties; + guint n_properties = 0; GParamSpec **props; if (G_TYPE_FUNDAMENTAL (type) == G_TYPE_OBJECT) @@ -244,7 +301,7 @@ dump_properties (GType type, GOutputStream *out) } static void -dump_signals (GType type, GOutputStream *out) +dump_signals (GType type, FILE *out) { guint i; guint n_sigs; @@ -296,7 +353,7 @@ dump_signals (GType type, GOutputStream *out) } static void -dump_object_type (GType type, const char *symbol, GOutputStream *out) +dump_object_type (GType type, const char *symbol, FILE *out) { guint n_interfaces; guint i; @@ -350,7 +407,7 @@ dump_object_type (GType type, const char *symbol, GOutputStream *out) } static void -dump_interface_type (GType type, const char *symbol, GOutputStream *out) +dump_interface_type (GType type, const char *symbol, FILE *out) { guint n_interfaces; guint i; @@ -383,14 +440,14 @@ dump_interface_type (GType type, const char *symbol, GOutputStream *out) } static void -dump_boxed_type (GType type, const char *symbol, GOutputStream *out) +dump_boxed_type (GType type, const char *symbol, FILE *out) { escaped_printf (out, " \n", g_type_name (type), symbol); } static void -dump_flags_type (GType type, const char *symbol, GOutputStream *out) +dump_flags_type (GType type, const char *symbol, FILE *out) { guint i; GFlagsClass *klass; @@ -410,7 +467,7 @@ dump_flags_type (GType type, const char *symbol, GOutputStream *out) } static void -dump_enum_type (GType type, const char *symbol, GOutputStream *out) +dump_enum_type (GType type, const char *symbol, FILE *out) { guint i; GEnumClass *klass; @@ -430,7 +487,7 @@ dump_enum_type (GType type, const char *symbol, GOutputStream *out) } static void -dump_fundamental_type (GType type, const char *symbol, GOutputStream *out) +dump_fundamental_type (GType type, const char *symbol, FILE *out) { guint n_interfaces; guint i; @@ -484,7 +541,7 @@ dump_fundamental_type (GType type, const char *symbol, GOutputStream *out) } static void -dump_type (GType type, const char *symbol, GOutputStream *out) +dump_type (GType type, const char *symbol, FILE *out) { switch (g_type_fundamental (type)) { @@ -513,7 +570,7 @@ dump_type (GType type, const char *symbol, GOutputStream *out) } static void -dump_error_quark (GQuark quark, const char *symbol, GOutputStream *out) +dump_error_quark (GQuark quark, const char *symbol, FILE *out) { escaped_printf (out, " \n", symbol, g_quark_to_string (quark)); @@ -556,11 +613,8 @@ gi_repository_dump (const char *input_filename, #endif { GHashTable *output_types; - GFile *input_file; - GFile *output_file; - GFileInputStream *input; - GFileOutputStream *output; - GDataInputStream *in; + FILE *input; + FILE *output; GModule *self; gboolean caught_error = FALSE; @@ -568,47 +622,47 @@ gi_repository_dump (const char *input_filename, if (!self) { g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, "failed to open self: %s", g_module_error ()); return FALSE; } - input_file = g_file_new_for_path (input_filename); - output_file = g_file_new_for_path (output_filename); - - input = g_file_read (input_file, NULL, error); - g_object_unref (input_file); - + input = fopen (input_filename, "rb"); if (input == NULL) { - g_object_unref (output_file); + int saved_errno = errno; + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno), + "Failed to open ‘%s’: %s", input_filename, g_strerror (saved_errno)); + + g_module_close (self); + return FALSE; } - output = g_file_replace (output_file, NULL, FALSE, 0, NULL, error); - g_object_unref (output_file); - + output = fopen (output_filename, "wb"); if (output == NULL) { - g_input_stream_close (G_INPUT_STREAM (input), NULL, NULL); - g_object_unref (input); + int saved_errno = errno; + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno), + "Failed to open ‘%s’: %s", output_filename, g_strerror (saved_errno)); + + fclose (input); + g_module_close (self); + return FALSE; } - goutput_write (G_OUTPUT_STREAM (output), "\n"); - goutput_write (G_OUTPUT_STREAM (output), "\n"); + goutput_write (output, "\n"); + goutput_write (output, "\n"); output_types = g_hash_table_new (NULL, NULL); - in = g_data_input_stream_new (G_INPUT_STREAM (input)); - g_object_unref (input); - while (TRUE) { gsize len; - char *line = g_data_input_stream_read_line (in, &len, NULL, NULL); + char *line = read_line (input, &len); const char *function; if (line == NULL || *line == '\0') @@ -639,7 +693,7 @@ gi_repository_dump (const char *input_filename, goto next; g_hash_table_insert (output_types, (gpointer) type, (gpointer) type); - dump_type (type, function, G_OUTPUT_STREAM (output)); + dump_type (type, function, output); } else if (strncmp (line, "error-quark:", strlen ("error-quark:")) == 0) { @@ -655,7 +709,7 @@ gi_repository_dump (const char *input_filename, break; } - dump_error_quark (quark, function, G_OUTPUT_STREAM (output)); + dump_error_quark (quark, function, output); } @@ -665,18 +719,30 @@ gi_repository_dump (const char *input_filename, g_hash_table_destroy (output_types); - goutput_write (G_OUTPUT_STREAM (output), "\n"); + goutput_write (output, "\n"); { /* Avoid overwriting an earlier set error */ - caught_error |= !g_input_stream_close (G_INPUT_STREAM (in), NULL, - caught_error ? NULL : error); - caught_error |= !g_output_stream_close (G_OUTPUT_STREAM (output), NULL, - caught_error ? NULL : error); - } + if (fclose (input) != 0 && !caught_error) + { + int saved_errno = errno; - g_object_unref (in); - g_object_unref (output); + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno), + "Error closing input file ‘%s’: %s", input_filename, + g_strerror (saved_errno)); + caught_error = TRUE; + } + + if (fclose (output) != 0 && !caught_error) + { + int saved_errno = errno; + + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno), + "Error closing output file ‘%s’: %s", output_filename, + g_strerror (saved_errno)); + caught_error = TRUE; + } + } return !caught_error; } diff --git a/girepository/gi-dump-types.c b/girepository/gi-dump-types.c index ca3735665..7e8f95e15 100644 --- a/girepository/gi-dump-types.c +++ b/girepository/gi-dump-types.c @@ -24,31 +24,14 @@ */ #include "gdump.c" -#ifdef G_OS_WIN32 - #include - #include /* For _get_osfhandle() */ - #include -#else - #include -#endif int main (int argc, char **argv) { int i; - GOutputStream *Stdout; GModule *self; -#if defined(G_OS_WIN32) - HANDLE *hnd = (HANDLE) _get_osfhandle (1); - - g_return_val_if_fail (hnd && hnd != INVALID_HANDLE_VALUE, 1); - Stdout = g_win32_output_stream_new (hnd, FALSE); -#else - Stdout = g_unix_output_stream_new (1, FALSE); -#endif - self = g_module_open (NULL, 0); for (i = 1; i < argc; i++) @@ -63,7 +46,7 @@ main (int argc, g_clear_error (&error); } else - dump_type (type, argv[i], Stdout); + dump_type (type, argv[i], stdout); } return 0; diff --git a/girepository/meson.build b/girepository/meson.build index 42eddf6df..05200fb1f 100644 --- a/girepository/meson.build +++ b/girepository/meson.build @@ -197,6 +197,7 @@ libgirepository = shared_library('girepository-2.0', libgirepository_dep = declare_dependency( link_with: libgirepository, dependencies: [libglib_dep, libgobject_dep, libgio_dep, libgmodule_dep], + sources: [gi_visibility_h], include_directories: [girepoinc], ) diff --git a/gobject/gobject.c b/gobject/gobject.c index 07da83f23..cf2f2801d 100644 --- a/gobject/gobject.c +++ b/gobject/gobject.c @@ -403,6 +403,26 @@ _g_object_type_init (void) #endif /* G_ENABLE_DEBUG */ } +/* Initialize the global GParamSpecPool; this function needs to be + * called whenever we access the GParamSpecPool and we cannot guarantee + * that g_object_do_class_init() has been called: for instance, by the + * interface property API. + * + * To avoid yet another global lock, we use atomic pointer checks: the + * first caller of this function will win the race. Any other access to + * the GParamSpecPool is done under its own mutex. + */ +static inline void +g_object_init_pspec_pool (void) +{ + if (G_UNLIKELY (g_atomic_pointer_get (&pspec_pool) == NULL)) + { + GParamSpecPool *pool = g_param_spec_pool_new (TRUE); + if (!g_atomic_pointer_compare_and_exchange (&pspec_pool, NULL, pool)) + g_param_spec_pool_free (pool); + } +} + static void g_object_base_class_init (GObjectClass *class) { @@ -459,7 +479,8 @@ g_object_do_class_init (GObjectClass *class) #ifndef HAVE_OPTIONAL_FLAGS quark_in_construction = g_quark_from_static_string ("GObject-in-construction"); #endif - pspec_pool = g_param_spec_pool_new (TRUE); + + g_object_init_pspec_pool (); class->constructor = g_object_constructor; class->constructed = g_object_constructed; @@ -525,11 +546,13 @@ install_property_internal (GType g_type, { g_param_spec_ref_sink (pspec); + g_object_init_pspec_pool (); + if (g_param_spec_pool_lookup (pspec_pool, pspec->name, g_type, FALSE)) { g_critical ("When installing property: type '%s' already has a property named '%s'", - g_type_name (g_type), - pspec->name); + g_type_name (g_type), + pspec->name); g_param_spec_unref (pspec); return FALSE; } @@ -950,7 +973,9 @@ g_object_interface_find_property (gpointer g_iface, g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_class->g_type), NULL); g_return_val_if_fail (property_name != NULL, NULL); - + + g_object_init_pspec_pool (); + return g_param_spec_pool_lookup (pspec_pool, property_name, iface_class->g_type, @@ -1076,10 +1101,10 @@ g_object_class_list_properties (GObjectClass *class, * Since: 2.4 * * Returns: (array length=n_properties_p) (transfer container): a - * pointer to an array of pointers to #GParamSpec - * structures. The paramspecs are owned by GLib, but the - * array should be freed with g_free() when you are done with - * it. + * pointer to an array of pointers to #GParamSpec + * structures. The paramspecs are owned by GLib, but the + * array should be freed with g_free() when you are done with + * it. */ GParamSpec** g_object_interface_list_properties (gpointer g_iface, @@ -1091,6 +1116,8 @@ g_object_interface_list_properties (gpointer g_iface, g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_class->g_type), NULL); + g_object_init_pspec_pool (); + pspecs = g_param_spec_pool_list (pspec_pool, iface_class->g_type, &n); diff --git a/gobject/gparam.c b/gobject/gparam.c index 311f8c984..1571b34e5 100644 --- a/gobject/gparam.c +++ b/gobject/gparam.c @@ -994,11 +994,32 @@ g_param_spec_pool_new (gboolean type_prefixing) memcpy (&pool->mutex, &init_mutex, sizeof (init_mutex)); pool->type_prefixing = type_prefixing != FALSE; - pool->hash_table = g_hash_table_new (param_spec_pool_hash, param_spec_pool_equals); + pool->hash_table = g_hash_table_new_full (param_spec_pool_hash, + param_spec_pool_equals, + (GDestroyNotify) g_param_spec_unref, + NULL); return pool; } +/** + * g_param_spec_pool_free: + * @pool: (transfer full): a #GParamSpecPool + * + * Frees the resources allocated by a #GParamSpecPool. + * + * Since: 2.80 + */ +void +g_param_spec_pool_free (GParamSpecPool *pool) +{ + g_mutex_lock (&pool->mutex); + g_hash_table_unref (pool->hash_table); + g_mutex_unlock (&pool->mutex); + g_mutex_clear (&pool->mutex); + g_free (pool); +} + /** * g_param_spec_pool_insert: * @pool: a #GParamSpecPool. @@ -1053,9 +1074,7 @@ g_param_spec_pool_remove (GParamSpecPool *pool, if (pool && pspec) { g_mutex_lock (&pool->mutex); - if (g_hash_table_remove (pool->hash_table, pspec)) - g_param_spec_unref (pspec); - else + if (!g_hash_table_remove (pool->hash_table, pspec)) g_critical (G_STRLOC ": attempt to remove unknown pspec '%s' from pool", pspec->name); g_mutex_unlock (&pool->mutex); } diff --git a/gobject/gparam.h b/gobject/gparam.h index 506a9c2cd..89374fec0 100644 --- a/gobject/gparam.h +++ b/gobject/gparam.h @@ -443,7 +443,8 @@ GOBJECT_AVAILABLE_IN_ALL GParamSpec** g_param_spec_pool_list (GParamSpecPool *pool, GType owner_type, guint *n_pspecs_p); - +GOBJECT_AVAILABLE_IN_2_80 +void g_param_spec_pool_free (GParamSpecPool *pool); /* contracts: * diff --git a/gobject/tests/meson.build b/gobject/tests/meson.build index 23f131b5c..713c1d737 100644 --- a/gobject/tests/meson.build +++ b/gobject/tests/meson.build @@ -83,6 +83,7 @@ gobject_tests = { 'binding' : {}, 'bindinggroup' : {}, 'properties' : {}, + 'properties-introspection' : {}, 'reference' : {}, 'flags' : {}, 'value' : {}, diff --git a/gobject/tests/param.c b/gobject/tests/param.c index 08bc80fff..f227e5938 100644 --- a/gobject/tests/param.c +++ b/gobject/tests/param.c @@ -1624,6 +1624,23 @@ test_param_spec_custom (void) g_param_spec_unref (pspec); } +static void +test_param_spec_pool (void) +{ + GParamSpecPool *pool = g_param_spec_pool_new (FALSE); + GParamSpec *pspec = g_param_spec_int ("int", NULL, NULL, -1, 100, -1, G_PARAM_READWRITE); + GParamSpec *check = NULL; + + g_param_spec_pool_insert (pool, g_param_spec_ref_sink (pspec), G_TYPE_OBJECT); + check = g_param_spec_pool_lookup (pool, "int", G_TYPE_OBJECT, FALSE); + g_assert_true (check->owner_type == G_TYPE_OBJECT); + + g_param_spec_pool_remove (pool, pspec); + g_assert_null (g_param_spec_pool_lookup (pool, "int", G_TYPE_OBJECT, FALSE)); + + g_param_spec_pool_free (pool); +} + int main (int argc, char *argv[]) { @@ -1680,6 +1697,7 @@ main (int argc, char *argv[]) g_test_add_func ("/paramspec/variant", test_param_spec_variant); g_test_add_func ("/paramspec/variant/cmp", test_param_spec_variant_cmp); g_test_add_func ("/paramspec/custom", test_param_spec_custom); + g_test_add_func ("/paramspec/pool", test_param_spec_pool); return g_test_run (); } diff --git a/gobject/tests/properties-introspection.c b/gobject/tests/properties-introspection.c new file mode 100644 index 000000000..ac72330c5 --- /dev/null +++ b/gobject/tests/properties-introspection.c @@ -0,0 +1,114 @@ +/* properties-introspection.c: Test the properties introspection API + * + * SPDX-FileCopyrightText: 2023 Emmanuele Bassi + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +/* This test is isolated so we can control the initialization of + * GObjectClass, and the global GParamSpecPool + */ + +#include +#include + +G_DECLARE_INTERFACE (MyTestable, my_testable, MY, TESTABLE, GObject) + +struct _MyTestableInterface +{ + GTypeInterface g_iface; +}; + +G_DEFINE_INTERFACE (MyTestable, my_testable, G_TYPE_OBJECT) + +static void +my_testable_default_init (MyTestableInterface *iface) +{ + g_object_interface_install_property (iface, + g_param_spec_int ("check", NULL, NULL, -1, 10, 0, G_PARAM_READWRITE)); +} + +static void +properties_introspection (void) +{ + g_test_summary ("Verify that introspecting properties on an interface initializes the GParamSpecPool."); + + if (g_test_subprocess ()) + { + gpointer klass = g_type_default_interface_ref (my_testable_get_type ()); + g_assert_nonnull (klass); + + GParamSpec *pspec = g_object_interface_find_property (klass, "check"); + g_assert_nonnull (pspec); + + g_type_default_interface_unref (klass); + return; + } + + g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT); + g_test_trap_assert_passed (); + g_test_trap_assert_stderr (""); +} + +static gpointer +inspect_func (gpointer data) +{ + unsigned int *n_checks = data; /* (atomic) */ + + gpointer klass = NULL; + do + { + klass = g_type_default_interface_ref (my_testable_get_type ()); + } + while (klass == NULL); + + GParamSpec *pspec = NULL; + do + { + pspec = g_object_interface_find_property (klass, "check"); + } + while (pspec == NULL); + + g_type_default_interface_unref (klass); + + g_atomic_int_inc (n_checks); + + return NULL; +} + +#define N_THREADS 10 + +static void +properties_collision (void) +{ + GThread *threads[N_THREADS]; + unsigned int n_checks = 0; /* (atomic) */ + + g_test_summary ("Verify that multiple threads create a single GParamSpecPool."); + + for (unsigned int i = 0; i < N_THREADS; i++) + { + char *t_name = g_strdup_printf ("inspect [%d]", i); + threads[i] = g_thread_new (t_name, inspect_func, &n_checks); + g_assert_nonnull (threads[i]); + g_free (t_name); + } + + while (g_atomic_int_get (&n_checks) != N_THREADS) + g_usleep (50); + + for (unsigned int i = 0; i < N_THREADS; i++) + g_thread_join (threads[i]); +} + +#undef N_THREADS + +int +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/properties/introspection", properties_introspection); + g_test_add_func ("/properties/collision", properties_collision); + + return g_test_run (); +}