diff options
author | Fish <ltfish@users.noreply.github.com> | 2019-02-07 19:51:59 (GMT) |
---|---|---|
committer | Antoine Pitrou <pitrou@free.fr> | 2019-02-07 19:51:59 (GMT) |
commit | 96d37dbcd23e65a7a57819aeced9034296ef747e (patch) | |
tree | f8a5c4d702642dbc6da04a85d818ae7689d5a1db | |
parent | df8d2cde63c865446468351f8f648e1c7bd45109 (diff) | |
download | cpython-96d37dbcd23e65a7a57819aeced9034296ef747e.zip cpython-96d37dbcd23e65a7a57819aeced9034296ef747e.tar.gz cpython-96d37dbcd23e65a7a57819aeced9034296ef747e.tar.bz2 |
bpo-35615: Fix crashes when copying a Weak{Key,Value}Dictionary. (GH-11384)
Protect dict iterations by wrapping them with _IterationGuard in the
following methods:
- WeakValueDictionary.copy()
- WeakValueDictionary.__deepcopy__()
- WeakKeyDictionary.copy()
- WeakKeyDictionary.__deepcopy__()
-rw-r--r-- | Lib/test/test_weakref.py | 82 | ||||
-rw-r--r-- | Lib/weakref.py | 36 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2018-12-30-20-00-05.bpo-35615.Uz1SVh.rst | 3 |
3 files changed, 105 insertions, 16 deletions
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 9fa0bbd..1fac08d 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -8,6 +8,7 @@ import contextlib import copy import threading import time +import random from test import support from test.support import script_helper @@ -1688,6 +1689,87 @@ class MappingTestCase(TestBase): self.assertEqual(len(d), 1) o = None # lose ref + def check_threaded_weak_dict_copy(self, type_, deepcopy): + # `type_` should be either WeakKeyDictionary or WeakValueDictionary. + # `deepcopy` should be either True or False. + exc = [] + + class DummyKey: + def __init__(self, ctr): + self.ctr = ctr + + class DummyValue: + def __init__(self, ctr): + self.ctr = ctr + + def dict_copy(d, exc): + try: + if deepcopy is True: + _ = copy.deepcopy(d) + else: + _ = d.copy() + except Exception as ex: + exc.append(ex) + + def pop_and_collect(lst): + gc_ctr = 0 + while lst: + i = random.randint(0, len(lst) - 1) + gc_ctr += 1 + lst.pop(i) + if gc_ctr % 10000 == 0: + gc.collect() # just in case + + self.assertIn(type_, (weakref.WeakKeyDictionary, weakref.WeakValueDictionary)) + + d = type_() + keys = [] + values = [] + # Initialize d with many entries + for i in range(70000): + k, v = DummyKey(i), DummyValue(i) + keys.append(k) + values.append(v) + d[k] = v + del k + del v + + t_copy = threading.Thread(target=dict_copy, args=(d, exc,)) + if type_ is weakref.WeakKeyDictionary: + t_collect = threading.Thread(target=pop_and_collect, args=(keys,)) + else: # weakref.WeakValueDictionary + t_collect = threading.Thread(target=pop_and_collect, args=(values,)) + + t_copy.start() + t_collect.start() + + t_copy.join() + t_collect.join() + + # Test exceptions + if exc: + raise exc[0] + + def test_threaded_weak_key_dict_copy(self): + # Issue #35615: Weakref keys or values getting GC'ed during dict + # copying should not result in a crash. + self.check_threaded_weak_dict_copy(weakref.WeakKeyDictionary, False) + + def test_threaded_weak_key_dict_deepcopy(self): + # Issue #35615: Weakref keys or values getting GC'ed during dict + # copying should not result in a crash. + self.check_threaded_weak_dict_copy(weakref.WeakKeyDictionary, True) + + def test_threaded_weak_value_dict_copy(self): + # Issue #35615: Weakref keys or values getting GC'ed during dict + # copying should not result in a crash. + self.check_threaded_weak_dict_copy(weakref.WeakValueDictionary, False) + + def test_threaded_weak_value_dict_deepcopy(self): + # Issue #35615: Weakref keys or values getting GC'ed during dict + # copying should not result in a crash. + self.check_threaded_weak_dict_copy(weakref.WeakValueDictionary, True) + from test import mapping_tests diff --git a/Lib/weakref.py b/Lib/weakref.py index 99de2ea..753f072 100644 --- a/Lib/weakref.py +++ b/Lib/weakref.py @@ -171,10 +171,11 @@ class WeakValueDictionary(_collections_abc.MutableMapping): if self._pending_removals: self._commit_removals() new = WeakValueDictionary() - for key, wr in self.data.items(): - o = wr() - if o is not None: - new[key] = o + with _IterationGuard(self): + for key, wr in self.data.items(): + o = wr() + if o is not None: + new[key] = o return new __copy__ = copy @@ -184,10 +185,11 @@ class WeakValueDictionary(_collections_abc.MutableMapping): if self._pending_removals: self._commit_removals() new = self.__class__() - for key, wr in self.data.items(): - o = wr() - if o is not None: - new[deepcopy(key, memo)] = o + with _IterationGuard(self): + for key, wr in self.data.items(): + o = wr() + if o is not None: + new[deepcopy(key, memo)] = o return new def get(self, key, default=None): @@ -408,10 +410,11 @@ class WeakKeyDictionary(_collections_abc.MutableMapping): def copy(self): new = WeakKeyDictionary() - for key, value in self.data.items(): - o = key() - if o is not None: - new[o] = value + with _IterationGuard(self): + for key, value in self.data.items(): + o = key() + if o is not None: + new[o] = value return new __copy__ = copy @@ -419,10 +422,11 @@ class WeakKeyDictionary(_collections_abc.MutableMapping): def __deepcopy__(self, memo): from copy import deepcopy new = self.__class__() - for key, value in self.data.items(): - o = key() - if o is not None: - new[o] = deepcopy(value, memo) + with _IterationGuard(self): + for key, value in self.data.items(): + o = key() + if o is not None: + new[o] = deepcopy(value, memo) return new def get(self, key, default=None): diff --git a/Misc/NEWS.d/next/Library/2018-12-30-20-00-05.bpo-35615.Uz1SVh.rst b/Misc/NEWS.d/next/Library/2018-12-30-20-00-05.bpo-35615.Uz1SVh.rst new file mode 100644 index 0000000..4aff8f7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-30-20-00-05.bpo-35615.Uz1SVh.rst @@ -0,0 +1,3 @@ +:mod:`weakref`: Fix a RuntimeError when copying a WeakKeyDictionary or a +WeakValueDictionary, due to some keys or values disappearing while +iterating. |