From 23efd8eb6f30a84e0626b8f7db28afbd9e846973 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 6 Jul 2019 00:37:13 +0100 Subject: [PATCH 1/9] bpo-37500: Make sure bytecode for always false conditional is not emitted Add a new field to the compiler structure that allows to be configured so no bytecode is emitted. In this way is possible to detect errors by walking the nodes while preserving optimizations. Add a specific tests for dead blocks being optimized. --- Lib/test/test_compile.py | 13 +++++++++++++ Python/compile.c | 40 ++++++++++++++++++++++++++++++++++++---- Python/peephole.c | 15 ++++----------- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 56f73f631534348..1f9eb9820c95cba 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -697,6 +697,19 @@ def test_stack_overflow(self): # complex statements. compile("if a: b\n" * 200000, "", "exec") + # Multiple users rely on the fact that CPython does not generate + # bytecode for dead code blocks. See bpo-37500 for more context. + @support.cpython_only + def test_dead_blocks_do_not_generate_bytecode(self): + def unused_block(): + if 0: + return 42 + opcodes = list(dis.get_instructions(unused_block)) + self.assertEqual(2, len(opcodes)) + self.assertEqual('LOAD_CONST', opcodes[0].opname) + self.assertEqual(None, opcodes[0].argval) + self.assertEqual('RETURN_VALUE', opcodes[1].opname) + class TestExpressionStackSize(unittest.TestCase): # These tests check that the computed stack size for a code object diff --git a/Python/compile.c b/Python/compile.c index 9cce8aeb4e1f638..defbc73585ad0c1 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -161,6 +161,9 @@ struct compiler { int c_optimize; /* optimization level */ int c_interactive; /* true if in interactive mode */ int c_nestlevel; + int c_emit_bytecode; /* The compiler won't emmit any bytecode + if this flag is false. This can be used + to visit nodes to check only errors. */ PyObject *c_const_cache; /* Python dict holding all constants, including names tuple */ @@ -340,6 +343,7 @@ PyAST_CompileObject(mod_ty mod, PyObject *filename, PyCompilerFlags *flags, c.c_flags = flags; c.c_optimize = (optimize == -1) ? config->optimization_level : optimize; c.c_nestlevel = 0; + c.c_emit_bytecode = 1; if (!_PyAST_Optimize(mod, arena, c.c_optimize)) { goto finally; @@ -1152,6 +1156,9 @@ compiler_addop(struct compiler *c, int opcode) struct instr *i; int off; assert(!HAS_ARG(opcode)); + if (!c->c_emit_bytecode) { + return 1; + } off = compiler_next_instr(c, c->u->u_curblock); if (off < 0) return 0; @@ -1359,6 +1366,10 @@ compiler_addop_i(struct compiler *c, int opcode, Py_ssize_t oparg) struct instr *i; int off; + if (!c->c_emit_bytecode) { + return 1; + } + /* oparg value is unsigned, but a signed C int is usually used to store it in the C code (like Python/ceval.c). @@ -1385,6 +1396,10 @@ compiler_addop_j(struct compiler *c, int opcode, basicblock *b, int absolute) struct instr *i; int off; + if (!c->c_emit_bytecode) { + return 1; + } + assert(HAS_ARG(opcode)); assert(b != NULL); off = compiler_next_instr(c, c->u->u_curblock); @@ -1519,6 +1534,17 @@ compiler_addop_j(struct compiler *c, int opcode, basicblock *b, int absolute) } \ } +/* These macros allows to check only for errors and not emmit bytecode + * while visiting nodes. +*/ + +#define BEGIN_DO_NOT_EMIT_BYTECODE { \ + c->c_emit_bytecode = 0; + +#define END_DO_NOT_EMIT_BYTECODE \ + c->c_emit_bytecode = 1; \ +} + /* Search if variable annotations are present statically in a block. */ static int @@ -2546,12 +2572,18 @@ compiler_if(struct compiler *c, stmt_ty s) return 0; constant = expr_constant(s->v.If.test); - /* constant = 0: "if 0" Leave the optimizations to - * the pephole optimizer to check for syntax errors - * in the block. + /* constant = 0: "if 0" * constant = 1: "if 1", "if 2", ... * constant = -1: rest */ - if (constant == 1) { + if (constant == 0) { + if (s->v.If.orelse) { + VISIT_SEQ(c, stmt, s->v.If.orelse); + } else{ + BEGIN_DO_NOT_EMIT_BYTECODE + VISIT_SEQ(c, stmt, s->v.If.body); + END_DO_NOT_EMIT_BYTECODE + } + } else if (constant == 1) { VISIT_SEQ(c, stmt, s->v.If.body); } else { if (asdl_seq_LEN(s->v.If.orelse)) { diff --git a/Python/peephole.c b/Python/peephole.c index 3e56e788b0079b5..caf6f1ed2dfa75e 100644 --- a/Python/peephole.c +++ b/Python/peephole.c @@ -306,18 +306,11 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names, case LOAD_CONST: cumlc = lastlc + 1; if (nextop != POP_JUMP_IF_FALSE || - !ISBASICBLOCK(blocks, op_start, i + 1)) { + !ISBASICBLOCK(blocks, op_start, i + 1) || + !PyObject_IsTrue(PyList_GET_ITEM(consts, get_arg(codestr, i)))) break; - } - PyObject* cnt = PyList_GET_ITEM(consts, get_arg(codestr, i)); - int is_true = PyObject_IsTrue(cnt); - if (is_true == -1) { - goto exitError; - } - if (is_true == 1) { - fill_nops(codestr, op_start, nexti + 1); - cumlc = 0; - } + fill_nops(codestr, op_start, nexti + 1); + cumlc = 0; break; /* Try to fold tuples of constants. From b58ad27289708e49ecf3aaf60b310bfe255b6eb4 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 6 Jul 2019 02:22:12 +0100 Subject: [PATCH 2/9] Add more tests and skip more dead code --- Lib/test/test_syntax.py | 20 ++++++++++++++++++++ Python/compile.c | 15 ++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 8451c072f64209b..21655d3a05c21f4 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -699,16 +699,36 @@ def test_break_outside_loop(self): def test_yield_outside_function(self): self._check_error("if 0: yield", "outside function") self._check_error("class C:\n if 0: yield", "outside function") + self._check_error("if 1: pass\nelse: yield", "outside function") + self._check_error("while 0: yield", "outside function") + self._check_error("class C:\n if 0: yield", "outside function") + self._check_error("class C:\n if 1: pass\n else: yield", + "outside function") + self._check_error("class C:\n while 0: yield", "outside function") def test_return_outside_function(self): self._check_error("if 0: return", "outside function") self._check_error("class C:\n if 0: return", "outside function") + self._check_error("if 1: pass\nelse: return", "outside function") + self._check_error("while 0: return", "outside function") + self._check_error("class C:\n if 0: return", "outside function") + self._check_error("class C:\n if 1: pass\n else: return", + "outside function") + self._check_error("class C:\n while 0: return", "outside function") def test_break_outside_loop(self): self._check_error("if 0: break", "outside loop") + self._check_error("if 1: pass\nelse: break", "outside loop") + self._check_error("class C:\n if 0: break", "outside loop") + self._check_error("class C:\n if 1: pass\n else: break", + "outside loop") def test_continue_outside_loop(self): self._check_error("if 0: continue", "not properly in loop") + self._check_error("if 1: pass\nelse: continue", "not properly in loop") + self._check_error("class C:\n if 0: continue", "not properly in loop") + self._check_error("class C:\n if 1: pass\n else: continue", + "not properly in loop") def test_unexpected_indent(self): self._check_error("foo()\n bar()\n", "unexpected indent", diff --git a/Python/compile.c b/Python/compile.c index defbc73585ad0c1..e4b9584305ef1d9 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1177,6 +1177,9 @@ compiler_add_o(struct compiler *c, PyObject *dict, PyObject *o) { PyObject *v; Py_ssize_t arg; + if (!c->c_emit_bytecode) { + return 1; + } v = PyDict_GetItemWithError(dict, o); if (!v) { @@ -2585,6 +2588,11 @@ compiler_if(struct compiler *c, stmt_ty s) } } else if (constant == 1) { VISIT_SEQ(c, stmt, s->v.If.body); + if (s->v.If.orelse) { + BEGIN_DO_NOT_EMIT_BYTECODE + VISIT_SEQ(c, stmt, s->v.If.orelse); + END_DO_NOT_EMIT_BYTECODE + } } else { if (asdl_seq_LEN(s->v.If.orelse)) { next = compiler_new_block(c); @@ -2694,8 +2702,13 @@ compiler_while(struct compiler *c, stmt_ty s) int constant = expr_constant(s->v.While.test); if (constant == 0) { - if (s->v.While.orelse) + if (s->v.While.orelse) { VISIT_SEQ(c, stmt, s->v.While.orelse); + } else { + BEGIN_DO_NOT_EMIT_BYTECODE + VISIT_SEQ(c, stmt, s->v.While.body); + END_DO_NOT_EMIT_BYTECODE + } return 1; } loop = compiler_new_block(c); From b314b72c8f396d8506858208f8150a1ed2808dd9 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 6 Jul 2019 03:24:41 +0100 Subject: [PATCH 3/9] fixup! Add more tests and skip more dead code --- Python/compile.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index e4b9584305ef1d9..7ebd8c54d781fdf 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -161,9 +161,10 @@ struct compiler { int c_optimize; /* optimization level */ int c_interactive; /* true if in interactive mode */ int c_nestlevel; - int c_emit_bytecode; /* The compiler won't emmit any bytecode + int c_emit_bytecode; /* The compiler won't emit any bytecode if this flag is false. This can be used - to visit nodes to check only errors. */ + to temporarily visit nodes without emitting + bytecode to check only errors. */ PyObject *c_const_cache; /* Python dict holding all constants, including names tuple */ From b47c97713901b10797e7de94d4273991d95db702 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 6 Jul 2019 15:47:51 +0100 Subject: [PATCH 4/9] Address Serhiy's feedback --- Lib/test/test_syntax.py | 38 +++++++++++++++++++++++--------------- Python/compile.c | 14 ++++++-------- Python/peephole.c | 15 +++++++++++---- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 21655d3a05c21f4..b776983bf37bca1 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -697,35 +697,43 @@ def test_break_outside_loop(self): self._check_error("break", "outside loop") def test_yield_outside_function(self): - self._check_error("if 0: yield", "outside function") - self._check_error("class C:\n if 0: yield", "outside function") - self._check_error("if 1: pass\nelse: yield", "outside function") - self._check_error("while 0: yield", "outside function") - self._check_error("class C:\n if 0: yield", "outside function") + self._check_error("if 0: yield", "outside function") + self._check_error("if 0: yield\nelse: x=1", "outside function") + self._check_error("class C:\n if 0: yield", "outside function") + self._check_error("if 1: pass\nelse: yield", "outside function") + self._check_error("while 0: yield", "outside function") + self._check_error("while 0: yield\nelse: x=1", "outside function") self._check_error("class C:\n if 1: pass\n else: yield", "outside function") self._check_error("class C:\n while 0: yield", "outside function") + self._check_error("class C:\n while 0: yield\n else: x = 1", + "outside function") def test_return_outside_function(self): - self._check_error("if 0: return", "outside function") - self._check_error("class C:\n if 0: return", "outside function") - self._check_error("if 1: pass\nelse: return", "outside function") - self._check_error("while 0: return", "outside function") - self._check_error("class C:\n if 0: return", "outside function") - self._check_error("class C:\n if 1: pass\n else: return", + self._check_error("if 0: return", "outside function") + self._check_error("if 0: return\nelse: x=1", "outside function") + self._check_error("class C:\n if 0: return", "outside function") + self._check_error("if 1: pass\nelse: return", "outside function") + self._check_error("while 0: return", "outside function") + self._check_error("class C:\n if 0: return", "outside function") + self._check_error("class C:\n if 0: return\n else: x= 1", "outside function") self._check_error("class C:\n while 0: return", "outside function") + self._check_error("class C:\n while 0: return\n else: x=1", + "outside function") def test_break_outside_loop(self): - self._check_error("if 0: break", "outside loop") - self._check_error("if 1: pass\nelse: break", "outside loop") + self._check_error("if 0: break", "outside loop") + self._check_error("if 0: break\nelse: x=1", "outside loop") + self._check_error("if 1: pass\nelse: break", "outside loop") self._check_error("class C:\n if 0: break", "outside loop") self._check_error("class C:\n if 1: pass\n else: break", "outside loop") def test_continue_outside_loop(self): - self._check_error("if 0: continue", "not properly in loop") - self._check_error("if 1: pass\nelse: continue", "not properly in loop") + self._check_error("if 0: continue", "not properly in loop") + self._check_error("if 0: continue\nelse: x=1", "not properly in loop") + self._check_error("if 1: pass\nelse: continue", "not properly in loop") self._check_error("class C:\n if 0: continue", "not properly in loop") self._check_error("class C:\n if 1: pass\n else: continue", "not properly in loop") diff --git a/Python/compile.c b/Python/compile.c index 7ebd8c54d781fdf..fec5d33c94765eb 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -2580,12 +2580,11 @@ compiler_if(struct compiler *c, stmt_ty s) * constant = 1: "if 1", "if 2", ... * constant = -1: rest */ if (constant == 0) { + BEGIN_DO_NOT_EMIT_BYTECODE + VISIT_SEQ(c, stmt, s->v.If.body); + END_DO_NOT_EMIT_BYTECODE if (s->v.If.orelse) { VISIT_SEQ(c, stmt, s->v.If.orelse); - } else{ - BEGIN_DO_NOT_EMIT_BYTECODE - VISIT_SEQ(c, stmt, s->v.If.body); - END_DO_NOT_EMIT_BYTECODE } } else if (constant == 1) { VISIT_SEQ(c, stmt, s->v.If.body); @@ -2703,12 +2702,11 @@ compiler_while(struct compiler *c, stmt_ty s) int constant = expr_constant(s->v.While.test); if (constant == 0) { + BEGIN_DO_NOT_EMIT_BYTECODE + VISIT_SEQ(c, stmt, s->v.While.body); + END_DO_NOT_EMIT_BYTECODE if (s->v.While.orelse) { VISIT_SEQ(c, stmt, s->v.While.orelse); - } else { - BEGIN_DO_NOT_EMIT_BYTECODE - VISIT_SEQ(c, stmt, s->v.While.body); - END_DO_NOT_EMIT_BYTECODE } return 1; } diff --git a/Python/peephole.c b/Python/peephole.c index caf6f1ed2dfa75e..3e56e788b0079b5 100644 --- a/Python/peephole.c +++ b/Python/peephole.c @@ -306,11 +306,18 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names, case LOAD_CONST: cumlc = lastlc + 1; if (nextop != POP_JUMP_IF_FALSE || - !ISBASICBLOCK(blocks, op_start, i + 1) || - !PyObject_IsTrue(PyList_GET_ITEM(consts, get_arg(codestr, i)))) + !ISBASICBLOCK(blocks, op_start, i + 1)) { break; - fill_nops(codestr, op_start, nexti + 1); - cumlc = 0; + } + PyObject* cnt = PyList_GET_ITEM(consts, get_arg(codestr, i)); + int is_true = PyObject_IsTrue(cnt); + if (is_true == -1) { + goto exitError; + } + if (is_true == 1) { + fill_nops(codestr, op_start, nexti + 1); + cumlc = 0; + } break; /* Try to fold tuples of constants. From 066210845932890f57aa85f22edb969267c286f8 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 6 Jul 2019 15:57:13 +0100 Subject: [PATCH 5/9] Add guards in other functions and remove them from compiler_add_o --- Python/compile.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index fec5d33c94765eb..1ade0e3794796ba 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1178,9 +1178,6 @@ compiler_add_o(struct compiler *c, PyObject *dict, PyObject *o) { PyObject *v; Py_ssize_t arg; - if (!c->c_emit_bytecode) { - return 1; - } v = PyDict_GetItemWithError(dict, o); if (!v) { @@ -1316,6 +1313,10 @@ merge_consts_recursive(struct compiler *c, PyObject *o) static Py_ssize_t compiler_add_const(struct compiler *c, PyObject *o) { + if (!c->c_emit_bytecode) { + return 0; + } + PyObject *key = merge_consts_recursive(c, o); if (key == NULL) { return -1; @@ -1329,6 +1330,10 @@ compiler_add_const(struct compiler *c, PyObject *o) static int compiler_addop_load_const(struct compiler *c, PyObject *o) { + if (!c->c_emit_bytecode) { + return 1; + } + Py_ssize_t arg = compiler_add_const(c, o); if (arg < 0) return 0; @@ -1339,6 +1344,10 @@ static int compiler_addop_o(struct compiler *c, int opcode, PyObject *dict, PyObject *o) { + if (!c->c_emit_bytecode) { + return 1; + } + Py_ssize_t arg = compiler_add_o(c, dict, o); if (arg < 0) return 0; @@ -1350,6 +1359,11 @@ compiler_addop_name(struct compiler *c, int opcode, PyObject *dict, PyObject *o) { Py_ssize_t arg; + + if (!c->c_emit_bytecode) { + return 1; + } + PyObject *mangled = _Py_Mangle(c->u->u_private, o); if (!mangled) return 0; From d6ffa317e5d4b1eb112542045094e80256df85ba Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 6 Jul 2019 16:03:10 +0100 Subject: [PATCH 6/9] Add more tests to test_compile --- Lib/test/test_compile.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 1f9eb9820c95cba..9d77f7af05d6fd0 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -701,14 +701,35 @@ def test_stack_overflow(self): # bytecode for dead code blocks. See bpo-37500 for more context. @support.cpython_only def test_dead_blocks_do_not_generate_bytecode(self): - def unused_block(): + def unused_block_if(): if 0: return 42 - opcodes = list(dis.get_instructions(unused_block)) - self.assertEqual(2, len(opcodes)) - self.assertEqual('LOAD_CONST', opcodes[0].opname) - self.assertEqual(None, opcodes[0].argval) - self.assertEqual('RETURN_VALUE', opcodes[1].opname) + + def unused_block_while(): + while 0: + return 42 + + def unused_block_if_else(): + if 1: + return None + else: + return 42 + + def unused_block_while_else(): + while 1: + return None + else: + return 42 + + funcs = [unused_block_if, unused_block_while, + unused_block_if_else, unused_block_while_else] + + for func in funcs: + opcodes = list(dis.get_instructions(func)) + self.assertEqual(2, len(opcodes)) + self.assertEqual('LOAD_CONST', opcodes[0].opname) + self.assertEqual(None, opcodes[0].argval) + self.assertEqual('RETURN_VALUE', opcodes[1].opname) class TestExpressionStackSize(unittest.TestCase): From 169ce596cdfb4130ce0d84f007f56c306f8ba272 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 6 Jul 2019 16:12:29 +0100 Subject: [PATCH 7/9] Add more tests to sys_settrace --- Lib/test/test_sys_settrace.py | 48 ++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index 112ea877205abc0..fdd789475d04dc4 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -53,22 +53,52 @@ def basic(): # following that clause? -# The entire "while 0:" statement is optimized away. No code -# exists for it, so the line numbers skip directly from "del x" -# to "x = 1". -def arigo_example(): +# Some constructs like "while 0:", "if 0:" or "if 1:...else:..." are optimized +# away. No code # exists for them, so the line numbers skip directly from +# "del x" to "x = 1". +def arigo_example0(): x = 1 del x while 0: pass x = 1 -arigo_example.events = [(0, 'call'), +arigo_example0.events = [(0, 'call'), (1, 'line'), (2, 'line'), (5, 'line'), (5, 'return')] +def arigo_example1(): + x = 1 + del x + if 0: + pass + x = 1 + +arigo_example1.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (5, 'line'), + (5, 'return')] + +def arigo_example2(): + x = 1 + del x + if 1: + x = 1 + else: + pass + return None + +arigo_example2.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (4, 'line'), + (7, 'line'), + (7, 'return')] + + # check that lines consisting of just one instruction get traced: def one_instr_line(): x = 1 @@ -349,8 +379,12 @@ def fn(*args): def test_01_basic(self): self.run_test(basic) - def test_02_arigo(self): - self.run_test(arigo_example) + def test_02_arigo0(self): + self.run_test(arigo_example0) + def test_02_arigo1(self): + self.run_test(arigo_example1) + def test_02_arigo2(self): + self.run_test(arigo_example2) def test_03_one_instr(self): self.run_test(one_instr_line) def test_04_no_pop_blocks(self): From 58322a52ee672f640a3d7b7abcfa868287782580 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 6 Jul 2019 16:32:55 +0100 Subject: [PATCH 8/9] Reorder tests --- Lib/test/test_syntax.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index b776983bf37bca1..3829746f1799a22 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -699,10 +699,10 @@ def test_break_outside_loop(self): def test_yield_outside_function(self): self._check_error("if 0: yield", "outside function") self._check_error("if 0: yield\nelse: x=1", "outside function") - self._check_error("class C:\n if 0: yield", "outside function") self._check_error("if 1: pass\nelse: yield", "outside function") self._check_error("while 0: yield", "outside function") self._check_error("while 0: yield\nelse: x=1", "outside function") + self._check_error("class C:\n if 0: yield", "outside function") self._check_error("class C:\n if 1: pass\n else: yield", "outside function") self._check_error("class C:\n while 0: yield", "outside function") @@ -712,15 +712,16 @@ def test_yield_outside_function(self): def test_return_outside_function(self): self._check_error("if 0: return", "outside function") self._check_error("if 0: return\nelse: x=1", "outside function") - self._check_error("class C:\n if 0: return", "outside function") self._check_error("if 1: pass\nelse: return", "outside function") self._check_error("while 0: return", "outside function") self._check_error("class C:\n if 0: return", "outside function") - self._check_error("class C:\n if 0: return\n else: x= 1", - "outside function") self._check_error("class C:\n while 0: return", "outside function") self._check_error("class C:\n while 0: return\n else: x=1", "outside function") + self._check_error("class C:\n if 0: return\n else: x= 1", + "outside function") + self._check_error("class C:\n if 1: pass\n else: return", + "outside function") def test_break_outside_loop(self): self._check_error("if 0: break", "outside loop") From 125c2786a71831ff2d704804da406d21f9051336 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sun, 14 Jul 2019 13:08:54 +0200 Subject: [PATCH 9/9] Make the macros re-entrant --- Python/compile.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 1ade0e3794796ba..0336959d3da286d 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -161,10 +161,11 @@ struct compiler { int c_optimize; /* optimization level */ int c_interactive; /* true if in interactive mode */ int c_nestlevel; - int c_emit_bytecode; /* The compiler won't emit any bytecode - if this flag is false. This can be used - to temporarily visit nodes without emitting - bytecode to check only errors. */ + int c_do_not_emit_bytecode; /* The compiler won't emit any bytecode + if this value is different from zero. + This can be used to temporarily visit + nodes without emitting bytecode to + check only errors. */ PyObject *c_const_cache; /* Python dict holding all constants, including names tuple */ @@ -344,7 +345,7 @@ PyAST_CompileObject(mod_ty mod, PyObject *filename, PyCompilerFlags *flags, c.c_flags = flags; c.c_optimize = (optimize == -1) ? config->optimization_level : optimize; c.c_nestlevel = 0; - c.c_emit_bytecode = 1; + c.c_do_not_emit_bytecode = 0; if (!_PyAST_Optimize(mod, arena, c.c_optimize)) { goto finally; @@ -1157,7 +1158,7 @@ compiler_addop(struct compiler *c, int opcode) struct instr *i; int off; assert(!HAS_ARG(opcode)); - if (!c->c_emit_bytecode) { + if (c->c_do_not_emit_bytecode) { return 1; } off = compiler_next_instr(c, c->u->u_curblock); @@ -1313,7 +1314,7 @@ merge_consts_recursive(struct compiler *c, PyObject *o) static Py_ssize_t compiler_add_const(struct compiler *c, PyObject *o) { - if (!c->c_emit_bytecode) { + if (c->c_do_not_emit_bytecode) { return 0; } @@ -1330,7 +1331,7 @@ compiler_add_const(struct compiler *c, PyObject *o) static int compiler_addop_load_const(struct compiler *c, PyObject *o) { - if (!c->c_emit_bytecode) { + if (c->c_do_not_emit_bytecode) { return 1; } @@ -1344,7 +1345,7 @@ static int compiler_addop_o(struct compiler *c, int opcode, PyObject *dict, PyObject *o) { - if (!c->c_emit_bytecode) { + if (c->c_do_not_emit_bytecode) { return 1; } @@ -1360,7 +1361,7 @@ compiler_addop_name(struct compiler *c, int opcode, PyObject *dict, { Py_ssize_t arg; - if (!c->c_emit_bytecode) { + if (c->c_do_not_emit_bytecode) { return 1; } @@ -1384,7 +1385,7 @@ compiler_addop_i(struct compiler *c, int opcode, Py_ssize_t oparg) struct instr *i; int off; - if (!c->c_emit_bytecode) { + if (c->c_do_not_emit_bytecode) { return 1; } @@ -1414,7 +1415,7 @@ compiler_addop_j(struct compiler *c, int opcode, basicblock *b, int absolute) struct instr *i; int off; - if (!c->c_emit_bytecode) { + if (c->c_do_not_emit_bytecode) { return 1; } @@ -1557,10 +1558,10 @@ compiler_addop_j(struct compiler *c, int opcode, basicblock *b, int absolute) */ #define BEGIN_DO_NOT_EMIT_BYTECODE { \ - c->c_emit_bytecode = 0; + c->c_do_not_emit_bytecode++; #define END_DO_NOT_EMIT_BYTECODE \ - c->c_emit_bytecode = 1; \ + c->c_do_not_emit_bytecode--; \ } /* Search if variable annotations are present statically in a block. */