tdf#155951 sw: fix crash using XTextRange::getString() in selectionChanged()

XTextRange::getString() triggered selection change event,
resulting infinite recursion, when getString() used
in selectionChanged() of the listener.

Steps to reproduce (used by the unit test, too):

Add a XSelectionChangeListener to the document with a
selectionChanged() calling the getString() of the selected
text range. Select a word in the document editor using
the Ctrl-Shift-arrow keys.

Change-Id: I87a0f60cee3663f5303d6eb6980058ccdcc373e1
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153356
Tested-by: Jenkins
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/qa/core/unocore/unocore.cxx b/sw/qa/core/unocore/unocore.cxx
index a1e931e..199da2e 100644
--- a/sw/qa/core/unocore/unocore.cxx
+++ b/sw/qa/core/unocore/unocore.cxx
@@ -15,6 +15,8 @@
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
#include <com/sun/star/text/XDependentTextField.hpp>
#include <com/sun/star/document/XDocumentInsertable.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>

#include <comphelper/propertyvalue.hxx>
#include <comphelper/sequenceashashmap.hxx>
@@ -912,6 +914,57 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testConvertToTextFrame)
    CPPUNIT_ASSERT_EQUAL(aPaM.GetPoint()->nNode, aFrame3Anchor);
}

namespace
{
/// This selection listener calls XTextRange::getString() on a selection change, which triggered
/// a new selection change event by accident, resulting infinite recursion and crash
struct SelectionChangeListener : public cppu::WeakImplHelper<view::XSelectionChangeListener>
{
public:
    SelectionChangeListener();
    // view::XSelectionChangeListener
    void SAL_CALL selectionChanged(const lang::EventObject& rEvent) override;

    // lang::XEventListener
    void SAL_CALL disposing(const lang::EventObject& rSource) override;
};
}

SelectionChangeListener::SelectionChangeListener() {}

void SelectionChangeListener::selectionChanged(const lang::EventObject& rEvent)
{
    uno::Reference<view::XSelectionSupplier> xSelectionSupplier(rEvent.Source, uno::UNO_QUERY);
    css::uno::Reference<css::container::XIndexAccess> xSelection(xSelectionSupplier->getSelection(),
                                                                 css::uno::UNO_QUERY_THROW);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSelection->getCount());
    css::uno::Reference<css::text::XTextRange> xTextRange(xSelection->getByIndex(0),
                                                          css::uno::UNO_QUERY_THROW);
    CPPUNIT_ASSERT(xTextRange->getString().startsWith("test"));
}

void SelectionChangeListener::disposing(const lang::EventObject& /*rSource*/) {}

CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testTdf155951)
{
    createSwDoc();
    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XText> xText = xTextDocument->getText();
    uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
    xText->insertString(xCursor, "test", /*bAbsorb=*/false);

    uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
    uno::Reference<view::XSelectionSupplier> xController(xModel->getCurrentController(),
                                                         uno::UNO_QUERY);
    xController->addSelectionChangeListener(new SelectionChangeListener());

    // This crashed here because of infinite recursion
    dispatchCommand(mxComponent, ".uno:WordLeftSel", {});

    // this needs to wait for dispatching (trigger also a second selection change)
    xText->insertString(xCursor, "test", /*bAbsorb=*/false);
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/unocore/unoobj.cxx b/sw/source/core/unocore/unoobj.cxx
index 2e65cf9..ccbcc53 100644
--- a/sw/source/core/unocore/unoobj.cxx
+++ b/sw/source/core/unocore/unoobj.cxx
@@ -74,6 +74,8 @@
#include <comphelper/propertyvalue.hxx>
#include <comphelper/servicehelper.hxx>
#include <comphelper/profilezone.hxx>
#include <comphelper/flagguard.hxx>
#include <swmodule.hxx>

using namespace ::com::sun::star;

@@ -158,6 +160,11 @@ void SwUnoCursorHelper::GetTextFromPam(SwPaM & rPam, OUString & rBuffer,
    const bool bOldShowProgress = xWrt->m_bShowProgress;
    xWrt->m_bShowProgress = false;
    xWrt->m_bHideDeleteRedlines = pLayout && pLayout->IsHideRedlines();
    // tdf#155951 SwWriter::Write calls EndAllAction, and that
    // called SelectShell(), triggering selection change event, which
    // resulted infinite recursion, if selectionChanged() calls
    // XTextRange::getString() e.g. on the selected range.
    ::comphelper::FlagRestorationGuard g(g_bNoInterrupt, true);

    if( ! aWriter.Write( xWrt ).IsError() )
    {