tdf#119904 : Generalize the fix for tdf#115093

The point is, if one of the FormulaTokens in the formula
is returned as the "result" FormulaToken, then we should not reuse
that same FormulaToken for each formula-cell in the group. If we do,
then we end up with whole group/batch having the same result.

Also adds a unit test specific to this bug.

This issue is non existent in master because "SoftwareInterpreter"
and related code were removed from master long after branch-off
to 6.1.

Change-Id: I10265211b5f14c82ed385401aa3fb838c492872d
Reviewed-on: https://gerrit.libreoffice.org/61362
Tested-by: Jenkins
Reviewed-by: Michael Meeks <michael.meeks@collabora.com>
diff --git a/sc/qa/unit/parallelism.cxx b/sc/qa/unit/parallelism.cxx
index c5c1961..2e74840 100644
--- a/sc/qa/unit/parallelism.cxx
+++ b/sc/qa/unit/parallelism.cxx
@@ -52,6 +52,7 @@ public:
    void testVLOOKUPSUM();
    void testSingleRef();
    void testSUMIFImplicitRange();
    void testTdf119904();

    CPPUNIT_TEST_SUITE(ScParallelismTest);
    CPPUNIT_TEST(testSUMIFS);
@@ -60,6 +61,7 @@ public:
    CPPUNIT_TEST(testVLOOKUPSUM);
    CPPUNIT_TEST(testSingleRef);
    CPPUNIT_TEST(testSUMIFImplicitRange);
    CPPUNIT_TEST(testTdf119904);
    CPPUNIT_TEST_SUITE_END();

private:
@@ -396,6 +398,29 @@ void ScParallelismTest::testSUMIFImplicitRange()
    m_pDoc->DeleteTab(0);
}

void ScParallelismTest::testTdf119904()
{
    m_pDoc->InsertTab(0, "1");

    const size_t nNumRows = 200;
    for (size_t i = 0; i < nNumRows; ++i)
    {
        m_pDoc->SetValue(0, i, 0, static_cast<double>(i));
        m_pDoc->SetFormula(ScAddress(1, i, 0), (i % 2) ? OUString("=TRUE()") : OUString("=FALSE()"), formula::FormulaGrammar::GRAM_NATIVE_UI);
        m_pDoc->SetFormula(ScAddress(2, i, 0), "=IF(B" + OUString::number(i+1) +
                           "; A" + OUString::number(i+1) + "; \"\")", formula::FormulaGrammar::GRAM_NATIVE_UI);
    }

    m_xDocShell->DoHardRecalc();

    for (size_t i = 0; i < nNumRows; ++i)
    {
        OString aMsg = "At row " + OString::number(i);
        CPPUNIT_ASSERT_EQUAL_MESSAGE(aMsg.getStr(), (i % 2) ? i : 0, static_cast<size_t>(m_pDoc->GetValue(2, i, 0)));
    }
    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_SUITE_REGISTRATION(ScParallelismTest);

CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sc/source/core/tool/formulagroup.cxx b/sc/source/core/tool/formulagroup.cxx
index ffd961a..8d0da74 100644
--- a/sc/source/core/tool/formulagroup.cxx
+++ b/sc/source/core/tool/formulagroup.cxx
@@ -29,7 +29,9 @@
#endif
#include <o3tl/make_unique.hxx>
#include <rtl/bootstrap.hxx>
#include <sal/alloca.h>

#include <algorithm>
#include <cstdio>
#include <unordered_map>
#include <vector>
@@ -177,7 +179,12 @@ public:
        ScTokenArray aCode2;

        ScInterpreterContext aContext(mrDoc, mpFormatter);
        sal_uInt16 nNumNonOpenClose = mrCode.GetLen();
        const formula::FormulaToken* pLastDoubleResultToken = nullptr;
        const formula::FormulaToken* pLastStringResultToken = nullptr;
        size_t nNumToks = mrCode.GetLen();
        bool* pReuseFlags = static_cast<bool*>(alloca(sizeof(bool)*nNumToks));
        if (pReuseFlags)
            std::fill_n(pReuseFlags, nNumToks, true);

        for (SCROW i = mnIdx; i <= mnLastIdx; ++i, maBatchTopPos.IncRow())
        {
@@ -213,7 +220,12 @@ public:
                                aCode2.AddString(rPool.intern(OUString(pStr)));
                            else
                            {
                                if ( ( pTargetTok->GetType() == formula::svString ) && ( nNumNonOpenClose > 1 ) )
                                bool bReuseToken = pReuseFlags && pReuseFlags[nTokIdx];
                                if (bReuseToken && pTargetTok == pLastStringResultToken)
                                    // Once pReuseFlags[nTokIdx] is set to false, it will continue to be so.
                                    bReuseToken = pReuseFlags[nTokIdx] = false;

                                if ( ( pTargetTok->GetType() == formula::svString ) && bReuseToken )
                                    pTargetTok->SetString(rPool.intern(OUString(pStr)));
                                else
                                {
@@ -227,7 +239,7 @@ public:
                            // Value of NaN represents an empty cell.
                            if ( !pTargetTok )
                                aCode2.AddToken(ScEmptyCellToken(false, false));
                            else if ( ( pTargetTok->GetType() != formula::svEmptyCell ) || ( nNumNonOpenClose == 1 ) )
                            else if ( pTargetTok->GetType() != formula::svEmptyCell )
                            {
                                ScEmptyCellToken* pEmptyTok = new ScEmptyCellToken(false, false);
                                aCode2.ReplaceToken(nTokIdx, pEmptyTok, formula::FormulaTokenArray::CODE_ONLY);
@@ -240,7 +252,12 @@ public:
                                aCode2.AddDouble(fVal);
                            else
                            {
                                if ( ( pTargetTok->GetType() == formula::svDouble ) && ( nNumNonOpenClose > 1 ) )
                                bool bReuseToken = pReuseFlags && pReuseFlags[nTokIdx];
                                if (bReuseToken && pTargetTok == pLastDoubleResultToken)
                                    // Once pReuseFlags[nTokIdx] is set to false, it will continue to be so.
                                    bReuseToken = pReuseFlags[nTokIdx] = false;

                                if ( ( pTargetTok->GetType() == formula::svDouble ) && bReuseToken )
                                    pTargetTok->GetDoubleAsReference() = fVal;
                                else
                                {
@@ -292,16 +309,8 @@ public:
                    break;
                    default:
                        if ( !pTargetTok )
                        {
                            if ( p->GetType() == formula::svSep )
                            {
                                OpCode eOp = p->GetOpCode();
                                if ( eOp == ocOpen || eOp == ocClose )
                                    --nNumNonOpenClose;
                            }

                            aCode2.AddToken(*p);
                        }

                } // end of switch statement
            } // end of formula token for loop

@@ -314,6 +323,14 @@ public:
            ScInterpreter aInterpreter(pDest, &mrDoc, aContext, maBatchTopPos, aCode2);
            aInterpreter.Interpret();
            mrResults[i] = aInterpreter.GetResultToken();
            const auto* pResultToken = mrResults[i].get();
            if (pResultToken)
            {
                if (pResultToken->GetType() == formula::svDouble)
                    pLastDoubleResultToken = pResultToken;
                else if (pResultToken->GetType() == formula::svString)
                    pLastStringResultToken = pResultToken;
            }
        } // Row iteration for loop end
    } // operator () end