Skip to content

Add supervisor.get_previous_traceback() #3696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/utils/pyexec.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
result->return_code = ret;
if (ret != 0) {
mp_obj_t return_value = (mp_obj_t)nlr.ret_val;
result->exception_type = mp_obj_get_type(return_value);
result->exception = return_value;
result->exception_line = -1;

if (mp_obj_is_exception_instance(return_value)) {
Expand Down
2 changes: 1 addition & 1 deletion lib/utils/pyexec.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ typedef enum {

typedef struct {
int return_code;
const mp_obj_type_t * exception_type;
mp_obj_t exception;
int exception_line;
} pyexec_result_t;

Expand Down
6 changes: 5 additions & 1 deletion locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ msgstr ""
msgid " is of type %q\n"
msgstr ""

#: main.c
msgid " not found.\n"
msgstr ""

#: main.c
msgid " output:\n"
msgstr ""
Expand Down Expand Up @@ -2213,7 +2217,7 @@ msgstr ""
msgid "argsort is not implemented for flattened arrays"
msgstr ""

#: py/runtime.c
#: py/runtime.c shared-bindings/supervisor/__init__.c
msgid "argument has wrong type"
msgstr ""

Expand Down
130 changes: 112 additions & 18 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include "supervisor/shared/safe_mode.h"
#include "supervisor/shared/stack.h"
#include "supervisor/shared/status_leds.h"
#include "supervisor/shared/traceback.h"
#include "supervisor/shared/translate.h"
#include "supervisor/shared/workflow.h"
#include "supervisor/usb.h"
Expand Down Expand Up @@ -216,7 +217,41 @@ STATIC bool maybe_run_list(const char * const * filenames, pyexec_result_t* exec
return true;
}

STATIC void cleanup_after_vm(supervisor_allocation* heap) {
STATIC void count_strn(void *data, const char *str, size_t len) {
*(size_t*)data += len;
}

STATIC void cleanup_after_vm(supervisor_allocation* heap, mp_obj_t exception) {
// Get the traceback of any exception from this run off the heap.
// MP_OBJ_SENTINEL means "this run does not contribute to traceback storage, don't touch it"
// MP_OBJ_NULL (=0) means "this run completed successfully, clear any stored traceback"
if (exception != MP_OBJ_SENTINEL) {
free_memory(prev_traceback_allocation);
// ReloadException is exempt from traceback printing in pyexec_file(), so treat it as "no
// traceback" here too.
if (exception && exception != MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception))) {
size_t traceback_len = 0;
mp_print_t print_count = {&traceback_len, count_strn};
mp_obj_print_exception(&print_count, exception);
prev_traceback_allocation = allocate_memory(align32_size(traceback_len + 1), false, true);
// Empirically, this never fails in practice - even when the heap is totally filled up
// with single-block-sized objects referenced by a root pointer, exiting the VM frees
// up several hundred bytes, sufficient for the traceback (which tends to be shortened
// because there wasn't memory for the full one). There may be convoluted ways of
// making it fail, but at this point I believe they are not worth spending code on.
if (prev_traceback_allocation != NULL) {
vstr_t vstr;
vstr_init_fixed_buf(&vstr, traceback_len, (char*)prev_traceback_allocation->ptr);
mp_print_t print = {&vstr, (mp_print_strn_t)vstr_add_strn};
mp_obj_print_exception(&print, exception);
((char*)prev_traceback_allocation->ptr)[traceback_len] = '\0';
}
}
else {
prev_traceback_allocation = NULL;
}
}

// Reset port-independent devices, like CIRCUITPY_BLEIO_HCI.
reset_devices();
// Turn off the display and flush the filesystem before the heap disappears.
Expand Down Expand Up @@ -269,10 +304,14 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
pyexec_result_t result;

result.return_code = 0;
result.exception_type = NULL;
result.exception = MP_OBJ_NULL;
result.exception_line = 0;

bool skip_repl;
bool found_main = false;
uint8_t next_code_options = 0;
// Collects stickiness bits that apply in the current situation.
uint8_t next_code_stickiness_situation = SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;

if (safe_mode == NO_SAFE_MODE) {
new_status_color(MAIN_RUNNING);
Expand All @@ -290,22 +329,62 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
supervisor_allocation* heap = allocate_remaining_memory();
start_mp(heap);

found_main = maybe_run_list(supported_filenames, &result);
#if CIRCUITPY_FULL_BUILD
if (!found_main){
found_main = maybe_run_list(double_extension_filenames, &result);
if (found_main) {
serial_write_compressed(translate("WARNING: Your code filename has two extensions\n"));
if (next_code_allocation) {
((next_code_info_t*)next_code_allocation->ptr)->options &= ~SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;
next_code_options = ((next_code_info_t*)next_code_allocation->ptr)->options;
if (((next_code_info_t*)next_code_allocation->ptr)->filename[0] != '\0') {
const char* next_list[] = {((next_code_info_t*)next_code_allocation->ptr)->filename, ""};
found_main = maybe_run_list(next_list, &result);
if (!found_main) {
serial_write(((next_code_info_t*)next_code_allocation->ptr)->filename);
serial_write_compressed(translate(" not found.\n"));
}
}
}
#endif
if (!found_main) {
found_main = maybe_run_list(supported_filenames, &result);
#if CIRCUITPY_FULL_BUILD
if (!found_main){
found_main = maybe_run_list(double_extension_filenames, &result);
if (found_main) {
serial_write_compressed(translate("WARNING: Your code filename has two extensions\n"));
}
}
#endif
}

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

cleanup_after_vm(heap);
cleanup_after_vm(heap, result.exception);

// If a new next code file was set, that is a reason to keep it (obviously). Stuff this into
// the options because it can be treated like any other reason-for-stickiness bit. The
// source is different though: it comes from the options that will apply to the next run,
// while the rest of next_code_options is what applied to this run.
if (next_code_allocation != NULL && (((next_code_info_t*)next_code_allocation->ptr)->options & SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET)) {
next_code_options |= SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;
}

if (reload_requested) {
next_code_stickiness_situation |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_RELOAD;
}
else if (result.return_code == 0) { //TODO mask out PYEXEC_DEEP_SLEEP?
next_code_stickiness_situation |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_SUCCESS;
if (next_code_options & SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_SUCCESS) {
skip_repl = true;
goto done;
}
}
else {
next_code_stickiness_situation |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_ERROR;
if (next_code_options & SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_ERROR) {
skip_repl = true;
goto done;
}
}
if (result.return_code & PYEXEC_FORCED_EXIT) {
return reload_requested;
skip_repl = reload_requested;
goto done;
}

if (reload_requested && result.return_code == PYEXEC_EXCEPTION) {
Expand Down Expand Up @@ -333,9 +412,16 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
board_init();
}
#endif
next_code_stickiness_situation |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_RELOAD;
// Should the STICKY_ON_SUCCESS and STICKY_ON_ERROR bits be cleared in
// next_code_stickiness_situation? I can see arguments either way, but I'm deciding
// "no" for now, mainly because it's a bit less code. At this point, we have both a
// success or error and a reload, so let's have both of the respective options take
// effect (in OR combination).
supervisor_set_run_reason(RUN_REASON_AUTO_RELOAD);
reload_requested = false;
return true;
skip_repl = true;
goto done;
}

if (serial_connected() && serial_bytes_available()) {
Expand All @@ -345,11 +431,11 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
}
#endif
// Skip REPL if reload was requested.
bool ctrl_d = serial_read() == CHAR_CTRL_D;
if (ctrl_d) {
skip_repl = serial_read() == CHAR_CTRL_D;
if (skip_repl) {
supervisor_set_run_reason(RUN_REASON_REPL_RELOAD);
}
return ctrl_d;
goto done;
}

// Check for a deep sleep alarm and restart the VM. This can happen if
Expand Down Expand Up @@ -428,6 +514,13 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
port_idle_until_interrupt();
}
}

done:
if ((next_code_options & next_code_stickiness_situation) == 0) {
free_memory(next_code_allocation);
next_code_allocation = NULL;
}
return skip_repl;
}

FIL* boot_output_file;
Expand Down Expand Up @@ -496,7 +589,8 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
start_mp(heap);

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

#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
Expand All @@ -507,7 +601,7 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
boot_output_file = NULL;
#endif

cleanup_after_vm(heap);
cleanup_after_vm(heap, result.exception);
}
}

Expand All @@ -524,7 +618,7 @@ STATIC int run_repl(void) {
} else {
exit_code = pyexec_friendly_repl();
}
cleanup_after_vm(heap);
cleanup_after_vm(heap, MP_OBJ_SENTINEL);
autoreload_resume();
return exit_code;
}
Expand Down
116 changes: 115 additions & 1 deletion shared-bindings/supervisor/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <string.h>

#include "py/obj.h"
#include "py/runtime.h"
#include "py/reload.h"
#include "py/objstr.h"

#include "lib/utils/interrupt_char.h"
#include "supervisor/shared/autoreload.h"
#include "supervisor/shared/rgb_led_status.h"
#include "supervisor/shared/stack.h"
#include "supervisor/shared/traceback.h"
#include "supervisor/shared/translate.h"
#include "supervisor/shared/workflow.h"

Expand Down Expand Up @@ -112,6 +116,115 @@ STATIC mp_obj_t supervisor_set_next_stack_limit(mp_obj_t size_obj) {
}
MP_DEFINE_CONST_FUN_OBJ_1(supervisor_set_next_stack_limit_obj, supervisor_set_next_stack_limit);

//| def set_next_code_file(filename: Optional[str], *, reload_on_success : bool = False, reload_on_error: bool = False, sticky_on_success: bool = False, sticky_on_error: bool = False, sticky_on_reload: bool = False) -> None:
//| """Set what file to run on the next vm run.
//|
//| When not ``None``, the given ``filename`` is inserted at the front of the usual ['code.py',
//| 'main.py'] search sequence.
//|
//| The optional keyword arguments specify what happens after the specified file has run:
//|
//| ``sticky_on_…`` determine whether the newly set filename and options stay in effect: If
//| True, further runs will continue to run that file (unless it says otherwise by calling
//| ``set_next_code_filename()`` itself). If False, the settings will only affect one run and
//| revert to the standard code.py/main.py afterwards.
//|
//| ``reload_on_…`` determine how to continue: If False, wait in the usual "Code done running.
//| Waiting for reload. / Press any key to enter the REPL. Use CTRL-D to reload." state. If
//| True, reload immediately as if CTRL-D was pressed.
//|
//| ``…_on_success`` take effect when the program runs to completion or calls ``sys.exit()``.
//|
//| ``…_on_error`` take effect when the program exits with an exception, including the
//| KeyboardInterrupt caused by CTRL-C.
//|
//| ``…_on_reload`` take effect when the program is interrupted by files being written to the USB
//| drive (auto-reload) or when it calls ``supervisor.reload()``.
//|
//| These settings are stored in RAM, not in persistent memory, and will therefore only affect
//| soft reloads. Powering off or resetting the device will always revert to standard settings.
//|
//| When called multiple times in the same run, only the last call takes effect, replacing any
//| settings made by previous ones. This is the main use of passing ``None`` as a filename: to
//| reset to the standard search sequence."""
//| ...
//|
STATIC mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_filename, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
{ MP_QSTR_reload_on_success, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
{ MP_QSTR_reload_on_error, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
{ MP_QSTR_sticky_on_success, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
{ MP_QSTR_sticky_on_error, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
{ MP_QSTR_sticky_on_reload, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
};
struct {
mp_arg_val_t filename;
mp_arg_val_t reload_on_success;
mp_arg_val_t reload_on_error;
mp_arg_val_t sticky_on_success;
mp_arg_val_t sticky_on_error;
mp_arg_val_t sticky_on_reload;
} args;
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t*)&args);
if (!MP_OBJ_IS_STR_OR_BYTES(args.filename.u_obj) && args.filename.u_obj != mp_const_none) {
mp_raise_TypeError(translate("argument has wrong type"));
}
if (args.filename.u_obj == mp_const_none) args.filename.u_obj = mp_const_empty_bytes;
uint8_t options = 0;
if (args.reload_on_success.u_bool) options |= SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_SUCCESS;
if (args.reload_on_error.u_bool) options |= SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_ERROR;
if (args.sticky_on_success.u_bool) options |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_SUCCESS;
if (args.sticky_on_error.u_bool) options |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_ERROR;
if (args.sticky_on_reload.u_bool) options |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_RELOAD;
size_t len;
const char* filename = mp_obj_str_get_data(args.filename.u_obj, &len);
free_memory(next_code_allocation);
if (options != 0 || len != 0) {
next_code_allocation = allocate_memory(align32_size(sizeof(next_code_info_t) + len + 1), false, true);
if (next_code_allocation == NULL) {
m_malloc_fail(sizeof(next_code_info_t) + len + 1);
}
next_code_info_t* next_code = (next_code_info_t*)next_code_allocation->ptr;
next_code->options = options | SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;
memcpy(&next_code->filename, filename, len);
next_code->filename[len] = '\0';
}
else {
next_code_allocation = NULL;
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_next_code_file_obj, 0, supervisor_set_next_code_file);

//| def get_previous_traceback() -> Optional[str]:
//| """If the last vm run ended with an exception (including the KeyboardInterrupt caused by
//| CTRL-C), returns the traceback as a string.
//| Otherwise, returns ``None``.
//|
//| An exception traceback is only preserved over a soft reload, a hard reset clears it.
//|
//| Only code (main or boot) runs are considered, not REPL runs."""
//| ...
//|
STATIC mp_obj_t supervisor_get_previous_traceback(void) {
//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?
if (prev_traceback_allocation) {
size_t len = strlen((const char*)prev_traceback_allocation->ptr);
if (len > 0) {
mp_obj_str_t *o = m_new_obj(mp_obj_str_t);
o->base.type = &mp_type_str;
o->len = len;
//TODO is it a good assumption that callers probably aren't going to compare this string, so skip computing the hash?
o->hash = 0;
o->data = (const byte*)prev_traceback_allocation->ptr;
return MP_OBJ_FROM_PTR(o);
}
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_0(supervisor_get_previous_traceback_obj, supervisor_get_previous_traceback);

STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_supervisor) },
{ MP_ROM_QSTR(MP_QSTR_enable_autoreload), MP_ROM_PTR(&supervisor_enable_autoreload_obj) },
Expand All @@ -121,7 +234,8 @@ STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_reload), MP_ROM_PTR(&supervisor_reload_obj) },
{ MP_ROM_QSTR(MP_QSTR_RunReason), MP_ROM_PTR(&supervisor_run_reason_type) },
{ MP_ROM_QSTR(MP_QSTR_set_next_stack_limit), MP_ROM_PTR(&supervisor_set_next_stack_limit_obj) },

{ MP_ROM_QSTR(MP_QSTR_set_next_code_file), MP_ROM_PTR(&supervisor_set_next_code_file_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_previous_traceback), MP_ROM_PTR(&supervisor_get_previous_traceback_obj) },
};

STATIC MP_DEFINE_CONST_DICT(supervisor_module_globals, supervisor_module_globals_table);
Expand Down
Loading