Skip to content

Commit 4852e09

Browse files
committed
py: Fix adding of traceback so that it appends to existing info.
This makes exception traceback info self contained (ie doesn't rely on list object, which was a bit of a hack), reduces code size, and reduces RAM footprint of exception by eliminating the list object. Addresses part of issue adafruit#1126.
1 parent d155fec commit 4852e09

File tree

5 files changed

+56
-66
lines changed

5 files changed

+56
-66
lines changed

py/objexcept.c

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
#include "py/gc.h"
3939

4040
// Instance of MemoryError exception - needed by mp_malloc_fail
41-
const mp_obj_exception_t mp_const_MemoryError_obj = {{&mp_type_MemoryError}, MP_OBJ_NULL, mp_const_empty_tuple};
41+
const mp_obj_exception_t mp_const_MemoryError_obj = {{&mp_type_MemoryError}, 0, 0, MP_OBJ_NULL, mp_const_empty_tuple};
4242

4343
// Optionally allocated buffer for storing the first argument of an exception
4444
// allocated when the heap is locked.
@@ -88,7 +88,7 @@ mp_obj_t mp_alloc_emergency_exception_buf(mp_obj_t size_in) {
8888
// Instance of GeneratorExit exception - needed by generator.close()
8989
// This would belong to objgenerator.c, but to keep mp_obj_exception_t
9090
// definition module-private so far, have it here.
91-
const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, MP_OBJ_NULL, mp_const_empty_tuple};
91+
const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, 0, 0, MP_OBJ_NULL, mp_const_empty_tuple};
9292

9393
STATIC void mp_obj_exception_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
9494
mp_obj_exception_t *o = o_in;
@@ -126,7 +126,7 @@ mp_obj_t mp_obj_exception_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t
126126
o->args = mp_obj_new_tuple(n_args, args);
127127
}
128128
o->base.type = type_in;
129-
o->traceback = MP_OBJ_NULL;
129+
o->traceback_data = NULL;
130130
return o;
131131
}
132132

@@ -298,7 +298,7 @@ mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char
298298
// Unfortunately, we won't be able to format the string...
299299
o = &MP_STATE_VM(mp_emergency_exception_obj);
300300
o->base.type = exc_type;
301-
o->traceback = MP_OBJ_NULL;
301+
o->traceback_data = NULL;
302302
o->args = mp_const_empty_tuple;
303303

304304
#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
@@ -332,21 +332,17 @@ mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char
332332
offset += sizeof(void *) - 1;
333333
offset &= ~(sizeof(void *) - 1);
334334

335-
if ((mp_emergency_exception_buf_size - offset) > (sizeof(mp_obj_list_t) + sizeof(mp_obj_t) * 3)) {
335+
if ((mp_emergency_exception_buf_size - offset) > (sizeof(mp_uint_t) * 3)) {
336336
// We have room to store some traceback.
337-
mp_obj_list_t *list = (mp_obj_list_t *)((byte *)MP_STATE_VM(mp_emergency_exception_buf) + offset);
338-
list->base.type = &mp_type_list;
339-
list->items = (mp_obj_t)&list[1];
340-
list->alloc = (MP_STATE_VM(mp_emergency_exception_buf) + mp_emergency_exception_buf_size - (byte *)list->items) / sizeof(list->items[0]);
341-
list->len = 0;
342-
343-
o->traceback = list;
337+
o->traceback_data = (mp_uint_t*)((byte *)MP_STATE_VM(mp_emergency_exception_buf) + offset);
338+
o->traceback_alloc = (MP_STATE_VM(mp_emergency_exception_buf) + mp_emergency_exception_buf_size - (byte *)o->traceback_data) / sizeof(o->traceback_data[0]);
339+
o->traceback_len = 0;
344340
}
345341
}
346342
#endif // MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
347343
} else {
348344
o->base.type = exc_type;
349-
o->traceback = MP_OBJ_NULL;
345+
o->traceback_data = NULL;
350346
o->args = mp_obj_new_tuple(1, NULL);
351347

352348
if (fmt == NULL) {
@@ -416,50 +412,47 @@ void mp_obj_exception_clear_traceback(mp_obj_t self_in) {
416412
GET_NATIVE_EXCEPTION(self, self_in);
417413
// just set the traceback to the null object
418414
// we don't want to call any memory management functions here
419-
self->traceback = MP_OBJ_NULL;
415+
self->traceback_data = NULL;
420416
}
421417

422418
void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, mp_uint_t line, qstr block) {
423419
GET_NATIVE_EXCEPTION(self, self_in);
424420

425-
#if MICROPY_ENABLE_GC
426-
if (gc_is_locked()) {
427-
if (self->traceback == MP_OBJ_NULL) {
428-
// We can't allocate any memory, and no memory has been
429-
// pre-allocated, so there is nothing else we can do.
421+
// append this traceback info to traceback data
422+
// if memory allocation fails (eg because gc is locked), just return
423+
424+
if (self->traceback_data == NULL) {
425+
self->traceback_data = m_new_maybe(mp_uint_t, 3);
426+
if (self->traceback_data == NULL) {
430427
return;
431428
}
432-
mp_obj_list_t *list = self->traceback;
433-
if (list->alloc <= (list->len + 3)) {
434-
// There is some preallocated memory, but not enough to store an
435-
// entire record.
429+
self->traceback_alloc = 3;
430+
self->traceback_len = 0;
431+
} else if (self->traceback_len + 3 > self->traceback_alloc) {
432+
// be conservative with growing traceback data
433+
mp_uint_t *tb_data = m_renew_maybe(mp_uint_t, self->traceback_data, self->traceback_alloc, self->traceback_alloc + 3);
434+
if (tb_data == NULL) {
436435
return;
437436
}
437+
self->traceback_data = tb_data;
438+
self->traceback_alloc += 3;
438439
}
439-
#endif
440440

441-
// for traceback, we are just using the list object for convenience, it's not really a list of Python objects
442-
if (self->traceback == MP_OBJ_NULL) {
443-
self->traceback = mp_obj_new_list_maybe(3);
444-
if (self->traceback == MP_OBJ_NULL) {
445-
return;
446-
}
447-
}
448-
mp_obj_list_t *list = self->traceback;
449-
list->items[0] = (mp_obj_t)(mp_uint_t)file;
450-
list->items[1] = (mp_obj_t)(mp_uint_t)line;
451-
list->items[2] = (mp_obj_t)(mp_uint_t)block;
441+
mp_uint_t *tb_data = &self->traceback_data[self->traceback_len];
442+
self->traceback_len += 3;
443+
tb_data[0] = (mp_uint_t)file;
444+
tb_data[1] = (mp_uint_t)line;
445+
tb_data[2] = (mp_uint_t)block;
452446
}
453447

454448
void mp_obj_exception_get_traceback(mp_obj_t self_in, mp_uint_t *n, mp_uint_t **values) {
455449
GET_NATIVE_EXCEPTION(self, self_in);
456450

457-
if (self->traceback == MP_OBJ_NULL) {
451+
if (self->traceback_data == NULL) {
458452
*n = 0;
459453
*values = NULL;
460454
} else {
461-
mp_uint_t n2;
462-
mp_obj_list_get(self->traceback, &n2, (mp_obj_t**)values);
463-
*n = n2;
455+
*n = self->traceback_len;
456+
*values = self->traceback_data;
464457
}
465458
}

py/objexcept.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131

3232
typedef struct _mp_obj_exception_t {
3333
mp_obj_base_t base;
34-
mp_obj_t traceback; // a list object, holding (file,line,block) as numbers (not Python objects); a hack for now
34+
mp_uint_t traceback_alloc : (BITS_PER_WORD / 2);
35+
mp_uint_t traceback_len : (BITS_PER_WORD / 2);
36+
mp_uint_t *traceback_data;
3537
mp_obj_tuple_t *args;
3638
} mp_obj_exception_t;
3739

py/objlist.c

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -477,25 +477,6 @@ mp_obj_t mp_obj_new_list(mp_uint_t n, mp_obj_t *items) {
477477
return o;
478478
}
479479

480-
// Special method for usage with exceptions
481-
// Doesn't initialize items, assumes they will be initialized by client.
482-
mp_obj_t mp_obj_new_list_maybe(mp_uint_t n) {
483-
mp_obj_list_t *o = m_new_obj_maybe(mp_obj_list_t);
484-
if (!o) {
485-
return o;
486-
}
487-
o->items = m_new_maybe(mp_obj_t, n);
488-
if (!o->items) {
489-
m_del_obj(mp_obj_list_t, o);
490-
return MP_OBJ_NULL;
491-
}
492-
493-
o->base.type = &mp_type_list;
494-
o->len = o->alloc = n;
495-
496-
return o;
497-
}
498-
499480
void mp_obj_list_get(mp_obj_t self_in, mp_uint_t *len, mp_obj_t **items) {
500481
mp_obj_list_t *self = self_in;
501482
*len = self->len;

py/objlist.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,4 @@ typedef struct _mp_obj_list_t {
3535
mp_obj_t *items;
3636
} mp_obj_list_t;
3737

38-
mp_obj_t mp_obj_new_list_maybe(mp_uint_t n);
39-
4038
#endif // __MICROPY_INCLUDED_PY_OBJLIST_H__

tests/misc/print_exception.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,35 @@
66
import traceback
77
print_exception = lambda e, f: traceback.print_exception(None, e, None, file=f)
88

9-
try:
10-
XXX
11-
except Exception as e:
12-
print('caught')
9+
def print_exc(e):
1310
buf = io.StringIO()
1411
print_exception(e, buf)
1512
s = buf.getvalue()
1613
for l in s.split("\n"):
1714
# uPy on pyboard prints <stdin> as file, so remove filename.
1815
if l.startswith(" File "):
19-
print(l[:8], l[-23:])
16+
l = l.split('"')
17+
print(l[0], l[2])
2018
# uPy and CPy tracebacks differ in that CPy prints a source line for
2119
# each traceback entry. In this case, we know that offending line
2220
# has 4-space indent, so filter it out.
2321
elif not l.startswith(" "):
2422
print(l)
23+
24+
# basic exception message
25+
try:
26+
XXX
27+
except Exception as e:
28+
print('caught')
29+
print_exc(e)
30+
31+
# exception message with more than 1 source-code line
32+
def f():
33+
g()
34+
def g():
35+
YYY
36+
try:
37+
f()
38+
except Exception as e:
39+
print('caught')
40+
print_exc(e)

0 commit comments

Comments
 (0)