Skip to content

Commit 65d303f

Browse files
committed
Handle converting StopIteration to RuntimeError in bytecode.
1 parent 0e15c31 commit 65d303f

File tree

10 files changed

+128
-40
lines changed

10 files changed

+128
-40
lines changed

Include/internal/pycore_opcode.h

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

Include/opcode.h

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

Lib/importlib/_bootstrap_external.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,9 @@ def _write_atomic(path, data, mode=0o666):
425425
# Python 3.12a1 3509 (Conditional jumps only jump forward)
426426
# Python 3.12a1 3510 (FOR_ITER leaves iterator on the stack)
427427

428+
429+
# Python 3.12a1 3514 (Add LOAD_ERROR instruction)
430+
428431
# Python 3.13 will start with 3550
429432

430433
# MAGIC must change whenever the bytecode emitted by the compiler may no
@@ -436,7 +439,7 @@ def _write_atomic(path, data, mode=0o666):
436439
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
437440
# in PC/launcher.c must also be updated.
438441

439-
MAGIC_NUMBER = (3510).to_bytes(2, 'little') + b'\r\n'
442+
MAGIC_NUMBER = (3514).to_bytes(2, 'little') + b'\r\n'
440443

441444
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
442445

Lib/opcode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def pseudo_op(name, op, real_ops):
190190
def_op('DELETE_DEREF', 139)
191191
hasfree.append(139)
192192
jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards)
193-
193+
def_op('LOAD_ERROR', 141)
194194
def_op('CALL_FUNCTION_EX', 142) # Flags
195195

196196
def_op('EXTENDED_ARG', 144)

Lib/test/test_dis.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,16 @@ async def _asyncwith(c):
543543
>> COPY 3
544544
POP_EXCEPT
545545
RERAISE 1
546+
>> LOAD_ERROR 1
547+
CHECK_EXC_MATCH
548+
POP_JUMP_IF_FALSE 8 (to 140)
549+
LOAD_ERROR 2
550+
LOAD_CONST 3 ('coroutine raised StopIteration')
551+
CALL 0
552+
RAISE_VARARGS 2
553+
>> RERAISE 1
546554
ExceptionTable:
547-
6 rows
555+
12 rows
548556
""" % (_asyncwith.__code__.co_firstlineno,
549557
_asyncwith.__code__.co_firstlineno + 1,
550558
_asyncwith.__code__.co_firstlineno + 2,
@@ -1256,6 +1264,7 @@ def f(c=c):
12561264
Constants:
12571265
0: None
12581266
1: <code object f at (.*), file "(.*)", line (.*)>
1267+
2: 'generator raised StopIteration'
12591268
Variable names:
12601269
0: a
12611270
1: b
@@ -1362,6 +1371,7 @@ async def async_def():
13621371
Constants:
13631372
0: None
13641373
1: 1
1374+
2: 'coroutine raised StopIteration'
13651375
Names:
13661376
0: b
13671377
1: c

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,7 @@ def bar(cls):
14391439
check(bar, size('PP'))
14401440
# generator
14411441
def get_gen(): yield 1
1442-
check(get_gen(), size('P2P4P4c7P2ic??P'))
1442+
check(get_gen(), size('P2P4P4c7P2ic??4P'))
14431443
# iterator
14441444
check(iter('abc'), size('lP'))
14451445
# callable-iterator

Lib/test/test_sys_settrace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ def make_tracer():
346346
return Tracer()
347347

348348
def compare_events(self, line_offset, events, expected_events):
349-
events = [(l - line_offset, e) for (l, e) in events]
349+
events = [(l - line_offset if l is not None else None, e) for (l, e) in events]
350350
if events != expected_events:
351351
self.fail(
352352
"events did not match expectation:\n" +

Python/ceval.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,6 +2175,26 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
21752175
goto exception_unwind;
21762176
}
21772177

2178+
TARGET(LOAD_ERROR) {
2179+
PyObject *value;
2180+
switch(oparg) {
2181+
case 0:
2182+
value = PyExc_AssertionError;
2183+
break;
2184+
case 1:
2185+
value = PyExc_StopIteration;
2186+
break;
2187+
case 2:
2188+
value = PyExc_RuntimeError;
2189+
break;
2190+
default:
2191+
Py_UNREACHABLE();
2192+
}
2193+
Py_INCREF(value);
2194+
PUSH(value);
2195+
DISPATCH();
2196+
}
2197+
21782198
TARGET(LOAD_ASSERTION_ERROR) {
21792199
PyObject *value = PyExc_AssertionError;
21802200
Py_INCREF(value);

Python/compile.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,7 @@ stack_effect(int opcode, int oparg, int jump)
12541254
return (oparg & FVS_MASK) == FVS_HAVE_SPEC ? -1 : 0;
12551255
case LOAD_METHOD:
12561256
return 1;
1257+
case LOAD_ERROR:
12571258
case LOAD_ASSERTION_ERROR:
12581259
return 1;
12591260
case LIST_TO_TUPLE:
@@ -2567,6 +2568,42 @@ compiler_check_debug_args(struct compiler *c, arguments_ty args)
25672568
return 1;
25682569
}
25692570

2571+
static int
2572+
coroutine_stopiteration_handler(struct compiler *c, jump_target_label *handler)
2573+
{
2574+
ADDOP_LOAD_CONST(c, NO_LOCATION, Py_None);
2575+
ADDOP(c, NO_LOCATION, RETURN_VALUE);
2576+
if (cfg_builder_use_label(CFG_BUILDER(c), *handler) < 0) {
2577+
return 0;
2578+
}
2579+
jump_target_label other = cfg_new_label(CFG_BUILDER(c));
2580+
if (!IS_LABEL(other)) {
2581+
return 0;
2582+
}
2583+
USE_LABEL(c, *handler);
2584+
ADDOP_I(c, NO_LOCATION, LOAD_ERROR, 1); // StopIteration
2585+
ADDOP(c, NO_LOCATION, CHECK_EXC_MATCH);
2586+
ADDOP_JUMP(c, NO_LOCATION, POP_JUMP_IF_FALSE, other);
2587+
ADDOP_I(c, NO_LOCATION, LOAD_ERROR, 2); // RuntimeError
2588+
const char *msg = c->u->u_ste->ste_coroutine ?
2589+
(c->u->u_ste->ste_generator ?
2590+
"async generator raised StopIteration" :
2591+
"coroutine raised StopIteration"
2592+
) :
2593+
"generator raised StopIteration";
2594+
PyObject *message = _PyUnicode_FromASCII(msg, strlen(msg));
2595+
if (message == NULL) {
2596+
return 0;
2597+
}
2598+
ADDOP_LOAD_CONST_NEW(c, NO_LOCATION, message);
2599+
ADDOP_I(c, NO_LOCATION, CALL, 0);
2600+
ADDOP_I(c, NO_LOCATION, RAISE_VARARGS, 2);
2601+
2602+
USE_LABEL(c, other);
2603+
ADDOP_I(c, NO_LOCATION, RERAISE, 1);
2604+
return 1;
2605+
}
2606+
25702607
static int
25712608
compiler_function(struct compiler *c, stmt_ty s, int is_async)
25722609
{
@@ -2632,6 +2669,17 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async)
26322669
return 0;
26332670
}
26342671

2672+
bool is_coro = (c->u->u_ste->ste_coroutine || c->u->u_ste->ste_generator);
2673+
jump_target_label handler;
2674+
if (is_coro) {
2675+
handler = cfg_new_label(CFG_BUILDER(c));
2676+
if (!IS_LABEL(handler)) {
2677+
compiler_exit_scope(c);
2678+
return 0;
2679+
}
2680+
ADDOP_JUMP(c, NO_LOCATION, SETUP_CLEANUP, handler);
2681+
}
2682+
26352683
/* if not -OO mode, add docstring */
26362684
if (c->c_optimize < 2) {
26372685
docstring = _PyAST_GetDocString(body);
@@ -2647,6 +2695,12 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async)
26472695
for (i = docstring ? 1 : 0; i < asdl_seq_LEN(body); i++) {
26482696
VISIT_IN_SCOPE(c, stmt, (stmt_ty)asdl_seq_GET(body, i));
26492697
}
2698+
if (is_coro) {
2699+
if (!coroutine_stopiteration_handler(c, &handler)) {
2700+
compiler_exit_scope(c);
2701+
return 0;
2702+
}
2703+
}
26502704
co = assemble(c, 1);
26512705
qualname = c->u->u_qualname;
26522706
Py_INCREF(qualname);

Python/opcode_targets.h

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

0 commit comments

Comments
 (0)