py/vm: Fix case of handling raised StopIteration within yield from.

This patch concerns the handling of an NLR-raised StopIteration, raised
during a call to mp_resume() which is handling the yield from opcode.

Previously, commit 6738c1dded introduced code
to handle this case, along with a test.  It seems that it was lucky that
the test worked because the code did not correctly handle the stack pointer
(sp).

Furthermore, commit 79d996a57b improved the
way mp_resume() propagated certain exceptions: it changed raising an NLR
value to returning MP_VM_RETURN_EXCEPTION.  This change meant that the
test introduced in gen_yield_from_ducktype.py was no longer hitting the
code introduced in 6738c1dded.

The patch here does two things:

1. Fixes the handling of sp in the VM for the case that yield from is
   interrupted by a StopIteration raised via NLR.

2. Introduces a new test to check this handling of sp and re-covers the
   code in the VM.
This commit is contained in:
Damien George 2018-02-27 15:39:31 +11:00
parent c5fe610ba1
commit 22ade2f5c4
2 changed files with 16 additions and 1 deletions

View File

@ -1162,6 +1162,7 @@ yield:
mp_obj_t send_value = POP();
mp_obj_t t_exc = MP_OBJ_NULL;
mp_obj_t ret_value;
code_state->sp = sp; // Save sp because it's needed if mp_resume raises StopIteration
if (inject_exc != MP_OBJ_NULL) {
t_exc = inject_exc;
inject_exc = MP_OBJ_NULL;
@ -1361,7 +1362,8 @@ exception_handler:
} else if (*code_state->ip == MP_BC_YIELD_FROM) {
// StopIteration inside yield from call means return a value of
// yield from, so inject exception's value as yield from's result
*++code_state->sp = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(nlr.ret_val));
// (Instead of stack pop then push we just replace exhausted gen with value)
*code_state->sp = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(nlr.ret_val));
code_state->ip++; // yield from is over, move to next instruction
goto outer_dispatch_loop; // continue with dispatch loop
}

View File

@ -40,3 +40,16 @@ def gen6():
g = gen6()
print(list(g))
# StopIteration from within a Python function, within a native iterator (map), within a yield from
def gen7(x):
if x < 3:
return x
else:
raise StopIteration(444)
def gen8():
print((yield from map(gen7, range(100))))
g = gen8()
print(list(g))