Skip to content

Commit 9da18f8

Browse files
committed
Add supervisor.get_previous_traceback() function.
Useful for #1084.
1 parent 9bd1ad6 commit 9da18f8

File tree

9 files changed

+157
-22
lines changed

9 files changed

+157
-22
lines changed

lib/utils/pyexec.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
143143
result->return_code = ret;
144144
if (ret != 0) {
145145
mp_obj_t return_value = (mp_obj_t)nlr.ret_val;
146-
result->exception_type = mp_obj_get_type(return_value);
146+
result->exception = return_value;
147147
result->exception_line = -1;
148148

149149
if (mp_obj_is_exception_instance(return_value)) {

lib/utils/pyexec.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ typedef enum {
3535

3636
typedef struct {
3737
int return_code;
38-
const mp_obj_type_t * exception_type;
38+
mp_obj_t exception;
3939
int exception_line;
4040
} pyexec_result_t;
4141

main.c

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#include "supervisor/shared/safe_mode.h"
5757
#include "supervisor/shared/status_leds.h"
5858
#include "supervisor/shared/stack.h"
59+
#include "supervisor/shared/traceback.h"
5960
#include "supervisor/serial.h"
6061

6162
#include "boards/board.h"
@@ -215,7 +216,41 @@ bool maybe_run_list(const char * const * filenames, pyexec_result_t* exec_result
215216
return true;
216217
}
217218

218-
void cleanup_after_vm(supervisor_allocation* heap) {
219+
STATIC void count_strn(void *data, const char *str, size_t len) {
220+
*(size_t*)data += len;
221+
}
222+
223+
void cleanup_after_vm(supervisor_allocation* heap, mp_obj_t exception) {
224+
// Get the traceback of any exception from this run off the heap.
225+
// MP_OBJ_SENTINEL means "this run does not contribute to traceback storage, don't touch it"
226+
// MP_OBJ_NULL (=0) means "this run completed successfully, clear any stored traceback"
227+
if (exception != MP_OBJ_SENTINEL) {
228+
free_memory(prev_traceback_allocation);
229+
// ReloadException is exempt from traceback printing in pyexec_file(), so treat it as "no
230+
// traceback" here too.
231+
if (exception && exception != MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception))) {
232+
size_t traceback_len = 0;
233+
mp_print_t print_count = {&traceback_len, count_strn};
234+
mp_obj_print_exception(&print_count, exception);
235+
prev_traceback_allocation = allocate_memory(align32_size(traceback_len + 1), false, true);
236+
// Empirically, this never fails in practice - even when the heap is totally filled up
237+
// with single-block-sized objects referenced by a root pointer, exiting the VM frees
238+
// up several hundred bytes, sufficient for the traceback (which tends to be shortened
239+
// because there wasn't memory for the full one). There may be convoluted ways of
240+
// making it fail, but at this point I believe they are not worth spending code on.
241+
if (prev_traceback_allocation != NULL) {
242+
vstr_t vstr;
243+
vstr_init_fixed_buf(&vstr, traceback_len, (char*)prev_traceback_allocation->ptr);
244+
mp_print_t print = {&vstr, (mp_print_strn_t)vstr_add_strn};
245+
mp_obj_print_exception(&print, exception);
246+
((char*)prev_traceback_allocation->ptr)[traceback_len] = '\0';
247+
}
248+
}
249+
else {
250+
prev_traceback_allocation = NULL;
251+
}
252+
}
253+
219254
// Reset port-independent devices, like CIRCUITPY_BLEIO_HCI.
220255
reset_devices();
221256
// Turn off the display and flush the fileystem before the heap disappears.
@@ -268,7 +303,7 @@ bool run_code_py(safe_mode_t safe_mode) {
268303
pyexec_result_t result;
269304

270305
result.return_code = 0;
271-
result.exception_type = NULL;
306+
result.exception = MP_OBJ_NULL;
272307
result.exception_line = 0;
273308

274309
bool skip_repl;
@@ -313,7 +348,7 @@ bool run_code_py(safe_mode_t safe_mode) {
313348
}
314349
#endif
315350
}
316-
cleanup_after_vm(heap);
351+
cleanup_after_vm(heap, result.exception);
317352

318353
// If a new next code file was set, that is a reason to keep it (obviously). Stuff this into
319354
// the options because it can be treated like any other reason-for-stickiness bit. The
@@ -473,7 +508,8 @@ void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
473508
start_mp(heap);
474509

475510
// TODO(tannewt): Re-add support for flashing boot error output.
476-
bool found_boot = maybe_run_list(boot_py_filenames, NULL);
511+
pyexec_result_t result = {0, MP_OBJ_NULL, 0};
512+
bool found_boot = maybe_run_list(boot_py_filenames, &result);
477513
(void) found_boot;
478514

479515
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
@@ -484,7 +520,7 @@ void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
484520
boot_output_file = NULL;
485521
#endif
486522

487-
cleanup_after_vm(heap);
523+
cleanup_after_vm(heap, result.exception);
488524
}
489525
}
490526

@@ -501,7 +537,7 @@ int run_repl(void) {
501537
} else {
502538
exit_code = pyexec_friendly_repl();
503539
}
504-
cleanup_after_vm(heap);
540+
cleanup_after_vm(heap, MP_OBJ_SENTINEL);
505541
autoreload_resume();
506542
return exit_code;
507543
}

shared-bindings/supervisor/__init__.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "supervisor/shared/autoreload.h"
3535
#include "supervisor/shared/rgb_led_status.h"
3636
#include "supervisor/shared/stack.h"
37+
#include "supervisor/shared/traceback.h"
3738
#include "supervisor/shared/translate.h"
3839

3940
#include "shared-bindings/supervisor/__init__.h"
@@ -193,6 +194,34 @@ STATIC mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos
193194
}
194195
MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_next_code_file_obj, 0, supervisor_set_next_code_file);
195196

197+
//| def get_previous_traceback() -> Optional[str]:
198+
//| """If the last vm run ended with an exception (including the KeyboardInterrupt caused by
199+
//| CTRL-C), returns the traceback as a string.
200+
//| Otherwise, returns ``None``.
201+
//|
202+
//| An exception traceback is only preserved over a soft reload, a hard reset clears it.
203+
//|
204+
//| Only code (main or boot) runs are considered, not REPL runs."""
205+
//| ...
206+
//|
207+
STATIC mp_obj_t supervisor_get_previous_traceback(void) {
208+
//TODO is this a safe and proper way of making a heap-allocated string object that points at long-lived (and likely long and unique) data outside the heap?
209+
if (prev_traceback_allocation) {
210+
size_t len = strlen((const char*)prev_traceback_allocation->ptr);
211+
if (len > 0) {
212+
mp_obj_str_t *o = m_new_obj(mp_obj_str_t);
213+
o->base.type = &mp_type_str;
214+
o->len = len;
215+
//TODO is it a good assumption that callers probably aren't going to compare this string, so skip computing the hash?
216+
o->hash = 0;
217+
o->data = (const byte*)prev_traceback_allocation->ptr;
218+
return MP_OBJ_FROM_PTR(o);
219+
}
220+
}
221+
return mp_const_none;
222+
}
223+
MP_DEFINE_CONST_FUN_OBJ_0(supervisor_get_previous_traceback_obj, supervisor_get_previous_traceback);
224+
196225
STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
197226
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_supervisor) },
198227
{ MP_OBJ_NEW_QSTR(MP_QSTR_enable_autoreload), MP_ROM_PTR(&supervisor_enable_autoreload_obj) },
@@ -202,6 +231,7 @@ STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
202231
{ MP_ROM_QSTR(MP_QSTR_reload), MP_ROM_PTR(&supervisor_reload_obj) },
203232
{ MP_ROM_QSTR(MP_QSTR_set_next_stack_limit), MP_ROM_PTR(&supervisor_set_next_stack_limit_obj) },
204233
{ MP_ROM_QSTR(MP_QSTR_set_next_code_file), MP_ROM_PTR(&supervisor_set_next_code_file_obj) },
234+
{ MP_ROM_QSTR(MP_QSTR_get_previous_traceback), MP_ROM_PTR(&supervisor_get_previous_traceback_obj) },
205235
};
206236

207237
STATIC MP_DEFINE_CONST_DICT(supervisor_module_globals, supervisor_module_globals_table);

supervisor/shared/memory.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ enum {
3838
2
3939
// next_code_allocation
4040
+ 1
41+
// prev_traceback_allocation
42+
+ 1
4143
#ifdef EXTERNAL_FLASH_DEVICES
4244
+ 1
4345
#endif

supervisor/shared/rgb_led_status.c

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -391,22 +391,25 @@ void prep_rgb_status_animation(const pyexec_result_t* result,
391391
if (!status->ok) {
392392
status->total_exception_cycle = EXCEPTION_TYPE_LENGTH_MS * 3 + LINE_NUMBER_TOGGLE_LENGTH * status->digit_sum + LINE_NUMBER_TOGGLE_LENGTH * num_places;
393393
}
394-
if (!result->exception_type) {
394+
if (!result->exception) {
395395
status->exception_color = OTHER_ERROR;
396-
} else if (mp_obj_is_subclass_fast(result->exception_type, &mp_type_IndentationError)) {
397-
status->exception_color = INDENTATION_ERROR;
398-
} else if (mp_obj_is_subclass_fast(result->exception_type, &mp_type_SyntaxError)) {
399-
status->exception_color = SYNTAX_ERROR;
400-
} else if (mp_obj_is_subclass_fast(result->exception_type, &mp_type_NameError)) {
401-
status->exception_color = NAME_ERROR;
402-
} else if (mp_obj_is_subclass_fast(result->exception_type, &mp_type_OSError)) {
403-
status->exception_color = OS_ERROR;
404-
} else if (mp_obj_is_subclass_fast(result->exception_type, &mp_type_ValueError)) {
405-
status->exception_color = VALUE_ERROR;
406-
} else if (mp_obj_is_subclass_fast(result->exception_type, &mp_type_MpyError)) {
407-
status->exception_color = MPY_ERROR;
408396
} else {
409-
status->exception_color = OTHER_ERROR;
397+
mp_obj_type_t* exception_type = mp_obj_get_type(result->exception);
398+
if (mp_obj_is_subclass_fast(exception_type, &mp_type_IndentationError)) {
399+
status->exception_color = INDENTATION_ERROR;
400+
} else if (mp_obj_is_subclass_fast(exception_type, &mp_type_SyntaxError)) {
401+
status->exception_color = SYNTAX_ERROR;
402+
} else if (mp_obj_is_subclass_fast(exception_type, &mp_type_NameError)) {
403+
status->exception_color = NAME_ERROR;
404+
} else if (mp_obj_is_subclass_fast(exception_type, &mp_type_OSError)) {
405+
status->exception_color = OS_ERROR;
406+
} else if (mp_obj_is_subclass_fast(exception_type, &mp_type_ValueError)) {
407+
status->exception_color = VALUE_ERROR;
408+
} else if (mp_obj_is_subclass_fast(exception_type, &mp_type_MpyError)) {
409+
status->exception_color = MPY_ERROR;
410+
} else {
411+
status->exception_color = OTHER_ERROR;
412+
}
410413
}
411414
#endif
412415
}

supervisor/shared/traceback.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2020 Christian Walther
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include "traceback.h"
28+
29+
supervisor_allocation* prev_traceback_allocation;

supervisor/shared/traceback.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2020 Christian Walther
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#ifndef MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H
28+
#define MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H
29+
30+
#include "supervisor/memory.h"
31+
32+
extern supervisor_allocation* prev_traceback_allocation;
33+
34+
#endif // MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H

supervisor/supervisor.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ SRC_SUPERVISOR = \
1212
supervisor/shared/stack.c \
1313
supervisor/shared/status_leds.c \
1414
supervisor/shared/tick.c \
15+
supervisor/shared/traceback.c \
1516
supervisor/shared/translate.c
1617

1718
ifndef $(NO_USB)

0 commit comments

Comments
 (0)