Skip to content
Merged
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
21 changes: 15 additions & 6 deletions lv_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,15 +305,24 @@
#else
/* On MicroPython a spinning assert locks the scheduler out: USB serial and
* WebREPL go silent until a power cycle. The MicroPython port's handler
* raises RuntimeError naming the function, file and line instead, which
* the nearest Python frame or LVGL callback reports, and the board stays
* reachable. It is weak here, so a build without it still links and
* halts as before. */
* raises RuntimeError naming the file and line instead, which the nearest
* Python frame or LVGL callback reports, and the board stays reachable.
*
* LVGL has over a thousand assert sites, so each one is kept to a call
* with two arguments: the file's base name and the line, which are
* enough to find the assert. The handler doesn't return, so nothing
* follows the call. Passing the full path and the function name as well
* cost about 50 KB of flash on an ESP32-S3, and with code run from PSRAM
* (XIP) it took 128 KB of PSRAM from the heap. */
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
#if !defined(PYCPARSER) && !defined(__ASSEMBLY__)
__attribute__((weak)) void lv_micropython_assert_failed(const char *file, int line, const char *func);
__attribute__((noreturn)) void lv_micropython_assert_failed(const char *file, int line);
#endif
#ifdef __FILE_NAME__
#define LV_ASSERT_HANDLER lv_micropython_assert_failed(__FILE_NAME__, __LINE__);
#else
#define LV_ASSERT_HANDLER lv_micropython_assert_failed(__FILE__, __LINE__);
#endif
#define LV_ASSERT_HANDLER { if (lv_micropython_assert_failed) { lv_micropython_assert_failed(__FILE__, __LINE__, __func__); } while(1); }
#endif

/*-------------
Expand Down
15 changes: 8 additions & 7 deletions ports/micropython/src/lv_assert_micropython.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
* to spin forever, and on a board that locks the MicroPython scheduler out:
* USB serial and WebREPL go silent and only a power cycle brings it back.
*
* Instead this raises RuntimeError("LVGL assert failed in <function>
* (<file>:<line>)"). LVGL's log is off in MicroPython builds, so the
* function and line are how to find the assertion's message in LVGL's source.
* Instead this raises RuntimeError("LVGL assert failed at <file>:<line>").
* LVGL's log is off in MicroPython builds, so the file and line are how to
* find the assertion's message in LVGL's source.
*
* Where the exception lands decides what LVGL state needs repair:
*
Expand All @@ -33,7 +33,7 @@
#include "lvgl/src/display/lv_display_private.h"


void lv_micropython_assert_failed(const char *file, int line, const char *func);
MP_NORETURN void lv_micropython_assert_failed(const char *file, int line);

// From the generated binding: how many Python callbacks LVGL is inside.
int mp_lv_nesting_depth(void);
Expand All @@ -48,8 +48,9 @@ static void lv_micropython_end_pass(void) {
}
}

void lv_micropython_assert_failed(const char *file, int line, const char *func) {
// Keep only the file's own name. A loop, not strrchr: some ports'
void lv_micropython_assert_failed(const char *file, int line) {
// Keep only the file's own name, for a compiler without __FILE_NAME__
// (lv_conf.h passes __FILE__ there). A loop, not strrchr: some ports'
// C libraries (stm32's) don't provide it.
const char *base = file;
for (const char *p = file; *p; p++) {
Expand All @@ -60,5 +61,5 @@ void lv_micropython_assert_failed(const char *file, int line, const char *func)
if (mp_lv_nesting_depth() == 0) {
lv_micropython_end_pass();
}
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("LVGL assert failed in %s (%s:%d)"), func, base, line);
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("LVGL assert failed at %s:%d"), base, line);
}
27 changes: 27 additions & 0 deletions tools/test_lvgl_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,32 @@ def test_object_lifetime_and_pointer_validation(lv):
print("OK: deleted-object and pointer validation")


def test_assert_raises(lv, disp):
# On MicroPython a failed LVGL assert raises RuntimeError naming the
# source file and line, and LVGL keeps working. CircuitPython reboots
# into safe mode and CPython halts, so only MicroPython can check it.
if _runtime_target() != "micropython":
return
try:
disp.set_tile_cnt(256) # LVGL asserts tile_cnt < 256
except RuntimeError as exc:
msg = str(exc)
else:
_fail("set_tile_cnt(256) returned instead of raising")
prefix = "LVGL assert failed at lv_display.c:"
if not msg.startswith(prefix) or not msg[len(prefix):].isdigit():
_fail("unexpected assert message: {}".format(msg))
if disp.get_tile_cnt() >= 256:
_fail("the asserting call changed the display")
label = lv.label(lv.screen_active())
label.set_text("after assert")
lv.refr_now(disp)
if label.get_text() != "after assert":
_fail("LVGL stopped working after an assert")
label.delete()
print("OK: an LVGL assert raises RuntimeError at file:line and LVGL carries on")


def test_target_exceptions(lv):
# LV_USE_TJPGD is 0 on every target: the JPEG decoder is jpegio's,
# registered via lv_image_decoder_create by displayif, the CircuitPython
Expand Down Expand Up @@ -773,6 +799,7 @@ def main():
test_refr_now(lv, disp)
test_pointer_buffer_dereference(lv, disp)
test_widget(lv)
test_assert_raises(lv, disp)
test_struct_fields_and_arrays(lv)
test_line_points_list_and_buffer(lv)
test_struct_array_fields(lv)
Expand Down
Loading