blob: b43fa47626bdd460b9deec73e4ec32e2a927b168 [file] [log] [blame]
Benjamin Petersonee8712c2008-05-20 21:35:26 +00001from test.support import verbose, TESTFN
Tim Peters95bf9392001-05-10 08:32:44 +00002import random
Tim Peters23cf6be2001-06-02 08:02:56 +00003import os
Tim Peters95bf9392001-05-10 08:32:44 +00004
5# From SF bug #422121: Insecurities in dict comparison.
6
Tim Peters8c3e91e2001-05-10 19:40:30 +00007# Safety of code doing comparisons has been an historical Python weak spot.
8# The problem is that comparison of structures written in C *naturally*
Tim Peters95bf9392001-05-10 08:32:44 +00009# wants to hold on to things like the size of the container, or "the
10# biggest" containee so far, across a traversal of the container; but
11# code to do containee comparisons can call back into Python and mutate
12# the container in arbitrary ways while the C loop is in midstream. If the
13# C code isn't extremely paranoid about digging things out of memory on
14# each trip, and artificially boosting refcounts for the duration, anything
15# from infinite loops to OS crashes can result (yes, I use Windows <wink>).
16#
17# The other problem is that code designed to provoke a weakness is usually
18# white-box code, and so catches only the particular vulnerabilities the
19# author knew to protect against. For example, Python's list.sort() code
20# went thru many iterations as one "new" vulnerability after another was
21# discovered.
22#
23# So the dict comparison test here uses a black-box approach instead,
24# generating dicts of various sizes at random, and performing random
25# mutations on them at random times. This proved very effective,
26# triggering at least six distinct failure modes the first 20 times I
27# ran it. Indeed, at the start, the driver never got beyond 6 iterations
28# before the test died.
29
Guido van Rossumdc5f6b22006-08-24 21:29:26 +000030# The dicts are global to make it easy to mutate tham from within functions.
Tim Peters95bf9392001-05-10 08:32:44 +000031dict1 = {}
32dict2 = {}
33
34# The current set of keys in dict1 and dict2. These are materialized as
35# lists to make it easy to pick a dict key at random.
36dict1keys = []
37dict2keys = []
38
Neal Norwitz2fcf2062005-11-24 23:28:37 +000039# Global flag telling maybe_mutate() whether to *consider* mutating.
Tim Peters95bf9392001-05-10 08:32:44 +000040mutate = 0
41
42# If global mutate is true, consider mutating a dict. May or may not
43# mutate a dict even if mutate is true. If it does decide to mutate a
44# dict, it picks one of {dict1, dict2} at random, and deletes a random
Tim Peters4c02fec2001-05-10 20:18:30 +000045# entry from it; or, more rarely, adds a random element.
Tim Peters95bf9392001-05-10 08:32:44 +000046
47def maybe_mutate():
Tim Peters4c02fec2001-05-10 20:18:30 +000048 global mutate
Tim Peters95bf9392001-05-10 08:32:44 +000049 if not mutate:
50 return
51 if random.random() < 0.5:
52 return
Tim Peters4c02fec2001-05-10 20:18:30 +000053
Tim Peters95bf9392001-05-10 08:32:44 +000054 if random.random() < 0.5:
55 target, keys = dict1, dict1keys
56 else:
57 target, keys = dict2, dict2keys
Tim Peters4c02fec2001-05-10 20:18:30 +000058
59 if random.random() < 0.2:
60 # Insert a new key.
61 mutate = 0 # disable mutation until key inserted
62 while 1:
63 newkey = Horrid(random.randrange(100))
64 if newkey not in target:
65 break
66 target[newkey] = Horrid(random.randrange(100))
67 keys.append(newkey)
68 mutate = 1
69
70 elif keys:
71 # Delete a key at random.
Armin Rigo57179fe2005-05-15 13:29:26 +000072 mutate = 0 # disable mutation until key deleted
Tim Peters95bf9392001-05-10 08:32:44 +000073 i = random.randrange(len(keys))
74 key = keys[i]
75 del target[key]
Tim Peters95bf9392001-05-10 08:32:44 +000076 del keys[i]
Armin Rigo57179fe2005-05-15 13:29:26 +000077 mutate = 1
Tim Peters95bf9392001-05-10 08:32:44 +000078
79# A horrid class that triggers random mutations of dict1 and dict2 when
80# instances are compared.
81
82class Horrid:
83 def __init__(self, i):
84 # Comparison outcomes are determined by the value of i.
85 self.i = i
86
87 # An artificial hashcode is selected at random so that we don't
Tim Peters8c3e91e2001-05-10 19:40:30 +000088 # have any systematic relationship between comparison outcomes
Tim Peters95bf9392001-05-10 08:32:44 +000089 # (based on self.i and other.i) and relative position within the
Tim Peters8c3e91e2001-05-10 19:40:30 +000090 # hash vector (based on hashcode).
Guido van Rossumdc5f6b22006-08-24 21:29:26 +000091 # XXX This is no longer effective.
92 ##self.hashcode = random.randrange(1000000000)
Tim Peters95bf9392001-05-10 08:32:44 +000093
94 def __hash__(self):
Guido van Rossumdc5f6b22006-08-24 21:29:26 +000095 return 42
Thomas Wouters89f507f2006-12-13 04:49:30 +000096 return self.hashcode
Tim Peters95bf9392001-05-10 08:32:44 +000097
Guido van Rossum47b9ff62006-08-24 00:41:19 +000098 def __eq__(self, other):
Tim Peters95bf9392001-05-10 08:32:44 +000099 maybe_mutate() # The point of the test.
Guido van Rossum47b9ff62006-08-24 00:41:19 +0000100 return self.i == other.i
Tim Peters95bf9392001-05-10 08:32:44 +0000101
Guido van Rossumdc5f6b22006-08-24 21:29:26 +0000102 def __ne__(self, other):
103 raise RuntimeError("I didn't expect some kind of Spanish inquisition!")
104
105 __lt__ = __le__ = __gt__ = __ge__ = __ne__
106
Tim Peters95bf9392001-05-10 08:32:44 +0000107 def __repr__(self):
108 return "Horrid(%d)" % self.i
109
110# Fill dict d with numentries (Horrid(i), Horrid(j)) key-value pairs,
111# where i and j are selected at random from the candidates list.
112# Return d.keys() after filling.
113
114def fill_dict(d, candidates, numentries):
115 d.clear()
Guido van Rossum805365e2007-05-07 22:24:25 +0000116 for i in range(numentries):
Tim Peters95bf9392001-05-10 08:32:44 +0000117 d[Horrid(random.choice(candidates))] = \
118 Horrid(random.choice(candidates))
Brett Cannonecca3132007-02-21 21:59:58 +0000119 return list(d.keys())
Tim Peters95bf9392001-05-10 08:32:44 +0000120
121# Test one pair of randomly generated dicts, each with n entries.
122# Note that dict comparison is trivial if they don't have the same number
123# of entires (then the "shorter" dict is instantly considered to be the
124# smaller one, without even looking at the entries).
125
126def test_one(n):
127 global mutate, dict1, dict2, dict1keys, dict2keys
128
129 # Fill the dicts without mutating them.
130 mutate = 0
131 dict1keys = fill_dict(dict1, range(n), n)
132 dict2keys = fill_dict(dict2, range(n), n)
133
134 # Enable mutation, then compare the dicts so long as they have the
135 # same size.
136 mutate = 1
137 if verbose:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000138 print("trying w/ lengths", len(dict1), len(dict2), end=' ')
Tim Peters95bf9392001-05-10 08:32:44 +0000139 while dict1 and len(dict1) == len(dict2):
140 if verbose:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000141 print(".", end=' ')
Guido van Rossum47b9ff62006-08-24 00:41:19 +0000142 c = dict1 == dict2
Tim Peters95bf9392001-05-10 08:32:44 +0000143 if verbose:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000144 print()
Tim Peters95bf9392001-05-10 08:32:44 +0000145
146# Run test_one n times. At the start (before the bugs were fixed), 20
147# consecutive runs of this test each blew up on or before the sixth time
148# test_one was run. So n doesn't have to be large to get an interesting
149# test.
150# OTOH, calling with large n is also interesting, to ensure that the fixed
151# code doesn't hold on to refcounts *too* long (in which case memory would
152# leak).
153
154def test(n):
Guido van Rossum805365e2007-05-07 22:24:25 +0000155 for i in range(n):
Tim Peters95bf9392001-05-10 08:32:44 +0000156 test_one(random.randrange(1, 100))
157
158# See last comment block for clues about good values for n.
159test(100)
Tim Peters23cf6be2001-06-02 08:02:56 +0000160
161##########################################################################
Tim Petersfa517b22001-06-02 08:18:58 +0000162# Another segfault bug, distilled by Michael Hudson from a c.l.py post.
Tim Peters23cf6be2001-06-02 08:02:56 +0000163
164class Child:
165 def __init__(self, parent):
166 self.__dict__['parent'] = parent
167 def __getattr__(self, attr):
168 self.parent.a = 1
169 self.parent.b = 1
170 self.parent.c = 1
171 self.parent.d = 1
172 self.parent.e = 1
173 self.parent.f = 1
174 self.parent.g = 1
175 self.parent.h = 1
176 self.parent.i = 1
177 return getattr(self.parent, attr)
178
179class Parent:
180 def __init__(self):
181 self.a = Child(self)
182
183# Hard to say what this will print! May vary from time to time. But
184# we're specifically trying to test the tp_print slot here, and this is
185# the clearest way to do it. We print the result to a temp file so that
186# the expected-output file doesn't need to change.
187
188f = open(TESTFN, "w")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000189print(Parent().__dict__, file=f)
Tim Peters23cf6be2001-06-02 08:02:56 +0000190f.close()
191os.unlink(TESTFN)
192
193##########################################################################
194# And another core-dumper from Michael Hudson.
195
196dict = {}
197
198# Force dict to malloc its table.
199for i in range(1, 10):
200 dict[i] = i
201
202f = open(TESTFN, "w")
203
204class Machiavelli:
205 def __repr__(self):
206 dict.clear()
207
208 # Michael sez: "doesn't crash without this. don't know why."
209 # Tim sez: "luck of the draw; crashes with or without for me."
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000210 print(file=f)
Tim Peters23cf6be2001-06-02 08:02:56 +0000211
Brett Cannon0b70cca2006-08-25 02:59:59 +0000212 return repr("machiavelli")
Tim Peters23cf6be2001-06-02 08:02:56 +0000213
214 def __hash__(self):
215 return 0
216
217dict[Machiavelli()] = Machiavelli()
218
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000219print(str(dict), file=f)
Tim Peters23cf6be2001-06-02 08:02:56 +0000220f.close()
221os.unlink(TESTFN)
222del f, dict
Tim Peters453163d2001-06-03 04:54:32 +0000223
224
225##########################################################################
226# And another core-dumper from Michael Hudson.
227
228dict = {}
229
230# let's force dict to malloc its table
231for i in range(1, 10):
232 dict[i] = i
233
234class Machiavelli2:
235 def __eq__(self, other):
236 dict.clear()
237 return 1
238
239 def __hash__(self):
240 return 0
241
242dict[Machiavelli2()] = Machiavelli2()
243
244try:
245 dict[Machiavelli2()]
246except KeyError:
247 pass
248
249del dict
250
251##########################################################################
252# And another core-dumper from Michael Hudson.
253
254dict = {}
255
256# let's force dict to malloc its table
257for i in range(1, 10):
258 dict[i] = i
259
260class Machiavelli3:
261 def __init__(self, id):
262 self.id = id
263
264 def __eq__(self, other):
265 if self.id == other.id:
266 dict.clear()
267 return 1
268 else:
269 return 0
270
271 def __repr__(self):
272 return "%s(%s)"%(self.__class__.__name__, self.id)
273
274 def __hash__(self):
275 return 0
276
277dict[Machiavelli3(1)] = Machiavelli3(0)
278dict[Machiavelli3(2)] = Machiavelli3(0)
279
280f = open(TESTFN, "w")
281try:
282 try:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000283 print(dict[Machiavelli3(2)], file=f)
Tim Peters453163d2001-06-03 04:54:32 +0000284 except KeyError:
285 pass
286finally:
287 f.close()
288 os.unlink(TESTFN)
289
290del dict
Neal Norwitz2fcf2062005-11-24 23:28:37 +0000291del dict1, dict2, dict1keys, dict2keys