From a909007fefbb94ac8de64517a712277cd76e9993 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 1 Apr 2018 12:15:35 -0500 Subject: [PATCH] py/objgenerator: Check stack before resuming a generator This turns a hard crash in a recursive generator into a 'maximum recursion depth exceeded' exception. --- py/objgenerator.c | 2 ++ tests/basics/gen_stack_overflow.py | 7 +++++++ tests/run-tests | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 tests/basics/gen_stack_overflow.py diff --git a/py/objgenerator.c b/py/objgenerator.c index 1394524421..ff566c0768 100644 --- a/py/objgenerator.c +++ b/py/objgenerator.c @@ -32,6 +32,7 @@ #include "py/bc.h" #include "py/objgenerator.h" #include "py/objfun.h" +#include "py/stackctrl.h" /******************************************************************************/ /* generator wrapper */ @@ -92,6 +93,7 @@ STATIC void gen_instance_print(const mp_print_t *print, mp_obj_t self_in, mp_pri } mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) { + MP_STACK_CHECK(); mp_check_self(MP_OBJ_IS_TYPE(self_in, &mp_type_gen_instance)); mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); if (self->code_state.ip == 0) { diff --git a/tests/basics/gen_stack_overflow.py b/tests/basics/gen_stack_overflow.py new file mode 100644 index 0000000000..5cba0e0549 --- /dev/null +++ b/tests/basics/gen_stack_overflow.py @@ -0,0 +1,7 @@ +def gen(): + yield from gen() + +try: + print(list(gen())) +except RuntimeError: + print("RuntimeError") diff --git a/tests/run-tests b/tests/run-tests index 4f05a4af61..bcd2e5dccf 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -345,7 +345,7 @@ def run_tests(pyb, tests, args, base_path=".", num_threads=1): # Remove them from the below when they work if args.emit == 'native': skip_tests.update({'basics/%s.py' % t for t in 'gen_yield_from gen_yield_from_close gen_yield_from_ducktype gen_yield_from_exc gen_yield_from_iter gen_yield_from_send gen_yield_from_stopped gen_yield_from_throw gen_yield_from_throw2 gen_yield_from_throw3 generator1 generator2 generator_args generator_close generator_closure generator_exc generator_return generator_send'.split()}) # require yield - skip_tests.update({'basics/%s.py' % t for t in 'bytes_gen class_store_class globals_del string_join'.split()}) # require yield + skip_tests.update({'basics/%s.py' % t for t in 'bytes_gen class_store_class globals_del string_join gen_stack_overflow'.split()}) # require yield skip_tests.update({'basics/async_%s.py' % t for t in 'def await await2 for for2 with with2'.split()}) # require yield skip_tests.update({'basics/%s.py' % t for t in 'try_reraise try_reraise2'.split()}) # require raise_varargs skip_tests.update({'basics/%s.py' % t for t in 'with_break with_continue with_return'.split()}) # require complete with support