From a7185320716c786e880838fdfaaef236a59b080e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20A=2E=20Alto=C3=A9=20Falqueto?= Date: Wed, 8 Jul 2026 16:41:11 -0300 Subject: [PATCH] Fix use-after-free/double-free crash when leaving an empty, newly-added lyrics syllable TextBase::endEdit() rolls back the "add element" command when a newly created text (e.g. a Lyrics syllable created while navigating with Space/Shift+Space) is left empty. Since 67b5fa12a4 ("Don't leak items owned by UndoableCommand deleted in `unwind`"), that rollback's unwind() now calls AddElement::cleanup(false), which synchronously deletes the element being edited (`this`). endEdit() kept using `this`/`ted` right after the rollback (setting ted->deleteText = true, calling isLyrics(), etc.), and that deleteText flag scheduled a second, redundant deletion via TextEditData::~TextEditData() -> deleteLater() -> deletePostponed(). The result was a use-after-free followed by a double free, crashing in mu::engraving::deletePostponed (cmd.cpp) - reproducible by creating an empty lyrics syllable and pressing Shift+Space to navigate away from it without typing anything. Detect up front when the rollback is about to delete `this` (i.e. the "add element" command for this element is part of the transaction being rolled back) and return immediately afterwards instead of touching the now-dangling object. Co-Authored-By: Claude Sonnet 5 --- src/engraving/editing/textedit.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/engraving/editing/textedit.cpp b/src/engraving/editing/textedit.cpp index c8acdafa6a35d..080f5774faa8f 100644 --- a/src/engraving/editing/textedit.cpp +++ b/src/engraving/editing/textedit.cpp @@ -161,12 +161,16 @@ void TextBase::endEdit(EditData& ed) } const bool newlyAdded = ted->oldXmlText.isEmpty(); + bool rollbackWillDeleteThis = false; if (newlyAdded) { const UndoableTransaction* transaction = textWasEdited ? undo->prev() : undo->last(); if (transaction && transaction->hasCommandsMatchingFilter(UndoableCommandFilter::AddElement, this)) { // We have just added this element to a score. // Combine undo records of text creation with text editing. undo->mergeTransactions(ted->startUndoIdx - 1); + // The merged transaction now contains the "add element" command for `this`, + // so rolling it back below will synchronously delete `this` (see AddElement::cleanup()). + rollbackWillDeleteThis = true; } } @@ -184,6 +188,14 @@ void TextBase::endEdit(EditData& ed) undo->reopen(); if (newlyAdded) { score()->endCmd(true); // rollback the "add element" command + + if (rollbackWillDeleteThis) { + // `this` (and therefore `ted`, which points to it) was just deleted by the + // rollback above. Any further use of them would be a use-after-free, so bail + // out immediately instead of falling through to the code below. + return; + } + ted->deleteText = true; } else { score()->undoRemoveElement(this);