Skip to content

game-activity: tolerate the NULL pre-IME GameTextInput buffer - #252

Open
mudbungie wants to merge 1 commit into
rust-mobile:mainfrom
mudbungie:game-text-null-buffer
Open

game-activity: tolerate the NULL pre-IME GameTextInput buffer#252
mudbungie wants to merge 1 commit into
rust-mobile:mainfrom
mudbungie:game-text-null-buffer

Conversation

@mudbungie

Copy link
Copy Markdown

The defect

GameTextInput delivers its initial state — before the IME has attached —
with a NULL text_UTF8 pointer and text_length == 0.
map_input_state_to_text_event_callback feeds that pointer straight to
std::slice::from_raw_parts, whose contract requires a non-null pointer
even for a zero-length slice. That is undefined behavior in release builds,
and under debug_assertions the standard library's precondition check
fires: a non-unwinding panic inside an extern "C" callback, i.e. an
immediate abort.

Observed

Every debug build of a game-activity-backend app closes on its first
frame (observed with android-activity 0.6.1 + androidx games-activity
4.4.0, egui/winit app, current-generation Pixel, SDK 35). The tombstone
points at the stdlib's from_raw_parts precondition inside the callback.
Release builds compile the check out and happen to survive the len-0 read,
which makes the failure look like a debug-only mystery rather than what it
is — UB reached on every launch.

The fix

Treat the NULL buffer as the empty text it represents before constructing
the slice. One guard, no behavior change for any non-null buffer.

GameTextInput's initial state, delivered before the IME has attached,
carries a NULL text pointer with length 0. The conversion callback fed it
straight to slice::from_raw_parts, which requires a non-null pointer even
for an empty slice: undefined behavior in release builds, and under
debug_assertions the standard library's precondition check aborts the
process (a non-unwinding panic inside an extern "C" callback) on the
first frame of every debug build using the game-activity backend.

Treat the NULL buffer as the empty text it represents.
@rysb-dev

rysb-dev commented Sep 4, 2026

Copy link
Copy Markdown

Hit this independently this week... Bevy 0.19, android-activity 0.6.1 (game-activity backend), Android 15, debug build.

Different shape from the report above: I only poll text_input_state() while the name-entry dialog is open, so it didn't die on the first frame. The first poll happened when the dialog
opened, and it landed before the main thread had drained my first setState, so that's where it died. Same tombstone (from_raw_parts precondition in map_input_state_to_text_event_callback). Release never reproduced, which is how it sat around for a while.

Unrelated, but a trap that made this one a pain to find: my panic hook calls internal_data_path(), which re-takes the app mutex the callback is already holding, so instead of the abort we got a frozen app and nothing in logcat. Took a native stack to figure out what was going on. Nothing to do with this PR.

If anyone hits this before the PR lands: push an empty TextInputState once at startup. After setStateInner runs the pointer is never null again, so one early push closes the race.

+1, the guard looks right to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants