Skip to content

Commit 56fbc2e

Browse files
committed
py/objexcept: Add __traceback__ attribute getter.
This makes it easier to provide custom formatting of exception stack traces, e.g. to make them fit on a tiny display. Signed-off-by: David (Pololu) <dev-david@pololu.com>
1 parent 1095bbb commit 56fbc2e

5 files changed

Lines changed: 69 additions & 4 deletions

File tree

py/objexcept.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,23 @@ void mp_obj_exception_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
280280
}
281281
return;
282282
}
283-
if (attr == MP_QSTR_args) {
283+
if (attr == MP_QSTR___traceback__) {
284+
// We expose traceback data via 'exc.__traceback__' to allow
285+
// custom formatting of the traceback by user code.
286+
size_t entry_count = self->traceback_len / TRACEBACK_ENTRY_LEN;
287+
size_t *data = self->traceback_data;
288+
mp_obj_t obj = mp_obj_new_list(entry_count, NULL);
289+
mp_obj_list_t *list = MP_OBJ_TO_PTR(obj);
290+
for (size_t i = 0; i < entry_count; i++) {
291+
size_t *src = &data[i * TRACEBACK_ENTRY_LEN];
292+
mp_obj_t entry[3];
293+
entry[0] = MP_OBJ_NEW_QSTR(src[0]); // filename
294+
entry[1] = MP_OBJ_NEW_SMALL_INT(src[1]); // line number
295+
entry[2] = MP_OBJ_NEW_QSTR(src[2]); // block
296+
list->items[i] = mp_obj_new_tuple(3, entry);
297+
}
298+
dest[0] = obj;
299+
} else if (attr == MP_QSTR_args) {
284300
decompress_error_text_maybe(self);
285301
dest[0] = MP_OBJ_FROM_PTR(self->args);
286302
} else if (attr == MP_QSTR_value || attr == MP_QSTR_errno) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
categories: Types,Exception
3+
description: The ``__traceback__`` attribute of exceptions is a simple list of tuples.
4+
cause: MicroPython is optimised to reduce code size.
5+
workaround: Either treat ``__traceback__`` as an opaque value and use the `traceback` module to format tracebacks, or check the type of ``__traceback__`` before using it.
6+
"""
7+
8+
9+
def foo():
10+
1 / 0
11+
12+
13+
try:
14+
foo()
15+
except Exception as e:
16+
print(e.__traceback__)

tests/micropython/traceback.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# This comment allows test runners like run_script_on_remote_target
2+
# to safely prepend a line of code without messing up line numbers.
3+
4+
5+
def foo():
6+
1 / 0
7+
8+
9+
def normalize(filename):
10+
if filename == "<stdin>":
11+
return "traceback.py"
12+
return filename.split("/")[-1]
13+
14+
15+
try:
16+
foo()
17+
except Exception as e:
18+
tb = e.__traceback__
19+
for t in tb:
20+
print("%s:%d" % (normalize(t[0]), t[1]), *t[2:])
21+
e.__traceback__ = None
22+
print(e.__traceback__)
23+
try:
24+
e.args = 77
25+
except AttributeError:
26+
print("setting args not allowed")

tests/micropython/traceback.py.exp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
traceback.py:6 foo
2+
traceback.py:16 <module>
3+
[]
4+
setting args not allowed

tests/run-tests.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"misc/sys_settrace_features.py",
8181
"misc/sys_settrace_generator.py",
8282
"misc/sys_settrace_loop.py",
83+
"micropython/traceback.py",
8384
),
8485
}
8586

@@ -108,6 +109,8 @@
108109
"micropython/schedule.py",
109110
# These require sys.exc_info().
110111
"misc/sys_exc_info.py",
112+
# These require exception tracebacks.
113+
"micropython/traceback.py",
111114
# These require sys.settrace().
112115
"misc/sys_settrace_cov.py",
113116
"misc/sys_settrace_features.py",
@@ -125,9 +128,7 @@
125128
"stress/list_sort.py", # watchdog kicks in because it takes too long
126129
),
127130
"minimal": (
128-
"basics/class_inplace_op.py", # all special methods not supported
129-
"basics/subclass_native_init.py", # native subclassing corner cases not support
130-
"micropython/opt_level.py", # don't assume line numbers are stored
131+
"micropython/traceback.py", # no line numbers, no list[N:] syntax
131132
),
132133
"nrf": (
133134
"basics/io_buffered_writer.py",
@@ -827,6 +828,8 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
827828

828829
# Skip platform-specific tests.
829830
skip_tests.update(platform_tests_to_skip.get(args.platform, ()))
831+
if args.build == "minimal":
832+
skip_tests.update(platform_tests_to_skip.get(args.build, ()))
830833

831834
# Some tests are known to fail on 64-bit machines
832835
if pyb is None and platform.architecture()[0] == "64bit":

0 commit comments

Comments
 (0)