• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import dis
2import re
3import sys
4import textwrap
5import unittest
6
7from test.bytecode_helper import BytecodeTestCase
8
9class TestTranforms(BytecodeTestCase):
10
11    def test_unot(self):
12        # UNARY_NOT POP_JUMP_IF_FALSE  -->  POP_JUMP_IF_TRUE'
13        def unot(x):
14            if not x == 2:
15                del x
16        self.assertNotInBytecode(unot, 'UNARY_NOT')
17        self.assertNotInBytecode(unot, 'POP_JUMP_IF_FALSE')
18        self.assertInBytecode(unot, 'POP_JUMP_IF_TRUE')
19
20    def test_elim_inversion_of_is_or_in(self):
21        for line, cmp_op in (
22            ('not a is b', 'is not',),
23            ('not a in b', 'not in',),
24            ('not a is not b', 'is',),
25            ('not a not in b', 'in',),
26            ):
27            code = compile(line, '', 'single')
28            self.assertInBytecode(code, 'COMPARE_OP', cmp_op)
29
30    def test_global_as_constant(self):
31        # LOAD_GLOBAL None/True/False  -->  LOAD_CONST None/True/False
32        def f():
33            x = None
34            x = None
35            return x
36        def g():
37            x = True
38            return x
39        def h():
40            x = False
41            return x
42
43        for func, elem in ((f, None), (g, True), (h, False)):
44            self.assertNotInBytecode(func, 'LOAD_GLOBAL')
45            self.assertInBytecode(func, 'LOAD_CONST', elem)
46
47        def f():
48            'Adding a docstring made this test fail in Py2.5.0'
49            return None
50
51        self.assertNotInBytecode(f, 'LOAD_GLOBAL')
52        self.assertInBytecode(f, 'LOAD_CONST', None)
53
54    def test_while_one(self):
55        # Skip over:  LOAD_CONST trueconst  POP_JUMP_IF_FALSE xx
56        def f():
57            while 1:
58                pass
59            return list
60        for elem in ('LOAD_CONST', 'POP_JUMP_IF_FALSE'):
61            self.assertNotInBytecode(f, elem)
62        for elem in ('JUMP_ABSOLUTE',):
63            self.assertInBytecode(f, elem)
64
65    def test_pack_unpack(self):
66        for line, elem in (
67            ('a, = a,', 'LOAD_CONST',),
68            ('a, b = a, b', 'ROT_TWO',),
69            ('a, b, c = a, b, c', 'ROT_THREE',),
70            ):
71            code = compile(line,'','single')
72            self.assertInBytecode(code, elem)
73            self.assertNotInBytecode(code, 'BUILD_TUPLE')
74            self.assertNotInBytecode(code, 'UNPACK_TUPLE')
75
76    def test_folding_of_tuples_of_constants(self):
77        for line, elem in (
78            ('a = 1,2,3', (1, 2, 3)),
79            ('("a","b","c")', ('a', 'b', 'c')),
80            ('a,b,c = 1,2,3', (1, 2, 3)),
81            ('(None, 1, None)', (None, 1, None)),
82            ('((1, 2), 3, 4)', ((1, 2), 3, 4)),
83            ):
84            code = compile(line,'','single')
85            self.assertInBytecode(code, 'LOAD_CONST', elem)
86            self.assertNotInBytecode(code, 'BUILD_TUPLE')
87
88        # Long tuples should be folded too.
89        code = compile(repr(tuple(range(10000))),'','single')
90        self.assertNotInBytecode(code, 'BUILD_TUPLE')
91        # One LOAD_CONST for the tuple, one for the None return value
92        load_consts = [instr for instr in dis.get_instructions(code)
93                              if instr.opname == 'LOAD_CONST']
94        self.assertEqual(len(load_consts), 2)
95
96        # Bug 1053819:  Tuple of constants misidentified when presented with:
97        # . . . opcode_with_arg 100   unary_opcode   BUILD_TUPLE 1  . . .
98        # The following would segfault upon compilation
99        def crater():
100            (~[
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                0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
108                0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
109                0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
110                0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
111            ],)
112
113    def test_folding_of_lists_of_constants(self):
114        for line, elem in (
115            # in/not in constants with BUILD_LIST should be folded to a tuple:
116            ('a in [1,2,3]', (1, 2, 3)),
117            ('a not in ["a","b","c"]', ('a', 'b', 'c')),
118            ('a in [None, 1, None]', (None, 1, None)),
119            ('a not in [(1, 2), 3, 4]', ((1, 2), 3, 4)),
120            ):
121            code = compile(line, '', 'single')
122            self.assertInBytecode(code, 'LOAD_CONST', elem)
123            self.assertNotInBytecode(code, 'BUILD_LIST')
124
125    def test_folding_of_sets_of_constants(self):
126        for line, elem in (
127            # in/not in constants with BUILD_SET should be folded to a frozenset:
128            ('a in {1,2,3}', frozenset({1, 2, 3})),
129            ('a not in {"a","b","c"}', frozenset({'a', 'c', 'b'})),
130            ('a in {None, 1, None}', frozenset({1, None})),
131            ('a not in {(1, 2), 3, 4}', frozenset({(1, 2), 3, 4})),
132            ('a in {1, 2, 3, 3, 2, 1}', frozenset({1, 2, 3})),
133            ):
134            code = compile(line, '', 'single')
135            self.assertNotInBytecode(code, 'BUILD_SET')
136            self.assertInBytecode(code, 'LOAD_CONST', elem)
137
138        # Ensure that the resulting code actually works:
139        def f(a):
140            return a in {1, 2, 3}
141
142        def g(a):
143            return a not in {1, 2, 3}
144
145        self.assertTrue(f(3))
146        self.assertTrue(not f(4))
147
148        self.assertTrue(not g(3))
149        self.assertTrue(g(4))
150
151
152    def test_folding_of_binops_on_constants(self):
153        for line, elem in (
154            ('a = 2+3+4', 9),                   # chained fold
155            ('"@"*4', '@@@@'),                  # check string ops
156            ('a="abc" + "def"', 'abcdef'),      # check string ops
157            ('a = 3**4', 81),                   # binary power
158            ('a = 3*4', 12),                    # binary multiply
159            ('a = 13//4', 3),                   # binary floor divide
160            ('a = 14%4', 2),                    # binary modulo
161            ('a = 2+3', 5),                     # binary add
162            ('a = 13-4', 9),                    # binary subtract
163            ('a = (12,13)[1]', 13),             # binary subscr
164            ('a = 13 << 2', 52),                # binary lshift
165            ('a = 13 >> 2', 3),                 # binary rshift
166            ('a = 13 & 7', 5),                  # binary and
167            ('a = 13 ^ 7', 10),                 # binary xor
168            ('a = 13 | 7', 15),                 # binary or
169            ):
170            code = compile(line, '', 'single')
171            self.assertInBytecode(code, 'LOAD_CONST', elem)
172            for instr in dis.get_instructions(code):
173                self.assertFalse(instr.opname.startswith('BINARY_'))
174
175        # Verify that unfoldables are skipped
176        code = compile('a=2+"b"', '', 'single')
177        self.assertInBytecode(code, 'LOAD_CONST', 2)
178        self.assertInBytecode(code, 'LOAD_CONST', 'b')
179
180        # Verify that large sequences do not result from folding
181        code = compile('a="x"*1000', '', 'single')
182        self.assertInBytecode(code, 'LOAD_CONST', 1000)
183
184    def test_binary_subscr_on_unicode(self):
185        # valid code get optimized
186        code = compile('"foo"[0]', '', 'single')
187        self.assertInBytecode(code, 'LOAD_CONST', 'f')
188        self.assertNotInBytecode(code, 'BINARY_SUBSCR')
189        code = compile('"\u0061\uffff"[1]', '', 'single')
190        self.assertInBytecode(code, 'LOAD_CONST', '\uffff')
191        self.assertNotInBytecode(code,'BINARY_SUBSCR')
192
193        # With PEP 393, non-BMP char get optimized
194        code = compile('"\U00012345"[0]', '', 'single')
195        self.assertInBytecode(code, 'LOAD_CONST', '\U00012345')
196        self.assertNotInBytecode(code, 'BINARY_SUBSCR')
197
198        # invalid code doesn't get optimized
199        # out of range
200        code = compile('"fuu"[10]', '', 'single')
201        self.assertInBytecode(code, 'BINARY_SUBSCR')
202
203    def test_folding_of_unaryops_on_constants(self):
204        for line, elem in (
205            ('-0.5', -0.5),                     # unary negative
206            ('-0.0', -0.0),                     # -0.0
207            ('-(1.0-1.0)', -0.0),               # -0.0 after folding
208            ('-0', 0),                          # -0
209            ('~-2', 1),                         # unary invert
210            ('+1', 1),                          # unary positive
211        ):
212            code = compile(line, '', 'single')
213            self.assertInBytecode(code, 'LOAD_CONST', elem)
214            for instr in dis.get_instructions(code):
215                self.assertFalse(instr.opname.startswith('UNARY_'))
216
217        # Check that -0.0 works after marshaling
218        def negzero():
219            return -(1.0-1.0)
220
221        for instr in dis.get_instructions(code):
222            self.assertFalse(instr.opname.startswith('UNARY_'))
223
224        # Verify that unfoldables are skipped
225        for line, elem, opname in (
226            ('-"abc"', 'abc', 'UNARY_NEGATIVE'),
227            ('~"abc"', 'abc', 'UNARY_INVERT'),
228        ):
229            code = compile(line, '', 'single')
230            self.assertInBytecode(code, 'LOAD_CONST', elem)
231            self.assertInBytecode(code, opname)
232
233    def test_elim_extra_return(self):
234        # RETURN LOAD_CONST None RETURN  -->  RETURN
235        def f(x):
236            return x
237        self.assertNotInBytecode(f, 'LOAD_CONST', None)
238        returns = [instr for instr in dis.get_instructions(f)
239                          if instr.opname == 'RETURN_VALUE']
240        self.assertEqual(len(returns), 1)
241
242    def test_elim_jump_to_return(self):
243        # JUMP_FORWARD to RETURN -->  RETURN
244        def f(cond, true_value, false_value):
245            return true_value if cond else false_value
246        self.assertNotInBytecode(f, 'JUMP_FORWARD')
247        self.assertNotInBytecode(f, 'JUMP_ABSOLUTE')
248        returns = [instr for instr in dis.get_instructions(f)
249                          if instr.opname == 'RETURN_VALUE']
250        self.assertEqual(len(returns), 2)
251
252    def test_elim_jump_after_return1(self):
253        # Eliminate dead code: jumps immediately after returns can't be reached
254        def f(cond1, cond2):
255            if cond1: return 1
256            if cond2: return 2
257            while 1:
258                return 3
259            while 1:
260                if cond1: return 4
261                return 5
262            return 6
263        self.assertNotInBytecode(f, 'JUMP_FORWARD')
264        self.assertNotInBytecode(f, 'JUMP_ABSOLUTE')
265        returns = [instr for instr in dis.get_instructions(f)
266                          if instr.opname == 'RETURN_VALUE']
267        self.assertEqual(len(returns), 6)
268
269    def test_elim_jump_after_return2(self):
270        # Eliminate dead code: jumps immediately after returns can't be reached
271        def f(cond1, cond2):
272            while 1:
273                if cond1: return 4
274        self.assertNotInBytecode(f, 'JUMP_FORWARD')
275        # There should be one jump for the while loop.
276        returns = [instr for instr in dis.get_instructions(f)
277                          if instr.opname == 'JUMP_ABSOLUTE']
278        self.assertEqual(len(returns), 1)
279        returns = [instr for instr in dis.get_instructions(f)
280                          if instr.opname == 'RETURN_VALUE']
281        self.assertEqual(len(returns), 2)
282
283    def test_make_function_doesnt_bail(self):
284        def f():
285            def g()->1+1:
286                pass
287            return g
288        self.assertNotInBytecode(f, 'BINARY_ADD')
289
290    def test_constant_folding(self):
291        # Issue #11244: aggressive constant folding.
292        exprs = [
293            '3 * -5',
294            '-3 * 5',
295            '2 * (3 * 4)',
296            '(2 * 3) * 4',
297            '(-1, 2, 3)',
298            '(1, -2, 3)',
299            '(1, 2, -3)',
300            '(1, 2, -3) * 6',
301            'lambda x: x in {(3 * -5) + (-1 - 6), (1, -2, 3) * 2, None}',
302        ]
303        for e in exprs:
304            code = compile(e, '', 'single')
305            for instr in dis.get_instructions(code):
306                self.assertFalse(instr.opname.startswith('UNARY_'))
307                self.assertFalse(instr.opname.startswith('BINARY_'))
308                self.assertFalse(instr.opname.startswith('BUILD_'))
309
310
311class TestBuglets(unittest.TestCase):
312
313    def test_bug_11510(self):
314        # folded constant set optimization was commingled with the tuple
315        # unpacking optimization which would fail if the set had duplicate
316        # elements so that the set length was unexpected
317        def f():
318            x, y = {1, 1}
319            return x, y
320        with self.assertRaises(ValueError):
321            f()
322
323
324if __name__ == "__main__":
325    unittest.main()
326