python-2.5.2/win32/Lib/test/test_mutants.py
changeset 0 ae805ac0140d
equal deleted inserted replaced
-1:000000000000 0:ae805ac0140d
       
     1 from test.test_support import verbose, TESTFN
       
     2 import random
       
     3 import os
       
     4 
       
     5 # From SF bug #422121:  Insecurities in dict comparison.
       
     6 
       
     7 # Safety of code doing comparisons has been an historical Python weak spot.
       
     8 # The problem is that comparison of structures written in C *naturally*
       
     9 # 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 
       
    30 # The dicts are global to make it easy to mutate tham from within functions.
       
    31 dict1 = {}
       
    32 dict2 = {}
       
    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.
       
    36 dict1keys = []
       
    37 dict2keys = []
       
    38 
       
    39 # Global flag telling maybe_mutate() whether to *consider* mutating.
       
    40 mutate = 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
       
    45 # entry from it; or, more rarely, adds a random element.
       
    46 
       
    47 def maybe_mutate():
       
    48     global mutate
       
    49     if not mutate:
       
    50         return
       
    51     if random.random() < 0.5:
       
    52         return
       
    53 
       
    54     if random.random() < 0.5:
       
    55         target, keys = dict1, dict1keys
       
    56     else:
       
    57         target, keys = dict2, dict2keys
       
    58 
       
    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.
       
    72         mutate = 0   # disable mutation until key deleted
       
    73         i = random.randrange(len(keys))
       
    74         key = keys[i]
       
    75         del target[key]
       
    76         del keys[i]
       
    77         mutate = 1
       
    78 
       
    79 # A horrid class that triggers random mutations of dict1 and dict2 when
       
    80 # instances are compared.
       
    81 
       
    82 class 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
       
    88         # have any systematic relationship between comparison outcomes
       
    89         # (based on self.i and other.i) and relative position within the
       
    90         # hash vector (based on hashcode).
       
    91         self.hashcode = random.randrange(1000000000)
       
    92 
       
    93     def __hash__(self):
       
    94         return 42
       
    95         return self.hashcode
       
    96 
       
    97     def __cmp__(self, other):
       
    98         maybe_mutate()   # The point of the test.
       
    99         return cmp(self.i, other.i)
       
   100 
       
   101     def __eq__(self, other):
       
   102         maybe_mutate()   # The point of the test.
       
   103         return self.i == other.i
       
   104 
       
   105     def __repr__(self):
       
   106         return "Horrid(%d)" % self.i
       
   107 
       
   108 # Fill dict d with numentries (Horrid(i), Horrid(j)) key-value pairs,
       
   109 # where i and j are selected at random from the candidates list.
       
   110 # Return d.keys() after filling.
       
   111 
       
   112 def fill_dict(d, candidates, numentries):
       
   113     d.clear()
       
   114     for i in xrange(numentries):
       
   115         d[Horrid(random.choice(candidates))] = \
       
   116             Horrid(random.choice(candidates))
       
   117     return d.keys()
       
   118 
       
   119 # Test one pair of randomly generated dicts, each with n entries.
       
   120 # Note that dict comparison is trivial if they don't have the same number
       
   121 # of entires (then the "shorter" dict is instantly considered to be the
       
   122 # smaller one, without even looking at the entries).
       
   123 
       
   124 def test_one(n):
       
   125     global mutate, dict1, dict2, dict1keys, dict2keys
       
   126 
       
   127     # Fill the dicts without mutating them.
       
   128     mutate = 0
       
   129     dict1keys = fill_dict(dict1, range(n), n)
       
   130     dict2keys = fill_dict(dict2, range(n), n)
       
   131 
       
   132     # Enable mutation, then compare the dicts so long as they have the
       
   133     # same size.
       
   134     mutate = 1
       
   135     if verbose:
       
   136         print "trying w/ lengths", len(dict1), len(dict2),
       
   137     while dict1 and len(dict1) == len(dict2):
       
   138         if verbose:
       
   139             print ".",
       
   140         if random.random() < 0.5:
       
   141             c = cmp(dict1, dict2)
       
   142         else:
       
   143             c = dict1 == dict2
       
   144     if verbose:
       
   145         print
       
   146 
       
   147 # Run test_one n times.  At the start (before the bugs were fixed), 20
       
   148 # consecutive runs of this test each blew up on or before the sixth time
       
   149 # test_one was run.  So n doesn't have to be large to get an interesting
       
   150 # test.
       
   151 # OTOH, calling with large n is also interesting, to ensure that the fixed
       
   152 # code doesn't hold on to refcounts *too* long (in which case memory would
       
   153 # leak).
       
   154 
       
   155 def test(n):
       
   156     for i in xrange(n):
       
   157         test_one(random.randrange(1, 100))
       
   158 
       
   159 # See last comment block for clues about good values for n.
       
   160 test(100)
       
   161 
       
   162 ##########################################################################
       
   163 # Another segfault bug, distilled by Michael Hudson from a c.l.py post.
       
   164 
       
   165 class Child:
       
   166     def __init__(self, parent):
       
   167         self.__dict__['parent'] = parent
       
   168     def __getattr__(self, attr):
       
   169         self.parent.a = 1
       
   170         self.parent.b = 1
       
   171         self.parent.c = 1
       
   172         self.parent.d = 1
       
   173         self.parent.e = 1
       
   174         self.parent.f = 1
       
   175         self.parent.g = 1
       
   176         self.parent.h = 1
       
   177         self.parent.i = 1
       
   178         return getattr(self.parent, attr)
       
   179 
       
   180 class Parent:
       
   181     def __init__(self):
       
   182         self.a = Child(self)
       
   183 
       
   184 # Hard to say what this will print!  May vary from time to time.  But
       
   185 # we're specifically trying to test the tp_print slot here, and this is
       
   186 # the clearest way to do it.  We print the result to a temp file so that
       
   187 # the expected-output file doesn't need to change.
       
   188 
       
   189 f = open(TESTFN, "w")
       
   190 print >> f, Parent().__dict__
       
   191 f.close()
       
   192 os.unlink(TESTFN)
       
   193 
       
   194 ##########################################################################
       
   195 # And another core-dumper from Michael Hudson.
       
   196 
       
   197 dict = {}
       
   198 
       
   199 # Force dict to malloc its table.
       
   200 for i in range(1, 10):
       
   201     dict[i] = i
       
   202 
       
   203 f = open(TESTFN, "w")
       
   204 
       
   205 class Machiavelli:
       
   206     def __repr__(self):
       
   207         dict.clear()
       
   208 
       
   209         # Michael sez:  "doesn't crash without this.  don't know why."
       
   210         # Tim sez:  "luck of the draw; crashes with or without for me."
       
   211         print >> f
       
   212 
       
   213         return `"machiavelli"`
       
   214 
       
   215     def __hash__(self):
       
   216         return 0
       
   217 
       
   218 dict[Machiavelli()] = Machiavelli()
       
   219 
       
   220 print >> f, str(dict)
       
   221 f.close()
       
   222 os.unlink(TESTFN)
       
   223 del f, dict
       
   224 
       
   225 
       
   226 ##########################################################################
       
   227 # And another core-dumper from Michael Hudson.
       
   228 
       
   229 dict = {}
       
   230 
       
   231 # let's force dict to malloc its table
       
   232 for i in range(1, 10):
       
   233     dict[i] = i
       
   234 
       
   235 class Machiavelli2:
       
   236     def __eq__(self, other):
       
   237         dict.clear()
       
   238         return 1
       
   239 
       
   240     def __hash__(self):
       
   241         return 0
       
   242 
       
   243 dict[Machiavelli2()] = Machiavelli2()
       
   244 
       
   245 try:
       
   246     dict[Machiavelli2()]
       
   247 except KeyError:
       
   248     pass
       
   249 
       
   250 del dict
       
   251 
       
   252 ##########################################################################
       
   253 # And another core-dumper from Michael Hudson.
       
   254 
       
   255 dict = {}
       
   256 
       
   257 # let's force dict to malloc its table
       
   258 for i in range(1, 10):
       
   259     dict[i] = i
       
   260 
       
   261 class Machiavelli3:
       
   262     def __init__(self, id):
       
   263         self.id = id
       
   264 
       
   265     def __eq__(self, other):
       
   266         if self.id == other.id:
       
   267             dict.clear()
       
   268             return 1
       
   269         else:
       
   270             return 0
       
   271 
       
   272     def __repr__(self):
       
   273         return "%s(%s)"%(self.__class__.__name__, self.id)
       
   274 
       
   275     def __hash__(self):
       
   276         return 0
       
   277 
       
   278 dict[Machiavelli3(1)] = Machiavelli3(0)
       
   279 dict[Machiavelli3(2)] = Machiavelli3(0)
       
   280 
       
   281 f = open(TESTFN, "w")
       
   282 try:
       
   283     try:
       
   284         print >> f, dict[Machiavelli3(2)]
       
   285     except KeyError:
       
   286         pass
       
   287 finally:
       
   288     f.close()
       
   289     os.unlink(TESTFN)
       
   290 
       
   291 del dict
       
   292 del dict1, dict2, dict1keys, dict2keys