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