[graphics/krita] libs/flake: Do not create a new path shape in KoMultiPathPointMergeCommand::redo() call

Dmitry Kazakov <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 9ab35881aebecc78971292849d86558a79a39999 by Dmitry Kazakov, on behalf of Elena Sagalaeva.
Committed on 27/07/2026 at 11:50.
Pushed by dkazakov into branch 'master'.

Do not create a new path shape in KoMultiPathPointMergeCommand::redo() call

The subsequent operations don't know about the new shape and continue to
use the old one, which causes crashes.

BUG:523070

M  +26   -20   libs/flake/commands/KoMultiPathPointMergeCommand.cpp
M  +63   -0    libs/flake/tests/TestPointMergeCommand.cpp
M  +3    -0    libs/flake/tests/TestPointMergeCommand.h

https://invent.kde.org/graphics/krita/-/commit/9ab35881aebecc78971292849d86558a79a39999

diff --git a/libs/flake/commands/KoMultiPathPointMergeCommand.cpp b/libs/flake/commands/KoMultiPathPointMergeCommand.cpp
index 86b15ce7367..f0c18f6f929 100644
--- a/libs/flake/commands/KoMultiPathPointMergeCommand.cpp
+++ b/libs/flake/commands/KoMultiPathPointMergeCommand.cpp
@@ -51,30 +51,38 @@ KUndo2Command *KoMultiPathPointMergeCommand::createMergeCommand(const KoPathPoin
 
 void KoMultiPathPointMergeCommand::redo()
 {
-    KoShape *mergedShape = 0;
-
-    if (m_d->pointData1.pathShape != m_d->pointData2.pathShape) {
-        KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->controller);
-
-        QList<KoPathShape*> shapes = {m_d->pointData1.pathShape, m_d->pointData2.pathShape};
-        m_d->combineCommand.reset(new KoPathCombineCommand(m_d->controller, shapes));
-        m_d->combineCommand->redo();
-
-        KoPathPointData newPD1 = m_d->combineCommand->originalToCombined(m_d->pointData1);
-        KoPathPointData newPD2 = m_d->combineCommand->originalToCombined(m_d->pointData2);
+    // Create shapes during the first call only.
+    if (!m_d->mergeCommand && !m_d->combineCommand)
+    {
+        if (m_d->pointData1.pathShape != m_d->pointData2.pathShape) {
+            KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->controller);
 
-        m_d->mergeCommand.reset(createMergeCommand(newPD1, newPD2));
-        m_d->mergeCommand->redo();
+            QList<KoPathShape*> shapes = { m_d->pointData1.pathShape, m_d->pointData2.pathShape };
+            m_d->combineCommand.reset(new KoPathCombineCommand(m_d->controller, shapes));
+            
+            KoPathPointData newPD1 = m_d->combineCommand->originalToCombined(m_d->pointData1);
+            KoPathPointData newPD2 = m_d->combineCommand->originalToCombined(m_d->pointData2);
 
-        mergedShape = m_d->combineCommand->combinedPath();
+            m_d->mergeCommand.reset(createMergeCommand(newPD1, newPD2));
+        }
+        else {
+            m_d->mergeCommand.reset(createMergeCommand(m_d->pointData1, m_d->pointData2));
+        }
+    }
 
-    } else {
-        m_d->mergeCommand.reset(createMergeCommand(m_d->pointData1, m_d->pointData2));
+    KoShape *mergedShape = 0;
+    if (m_d->mergeCommand)
+    {
         m_d->mergeCommand->redo();
-
         mergedShape = m_d->pointData1.pathShape;
     }
 
+    if (m_d->combineCommand)
+    {
+        m_d->combineCommand->redo();
+        mergedShape = m_d->combineCommand->combinedPath();
+    }
+ 
     if (m_d->selection) {
         m_d->selection->select(mergedShape);
     }
@@ -92,13 +100,11 @@ void KoMultiPathPointMergeCommand::undo()
     KUndo2Command::undo();
 
     if (m_d->mergeCommand) {
-            m_d->mergeCommand->undo();
-            m_d->mergeCommand.reset();
+        m_d->mergeCommand->undo();
     }
 
     if (m_d->combineCommand) {
         m_d->combineCommand->undo();
-        m_d->combineCommand.reset();
     }
 
     if (m_d->selection) {
diff --git a/libs/flake/tests/TestPointMergeCommand.cpp b/libs/flake/tests/TestPointMergeCommand.cpp
index 38e6481af3f..4eb0f324e0a 100644
--- a/libs/flake/tests/TestPointMergeCommand.cpp
+++ b/libs/flake/tests/TestPointMergeCommand.cpp
@@ -554,6 +554,69 @@ void TestPointMergeCommand::testMultipathJoinShapesSingleShapeStartToEnd()
              }, true);
 }
 
+#include "KoShapeDeleteCommand.h"
 
+void TestPointMergeCommand::undoRedo()
+{
+    KoPathShape path1, path2;
+    path1.moveTo(QPointF(40, 0));
+    path1.lineTo(QPointF(60, 0));
+
+    path2.lineTo(QPointF(60, 30));
+    path2.lineTo(QPointF(0, 30));
+    
+    KoPathPointIndex index1(0, 0);
+    KoPathPointIndex index2(0, 0);
+
+    MockShapeController mockController;
+    MockCanvas canvas(&mockController);
+    MockContainer container1(nullptr);
+
+    path1.setParent(&container1);
+    path2.setParent(&container1);
+
+    KoPathPointData pd1(&path1, index1);
+    KoPathPointData pd2(&path2, index2);
+
+    // Test merge
+    {
+        KoMultiPathPointMergeCommand cmd(pd1, pd2, &mockController, canvas.shapeManager()->selection());
+        cmd.redo();
+
+        KoShapeDeleteCommand  deleteCommand(&mockController, cmd.testingCombinedPath());
+        deleteCommand.redo();
+
+        deleteCommand.undo();
+        cmd.undo();
+
+        cmd.redo();
+        deleteCommand.redo();
+
+        deleteCommand.undo();
+        cmd.undo();
+    }
+
+
+    // Test join
+    {
+        KoMultiPathPointJoinCommand cmd(pd1, pd2, &mockController, canvas.shapeManager()->selection());
+        cmd.redo();
+
+        KoShapeDeleteCommand  deleteCommand(&mockController, cmd.testingCombinedPath());
+        deleteCommand.redo();
+
+        deleteCommand.undo();
+        cmd.undo();
+
+        cmd.redo();
+        deleteCommand.redo();
+
+        deleteCommand.undo();
+        cmd.undo();
+    }
+
+    path1.setParent(nullptr);
+    path2.setParent(nullptr);
+}
 
 KISTEST_MAIN(TestPointMergeCommand)
diff --git a/libs/flake/tests/TestPointMergeCommand.h b/libs/flake/tests/TestPointMergeCommand.h
index 0184c628ac1..95e32e0ef6a 100644
--- a/libs/flake/tests/TestPointMergeCommand.h
+++ b/libs/flake/tests/TestPointMergeCommand.h
@@ -35,6 +35,9 @@ private Q_SLOTS:
     void testMultipathJoinShapesSingleShapeEndToStart();
     void testMultipathJoinShapesSingleShapeStartToEnd();
 
+    // Regression test. Making sure that undo/redo operations don't cause crashes.
+    void undoRedo();
+
 };
 
 #endif // TESTPOINTMERGECOMMAND_H
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.