Skip to content

Runtime: reference counts from callback reads and __cast_instance__; buffers stay exported for the LVGL call - #35

Merged
bdbarnett merged 1 commit into
mainfrom
ostinato/lvgl-runtime-fixes
Oct 15, 2026
Merged

bdbarnett merged 1 commit into
mainfrom
ostinato/lvgl-runtime-fixes

Conversation

@bdbarnett

Copy link
Copy Markdown
Contributor

Three reference bugs in src/lvpy_runtime.c, on every Python build.

Reading a callback back from a struct (drv.open_cb, font.get_glyph_dsc, any callback field) returned the callback dict's borrowed reference as if it were new. Each read dropped the callback's reference count by one, so a few reads freed a callback that LVGL still calls. mp_lv_funcptr now returns a reference of its own, and None when the dict has no entry (it returned NULL with no exception set).

A buffer passed where LVGL takes a void * (obj.send_event(code, buf), a user_data, a dst) was released before LVGL used the pointer. Python code run during the call, such as an event callback, could then resize a bytearray and leave LVGL reading freed memory. The buffer now stays exported until the outermost LVGL call returns, so resizing it during the call raises BufferError and works again afterwards. A non-contiguous buffer is refused with BufferError rather than passed through.

__cast_instance__ returned self without a reference, and a struct that owned its memory freed it twice after being cast onto another struct's. It now lets go of its own memory and returns a new reference.

tests/test_runtime_references.py checks all three in a subprocess under MALLOC_PERTURB_. On the code before this change it fails three ways: the callback's count drops by 4 over 4 reads, the bytearray resizes mid-call, and __cast_instance__ aborts with a double free. With the fix reverted by hand on this branch (a planted fault, since undone), the same three tests fail; with the fix, the full suite passes (28 tests).

…ffers stay exported for the LVGL call

- Reading a callback back from a struct field (drv.open_cb) returned the
  callback dict's borrowed reference as a new one, so every read dropped
  the callback's reference count by one until it was freed.
- A buffer passed as a void * (obj.send_event(code, buf)) was released
  before LVGL used the pointer, so Python code run during the call could
  resize it under LVGL. The export now lasts until the outermost LVGL call
  returns.
- __cast_instance__ returned self without a reference, and a struct that
  owned its memory freed it twice once cast onto another struct's.
@bdbarnett
bdbarnett merged commit 40179d9 into main Oct 15, 2026
1 check passed
@bdbarnett
bdbarnett deleted the ostinato/lvgl-runtime-fixes branch October 15, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant