this repo has no description
1#!/usr/bin/env python3
2# Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
3import asyncio
4import io
5import unittest
6from types import CodeType
7
8try:
9 from _compiler_opcode import opcode
10except ImportError:
11 import opcode
12from test_support import pyro_only
13
14
15def _dis_instruction(opcode, code: CodeType, op: int, oparg: int): # noqa: C901
16 result = opcode.opname[op]
17 if op < opcode.HAVE_ARGUMENT and oparg == 0:
18 oparg_str = None
19 elif op in opcode.hasconst:
20 cnst = code.co_consts[oparg]
21 co_name = getattr(cnst, "co_name", None)
22 if co_name:
23 oparg_str = f"{oparg}:code object {co_name}"
24 else:
25 oparg_str = repr(code.co_consts[oparg])
26 elif op in opcode.hasname:
27 oparg_str = code.co_names[oparg]
28 elif op in (
29 opcode.LOAD_FAST_REVERSE_UNCHECKED,
30 opcode.STORE_FAST_REVERSE,
31 opcode.DELETE_FAST_REVERSE_UNCHECKED,
32 ):
33 total_locals = (
34 len(code.co_varnames) + len(code.co_cellvars) + len(code.co_freevars)
35 )
36 oparg_str = code.co_varnames[total_locals - oparg - 1]
37 elif op in opcode.haslocal:
38 oparg_str = code.co_varnames[oparg]
39 elif op in opcode.hascompare:
40 oparg_str = opcode.CMP_OP[oparg]
41 elif result == "FORMAT_VALUE":
42 fvc = oparg & opcode.FVC_MASK
43 if fvc == opcode.FVC_STR:
44 oparg_str = "str"
45 elif fvc == opcode.FVC_REPR:
46 oparg_str = "repr"
47 elif fvc == opcode.FVC_ASCII:
48 oparg_str = "ascii"
49 else:
50 assert fvc == opcode.FVC_NONE
51 oparg_str = None
52 if (oparg & opcode.FVS_HAVE_SPEC) != 0:
53 oparg_str = "" if oparg_str is None else (oparg_str + " ")
54 oparg_str += "have_spec"
55 else:
56 oparg_str = str(oparg)
57 if oparg_str is not None:
58 result = f"{result} {oparg_str}"
59 return result
60
61
62def _dis_code(opcode, lines, code: CodeType):
63 bytecode = code.co_code
64 bytecode_len = len(bytecode)
65 i = 0
66 while i < bytecode_len:
67 op = bytecode[i]
68 oparg = bytecode[i + 1]
69 i += opcode.CODEUNIT_SIZE
70 while op == opcode.EXTENDED_ARG:
71 op = bytecode[i]
72 oparg = oparg << 8 | bytecode[i + 1]
73 i += opcode.CODEUNIT_SIZE
74 lines.append(_dis_instruction(opcode, code, op, oparg))
75 lines.append("")
76 for idx, cnst in enumerate(code.co_consts):
77 co_code = getattr(cnst, "co_code", None)
78 if co_code:
79 lines.append(f"# {idx}:code object {cnst.co_name}")
80 _dis_code(opcode, lines, cnst)
81
82
83def simpledis(opcode, code: CodeType):
84 """Simple disassembler, showing only opcode names + args.
85 Compare to the `dis` module: This producers simpler output making it easier
86 to match in tests. The `opcode` module is parameterized."""
87 lines = []
88 _dis_code(opcode, lines, code)
89 return "\n".join(lines)
90
91
92def test_compile(source, filename="<test>", mode="eval", flags=0, optimize=True):
93 return compile(source, filename, mode, flags, None, optimize)
94
95
96def compile_function(source, func_name):
97 module_code = test_compile(source, mode="exec")
98 globals = {}
99 locals = {}
100 exec(module_code, globals, locals)
101 return locals[func_name]
102
103
104def dis(code):
105 return simpledis(opcode, code)
106
107
108@pyro_only
109class StrModOptimizer(unittest.TestCase):
110 def test_constant_folds(self):
111 source = "'%s %% foo %r bar %a %s' % (1, 'baz', 3, 4)"
112 self.assertEqual(
113 dis(test_compile(source)),
114 """\
115LOAD_CONST "1 % foo 'baz' bar 3 4"
116RETURN_VALUE
117""",
118 )
119
120 def test_empty_args(self):
121 source = "'foo' % ()"
122 self.assertEqual(
123 dis(test_compile(source)),
124 """\
125LOAD_CONST 'foo'
126RETURN_VALUE
127""",
128 )
129
130 @pyro_only
131 def test_empty_args_executes(self):
132 source = "'foo' % ()"
133 self.assertEqual(eval(test_compile(source)), "foo") # noqa: P204
134
135 def test_percent_a_uses_format_value(self):
136 source = "'%a' % (x,)"
137 self.assertEqual(
138 dis(test_compile(source)),
139 """\
140LOAD_NAME x
141FORMAT_VALUE ascii
142RETURN_VALUE
143""",
144 )
145
146 @pyro_only
147 def test_percent_a_executes(self):
148 source = "'%a' % (x,)"
149 self.assertEqual(
150 eval(test_compile(source), {"x": "\u1234"}), "'\\u1234'" # noqa: P204
151 )
152
153 def test_percent_percent(self):
154 source = "'T%%est' % ()"
155 self.assertEqual(
156 dis(test_compile(source)),
157 """\
158LOAD_CONST 'T%est'
159RETURN_VALUE
160""",
161 )
162
163 def test_percent_r_uses_format_value(self):
164 source = "'%r' % (x,)"
165 self.assertEqual(
166 dis(test_compile(source)),
167 """\
168LOAD_NAME x
169FORMAT_VALUE repr
170RETURN_VALUE
171""",
172 )
173
174 @pyro_only
175 def test_percent_r_executes(self):
176 source = "'%r' % (x,)"
177 self.assertEqual(
178 eval(test_compile(source), {"x": "bar"}), "'bar'" # noqa: P204
179 )
180
181 def test_percent_s_uses_format_value(self):
182 source = "'%s' % (x,)"
183 self.assertEqual(
184 dis(test_compile(source)),
185 """\
186LOAD_NAME x
187FORMAT_VALUE str
188RETURN_VALUE
189""",
190 )
191
192 @pyro_only
193 def test_percent_s_executes(self):
194 source = "'%s' % (x,)"
195 self.assertEqual(eval(test_compile(source), {"x": "bar"}), "bar") # noqa: P204
196
197 def test_percent_s_with_width_uses_format_value(self):
198 source = "'%13s' % (x,)"
199 self.assertEqual(
200 dis(test_compile(source)),
201 """\
202LOAD_NAME x
203LOAD_CONST '>13'
204FORMAT_VALUE str have_spec
205RETURN_VALUE
206""",
207 )
208
209 @pyro_only
210 def test_percent_s_with_width_executes(self):
211 source = "'%7s' % (x,)"
212 self.assertEqual(
213 eval(test_compile(source), {"x": 4231}), " 4231" # noqa: P204
214 )
215
216 def test_percent_d_i_u_calls_convert_and_formats_value(self):
217 expected = """\
218LOAD_CONST ''
219LOAD_METHOD _mod_convert_number_int
220LOAD_NAME x
221CALL_METHOD 1
222FORMAT_VALUE
223RETURN_VALUE
224"""
225 source0 = "'%d' % (x,)"
226 source1 = "'%i' % (x,)"
227 source2 = "'%u' % (x,)"
228 self.assertEqual(dis(test_compile(source0)), expected)
229 self.assertEqual(dis(test_compile(source1)), expected)
230 self.assertEqual(dis(test_compile(source2)), expected)
231
232 @pyro_only
233 def test_percent_d_i_u_executes(self):
234 class C:
235 def __index__(self):
236 raise Exception("should not be called")
237
238 def __int__(self):
239 return 13
240
241 source0 = "'%d' % (x,)"
242 source1 = "'%i' % (x,)"
243 source2 = "'%u' % (x,)"
244 self.assertEqual(eval(test_compile(source0), {"x": -42}), "-42") # noqa: P204
245 self.assertEqual(
246 eval(test_compile(source1), {"x": 0x1234}), "4660" # noqa: P204
247 )
248 self.assertEqual(eval(test_compile(source2), {"x": C()}), "13") # noqa: P204
249
250 @pyro_only
251 def test_percent_d_raises_type_error(self):
252 class C:
253 pass
254
255 source = "'%d' % (x,)"
256 code = test_compile(source)
257 with self.assertRaisesRegex(TypeError, "format requires a number, not C"):
258 eval(code, None, {"x": C()}) # noqa: P204
259
260 def test_percent_i_with_width_and_zero_uses_format_value_with_spec(self):
261 source = "'%05d' % (x,)"
262 self.assertEqual(
263 dis(test_compile(source)),
264 """\
265LOAD_CONST ''
266LOAD_METHOD _mod_convert_number_int
267LOAD_NAME x
268CALL_METHOD 1
269LOAD_CONST '05'
270FORMAT_VALUE have_spec
271RETURN_VALUE
272""",
273 )
274
275 def test_percent_o_calls_convert_and_formats_value(self):
276 source = "'%5o' % (x,)"
277 self.assertEqual(
278 dis(test_compile(source)),
279 """\
280LOAD_CONST ''
281LOAD_METHOD _mod_convert_number_index
282LOAD_NAME x
283CALL_METHOD 1
284LOAD_CONST '5o'
285FORMAT_VALUE have_spec
286RETURN_VALUE
287""",
288 )
289
290 @pyro_only
291 def test_percent_o_executes(self):
292 class C:
293 def __index__(self):
294 return -93
295
296 def __int__(self):
297 raise Exception("should not be called")
298
299 source = "'%5o' % (x,)"
300 self.assertEqual(eval(test_compile(source), {"x": C()}), " -135") # noqa: P204
301
302 def test_percent_x_calls_convert_and_formats_value(self):
303 source = "'%03x' % (x,)"
304 self.assertEqual(
305 dis(test_compile(source)),
306 """\
307LOAD_CONST ''
308LOAD_METHOD _mod_convert_number_index
309LOAD_NAME x
310CALL_METHOD 1
311LOAD_CONST '03x'
312FORMAT_VALUE have_spec
313RETURN_VALUE
314""",
315 )
316
317 @pyro_only
318 def test_percent_x_executes(self):
319 class C:
320 def __index__(self):
321 return -11
322
323 def __int__(self):
324 raise Exception("should not be called")
325
326 source = "'%04x' % (x,)"
327 self.assertEqual(eval(test_compile(source), {"x": C()}), "-00b") # noqa: P204
328
329 def test_percent_X_calls_convert_and_formats_value(self):
330 source = "'%4X' % (x,)"
331 self.assertEqual(
332 dis(test_compile(source)),
333 """\
334LOAD_CONST ''
335LOAD_METHOD _mod_convert_number_index
336LOAD_NAME x
337CALL_METHOD 1
338LOAD_CONST '4X'
339FORMAT_VALUE have_spec
340RETURN_VALUE
341""",
342 )
343
344 @pyro_only
345 def test_percent_X_executes(self):
346 class C:
347 def __index__(self):
348 return 51966
349
350 def __int__(self):
351 raise Exception("should not be called")
352
353 source = "'%04X' % (x,)"
354 self.assertEqual(eval(test_compile(source), {"x": C()}), "CAFE") # noqa: P204
355
356 def test_mixed_format_uses_format_value_and_build_string(self):
357 source = "'%s %% foo %r bar %a %s' % (a, b, c, d)"
358 self.assertEqual(
359 dis(test_compile(source)),
360 """\
361LOAD_NAME a
362FORMAT_VALUE str
363LOAD_CONST ' '
364LOAD_CONST '% foo '
365LOAD_NAME b
366FORMAT_VALUE repr
367LOAD_CONST ' bar '
368LOAD_NAME c
369FORMAT_VALUE ascii
370LOAD_CONST ' '
371LOAD_NAME d
372FORMAT_VALUE str
373BUILD_STRING 8
374RETURN_VALUE
375""",
376 )
377
378 @pyro_only
379 def test_mixed_format_executes(self):
380 source = "'%s %% foo %r bar %a %s' % (a, b, c, d)"
381 locals = {"a": 1, "b": 2, "c": 3, "d": 4}
382 self.assertEqual(
383 eval(test_compile(source), locals), "1 % foo 2 bar 3 4" # noqa: P204
384 )
385
386 def test_no_tuple_arg_calls_check_single_arg(self):
387 source = "'%s' % x"
388 self.assertEqual(
389 dis(test_compile(source)),
390 """\
391LOAD_CONST ''
392LOAD_METHOD _mod_check_single_arg
393LOAD_NAME x
394CALL_METHOD 1
395LOAD_CONST 0
396BINARY_SUBSCR
397FORMAT_VALUE str
398RETURN_VALUE
399""",
400 )
401
402 @pyro_only
403 def test_no_tuple_arg_executes(self):
404 source = "'%s' % x"
405 self.assertEqual(eval(test_compile(source), {"x": 5.5}), "5.5") # noqa: P204
406
407 @pyro_only
408 def test_no_tuple_raises_type_error_not_enough_arguments(self):
409 source = "'%s' % x"
410 with self.assertRaisesRegex(
411 TypeError, "not enough arguments for format string"
412 ):
413 eval(test_compile(source), {"x": ()}) # noqa: P204
414
415 @pyro_only
416 def test_no_tuple_raises_type_error_not_all_converted(self):
417 source = "'%s' % x"
418 with self.assertRaisesRegex(
419 TypeError, "not all arguments converted during string formatting"
420 ):
421 eval(test_compile(source), {"x": (1, 2)}) # noqa: P204
422
423 def test_trailing_percent_it_not_optimized(self):
424 source = "'foo%' % (x,)"
425 self.assertIn("BINARY_MODULO", dis(test_compile(source)))
426
427 def test_multiple_specs_without_tuple_is_not_optimized(self):
428 source = "'%s%s' % x"
429 self.assertIn("BINARY_MODULO", dis(test_compile(source)))
430
431 def test_spec_with_mapping_key_is_not_optimized(self):
432 source = "'%(%s)' % x"
433 self.assertIn("BINARY_MODULO", dis(test_compile(source)))
434
435 def test_with_too_small_arg_tuple_is_not_optimized(self):
436 source = "'%s%s%s' % (x,y)"
437 self.assertIn("BINARY_MODULO", dis(test_compile(source)))
438
439 def test_with_too_large_arg_tuple_is_not_optimized(self):
440 source = "'%s%s%s' % (w,x,y,z)"
441 self.assertIn("BINARY_MODULO", dis(test_compile(source)))
442
443 def test_with_unknown_specifier_is_not_optimized(self):
444 source = "'%Z' % (x,)"
445 self.assertIn("BINARY_MODULO", dis(test_compile(source)))
446
447
448@pyro_only
449class CustomOpcodeTests(unittest.TestCase):
450 def test_is_generates_COMPARE_IS(self):
451 source = "a is b"
452 self.assertEqual(
453 dis(test_compile(source)),
454 """\
455LOAD_NAME a
456LOAD_NAME b
457COMPARE_IS 0
458RETURN_VALUE
459""",
460 )
461
462 def test_is_not_generates_COMPARE_IS_NOT(self):
463 source = "a is not b"
464 self.assertEqual(
465 dis(test_compile(source)),
466 """\
467LOAD_NAME a
468LOAD_NAME b
469COMPARE_IS_NOT 0
470RETURN_VALUE
471""",
472 )
473
474
475@pyro_only
476class InlineComprehensionTests(unittest.TestCase):
477 def test_list_comprehension_code_is_inline(self):
478 source = "[x for x in y]"
479 self.assertEqual(
480 dis(test_compile(source)),
481 """\
482BUILD_LIST 0
483LOAD_NAME y
484GET_ITER
485FOR_ITER 8
486STORE_NAME _gen$x
487LOAD_NAME _gen$x
488LIST_APPEND 2
489JUMP_ABSOLUTE 6
490RETURN_VALUE
491""",
492 )
493
494 @pyro_only
495 def test_list_comprehension_code_executes(self):
496 source = "[x for x in y]"
497 self.assertEqual(
498 eval(test_compile(source), {"y": range(4)}), [0, 1, 2, 3] # noqa: P204
499 )
500
501 def test_set_comprehension_code_is_inline(self):
502 source = "{x for x in y}"
503 self.assertEqual(
504 dis(test_compile(source)),
505 """\
506BUILD_SET 0
507LOAD_NAME y
508GET_ITER
509FOR_ITER 8
510STORE_NAME _gen$x
511LOAD_NAME _gen$x
512SET_ADD 2
513JUMP_ABSOLUTE 6
514RETURN_VALUE
515""",
516 )
517
518 @pyro_only
519 def test_set_comprehension_code_executes(self):
520 source = "{x for x in y}"
521 self.assertEqual(
522 eval(test_compile(source), {"y": range(4)}), {0, 1, 2, 3} # noqa: P204
523 )
524
525 def test_dict_comprehension_code_is_inline(self):
526 source = "{x:-x for x in y}"
527 self.assertEqual(
528 dis(test_compile(source)),
529 """\
530BUILD_MAP 0
531LOAD_NAME y
532GET_ITER
533FOR_ITER 12
534STORE_NAME _gen$x
535LOAD_NAME _gen$x
536LOAD_NAME _gen$x
537UNARY_NEGATIVE
538MAP_ADD 2
539JUMP_ABSOLUTE 6
540RETURN_VALUE
541""",
542 )
543
544 @pyro_only
545 def test_dict_comprehension_code_executes(self):
546 source = "{x:-x for x in y}"
547 self.assertEqual(
548 eval(test_compile(source), {"y": range(4)}), # noqa: P204
549 {0: 0, 1: -1, 2: -2, 3: -3},
550 )
551
552 def test_nested_comprehension_in_iterator_code_is_inline(self):
553 source = "[-x for x in [x for x in x if x > 3]]"
554 self.assertEqual(
555 (dis(test_compile(source))),
556 """\
557BUILD_LIST 0
558BUILD_LIST 0
559LOAD_NAME x
560GET_ITER
561FOR_ITER 16
562STORE_NAME _gen$x
563LOAD_NAME _gen$x
564LOAD_CONST 3
565COMPARE_OP >
566POP_JUMP_IF_FALSE 8
567LOAD_NAME _gen$x
568LIST_APPEND 2
569JUMP_ABSOLUTE 8
570GET_ITER
571FOR_ITER 10
572STORE_NAME _gen1$x
573LOAD_NAME _gen1$x
574UNARY_NEGATIVE
575LIST_APPEND 2
576JUMP_ABSOLUTE 28
577RETURN_VALUE
578""",
579 )
580
581 @pyro_only
582 def test_nested_comprehension_in_iterator_executes(self):
583 source = "[-x for x in [x for x in x if x > 3]]"
584 self.assertEqual(
585 eval(test_compile(source), {"x": range(8)}), [-4, -5, -6, -7] # noqa: P204
586 )
587
588 def test_nested_comprehension_in_element_code_is_inline(self):
589 source = "[[x for x in range(x) if x & 1 == 1] for x in x]"
590 self.assertEqual(
591 (dis(test_compile(source))),
592 """\
593BUILD_LIST 0
594LOAD_NAME x
595GET_ITER
596FOR_ITER 38
597STORE_NAME _gen$x
598BUILD_LIST 0
599LOAD_NAME range
600LOAD_NAME _gen$x
601CALL_FUNCTION 1
602GET_ITER
603FOR_ITER 20
604STORE_NAME _gen1$_gen$x
605LOAD_NAME _gen1$_gen$x
606LOAD_CONST 1
607BINARY_AND
608LOAD_CONST 1
609COMPARE_OP ==
610POP_JUMP_IF_FALSE 20
611LOAD_NAME _gen1$_gen$x
612LIST_APPEND 2
613JUMP_ABSOLUTE 20
614LIST_APPEND 2
615JUMP_ABSOLUTE 6
616RETURN_VALUE
617""",
618 )
619
620 @pyro_only
621 def test_nested_comprehension_in_element_executes(self):
622 source = "[[x for x in range(x) if x & 1 == 1] for x in x]"
623 self.assertEqual(
624 eval(test_compile(source), {"x": range(5)}), # noqa: P204
625 [[], [], [1], [1], [1, 3]],
626 )
627
628 def test_nested_lambda_in_comprehension_has_arg_renamed(self):
629 source = "[lambda x: x for x in r]"
630 self.assertEqual(
631 (dis(test_compile(source))),
632 """\
633BUILD_LIST 0
634LOAD_NAME r
635GET_ITER
636FOR_ITER 12
637STORE_NAME _gen$x
638LOAD_CONST 0:code object <lambda>
639LOAD_CONST '<lambda>'
640MAKE_FUNCTION 0
641LIST_APPEND 2
642JUMP_ABSOLUTE 6
643RETURN_VALUE
644
645# 0:code object <lambda>
646LOAD_FAST_REVERSE_UNCHECKED _gen$x
647RETURN_VALUE
648""",
649 )
650
651 @pyro_only
652 def test_nested_lambda_in_comprehension_executes(self):
653 source = "[lambda x: x for x in r]"
654 lambdas = eval(test_compile(source), {"r": range(3)}) # noqa: P204
655 self.assertEqual(lambdas[0](22), 22)
656 self.assertEqual(lambdas[1]("foo"), "foo")
657 self.assertEqual(lambdas[2](int), int)
658
659 def test_multiple_comprehensions_use_unique_prefixes(self):
660 source = "[lambda: x for x in r], [x for x in (42,)]"
661 self.assertEqual(
662 (dis(test_compile(source))),
663 """\
664BUILD_LIST 0
665LOAD_NAME r
666GET_ITER
667FOR_ITER 12
668STORE_NAME _gen$x
669LOAD_CONST 0:code object <lambda>
670LOAD_CONST '<lambda>'
671MAKE_FUNCTION 0
672LIST_APPEND 2
673JUMP_ABSOLUTE 6
674BUILD_LIST 0
675LOAD_CONST (42,)
676GET_ITER
677FOR_ITER 8
678STORE_NAME _gen1$x
679LOAD_NAME _gen1$x
680LIST_APPEND 2
681JUMP_ABSOLUTE 26
682BUILD_TUPLE 2
683RETURN_VALUE
684
685# 0:code object <lambda>
686LOAD_GLOBAL _gen$x
687RETURN_VALUE
688""",
689 )
690
691 @pyro_only
692 def test_multiple_comprehensions_executes(self):
693 source = "[lambda: x for x in r], [x for x in (42,)]"
694 lambdas, dummy = eval(test_compile(source), {"r": range(13, 15)}) # noqa: P204
695 self.assertEqual(lambdas[0](), 14)
696 self.assertEqual(lambdas[1](), 14)
697
698 def test_renamer_with_locals_in_list_comprehension_does_not_rename(self):
699 iterable = [1]
700 result = [locals().keys() for item in iterable][0]
701 self.assertEqual(sorted(result), [".0", "item"])
702
703 def test_renamer_with_locals_in_dict_comprehension_does_not_rename(self):
704 iterable = [1]
705 result = {"key": locals().keys() for item in iterable}["key"]
706 self.assertEqual(sorted(result), [".0", "item"])
707
708 def test_renamer_with_locals_in_set_comprehension_does_not_rename(self):
709 iterable = [1]
710 result = {locals().keys() for item in iterable}.pop()
711 self.assertEqual(sorted(result), [".0", "item"])
712
713 def test_renamer_with_locals_in_gen_comprehension_does_not_rename(self):
714 iterable = [1]
715 result = tuple(locals().keys() for item in iterable)[0]
716 self.assertEqual(sorted(result), [".0", "item"])
717
718
719@pyro_only
720class DefiniteAssignmentAnalysisTests(unittest.TestCase):
721 def test_load_parameter_is_unchecked(self):
722 source = """
723def foo(x):
724 return x
725"""
726 func = compile_function(source, "foo")
727 self.assertEqual(
728 dis(func.__code__),
729 """\
730LOAD_FAST_REVERSE_UNCHECKED x
731RETURN_VALUE
732""",
733 )
734 self.assertEqual(func(123), 123)
735
736 def test_load_kwonly_parameter_is_unchecked(self):
737 source = """
738def foo(*, x):
739 return x
740"""
741 func = compile_function(source, "foo")
742 self.assertEqual(
743 dis(func.__code__),
744 """\
745LOAD_FAST_REVERSE_UNCHECKED x
746RETURN_VALUE
747""",
748 )
749 self.assertEqual(func(x=123), 123)
750
751 def test_load_varargs_parameter_is_unchecked(self):
752 source = """
753def foo(*x):
754 return x
755"""
756 func = compile_function(source, "foo")
757 self.assertEqual(
758 dis(func.__code__),
759 """\
760LOAD_FAST_REVERSE_UNCHECKED x
761RETURN_VALUE
762""",
763 )
764 self.assertEqual(func(123), (123,))
765
766 def test_load_varkeyargs_parameter_is_unchecked(self):
767 source = """
768def foo(**x):
769 return x
770"""
771 func = compile_function(source, "foo")
772 self.assertEqual(
773 dis(func.__code__),
774 """\
775LOAD_FAST_REVERSE_UNCHECKED x
776RETURN_VALUE
777""",
778 )
779 self.assertEqual(func(a=123), {"a": 123})
780
781 def test_load_parameter_with_del_is_checked(self):
782 source = """
783def foo(x):
784 del x
785 return x
786"""
787 func = compile_function(source, "foo")
788 self.assertEqual(
789 dis(func.__code__),
790 """\
791DELETE_FAST x
792LOAD_FAST x
793RETURN_VALUE
794""",
795 )
796 with self.assertRaisesRegex(
797 UnboundLocalError, "local variable 'x' referenced before assignment"
798 ):
799 func(123)
800
801 def test_load_parameter_with_del_after_is_unchecked(self):
802 source = """
803def foo(x):
804 y = x
805 del x
806 return y
807"""
808 func = compile_function(source, "foo")
809 self.assertEqual(
810 dis(func.__code__),
811 """\
812LOAD_FAST_REVERSE_UNCHECKED x
813STORE_FAST_REVERSE y
814DELETE_FAST x
815LOAD_FAST_REVERSE_UNCHECKED y
816RETURN_VALUE
817""",
818 )
819 self.assertEqual(func(123), 123)
820
821 def test_variable_defined_in_local_is_unchecked(self):
822 source = """
823def foo():
824 x = 3
825 return x
826"""
827 func = compile_function(source, "foo")
828 self.assertEqual(
829 dis(func.__code__),
830 """\
831LOAD_CONST 3
832STORE_FAST_REVERSE x
833LOAD_FAST_REVERSE_UNCHECKED x
834RETURN_VALUE
835""",
836 )
837 self.assertEqual(func(), 3)
838
839 def test_variable_defined_in_one_branch_is_checked(self):
840 source = """
841def foo(cond):
842 if cond:
843 x = 3
844 return x
845"""
846 func = compile_function(source, "foo")
847 self.assertEqual(
848 dis(func.__code__),
849 """\
850DELETE_FAST_REVERSE_UNCHECKED x
851LOAD_FAST_REVERSE_UNCHECKED cond
852POP_JUMP_IF_FALSE 10
853LOAD_CONST 3
854STORE_FAST_REVERSE x
855LOAD_FAST x
856RETURN_VALUE
857""",
858 )
859 self.assertEqual(func(True), 3)
860 with self.assertRaisesRegex(
861 UnboundLocalError, "local variable 'x' referenced before assignment"
862 ):
863 func(False)
864
865 def test_variable_defined_in_one_branch_if_else_is_checked(self):
866 source = """
867def foo(cond):
868 if cond:
869 x = 3
870 else:
871 pass
872 return x
873"""
874 func = compile_function(source, "foo")
875 self.assertEqual(
876 dis(func.__code__),
877 """\
878DELETE_FAST_REVERSE_UNCHECKED x
879LOAD_FAST_REVERSE_UNCHECKED cond
880POP_JUMP_IF_FALSE 12
881LOAD_CONST 3
882STORE_FAST_REVERSE x
883JUMP_FORWARD 0
884LOAD_FAST x
885RETURN_VALUE
886""",
887 )
888 self.assertEqual(func(True), 3)
889 with self.assertRaisesRegex(
890 UnboundLocalError, "local variable 'x' referenced before assignment"
891 ):
892 func(False)
893
894 def test_variable_defined_in_both_branches_if_else_is_unchecked(self):
895 source = """
896def foo(cond):
897 if cond:
898 x = 3
899 else:
900 x = 4
901 return x
902"""
903 func = compile_function(source, "foo")
904 self.assertEqual(
905 dis(func.__code__),
906 """\
907LOAD_FAST_REVERSE_UNCHECKED cond
908POP_JUMP_IF_FALSE 10
909LOAD_CONST 3
910STORE_FAST_REVERSE x
911JUMP_FORWARD 4
912LOAD_CONST 4
913STORE_FAST_REVERSE x
914LOAD_FAST_REVERSE_UNCHECKED x
915RETURN_VALUE
916""",
917 )
918 self.assertEqual(func(True), 3)
919 self.assertEqual(func(False), 4)
920
921 def test_loop_is_unchecked(self):
922 source = """
923def foo(cond):
924 while True:
925 print(cond)
926"""
927 self.assertEqual(
928 dis(compile_function(source, "foo").__code__),
929 """\
930LOAD_GLOBAL print
931LOAD_FAST_REVERSE_UNCHECKED cond
932CALL_FUNCTION 1
933POP_TOP
934JUMP_ABSOLUTE 0
935LOAD_CONST None
936RETURN_VALUE
937""",
938 )
939
940 def test_loop_with_del_is_checked(self):
941 source = """
942def foo(cond):
943 while True:
944 1 + cond # use it
945 del cond
946"""
947 func = compile_function(source, "foo")
948 self.assertEqual(
949 dis(func.__code__),
950 """\
951LOAD_CONST 1
952LOAD_FAST cond
953BINARY_ADD
954POP_TOP
955DELETE_FAST cond
956JUMP_ABSOLUTE 0
957LOAD_CONST None
958RETURN_VALUE
959""",
960 )
961 with self.assertRaisesRegex(
962 UnboundLocalError, "local variable 'cond' referenced before assignment"
963 ):
964 func(True)
965
966 def test_loop_variable_in_for_loop_is_unchecked(self):
967 source = """
968def foo(n):
969 result = []
970 for i in range(n):
971 result.append(i)
972 return result
973"""
974 func = compile_function(source, "foo")
975 self.assertEqual(
976 dis(func.__code__),
977 """\
978BUILD_LIST 0
979STORE_FAST_REVERSE result
980LOAD_GLOBAL range
981LOAD_FAST_REVERSE_UNCHECKED n
982CALL_FUNCTION 1
983GET_ITER
984FOR_ITER 14
985STORE_FAST_REVERSE i
986LOAD_FAST_REVERSE_UNCHECKED result
987LOAD_METHOD append
988LOAD_FAST_REVERSE_UNCHECKED i
989CALL_METHOD 1
990POP_TOP
991JUMP_ABSOLUTE 12
992LOAD_FAST_REVERSE_UNCHECKED result
993RETURN_VALUE
994""",
995 )
996 self.assertEqual(func(3), [0, 1, 2])
997
998 def test_loop_variable_after_for_loop_is_checked(self):
999 source = """
1000def foo(n):
1001 for i in range(n):
1002 pass
1003 return i
1004"""
1005 func = compile_function(source, "foo")
1006 self.assertEqual(
1007 dis(func.__code__),
1008 """\
1009DELETE_FAST_REVERSE_UNCHECKED i
1010LOAD_GLOBAL range
1011LOAD_FAST_REVERSE_UNCHECKED n
1012CALL_FUNCTION 1
1013GET_ITER
1014FOR_ITER 4
1015STORE_FAST_REVERSE i
1016JUMP_ABSOLUTE 10
1017LOAD_FAST i
1018RETURN_VALUE
1019""",
1020 )
1021 self.assertEqual(func(3), 2)
1022
1023 def test_loop_variable_in_async_for_loop_is_unchecked(self):
1024 source = """
1025async def foo(n):
1026 result = []
1027 async for i in range(n):
1028 result.append(i)
1029 return result
1030"""
1031 func = compile_function(source, "foo")
1032 self.assertEqual(
1033 dis(func.__code__),
1034 """\
1035BUILD_LIST 0
1036STORE_FAST_REVERSE result
1037LOAD_GLOBAL range
1038LOAD_FAST_REVERSE_UNCHECKED n
1039CALL_FUNCTION 1
1040GET_AITER
1041SETUP_FINALLY 22
1042GET_ANEXT
1043LOAD_CONST None
1044YIELD_FROM
1045POP_BLOCK
1046STORE_FAST_REVERSE i
1047LOAD_FAST_REVERSE_UNCHECKED result
1048LOAD_METHOD append
1049LOAD_FAST_REVERSE_UNCHECKED i
1050CALL_METHOD 1
1051POP_TOP
1052JUMP_ABSOLUTE 12
1053END_ASYNC_FOR
1054LOAD_FAST_REVERSE_UNCHECKED result
1055RETURN_VALUE
1056""",
1057 )
1058 # TODO(emacs): Figure out how to get the NameError for CM to go away.
1059 return
1060 self.assertEqual(asyncio.run(func(3)), [0, 1, 2])
1061
1062 def test_loop_variable_after_async_for_loop_is_checked(self):
1063 source = """
1064import asyncio
1065
1066async def arange(n):
1067 for i in range(n):
1068 yield i
1069 asyncio.sleep(0)
1070
1071async def foo(n):
1072 async for i in arange(n):
1073 pass
1074 return i
1075"""
1076 func = compile_function(source, "foo")
1077 self.assertEqual(
1078 dis(func.__code__),
1079 """\
1080DELETE_FAST_REVERSE_UNCHECKED i
1081LOAD_GLOBAL arange
1082LOAD_FAST_REVERSE_UNCHECKED n
1083CALL_FUNCTION 1
1084GET_AITER
1085SETUP_FINALLY 12
1086GET_ANEXT
1087LOAD_CONST None
1088YIELD_FROM
1089POP_BLOCK
1090STORE_FAST_REVERSE i
1091JUMP_ABSOLUTE 10
1092END_ASYNC_FOR
1093LOAD_FAST i
1094RETURN_VALUE
1095""",
1096 )
1097 # TODO(emacs): Figure out how to get the NameError for CM to go away.
1098 return
1099 self.assertEqual(asyncio.run(func(3)), 2)
1100
1101 def test_try_with_use_in_try_is_unchecked(self):
1102 source = """
1103def foo():
1104 try:
1105 x = 123
1106 return x
1107 except:
1108 pass
1109"""
1110 func = compile_function(source, "foo")
1111 self.assertEqual(
1112 dis(func.__code__),
1113 """\
1114SETUP_FINALLY 10
1115LOAD_CONST 123
1116STORE_FAST_REVERSE x
1117LOAD_FAST_REVERSE_UNCHECKED x
1118POP_BLOCK
1119RETURN_VALUE
1120POP_TOP
1121POP_TOP
1122POP_TOP
1123POP_EXCEPT
1124JUMP_FORWARD 2
1125END_FINALLY
1126LOAD_CONST None
1127RETURN_VALUE
1128""",
1129 )
1130 self.assertEqual(func(), 123)
1131
1132 @unittest.skip(
1133 "TODO(emacs): This should be unchecked but we need to tail-duplicate finally blocks"
1134 )
1135 def test_try_with_use_in_else_is_unchecked(self):
1136 source = """
1137def foo():
1138 try:
1139 x = 123
1140 except:
1141 pass
1142 else:
1143 return x
1144"""
1145 func = compile_function(source, "foo")
1146 self.assertEqual(
1147 dis(func.__code__),
1148 """\
1149SETUP_FINALLY 8
1150LOAD_CONST 123
1151STORE_FAST_REVERSE x
1152POP_BLOCK
1153JUMP_FORWARD 12
1154POP_TOP
1155POP_TOP
1156POP_TOP
1157POP_EXCEPT
1158JUMP_FORWARD 6
1159END_FINALLY
1160LOAD_FAST_REVERSE_UNCHECKED x
1161RETURN_VALUE
1162LOAD_CONST None
1163RETURN_VALUE
1164""",
1165 )
1166 self.assertEqual(func(), 123)
1167
1168 def test_try_with_use_after_except_is_checked(self):
1169 source = """
1170def foo():
1171 try:
1172 x = 123
1173 except:
1174 pass
1175 return x
1176"""
1177 func = compile_function(source, "foo")
1178 self.assertEqual(
1179 dis(func.__code__),
1180 """\
1181DELETE_FAST_REVERSE_UNCHECKED x
1182SETUP_FINALLY 8
1183LOAD_CONST 123
1184STORE_FAST_REVERSE x
1185POP_BLOCK
1186JUMP_FORWARD 12
1187POP_TOP
1188POP_TOP
1189POP_TOP
1190POP_EXCEPT
1191JUMP_FORWARD 2
1192END_FINALLY
1193LOAD_FAST x
1194RETURN_VALUE
1195""",
1196 )
1197 self.assertEqual(func(), 123)
1198
1199 def test_try_with_use_in_except_is_checked(self):
1200 source = """
1201def foo():
1202 try:
1203 x = 123
1204 except:
1205 return x
1206"""
1207 func = compile_function(source, "foo")
1208 self.assertEqual(
1209 dis(func.__code__),
1210 """\
1211DELETE_FAST_REVERSE_UNCHECKED x
1212SETUP_FINALLY 8
1213LOAD_CONST 123
1214STORE_FAST_REVERSE x
1215POP_BLOCK
1216JUMP_FORWARD 16
1217POP_TOP
1218POP_TOP
1219POP_TOP
1220LOAD_FAST x
1221ROT_FOUR
1222POP_EXCEPT
1223RETURN_VALUE
1224END_FINALLY
1225LOAD_CONST None
1226RETURN_VALUE
1227""",
1228 )
1229 self.assertEqual(func(), None)
1230
1231 def test_try_with_use_in_finally_is_checked(self):
1232 source = """
1233def foo():
1234 try:
1235 x = 123
1236 finally:
1237 return x
1238"""
1239 func = compile_function(source, "foo")
1240 self.assertEqual(
1241 dis(func.__code__),
1242 """\
1243DELETE_FAST_REVERSE_UNCHECKED x
1244LOAD_CONST None
1245SETUP_FINALLY 8
1246LOAD_CONST 123
1247STORE_FAST_REVERSE x
1248POP_BLOCK
1249BEGIN_FINALLY
1250LOAD_FAST x
1251POP_FINALLY 1
1252ROT_TWO
1253POP_TOP
1254RETURN_VALUE
1255END_FINALLY
1256POP_TOP
1257""",
1258 )
1259 self.assertEqual(func(), 123)
1260
1261 def test_try_with_use_after_finally_is_unchecked(self):
1262 source = """
1263def foo():
1264 try:
1265 x = 123
1266 finally:
1267 x = 456
1268 return x
1269"""
1270 func = compile_function(source, "foo")
1271 self.assertEqual(
1272 dis(func.__code__),
1273 """\
1274SETUP_FINALLY 8
1275LOAD_CONST 123
1276STORE_FAST_REVERSE x
1277POP_BLOCK
1278BEGIN_FINALLY
1279LOAD_CONST 456
1280STORE_FAST_REVERSE x
1281END_FINALLY
1282LOAD_FAST_REVERSE_UNCHECKED x
1283RETURN_VALUE
1284""",
1285 )
1286 self.assertEqual(func(), 456)
1287
1288 def test_try_with_use_after_finally_is_unchecked_2(self):
1289 source = """
1290def foo():
1291 try:
1292 pass
1293 finally:
1294 x = 456
1295 return x
1296"""
1297 func = compile_function(source, "foo")
1298 self.assertEqual(
1299 dis(func.__code__),
1300 """\
1301SETUP_FINALLY 4
1302POP_BLOCK
1303BEGIN_FINALLY
1304LOAD_CONST 456
1305STORE_FAST_REVERSE x
1306END_FINALLY
1307LOAD_FAST_REVERSE_UNCHECKED x
1308RETURN_VALUE
1309""",
1310 )
1311 self.assertEqual(func(), 456)
1312
1313 def test_use_in_with_is_unchecked(self):
1314 source = """
1315class CM(object):
1316 def __init__(self):
1317 self.state = "init"
1318 def __enter__(self):
1319 self.state = "enter"
1320 def __exit__(self, type, value, traceback):
1321 self.state = "exit"
1322
1323def foo():
1324 with CM() as cm:
1325 return cm
1326"""
1327 func = compile_function(source, "foo")
1328 self.assertEqual(
1329 dis(func.__code__),
1330 """\
1331LOAD_GLOBAL CM
1332CALL_FUNCTION 0
1333SETUP_WITH 18
1334STORE_FAST_REVERSE cm
1335LOAD_FAST_REVERSE_UNCHECKED cm
1336POP_BLOCK
1337ROT_TWO
1338BEGIN_FINALLY
1339WITH_CLEANUP_START
1340WITH_CLEANUP_FINISH
1341POP_FINALLY 0
1342RETURN_VALUE
1343WITH_CLEANUP_START
1344WITH_CLEANUP_FINISH
1345END_FINALLY
1346LOAD_CONST None
1347RETURN_VALUE
1348""",
1349 )
1350 # TODO(emacs): Figure out how to get the NameError for CM to go away.
1351 return
1352 self.assertEqual(func(), 456)
1353
1354 def test_use_after_with_is_checked(self):
1355 source = """
1356class CM(object):
1357 def __init__(self):
1358 self.state = "init"
1359 def __enter__(self):
1360 self.state = "enter"
1361 def __exit__(self, type, value, traceback):
1362 self.state = "exit"
1363
1364def foo():
1365 with CM() as cm:
1366 pass
1367 return cm
1368"""
1369 func = compile_function(source, "foo")
1370 self.assertEqual(
1371 dis(func.__code__),
1372 """\
1373DELETE_FAST_REVERSE_UNCHECKED cm
1374LOAD_GLOBAL CM
1375CALL_FUNCTION 0
1376SETUP_WITH 6
1377STORE_FAST_REVERSE cm
1378POP_BLOCK
1379BEGIN_FINALLY
1380WITH_CLEANUP_START
1381WITH_CLEANUP_FINISH
1382END_FINALLY
1383LOAD_FAST cm
1384RETURN_VALUE
1385""",
1386 )
1387 # TODO(emacs): Figure out how to get the NameError for CM to go away.
1388 return
1389 self.assertEqual(func(), 456)
1390
1391 def test_use_in_async_with_is_unchecked(self):
1392 source = """
1393class CM(object):
1394 def __init__(self):
1395 self.state = "init"
1396 def __aenter__(self):
1397 self.state = "enter"
1398 def __aexit__(self, type, value, traceback):
1399 self.state = "exit"
1400
1401async def foo():
1402 async with CM() as cm:
1403 return cm
1404"""
1405 func = compile_function(source, "foo")
1406 self.assertEqual(
1407 dis(func.__code__),
1408 """\
1409LOAD_GLOBAL CM
1410CALL_FUNCTION 0
1411BEFORE_ASYNC_WITH
1412GET_AWAITABLE
1413LOAD_CONST None
1414YIELD_FROM
1415SETUP_ASYNC_WITH 24
1416STORE_FAST_REVERSE cm
1417LOAD_FAST_REVERSE_UNCHECKED cm
1418POP_BLOCK
1419ROT_TWO
1420BEGIN_FINALLY
1421WITH_CLEANUP_START
1422GET_AWAITABLE
1423LOAD_CONST None
1424YIELD_FROM
1425WITH_CLEANUP_FINISH
1426POP_FINALLY 0
1427RETURN_VALUE
1428WITH_CLEANUP_START
1429GET_AWAITABLE
1430LOAD_CONST None
1431YIELD_FROM
1432WITH_CLEANUP_FINISH
1433END_FINALLY
1434LOAD_CONST None
1435RETURN_VALUE
1436""",
1437 )
1438 # TODO(emacs): Figure out how to get the NameError for CM to go away.
1439 return
1440 self.assertEqual(func(), 456)
1441
1442 def test_use_after_async_with_is_checked(self):
1443 source = """
1444class CM(object):
1445 def __init__(self):
1446 self.state = "init"
1447 def __aenter__(self):
1448 self.state = "enter"
1449 def __aexit__(self, type, value, traceback):
1450 self.state = "exit"
1451
1452async def foo():
1453 async with CM() as cm:
1454 pass
1455 return cm
1456"""
1457 func = compile_function(source, "foo")
1458 self.assertEqual(
1459 dis(func.__code__),
1460 """\
1461DELETE_FAST_REVERSE_UNCHECKED cm
1462LOAD_GLOBAL CM
1463CALL_FUNCTION 0
1464BEFORE_ASYNC_WITH
1465GET_AWAITABLE
1466LOAD_CONST None
1467YIELD_FROM
1468SETUP_ASYNC_WITH 6
1469STORE_FAST_REVERSE cm
1470POP_BLOCK
1471BEGIN_FINALLY
1472WITH_CLEANUP_START
1473GET_AWAITABLE
1474LOAD_CONST None
1475YIELD_FROM
1476WITH_CLEANUP_FINISH
1477END_FINALLY
1478LOAD_FAST cm
1479RETURN_VALUE
1480""",
1481 )
1482 # TODO(emacs): Figure out how to get the NameError for CM to go away.
1483 return
1484 self.assertEqual(func(), 456)
1485
1486 # TODO(emacs): Test with (multiple context managers)
1487
1488
1489if __name__ == "__main__":
1490 unittest.main()