Skip to content

Commit efb8a25

Browse files
authored
GH-103488: Use return-offset, not yield-offset. (GH-103502)
* Use return-offset, not yield-offset, so that instruction pointer is correct when sending to a generator or coroutine.
1 parent 4307fea commit efb8a25

File tree

6 files changed

+455
-414
lines changed

6 files changed

+455
-414
lines changed

Include/internal/pycore_frame.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@ typedef struct _PyInterpreterFrame {
6161
// over, or (in the case of a newly-created frame) a totally invalid value:
6262
_Py_CODEUNIT *prev_instr;
6363
int stacktop; /* Offset of TOS from localsplus */
64-
uint16_t yield_offset;
64+
/* The return_offset determines where a `RETURN` should go in the caller,
65+
* relative to `prev_instr`.
66+
* It is only meaningful to the callee,
67+
* so it needs to be set in any CALL (to a Python function)
68+
* or SEND (to a coroutine or generator).
69+
* If there is no callee, then it is meaningless. */
70+
uint16_t return_offset;
6571
char owner;
6672
/* Locals and stack */
6773
PyObject *localsplus[1];
@@ -121,7 +127,7 @@ _PyFrame_Initialize(
121127
frame->stacktop = code->co_nlocalsplus;
122128
frame->frame_obj = NULL;
123129
frame->prev_instr = _PyCode_CODE(code) - 1;
124-
frame->yield_offset = 0;
130+
frame->return_offset = 0;
125131
frame->owner = FRAME_OWNED_BY_THREAD;
126132

127133
for (int i = null_locals_from; i < code->co_nlocalsplus; i++) {

Lib/test/test_generators.py

+15
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,22 @@ def f():
225225
gi = f()
226226
self.assertIsNone(gi.gi_frame.f_back)
227227

228+
def test_issue103488(self):
228229

230+
def gen_raises():
231+
yield
232+
raise ValueError()
233+
234+
def loop():
235+
try:
236+
for _ in gen_raises():
237+
if True is False:
238+
return
239+
except ValueError:
240+
pass
241+
242+
#This should not raise
243+
loop()
229244

230245
class ExceptionTest(unittest.TestCase):
231246
# Tests for the issue #23353: check that the currently handled exception
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Change the internal offset distinguishing yield and return target addresses,
2+
so that the instruction pointer is correct for exception handling and other
3+
stack unwinding.

Python/bytecodes.c

+18-10
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ dummy_func(
506506
new_frame->localsplus[0] = container;
507507
new_frame->localsplus[1] = sub;
508508
JUMPBY(INLINE_CACHE_ENTRIES_BINARY_SUBSCR);
509+
frame->return_offset = 0;
509510
DISPATCH_INLINED(new_frame);
510511
}
511512

@@ -637,6 +638,7 @@ dummy_func(
637638
_PyInterpreterFrame *dying = frame;
638639
frame = cframe.current_frame = dying->previous;
639640
_PyEvalFrameClearAndPop(tstate, dying);
641+
frame->prev_instr += frame->return_offset;
640642
_PyFrame_StackPush(frame, retval);
641643
goto resume_frame;
642644
}
@@ -655,6 +657,7 @@ dummy_func(
655657
_PyInterpreterFrame *dying = frame;
656658
frame = cframe.current_frame = dying->previous;
657659
_PyEvalFrameClearAndPop(tstate, dying);
660+
frame->prev_instr += frame->return_offset;
658661
_PyFrame_StackPush(frame, retval);
659662
goto resume_frame;
660663
}
@@ -670,6 +673,7 @@ dummy_func(
670673
_PyInterpreterFrame *dying = frame;
671674
frame = cframe.current_frame = dying->previous;
672675
_PyEvalFrameClearAndPop(tstate, dying);
676+
frame->prev_instr += frame->return_offset;
673677
_PyFrame_StackPush(frame, retval);
674678
goto resume_frame;
675679
}
@@ -689,6 +693,7 @@ dummy_func(
689693
_PyInterpreterFrame *dying = frame;
690694
frame = cframe.current_frame = dying->previous;
691695
_PyEvalFrameClearAndPop(tstate, dying);
696+
frame->prev_instr += frame->return_offset;
692697
_PyFrame_StackPush(frame, retval);
693698
goto resume_frame;
694699
}
@@ -823,13 +828,13 @@ dummy_func(
823828
{
824829
PyGenObject *gen = (PyGenObject *)receiver;
825830
_PyInterpreterFrame *gen_frame = (_PyInterpreterFrame *)gen->gi_iframe;
826-
frame->yield_offset = oparg;
831+
frame->return_offset = oparg;
827832
STACK_SHRINK(1);
828833
_PyFrame_StackPush(gen_frame, v);
829834
gen->gi_frame_state = FRAME_EXECUTING;
830835
gen->gi_exc_state.previous_item = tstate->exc_info;
831836
tstate->exc_info = &gen->gi_exc_state;
832-
JUMPBY(INLINE_CACHE_ENTRIES_SEND + oparg);
837+
JUMPBY(INLINE_CACHE_ENTRIES_SEND);
833838
DISPATCH_INLINED(gen_frame);
834839
}
835840
if (Py_IsNone(v) && PyIter_Check(receiver)) {
@@ -861,13 +866,13 @@ dummy_func(
861866
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING, SEND);
862867
STAT_INC(SEND, hit);
863868
_PyInterpreterFrame *gen_frame = (_PyInterpreterFrame *)gen->gi_iframe;
864-
frame->yield_offset = oparg;
869+
frame->return_offset = oparg;
865870
STACK_SHRINK(1);
866871
_PyFrame_StackPush(gen_frame, v);
867872
gen->gi_frame_state = FRAME_EXECUTING;
868873
gen->gi_exc_state.previous_item = tstate->exc_info;
869874
tstate->exc_info = &gen->gi_exc_state;
870-
JUMPBY(INLINE_CACHE_ENTRIES_SEND + oparg);
875+
JUMPBY(INLINE_CACHE_ENTRIES_SEND);
871876
DISPATCH_INLINED(gen_frame);
872877
}
873878

@@ -886,7 +891,6 @@ dummy_func(
886891
_PyInterpreterFrame *gen_frame = frame;
887892
frame = cframe.current_frame = frame->previous;
888893
gen_frame->previous = NULL;
889-
frame->prev_instr -= frame->yield_offset;
890894
_PyFrame_StackPush(frame, retval);
891895
goto resume_frame;
892896
}
@@ -905,7 +909,6 @@ dummy_func(
905909
_PyInterpreterFrame *gen_frame = frame;
906910
frame = cframe.current_frame = frame->previous;
907911
gen_frame->previous = NULL;
908-
frame->prev_instr -= frame->yield_offset;
909912
_PyFrame_StackPush(frame, retval);
910913
goto resume_frame;
911914
}
@@ -1724,6 +1727,7 @@ dummy_func(
17241727
STACK_SHRINK(shrink_stack);
17251728
new_frame->localsplus[0] = owner;
17261729
JUMPBY(INLINE_CACHE_ENTRIES_LOAD_ATTR);
1730+
frame->return_offset = 0;
17271731
DISPATCH_INLINED(new_frame);
17281732
}
17291733

@@ -1751,6 +1755,7 @@ dummy_func(
17511755
new_frame->localsplus[0] = owner;
17521756
new_frame->localsplus[1] = Py_NewRef(name);
17531757
JUMPBY(INLINE_CACHE_ENTRIES_LOAD_ATTR);
1758+
frame->return_offset = 0;
17541759
DISPATCH_INLINED(new_frame);
17551760
}
17561761

@@ -2259,14 +2264,14 @@ dummy_func(
22592264
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING, FOR_ITER);
22602265
STAT_INC(FOR_ITER, hit);
22612266
_PyInterpreterFrame *gen_frame = (_PyInterpreterFrame *)gen->gi_iframe;
2262-
frame->yield_offset = oparg;
2267+
frame->return_offset = oparg;
22632268
_PyFrame_StackPush(gen_frame, Py_NewRef(Py_None));
22642269
gen->gi_frame_state = FRAME_EXECUTING;
22652270
gen->gi_exc_state.previous_item = tstate->exc_info;
22662271
tstate->exc_info = &gen->gi_exc_state;
2267-
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER + oparg);
2268-
assert(next_instr->op.code == END_FOR ||
2269-
next_instr->op.code == INSTRUMENTED_END_FOR);
2272+
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER);
2273+
assert(next_instr[oparg].op.code == END_FOR ||
2274+
next_instr[oparg].op.code == INSTRUMENTED_END_FOR);
22702275
DISPATCH_INLINED(gen_frame);
22712276
}
22722277

@@ -2521,6 +2526,7 @@ dummy_func(
25212526
goto error;
25222527
}
25232528
JUMPBY(INLINE_CACHE_ENTRIES_CALL);
2529+
frame->return_offset = 0;
25242530
DISPATCH_INLINED(new_frame);
25252531
}
25262532
/* Callable is not a normal Python function */
@@ -2594,6 +2600,7 @@ dummy_func(
25942600
// Manipulate stack directly since we leave using DISPATCH_INLINED().
25952601
STACK_SHRINK(oparg + 2);
25962602
JUMPBY(INLINE_CACHE_ENTRIES_CALL);
2603+
frame->return_offset = 0;
25972604
DISPATCH_INLINED(new_frame);
25982605
}
25992606

@@ -2631,6 +2638,7 @@ dummy_func(
26312638
// Manipulate stack and cache directly since we leave using DISPATCH_INLINED().
26322639
STACK_SHRINK(oparg + 2);
26332640
JUMPBY(INLINE_CACHE_ENTRIES_CALL);
2641+
frame->return_offset = 0;
26342642
DISPATCH_INLINED(new_frame);
26352643
}
26362644

Python/ceval.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
672672
_PyCode_CODE(tstate->interp->interpreter_trampoline);
673673
entry_frame.stacktop = 0;
674674
entry_frame.owner = FRAME_OWNED_BY_CSTACK;
675-
entry_frame.yield_offset = 0;
675+
entry_frame.return_offset = 0;
676676
/* Push frame */
677677
entry_frame.previous = prev_cframe->current_frame;
678678
frame->previous = &entry_frame;
@@ -881,6 +881,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
881881
_PyInterpreterFrame *dying = frame;
882882
frame = cframe.current_frame = dying->previous;
883883
_PyEvalFrameClearAndPop(tstate, dying);
884+
frame->return_offset = 0;
884885
if (frame == &entry_frame) {
885886
/* Restore previous cframe and exit */
886887
tstate->cframe = cframe.previous;

0 commit comments

Comments
 (0)