sw/qa/extras/uiwriter/data/tdf131431.odt |binary
 sw/qa/extras/uiwriter/uiwriter7.cxx      |   47 +++++++++++++++++++++++++++++++
 sw/source/core/crsr/swcrsr.cxx           |   23 +++++++++++----
 3 files changed, 64 insertions(+), 6 deletions(-)

New commits:
commit 17c9b49f3d215b58604a3d301b18ada322e3986f
Author:     Mateusz Wlazłowski <[email protected]>
AuthorDate: Fri May 2 14:43:29 2025 +0200
Commit:     Noel Grandin <[email protected]>
CommitDate: Mon Feb 9 10:15:04 2026 +0100

    tdf#131431 Move next node if pCurrentCursor hasn't moved in replaceAll
    
    ...to avoid an infinite loop
    
    This commit addresses an issue where replacing an attribute at the
    end of a paragraph with a new line results in an infinite loop.
    The loop occurs because the cursor fails to advance
    past the node boundary.
    
    The root cause is an additional constraint that skips moving to the next 
node
    when the cursor is at the beginning/end of the node. Introduced in commit
    7087eb75b0e88429d5d2410c1aef54f30a86c560, the additional check aimed to
    ensure that attributes in empty lines are not skipped.
    
    This commit adds a check to detect when pCurrentCursor does not
    advance and moves it forward.
    
    Change-Id: I18ef6d38842ee4b06a72c9ae144eb69f2353cac9
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185135
    Reviewed-by: Noel Grandin <[email protected]>
    Tested-by: Jenkins

diff --git a/sw/qa/extras/uiwriter/data/tdf131431.odt 
b/sw/qa/extras/uiwriter/data/tdf131431.odt
new file mode 100644
index 000000000000..856310b3dd88
Binary files /dev/null and b/sw/qa/extras/uiwriter/data/tdf131431.odt differ
diff --git a/sw/qa/extras/uiwriter/uiwriter7.cxx 
b/sw/qa/extras/uiwriter/uiwriter7.cxx
index a28423a0a056..c22b02d415e0 100644
--- a/sw/qa/extras/uiwriter/uiwriter7.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter7.cxx
@@ -364,6 +364,53 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest7, testTextSearch)
                          pCursor->GetPointNode().GetTextNode()->GetText());
 }
 
+CPPUNIT_TEST_FIXTURE(SwUiWriterTest7, testTdf131431)
+{
+    // the goal of this test is to check if replaceAll does not go into an 
infinite loop
+
+    // load document with underlined text with empty and non empty lines
+    createSwDoc("tdf131431.odt");
+
+    // setup search for any underline text
+    uno::Sequence<beans::PropertyValue> 
aSearchAttribute(comphelper::InitPropertySequence(
+        { { "CharUnderline", 
uno::Any(sal_Int32(css::awt::FontUnderline::NONE)) } }));
+
+    // setup replace with green highlight color
+    uno::Sequence<beans::PropertyValue> aReplaceAttribute(
+        comphelper::InitPropertySequence({ { "CharBackColor", 
uno::Any(sal_Int32(0x00FF00)) } }));
+
+    uno::Reference<util::XReplaceable> xReplace(mxComponent, 
uno::UNO_QUERY_THROW);
+    uno::Reference<util::XReplaceDescriptor> xReplaceDes = 
xReplace->createReplaceDescriptor();
+    uno::Reference<util::XPropertyReplace> xPropReplace(xReplaceDes, 
uno::UNO_QUERY_THROW);
+    xPropReplace->setSearchAttributes(aSearchAttribute);
+    xPropReplace->setReplaceAttributes(aReplaceAttribute);
+
+    // time out after 30 seconds if replaceAll hasn't returned
+    std::atomic<bool> completed{ false };
+    std::thread TimeoutThread([&completed]() {
+        for (int i = 0; i < 300; ++i)
+        {
+            std::this_thread::sleep_for(std::chrono::milliseconds(100));
+            if (completed)
+            {
+                return;
+            }
+        }
+        CPPUNIT_FAIL("Test timed out after 30 seconds - infinite loop 
detected");
+    });
+
+    // actual test
+    sal_Int32 nReplaceCount = xReplace->replaceAll(xReplaceDes);
+
+    completed = true;
+    TimeoutThread.join();
+
+    // ideally should be 9, but due to some bugs it reports more
+    // CPPUNIT_ASSERT_EQUAL(sal_Int32(9), nReplaceCount);
+    CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(8), nReplaceCount);
+    CPPUNIT_ASSERT_LESSEQUAL(sal_Int32(14), nReplaceCount);
+}
+
 CPPUNIT_TEST_FIXTURE(SwUiWriterTest7, testTdf147583_backwardSearch)
 {
     createSwDoc("tdf147583_backwardSearch.odt");
diff --git a/sw/source/core/crsr/swcrsr.cxx b/sw/source/core/crsr/swcrsr.cxx
index 51a695fc8194..feaf62bf0e12 100644
--- a/sw/source/core/crsr/swcrsr.cxx
+++ b/sw/source/core/crsr/swcrsr.cxx
@@ -56,6 +56,7 @@
 #include <memory>
 #include <comphelper/lok.hxx>
 #include <editsh.hxx>
+#include <pamtyp.hxx>
 
 #include <viewopt.hxx>
 #include <annotationmark.hxx>
@@ -797,7 +798,7 @@ static sal_Int32 lcl_FindSelection( SwFindParas& rParas, 
SwCursor* pCurrentCurso
         // independent from search direction: SPoint is always bigger than mark
         // if the search area is valid
         SwPosition *pSttPos = aRegion.GetMark(),
-                        *pEndPos = aRegion.GetPoint();
+                   *pEndPos = aRegion.GetPoint();
         *pSttPos = *pTmpCursor->Start();
         *pEndPos = *pTmpCursor->End();
         if( bSrchBkwrd )
@@ -807,7 +808,7 @@ static sal_Int32 lcl_FindSelection( SwFindParas& rParas, 
SwCursor* pCurrentCurso
             pPHdl.reset(new PercentHdl( aRegion ));
 
         // as long as found and not at same position
-        while(  *pSttPos <= *pEndPos )
+        while( *pSttPos <= *pEndPos )
         {
             nFndRet = rParas.DoFind(*pCurrentCursor, fnMove, aRegion, 
bInReadOnly, xSearchItem);
             if( 0 == nFndRet ||
@@ -855,6 +856,20 @@ static sal_Int32 lcl_FindSelection( SwFindParas& rParas, 
SwCursor* pCurrentCurso
                 }
             }
 
+            // tdf#131431 move pCurrentCursor if it hasn't moved to avoid an 
infinte loop
+            if( bSrchBkwrd && *pEndPos == *pCurrentCursor->Start() )
+            {
+                (*fnMove.fnPos)( pCurrentCursor->GetMark(), false );
+            }
+            else if ( !bSrchBkwrd && *pSttPos == *pCurrentCursor->End() )
+            {
+                (*fnMove.fnPos)( pCurrentCursor->GetPoint(), false );
+            }
+
+            if( *pSttPos == *pEndPos )
+                // in area but at the end => done
+                break;
+
             if( bSrchBkwrd )
                 // move pEndPos in front of the found area
                 *pEndPos = *pCurrentCursor->Start();
@@ -862,10 +877,6 @@ static sal_Int32 lcl_FindSelection( SwFindParas& rParas, 
SwCursor* pCurrentCurso
                 // move pSttPos behind the found area
                 *pSttPos = *pCurrentCursor->End();
 
-            if( *pSttPos == *pEndPos )
-                // in area but at the end => done
-                break;
-
             if( !nCursorCnt && pPHdl )
             {
                 pPHdl->NextPos( *aRegion.GetMark() );

Reply via email to