Fix shutdown use-after-free of UiState via the ImGui settings handler

register_ui_settings installs an ImGui settings handler whose UserData points at
the run()-local UiState. ~ImGuiLayer calls DestroyContext, which flushes the .ini
through that handler (ui_settings_write_all dereferences UserData). But UiState
was declared after ImGuiLayer in run(), so it (and the panels between them) were
destroyed first -- the shutdown save read freed/clobbered stack every clean exit
(UB; could corrupt coop_layout.ini / the persisted debug-details flag).

Declare UiState before ImGuiLayer so it outlives the context and is destroyed
last. Not unit-testable (shutdown lifetime ordering); fixed by inspection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 01:05:47 +02:00
parent 3f1ce0c78a
commit 43d093405e
2 changed files with 5 additions and 4 deletions

View File

@@ -323,6 +323,10 @@ int run()
return 1;
}
// `ui` must outlive `imgui`: register_ui_settings (below) installs an ImGui settings handler that
// holds &ui, and ~ImGuiLayer's DestroyContext flushes the .ini through that handler on shutdown.
// Declaring ui first means it's destroyed AFTER imgui, so that final save never reads freed state.
coop::UiState ui;
coop::ImGuiLayer imgui;
if (!imgui.init(window.hwnd(), window.device(), window.context()))
{
@@ -364,9 +368,9 @@ int run()
double overlay_hidden_at = 0.0;
double last_shot_at = -10.0; // when the last F10 screenshot was saved (for the toast)
std::string last_shot_name;
coop::UiState ui;
// Persist the "Debug details" verbosity in the .ini. Register before the first
// begin_frame() below, which is when ImGui loads the .ini and replays our handler.
// (ui is declared earlier, before imgui, so it outlives the context -- see the note there.)
coop::register_ui_settings(ui);
coop::FrameStats stats;