[PyQt] QModelIndex.internalPointer Bug

Phil Thompson phil at riverbankcomputing.com
Sat Jun 10 14:04:51 BST 2017


Martin,

I like this but I need to think about it a bit more. The weak value dict and the set would have to be per-model (so they have a chance of being garbage collected).

I may go as far as raising exceptions...

- if a value passed to createIndex() is not an int and cannot be weak ref'ed
- if internalId() is called on an index with a non-int value
- if internalPointer() is called on an index with an in value

Phil

On 9 Jun 2017, at 9:53 am, Martin Teichmann <martin.teichmann at gmail.com> wrote:
> 
> 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