cpython: 934aaf2191d0 (original) (raw)
deleted file mode 100644 --- a/Lib/test/crashers/nasty_eq_vs_dict.py +++ /dev/null @@ -1,47 +0,0 @@ -# from http://mail.python.org/pipermail/python-dev/2001-June/015239.html[](#l1.5) - -# if you keep changing a dictionary while looking up a key, you can -# provoke an infinite recursion in C - -# At the time neither Tim nor Michael could be bothered to think of a -# way to fix it. - -class Yuck:
- def hash(self):
# direct to slot 4 in table of size 8; slot 12 when size 16[](#l1.21)
return 4 + 8[](#l1.22)
- def eq(self, other):
if self.i == 0:[](#l1.25)
# leave dict alone[](#l1.26)
pass[](#l1.27)
elif self.i == 1:[](#l1.28)
# fiddle to 16 slots[](#l1.29)
self.__fill_dict(6)[](#l1.30)
self.i = 2[](#l1.31)
else:[](#l1.32)
# fiddle to 8 slots[](#l1.33)
self.__fill_dict(4)[](#l1.34)
self.i = 1[](#l1.35)
return 1[](#l1.37)
- def __fill_dict(self, n):
self.i = 0[](#l1.40)
dict.clear()[](#l1.41)
for i in range(n):[](#l1.42)
dict[i] = i[](#l1.43)
dict[self] = "OK!"[](#l1.44)
- -y = Yuck() -dict = {y: "OK!"} - -z = Yuck() -y.make_dangerous() -print(dict[z])
--- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -379,7 +379,7 @@ class DictTest(unittest.TestCase): x.fail = True self.assertRaises(Exc, d.pop, x)
@@ -387,6 +387,26 @@ class DictTest(unittest.TestCase): for i in d: d[i+1] = 1
- def test_mutating_lookup(self):
# changing dict during a lookup[](#l2.17)
class NastyKey:[](#l2.18)
mutate_dict = None[](#l2.19)
def __hash__(self):[](#l2.21)
# hash collision
return 1[](#l2.23)
def __eq__(self, other):[](#l2.25)
if self.mutate_dict:[](#l2.26)
self.mutate_dict[self] = 1[](#l2.27)
return self == other[](#l2.28)
d = {}[](#l2.30)
d[NastyKey()] = 0[](#l2.31)
NastyKey.mutate_dict = d[](#l2.32)
with self.assertRaises(RuntimeError):[](#l2.33)
d[NastyKey()] = None[](#l2.34)
+ def test_repr(self): d = {} self.assertEqual(repr(d), '{}')
deleted file mode 100644 --- a/Lib/test/test_mutants.py +++ /dev/null @@ -1,291 +0,0 @@ -from test.support import verbose, TESTFN -import random -import os - -# From SF bug #422121: Insecurities in dict comparison. - -# Safety of code doing comparisons has been an historical Python weak spot. -# The problem is that comparison of structures written in C naturally -# wants to hold on to things like the size of the container, or "the -# biggest" containee so far, across a traversal of the container; but -# code to do containee comparisons can call back into Python and mutate -# the container in arbitrary ways while the C loop is in midstream. If the -# C code isn't extremely paranoid about digging things out of memory on -# each trip, and artificially boosting refcounts for the duration, anything -# from infinite loops to OS crashes can result (yes, I use Windows ). -# -# The other problem is that code designed to provoke a weakness is usually -# white-box code, and so catches only the particular vulnerabilities the -# author knew to protect against. For example, Python's list.sort() code -# went thru many iterations as one "new" vulnerability after another was -# discovered. -# -# So the dict comparison test here uses a black-box approach instead, -# generating dicts of various sizes at random, and performing random -# mutations on them at random times. This proved very effective, -# triggering at least six distinct failure modes the first 20 times I -# ran it. Indeed, at the start, the driver never got beyond 6 iterations -# before the test died. - -# The dicts are global to make it easy to mutate tham from within functions. -dict1 = {} -dict2 = {} - -# The current set of keys in dict1 and dict2. These are materialized as -# lists to make it easy to pick a dict key at random. -dict1keys = [] -dict2keys = [] - -# Global flag telling maybe_mutate() whether to consider mutating. -mutate = 0 - -# If global mutate is true, consider mutating a dict. May or may not -# mutate a dict even if mutate is true. If it does decide to mutate a -# dict, it picks one of {dict1, dict2} at random, and deletes a random -# entry from it; or, more rarely, adds a random element. - -def maybe_mutate():
- if random.random() < 0.5:
target, keys = dict1, dict1keys[](#l3.59)
- else:
target, keys = dict2, dict2keys[](#l3.61)
- if random.random() < 0.2:
# Insert a new key.[](#l3.64)
mutate = 0 # disable mutation until key inserted[](#l3.65)
while 1:[](#l3.66)
newkey = Horrid(random.randrange(100))[](#l3.67)
if newkey not in target:[](#l3.68)
break[](#l3.69)
target[newkey] = Horrid(random.randrange(100))[](#l3.70)
keys.append(newkey)[](#l3.71)
mutate = 1[](#l3.72)
- elif keys:
# Delete a key at random.[](#l3.75)
mutate = 0 # disable mutation until key deleted[](#l3.76)
i = random.randrange(len(keys))[](#l3.77)
key = keys[i][](#l3.78)
del target[key][](#l3.79)
del keys[i][](#l3.80)
mutate = 1[](#l3.81)
- -# A horrid class that triggers random mutations of dict1 and dict2 when -# instances are compared. - -class Horrid:
- def init(self, i):
# Comparison outcomes are determined by the value of i.[](#l3.88)
self.i = i[](#l3.89)
# An artificial hashcode is selected at random so that we don't[](#l3.91)
# have any systematic relationship between comparison outcomes[](#l3.92)
# (based on self.i and other.i) and relative position within the[](#l3.93)
# hash vector (based on hashcode).[](#l3.94)
# XXX This is no longer effective.[](#l3.95)
##self.hashcode = random.randrange(1000000000)[](#l3.96)
- def eq(self, other):
maybe_mutate() # The point of the test.[](#l3.103)
return self.i == other.i[](#l3.104)
- def ne(self, other):
raise RuntimeError("I didn't expect some kind of Spanish inquisition!")[](#l3.107)
- -# Fill dict d with numentries (Horrid(i), Horrid(j)) key-value pairs, -# where i and j are selected at random from the candidates list. -# Return d.keys() after filling. - -def fill_dict(d, candidates, numentries):
- d.clear()
- for i in range(numentries):
d[Horrid(random.choice(candidates))] = \[](#l3.121)
Horrid(random.choice(candidates))[](#l3.122)
- return list(d.keys())
- -# Test one pair of randomly generated dicts, each with n entries. -# Note that dict comparison is trivial if they don't have the same number -# of entires (then the "shorter" dict is instantly considered to be the -# smaller one, without even looking at the entries). - -def test_one(n):
Fill the dicts without mutating them.
- mutate = 0
- dict1keys = fill_dict(dict1, range(n), n)
- dict2keys = fill_dict(dict2, range(n), n)
Enable mutation, then compare the dicts so long as they have the
same size.
- mutate = 1
- if verbose:
print("trying w/ lengths", len(dict1), len(dict2), end=' ')[](#l3.142)
- while dict1 and len(dict1) == len(dict2):
if verbose:[](#l3.144)
print(".", end=' ')[](#l3.145)
c = dict1 == dict2[](#l3.146)
- if verbose:
print()[](#l3.148)
- -# Run test_one n times. At the start (before the bugs were fixed), 20 -# consecutive runs of this test each blew up on or before the sixth time -# test_one was run. So n doesn't have to be large to get an interesting -# test. -# OTOH, calling with large n is also interesting, to ensure that the fixed -# code doesn't hold on to refcounts too long (in which case memory would -# leak). - -def test(n):
- -# See last comment block for clues about good values for n. -test(100) - -########################################################################## -# Another segfault bug, distilled by Michael Hudson from a c.l.py post. - -class Child:
- def init(self, parent):
self.__dict__['parent'] = parent[](#l3.170)
- def getattr(self, attr):
self.parent.a = 1[](#l3.172)
self.parent.b = 1[](#l3.173)
self.parent.c = 1[](#l3.174)
self.parent.d = 1[](#l3.175)
self.parent.e = 1[](#l3.176)
self.parent.f = 1[](#l3.177)
self.parent.g = 1[](#l3.178)
self.parent.h = 1[](#l3.179)
self.parent.i = 1[](#l3.180)
return getattr(self.parent, attr)[](#l3.181)
- -# Hard to say what this will print! May vary from time to time. But -# we're specifically trying to test the tp_print slot here, and this is -# the clearest way to do it. We print the result to a temp file so that -# the expected-output file doesn't need to change. - -f = open(TESTFN, "w") -print(Parent().dict, file=f) -f.close() -os.unlink(TESTFN) - -########################################################################## -# And another core-dumper from Michael Hudson. - -dict = {} - -# Force dict to malloc its table. -for i in range(1, 10):
- -f = open(TESTFN, "w") - -class Machiavelli:
# Michael sez: "doesn't crash without this. don't know why."[](#l3.212)
# Tim sez: "luck of the draw; crashes with or without for me."[](#l3.213)
print(file=f)[](#l3.214)
return repr("machiavelli")[](#l3.216)
- -dict[Machiavelli()] = Machiavelli() - -print(str(dict), file=f) -f.close() -os.unlink(TESTFN) -del f, dict - - -########################################################################## -# And another core-dumper from Michael Hudson. - -dict = {} - -# let's force dict to malloc its table -for i in range(1, 10):
- -dict[Machiavelli2()] = Machiavelli2() - -try:
- -del dict - -########################################################################## -# And another core-dumper from Michael Hudson. - -dict = {} - -# let's force dict to malloc its table -for i in range(1, 10):
- def eq(self, other):
if self.id == other.id:[](#l3.269)
dict.clear()[](#l3.270)
return 1[](#l3.271)
else:[](#l3.272)
return 0[](#l3.273)
- -dict[Machiavelli3(1)] = Machiavelli3(0) -dict[Machiavelli3(2)] = Machiavelli3(0) - -f = open(TESTFN, "w") -try:
- -del dict -del dict1, dict2, dict1keys, dict2keys
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,10 +10,13 @@ What's New in Python 3.3.0 Alpha 2? Core and Builtins ----------------- +- Issue #14205: dict lookup raises a RuntimeError if the dict is modified
- during a lookup. + Library ------- -- Issue #14168: Check for presence of Element._attrs in minidom before +- Issue #14168: Check for presence of Element._attrs in minidom before accessing it.
--- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -347,12 +347,9 @@ lookdict(PyDictObject *mp, PyObject *key return ep; } else {
/* The compare did major nasty stuff to the[](#l5.7)
* dict: start over.[](#l5.8)
* XXX A clever adversary could prevent this[](#l5.9)
* XXX from terminating.[](#l5.10)
*/[](#l5.11)
return lookdict(mp, key, hash);[](#l5.12)
PyErr_SetString(PyExc_RuntimeError,[](#l5.13)
"dictionary changed size during lookup");[](#l5.14)
return NULL;[](#l5.15) }[](#l5.16) }[](#l5.17) freeslot = NULL;[](#l5.18)
@@ -379,12 +376,9 @@ lookdict(PyDictObject *mp, PyObject *key return ep; } else {
/* The compare did major nasty stuff to the[](#l5.23)
* dict: start over.[](#l5.24)
* XXX A clever adversary could prevent this[](#l5.25)
* XXX from terminating.[](#l5.26)
*/[](#l5.27)
return lookdict(mp, key, hash);[](#l5.28)
PyErr_SetString(PyExc_RuntimeError,[](#l5.29)
"dictionary changed size during lookup");[](#l5.30)
return NULL;[](#l5.31) }[](#l5.32) }[](#l5.33) else if (ep->me_key == dummy && freeslot == NULL)[](#l5.34)