FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

bpo-43838: Use a copy to delegate `types.MappingProxyType` operators by brandtbucher · Pull Request #27300 · python/cpython · GitHub

Repository navigation

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .c  (1) .py  (1) .rst  (2) All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
6 changes: 6 additions & 0 deletions Doc/library/types.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,12 @@ Standard names are defined for the following types:
Updated to support the new union (``|``) operator from :pep:`584`, which
simply delegates to the underlying mapping.

.. versionchanged:: 3.10

To avoid exposing the actual proxied object to arbitrary code, union and
rich comparison operations now delegate to a copy of the underlying
mapping instead.

.. describe:: key in proxy

Return ``True`` if the underlying mapping has a key *key*, else
Expand Down
41 changes: 41 additions & 0 deletions Lib/test/test_types.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,47 @@ def test_union(self):
self.assertDictEqual(mapping, {'a': 0, 'b': 1, 'c': 2})
self.assertDictEqual(other, {'c': 3, 'p': 0})

def test_delegated_operators(self):
class MyDict(dict):
def __eq__(self, other):
return super().__eq__(other)
def __or__(self, other):
return super().__or__(other)
def __ror__(self, other):
return super().__ror__(other)
dict_a = {0: "a"}
dict_b = {0: "b"}
mydict_a = MyDict(dict_a)
mydict_b = MyDict(dict_b)
view_dict_a = self.mappingproxy(dict_a)
view_mydict_a = self.mappingproxy(mydict_a)
view_view_dict_a = self.mappingproxy(view_dict_a)
a_mappings = (dict_a, mydict_a)
b_mappings = (dict_b, mydict_b)
a_views = (view_dict_a, view_mydict_a, view_view_dict_a)
for view in a_views:
for mapping in a_mappings + a_views:
self.assertDictEqual(view | mapping, dict_a)
self.assertDictEqual(mapping | view, dict_a)
self.assertEqual(view, mapping)
self.assertEqual(mapping, view)
for mapping in b_mappings:
self.assertDictEqual(view | mapping, dict_b)
self.assertDictEqual(mapping | view, dict_a)
self.assertNotEqual(view, mapping)
self.assertNotEqual(mapping, view)

def test_bpo_43838(self):
mapping = {}
proxy = self.mappingproxy(mapping)
class Sneaky:
def __eq__(self, other):
other['x'] = 42
return None
self.assertIs(proxy == Sneaky(), None)
self.assertDictEqual(mapping, {})
self.assertEqual(proxy, {})


class ClassCreationTests(unittest.TestCase):

Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
To avoid exposing the actual proxied object to arbitrary code, union and
rich comparison operations on :class:`types.MappingProxyType` now delegate
to a copy of the underlying mapping instead.
37 changes: 35 additions & 2 deletions Objects/descrobject.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1032,13 +1032,36 @@ static PyMappingMethods mappingproxy_as_mapping = {
static PyObject *
mappingproxy_or(PyObject *left, PyObject *right)
{
// bpo-43838: We can't just return PyNumber_Or(left->mapping, right) or
// PyNumber_Or(left, right->mapping) here, because the operator dispatch dance can
// expose our hidden mapping to arbitrary code. Perform the operation with a copy of
// the mapping instead:
_Py_IDENTIFIER(copy);
if (PyObject_TypeCheck(left, &PyDictProxy_Type)) {
left = ((mappingproxyobject*)left)->mapping;
left = _PyObject_CallMethodIdNoArgs(left, &PyId_copy);
if (left == NULL) {
return NULL;
}
}
else {
Py_INCREF(left);
}
if (PyObject_TypeCheck(right, &PyDictProxy_Type)) {
right = ((mappingproxyobject*)right)->mapping;
right = _PyObject_CallMethodIdNoArgs(right, &PyId_copy);
if (right == NULL) {
Py_DECREF(left);
return NULL;
}
}
return PyNumber_Or(left, right);
else {
Py_INCREF(right);
}
PyObject *result = PyNumber_Or(left, right);
Py_DECREF(left);
Py_DECREF(right);
return result;
}

static PyObject *
Expand Down Expand Up @@ -1188,7 +1211,17 @@ mappingproxy_traverse(PyObject *self, visitproc visit, void *arg)
static PyObject *
mappingproxy_richcompare(mappingproxyobject *v, PyObject *w, int op)
{
return PyObject_RichCompare(v->mapping, w, op);
// bpo-43838: We can't just return PyObject_RichCompare(v->mapping, w, op) here,
// because the operator dispatch dance can expose our hidden mapping to arbitrary
// code. Perform the operation with a copy of the mapping instead:
_Py_IDENTIFIER(copy);
PyObject *copy = _PyObject_CallMethodIdNoArgs(v->mapping, &PyId_copy);
if (copy == NULL) {
return NULL;
}
PyObject *result = PyObject_RichCompare(copy, w, op);
Py_DECREF(copy);
return result;
}

static int
Expand Down

Back | FazBrowse Home | New Git URL