diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 9afa77328d..0ab2dede22 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -35,11 +35,11 @@ msgid "" "https://github.com/adafruit/circuitpython/issues\n" msgstr "" -#: py/obj.c +#: py/obj.c shared-bindings/traceback/__init__.c msgid " File \"%q\"" msgstr "" -#: py/obj.c +#: py/obj.c shared-bindings/traceback/__init__.c msgid " File \"%q\", line %d" msgstr "" @@ -322,7 +322,7 @@ msgstr "" msgid "*x must be assignment target" msgstr "" -#: py/obj.c +#: py/obj.c shared-bindings/traceback/__init__.c msgid ", in %q\n" msgstr "" @@ -1185,11 +1185,6 @@ msgstr "" msgid "Input/output error" msgstr "" -#: ports/raspberrypi/common-hal/rp2pio/StateMachine.c -#, c-format -msgid "Missing jmp_pin. Instruction %d jumps on pin" -msgstr "" - #: ports/raspberrypi/common-hal/rp2pio/StateMachine.c #, c-format msgid "Instruction %d shifts in more bits than pin count" @@ -1506,6 +1501,11 @@ msgstr "" msgid "Missing first_set_pin. Instruction %d sets pin(s)" msgstr "" +#: ports/raspberrypi/common-hal/rp2pio/StateMachine.c +#, c-format +msgid "Missing jmp_pin. Instruction %d jumps on pin" +msgstr "" + #: shared-bindings/busio/UART.c shared-bindings/displayio/Group.c msgid "Must be a %q subclass." msgstr "" @@ -2227,7 +2227,7 @@ msgstr "" msgid "Touch alarms not available" msgstr "" -#: py/obj.c +#: py/obj.c shared-bindings/traceback/__init__.c msgid "Traceback (most recent call last):\n" msgstr "" @@ -2525,7 +2525,7 @@ msgid "argument name reused" msgstr "" #: py/argcheck.c shared-bindings/_stage/__init__.c -#: shared-bindings/digitalio/DigitalInOut.c shared-bindings/gamepad/GamePad.c +#: shared-bindings/digitalio/DigitalInOut.c msgid "argument num/types mismatch" msgstr "" @@ -3089,6 +3089,10 @@ msgstr "" msgid "file must be a file opened in byte mode" msgstr "" +#: shared-bindings/traceback/__init__.c +msgid "file write is not available" +msgstr "" + #: shared-bindings/storage/__init__.c msgid "filesystem must provide mount method" msgstr "" @@ -3376,6 +3380,10 @@ msgstr "" msgid "invalid element_size %d, must be, 1, 2, or 4" msgstr "" +#: shared-bindings/traceback/__init__.c +msgid "invalid exception" +msgstr "" + #: extmod/modframebuf.c msgid "invalid format" msgstr "" @@ -3457,6 +3465,10 @@ msgstr "" msgid "lhs and rhs should be compatible" msgstr "" +#: shared-bindings/traceback/__init__.c +msgid "limit should be an int" +msgstr "" + #: py/emitnative.c msgid "local '%q' has type '%q' but source is '%q'" msgstr "" @@ -3609,10 +3621,6 @@ msgstr "" msgid "no active exception to reraise" msgstr "" -#: shared-bindings/socket/__init__.c shared-module/network/__init__.c -msgid "no available NIC" -msgstr "" - #: py/compile.c msgid "no binding for nonlocal found" msgstr "" @@ -4085,6 +4093,10 @@ msgstr "" msgid "source palette too large" msgstr "" +#: shared-bindings/traceback/__init__.c +msgid "stack is not ok" +msgstr "" + #: py/objstr.c msgid "start/end indices" msgstr "" diff --git a/ports/atmel-samd/boards/adafruit_proxlight_trinkey_m0/mpconfigboard.mk b/ports/atmel-samd/boards/adafruit_proxlight_trinkey_m0/mpconfigboard.mk index a617947f9c..cf9afb3d21 100644 --- a/ports/atmel-samd/boards/adafruit_proxlight_trinkey_m0/mpconfigboard.mk +++ b/ports/atmel-samd/boards/adafruit_proxlight_trinkey_m0/mpconfigboard.mk @@ -19,6 +19,7 @@ CIRCUITPY_PWMIO = 0 CIRCUITPY_ROTARYIO = 0 CIRCUITPY_RTC = 0 CIRCUITPY_USB_MIDI = 0 +CIRCUITPY_TRACEBACK = 0 CIRCUITPY_PIXELBUF = 1 CIRCUITPY_BUSDEVICE = 1 diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 5bf46039a7..2acd0cb8db 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -293,6 +293,9 @@ endif ifeq ($(CIRCUITPY_TOUCHIO),1) SRC_PATTERNS += touchio/% endif +ifeq ($(CIRCUITPY_TRACEBACK),1) +SRC_PATTERNS += traceback/% +endif ifeq ($(CIRCUITPY_UHEAP),1) SRC_PATTERNS += uheap/% endif @@ -544,6 +547,7 @@ SRC_SHARED_MODULE_ALL = \ terminalio/Terminal.c \ terminalio/__init__.c \ time/__init__.c \ + traceback/__init__.c \ uheap/__init__.c \ ustack/__init__.c \ vectorio/Circle.c \ diff --git a/py/circuitpy_mpconfig.h b/py/circuitpy_mpconfig.h index 8e31f0411b..ea57f17218 100644 --- a/py/circuitpy_mpconfig.h +++ b/py/circuitpy_mpconfig.h @@ -763,6 +763,13 @@ extern const struct _mp_obj_module_t touchio_module; #define TOUCHIO_MODULE #endif +#if CIRCUITPY_TRACEBACK +extern const struct _mp_obj_module_t traceback_module; +#define TRACEBACK_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_traceback), (mp_obj_t)&traceback_module }, +#else +#define TRACEBACK_MODULE +#endif + #if CIRCUITPY_UHEAP extern const struct _mp_obj_module_t uheap_module; #define UHEAP_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_uheap),(mp_obj_t)&uheap_module }, @@ -917,6 +924,7 @@ extern const struct _mp_obj_module_t msgpack_module; SUPERVISOR_MODULE \ SYNTHIO_MODULE \ TOUCHIO_MODULE \ + TRACEBACK_MODULE \ UHEAP_MODULE \ USB_CDC_MODULE \ USB_HID_MODULE \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 3d72be7491..20056d7377 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -324,6 +324,9 @@ CFLAGS += -DCIRCUITPY_TOUCHIO_USE_NATIVE=$(CIRCUITPY_TOUCHIO_USE_NATIVE) CIRCUITPY_TOUCHIO ?= 1 CFLAGS += -DCIRCUITPY_TOUCHIO=$(CIRCUITPY_TOUCHIO) +CIRCUITPY_TRACEBACK ?= 1 +CFLAGS += -DCIRCUITPY_TRACEBACK=$(CIRCUITPY_TRACEBACK) + # For debugging. CIRCUITPY_UHEAP ?= 0 CFLAGS += -DCIRCUITPY_UHEAP=$(CIRCUITPY_UHEAP) diff --git a/py/modsys.c b/py/modsys.c index cbd1712920..484e29f411 100644 --- a/py/modsys.c +++ b/py/modsys.c @@ -120,25 +120,6 @@ STATIC mp_obj_t mp_sys_exit(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_sys_exit_obj, 0, 1, mp_sys_exit); -STATIC mp_obj_t mp_sys_print_exception(size_t n_args, const mp_obj_t *args) { - #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES - void *stream_obj = &mp_sys_stdout_obj; - if (n_args > 1) { - mp_get_stream_raise(args[1], MP_STREAM_OP_WRITE); - stream_obj = MP_OBJ_TO_PTR(args[1]); - } - - mp_print_t print = {stream_obj, mp_stream_write_adaptor}; - mp_obj_print_exception(&print, args[0]); - #else - (void)n_args; - mp_obj_print_exception(&mp_plat_print, args[0]); - #endif - - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_sys_print_exception_obj, 1, 2, mp_sys_print_exception); - #if MICROPY_PY_SYS_EXC_INFO STATIC mp_obj_t mp_sys_exc_info(void) { mp_obj_t cur_exc = MP_OBJ_FROM_PTR(MP_STATE_VM(cur_exception)); diff --git a/shared-bindings/traceback/__init__.c b/shared-bindings/traceback/__init__.c new file mode 100644 index 0000000000..3d3914bfd1 --- /dev/null +++ b/shared-bindings/traceback/__init__.c @@ -0,0 +1,171 @@ +/* + * This file is part of the Micro Python 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/stream.h" +#include "py/runtime.h" + +#include "supervisor/shared/stack.h" + +//| """Traceback Module +//| +//| This module provides a standard interface to print stack traces of programs. +//| This is useful when you want to print stack traces under program control. +//| +//| """ +//| ... +//| + +//| def print_exception(etype: Type[BaseException], value: BaseException, tb: TracebackType, +//| limit: Optional[int] = None, file: Optional[io.FileIO] = None, chain: Optional[bool] = True) -> None: +//| +//| """Prints exception information and stack trace entries. +//| +//| .. note: Setting `chain` will have no effect as chained exceptions are not yet implemented. +//| +//| :param Type[BaseException] etype: This is ignored and inferred from the type of ``value``. +//| :param BaseException value: The exception. Must be an instance of `BaseException`. +//| :param TracebackType tb: The traceback object. If `None`, the traceback will not be printed. +//| :param int limit: Print up to limit stack trace entries (starting from the caller’s frame) if limit is positive. +//| Otherwise, print the last ``abs(limit)`` entries. If limit is omitted or None, all entries are printed. +//| :param io.FileIO file: If file is omitted or `None`, the output goes to `sys.stderr`; otherwise it should be an open +//| file or file-like object to receive the output. +//| :param bool chain: If `True` then chained exceptions will be printed. +//| +//| """ +//| ... +//| +STATIC mp_obj_t traceback_print_exception(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_etype, ARG_value, ARG_tb, ARG_limit, ARG_file, ARG_chain }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_etype, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_value, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_tb, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_limit, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_file, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_chain, MP_ARG_BOOL, {.u_bool = true} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t exc = args[ARG_value].u_obj; + if (!mp_obj_is_exception_instance(exc)) { + mp_raise_TypeError(translate("invalid exception")); + } + + mp_print_t print = mp_plat_print; + if (args[ARG_file].u_obj != mp_const_none) { + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_get_stream_raise(args[ARG_file].u_obj, MP_STREAM_OP_WRITE); + print.data = MP_OBJ_TO_PTR(args[ARG_file].u_obj); + print.print_strn = mp_stream_write_adaptor; + #else + mp_raise_NotImplementedError(translate("file write is not available")); + #endif + } + + mp_int_t limit = 0; + bool print_tb = true; + if (args[ARG_limit].u_obj != mp_const_none) { + if (!mp_obj_get_int_maybe(args[ARG_limit].u_obj, &limit)) { + mp_raise_TypeError(translate("limit should be an int")); + } + print_tb = !(limit == 0); + } + + if (args[ARG_tb].u_obj != mp_const_none && print_tb) { + if (!stack_ok()) { + mp_raise_RuntimeError(translate("stack is not ok")); + } + size_t n, *values; + mp_obj_exception_get_traceback(exc, &n, &values); + if (n > 0) { + assert(n % 3 == 0); + // Decompress the format strings + const compressed_string_t *traceback = MP_ERROR_TEXT("Traceback (most recent call last):\n"); + char decompressed[decompress_length(traceback)]; + decompress(traceback, decompressed); + #if MICROPY_ENABLE_SOURCE_LINE + const compressed_string_t *frame = MP_ERROR_TEXT(" File \"%q\", line %d"); + #else + const compressed_string_t *frame = MP_ERROR_TEXT(" File \"%q\""); + #endif + char decompressed_frame[decompress_length(frame)]; + decompress(frame, decompressed_frame); + const compressed_string_t *block_fmt = MP_ERROR_TEXT(", in %q\n"); + char decompressed_block[decompress_length(block_fmt)]; + decompress(block_fmt, decompressed_block); + + // Set traceback formatting + // Default: Print full traceback + int i = n - 3, j; + if (limit > 0) { + // Print upto limit traceback + // from caller's frame + limit = n - (limit * 3); + } else if (limit < 0) { + // Print upto limit traceback + // from last + i = 0, limit = 3 + (limit * 3); + } + + // Print the traceback + mp_print_str(&print, decompressed); + for (; i >= limit; i -= 3) { + j = (i < 0) ? -i : i; + #if MICROPY_ENABLE_SOURCE_LINE + mp_printf(&print, decompressed_frame, values[j], (int)values[j + 1]); + #else + mp_printf(&print, decompressed_frame, values[j]); + #endif + // The block name can be NULL if it's unknown + qstr block = values[j + 2]; + if (block == MP_QSTRnull) { + mp_print_str(&print, "\n"); + } else { + mp_printf(&print, decompressed_block, block); + } + } + } + } + mp_obj_print_helper(&print, exc, PRINT_EXC); + mp_print_str(&print, "\n"); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(traceback_print_exception_obj, 3, traceback_print_exception); + +STATIC const mp_rom_map_elem_t traceback_module_globals_table[] = { + // module name + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_traceback) }, + // module functions + { MP_ROM_QSTR(MP_QSTR_print_exception), MP_ROM_PTR(&traceback_print_exception_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(traceback_module_globals, traceback_module_globals_table); + +const mp_obj_module_t traceback_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&traceback_module_globals, +}; diff --git a/shared-module/traceback/__init__.c b/shared-module/traceback/__init__.c new file mode 100644 index 0000000000..4bd8276f34 --- /dev/null +++ b/shared-module/traceback/__init__.c @@ -0,0 +1 @@ +// empty file