Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-gobject for openSUSE:Factory checked in at 2026-05-13 17:19:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-gobject (Old) and /work/SRC/openSUSE:Factory/.python-gobject.new.1966 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-gobject" Wed May 13 17:19:32 2026 rev:111 rq:1352553 version:3.56.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-gobject/python-gobject.changes 2026-03-28 20:14:30.280279566 +0100 +++ /work/SRC/openSUSE:Factory/.python-gobject.new.1966/python-gobject.changes 2026-05-13 17:20:43.654755014 +0200 @@ -1,0 +2,9 @@ +Sat May 9 08:38:33 UTC 2026 - Bjørn Lie <[email protected]> + +- Update to version 3.56.3: + + Fix crash when user_data is defined before callback + + Add missing msg argument to asyncio cancel() + + Fix potential buffer overflow errors + + Fix memory leak when initializing GTK templates + +------------------------------------------------------------------- Old: ---- pygobject-3.56.2.tar.xz New: ---- pygobject-3.56.3.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-gobject.spec ++++++ --- /var/tmp/diff_new_pack.G1R32w/_old 2026-05-13 17:20:46.266862928 +0200 +++ /var/tmp/diff_new_pack.G1R32w/_new 2026-05-13 17:20:46.290863920 +0200 @@ -34,7 +34,7 @@ %define libffi_version 3.0 %{?sle15_python_module_pythons} Name: python-gobject -Version: 3.56.2 +Version: 3.56.3 Release: 0 Summary: Python bindings for GObject License: LGPL-2.1-or-later ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.G1R32w/_old 2026-05-13 17:20:46.674879785 +0200 +++ /var/tmp/diff_new_pack.G1R32w/_new 2026-05-13 17:20:46.714881437 +0200 @@ -1,6 +1,6 @@ -mtime: 1774467603 -commit: fb6a735bad0d053bb900ee900197b1eee68b6b29f630026f3197bbf2de263638 +mtime: 1778315976 +commit: b6c66d026495741570deda1c8e878c855b4e89e18a3ad020cfff07ae7e77be81 url: https://src.opensuse.org/GNOME/python-gobject -revision: fb6a735bad0d053bb900ee900197b1eee68b6b29f630026f3197bbf2de263638 +revision: b6c66d026495741570deda1c8e878c855b4e89e18a3ad020cfff07ae7e77be81 projectscmsync: https://src.opensuse.org/GNOME/_ObsPrj ++++++ _service ++++++ --- /var/tmp/diff_new_pack.G1R32w/_old 2026-05-13 17:20:46.890888709 +0200 +++ /var/tmp/diff_new_pack.G1R32w/_new 2026-05-13 17:20:46.926890196 +0200 @@ -3,7 +3,7 @@ <service name="obs_scm" mode="manual"> <param name="scm">git</param> <param name="url">https://gitlab.gnome.org/GNOME/pygobject.git</param> - <param name="revision">3.56.2</param> + <param name="revision">3.56.3</param> <param name="versionformat">@PARENT_TAG@+@TAG_OFFSET@</param> <param name="versionrewrite-pattern">v?(.*)\+0</param> <param name="versionrewrite-replacement">\1</param> ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-05-09 10:39:36.000000000 +0200 @@ -0,0 +1,5 @@ +*.obscpio +*.osc +_build.* +.pbuild +osc-collab.* ++++++ pygobject-3.56.2.tar.xz -> pygobject-3.56.3.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/NEWS new/pygobject-3.56.3/NEWS --- old/pygobject-3.56.2/NEWS 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/NEWS 2026-05-08 22:10:32.000000000 +0200 @@ -1,3 +1,11 @@ +3.56.3 - 2026-05-08 +------------------- + +* Fix crash when user_data is defined before callback :mr:`554` +* Add missing msg argument to asyncio cancel() :mr:`541` +* Fix potential buffer overflow errors :mr:`540` +* Fix memory leak when initializing GTK templates :mr:`526` + 3.56.2 - 2026-03-25 ------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/gi/pygi-async.c new/pygobject-3.56.3/gi/pygi-async.c --- old/pygobject-3.56.2/gi/pygi-async.c 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/gi/pygi-async.c 2026-05-08 22:10:32.000000000 +0200 @@ -63,8 +63,14 @@ * Cancel the asynchronous operation. */ static PyObject * -async_cancel (PyGIAsync *self) +async_cancel (PyGIAsync *self, PyObject *args, PyObject *kwargs) { + static char *kwlist[] = { "msg", NULL }; + const char *msg = NULL; + + if (!PyArg_ParseTupleAndKeywords (args, kwargs, "|z", kwlist, &msg)) + return NULL; + return PyObject_CallMethod (self->cancellable, "cancel", NULL); } @@ -295,7 +301,7 @@ } static PyMethodDef async_methods[] = { - { "cancel", (PyCFunction)async_cancel, METH_NOARGS }, + { "cancel", (PyCFunction)async_cancel, METH_VARARGS | METH_KEYWORDS }, { "done", (PyCFunction)async_done, METH_NOARGS }, { "result", (PyCFunction)async_result, METH_NOARGS }, { "exception", (PyCFunction)async_exception, METH_NOARGS }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/gi/pygi-cache-array.c new/pygobject-3.56.3/gi/pygi-cache-array.c --- old/pygobject-3.56.2/gi/pygi-cache-array.c 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/gi/pygi-cache-array.c 2026-05-08 22:10:32.000000000 +0200 @@ -551,6 +551,7 @@ PyGIMarshalToPyFunc item_to_py_marshaller; PyGIArgCache *item_arg_cache; GPtrArray *item_cleanups; + gboolean item_size_warning = FALSE; py_obj = PyList_New (array_->len); if (py_obj == NULL) goto err; @@ -564,7 +565,7 @@ item_size = g_array_get_element_size (array_); for (i = 0; i < array_->len; i++) { - GIArgument item_arg; + GIArgument item_arg = PYGI_ARG_INIT; PyObject *py_item; gpointer item_cleanup_data = NULL; @@ -577,8 +578,25 @@ g_ptr_array_index ((GPtrArray *)array_, i); } else if (item_arg_cache->is_pointer) { - item_arg.v_pointer = g_array_index (array_, gpointer, i); - + if (item_size == sizeof (gpointer)) { + item_arg.v_pointer = + g_array_index (array_, gpointer, i); + } else { + // TODO: this warning needs to be actionable + // I do not have enough context to show which field/argument is in error, maybe + // allow to raise errors for those cases (e.g. an option that can be set in unit tests) + if (!item_size_warning) { + g_warning ( + "Array type is assumed to be a pointer, but " + "item size is %" G_GSIZE_FORMAT " bytes", + item_size); + item_size_warning = TRUE; + } + + // For now, copy the right number of bytes + memcpy (&item_arg, array_->data + i * item_size, + item_size); + } } else if (item_arg_cache->type_tag == GI_TYPE_TAG_INTERFACE) { PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)item_arg_cache; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/gi/pygi-cache-closure.c new/pygobject-3.56.3/gi/pygi-cache-closure.c --- old/pygobject-3.56.2/gi/pygi-cache-closure.c 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/gi/pygi-cache-closure.c 2026-05-08 22:10:32.000000000 +0200 @@ -270,6 +270,26 @@ PyGIArgCache *user_data_arg_cache = pygi_arg_cache_alloc (); user_data_arg_cache->meta_type = PYGI_META_ARG_TYPE_CHILD_WITH_PYARG; user_data_arg_cache->direction = direction; + + PyGIArgCache *existing = _pygi_callable_cache_get_arg ( + callable_cache, callback_cache->user_data_index); + if (existing != NULL) { + /* user_data preceded the callback in the arg list and was already + * built as a PARENT by the main loop. Reuse its py_arg_index so + * we don't leave a hole in the [0..n_py_args) numbering. + */ + + /* user_data should never be (out); if it ever is, we'd also need to + * remove `existing` from `callable_cache->to_py_args` and decrememnt + * `n_to_py_args` before freeing it. */ + g_assert (!(existing->direction & PYGI_DIRECTION_TO_PYTHON)); + + user_data_arg_cache->py_arg_index = existing->py_arg_index; + user_data_arg_cache->c_arg_index = existing->c_arg_index; + user_data_arg_cache->type_tag = existing->type_tag; + pygi_arg_cache_free (existing); + } + _pygi_callable_cache_set_arg (callable_cache, callback_cache->user_data_index, user_data_arg_cache); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/gi/pygi-cache.h new/pygobject-3.56.3/gi/pygi-cache.h --- old/pygobject-3.56.2/gi/pygi-cache.h 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/gi/pygi-cache.h 2026-05-08 22:10:32.000000000 +0200 @@ -287,6 +287,7 @@ _pygi_callable_cache_set_arg (PyGICallableCache *cache, guint index, PyGIArgCache *arg_cache) { + g_assert (index < cache->args_cache->len); cache->args_cache->pdata[index] = arg_cache; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/gi/pygobject-class.c new/pygobject-3.56.3/gi/pygobject-class.c --- old/pygobject-3.56.2/gi/pygobject-class.c 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/gi/pygobject-class.c 2026-05-08 22:10:32.000000000 +0200 @@ -768,12 +768,20 @@ if (is_final_subclass && PyObject_HasAttrString ((PyObject *)Py_TYPE (wrapper), "__dontuse_ginstance_init__")) { + gboolean was_floating = g_object_is_floating (object); + g_object_ref_sink (object); + result = PyObject_CallMethod (wrapper, "__dontuse_ginstance_init__", NULL); if (result == NULL) PyErr_Print (); else Py_DECREF (result); + + if (was_floating) + g_object_force_floating (object); + else + g_object_unref (object); } if (needs_init) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/meson.build new/pygobject-3.56.3/meson.build --- old/pygobject-3.56.2/meson.build 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/meson.build 2026-05-08 22:10:32.000000000 +0200 @@ -1,7 +1,7 @@ project( 'pygobject', 'c', - version : '3.56.2', + version : '3.56.3', meson_version : '>= 0.64.0', default_options : [ 'warning_level=1', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/subprojects/gobject-introspection-tests.wrap new/pygobject-3.56.3/subprojects/gobject-introspection-tests.wrap --- old/pygobject-3.56.2/subprojects/gobject-introspection-tests.wrap 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/subprojects/gobject-introspection-tests.wrap 2026-05-08 22:10:32.000000000 +0200 @@ -2,5 +2,5 @@ directory=gobject-introspection-tests url=https://gitlab.gnome.org/GNOME/gobject-introspection-tests.git [email protected]:GNOME/gobject-introspection-tests.git -revision=5e961ff83039340d104f57dbb1f9c3a9f55f5029 +revision=53e6bc978d5011f22d0a27cca49a94b19816ca7d depth=1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/tests/conftest.py new/pygobject-3.56.3/tests/conftest.py --- old/pygobject-3.56.2/tests/conftest.py 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/tests/conftest.py 2026-05-08 22:10:32.000000000 +0200 @@ -88,12 +88,8 @@ # force untranslated messages, as we check for them in some tests os.environ["LC_MESSAGES"] = "C" - os.environ["G_DEBUG"] = "fatal-warnings fatal-criticals" - if sys.platform == "darwin" or os.name == "nt": - # gtk 3.22 has warnings and ciriticals on OS X, ignore for now. - # On Windows glib will create an error dialog which will block tests - # so it's never a good idea there to make things fatal. - os.environ["G_DEBUG"] = "" + if os.name != "nt": + os.environ["G_DEBUG"] = "fatal-warnings fatal-criticals" # First add test directory, since we have a gi package there tests_srcdir = os.path.abspath(os.path.dirname(__file__)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/tests/test_async.py new/pygobject-3.56.3/tests/test_async.py --- old/pygobject-3.56.2/tests/test_async.py 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/tests/test_async.py 2026-05-08 22:10:32.000000000 +0200 @@ -1,3 +1,4 @@ +import sys import pytest import platform import unittest @@ -76,6 +77,86 @@ self.loop.run_until_complete(run()) + def test_cancel_async_task(self): + # Regression test for https://gitlab.gnome.org/GNOME/pygobject/-/issues/755 + async def _long_task(proc): + await proc.wait_async() + + async def run(): + nonlocal self + + # Create a process which waits for input on stdin, without ever + # actually providing any such input. This process won't exit unless + # we explicitly kill it. + flags = ( + Gio.SubprocessFlags.STDOUT_SILENCE + | Gio.SubprocessFlags.STDERR_SILENCE + | Gio.SubprocessFlags.STDIN_PIPE + ) + proc = Gio.Subprocess.new([sys.executable], flags) + + try: + # Create a task which waits for said process to exit; but since + # the process won't actually do so we've created a task which + # indefinitely awaits on a Gio cancellable. + res = asyncio.create_task(_long_task(proc)) + # Briefly yield control to make sure the task we just created + # starts running on the loop and polls on a Gio task. + await asyncio.sleep(0.1) + # Now cancel the task while it's awaiting on a Gio task; this + # will now hit the async_cancel implementation on the C side. + # + # Also, explicitly assert that the task was really cancelled + # (.cancel() returns True), and hasn't already finished by this + # time (.cancel() returns False). This makes sure that we're + # really testing the cancellation here. + assert res.cancel(), "Test invalid, task already finished" + finally: + proc.force_exit() + + self.loop.run_until_complete(run()) + + def test_async_task_cancellation(self): + """Cancellation works on wrapping task.""" + f = Gio.file_new_for_path("./") + + async def run(): + nonlocal self, task + + # Queue cancellation of ourselves with default idle priority + GLib.idle_add(lambda: task.cancel("custom cancellation message") and False) + + cancel = Gio.Cancellable() + + try: + # Start low priority task which will be cancelled (lower than default idle) + async_call = f.enumerate_children_async( + "standard::*", 0, GLib.PRIORITY_LOW, cancel + ) + await async_call + + self.fail("await did not raise!") + except asyncio.CancelledError: + self.assertTrue(cancel.is_cancelled()) + + gio_exception = async_call.exception() + self.assertIsInstance(gio_exception, GLib.GError) + self.assertTrue( + gio_exception.matches( + Gio.io_error_quark(), Gio.IOErrorEnum.CANCELLED + ) + ) + + # re-raise cancellation + raise + + task = self.loop.create_task(run()) + with self.assertRaisesRegex( + asyncio.CancelledError, + "" if sys.version_info < (3, 11) else "custom cancellation message", + ): + self.loop.run_until_complete(task) + def test_not_completed(self): """Querying an uncompleted task raises exceptions.""" f = Gio.file_new_for_path("./") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/tests/test_gi.py new/pygobject-3.56.3/tests/test_gi.py --- old/pygobject-3.56.2/tests/test_gi.py 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/tests/test_gi.py 2026-05-08 22:10:32.000000000 +0200 @@ -20,7 +20,7 @@ from gi.repository import GIMarshallingTests import pytest -from .helper import capture_exceptions, capture_output +from .helper import capture_exceptions, capture_glib_warnings, capture_output import contextlib @@ -2574,6 +2574,12 @@ del in_struct del out_struct + def test_pointer_array_struct_with_guint8(self): + out_struct = GIMarshallingTests.PointerArrayStruct.with_uint8_array() + + with capture_glib_warnings(allow_warnings=True): + assert out_struct.array == list(map(ord, "0123456789")) + def test_struct_field_assignment(self): struct = GIMarshallingTests.BoxedStruct() @@ -3107,6 +3113,42 @@ GIMarshallingTests.callback_owned_boxed(nop_callback, None) self.assertEqual(self.box.long_, 1) + def test_callback_user_data_after_callback(self): + def callback(*args): + self.callback_args = args + + GIMarshallingTests.callback_user_data_after_callback(1, 2, callback) + self.assertEqual(self.callback_args, (1, 2)) + + GIMarshallingTests.callback_user_data_after_callback(3, 4, callback, "testdata") + self.assertEqual(self.callback_args, (3, 4, "testdata")) + + GIMarshallingTests.callback_user_data_after_callback( + 5, 6, callback, "more testdata", "even more testdata" + ) + self.assertEqual( + self.callback_args, (5, 6, "more testdata", "even more testdata") + ) + + def test_callback_user_data_before_callback(self): + def callback(*args): + self.callback_args = args + + GIMarshallingTests.callback_user_data_before_callback(1, 2, None, callback) + self.assertEqual(self.callback_args, (1, 2, None)) + + GIMarshallingTests.callback_user_data_before_callback( + 3, 4, "testdata", callback + ) + self.assertEqual(self.callback_args, (3, 4, "testdata")) + + GIMarshallingTests.callback_user_data_before_callback( + 5, 6, ("more testdata", "even more testdata"), callback + ) + self.assertEqual( + self.callback_args, (5, 6, ("more testdata", "even more testdata")) + ) + class TestMultiOutputArgs(unittest.TestCase): def test_int_out_out(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pygobject-3.56.2/tests/test_gtk_template.py new/pygobject-3.56.3/tests/test_gtk_template.py --- old/pygobject-3.56.2/tests/test_gtk_template.py 2026-03-25 16:01:00.000000000 +0100 +++ new/pygobject-3.56.3/tests/test_gtk_template.py 2026-05-08 22:10:32.000000000 +0200 @@ -1,5 +1,6 @@ -import tempfile +import gc import os +import tempfile import weakref import pytest @@ -782,3 +783,31 @@ assert "CustomBox -> GObject finalized" in finalized assert "CustomLabel -> PyObject finalized" in finalized assert "CustomLabel -> GObject finalized" in finalized + + +def test_signal_handler_with_object_does_not_leak_memory(): + """Regression test for a memory leak when using signal handlers in GTK templates. + Note that this example is intentionally minimal, i.e. the handler doesn't even + need to exist on the class. The only requirement is that the `object` is specified. + """ + type_name = new_gtype_name() + xml = f"""\ + <?xml version="1.0" encoding="UTF-8"?> + <interface> + <requires lib="gtk" version="4.0" /> + <template class="{type_name}" parent="GtkWidget"> + <signal name="show" handler="on_show" object="{type_name}" swapped="no" /> + </template> + </interface> + """ + + @Gtk.Template(string=xml) + class MyWidget(Gtk.Widget): + __gtype_name__ = type_name + + weak = MyWidget().weak_ref() + + gc.collect() + gc.collect() # One more for PyPy + + assert weak() is None ++++++ pygobject.obsinfo ++++++ --- /var/tmp/diff_new_pack.G1R32w/_old 2026-05-13 17:20:49.783008190 +0200 +++ /var/tmp/diff_new_pack.G1R32w/_new 2026-05-13 17:20:49.831010174 +0200 @@ -1,5 +1,5 @@ name: pygobject -version: 3.56.2 -mtime: 1774450860 -commit: 3e62df853e7bd3e65abfc887b09616d9803ef048 +version: 3.56.3 +mtime: 1778271032 +commit: 57b42b3423227010946db6fd84aad72c734ddf63
