Skip to content

gh-152405: Fix crash of rich-comparing MappingProxyType objects#152483

Open
sobolevn wants to merge 9 commits into
python:mainfrom
sobolevn:issue-152405
Open

gh-152405: Fix crash of rich-comparing MappingProxyType objects#152483
sobolevn wants to merge 9 commits into
python:mainfrom
sobolevn:issue-152405

Conversation

@sobolevn

@sobolevn sobolevn commented Jun 28, 2026

Copy link
Copy Markdown
Member

This is an alternative to #152449

Now I send a copy of the dict if it is under a mapping, so it can't be mutated.
Credit for this idea goes to @da-woods in this comment

@da-woods

Copy link
Copy Markdown
Contributor

Would it be worth optimizing the common case where an exact (any?)dict is ultimately being compared with another exact (any?)dict. Because if it's just going to go through the dict comparison operator then it should be safe?

@da-woods

Copy link
Copy Markdown
Contributor

The other variation that I was thinking about (but is untested so I might have missed a detail), is to do exact (any?)dict <-> exact (any?)dict specifically*, and then return not implemented for anything else.

I think the result of that is that it would then fall back to the other classes' comparison operator, which would presumably be able to use the mapping interface of the proxy type to do the comparison.

[*] possibly also with a a check for the comparison operator because the collections.OrderDict just uses the dict comparison operator

@sobolevn

This comment was marked as outdated.

Comment thread Objects/descrobject.c Outdated
@da-woods

Copy link
Copy Markdown
Contributor

Perf results

That does suggest it might be get you a huge amount of benefit. Ah well...

@da-woods

Copy link
Copy Markdown
Contributor

On more comment (then I stop thinking about this): if you're only concerned about the crash and not the leak then the only case you're really worried about is MappingProxy(dict()) == something_with_custom_operator (because that's what lets you change builtin types).

@sobolevn

Copy link
Copy Markdown
Member Author

@da-woods thanks a lot for your help and ideas!

I am not sure that something_with_custom_operator is easily representable in C 🤔
Because mappingproxy can compare a lot of things: dict, frozendict, ordereddict, itself (with all possible combinations), etc.

@da-woods

Copy link
Copy Markdown
Contributor

I am not sure that something_with_custom_operator is easily representable in C

I think it's just "not the dict comparison operator".


I've put an alternative (draft) proposal in #152489 mostly to see if it works and passes everything. It intentionally isn't complete though and is just a quick C code suggestion right now.

Comment thread Objects/descrobject.c
static PyObject *
mappingproxy_or(PyObject *left, PyObject *right)
{
if (PyObject_TypeCheck(left, &PyDictProxy_Type)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in principle this needs fixing too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can apply the same idea to many other methods. Let's keep this one about the actual rich-compare and the algorithm though :)

@sobolevn

Copy link
Copy Markdown
Member Author

Behaviour change:

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})

This code passes on main and fails with my change on AssertionError.

@sobolevn

sobolevn commented Jun 28, 2026

Copy link
Copy Markdown
Member Author

@da-woods please, check my new approach :)
It passes all tests, does not crash, and does not have any behavior changes.

@da-woods

Copy link
Copy Markdown
Contributor

Yes - I believe that will work. It may be worth special casing not making the copy when you know the right-hand side is another exact anydict. But that's a minor implementation detail.

If I was starting from scratch with no existing tests or behaviour then I think I'd prefer my approach. But obviously Python is very much not starting from scratch.

So yeah - I'd agree that this fixes the important problem in the least modifying way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants