Skip to content

Commit 5d051df

Browse files
committed
Add supervisor.get_previous_traceback() function.
Useful for #1084.
1 parent 8889ac1 commit 5d051df

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
@@ -147,7 +147,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
147147
result->return_code = ret;
148148
if (ret != 0) {
149149
mp_obj_t return_value = (mp_obj_t)nlr.ret_val;
150-
result->exception_type = mp_obj_get_type(return_value);
150+
result->exception = return_value;
151151
result->exception_line = -1;
152152

153153
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
@@ -57,6 +57,7 @@
5757
#include "supervisor/shared/safe_mode.h"
5858
#include "supervisor/shared/stack.h"
5959
#include "supervisor/shared/status_leds.h"
60+
#include "supervisor/shared/traceback.h"
6061
#include "supervisor/shared/translate.h"
6162
#include "supervisor/shared/workflow.h"
6263
#include "supervisor/usb.h"
@@ -216,7 +217,41 @@ STATIC bool maybe_run_list(const char * const * filenames, pyexec_result_t* exec
216217
return true;
217218
}
218219

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

271306
result.return_code = 0;
272-
result.exception_type = NULL;
307+
result.exception = MP_OBJ_NULL;
273308
result.exception_line = 0;
274309

275310
bool skip_repl;
@@ -320,7 +355,7 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
320355

321356
// TODO: on deep sleep, make sure display is refreshed before sleeping (for e-ink).
322357

323-
cleanup_after_vm(heap);
358+
cleanup_after_vm(heap, result.exception);
324359

325360
// If a new next code file was set, that is a reason to keep it (obviously). Stuff this into
326361
// the options because it can be treated like any other reason-for-stickiness bit. The
@@ -554,7 +589,8 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
554589
start_mp(heap);
555590

556591
// TODO(tannewt): Re-add support for flashing boot error output.
557-
bool found_boot = maybe_run_list(boot_py_filenames, NULL);
592+
pyexec_result_t result = {0, MP_OBJ_NULL, 0};
593+
bool found_boot = maybe_run_list(boot_py_filenames, &result);
558594
(void) found_boot;
559595

560596
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
@@ -565,7 +601,7 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
565601
boot_output_file = NULL;
566602
#endif
567603

568-
cleanup_after_vm(heap);
604+
cleanup_after_vm(heap, result.exception);
569605
}
570606
}
571607

@@ -582,7 +618,7 @@ STATIC int run_repl(void) {
582618
} else {
583619
exit_code = pyexec_friendly_repl();
584620
}
585-
cleanup_after_vm(heap);
621+
cleanup_after_vm(heap, MP_OBJ_SENTINEL);
586622
autoreload_resume();
587623
return exit_code;
588624
}

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
#include "supervisor/shared/workflow.h"
3940

@@ -196,6 +197,34 @@ STATIC mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos
196197
}
197198
MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_next_code_file_obj, 0, supervisor_set_next_code_file);
198199

200+
//| def get_previous_traceback() -> Optional[str]:
201+
//| """If the last vm run ended with an exception (including the KeyboardInterrupt caused by
202+
//| CTRL-C), returns the traceback as a string.
203+
//| Otherwise, returns ``None``.
204+
//|
205+
//| An exception traceback is only preserved over a soft reload, a hard reset clears it.
206+
//|
207+
//| Only code (main or boot) runs are considered, not REPL runs."""
208+
//| ...
209+
//|
210+
STATIC mp_obj_t supervisor_get_previous_traceback(void) {
211+
//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?
212+
if (prev_traceback_allocation) {
213+
size_t len = strlen((const char*)prev_traceback_allocation->ptr);
214+
if (len > 0) {
215+
mp_obj_str_t *o = m_new_obj(mp_obj_str_t);
216+
o->base.type = &mp_type_str;
217+
o->len = len;
218+
//TODO is it a good assumption that callers probably aren't going to compare this string, so skip computing the hash?
219+
o->hash = 0;
220+
o->data = (const byte*)prev_traceback_allocation->ptr;
221+
return MP_OBJ_FROM_PTR(o);
222+
}
223+
}
224+
return mp_const_none;
225+
}
226+
MP_DEFINE_CONST_FUN_OBJ_0(supervisor_get_previous_traceback_obj, supervisor_get_previous_traceback);
227+
199228
STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
200229
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_supervisor) },
201230
{ MP_ROM_QSTR(MP_QSTR_enable_autoreload), MP_ROM_PTR(&supervisor_enable_autoreload_obj) },
@@ -206,6 +235,7 @@ STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
206235
{ MP_ROM_QSTR(MP_QSTR_RunReason), MP_ROM_PTR(&supervisor_run_reason_type) },
207236
{ MP_ROM_QSTR(MP_QSTR_set_next_stack_limit), MP_ROM_PTR(&supervisor_set_next_stack_limit_obj) },
208237
{ MP_ROM_QSTR(MP_QSTR_set_next_code_file), MP_ROM_PTR(&supervisor_set_next_code_file_obj) },
238+
{ MP_ROM_QSTR(MP_QSTR_get_previous_traceback), MP_ROM_PTR(&supervisor_get_previous_traceback_obj) },
209239
};
210240

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