GNOME Bugzilla – Bug 786508
Implementing Gio.ListModel broken -- return value from do_get_item() seems to get lost
Last modified: 2017-09-25 14:09:48 UTC
The following test case fails: class TestListModel(unittest.TestCase): def test_list_model(self): data = ["Foo", "Bar", "Bazunga"] class MyListItem(GObject.Object): def __init__(self, value): super(MyListItem, self).__init__() self.value = value class MyListModel(GObject.Object, Gio.ListModel): def __init__(self): super(MyListModel, self).__init__() self.items = [MyListItem(s) for s in data] def do_get_n_items(self): return len(self.items) def do_get_item(self, position): print ("Returning %s for %i" % (self.items[position], position) return self.items[position] mylist = MyListModel() for i in range(0, mylist.get_n_items()): item = mylist.get_item(i) print(item) self.assertEqual(item.value, data[i]) I've seen it fail in various ways. Sometimes it segfaults somewhere inside the PyGObject marshalling code. Sometimes an assert fails inside GLib: (app:12933): GLib-GObject-CRITICAL **: g_object_get_qdata: assertion 'G_IS_OBJECT (object)' failed If it makes it all the way back to the Python code without crashing, the assertion there will then fail. Valgrind reports initialized memory access that occurs after the do_get_item() call: test_list_model (test_gio.TestListModel) ... Returning: <test_gio.MyListItem object at 0x60fef58 (test_gio+MyListItem at 0x61d87f0)> for 0 ==13624== Conditional jump or move depends on uninitialised value(s) ==13624== at 0xE917ABE: pygi_arg_gobject_to_py (pygi-object.c:230) ==13624== by 0xE91217D: _invoke_marshal_out_args (pygi-invoke.c:568) ==13624== by 0xE91217D: pygi_invoke_c_callable (pygi-invoke.c:709) ==13624== by 0xE913C97: pygi_function_cache_invoke (pygi-cache.c:863) ==13624== by 0xE908178: _callable_info_call (pygi-info.c:561) ==13624== by 0x4F55643: _PyObject_FastCallDict (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4FC65EC: call_function (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x5003619: _PyEval_EvalFrameDefault (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F5481C: _PyEval_EvalCodeWithName (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F88E80: fast_function (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4FC657D: call_function (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x5003619: _PyEval_EvalFrameDefault (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F54502: _PyEval_EvalCodeWithName (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F55411: _PyFunction_FastCallDict (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F5581D: _PyObject_FastCallDict (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F5F450: _PyObject_Call_Prepend (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F55C3A: PyObject_Call (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x5005044: _PyEval_EvalFrameDefault (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F54502: _PyEval_EvalCodeWithName (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F551BA: _PyFunction_FastCallDict (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F5581D: _PyObject_FastCallDict (in /usr/lib64/libpython3.6m.so.1.0) ==13624== ==13624== Conditional jump or move depends on uninitialised value(s) ==13624== at 0xE914037: pygi_marshal_cleanup_args_to_py_marshal_success (pygi-marshal-cleanup.c:125) ==13624== by 0xE91227F: pygi_invoke_c_callable (pygi-invoke.c:713) ==13624== by 0xE913C97: pygi_function_cache_invoke (pygi-cache.c:863) ==13624== by 0xE908178: _callable_info_call (pygi-info.c:561) ==13624== by 0x4F55643: _PyObject_FastCallDict (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4FC65EC: call_function (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x5003619: _PyEval_EvalFrameDefault (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F5481C: _PyEval_EvalCodeWithName (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F88E80: fast_function (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4FC657D: call_function (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x5003619: _PyEval_EvalFrameDefault (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F54502: _PyEval_EvalCodeWithName (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F55411: _PyFunction_FastCallDict (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F5581D: _PyObject_FastCallDict (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F5F450: _PyObject_Call_Prepend (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F55C3A: PyObject_Call (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x5005044: _PyEval_EvalFrameDefault (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F54502: _PyEval_EvalCodeWithName (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F551BA: _PyFunction_FastCallDict (in /usr/lib64/libpython3.6m.so.1.0) ==13624== by 0x4F5581D: _PyObject_FastCallDict (in /usr/lib64/libpython3.6m.so.1.0) ==13624== The Python do_get_item() function appears to be returning a correct value, but somewhere after that it gets corrupted and causes crashes. I originally noticed this when binding a GtkListBox to a Gio.ListModel that was implemented in Python but the pure Python testcase is triggering exactly the same thing.
This is with commit 20430e87c66b03ac0 of pygobject.git.
I've been poking at this a bit with GDB to see if I can figure out the cause. In _pygi_closure_handle(), `retval` is set correctly after PyObject_CallObject() (here: https://git.gnome.org/browse/pygobject/tree/gi/pygi-closure.c#n579) However in _pygi_closure_set_out_arguments(), cache->return_cache->type_tag is set to GI_TYPE_TAG_VOID and so the return value seems to get ignored (here: https://git.gnome.org/browse/pygobject/tree/gi/pygi-closure.c#n428) In the Gio-2.0.gir file, Gio.ListModel.get_item() is marked as returning a pointer, so there must be some other cause for this ...
I wrote a C testcase to see what the g-ir code is returning. It seems to be returning the return type of ListStore.get_item() as `void`. So this isn't a pygobject but at all, something must be going wrong in gobject-introspection itself.
Created attachment 358869 [details] g-ir testcase showing the return type of ListModel.get_item() as void
Seems the issue here is actually in the introspection data for GIO. g_list_model_get_item() returns `gpointer`, which becomes GI_TYPE_TAG_VOID. This is "fixed" by g_list_model_get_object() which returns a `GObject *` and is thus introspectable. The same fix isn't done for the vfuncs on the GListModel interface though, making it impossible to implement that interface via introspection. It might be possible to fix this with a GListModel->get_object () vfunc that overrides ->get_item ()...
Also see bug 778290
Ah, the patch from #759646 might be a better shot than trying to fix it in Gio. Of course fixing it in Gio would be ideal but I'm not sure how to do that without breaking API or ABI.
It seems we can actually fix this just by tweaking the introspection annotations in Gio. I've opened https://bugzilla.gnome.org/show_bug.cgi?id=787271 with the patch as this bug is getting a bit messy already.
Fixed in master of GLib as part of #787271. Waiting on a new release of GLib and GObject Introspection.