py/modsys: Add optional sys.tracebacklimit attribute.

With behaviour as per CPython.

Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
Damien George 2021-07-27 00:41:27 +10:00
parent bc181550a4
commit cac939ddc3
10 changed files with 117 additions and 3 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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,
};

View File

@ -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

View File

@ -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,
};

View File

@ -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) {

View File

@ -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

View File

@ -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"))

View File

@ -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

View File

@ -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)