diff --git a/lib/utils/pyexec.c b/lib/utils/pyexec.c index 2651189915..b3c1ac34bb 100644 --- a/lib/utils/pyexec.c +++ b/lib/utils/pyexec.c @@ -242,7 +242,7 @@ STATIC mp_uint_t mp_reader_stdin_readbyte(void *data) { mp_hal_stdout_tx_strn("\x04", 1); // indicate end to host if (c == CHAR_CTRL_C) { #if MICROPY_KBD_EXCEPTION - MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; + MP_STATE_VM(mp_kbd_exception).traceback->data = NULL; nlr_raise(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); #else mp_raise_type(&mp_type_KeyboardInterrupt); diff --git a/py/mpstate.h b/py/mpstate.h index 423463109d..9f7d72a686 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -120,6 +120,9 @@ typedef struct _mp_state_vm_t { qstr_pool_t *last_pool; + // non-heap memory for creating a traceback if we can't allocate RAM + mp_obj_traceback_t mp_emergency_traceback_obj; + // non-heap memory for creating an exception if we can't allocate RAM mp_obj_exception_t mp_emergency_exception_obj; @@ -137,6 +140,8 @@ typedef struct _mp_state_vm_t { #if MICROPY_KBD_EXCEPTION // exception object of type KeyboardInterrupt mp_obj_exception_t mp_kbd_exception; + // traceback object to store traceback + mp_obj_traceback_t mp_kbd_traceback; #endif // exception object of type ReloadException diff --git a/py/obj.h b/py/obj.h index b259128a2a..b91932b11e 100644 --- a/py/obj.h +++ b/py/obj.h @@ -692,6 +692,7 @@ extern const mp_obj_type_t mp_type_bytearray; extern const mp_obj_type_t mp_type_memoryview; extern const mp_obj_type_t mp_type_float; extern const mp_obj_type_t mp_type_complex; +extern const mp_obj_type_t mp_type_traceback; extern const mp_obj_type_t mp_type_tuple; extern const mp_obj_type_t mp_type_list; extern const mp_obj_type_t mp_type_map; // map (the python builtin, not the dict implementation detail) @@ -791,6 +792,7 @@ extern const struct _mp_obj_bool_t mp_const_true_obj; extern const struct _mp_obj_str_t mp_const_empty_bytes_obj; extern const struct _mp_obj_tuple_t mp_const_empty_tuple_obj; extern const struct _mp_obj_dict_t mp_const_empty_dict_obj; +extern const struct _mp_obj_traceback_t mp_const_empty_traceback_obj; extern const struct _mp_obj_singleton_t mp_const_ellipsis_obj; extern const struct _mp_obj_singleton_t mp_const_notimplemented_obj; extern const struct _mp_obj_exception_t mp_const_GeneratorExit_obj; diff --git a/py/objexcept.c b/py/objexcept.c index d5a858a442..6488413115 100644 --- a/py/objexcept.c +++ b/py/objexcept.c @@ -156,7 +156,15 @@ mp_obj_t mp_obj_exception_make_new(const mp_obj_type_t *type, size_t n_args, con // Populate the exception object o_exc->base.type = type; - o_exc->traceback_data = NULL; + + // Try to allocate memory for the traceback, with fallback to emergency traceback object + o_exc->traceback = m_new_obj_maybe(mp_obj_traceback_t); + if (o_exc->traceback == NULL) { + o_exc->traceback = &MP_STATE_VM(mp_emergency_traceback_obj); + } + + // Populate the traceback object + *o_exc->traceback = mp_const_empty_traceback_obj; mp_obj_tuple_t *o_tuple; if (n_args == 0) { @@ -208,22 +216,25 @@ void mp_obj_exception_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_exception_t *self = MP_OBJ_TO_PTR(self_in); if (dest[0] != MP_OBJ_NULL) { // store/delete attribute - if (attr == MP_QSTR___traceback__ && dest[1] == mp_const_none) { - // We allow 'exc.__traceback__ = None' assignment as low-level - // optimization of pre-allocating exception instance and raising - // it repeatedly - this avoids memory allocation during raise. - // However, uPy will keep adding traceback entries to such - // exception instance, so before throwing it, traceback should - // be cleared like above. - self->traceback_len = 0; + if (attr == MP_QSTR___traceback__) { + if (dest[1] == mp_const_none) { + self->traceback->data = NULL; + } else { + if (!mp_obj_is_type(dest[1], &mp_type_traceback)) { + mp_raise_TypeError(MP_ERROR_TEXT("invalid traceback")); + } + self->traceback = MP_OBJ_TO_PTR(dest[1]); + } dest[0] = MP_OBJ_NULL; // indicate success } return; } if (attr == MP_QSTR_args) { dest[0] = MP_OBJ_FROM_PTR(self->args); - } else if (self->base.type == &mp_type_StopIteration && attr == MP_QSTR_value) { + } else if (attr == MP_QSTR_value && self->base.type == &mp_type_StopIteration) { dest[0] = mp_obj_exception_get_value(self_in); + } else if (attr == MP_QSTR___traceback__) { + dest[0] = (self->traceback->data) ? MP_OBJ_FROM_PTR(self->traceback) : mp_const_none; #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) { @@ -552,7 +563,7 @@ void mp_obj_exception_clear_traceback(mp_obj_t self_in) { GET_NATIVE_EXCEPTION(self, self_in); // just set the traceback to the null object // we don't want to call any memory management functions here - self->traceback_data = NULL; + self->traceback->data = NULL; } void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, size_t line, qstr block) { @@ -561,16 +572,16 @@ void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, size_t line, qs // append this traceback info to traceback data // if memory allocation fails (eg because gc is locked), just return - if (self->traceback_data == NULL) { - self->traceback_data = m_new_maybe(size_t, TRACEBACK_ENTRY_LEN); - if (self->traceback_data == NULL) { + if (self->traceback->data == NULL) { + self->traceback->data = m_new_maybe(size_t, TRACEBACK_ENTRY_LEN); + if (self->traceback->data == NULL) { #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF if (mp_emergency_exception_buf_size >= (mp_int_t)(EMG_BUF_TRACEBACK_OFFSET + EMG_BUF_TRACEBACK_SIZE)) { // There is room in the emergency buffer for traceback data size_t *tb = (size_t *)((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_TRACEBACK_OFFSET); - self->traceback_data = tb; - self->traceback_alloc = EMG_BUF_TRACEBACK_SIZE / sizeof(size_t); + self->traceback->data = tb; + self->traceback->alloc = EMG_BUF_TRACEBACK_SIZE / sizeof(size_t); } else { // Can't allocate and no room in emergency buffer return; @@ -581,28 +592,28 @@ void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, size_t line, qs #endif } else { // Allocated the traceback data on the heap - self->traceback_alloc = TRACEBACK_ENTRY_LEN; + self->traceback->alloc = TRACEBACK_ENTRY_LEN; } - self->traceback_len = 0; - } else if (self->traceback_len + TRACEBACK_ENTRY_LEN > self->traceback_alloc) { + self->traceback->len = 0; + } else if (self->traceback->len + TRACEBACK_ENTRY_LEN > self->traceback->alloc) { #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF - if (self->traceback_data == (size_t *)MP_STATE_VM(mp_emergency_exception_buf)) { + if (self->traceback->data == (size_t *)MP_STATE_VM(mp_emergency_exception_buf)) { // Can't resize the emergency buffer return; } #endif // be conservative with growing traceback data - size_t *tb_data = m_renew_maybe(size_t, self->traceback_data, self->traceback_alloc, - self->traceback_alloc + TRACEBACK_ENTRY_LEN, true); + size_t *tb_data = m_renew_maybe(size_t, self->traceback->data, self->traceback->alloc, + self->traceback->alloc + TRACEBACK_ENTRY_LEN, true); if (tb_data == NULL) { return; } - self->traceback_data = tb_data; - self->traceback_alloc += TRACEBACK_ENTRY_LEN; + self->traceback->data = tb_data; + self->traceback->alloc += TRACEBACK_ENTRY_LEN; } - size_t *tb_data = &self->traceback_data[self->traceback_len]; - self->traceback_len += TRACEBACK_ENTRY_LEN; + size_t *tb_data = &self->traceback->data[self->traceback->len]; + self->traceback->len += TRACEBACK_ENTRY_LEN; tb_data[0] = file; tb_data[1] = line; tb_data[2] = block; @@ -611,12 +622,12 @@ void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, size_t line, qs void mp_obj_exception_get_traceback(mp_obj_t self_in, size_t *n, size_t **values) { GET_NATIVE_EXCEPTION(self, self_in); - if (self->traceback_data == NULL) { + if (self->traceback->data == NULL) { *n = 0; *values = NULL; } else { - *n = self->traceback_len; - *values = self->traceback_data; + *n = self->traceback->len; + *values = self->traceback->data; } } diff --git a/py/objexcept.h b/py/objexcept.h index d7b39add87..261885e54d 100644 --- a/py/objexcept.h +++ b/py/objexcept.h @@ -28,13 +28,12 @@ #include "py/obj.h" #include "py/objtuple.h" +#include "py/objtraceback.h" typedef struct _mp_obj_exception_t { mp_obj_base_t base; - size_t traceback_alloc : (8 * sizeof(size_t) / 2); - size_t traceback_len : (8 * sizeof(size_t) / 2); - size_t *traceback_data; mp_obj_tuple_t *args; + mp_obj_traceback_t *traceback; } mp_obj_exception_t; void mp_obj_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind); diff --git a/py/objgenerator.c b/py/objgenerator.c index 2139e4478a..7bafd9ce98 100644 --- a/py/objgenerator.c +++ b/py/objgenerator.c @@ -38,7 +38,7 @@ #include "supervisor/shared/translate.h" // Instance of GeneratorExit exception - needed by generator.close() -const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, 0, 0, NULL, (mp_obj_tuple_t *)&mp_const_empty_tuple_obj}; +const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, (mp_obj_tuple_t *)&mp_const_empty_tuple_obj, (mp_obj_traceback_t *)&mp_const_empty_traceback_obj}; /******************************************************************************/ /* generator wrapper */ diff --git a/py/objtraceback.c b/py/objtraceback.c new file mode 100644 index 0000000000..f1f74142d6 --- /dev/null +++ b/py/objtraceback.c @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 microDev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/objtraceback.h" + +const mp_obj_traceback_t mp_const_empty_traceback_obj = {{&mp_type_traceback}, 0, 0, NULL}; + +STATIC void mp_obj_traceback_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_traceback_t *o = MP_OBJ_TO_PTR(o_in); + mp_printf(print, "", o); +} + +const mp_obj_type_t mp_type_traceback = { + { &mp_type_type }, + .name = MP_QSTR_traceback, + .print = mp_obj_traceback_print, +}; diff --git a/py/objtraceback.h b/py/objtraceback.h new file mode 100644 index 0000000000..992fe89b01 --- /dev/null +++ b/py/objtraceback.h @@ -0,0 +1,39 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 microDev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_PY_OBJTRACEBACK_H +#define MICROPY_INCLUDED_PY_OBJTRACEBACK_H + +#include "py/obj.h" + +typedef struct _mp_obj_traceback_t { + mp_obj_base_t base; + size_t alloc : (8 * sizeof(size_t) / 2); + size_t len : (8 * sizeof(size_t) / 2); + size_t *data; +} mp_obj_traceback_t; + +#endif // MICROPY_INCLUDED_PY_OBJTRACEBACK_H diff --git a/py/py.cmake b/py/py.cmake index 2b5d437b57..6f0514e0e3 100644 --- a/py/py.cmake +++ b/py/py.cmake @@ -95,6 +95,7 @@ set(MICROPY_SOURCE_PY ${MICROPY_PY_DIR}/objstr.c ${MICROPY_PY_DIR}/objstringio.c ${MICROPY_PY_DIR}/objstrunicode.c + ${MICROPY_PY_DIR}/objtraceback.c ${MICROPY_PY_DIR}/objtuple.c ${MICROPY_PY_DIR}/objtype.c ${MICROPY_PY_DIR}/objzip.c diff --git a/py/py.mk b/py/py.mk index 94ac82f6f0..7c3eaf4f46 100644 --- a/py/py.mk +++ b/py/py.mk @@ -153,6 +153,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\ objstr.o \ objstrunicode.o \ objstringio.o \ + objtraceback.o \ objtuple.o \ objtype.o \ objzip.o \ diff --git a/py/runtime.c b/py/runtime.c index ceb5e83b14..4d4fc3592b 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -79,17 +79,14 @@ void mp_init(void) { #if MICROPY_KBD_EXCEPTION // initialise the exception object for raising KeyboardInterrupt MP_STATE_VM(mp_kbd_exception).base.type = &mp_type_KeyboardInterrupt; - MP_STATE_VM(mp_kbd_exception).traceback_alloc = 0; - MP_STATE_VM(mp_kbd_exception).traceback_len = 0; - MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; MP_STATE_VM(mp_kbd_exception).args = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj; + MP_STATE_VM(mp_kbd_exception).traceback = &MP_STATE_VM(mp_kbd_traceback); + *MP_STATE_VM(mp_kbd_exception).traceback = mp_const_empty_traceback_obj; #endif MP_STATE_VM(mp_reload_exception).base.type = &mp_type_ReloadException; - MP_STATE_VM(mp_reload_exception).traceback_alloc = 0; - MP_STATE_VM(mp_reload_exception).traceback_len = 0; - MP_STATE_VM(mp_reload_exception).traceback_data = NULL; MP_STATE_VM(mp_reload_exception).args = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj; + MP_STATE_VM(mp_reload_exception).traceback = (mp_obj_traceback_t *)&mp_const_empty_traceback_obj; // call port specific initialization if any #ifdef MICROPY_PORT_INIT_FUNC diff --git a/py/scheduler.c b/py/scheduler.c index f11317dc1d..733722e46a 100644 --- a/py/scheduler.c +++ b/py/scheduler.c @@ -40,7 +40,7 @@ void MICROPY_WRAP_MP_SCHED_EXCEPTION(mp_sched_exception)(mp_obj_t exc) { #if MICROPY_KBD_EXCEPTION // This function may be called asynchronously at any time so only do the bare minimum. void MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(mp_sched_keyboard_interrupt)(void) { - MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; + MP_STATE_VM(mp_kbd_exception).traceback->data = NULL; mp_sched_exception(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); } #endif diff --git a/shared-bindings/watchdog/__init__.c b/shared-bindings/watchdog/__init__.c index 0911aca282..dbe06d1323 100644 --- a/shared-bindings/watchdog/__init__.c +++ b/shared-bindings/watchdog/__init__.c @@ -61,10 +61,8 @@ const mp_obj_type_t mp_type_WatchDogTimeout = { mp_obj_exception_t mp_watchdog_timeout_exception = { .base.type = &mp_type_WatchDogTimeout, - .traceback_alloc = 0, - .traceback_len = 0, - .traceback_data = NULL, .args = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj, + .traceback = (mp_obj_traceback_t *)&mp_const_empty_traceback_obj, }; STATIC const mp_rom_map_elem_t watchdog_module_globals_table[] = {