summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien George <damien@micropython.org>2021-07-27 00:41:27 +1000
committerDamien George <damien@micropython.org>2022-03-10 10:43:21 +1100
commitcac939ddc3625da7e6cf1cf0309daba25fc1cedb (patch)
tree8e0417aab0ea5c9e22d6679edefb6a265cd31ab5
parentbc181550a4790f4dcfaf10e7e61ecfcdf346d5a8 (diff)
py/modsys: Add optional sys.tracebacklimit attribute.
With behaviour as per CPython. Signed-off-by: Damien George <damien@micropython.org>
-rw-r--r--docs/library/sys.rst8
-rw-r--r--ports/unix/variants/coverage/mpconfigvariant.h2
-rw-r--r--py/modsys.c3
-rw-r--r--py/mpconfig.h7
-rw-r--r--py/mpstate.h3
-rw-r--r--py/objexcept.c10
-rw-r--r--py/runtime.c4
-rw-r--r--tests/basics/sys_tracebacklimit.py78
-rwxr-xr-xtests/run-tests.py1
-rw-r--r--tests/unix/extra_coverage.py.exp4
10 files changed, 117 insertions, 3 deletions
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)