Implement chained exceptions
This adds the __cause__, __context__ and __suppress_context__ members to exception objects and makes e.g., `raise exc from cause` set them in the same way as standard Python.
This commit is contained in:
parent
b499275bb5
commit
f3169246ba
|
@ -65,6 +65,7 @@
|
|||
#define MICROPY_PY_UCRYPTOLIB (1)
|
||||
#define MICROPY_PY_UCRYPTOLIB_CTR (1)
|
||||
#define MICROPY_PY_MICROPYTHON_HEAP_LOCKED (1)
|
||||
#define MICROPY_CPYTHON_EXCEPTION_CHAIN (1)
|
||||
|
||||
// use vfs's functions for import stat and builtin open
|
||||
#define mp_import_stat mp_vfs_import_stat
|
||||
|
|
|
@ -224,6 +224,9 @@ typedef long mp_off_t;
|
|||
#ifndef MICROPY_CPYTHON_COMPAT
|
||||
#define MICROPY_CPYTHON_COMPAT (CIRCUITPY_FULL_BUILD)
|
||||
#endif
|
||||
#ifndef MICROPY_CPYTHON_EXCEPTION_CHAIN
|
||||
#define MICROPY_CPYTHON_EXCEPTION_CHAIN (CIRCUITPY_FULL_BUILD)
|
||||
#endif
|
||||
#define MICROPY_PY_BUILTINS_POW3 (CIRCUITPY_BUILTINS_POW3)
|
||||
#define MICROPY_PY_FSTRINGS (1)
|
||||
#define MICROPY_MODULE_WEAK_LINKS (0)
|
||||
|
|
|
@ -228,11 +228,35 @@ void mp_obj_exception_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
|
|||
self->traceback = (mp_obj_traceback_t *)&mp_const_empty_traceback_obj;
|
||||
} else {
|
||||
if (!mp_obj_is_type(dest[1], &mp_type_traceback)) {
|
||||
mp_raise_TypeError(MP_ERROR_TEXT("invalid traceback"));
|
||||
mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or None"), MP_QSTR___context__, MP_QSTR_traceback);
|
||||
}
|
||||
self->traceback = MP_OBJ_TO_PTR(dest[1]);
|
||||
}
|
||||
dest[0] = MP_OBJ_NULL; // indicate success
|
||||
#if MICROPY_CPYTHON_EXCEPTION_CHAIN
|
||||
} else if (attr == MP_QSTR___cause__) {
|
||||
if (dest[1] == mp_const_none) {
|
||||
self->cause = NULL;
|
||||
} else if (!mp_obj_is_type(dest[1], &mp_type_BaseException)) {
|
||||
self->cause = dest[1];
|
||||
} else {
|
||||
mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or None"), attr, MP_QSTR_BaseException);
|
||||
}
|
||||
self->suppress_context = true;
|
||||
dest[0] = MP_OBJ_NULL; // indicate success
|
||||
} else if (attr == MP_QSTR___context__) {
|
||||
if (dest[1] == mp_const_none) {
|
||||
self->context = NULL;
|
||||
} else if (!mp_obj_is_type(dest[1], &mp_type_BaseException)) {
|
||||
self->context = dest[1];
|
||||
} else {
|
||||
mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or None"), attr, MP_QSTR_BaseException);
|
||||
}
|
||||
dest[0] = MP_OBJ_NULL; // indicate success
|
||||
} else if (attr == MP_QSTR___suppress_context__) {
|
||||
self->suppress_context = mp_obj_is_true(dest[1]);
|
||||
dest[0] = MP_OBJ_NULL; // indicate success
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -242,6 +266,14 @@ void mp_obj_exception_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
|
|||
dest[0] = mp_obj_exception_get_value(self_in);
|
||||
} else if (attr == MP_QSTR___traceback__) {
|
||||
dest[0] = (self->traceback) ? MP_OBJ_FROM_PTR(self->traceback) : mp_const_none;
|
||||
#if MICROPY_CPYTHON_EXCEPTION_CHAIN
|
||||
} else if (attr == MP_QSTR___cause__) {
|
||||
dest[0] = (self->cause) ? MP_OBJ_FROM_PTR(self->cause) : mp_const_none;
|
||||
} else if (attr == MP_QSTR___context__) {
|
||||
dest[0] = (self->context) ? MP_OBJ_FROM_PTR(self->context) : mp_const_none;
|
||||
} else if (attr == MP_QSTR___suppress_context__) {
|
||||
dest[0] = mp_obj_new_bool(self->suppress_context);
|
||||
#endif
|
||||
#if MICROPY_CPYTHON_COMPAT
|
||||
} else if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(self->base.type), MP_OBJ_FROM_PTR(&mp_type_OSError))) {
|
||||
if (attr == MP_QSTR_errno) {
|
||||
|
|
|
@ -34,6 +34,8 @@ typedef struct _mp_obj_exception_t {
|
|||
mp_obj_base_t base;
|
||||
mp_obj_tuple_t *args;
|
||||
mp_obj_traceback_t *traceback;
|
||||
mp_obj_t cause, context;
|
||||
bool suppress_context;
|
||||
} mp_obj_exception_t;
|
||||
|
||||
void mp_obj_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind);
|
||||
|
|
37
py/vm.c
37
py/vm.c
|
@ -183,6 +183,15 @@
|
|||
#define TRACE_TICK(current_ip, current_sp, is_exception)
|
||||
#endif // MICROPY_PY_SYS_SETTRACE
|
||||
|
||||
STATIC mp_obj_t get_active_exception(mp_exc_stack_t *exc_sp, mp_exc_stack_t *exc_stack) {
|
||||
for (mp_exc_stack_t *e = exc_sp; e >= exc_stack; --e) {
|
||||
if (e->prev_exc != NULL) {
|
||||
return MP_OBJ_FROM_PTR(e->prev_exc);
|
||||
}
|
||||
}
|
||||
return MP_OBJ_NULL;
|
||||
}
|
||||
|
||||
// fastn has items in reverse order (fastn[0] is local[0], fastn[-1] is local[1], etc)
|
||||
// sp points to bottom of stack which grows up
|
||||
// returns:
|
||||
|
@ -1129,13 +1138,7 @@ unwind_return:
|
|||
ENTRY(MP_BC_RAISE_LAST): {
|
||||
MARK_EXC_IP_SELECTIVE();
|
||||
// search for the inner-most previous exception, to reraise it
|
||||
mp_obj_t obj = MP_OBJ_NULL;
|
||||
for (mp_exc_stack_t *e = exc_sp; e >= exc_stack; --e) {
|
||||
if (e->prev_exc != NULL) {
|
||||
obj = MP_OBJ_FROM_PTR(e->prev_exc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mp_obj_t obj = get_active_exception(exc_sp, exc_stack);
|
||||
if (obj == MP_OBJ_NULL) {
|
||||
obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("no active exception to reraise"));
|
||||
}
|
||||
|
@ -1145,14 +1148,30 @@ unwind_return:
|
|||
ENTRY(MP_BC_RAISE_OBJ): {
|
||||
MARK_EXC_IP_SELECTIVE();
|
||||
mp_obj_t obj = mp_make_raise_obj(TOP());
|
||||
#if MICROPY_CPYTHON_EXCEPTION_CHAIN
|
||||
mp_obj_t active_exception = get_active_exception(exc_sp, exc_stack);
|
||||
if (active_exception != MP_OBJ_NULL) {
|
||||
mp_store_attr(obj, MP_QSTR___context__, active_exception);
|
||||
}
|
||||
#endif
|
||||
RAISE(obj);
|
||||
}
|
||||
|
||||
ENTRY(MP_BC_RAISE_FROM): {
|
||||
MARK_EXC_IP_SELECTIVE();
|
||||
mp_warning(NULL, "exception chaining not supported");
|
||||
sp--; // ignore (pop) "from" argument
|
||||
mp_obj_t cause = POP();
|
||||
mp_obj_t obj = mp_make_raise_obj(TOP());
|
||||
#if MICROPY_CPYTHON_EXCEPTION_CHAIN
|
||||
// search for the inner-most previous exception, to chain it
|
||||
mp_obj_t active_exception = get_active_exception(exc_sp, exc_stack);
|
||||
if (active_exception != MP_OBJ_NULL) {
|
||||
mp_store_attr(obj, MP_QSTR___context__, active_exception);
|
||||
}
|
||||
mp_store_attr(obj, MP_QSTR___cause__, cause);
|
||||
#else
|
||||
(void)cause;
|
||||
mp_warning(NULL, "exception chaining not supported");
|
||||
#endif
|
||||
RAISE(obj);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,46 @@
|
|||
# Exception chaining is not supported, but check that basic
|
||||
# exception works as expected.
|
||||
try:
|
||||
raise Exception from None
|
||||
except Exception:
|
||||
print("Caught Exception")
|
||||
Exception().__cause__
|
||||
except AttributeError:
|
||||
print("SKIP")
|
||||
raise SystemExit
|
||||
|
||||
def print_exc_info(e):
|
||||
print("exception", type(e), repr(e))
|
||||
print("context", type(e.__context__), e.__suppress_context__)
|
||||
print("cause", type(e.__cause__))
|
||||
|
||||
try:
|
||||
try:
|
||||
1/0
|
||||
except Exception as inner:
|
||||
raise RuntimeError() from inner
|
||||
except Exception as e:
|
||||
print_exc_info(e)
|
||||
print()
|
||||
|
||||
try:
|
||||
try:
|
||||
1/0
|
||||
except Exception as inner:
|
||||
raise RuntimeError() from OSError()
|
||||
except Exception as e:
|
||||
print_exc_info(e)
|
||||
print()
|
||||
|
||||
|
||||
try:
|
||||
try:
|
||||
1/0
|
||||
except Exception as inner:
|
||||
raise RuntimeError()
|
||||
except Exception as e:
|
||||
print_exc_info(e)
|
||||
print()
|
||||
|
||||
try:
|
||||
try:
|
||||
1/0
|
||||
except Exception as inner:
|
||||
raise RuntimeError() from None
|
||||
except Exception as e:
|
||||
print_exc_info(e)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
Warning: exception chaining not supported
|
||||
Caught Exception
|
|
@ -522,8 +522,12 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
|
|||
skip_tests.add("basics/scope_implicit.py") # requires checking for unbound local
|
||||
skip_tests.add("basics/try_finally_return2.py") # requires raise_varargs
|
||||
skip_tests.add("basics/unboundlocal.py") # requires checking for unbound local
|
||||
skip_tests.add(
|
||||
"circuitpython/traceback_test.py"
|
||||
skip_tests.update(
|
||||
(
|
||||
"basics/chained_exception.py",
|
||||
"circuitpython/traceback_test.py",
|
||||
"circuitpython/traceback_test_chained.py",
|
||||
)
|
||||
) # because native doesn't have proper traceback info
|
||||
skip_tests.add("extmod/uasyncio_event.py") # unknown issue
|
||||
skip_tests.add("extmod/uasyncio_lock.py") # requires async with
|
||||
|
|
Loading…
Reference in New Issue