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
7087eb75b0, 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 <noel.grandin@collabora.co.uk>
Tested-by: Jenkins
This commit is contained in:
Mateusz Wlazłowski
2026-02-09 10:15:04 +01:00
committed by Noel Grandin
parent a94cf807f3
commit 17c9b49f3d
3 changed files with 64 additions and 6 deletions
Binary file not shown.
+47
View File
@@ -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");
+17 -6
View File
@@ -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() );