sw/inc/fesh.hxx                          |    2 
 sw/inc/strings.hrc                       |    2 
 sw/inc/swundo.hxx                        |    1 
 sw/qa/extras/uiwriter/data/tdf169651.odt |binary
 sw/qa/extras/uiwriter/uiwriter11.cxx     |   25 +++++++++++
 sw/source/core/draw/dcontact.cxx         |    1 
 sw/source/core/frmedt/fefly1.cxx         |   67 +++++++++++++++++++++++++++++--
 sw/source/core/undo/undobj.cxx           |    3 +
 8 files changed, 96 insertions(+), 5 deletions(-)

New commits:
commit ef597d2e1e0fa24c2bdcdde30faa416cef7bf9c1
Author:     Jim Raykowski <[email protected]>
AuthorDate: Sat Dec 13 16:10:52 2025 -0900
Commit:     Jim Raykowski <[email protected]>
CommitDate: Wed Feb 4 09:04:28 2026 +0100

    tdf#169651 Avoid crash in SwLayAction::FormatContent
    
    Here is effort to avoid a crash that occurs when a fly frame is
    unfloated that has a drawing object or fly frame anchored to its frame.
    
    To avoid the crash, this approach uses FN_TOOL_ANCHOR_PARAGRAPH to
    change the anchor of frame anchored drawing and fly frame objects to be
    paragraph anchored in the fly frame content. MoveObjToVisibleLayer is
    added in SwDrawContact::SwClientNotify
    sw::DrawFrameFormatHintId::POST_RESTORE_FLY_ANCHOR case handling to make
    undo restored fly anchored objects visible. SwFEShell::SelectFlyFrame is
    made SW_DLLPUBLIC to make the clang linker not complain about 'linker
    cannot find symbol(s) for architecture x86_64' when used in the included
    unit test.
    
    Change-Id: Ie6791cb45e7dce31132d3b6a8dac79d63bc27eb2
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/195650
    Tested-by: Jenkins
    Reviewed-by: Jim Raykowski <[email protected]>

diff --git a/sw/inc/fesh.hxx b/sw/inc/fesh.hxx
index 085adf8dee52..3871507b9120 100644
--- a/sw/inc/fesh.hxx
+++ b/sw/inc/fesh.hxx
@@ -268,7 +268,7 @@ public:
     SW_DLLPUBLIC bool Copy( SwFEShell&, const Point& rSttPt, const Point& 
rInsPt,
                bool bIsMove = false, bool bSelectInsert = true );
 
-    void SelectFlyFrame( SwFlyFrame& rFrame );
+    SW_DLLPUBLIC void SelectFlyFrame(SwFlyFrame& rFrame);
 
     SW_DLLPUBLIC void UnfloatFlyFrame();
 
diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc
index 0b70e22c6077..99e23aab3110 100644
--- a/sw/inc/strings.hrc
+++ b/sw/inc/strings.hrc
@@ -1577,6 +1577,8 @@
 
 #define STR_UNDO_SORT_CHAPTERS NC_("STR_UNDO_SORT_CHAPTERS", "Sort chapters")
 
+#define STR_UNDO_UNFLOAT_FRAME_CONTENT NC_("STR_UNDO_UNFLOAT_FRAME_CONTENT", 
"Unfloat frame content")
+
 #endif
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/inc/swundo.hxx b/sw/inc/swundo.hxx
index 76af157fd129..56bbd771fb25 100644
--- a/sw/inc/swundo.hxx
+++ b/sw/inc/swundo.hxx
@@ -186,6 +186,7 @@ enum class SwUndoId
     REINSTATE_REDLINE = 154,
     COPY_HEADER_FOOTER = 155,
     SORT_CHAPTERS = 156,
+    UNFLOAT_FRAME_CONTENT = 157
 };
 
 OUString GetUndoComment(SwUndoId eId);
diff --git a/sw/qa/extras/uiwriter/data/tdf169651.odt 
b/sw/qa/extras/uiwriter/data/tdf169651.odt
new file mode 100644
index 000000000000..dccae694025f
Binary files /dev/null and b/sw/qa/extras/uiwriter/data/tdf169651.odt differ
diff --git a/sw/qa/extras/uiwriter/uiwriter11.cxx 
b/sw/qa/extras/uiwriter/uiwriter11.cxx
index 9da790d04fde..92548813bd25 100644
--- a/sw/qa/extras/uiwriter/uiwriter11.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter11.cxx
@@ -30,6 +30,10 @@
 #include <ndtxt.hxx>
 #include <IDocumentLayoutAccess.hxx>
 #include <svx/svxids.hrc>
+#include <sortedobjs.hxx>
+#include <rootfrm.hxx>
+#include <anchoredobject.hxx>
+#include <flyfrm.hxx>
 
 namespace
 {
@@ -621,6 +625,27 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest11, 
testTdf165206DirSwitchPreservesAlignment)
                          getProperty<short>(getRun(getParagraph(1), 1), 
u"ParaAdjust"_ustr));
 }
 
+CPPUNIT_TEST_FIXTURE(SwUiWriterTest11, testTdf169651)
+{
+    // Given a document with a fly frame that has anchored FLY_AT_FLY, a shape 
object and a
+    // fly frame that itself has a shape object anchored FLY_AT_FLY:
+    createSwDoc("tdf169651.odt");
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+    CPPUNIT_ASSERT(pWrtShell);
+
+    const SwSortedObjs* pAnchoredObjs
+        = 
pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs();
+    CPPUNIT_ASSERT(pAnchoredObjs);
+    SwAnchoredObject* pAnchoredObj = (*pAnchoredObjs)[0];
+    SwFlyFrame* pFlyFrame = pAnchoredObj->DynCastFlyFrame();
+    CPPUNIT_ASSERT(pFlyFrame);
+
+    pWrtShell->SelectFlyFrame(*pFlyFrame);
+
+    // Without the patch this test would crash during the following
+    pWrtShell->UnfloatFlyFrame();
+}
+
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 
diff --git a/sw/source/core/draw/dcontact.cxx b/sw/source/core/draw/dcontact.cxx
index 2421f2a3622c..129e6e1b12b2 100644
--- a/sw/source/core/draw/dcontact.cxx
+++ b/sw/source/core/draw/dcontact.cxx
@@ -1573,6 +1573,7 @@ void SwDrawContact::SwClientNotify(const SwModify& rMod, 
const SfxHint& rHint)
                  break;
             case sw::DrawFrameFormatHintId::POST_RESTORE_FLY_ANCHOR:
                 GetAnchoredObj(GetMaster())->MakeObjPos();
+                MoveObjToVisibleLayer(GetMaster());
                 break;
             default:
                 ;
diff --git a/sw/source/core/frmedt/fefly1.cxx b/sw/source/core/frmedt/fefly1.cxx
index 15af7cfd2376..241a5272b2b2 100644
--- a/sw/source/core/frmedt/fefly1.cxx
+++ b/sw/source/core/frmedt/fefly1.cxx
@@ -75,6 +75,9 @@
 #include <frameformats.hxx>
 #include <textboxhelper.hxx>
 
+#include <sortedobjs.hxx>
+#include <cmdid.h>
+#include <sfx2/dispatch.hxx>
 
 using namespace ::com::sun::star;
 
@@ -276,10 +279,6 @@ void SwFEShell::SelectFlyFrame( SwFlyFrame& rFrame )
 
 void SwFEShell::UnfloatFlyFrame()
 {
-    GetIDocumentUndoRedo().StartUndo(SwUndoId::DELLAYFMT, nullptr);
-    comphelper::ScopeGuard g([this]
-                             { 
GetIDocumentUndoRedo().EndUndo(SwUndoId::DELLAYFMT, nullptr); });
-
     SwFlyFrame* pFly = GetSelectedFlyFrame();
     if (!pFly)
     {
@@ -300,6 +299,55 @@ void SwFEShell::UnfloatFlyFrame()
         return;
     }
 
+    GetIDocumentUndoRedo().StartUndo(SwUndoId::UNFLOAT_FRAME_CONTENT, nullptr);
+    comphelper::ScopeGuard g(
+        [this]
+        {
+            GetIDocumentUndoRedo().EndUndo(SwUndoId::UNFLOAT_FRAME_CONTENT, 
nullptr);
+            EndAllAction();
+        });
+
+    StartAllAction();
+
+    // tdf#169651 Crash in: SwLayAction::FormatContent
+    // To avoid crash, dispatch FN_TOOL_ANCHOR_PARAGRAPH to change anchored 
objects that have an
+    // anchor id of FLY_AT_FLY to have an anchored id of FLY_AT_PARA before 
removing the containing
+    // fly frame.
+    if (pFly->GetDrawObjs())
+    {
+        for (size_t i = 0; pFly->GetDrawObjs() && i < 
pFly->GetDrawObjs()->size(); i++)
+        {
+            SwAnchoredObject* pAnchoredObj = (*pFly->GetDrawObjs())[i];
+            if (!pAnchoredObj)
+                continue;
+
+            const SwFrameFormat* pFrameFormat = pAnchoredObj->GetFrameFormat();
+            if (!pFrameFormat)
+                continue;
+
+            if (pFrameFormat->GetAnchor().GetAnchorId() != 
RndStdIds::FLY_AT_FLY)
+                continue;
+
+            // if (SwFlyFrame* pFlyFrame = pAnchoredObj->DynCastFlyFrame())
+            //     SelectFlyFrame(*pFlyFrame);
+            // else
+            SelectObj(Point(), 0, pAnchoredObj->DrawObj());
+
+            
GetSfxViewShell()->GetDispatcher()->Execute(FN_TOOL_ANCHOR_PARAGRAPH,
+                                                        SfxCallMode::SYNCHRON);
+
+            // Check if anchor id changed.
+            if (pFrameFormat->GetAnchor().GetAnchorId() != 
RndStdIds::FLY_AT_PARA)
+                continue;
+
+            // The fly frame should now have one less draw object anchored to 
it. Anchored
+            // objects are stored sorted. The next anchored object in the 
sorted objects vector
+            // should now be at the current index. Adjust the index variable 
value by -1 so the
+            // next pass of the for loop will set it to the same index as this 
pass.
+            i--;
+        }
+    }
+
     // Create an empty paragraph after the table, so the frame's SwNodes 
section is non-empty after
     // MoveNodeRange(). Undo would ensure it's non-empty and then node offsets 
won't match.
     IDocumentContentOperations& rIDCO = 
GetDoc()->getIDocumentContentOperations();
@@ -322,6 +370,17 @@ void SwFEShell::UnfloatFlyFrame()
         return;
     }
 
+    // The anchor node could be a start node, indicating anchored to frame, 
move past it to the
+    // first content node. pFlyFormat->GetAnchor().GetAnchorId() == 
RndStdIds::FLY_AT_FLY
+    if (pAnchor->IsStartNode())
+    {
+        SwNodeIndex aIdx(*pAnchor);
+        pAnchor = SwNodes::GoNext(&aIdx);
+        assert(pAnchor);
+        if (!pAnchor)
+            return;
+    }
+
     // Move the content outside of the text frame.
     SwNodeIndex aInsertPos(*pAnchor);
     rIDCO.MoveNodeRange(aRange, aInsertPos.GetNode(), 
SwMoveFlags::CREATEUNDOOBJ);
diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx
index c994448a10c6..1c5f0571b7b7 100644
--- a/sw/source/core/undo/undobj.cxx
+++ b/sw/source/core/undo/undobj.cxx
@@ -703,6 +703,9 @@ OUString GetUndoComment(SwUndoId eId)
         case SwUndoId::SORT_CHAPTERS:
             pId = STR_UNDO_SORT_CHAPTERS;
             break;
+        case SwUndoId::UNFLOAT_FRAME_CONTENT:
+            pId = STR_UNDO_UNFLOAT_FRAME_CONTENT;
+            break;
     }
 
     assert(pId);

Reply via email to