1 import dis 2 import sys 3 from cStringIO import StringIO 4 import unittest 5 6 def disassemble(func): 7 f = StringIO() 8 tmp = sys.stdout 9 sys.stdout = f 10 dis.dis(func) 11 sys.stdout = tmp 12 result = f.getvalue() 13 f.close() 14 return result 15 16 def dis_single(line): 17 return disassemble(compile(line, '', 'single')) 18 19 class TestTranforms(unittest.TestCase): 20 21 def test_unot(self): 22 # UNARY_NOT POP_JUMP_IF_FALSE --> POP_JUMP_IF_TRUE 23 def unot(x): 24 if not x == 2: 25 del x 26 asm = disassemble(unot) 27 for elem in ('UNARY_NOT', 'POP_JUMP_IF_FALSE'): 28 self.assertNotIn(elem, asm) 29 self.assertIn('POP_JUMP_IF_TRUE', asm) 30 31 def test_elim_inversion_of_is_or_in(self): 32 for line, elem in ( 33 ('not a is b', '(is not)',), 34 ('not a in b', '(not in)',), 35 ('not a is not b', '(is)',), 36 ('not a not in b', '(in)',), 37 ): 38 asm = dis_single(line) 39 self.assertIn(elem, asm) 40 41 def test_none_as_constant(self): 42 # LOAD_GLOBAL None --> LOAD_CONST None 43 def f(x): 44 None 45 return x 46 asm = disassemble(f) 47 for elem in ('LOAD_GLOBAL',): 48 self.assertNotIn(elem, asm) 49 for elem in ('LOAD_CONST', '(None)'): 50 self.assertIn(elem, asm) 51 def f(): 52 'Adding a docstring made this test fail in Py2.5.0' 53 return None 54 self.assertIn('LOAD_CONST', disassemble(f)) 55 self.assertNotIn('LOAD_GLOBAL', disassemble(f)) 56 57 def test_while_one(self): 58 # Skip over: LOAD_CONST trueconst POP_JUMP_IF_FALSE xx 59 def f(): 60 while 1: 61 pass 62 return list 63 asm = disassemble(f) 64 for elem in ('LOAD_CONST', 'POP_JUMP_IF_FALSE'): 65 self.assertNotIn(elem, asm) 66 for elem in ('JUMP_ABSOLUTE',): 67 self.assertIn(elem, asm) 68 69 def test_pack_unpack(self): 70 for line, elem in ( 71 ('a, = a,', 'LOAD_CONST',), 72 ('a, b = a, b', 'ROT_TWO',), 73 ('a, b, c = a, b, c', 'ROT_THREE',), 74 ): 75 asm = dis_single(line) 76 self.assertIn(elem, asm) 77 self.assertNotIn('BUILD_TUPLE', asm) 78 self.assertNotIn('UNPACK_TUPLE', asm) 79 80 def test_folding_of_tuples_of_constants(self): 81 for line, elem in ( 82 ('a = 1,2,3', '((1, 2, 3))'), 83 ('("a","b","c")', "(('a', 'b', 'c'))"), 84 ('a,b,c = 1,2,3', '((1, 2, 3))'), 85 ('(None, 1, None)', '((None, 1, None))'), 86 ('((1, 2), 3, 4)', '(((1, 2), 3, 4))'), 87 ): 88 asm = dis_single(line) 89 self.assertIn(elem, asm) 90 self.assertNotIn('BUILD_TUPLE', asm) 91 92 # Bug 1053819: Tuple of constants misidentified when presented with: 93 # . . . opcode_with_arg 100 unary_opcode BUILD_TUPLE 1 . . . 94 # The following would segfault upon compilation 95 def crater(): 96 (~[ 97 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 98 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 99 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 101 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 102 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 103 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 104 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 105 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 106 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 107 ],) 108 109 def test_folding_of_binops_on_constants(self): 110 for line, elem in ( 111 ('a = 2+3+4', '(9)'), # chained fold 112 ('"@"*4', "('@@@@')"), # check string ops 113 ('a="abc" + "def"', "('abcdef')"), # check string ops 114 ('a = 3**4', '(81)'), # binary power 115 ('a = 3*4', '(12)'), # binary multiply 116 ('a = 13//4', '(3)'), # binary floor divide 117 ('a = 14%4', '(2)'), # binary modulo 118 ('a = 2+3', '(5)'), # binary add 119 ('a = 13-4', '(9)'), # binary subtract 120 ('a = (12,13)[1]', '(13)'), # binary subscr 121 ('a = 13 << 2', '(52)'), # binary lshift 122 ('a = 13 >> 2', '(3)'), # binary rshift 123 ('a = 13 & 7', '(5)'), # binary and 124 ('a = 13 ^ 7', '(10)'), # binary xor 125 ('a = 13 | 7', '(15)'), # binary or 126 ): 127 asm = dis_single(line) 128 self.assertIn(elem, asm, asm) 129 self.assertNotIn('BINARY_', asm) 130 131 # Verify that unfoldables are skipped 132 asm = dis_single('a=2+"b"') 133 self.assertIn('(2)', asm) 134 self.assertIn("('b')", asm) 135 136 # Verify that large sequences do not result from folding 137 asm = dis_single('a="x"*1000') 138 self.assertIn('(1000)', asm) 139 140 def test_binary_subscr_on_unicode(self): 141 # valid code get optimized 142 asm = dis_single('u"foo"[0]') 143 self.assertIn("(u'f')", asm) 144 self.assertNotIn('BINARY_SUBSCR', asm) 145 asm = dis_single('u"\u0061\uffff"[1]') 146 self.assertIn("(u'\\uffff')", asm) 147 self.assertNotIn('BINARY_SUBSCR', asm) 148 149 # invalid code doesn't get optimized 150 # out of range 151 asm = dis_single('u"fuu"[10]') 152 self.assertIn('BINARY_SUBSCR', asm) 153 # non-BMP char (see #5057) 154 asm = dis_single('u"\U00012345"[0]') 155 self.assertIn('BINARY_SUBSCR', asm) 156 157 158 def test_folding_of_unaryops_on_constants(self): 159 for line, elem in ( 160 ('`1`', "('1')"), # unary convert 161 ('-0.5', '(-0.5)'), # unary negative 162 ('~-2', '(1)'), # unary invert 163 ): 164 asm = dis_single(line) 165 self.assertIn(elem, asm, asm) 166 self.assertNotIn('UNARY_', asm) 167 168 # Verify that unfoldables are skipped 169 for line, elem in ( 170 ('-"abc"', "('abc')"), # unary negative 171 ('~"abc"', "('abc')"), # unary invert 172 ): 173 asm = dis_single(line) 174 self.assertIn(elem, asm, asm) 175 self.assertIn('UNARY_', asm) 176 177 def test_elim_extra_return(self): 178 # RETURN LOAD_CONST None RETURN --> RETURN 179 def f(x): 180 return x 181 asm = disassemble(f) 182 self.assertNotIn('LOAD_CONST', asm) 183 self.assertNotIn('(None)', asm) 184 self.assertEqual(asm.split().count('RETURN_VALUE'), 1) 185 186 def test_elim_jump_to_return(self): 187 # JUMP_FORWARD to RETURN --> RETURN 188 def f(cond, true_value, false_value): 189 return true_value if cond else false_value 190 asm = disassemble(f) 191 self.assertNotIn('JUMP_FORWARD', asm) 192 self.assertNotIn('JUMP_ABSOLUTE', asm) 193 self.assertEqual(asm.split().count('RETURN_VALUE'), 2) 194 195 def test_elim_jump_after_return1(self): 196 # Eliminate dead code: jumps immediately after returns can't be reached 197 def f(cond1, cond2): 198 if cond1: return 1 199 if cond2: return 2 200 while 1: 201 return 3 202 while 1: 203 if cond1: return 4 204 return 5 205 return 6 206 asm = disassemble(f) 207 self.assertNotIn('JUMP_FORWARD', asm) 208 self.assertNotIn('JUMP_ABSOLUTE', asm) 209 self.assertEqual(asm.split().count('RETURN_VALUE'), 6) 210 211 def test_elim_jump_after_return2(self): 212 # Eliminate dead code: jumps immediately after returns can't be reached 213 def f(cond1, cond2): 214 while 1: 215 if cond1: return 4 216 asm = disassemble(f) 217 self.assertNotIn('JUMP_FORWARD', asm) 218 # There should be one jump for the while loop. 219 self.assertEqual(asm.split().count('JUMP_ABSOLUTE'), 1) 220 self.assertEqual(asm.split().count('RETURN_VALUE'), 2) 221 222 223 def test_main(verbose=None): 224 import sys 225 from test import test_support 226 test_classes = (TestTranforms,) 227 228 with test_support.check_py3k_warnings( 229 ("backquote not supported", SyntaxWarning)): 230 test_support.run_unittest(*test_classes) 231 232 # verify reference counting 233 if verbose and hasattr(sys, "gettotalrefcount"): 234 import gc 235 counts = [None] * 5 236 for i in xrange(len(counts)): 237 test_support.run_unittest(*test_classes) 238 gc.collect() 239 counts[i] = sys.gettotalrefcount() 240 print counts 241 242 if __name__ == "__main__": 243 test_main(verbose=True) 244