Skip to content

Commit 78aeb38

Browse files
authored
gh-124285: Fix bug where bool() is called multiple times for the same part of a boolean expression (#124394)
1 parent c58c572 commit 78aeb38

File tree

14 files changed

+234
-47
lines changed

14 files changed

+234
-47
lines changed

Doc/library/dis.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1872,6 +1872,12 @@ but are replaced by real opcodes or removed before bytecode is generated.
18721872
Undirected relative jump instructions which are replaced by their
18731873
directed (forward/backward) counterparts by the assembler.
18741874

1875+
.. opcode:: JUMP_IF_TRUE
1876+
.. opcode:: JUMP_IF_FALSE
1877+
1878+
Conditional jumps which do not impact the stack. Replaced by the sequence
1879+
``COPY 1``, ``TO_BOOL``, ``POP_JUMP_IF_TRUE/FALSE``.
1880+
18751881
.. opcode:: LOAD_CLOSURE (i)
18761882

18771883
Pushes a reference to the cell contained in slot ``i`` of the "fast locals"

Include/internal/pycore_magic_number.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ Known values:
258258
Python 3.14a1 3604 (Do not duplicate test at end of while statements)
259259
Python 3.14a1 3605 (Move ENTER_EXECUTOR to opcode 255)
260260
Python 3.14a1 3606 (Specialize CALL_KW)
261+
Python 3.14a1 3607 (Add pseudo instructions JUMP_IF_TRUE/FALSE)
261262
262263
Python 3.15 will start with 3650
263264
@@ -270,7 +271,7 @@ PC/launcher.c must also be updated.
270271
271272
*/
272273

273-
#define PYC_MAGIC_NUMBER 3606
274+
#define PYC_MAGIC_NUMBER 3607
274275
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
275276
(little-endian) and then appending b'\r\n'. */
276277
#define PYC_MAGIC_NUMBER_TOKEN \

Include/internal/pycore_opcode_metadata.h

Lines changed: 34 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/opcode_ids.h

Lines changed: 9 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/_opcode_metadata.py

Lines changed: 9 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_compile.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,45 @@ async def name_4():
15271527
pass
15281528
[[]]
15291529

1530+
class TestBooleanExpression(unittest.TestCase):
1531+
class Value:
1532+
def __init__(self):
1533+
self.called = 0
1534+
1535+
def __bool__(self):
1536+
self.called += 1
1537+
return self.value
1538+
1539+
class Yes(Value):
1540+
value = True
1541+
1542+
class No(Value):
1543+
value = False
1544+
1545+
def test_short_circuit_and(self):
1546+
v = [self.Yes(), self.No(), self.Yes()]
1547+
res = v[0] and v[1] and v[0]
1548+
self.assertIs(res, v[1])
1549+
self.assertEqual([e.called for e in v], [1, 1, 0])
1550+
1551+
def test_short_circuit_or(self):
1552+
v = [self.No(), self.Yes(), self.No()]
1553+
res = v[0] or v[1] or v[0]
1554+
self.assertIs(res, v[1])
1555+
self.assertEqual([e.called for e in v], [1, 1, 0])
1556+
1557+
def test_compound(self):
1558+
# See gh-124285
1559+
v = [self.No(), self.Yes(), self.Yes(), self.Yes()]
1560+
res = v[0] and v[1] or v[2] or v[3]
1561+
self.assertIs(res, v[2])
1562+
self.assertEqual([e.called for e in v], [1, 0, 1, 0])
1563+
1564+
v = [self.No(), self.No(), self.Yes(), self.Yes(), self.No()]
1565+
res = v[0] or v[1] and v[2] or v[3] or v[4]
1566+
self.assertIs(res, v[3])
1567+
self.assertEqual([e.called for e in v], [1, 1, 0, 1, 0])
1568+
15301569
@requires_debug_ranges()
15311570
class TestSourcePositions(unittest.TestCase):
15321571
# Ensure that compiled code snippets have correct line and column numbers

Lib/test/test_generated_cases.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,36 @@ def test_pseudo_instruction_with_flags(self):
523523
"""
524524
self.run_cases_test(input, output)
525525

526+
def test_pseudo_instruction_as_sequence(self):
527+
input = """
528+
pseudo(OP, (in -- out1, out2)) = [
529+
OP1, OP2
530+
];
531+
532+
inst(OP1, (--)) {
533+
}
534+
535+
inst(OP2, (--)) {
536+
}
537+
"""
538+
output = """
539+
TARGET(OP1) {
540+
frame->instr_ptr = next_instr;
541+
next_instr += 1;
542+
INSTRUCTION_STATS(OP1);
543+
DISPATCH();
544+
}
545+
546+
TARGET(OP2) {
547+
frame->instr_ptr = next_instr;
548+
next_instr += 1;
549+
INSTRUCTION_STATS(OP2);
550+
DISPATCH();
551+
}
552+
"""
553+
self.run_cases_test(input, output)
554+
555+
526556
def test_array_input(self):
527557
input = """
528558
inst(OP, (below, values[oparg*2], above --)) {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix bug where ``bool(a)`` can be invoked more than once during the
2+
evaluation of a compound boolean expression.

Python/bytecodes.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2570,6 +2570,14 @@ dummy_func(
25702570
JUMP_BACKWARD_NO_INTERRUPT,
25712571
};
25722572

2573+
pseudo(JUMP_IF_FALSE, (cond -- cond)) = [
2574+
COPY, TO_BOOL, POP_JUMP_IF_FALSE,
2575+
];
2576+
2577+
pseudo(JUMP_IF_TRUE, (cond -- cond)) = [
2578+
COPY, TO_BOOL, POP_JUMP_IF_TRUE,
2579+
];
2580+
25732581
tier1 inst(ENTER_EXECUTOR, (--)) {
25742582
#ifdef _Py_TIER2
25752583
PyCodeObject *code = _PyFrame_GetCode(frame);

Python/codegen.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3140,17 +3140,15 @@ codegen_boolop(compiler *c, expr_ty e)
31403140
location loc = LOC(e);
31413141
assert(e->kind == BoolOp_kind);
31423142
if (e->v.BoolOp.op == And)
3143-
jumpi = POP_JUMP_IF_FALSE;
3143+
jumpi = JUMP_IF_FALSE;
31443144
else
3145-
jumpi = POP_JUMP_IF_TRUE;
3145+
jumpi = JUMP_IF_TRUE;
31463146
NEW_JUMP_TARGET_LABEL(c, end);
31473147
s = e->v.BoolOp.values;
31483148
n = asdl_seq_LEN(s) - 1;
31493149
assert(n >= 0);
31503150
for (i = 0; i < n; ++i) {
31513151
VISIT(c, expr, (expr_ty)asdl_seq_GET(s, i));
3152-
ADDOP_I(c, loc, COPY, 1);
3153-
ADDOP(c, loc, TO_BOOL);
31543152
ADDOP_JUMP(c, loc, jumpi, end);
31553153
ADDOP(c, loc, POP_TOP);
31563154
}

0 commit comments

Comments
 (0)