Commit 96d37dbc authored by Fish's avatar Fish Committed by Antoine Pitrou

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__()
parent df8d2cde
...@@ -8,6 +8,7 @@ import contextlib ...@@ -8,6 +8,7 @@ import contextlib
import copy import copy
import threading import threading
import time import time
import random
from test import support from test import support
from test.support import script_helper from test.support import script_helper
...@@ -1688,6 +1689,87 @@ class MappingTestCase(TestBase): ...@@ -1688,6 +1689,87 @@ class MappingTestCase(TestBase):
self.assertEqual(len(d), 1) self.assertEqual(len(d), 1)
o = None # lose ref 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 from test import mapping_tests
......
...@@ -171,10 +171,11 @@ class WeakValueDictionary(_collections_abc.MutableMapping): ...@@ -171,10 +171,11 @@ class WeakValueDictionary(_collections_abc.MutableMapping):
if self._pending_removals: if self._pending_removals:
self._commit_removals() self._commit_removals()
new = WeakValueDictionary() new = WeakValueDictionary()
for key, wr in self.data.items(): with _IterationGuard(self):
o = wr() for key, wr in self.data.items():
if o is not None: o = wr()
new[key] = o if o is not None:
new[key] = o
return new return new
__copy__ = copy __copy__ = copy
...@@ -184,10 +185,11 @@ class WeakValueDictionary(_collections_abc.MutableMapping): ...@@ -184,10 +185,11 @@ class WeakValueDictionary(_collections_abc.MutableMapping):
if self._pending_removals: if self._pending_removals:
self._commit_removals() self._commit_removals()
new = self.__class__() new = self.__class__()
for key, wr in self.data.items(): with _IterationGuard(self):
o = wr() for key, wr in self.data.items():
if o is not None: o = wr()
new[deepcopy(key, memo)] = o if o is not None:
new[deepcopy(key, memo)] = o
return new return new
def get(self, key, default=None): def get(self, key, default=None):
...@@ -408,10 +410,11 @@ class WeakKeyDictionary(_collections_abc.MutableMapping): ...@@ -408,10 +410,11 @@ class WeakKeyDictionary(_collections_abc.MutableMapping):
def copy(self): def copy(self):
new = WeakKeyDictionary() new = WeakKeyDictionary()
for key, value in self.data.items(): with _IterationGuard(self):
o = key() for key, value in self.data.items():
if o is not None: o = key()
new[o] = value if o is not None:
new[o] = value
return new return new
__copy__ = copy __copy__ = copy
...@@ -419,10 +422,11 @@ class WeakKeyDictionary(_collections_abc.MutableMapping): ...@@ -419,10 +422,11 @@ class WeakKeyDictionary(_collections_abc.MutableMapping):
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
from copy import deepcopy from copy import deepcopy
new = self.__class__() new = self.__class__()
for key, value in self.data.items(): with _IterationGuard(self):
o = key() for key, value in self.data.items():
if o is not None: o = key()
new[o] = deepcopy(value, memo) if o is not None:
new[o] = deepcopy(value, memo)
return new return new
def get(self, key, default=None): def get(self, key, default=None):
......
:mod:`weakref`: Fix a RuntimeError when copying a WeakKeyDictionary or a
WeakValueDictionary, due to some keys or values disappearing while
iterating.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment