From 5e37647023463cec4331dc48dc360ba60b22c888 Mon Sep 17 00:00:00 2001 From: Bitflash Date: Mon, 3 Aug 2026 10:33:07 -0300 Subject: gui: read the Finish condition once, before the button that clears it (#118) Creating a recovery phrase from the window aborted with an ImGui assertion on the success path: Expression: (g.DisabledStackSize > 0) && "Calling EndDisabled() too many times!" File: imgui/imgui.cpp Line: 8435 The Finish button is wrapped in BeginDisabled/EndDisabled guarded by !g_phraseWrittenDown, and its own click handler sets g_phraseWrittenDown to false. So the pair was opened while the checkbox was ticked and closed after the handler had unticked it: BeginDisabled was never called, EndDisabled was, and ImGui asserted on the unmatched close. It fires on the only path that installs a seed -- tick the box, press Finish -- so every phrase created from the window has hit it since the feature shipped in 1.2.12. Three releases carried it because nothing tests the window. Reproduced on the published 1.2.14 binary and confirmed fixed on this build, driving the real window: Wallet Safety -> Create recovery phrase -> tick -> Finish. The old build raises the assertion; the fixed build closes the dialog, reports "This wallet has a recovery phrase", and the address changes to a derived one. The seed itself was already written before the assertion, so the wallet was never left half-installed -- and ImGui's IM_ASSERT_USER_ERROR_RET returns before touching its state, so answering Ignore was safe. Abort was not: it kills the process with no DBFlush. The restore dialog next door already does this correctly, copying the condition into a local first. This makes the create dialog match. --- src/gui.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src/gui.cpp') diff --git a/src/gui.cpp b/src/gui.cpp index e800244..e3a8499 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1005,7 +1005,15 @@ static void DrawCreatePhraseDialog() ImGui::Checkbox("I have written these words down", &g_phraseWrittenDown); ImGui::Spacing(); - if (!g_phraseWrittenDown) + // Read the condition once, before the button that changes it. + // + // Pressing Finish clears g_phraseWrittenDown, so testing it again after + // the click asked BeginDisabled and EndDisabled two different questions: + // the pair was opened while the checkbox was ticked and closed after the + // handler had unticked it. ImGui asserts on the unmatched EndDisabled -- + // on the success path, so every phrase created from the window hit it. + const bool fFinishDisabled = !g_phraseWrittenDown; + if (fFinishDisabled) ImGui::BeginDisabled(); if (ImGui::Button("Finish", ImVec2(120.0f, 0.0f))) { @@ -1024,7 +1032,7 @@ static void DrawCreatePhraseDialog() g_showCreatePhrase = false; g_needRefresh = true; } - if (!g_phraseWrittenDown) + if (fFinishDisabled) ImGui::EndDisabled(); ImGui::SameLine(); -- cgit v1.2.3