[PyQt] Fwd: QModelIndex.internalPointer Bug

Martin Teichmann martin.teichmann at gmail.com
Fri Jun 9 09:53:49 BST 2017


Hi Andres, Hi Phil, Hi list,

> The behavior of the bug is that python crashes when you create an index with
> a pointer to an instantiated object's attribute, and then try to access that
> pointer using the internalPointer method.

The problem is that this is not what you're doing in your example.
createIndex with an integer as a parameter does not create a reference
to that integer, but actually stores the integer into the model index.
When you get it out with internalPointer it is re-interpreted as a
pointer, in this case to memory address 2, and this gives a segfault.

All of this is a big nightmare. You have to have disciplin in what you
put into a QModelIndex, as upon retrieving it there is no way to tell
what type it originally was. And the really bad thing about it is that
you are punished with a SEGFAULT, not just a Python exception.

It gets even worse: there is no way to tell whether Qt destroyed a
QModelIndex. This kills Python reference counting, as we don't know
when to decrease it. That's why PyQt does not increase the Python
reference counter for an objected entered into createIndex. This also
means that upon retrieving it with internalPointer may crash with a
SEGFAULT if the object referenced to has been collected in the
meantime.

For the longest time, I thought there is no solution to this problem.
But finally I solved it for my code: I store all objects that go into
a WeakValueDictionary, and only hand over the id to Qt. About like
that:

    objects = WeakValueDictionary()

    def createIndex(model, row, col, obj):
        objects[id(obj)] = obj
        return model.createIndex(row, col, id(obj))

    def retrieveObject(index):
        # this will return None if the object has already been collected
        return objects.get(index.internalId())

This works well without SEGFAULT. I think that it would be a good idea
for PyQt to use a model like that internally, as it would stop
programs from segfaulting. It would be nice if this was even the
standard behavior of internalPointer. This would indeed change the API
in a potentially incompatible way. But this incompatibility would only
be that a program doesn't segfault but return a None. The only problem
is that not everything can be put into a WeakValueDictionary. This
could be mitigated by remembering what could not be weak referenced,
in this case the code would still SEGFAULT, for backwards
compatibility...

Written in Python:

    objects = WeakValueDictionary()
    non_weak_ids = set()

    def createIndex(model, row, col, obj):
        if isinstance(obj, int):  # for those who prefer to store ids
            return model.createIndex(row, col, obj):
        try:
            objects[id(obj)] = obj
            return model.createIndex(row, col, id(obj))
        except TypeError:  # weak ref could not be created
            non_weak_ids.add(id(obj))
            return model.createIndex(row, col, obj)

    def internalPointer(index):
        ret = objects.get(index.internalId())
        if ret is None and index.internalId() in non_weak_ids:
            return index.internalPointer()  # this may SEGFAULT!
        return ret

This would be a backwards-compatible implementation.

Greetings

Martin


More information about the PyQt mailing list