gh-152405 Safer mapping proxy comparisons#152489
Conversation
Essentially: * if `w` is a mapping proxy hen unwrap it. * if `w` uses the dict comparison operator then use that. * otherwise return NOT_IMPLEMENTED (on the basis that it's likely that `w`'s rich comparison can do it through the standard mapping interface.
|
|
| if (PyAnyDict_CheckExact(w) || (PyAnyDict_Check(w) && Py_TYPE(w)->tp_richcompare == PyDict_Type.tp_richcompare)) { | ||
| return PyObject_RichCompare(v->mapping, w, op); | ||
| } | ||
| if (PyFrozenDict_CheckExact(v->mapping)) { |
There was a problem hiding this comment.
This might now work with OrderedDict I think 🤔
Let's wait for tests to report that.
| w = ((mappingproxyobject *)w)->mapping; | ||
| } | ||
|
|
||
| if (PyAnyDict_CheckExact(w) || (PyAnyDict_Check(w) && Py_TYPE(w)->tp_richcompare == PyDict_Type.tp_richcompare)) { |
There was a problem hiding this comment.
You don't need the second PyFrozenDict_CheckExact call:
#define PyAnyDict_CheckExact(ob) \
(PyDict_CheckExact(ob) || PyFrozenDict_CheckExact(ob))There was a problem hiding this comment.
Note the second check is on v->mapping rather than w so I think it serves a purpose if w is a (any)dict subclass with an overridden rich-compare. It's possible that it's not worth the effort though
|
So ordereddict fails. I can easily add another special-case for ordereddict although it does illustrate the point that this changes the behaviour for anything that has a comparison operator that relies on the exact type of what is passed to it. While looking at ordereddict, I notice that it isn't comparable with frozendict. That's probably worth fixing (but separately). |
|
I've pushed an equivalent fix to At this stage I'm happy with:
|
| } | ||
|
|
||
| static PyObject * | ||
| mappingproxy_or(PyObject *left, PyObject *right) |
There was a problem hiding this comment.
I would prefer to keep this PR focused on one specific problem :)
We already have 3 versions, let's no make it even more complicated, please
There was a problem hiding this comment.
Sorry! I promise I'm done changing this for now.
Obviously it can be split off separately if cleaner (but probably not worth it while the approach is still undecided).
|
Both our versions fail this test: import types
from collections.abc import Mapping
class CustomMapping1(Mapping):
def __init__(self, data):
self._data = data
def __getitem__(self, key):
return self._data[key]
def __iter__(self):
return iter(self._data)
def __len__(self):
return len(self._data)
def __contains__(self, item):
return item in self._data
class CustomMapping2(CustomMapping1):
def __eq__(self, other):
return isinstance(other, CustomMapping1) and self._data == other._data
m = types.MappingProxyType(CustomMapping1({'a': 1}))
assert m == CustomMapping2({'a': 1})it passes on |
|
I think that's sort of unavoidable with anything that relies on My changed If you took the other approach of "dicts are the only thing we care about leaking because type dicts can cause a crash so make a copy of them only" then I think that test would work. |
i.e. something like this would fix the crash and I think not change anything else |
|
Yeap, this is exactly what I thought of after your comment here:
I will credit you for this amazing idea! Thanks a lot for your help, it was fun debigging this issue 🤝 I will now close this PR and propose to concentrate on #152483 :) |
Essentially:
wis a mapping proxy then unwrap it.wuses the dict comparison operator then use that.v->mappingis a frozendict then forward that (because it's immutable so safe)w's rich comparison can do it through the standard mapping interface.This is a draft (without tests) because two other people have already proposed possible solutions. I just wanted to propose my C code solution, but my assumption is that (if we want to use it) it'll be stolen and merged with someone else's proposed tests.