diff --git a/py/bc.h b/py/bc.h index aac35954d7add..0709897750019 100644 --- a/py/bc.h +++ b/py/bc.h @@ -4,6 +4,16 @@ typedef enum { MP_VM_RETURN_EXCEPTION, } mp_vm_return_kind_t; +// Exception stack entry +typedef struct _mp_exc_stack { + const byte *handler; + // bit 0 is saved currently_in_except_block value + machine_uint_t val_sp; + // We might only have 2 interesting cases here: SETUP_EXCEPT & SETUP_FINALLY, + // consider storing it in bit 1 of val_sp. TODO: SETUP_WITH? + byte opcode; +} mp_exc_stack; + mp_vm_return_kind_t mp_execute_byte_code(const byte *code, const mp_obj_t *args, uint n_args, const mp_obj_t *args2, uint n_args2, uint n_state, mp_obj_t *ret); -mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **ip_in_out, mp_obj_t *fastn, mp_obj_t **sp_in_out); +mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **ip_in_out, mp_obj_t *fastn, mp_obj_t **sp_in_out, mp_exc_stack *exc_stack, mp_exc_stack **exc_sp_in_out, volatile mp_obj_t inject_exc); void mp_byte_code_print(const byte *code, int len); diff --git a/py/objgenerator.c b/py/objgenerator.c index 8c4bb595f1673..2db04ad742199 100644 --- a/py/objgenerator.c +++ b/py/objgenerator.c @@ -56,8 +56,10 @@ typedef struct _mp_obj_gen_instance_t { const byte *code_info; const byte *ip; mp_obj_t *sp; + mp_exc_stack *exc_sp; uint n_state; - mp_obj_t state[]; + mp_obj_t state[0]; // Variable-length + mp_exc_stack exc_state[0]; // Variable-length } mp_obj_gen_instance_t; void gen_instance_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { @@ -68,7 +70,7 @@ mp_obj_t gen_instance_getiter(mp_obj_t self_in) { return self_in; } -STATIC mp_obj_t gen_next_send(mp_obj_t self_in, mp_obj_t send_value) { +STATIC mp_obj_t gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value) { mp_obj_gen_instance_t *self = self_in; if (self->ip == 0) { return mp_const_stop_iteration; @@ -80,7 +82,9 @@ STATIC mp_obj_t gen_next_send(mp_obj_t self_in, mp_obj_t send_value) { } else { *self->sp = send_value; } - mp_vm_return_kind_t vm_return_kind = mp_execute_byte_code_2(self->code_info, &self->ip, &self->state[self->n_state - 1], &self->sp); + mp_vm_return_kind_t vm_return_kind = mp_execute_byte_code_2(self->code_info, &self->ip, + &self->state[self->n_state - 1], &self->sp, (mp_exc_stack*)(self->state + self->n_state), + &self->exc_sp, throw_value); switch (vm_return_kind) { case MP_VM_RETURN_NORMAL: // Explicitly mark generator as completed. If we don't do this, @@ -100,19 +104,21 @@ STATIC mp_obj_t gen_next_send(mp_obj_t self_in, mp_obj_t send_value) { return *self->sp; case MP_VM_RETURN_EXCEPTION: + self->ip = 0; + nlr_jump(self->state[self->n_state - 1]); + default: - // TODO assert(0); return mp_const_none; } } mp_obj_t gen_instance_iternext(mp_obj_t self_in) { - return gen_next_send(self_in, mp_const_none); + return gen_resume(self_in, mp_const_none, MP_OBJ_NULL); } STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) { - mp_obj_t ret = gen_next_send(self_in, send_value); + mp_obj_t ret = gen_resume(self_in, send_value, MP_OBJ_NULL); if (ret == mp_const_stop_iteration) { nlr_jump(mp_obj_new_exception(&mp_type_StopIteration)); } else { @@ -122,8 +128,21 @@ STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) { STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send); +STATIC mp_obj_t gen_instance_throw(uint n_args, const mp_obj_t *args) { + mp_obj_t ret = gen_resume(args[0], mp_const_none, n_args == 2 ? args[1] : args[2]); + if (ret == mp_const_stop_iteration) { + nlr_jump(mp_obj_new_exception(&mp_type_StopIteration)); + } else { + return ret; + } +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gen_instance_throw_obj, 2, 4, gen_instance_throw); + + STATIC const mp_method_t gen_type_methods[] = { { "send", &gen_instance_send_obj }, + { "throw", &gen_instance_throw_obj }, { NULL, NULL }, // end-of-list sentinel }; @@ -137,11 +156,13 @@ const mp_obj_type_t gen_instance_type = { }; mp_obj_t mp_obj_new_gen_instance(const byte *bytecode, uint n_state, int n_args, const mp_obj_t *args) { - mp_obj_gen_instance_t *o = m_new_obj_var(mp_obj_gen_instance_t, mp_obj_t, n_state); + // TODO: 4 is hardcoded number from vm.c, calc exc stack size instead. + mp_obj_gen_instance_t *o = m_new_obj_var(mp_obj_gen_instance_t, byte, n_state * sizeof(mp_obj_t) + 4 * sizeof(mp_exc_stack)); o->base.type = &gen_instance_type; o->code_info = bytecode; o->ip = bytecode; o->sp = &o->state[0] - 1; // sp points to top of stack, which starts off 1 below the state + o->exc_sp = (mp_exc_stack*)(o->state + n_state) - 1; o->n_state = n_state; // copy args to end of state array, in reverse (that's how mp_execute_byte_code_2 needs it) diff --git a/py/vm.c b/py/vm.c index 7b78b5fefb28c..7f4efeea0fb6b 100644 --- a/py/vm.c +++ b/py/vm.c @@ -17,16 +17,6 @@ // top element. // Exception stack also grows up, top element is also pointed at. -// Exception stack entry -typedef struct _mp_exc_stack { - const byte *handler; - // bit 0 is saved currently_in_except_block value - machine_uint_t val_sp; - // We might only have 2 interesting cases here: SETUP_EXCEPT & SETUP_FINALLY, - // consider storing it in bit 1 of val_sp. TODO: SETUP_WITH? - byte opcode; -} mp_exc_stack; - // Exception stack unwind reasons (WHY_* in CPython-speak) // TODO perhaps compress this to RETURN=0, JUMP>0, with number of unwinds // left to do encoded in the JUMP number @@ -89,8 +79,10 @@ mp_vm_return_kind_t mp_execute_byte_code(const byte *code, const mp_obj_t *args, } } + mp_exc_stack exc_stack[4]; + mp_exc_stack *exc_sp = &exc_stack[0] - 1; // execute the byte code - mp_vm_return_kind_t vm_return_kind = mp_execute_byte_code_2(code, &ip, &state[n_state - 1], &sp); + mp_vm_return_kind_t vm_return_kind = mp_execute_byte_code_2(code, &ip, &state[n_state - 1], &sp, exc_stack, &exc_sp, MP_OBJ_NULL); switch (vm_return_kind) { case MP_VM_RETURN_NORMAL: @@ -113,7 +105,10 @@ mp_vm_return_kind_t mp_execute_byte_code(const byte *code, const mp_obj_t *args, // MP_VM_RETURN_NORMAL, sp valid, return value in *sp // MP_VM_RETURN_YIELD, ip, sp valid, yielded value in *sp // MP_VM_RETURN_EXCEPTION, exception in fastn[0] -mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **ip_in_out, mp_obj_t *fastn, mp_obj_t **sp_in_out) { +mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **ip_in_out, + mp_obj_t *fastn, mp_obj_t **sp_in_out, + mp_exc_stack *exc_stack, mp_exc_stack **exc_sp_in_out, + volatile mp_obj_t inject_exc) { // careful: be sure to declare volatile any variables read in the exception handler (written is ok, I think) const byte *ip = *ip_in_out; @@ -123,14 +118,21 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i mp_obj_t obj1, obj2; nlr_buf_t nlr; - volatile machine_uint_t currently_in_except_block = 0; // 0 or 1, to detect nested exceptions - mp_exc_stack exc_stack[4]; - mp_exc_stack *volatile exc_sp = &exc_stack[0] - 1; // stack grows up, exc_sp points to top of stack + volatile machine_uint_t currently_in_except_block = (int)*exc_sp_in_out & 1; // 0 or 1, to detect nested exceptions + mp_exc_stack *volatile exc_sp = (void*)((int)*exc_sp_in_out & ~1); // stack grows up, exc_sp points to top of stack const byte *volatile save_ip = ip; // this is so we can access ip in the exception handler without making ip volatile (which means the compiler can't keep it in a register in the main loop) // outer exception handling loop for (;;) { if (nlr_push(&nlr) == 0) { + // If we have exception to inject, now that we finish setting up + // execution context, raise it. This works as if RAISE_VARARGS + // bytecode was executed. + if (inject_exc != MP_OBJ_NULL) { + mp_obj_t t = inject_exc; + inject_exc = MP_OBJ_NULL; + nlr_jump(rt_make_raise_obj(t)); + } // loop to execute byte code for (;;) { dispatch_loop: @@ -434,7 +436,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i // matched against: SETUP_EXCEPT, SETUP_FINALLY, SETUP_WITH case MP_BC_POP_BLOCK: // we are exiting an exception handler, so pop the last one of the exception-stack - assert(exc_sp >= &exc_stack[0]); + assert(exc_sp >= exc_stack); currently_in_except_block = (exc_sp->val_sp & 1); // restore previous state exc_sp--; // pop back to previous exception handler break; @@ -443,7 +445,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i case MP_BC_POP_EXCEPT: // TODO need to work out how blocks work etc // pops block, checks it's an exception block, and restores the stack, saving the 3 exception values to local threadstate - assert(exc_sp >= &exc_stack[0]); + assert(exc_sp >= exc_stack); assert(currently_in_except_block); //sp = (mp_obj_t*)(*exc_sp--); //exc_sp--; // discard ip @@ -592,7 +594,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i } nlr_pop(); *sp_in_out = sp; - assert(exc_sp == &exc_stack[0] - 1); + assert(exc_sp == exc_stack - 1); return MP_VM_RETURN_NORMAL; case MP_BC_RAISE_VARARGS: @@ -605,6 +607,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i nlr_pop(); *ip_in_out = ip; *sp_in_out = sp; + *exc_sp_in_out = (void*)((int)exc_sp | currently_in_except_block); return MP_VM_RETURN_YIELD; case MP_BC_IMPORT_NAME: @@ -654,7 +657,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i while (currently_in_except_block) { // nested exception - assert(exc_sp >= &exc_stack[0]); + assert(exc_sp >= exc_stack); // TODO make a proper message for nested exception // at the moment we are just raising the very last exception (the one that caused the nested exception) @@ -664,7 +667,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i exc_sp--; // pop back to previous exception handler } - if (exc_sp >= &exc_stack[0]) { + if (exc_sp >= exc_stack) { // set flag to indicate that we are now handling an exception currently_in_except_block = 1; diff --git a/tests/basics/generator-exc.py b/tests/basics/generator-exc.py new file mode 100644 index 0000000000000..09aca5d9a0f4b --- /dev/null +++ b/tests/basics/generator-exc.py @@ -0,0 +1,53 @@ +# Test proper handling of exceptions within generator across yield +def gen(): + try: + yield 1 + raise ValueError + except ValueError: + print("Caught") + yield 2 + +for i in gen(): + print(i) + + +# Test throwing exceptions out of generator +def gen2(): + yield 1 + raise ValueError + yield 2 + yield 3 + +g = gen2() +print(next(g)) +try: + print(next(g)) +except ValueError: + print("ValueError") + +try: + print(next(g)) +except StopIteration: + print("StopIteration") + + +# Test throwing exception into generator +def gen3(): + yield 1 + try: + yield 2 + except ValueError: + print("ValueError received") + yield 3 + yield 4 + yield 5 + +g = gen3() +print(next(g)) +print(next(g)) +print("out of throw:", g.throw(ValueError)) +print(next(g)) +try: + print("out of throw2:", g.throw(ValueError)) +except ValueError: + print("Boomerang ValueError caught")
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: