Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Lib/test/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -2135,6 +2135,19 @@ def test_basics(self):
self.assertEqual(c.setdefault('e', 5), 5)
self.assertEqual(c['e'], 5)

def test_update_reentrant_add_clears_counter(self):
c = Counter()
key = object()

class Evil(int):
def __add__(self, other):
c.clear()
return NotImplemented

c[key] = Evil()
c.update([key])
self.assertEqual(c[key], 1)

def test_init(self):
self.assertEqual(list(Counter(self=42).items()), [('self', 42)])
self.assertEqual(list(Counter(iterable=42).items()), [('iterable', 42)])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a potential use-after-free in :meth:`collections.Counter.update` when user code
mutates the Counter during an update.
5 changes: 5 additions & 0 deletions Modules/_collectionsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2577,7 +2577,12 @@ _collections__count_elements_impl(PyObject *module, PyObject *mapping,
if (_PyDict_SetItem_KnownHash(mapping, key, one, hash) < 0)
goto done;
} else {
/* oldval is a borrowed reference. Keep it alive across
PyNumber_Add(), which can execute arbitrary user code and
mutate (or even clear) the underlying dict. */
Py_INCREF(oldval);
newval = PyNumber_Add(oldval, one);
Py_DECREF(oldval);
if (newval == NULL)
goto done;
if (_PyDict_SetItem_KnownHash(mapping, key, newval, hash) < 0)
Expand Down
Loading