summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFish <ltfish@users.noreply.github.com>2019-02-07 19:51:59 (GMT)
committerAntoine Pitrou <pitrou@free.fr>2019-02-07 19:51:59 (GMT)
commit96d37dbcd23e65a7a57819aeced9034296ef747e (patch)
treef8a5c4d702642dbc6da04a85d818ae7689d5a1db
parentdf8d2cde63c865446468351f8f648e1c7bd45109 (diff)
downloadcpython-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.py82
-rw-r--r--Lib/weakref.py36
-rw-r--r--Misc/NEWS.d/next/Library/2018-12-30-20-00-05.bpo-35615.Uz1SVh.rst3
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.