After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 786508 - Implementing Gio.ListModel broken -- return value from do_get_item() seems to get lost
Implementing Gio.ListModel broken -- return value from do_get_item() seems to...
Status: RESOLVED FIXED
Product: gobject-introspection
Classification: Platform
Component: general
1.52.x
Other Linux
: Normal normal
: ---
Assigned To: Nobody's working on this now (help wanted and appreciated)
Python bindings maintainers
Depends on:
Blocks:
 
 
Reported: 2017-08-19 11:46 UTC by Sam Thursfield
Modified: 2017-09-25 14:09 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
g-ir testcase showing the return type of ListModel.get_item() as void (926 bytes, text/plain)
2017-08-31 16:34 UTC, Sam Thursfield
Details

Description Sam Thursfield 2017-08-19 11:46:42 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.
Comment 1 Sam Thursfield 2017-08-19 11:47:22 UTC
This is with commit 20430e87c66b03ac0 of pygobject.git.
Comment 2 Sam Thursfield 2017-08-28 13:32:55 UTC
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 ...
Comment 3 Sam Thursfield 2017-08-31 16:32:19 UTC
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.
Comment 4 Sam Thursfield 2017-08-31 16:34:15 UTC
Created attachment 358869 [details]
g-ir testcase showing the return type of ListModel.get_item() as void
Comment 5 Sam Thursfield 2017-09-04 16:59:56 UTC
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 ()...
Comment 6 Christoph Reiter (lazka) 2017-09-04 17:05:26 UTC
Also see bug 778290
Comment 7 Sam Thursfield 2017-09-04 17:20:41 UTC
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.
Comment 8 Sam Thursfield 2017-09-04 22:16:40 UTC
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.
Comment 9 Sam Thursfield 2017-09-25 14:09:48 UTC
Fixed in master of GLib as part of #787271. Waiting on a new release of GLib and GObject Introspection.