Skip to content

Commit 23efd8e

Browse files
committed
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.
1 parent f7d72e4 commit 23efd8e

File tree

3 files changed

+53
-15
lines changed

3 files changed

+53
-15
lines changed

Lib/test/test_compile.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,19 @@ def test_stack_overflow(self):
697697
# complex statements.
698698
compile("if a: b\n" * 200000, "<dummy>", "exec")
699699

700+
# Multiple users rely on the fact that CPython does not generate
701+
# bytecode for dead code blocks. See bpo-37500 for more context.
702+
@support.cpython_only
703+
def test_dead_blocks_do_not_generate_bytecode(self):
704+
def unused_block():
705+
if 0:
706+
return 42
707+
opcodes = list(dis.get_instructions(unused_block))
708+
self.assertEqual(2, len(opcodes))
709+
self.assertEqual('LOAD_CONST', opcodes[0].opname)
710+
self.assertEqual(None, opcodes[0].argval)
711+
self.assertEqual('RETURN_VALUE', opcodes[1].opname)
712+
700713

701714
class TestExpressionStackSize(unittest.TestCase):
702715
# These tests check that the computed stack size for a code object

Python/compile.c

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ struct compiler {
161161
int c_optimize; /* optimization level */
162162
int c_interactive; /* true if in interactive mode */
163163
int c_nestlevel;
164+
int c_emit_bytecode; /* The compiler won't emmit any bytecode
165+
if this flag is false. This can be used
166+
to visit nodes to check only errors. */
164167

165168
PyObject *c_const_cache; /* Python dict holding all constants,
166169
including names tuple */
@@ -340,6 +343,7 @@ PyAST_CompileObject(mod_ty mod, PyObject *filename, PyCompilerFlags *flags,
340343
c.c_flags = flags;
341344
c.c_optimize = (optimize == -1) ? config->optimization_level : optimize;
342345
c.c_nestlevel = 0;
346+
c.c_emit_bytecode = 1;
343347

344348
if (!_PyAST_Optimize(mod, arena, c.c_optimize)) {
345349
goto finally;
@@ -1152,6 +1156,9 @@ compiler_addop(struct compiler *c, int opcode)
11521156
struct instr *i;
11531157
int off;
11541158
assert(!HAS_ARG(opcode));
1159+
if (!c->c_emit_bytecode) {
1160+
return 1;
1161+
}
11551162
off = compiler_next_instr(c, c->u->u_curblock);
11561163
if (off < 0)
11571164
return 0;
@@ -1359,6 +1366,10 @@ compiler_addop_i(struct compiler *c, int opcode, Py_ssize_t oparg)
13591366
struct instr *i;
13601367
int off;
13611368

1369+
if (!c->c_emit_bytecode) {
1370+
return 1;
1371+
}
1372+
13621373
/* oparg value is unsigned, but a signed C int is usually used to store
13631374
it in the C code (like Python/ceval.c).
13641375
@@ -1385,6 +1396,10 @@ compiler_addop_j(struct compiler *c, int opcode, basicblock *b, int absolute)
13851396
struct instr *i;
13861397
int off;
13871398

1399+
if (!c->c_emit_bytecode) {
1400+
return 1;
1401+
}
1402+
13881403
assert(HAS_ARG(opcode));
13891404
assert(b != NULL);
13901405
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)
15191534
} \
15201535
}
15211536

1537+
/* These macros allows to check only for errors and not emmit bytecode
1538+
* while visiting nodes.
1539+
*/
1540+
1541+
#define BEGIN_DO_NOT_EMIT_BYTECODE { \
1542+
c->c_emit_bytecode = 0;
1543+
1544+
#define END_DO_NOT_EMIT_BYTECODE \
1545+
c->c_emit_bytecode = 1; \
1546+
}
1547+
15221548
/* Search if variable annotations are present statically in a block. */
15231549

15241550
static int
@@ -2546,12 +2572,18 @@ compiler_if(struct compiler *c, stmt_ty s)
25462572
return 0;
25472573

25482574
constant = expr_constant(s->v.If.test);
2549-
/* constant = 0: "if 0" Leave the optimizations to
2550-
* the pephole optimizer to check for syntax errors
2551-
* in the block.
2575+
/* constant = 0: "if 0"
25522576
* constant = 1: "if 1", "if 2", ...
25532577
* constant = -1: rest */
2554-
if (constant == 1) {
2578+
if (constant == 0) {
2579+
if (s->v.If.orelse) {
2580+
VISIT_SEQ(c, stmt, s->v.If.orelse);
2581+
} else{
2582+
BEGIN_DO_NOT_EMIT_BYTECODE
2583+
VISIT_SEQ(c, stmt, s->v.If.body);
2584+
END_DO_NOT_EMIT_BYTECODE
2585+
}
2586+
} else if (constant == 1) {
25552587
VISIT_SEQ(c, stmt, s->v.If.body);
25562588
} else {
25572589
if (asdl_seq_LEN(s->v.If.orelse)) {

Python/peephole.c

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -306,18 +306,11 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
306306
case LOAD_CONST:
307307
cumlc = lastlc + 1;
308308
if (nextop != POP_JUMP_IF_FALSE ||
309-
!ISBASICBLOCK(blocks, op_start, i + 1)) {
309+
!ISBASICBLOCK(blocks, op_start, i + 1) ||
310+
!PyObject_IsTrue(PyList_GET_ITEM(consts, get_arg(codestr, i))))
310311
break;
311-
}
312-
PyObject* cnt = PyList_GET_ITEM(consts, get_arg(codestr, i));
313-
int is_true = PyObject_IsTrue(cnt);
314-
if (is_true == -1) {
315-
goto exitError;
316-
}
317-
if (is_true == 1) {
318-
fill_nops(codestr, op_start, nexti + 1);
319-
cumlc = 0;
320-
}
312+
fill_nops(codestr, op_start, nexti + 1);
313+
cumlc = 0;
321314
break;
322315

323316
/* Try to fold tuples of constants.

0 commit comments

Comments
 (0)