From 01b58dea549da7bfb668fc84f0097bcd4c85bd93 Mon Sep 17 00:00:00 2001 From: da-woods Date: Sun, 28 Jun 2026 14:15:00 +0100 Subject: [PATCH 1/7] gh-152405 Safer mapping proxy comparisons 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. --- Objects/descrobject.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 30444b7d6774247..90a383d213cee3e 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1232,11 +1232,21 @@ mappingproxy_traverse(PyObject *self, visitproc visit, void *arg) static PyObject * mappingproxy_richcompare(PyObject *self, PyObject *w, int op) { - mappingproxyobject *v = (mappingproxyobject *)self; if (op == Py_EQ || op == Py_NE) { - return PyObject_RichCompare(v->mapping, w, op); + mappingproxyobject *v = (mappingproxyobject *)self; + if (Py_TYPE(w) == Py_TYPE(self)) { + w = ((mappingproxyobject *)w)->mapping; + } + + 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)) { + // immutable - safe to foward. + return PyObject_RichCompare(v->mapping, w, op); + } } - Py_RETURN_NOTIMPLEMENTED; + Py_RETURN_NOTIMPLEMENTED; // Defer to w's richcompare } static int From f8b63a5dea8e1f720f89c9ad35b0663fbf44976b Mon Sep 17 00:00:00 2001 From: da-woods Date: Sun, 28 Jun 2026 14:27:25 +0100 Subject: [PATCH 2/7] Fix 'w' type check --- Objects/descrobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 90a383d213cee3e..fc53fdb240d89d8 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1234,7 +1234,7 @@ mappingproxy_richcompare(PyObject *self, PyObject *w, int op) { if (op == Py_EQ || op == Py_NE) { mappingproxyobject *v = (mappingproxyobject *)self; - if (Py_TYPE(w) == Py_TYPE(self)) { + if (PyObject_TypeCheck(w, &PyDictProxy_Type)) { w = ((mappingproxyobject *)w)->mapping; } From c1b3d47ef59439456142715fdb1894372ec55f7d Mon Sep 17 00:00:00 2001 From: da-woods Date: Sun, 28 Jun 2026 14:55:04 +0100 Subject: [PATCH 3/7] Orderdict special case too --- Objects/descrobject.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index fc53fdb240d89d8..97d4cedb6d4d443 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1238,7 +1238,12 @@ mappingproxy_richcompare(PyObject *self, PyObject *w, int op) w = ((mappingproxyobject *)w)->mapping; } - if (PyAnyDict_CheckExact(w) || (PyAnyDict_Check(w) && Py_TYPE(w)->tp_richcompare == PyDict_Type.tp_richcompare)) { + if (PyAnyDict_CheckExact(w) || + (PyAnyDict_Check(w) && Py_TYPE(w)->tp_richcompare == PyDict_Type.tp_richcompare)) { + return PyObject_RichCompare(v->mapping, w, op); + } + if (PyODict_CheckExact(w) || + (PyODict_Check(w) && Py_TYPE(w)->tp_richcompare == PyODict_Type.tp_richcompare)) { return PyObject_RichCompare(v->mapping, w, op); } if (PyFrozenDict_CheckExact(v->mapping)) { From 3e2f474c66c73b70875f2040eec23a8c15942112 Mon Sep 17 00:00:00 2001 From: da-woods Date: Sun, 28 Jun 2026 14:57:51 +0100 Subject: [PATCH 4/7] Appease linter --- Objects/descrobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 97d4cedb6d4d443..8c2c35544654a7c 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1251,7 +1251,7 @@ mappingproxy_richcompare(PyObject *self, PyObject *w, int op) return PyObject_RichCompare(v->mapping, w, op); } } - Py_RETURN_NOTIMPLEMENTED; // Defer to w's richcompare + Py_RETURN_NOTIMPLEMENTED; // Defer to w's richcompare } static int From ed448172107af6fbc6bf5fad9e8136b988433012 Mon Sep 17 00:00:00 2001 From: da-woods Date: Sun, 28 Jun 2026 15:00:19 +0100 Subject: [PATCH 5/7] Actually appease linter --- Objects/descrobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 8c2c35544654a7c..4ece2704991d546 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1251,7 +1251,7 @@ mappingproxy_richcompare(PyObject *self, PyObject *w, int op) return PyObject_RichCompare(v->mapping, w, op); } } - Py_RETURN_NOTIMPLEMENTED; // Defer to w's richcompare + Py_RETURN_NOTIMPLEMENTED; // Defer to w's richcompare } static int From 6cdf9a09aed7f7af067810db3f282490c43b66f4 Mon Sep 17 00:00:00 2001 From: da-woods Date: Sun, 28 Jun 2026 15:55:14 +0100 Subject: [PATCH 6/7] Fix mappingproxy "or" too --- Objects/descrobject.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 4ece2704991d546..8cfb50203d9ec65 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1061,16 +1061,33 @@ static PyMappingMethods mappingproxy_as_mapping = { 0, /* mp_ass_subscript */ }; +static int is_known_dict_subclass_or(PyObject *o) { + return (PyAnyDict_CheckExact(o) || PyODict_CheckExact(o) || + (PyAnyDict_Check(o) && Py_TYPE(o)->tp_as_number->nb_or == PyDict_Type.tp_as_number->nb_or)) || + (PyODict_Check(o) && Py_TYPE(o)->tp_as_number->nb_or == PyODict_Type.tp_as_number->nb_or); +} + static PyObject * mappingproxy_or(PyObject *left, PyObject *right) { if (PyObject_TypeCheck(left, &PyDictProxy_Type)) { - left = ((mappingproxyobject*)left)->mapping; - } - if (PyObject_TypeCheck(right, &PyDictProxy_Type)) { - right = ((mappingproxyobject*)right)->mapping; + if (PyObject_TypeCheck(right, &PyDictProxy_Type)) { + right = ((mappingproxyobject*)right)->mapping; + } + PyObject *left_mapping = ((mappingproxyobject*)left)->mapping; + if (is_known_dict_subclass_or(right) || PyFrozenDict_CheckExact(left_mapping)) { + return PyNumber_Or(left_mapping, right); + } + } else { + assert(PyObject_TypeCheck(right, &PyDictProxy_Type)); + PyObject *right_mapping = ((mappingproxyobject*)right)->mapping; + if (is_known_dict_subclass_or(left) || PyFrozenDict_CheckExact(right_mapping)) { + return PyNumber_Or(left, right_mapping); + } } - return PyNumber_Or(left, right); + + // The non-mappingproxy "or" will have to deal with a mappingproxy. + Py_RETURN_NOTIMPLEMENTED; } static PyObject * From d0d0833065b200abf9debd9664d2de78400a060d Mon Sep 17 00:00:00 2001 From: da-woods Date: Sun, 28 Jun 2026 16:05:27 +0100 Subject: [PATCH 7/7] Small formatting cleanup --- Objects/descrobject.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 8cfb50203d9ec65..36e3318d320cee5 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1061,7 +1061,7 @@ static PyMappingMethods mappingproxy_as_mapping = { 0, /* mp_ass_subscript */ }; -static int is_known_dict_subclass_or(PyObject *o) { +static int mappingproxy_is_known_dict_subclass_or(PyObject *o) { return (PyAnyDict_CheckExact(o) || PyODict_CheckExact(o) || (PyAnyDict_Check(o) && Py_TYPE(o)->tp_as_number->nb_or == PyDict_Type.tp_as_number->nb_or)) || (PyODict_Check(o) && Py_TYPE(o)->tp_as_number->nb_or == PyODict_Type.tp_as_number->nb_or); @@ -1075,13 +1075,13 @@ mappingproxy_or(PyObject *left, PyObject *right) right = ((mappingproxyobject*)right)->mapping; } PyObject *left_mapping = ((mappingproxyobject*)left)->mapping; - if (is_known_dict_subclass_or(right) || PyFrozenDict_CheckExact(left_mapping)) { + if (mappingproxy_is_known_dict_subclass_or(right) || PyFrozenDict_CheckExact(left_mapping)) { return PyNumber_Or(left_mapping, right); } } else { assert(PyObject_TypeCheck(right, &PyDictProxy_Type)); PyObject *right_mapping = ((mappingproxyobject*)right)->mapping; - if (is_known_dict_subclass_or(left) || PyFrozenDict_CheckExact(right_mapping)) { + if (mappingproxy_is_known_dict_subclass_or(left) || PyFrozenDict_CheckExact(right_mapping)) { return PyNumber_Or(left, right_mapping); } } @@ -1246,6 +1246,13 @@ mappingproxy_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +mappingproxy_is_known_dict_subclass_richcompare(PyObject *o) { + return (PyAnyDict_CheckExact(o) || PyODict_CheckExact(o) || + (PyAnyDict_Check(o) && Py_TYPE(o)->tp_richcompare == PyDict_Type.tp_richcompare) || + (PyODict_Check(o) && Py_TYPE(o)->tp_richcompare == PyODict_Type.tp_richcompare)); +} + static PyObject * mappingproxy_richcompare(PyObject *self, PyObject *w, int op) { @@ -1254,17 +1261,7 @@ mappingproxy_richcompare(PyObject *self, PyObject *w, int op) if (PyObject_TypeCheck(w, &PyDictProxy_Type)) { w = ((mappingproxyobject *)w)->mapping; } - - if (PyAnyDict_CheckExact(w) || - (PyAnyDict_Check(w) && Py_TYPE(w)->tp_richcompare == PyDict_Type.tp_richcompare)) { - return PyObject_RichCompare(v->mapping, w, op); - } - if (PyODict_CheckExact(w) || - (PyODict_Check(w) && Py_TYPE(w)->tp_richcompare == PyODict_Type.tp_richcompare)) { - return PyObject_RichCompare(v->mapping, w, op); - } - if (PyFrozenDict_CheckExact(v->mapping)) { - // immutable - safe to foward. + if (mappingproxy_is_known_dict_subclass_richcompare(w) || PyFrozenDict_CheckExact(v->mapping)) { return PyObject_RichCompare(v->mapping, w, op); } }