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() );
