From cac939ddc3625da7e6cf1cf0309daba25fc1cedb Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 27 Jul 2021 00:41:27 +1000 Subject: [PATCH] py/modsys: Add optional sys.tracebacklimit attribute. With behaviour as per CPython. Signed-off-by: Damien George --- docs/library/sys.rst | 8 ++ .../unix/variants/coverage/mpconfigvariant.h | 2 + py/modsys.c | 3 + py/mpconfig.h | 7 +- py/mpstate.h | 3 + py/objexcept.c | 10 +++ py/runtime.c | 4 + tests/basics/sys_tracebacklimit.py | 78 +++++++++++++++++++ tests/run-tests.py | 1 + tests/unix/extra_coverage.py.exp | 4 +- 10 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 tests/basics/sys_tracebacklimit.py diff --git a/docs/library/sys.rst b/docs/library/sys.rst index d36394c88..f4ff8786a 100644 --- a/docs/library/sys.rst +++ b/docs/library/sys.rst @@ -144,6 +144,14 @@ Constants Standard output `stream`. +.. data:: tracebacklimit + + A mutable attribute holding an integer value which is the maximum number of traceback + entries to store in an exception. Set to 0 to disable adding tracebacks. Defaults + to 1000. + + Note: this is not available on all ports. + .. data:: version Python language version that this implementation conforms to, as a string. diff --git a/ports/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index db01c4bcd..9b6b40775 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -34,6 +34,7 @@ #define MICROPY_REPL_EMACS_WORDS_MOVE (1) #define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (1) #define MICROPY_WARNINGS_CATEGORY (1) +#define MICROPY_MODULE_ATTR_DELEGATION (1) #define MICROPY_MODULE_GETATTR (1) #define MICROPY_PY_DELATTR_SETATTR (1) #define MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS (1) @@ -44,6 +45,7 @@ #define MICROPY_PY_BUILTINS_HELP (1) #define MICROPY_PY_BUILTINS_HELP_MODULES (1) #define MICROPY_PY_SYS_GETSIZEOF (1) +#define MICROPY_PY_SYS_TRACEBACKLIMIT (1) #define MICROPY_PY_MATH_CONSTANTS (1) #define MICROPY_PY_MATH_FACTORIAL (1) #define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) diff --git a/py/modsys.c b/py/modsys.c index a05709f8e..c44c7ed45 100644 --- a/py/modsys.c +++ b/py/modsys.c @@ -185,6 +185,9 @@ MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_settrace_obj, mp_sys_settrace); #if MICROPY_PY_SYS_ATTR_DELEGATION STATIC const uint16_t sys_mutable_keys[] = { + #if MICROPY_PY_SYS_TRACEBACKLIMIT + MP_QSTR_tracebacklimit, + #endif MP_QSTRnull, }; diff --git a/py/mpconfig.h b/py/mpconfig.h index 617e89708..be967e698 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1377,10 +1377,15 @@ typedef double mp_float_t; #define MICROPY_PY_SYS_STDIO_BUFFER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif +// Whether to provide sys.tracebacklimit mutable attribute +#ifndef MICROPY_PY_SYS_TRACEBACKLIMIT +#define MICROPY_PY_SYS_TRACEBACKLIMIT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) +#endif + // Whether the sys module supports attribute delegation // This is enabled automatically when needed by other features #ifndef MICROPY_PY_SYS_ATTR_DELEGATION -#define MICROPY_PY_SYS_ATTR_DELEGATION (0) +#define MICROPY_PY_SYS_ATTR_DELEGATION (MICROPY_PY_SYS_TRACEBACKLIMIT) #endif // Whether to provide "uerrno" module diff --git a/py/mpstate.h b/py/mpstate.h index f29e6be50..499d86351 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -41,6 +41,9 @@ // variable, but in the future it is hoped that the state can become local. enum { + #if MICROPY_PY_SYS_TRACEBACKLIMIT + MP_SYS_MUTABLE_TRACEBACKLIMIT, + #endif MP_SYS_MUTABLE_NUM, }; diff --git a/py/objexcept.c b/py/objexcept.c index 7a86c3647..dca287bb6 100644 --- a/py/objexcept.c +++ b/py/objexcept.c @@ -575,6 +575,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 MICROPY_PY_SYS_TRACEBACKLIMIT + mp_int_t max_traceback = MP_OBJ_SMALL_INT_VALUE(MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_TRACEBACKLIMIT])); + if (max_traceback <= 0) { + return; + } else if (self->traceback_data != NULL && self->traceback_len >= max_traceback * TRACEBACK_ENTRY_LEN) { + self->traceback_len -= TRACEBACK_ENTRY_LEN; + memmove(self->traceback_data, self->traceback_data + TRACEBACK_ENTRY_LEN, self->traceback_len * sizeof(self->traceback_data[0])); + } + #endif + if (self->traceback_data == NULL) { self->traceback_data = m_new_maybe(size_t, TRACEBACK_ENTRY_LEN); if (self->traceback_data == NULL) { diff --git a/py/runtime.c b/py/runtime.c index 8c93f539e..665c9f220 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -141,6 +141,10 @@ void mp_init(void) { MP_STATE_THREAD(current_code_state) = NULL; #endif + #if MICROPY_PY_SYS_TRACEBACKLIMIT + MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_TRACEBACKLIMIT]) = MP_OBJ_NEW_SMALL_INT(1000); + #endif + #if MICROPY_PY_BLUETOOTH MP_STATE_VM(bluetooth) = MP_OBJ_NULL; #endif diff --git a/tests/basics/sys_tracebacklimit.py b/tests/basics/sys_tracebacklimit.py new file mode 100644 index 000000000..1ee638967 --- /dev/null +++ b/tests/basics/sys_tracebacklimit.py @@ -0,0 +1,78 @@ +# test sys.tracebacklimit + +try: + try: + import usys as sys + import uio as io + except ImportError: + import sys + import io +except ImportError: + print("SKIP") + raise SystemExit + +try: + sys.tracebacklimit = 1000 +except AttributeError: + print("SKIP") + raise SystemExit + +if hasattr(sys, "print_exception"): + print_exception = sys.print_exception +else: + import traceback + + print_exception = lambda e, f: traceback.print_exception(None, e, sys.exc_info()[2], file=f) + + +def print_exc(e): + buf = io.StringIO() + print_exception(e, buf) + s = buf.getvalue() + for l in s.split("\n"): + # Remove filename. + if l.startswith(" File "): + l = l.split('"') + print(l[0], l[2]) + # uPy and CPy tracebacks differ in that CPy prints a source line for + # each traceback entry. In this case, we know that offending line + # has 4-space indent, so filter it out. + elif not l.startswith(" "): + print(l) + + +def f0(): + raise ValueError("value") + + +def f1(): + f0() + + +def f2(): + f1() + + +def f3(): + f2() + + +def ftop(): + try: + f3() + except ValueError as er: + print_exc(er) + + +ftop() + +for limit in range(4, -2, -1): + print("limit", limit) + sys.tracebacklimit = limit + ftop() + + +# test deleting the attribute +print(hasattr(sys, "tracebacklimit")) +del sys.tracebacklimit +print(hasattr(sys, "tracebacklimit")) diff --git a/tests/run-tests.py b/tests/run-tests.py index edd20b9bd..dfe0a8e55 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -504,6 +504,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("basics/del_local.py") # requires checking for unbound local skip_tests.add("basics/exception_chain.py") # raise from is not supported skip_tests.add("basics/scope_implicit.py") # requires checking for unbound local + skip_tests.add("basics/sys_tracebacklimit.py") # requires traceback info 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("extmod/uasyncio_event.py") # unknown issue diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 1a5a2cde8..67d299bca 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -45,8 +45,8 @@ utime utimeq argv atexit byteorder exc_info exit getsizeof implementation maxsize modules path platform print_exception -stderr stdin stdout version -version_info +stderr stdin stdout tracebacklimit +version version_info ementation # attrtuple (start=1, stop=2, step=3)