xref: /core/sc/source/core/data/formulacell.cxx (revision 36d898b3)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <config_feature_opencl.h>
21 
22 #include <sal/config.h>
23 #include <sal/log.hxx>
24 #include <osl/diagnose.h>
25 
26 #include <cassert>
27 #include <cstdlib>
28 
29 #include <formulacell.hxx>
30 #include <grouptokenconverter.hxx>
31 
32 #include <compiler.hxx>
33 #include <document.hxx>
34 #include <cellvalue.hxx>
35 #include <interpre.hxx>
36 #include <macromgr.hxx>
37 #include <refupdat.hxx>
38 #include <recursionhelper.hxx>
39 #include <docoptio.hxx>
40 #include <rangenam.hxx>
41 #include <rangelst.hxx>
42 #include <dbdata.hxx>
43 #include <progress.hxx>
44 #include <scmatrix.hxx>
45 #include <rechead.hxx>
46 #include <scitems.hxx>
47 #include <validat.hxx>
48 #include <editutil.hxx>
49 #include <chgtrack.hxx>
50 #include <tokenarray.hxx>
51 
52 #include <comphelper/threadpool.hxx>
53 #include <editeng/editobj.hxx>
54 #include <formula/errorcodes.hxx>
55 #include <svl/intitem.hxx>
56 #include <svl/numformat.hxx>
57 #include <formulagroup.hxx>
58 #include <listenercontext.hxx>
59 #include <types.hxx>
60 #include <scopetools.hxx>
61 #include <refupdatecontext.hxx>
62 #include <tokenstringcontext.hxx>
63 #include <refhint.hxx>
64 #include <listenerquery.hxx>
65 #include <listenerqueryids.hxx>
66 #include <grouparealistener.hxx>
67 #include <formulalogger.hxx>
68 #include <com/sun/star/sheet/FormulaLanguage.hpp>
69 
70 #if HAVE_FEATURE_OPENCL
71 #include <opencl/openclwrapper.hxx>
72 #endif
73 
74 #include <memory>
75 #include <map>
76 
77 using namespace formula;
78 
79 #define DEBUG_CALCULATION 0
80 #if DEBUG_CALCULATION
81 static bool bDebugCalculationActive = false;                // Set to true for global active init,
82 static ScAddress aDebugCalculationTriggerAddress(1,2,0);    // or on cell Sheet1.B3, whatever you like
83 
84 struct DebugCalculationEntry
85 {
86           ScAddress     maPos;
87           OUString      maResult;
88     const ScDocument&   mrDoc;
89           sal_uInt32    mnGroup;
90           sal_uInt16    mnRecursion;
91 
DebugCalculationEntryDebugCalculationEntry92     DebugCalculationEntry( const ScAddress& rPos, ScDocument& rDoc, sal_uInt32 nGroup ) :
93         maPos(rPos),
94         mrDoc(rDoc),
95         mnGroup(nGroup),
96         mnRecursion(rDoc.GetRecursionHelper().GetRecursionCount())
97     {
98     }
99 };
100 
101 /** Debug/dump formula cell calculation chain.
102     Either, somewhere set aDC.mbActive=true, or
103     aDC.maTrigger=ScAddress(col,row,tab) of interest from where to start.
104     This does not work for deep recursion > MAXRECURSION, the results are
105     somewhat... funny... ;)
106  */
107 static struct DebugCalculation
108 {
109     std::vector< DebugCalculationEntry >    mvPos;
110     std::vector< DebugCalculationEntry >    mvResults;
111     ScAddress                               maTrigger;
112     sal_uInt32                              mnGroup;
113     bool                                    mbActive;
114     bool                                    mbSwitchOff;
115     bool                                    mbPrint;
116     bool                                    mbPrintResults;
117 
DebugCalculationDebugCalculation118     DebugCalculation() : mnGroup(0), mbActive(bDebugCalculationActive), mbSwitchOff(false),
119     mbPrint(true), mbPrintResults(false) {}
120 
121     /** Print chain in encountered dependency order. */
printDebugCalculation122     void print() const
123     {
124         for (auto const& it : mvPos)
125         {
126             OUString aStr( it.maPos.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &it.mrDoc) +
127                     " [" + OUString::number( it.mnRecursion) + "," + OUString::number( it.mnGroup) + "]");
128             fprintf( stderr, "%s -> ", aStr.toUtf8().getStr());
129         }
130         fprintf( stderr, "%s", "END\n");
131     }
132 
133     /** Print chain results. */
printResultsDebugCalculation134     void printResults() const
135     {
136         for (auto const& it : mvResults)
137         {
138             OUString aStr( it.maPos.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &it.mrDoc));
139             aStr += " (" + it.maResult + ")";
140             fprintf( stderr, "%s, ", aStr.toUtf8().getStr());
141         }
142         fprintf( stderr, "%s", "END\n");
143     }
144 
storeResultDebugCalculation145     void storeResult( const svl::SharedString& rStr )
146     {
147         if (mbActive && !mvPos.empty())
148             mvPos.back().maResult = "\"" + rStr.getString() + "\"";
149     }
150 
storeResultDebugCalculation151     void storeResult( const double& fVal )
152     {
153         if (mbActive && !mvPos.empty())
154             mvPos.back().maResult = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_G, 2, '.', true);
155     }
156 
storeResultErrorDebugCalculation157     void storeResultError( FormulaError nErr )
158     {
159         if (mbActive && !mvPos.empty())
160             mvPos.back().maResult = "Err:" + OUString::number( int( nErr ));
161     }
162 
enterGroupDebugCalculation163     void enterGroup()
164     {
165         ++mnGroup;
166     }
167 
leaveGroupDebugCalculation168     void leaveGroup()
169     {
170         --mnGroup;
171     }
172 
173 } aDC;
174 
175 struct DebugCalculationStacker
176 {
DebugCalculationStackerDebugCalculationStacker177     DebugCalculationStacker( const ScAddress& rPos, ScDocument& rDoc )
178     {
179         if (!aDC.mbActive && rPos == aDC.maTrigger)
180             aDC.mbActive = aDC.mbSwitchOff = true;
181         if (aDC.mbActive)
182         {
183             aDC.mvPos.push_back( DebugCalculationEntry( rPos, rDoc, aDC.mnGroup));
184             aDC.mbPrint = true;
185         }
186     }
187 
~DebugCalculationStackerDebugCalculationStacker188     ~DebugCalculationStacker()
189     {
190         if (aDC.mbActive)
191         {
192             if (!aDC.mvPos.empty())
193             {
194                 if (aDC.mbPrint)
195                 {
196                     aDC.print();
197                     aDC.mbPrint = false;
198                 }
199                 if (aDC.mbPrintResults)
200                 {
201                     // Store results until final result is available, reversing order.
202                     aDC.mvResults.push_back( aDC.mvPos.back());
203                 }
204                 aDC.mvPos.pop_back();
205                 if (aDC.mbPrintResults && aDC.mvPos.empty())
206                 {
207                     aDC.printResults();
208                     std::vector< DebugCalculationEntry >().swap( aDC.mvResults);
209                 }
210                 if (aDC.mbSwitchOff && aDC.mvPos.empty())
211                     aDC.mbActive = false;
212             }
213         }
214     }
215 };
216 #endif
217 
218 namespace {
219 
220 // More or less arbitrary, of course all recursions must fit into available
221 // stack space (which is what on all systems we don't know yet?). Choosing a
222 // lower value may be better than trying a much higher value that also isn't
223 // sufficient but temporarily leads to high memory consumption. On the other
224 // hand, if the value fits all recursions, execution is quicker as no resumes
225 // are necessary. Could be made a configurable option.
226 // Allow for a year's calendar (366).
227 const sal_uInt16 MAXRECURSION = 400;
228 
229 typedef SCCOLROW(*DimensionSelector)(const ScDocument&, const ScAddress&, const ScSingleRefData&);
230 
lcl_GetCol(const ScDocument & rDoc,const ScAddress & rPos,const ScSingleRefData & rData)231 SCCOLROW lcl_GetCol(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
232 {
233     return rData.toAbs(rDoc, rPos).Col();
234 }
235 
lcl_GetRow(const ScDocument & rDoc,const ScAddress & rPos,const ScSingleRefData & rData)236 SCCOLROW lcl_GetRow(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
237 {
238     return rData.toAbs(rDoc, rPos).Row();
239 }
240 
lcl_GetTab(const ScDocument & rDoc,const ScAddress & rPos,const ScSingleRefData & rData)241 SCCOLROW lcl_GetTab(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
242 {
243     return rData.toAbs(rDoc, rPos).Tab();
244 }
245 
246 /** Check if both references span the same range in selected dimension.
247  */
248 bool
lcl_checkRangeDimension(const ScDocument & rDoc,const ScAddress & rPos,const SingleDoubleRefProvider & rRef1,const SingleDoubleRefProvider & rRef2,const DimensionSelector aWhich)249 lcl_checkRangeDimension(
250     const ScDocument& rDoc,
251     const ScAddress& rPos, const SingleDoubleRefProvider& rRef1, const SingleDoubleRefProvider& rRef2,
252     const DimensionSelector aWhich)
253 {
254     return aWhich(rDoc, rPos, rRef1.Ref1) == aWhich(rDoc, rPos, rRef2.Ref1) &&
255         aWhich(rDoc, rPos, rRef1.Ref2) == aWhich(rDoc, rPos, rRef2.Ref2);
256 }
257 
258 bool
lcl_checkRangeDimensions(const ScDocument & rDoc,const ScAddress & rPos,const SingleDoubleRefProvider & rRef1,const SingleDoubleRefProvider & rRef2,bool & bCol,bool & bRow,bool & bTab)259 lcl_checkRangeDimensions(
260     const ScDocument& rDoc,
261     const ScAddress& rPos, const SingleDoubleRefProvider& rRef1, const SingleDoubleRefProvider& rRef2,
262     bool& bCol, bool& bRow, bool& bTab)
263 {
264     const bool bSameCols(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetCol));
265     const bool bSameRows(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetRow));
266     const bool bSameTabs(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetTab));
267 
268     // Test if exactly two dimensions are equal
269     if (int(bSameCols) + int(bSameRows) + int(bSameTabs) == 2)
270     {
271         bCol = !bSameCols;
272         bRow = !bSameRows;
273         bTab = !bSameTabs;
274         return true;
275     }
276     return false;
277 }
278 
279 /** Check if references in given reference list can possibly
280     form a range. To do that, two of their dimensions must be the same.
281  */
282 bool
lcl_checkRangeDimensions(const ScDocument & rDoc,const ScAddress & rPos,const std::vector<formula::FormulaToken * >::const_iterator & rBegin,const std::vector<formula::FormulaToken * >::const_iterator & rEnd,bool & bCol,bool & bRow,bool & bTab)283 lcl_checkRangeDimensions(
284     const ScDocument& rDoc, const ScAddress& rPos,
285     const std::vector<formula::FormulaToken*>::const_iterator& rBegin,
286     const std::vector<formula::FormulaToken*>::const_iterator& rEnd,
287     bool& bCol, bool& bRow, bool& bTab)
288 {
289     std::vector<formula::FormulaToken*>::const_iterator aCur(rBegin);
290     ++aCur;
291     const SingleDoubleRefProvider aRef(**rBegin);
292     bool bOk(false);
293     {
294         const SingleDoubleRefProvider aRefCur(**aCur);
295         bOk = lcl_checkRangeDimensions(rDoc, rPos, aRef, aRefCur, bCol, bRow, bTab);
296     }
297     while (bOk && aCur != rEnd)
298     {
299         const SingleDoubleRefProvider aRefCur(**aCur);
300         bool bColTmp(false);
301         bool bRowTmp(false);
302         bool bTabTmp(false);
303         bOk = lcl_checkRangeDimensions(rDoc, rPos, aRef, aRefCur, bColTmp, bRowTmp, bTabTmp);
304         bOk = bOk && (bCol == bColTmp && bRow == bRowTmp && bTab == bTabTmp);
305         ++aCur;
306     }
307 
308     return bOk && aCur == rEnd;
309 }
310 
311 class LessByReference
312 {
313     const ScDocument& mrDoc;
314     ScAddress         maPos;
315     DimensionSelector maFunc;
316 public:
LessByReference(const ScDocument & rDoc,const ScAddress & rPos,const DimensionSelector & rFunc)317     LessByReference(const ScDocument& rDoc, const ScAddress& rPos, const DimensionSelector& rFunc) :
318         mrDoc(rDoc), maPos(rPos), maFunc(rFunc) {}
319 
operator ()(const formula::FormulaToken * pRef1,const formula::FormulaToken * pRef2)320     bool operator() (const formula::FormulaToken* pRef1, const formula::FormulaToken* pRef2)
321     {
322         const SingleDoubleRefProvider aRef1(*pRef1);
323         const SingleDoubleRefProvider aRef2(*pRef2);
324         return maFunc(mrDoc, maPos, aRef1.Ref1) < maFunc(mrDoc, maPos, aRef2.Ref1);
325     }
326 };
327 
328 /**
329  * Returns true if range denoted by token p2 starts immediately after range
330  * denoted by token p1. Dimension, in which the comparison takes place, is
331  * given by maFunc.
332  */
333 class AdjacentByReference
334 {
335     const ScDocument& mrDoc;
336     ScAddress         maPos;
337     DimensionSelector maFunc;
338 public:
AdjacentByReference(const ScDocument & rDoc,const ScAddress & rPos,DimensionSelector aFunc)339     AdjacentByReference(const ScDocument& rDoc, const ScAddress& rPos, DimensionSelector aFunc) :
340         mrDoc(rDoc), maPos(rPos), maFunc(aFunc) {}
341 
operator ()(const formula::FormulaToken * p1,const formula::FormulaToken * p2)342     bool operator() (const formula::FormulaToken* p1, const formula::FormulaToken* p2)
343     {
344         const SingleDoubleRefProvider aRef1(*p1);
345         const SingleDoubleRefProvider aRef2(*p2);
346         return maFunc(mrDoc, maPos, aRef2.Ref1) - maFunc(mrDoc, maPos, aRef1.Ref2) == 1;
347     }
348 };
349 
350 bool
lcl_checkIfAdjacent(const ScDocument & rDoc,const ScAddress & rPos,const std::vector<formula::FormulaToken * > & rReferences,const DimensionSelector aWhich)351 lcl_checkIfAdjacent(
352     const ScDocument& rDoc,
353     const ScAddress& rPos, const std::vector<formula::FormulaToken*>& rReferences, const DimensionSelector aWhich)
354 {
355     auto aBegin(rReferences.cbegin());
356     auto aEnd(rReferences.cend());
357     auto aBegin1(aBegin);
358     ++aBegin1;
359     --aEnd;
360     return std::equal(aBegin, aEnd, aBegin1, AdjacentByReference(rDoc, rPos, aWhich));
361 }
362 
363 void
lcl_fillRangeFromRefList(const ScDocument & rDoc,const ScAddress & aPos,const std::vector<formula::FormulaToken * > & rReferences,ScRange & rRange)364 lcl_fillRangeFromRefList(
365     const ScDocument& rDoc,
366     const ScAddress& aPos, const std::vector<formula::FormulaToken*>& rReferences, ScRange& rRange)
367 {
368     const ScSingleRefData aStart(
369             SingleDoubleRefProvider(*rReferences.front()).Ref1);
370     rRange.aStart = aStart.toAbs(rDoc, aPos);
371     const ScSingleRefData aEnd(
372             SingleDoubleRefProvider(*rReferences.back()).Ref2);
373     rRange.aEnd = aEnd.toAbs(rDoc, aPos);
374 }
375 
376 bool
lcl_refListFormsOneRange(const ScDocument & rDoc,const ScAddress & rPos,std::vector<formula::FormulaToken * > & rReferences,ScRange & rRange)377 lcl_refListFormsOneRange(
378         const ScDocument& rDoc,
379         const ScAddress& rPos, std::vector<formula::FormulaToken*>& rReferences,
380         ScRange& rRange)
381 {
382     if (rReferences.size() == 1)
383     {
384         lcl_fillRangeFromRefList(rDoc, rPos, rReferences, rRange);
385         return true;
386     }
387 
388     bool bCell(false);
389     bool bRow(false);
390     bool bTab(false);
391     if (lcl_checkRangeDimensions(rDoc, rPos, rReferences.begin(), rReferences.end(), bCell, bRow, bTab))
392     {
393         DimensionSelector aWhich;
394         if (bCell)
395         {
396             aWhich = lcl_GetCol;
397         }
398         else if (bRow)
399         {
400             aWhich = lcl_GetRow;
401         }
402         else if (bTab)
403         {
404             aWhich = lcl_GetTab;
405         }
406         else
407         {
408             OSL_FAIL( "lcl_checkRangeDimensions shouldn't allow that!");
409             aWhich = lcl_GetRow;    // initialize to avoid warning
410         }
411 
412         // Sort the references by start of range
413         std::sort(rReferences.begin(), rReferences.end(), LessByReference(rDoc, rPos, aWhich));
414         if (lcl_checkIfAdjacent(rDoc, rPos, rReferences, aWhich))
415         {
416             lcl_fillRangeFromRefList(rDoc, rPos, rReferences, rRange);
417             return true;
418         }
419     }
420     return false;
421 }
422 
lcl_isReference(const FormulaToken & rToken)423 bool lcl_isReference(const FormulaToken& rToken)
424 {
425     return
426         rToken.GetType() == svSingleRef ||
427         rToken.GetType() == svDoubleRef;
428 }
429 
adjustRangeName(formula::FormulaToken * pToken,ScDocument & rNewDoc,const ScDocument & rOldDoc,const ScAddress & rNewPos,const ScAddress & rOldPos,bool bGlobalNamesToLocal)430 void adjustRangeName(formula::FormulaToken* pToken, ScDocument& rNewDoc, const ScDocument& rOldDoc,
431         const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal)
432 {
433     ScRangeData* pRangeData = nullptr;
434     SCTAB nSheet = pToken->GetSheet();
435     sal_uInt16 nIndex = pToken->GetIndex();
436     if (!rOldDoc.CopyAdjustRangeName( nSheet, nIndex, pRangeData, rNewDoc, rNewPos, rOldPos, bGlobalNamesToLocal, true))
437         return; // nothing to do
438 
439     if (!pRangeData)
440     {
441         // If this happened we have a real problem.
442         pToken->SetIndex(0);
443         assert(!"inserting the range name should not fail");
444         return;
445     }
446 
447     pToken->SetIndex(nIndex);
448     pToken->SetSheet(nSheet);
449 }
450 
adjustDBRange(formula::FormulaToken * pToken,ScDocument & rNewDoc,const ScDocument & rOldDoc)451 void adjustDBRange(formula::FormulaToken* pToken, ScDocument& rNewDoc, const ScDocument& rOldDoc)
452 {
453     ScDBCollection* pOldDBCollection = rOldDoc.GetDBCollection();
454     if (!pOldDBCollection)
455         return;//strange error case, don't do anything
456     ScDBCollection::NamedDBs& aOldNamedDBs = pOldDBCollection->getNamedDBs();
457     ScDBData* pDBData = aOldNamedDBs.findByIndex(pToken->GetIndex());
458     if (!pDBData)
459         return; //invalid index
460     OUString aDBName = pDBData->GetUpperName();
461 
462     //search in new document
463     ScDBCollection* pNewDBCollection = rNewDoc.GetDBCollection();
464     if (!pNewDBCollection)
465     {
466         rNewDoc.SetDBCollection(std::unique_ptr<ScDBCollection>(new ScDBCollection(rNewDoc)));
467         pNewDBCollection = rNewDoc.GetDBCollection();
468     }
469     ScDBCollection::NamedDBs& aNewNamedDBs = pNewDBCollection->getNamedDBs();
470     ScDBData* pNewDBData = aNewNamedDBs.findByUpperName(aDBName);
471     if (!pNewDBData)
472     {
473         pNewDBData = new ScDBData(*pDBData);
474         bool ins = aNewNamedDBs.insert(std::unique_ptr<ScDBData>(pNewDBData));
475         assert(ins); (void)ins;
476     }
477     pToken->SetIndex(pNewDBData->GetIndex());
478 }
479 
480 }
481 
operator <(const AreaListenerKey & r) const482 bool AreaListenerKey::operator < ( const AreaListenerKey& r ) const
483 {
484     if (maRange.aStart.Tab() != r.maRange.aStart.Tab())
485         return maRange.aStart.Tab() < r.maRange.aStart.Tab();
486     if (maRange.aStart.Col() != r.maRange.aStart.Col())
487         return maRange.aStart.Col() < r.maRange.aStart.Col();
488     if (maRange.aStart.Row() != r.maRange.aStart.Row())
489         return maRange.aStart.Row() < r.maRange.aStart.Row();
490     if (maRange.aEnd.Tab() != r.maRange.aEnd.Tab())
491         return maRange.aEnd.Tab() < r.maRange.aEnd.Tab();
492     if (maRange.aEnd.Col() != r.maRange.aEnd.Col())
493         return maRange.aEnd.Col() < r.maRange.aEnd.Col();
494     if (maRange.aEnd.Row() != r.maRange.aEnd.Row())
495         return maRange.aEnd.Row() < r.maRange.aEnd.Row();
496     if (mbStartFixed != r.mbStartFixed)
497         return r.mbStartFixed;
498     if (mbEndFixed != r.mbEndFixed)
499         return r.mbEndFixed;
500 
501     return false;
502 }
503 
ScFormulaCellGroup()504 ScFormulaCellGroup::ScFormulaCellGroup() :
505     mnRefCount(0),
506     mpTopCell(nullptr),
507     mnLength(0),
508     mnWeight(0),
509     mnFormatType(SvNumFormatType::NUMBER),
510     mbInvariant(false),
511     mbSubTotal(false),
512     mbPartOfCycle(false),
513     meCalcState(sc::GroupCalcEnabled)
514 {
515 }
516 
~ScFormulaCellGroup()517 ScFormulaCellGroup::~ScFormulaCellGroup()
518 {
519 }
520 
setCode(const ScTokenArray & rCode)521 void ScFormulaCellGroup::setCode( const ScTokenArray& rCode )
522 {
523     mpCode = rCode.CloneValue();
524     mbInvariant = mpCode->IsInvariant();
525     mpCode->GenHash();
526 }
527 
compileCode(ScDocument & rDoc,const ScAddress & rPos,FormulaGrammar::Grammar eGram)528 void ScFormulaCellGroup::compileCode(
529     ScDocument& rDoc, const ScAddress& rPos, FormulaGrammar::Grammar eGram )
530 {
531     if (!mpCode)
532         return;
533 
534     if (mpCode->GetLen() && mpCode->GetCodeError() == FormulaError::NONE && !mpCode->GetCodeLen())
535     {
536         bool bMatrixFormula = mpTopCell->GetMatrixFlag() != ScMatrixMode::NONE;
537         ScCompiler aComp(rDoc, rPos, *mpCode, eGram, true, bMatrixFormula);
538         mbSubTotal = aComp.CompileTokenArray();
539         mnFormatType = aComp.GetNumFormatType();
540     }
541     else
542     {
543         mbSubTotal = mpCode->HasOpCodeRPN( ocSubTotal ) || mpCode->HasOpCodeRPN( ocAggregate );
544     }
545 }
546 
getAreaListener(ScFormulaCell ** ppTopCell,const ScRange & rRange,bool bStartFixed,bool bEndFixed)547 sc::FormulaGroupAreaListener* ScFormulaCellGroup::getAreaListener(
548     ScFormulaCell** ppTopCell, const ScRange& rRange, bool bStartFixed, bool bEndFixed )
549 {
550     AreaListenerKey aKey(rRange, bStartFixed, bEndFixed);
551 
552     AreaListenersType::iterator it = m_AreaListeners.lower_bound(aKey);
553     if (it == m_AreaListeners.end() || m_AreaListeners.key_comp()(aKey, it->first))
554     {
555         // Insert a new one.
556         it = m_AreaListeners.emplace_hint(
557              it, std::piecewise_construct,
558              std::forward_as_tuple(aKey),
559              std::forward_as_tuple(
560                 rRange, (*ppTopCell)->GetDocument(), (*ppTopCell)->aPos, mnLength, bStartFixed, bEndFixed));
561     }
562 
563     return &it->second;
564 }
565 
endAllGroupListening(ScDocument & rDoc)566 void ScFormulaCellGroup::endAllGroupListening( ScDocument& rDoc )
567 {
568     for (auto& rEntry : m_AreaListeners)
569     {
570         sc::FormulaGroupAreaListener& rListener = rEntry.second;
571         ScRange aListenRange = rListener.getListeningRange();
572         // This "always listen" special range is never grouped.
573         bool bGroupListening = (aListenRange != BCA_LISTEN_ALWAYS);
574         rDoc.EndListeningArea(aListenRange, bGroupListening, &rListener);
575     }
576 
577     m_AreaListeners.clear();
578 }
579 
ScFormulaCell(ScDocument & rDoc,const ScAddress & rPos)580 ScFormulaCell::ScFormulaCell( ScDocument& rDoc, const ScAddress& rPos ) :
581     bDirty(false),
582     bTableOpDirty(false),
583     bChanged(false),
584     bRunning(false),
585     bCompile(false),
586     bSubTotal(false),
587     bIsIterCell(false),
588     bInChangeTrack(false),
589     bNeedListening(false),
590     mbNeedsNumberFormat(false),
591     mbAllowNumberFormatChange(false),
592     mbPostponedDirty(false),
593     mbIsExtRef(false),
594     mbSeenInPath(false),
595     mbFreeFlying(false),
596     cMatrixFlag(ScMatrixMode::NONE),
597     nSeenInIteration(0),
598     nFormatType(SvNumFormatType::NUMBER),
599     eTempGrammar(formula::FormulaGrammar::GRAM_DEFAULT),
600     pCode(new ScTokenArray(rDoc)),
601     rDocument(rDoc),
602     pPrevious(nullptr),
603     pNext(nullptr),
604     pPreviousTrack(nullptr),
605     pNextTrack(nullptr),
606     aPos(rPos)
607 {
608 }
609 
ScFormulaCell(ScDocument & rDoc,const ScAddress & rPos,const OUString & rFormula,const FormulaGrammar::Grammar eGrammar,ScMatrixMode cMatInd)610 ScFormulaCell::ScFormulaCell( ScDocument& rDoc, const ScAddress& rPos,
611                               const OUString& rFormula,
612                               const FormulaGrammar::Grammar eGrammar,
613                               ScMatrixMode cMatInd ) :
614     bDirty( true ), // -> Because of the use of the Auto Pilot Function was: cMatInd != 0
615     bTableOpDirty( false ),
616     bChanged( false ),
617     bRunning( false ),
618     bCompile( false ),
619     bSubTotal( false ),
620     bIsIterCell( false ),
621     bInChangeTrack( false ),
622     bNeedListening( false ),
623     mbNeedsNumberFormat( false ),
624     mbAllowNumberFormatChange(false),
625     mbPostponedDirty(false),
626     mbIsExtRef(false),
627     mbSeenInPath(false),
628     mbFreeFlying(false),
629     cMatrixFlag ( cMatInd ),
630     nSeenInIteration(0),
631     nFormatType ( SvNumFormatType::NUMBER ),
632     eTempGrammar( eGrammar),
633     pCode( nullptr ),
634     rDocument( rDoc ),
635     pPrevious(nullptr),
636     pNext(nullptr),
637     pPreviousTrack(nullptr),
638     pNextTrack(nullptr),
639     aPos(rPos)
640 {
641     Compile( rFormula, true, eGrammar );    // bNoListening, Insert does that
642     if (!pCode)
643         // We need to have a non-NULL token array instance at all times.
644         pCode = new ScTokenArray(rDoc);
645 }
646 
ScFormulaCell(ScDocument & rDoc,const ScAddress & rPos,std::unique_ptr<ScTokenArray> pArray,const FormulaGrammar::Grammar eGrammar,ScMatrixMode cMatInd)647 ScFormulaCell::ScFormulaCell(
648     ScDocument& rDoc, const ScAddress& rPos, std::unique_ptr<ScTokenArray> pArray,
649     const FormulaGrammar::Grammar eGrammar, ScMatrixMode cMatInd ) :
650     bDirty( true ),
651     bTableOpDirty( false ),
652     bChanged( false ),
653     bRunning( false ),
654     bCompile( false ),
655     bSubTotal( false ),
656     bIsIterCell( false ),
657     bInChangeTrack( false ),
658     bNeedListening( false ),
659     mbNeedsNumberFormat( false ),
660     mbAllowNumberFormatChange(false),
661     mbPostponedDirty(false),
662     mbIsExtRef(false),
663     mbSeenInPath(false),
664     mbFreeFlying(false),
665     cMatrixFlag ( cMatInd ),
666     nSeenInIteration(0),
667     nFormatType ( SvNumFormatType::NUMBER ),
668     eTempGrammar( eGrammar),
669     pCode(pArray.release()),
670     rDocument( rDoc ),
671     pPrevious(nullptr),
672     pNext(nullptr),
673     pPreviousTrack(nullptr),
674     pNextTrack(nullptr),
675     aPos(rPos)
676 {
677     assert(pCode); // Never pass a NULL pointer here.
678 
679     pCode->Finalize(); // Reduce memory usage if needed.
680 
681     // Generate RPN token array.
682     if (pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen())
683     {
684         ScCompiler aComp(rDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE);
685         bSubTotal = aComp.CompileTokenArray();
686         nFormatType = aComp.GetNumFormatType();
687     }
688     else
689     {
690         if ( pCode->HasOpCodeRPN( ocSubTotal ) || pCode->HasOpCodeRPN( ocAggregate ) )
691             bSubTotal = true;
692     }
693 
694     if (bSubTotal)
695         rDocument.AddSubTotalCell(this);
696 
697     pCode->GenHash();
698 }
699 
ScFormulaCell(ScDocument & rDoc,const ScAddress & rPos,const ScTokenArray & rArray,const FormulaGrammar::Grammar eGrammar,ScMatrixMode cMatInd)700 ScFormulaCell::ScFormulaCell(
701     ScDocument& rDoc, const ScAddress& rPos, const ScTokenArray& rArray,
702     const FormulaGrammar::Grammar eGrammar, ScMatrixMode cMatInd ) :
703     bDirty( true ),
704     bTableOpDirty( false ),
705     bChanged( false ),
706     bRunning( false ),
707     bCompile( false ),
708     bSubTotal( false ),
709     bIsIterCell( false ),
710     bInChangeTrack( false ),
711     bNeedListening( false ),
712     mbNeedsNumberFormat( false ),
713     mbAllowNumberFormatChange(false),
714     mbPostponedDirty(false),
715     mbIsExtRef(false),
716     mbSeenInPath(false),
717     mbFreeFlying(false),
718     cMatrixFlag ( cMatInd ),
719     nSeenInIteration(0),
720     nFormatType ( SvNumFormatType::NUMBER ),
721     eTempGrammar( eGrammar),
722     pCode(new ScTokenArray(rArray)), // also implicitly does Finalize() on the array
723     rDocument( rDoc ),
724     pPrevious(nullptr),
725     pNext(nullptr),
726     pPreviousTrack(nullptr),
727     pNextTrack(nullptr),
728     aPos(rPos)
729 {
730     // RPN array generation
731     if( pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen() )
732     {
733         ScCompiler aComp( rDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE );
734         bSubTotal = aComp.CompileTokenArray();
735         nFormatType = aComp.GetNumFormatType();
736     }
737     else
738     {
739         if ( pCode->HasOpCodeRPN( ocSubTotal ) || pCode->HasOpCodeRPN( ocAggregate ) )
740             bSubTotal = true;
741     }
742 
743     if (bSubTotal)
744         rDocument.AddSubTotalCell(this);
745 
746     pCode->GenHash();
747 }
748 
ScFormulaCell(ScDocument & rDoc,const ScAddress & rPos,const ScFormulaCellGroupRef & xGroup,const FormulaGrammar::Grammar eGrammar,ScMatrixMode cInd)749 ScFormulaCell::ScFormulaCell(
750     ScDocument& rDoc, const ScAddress& rPos, const ScFormulaCellGroupRef& xGroup,
751     const FormulaGrammar::Grammar eGrammar, ScMatrixMode cInd ) :
752     mxGroup(xGroup),
753     bDirty(true),
754     bTableOpDirty( false ),
755     bChanged( false ),
756     bRunning( false ),
757     bCompile( false ),
758     bSubTotal(xGroup->mbSubTotal),
759     bIsIterCell( false ),
760     bInChangeTrack( false ),
761     bNeedListening( false ),
762     mbNeedsNumberFormat( false ),
763     mbAllowNumberFormatChange(false),
764     mbPostponedDirty(false),
765     mbIsExtRef(false),
766     mbSeenInPath(false),
767     mbFreeFlying(false),
768     cMatrixFlag ( cInd ),
769     nSeenInIteration(0),
770     nFormatType(xGroup->mnFormatType),
771     eTempGrammar( eGrammar),
772     pCode(xGroup->mpCode ? &*xGroup->mpCode : new ScTokenArray(rDoc)),
773     rDocument( rDoc ),
774     pPrevious(nullptr),
775     pNext(nullptr),
776     pPreviousTrack(nullptr),
777     pNextTrack(nullptr),
778     aPos(rPos)
779 {
780     if (bSubTotal)
781         rDocument.AddSubTotalCell(this);
782 }
783 
ScFormulaCell(const ScFormulaCell & rCell,ScDocument & rDoc,const ScAddress & rPos,ScCloneFlags nCloneFlags)784 ScFormulaCell::ScFormulaCell(const ScFormulaCell& rCell, ScDocument& rDoc, const ScAddress& rPos, ScCloneFlags nCloneFlags) :
785     bDirty( rCell.bDirty ),
786     bTableOpDirty( false ),
787     bChanged( rCell.bChanged ),
788     bRunning( false ),
789     bCompile( rCell.bCompile ),
790     bSubTotal( rCell.bSubTotal ),
791     bIsIterCell( false ),
792     bInChangeTrack( false ),
793     bNeedListening( false ),
794     mbNeedsNumberFormat( rCell.mbNeedsNumberFormat ),
795     mbAllowNumberFormatChange(false),
796     mbPostponedDirty(false),
797     mbIsExtRef(false),
798     mbSeenInPath(false),
799     mbFreeFlying(false),
800     cMatrixFlag ( rCell.cMatrixFlag ),
801     nSeenInIteration(0),
802     nFormatType( rCell.nFormatType ),
803     aResult( rCell.aResult ),
804     eTempGrammar( rCell.eTempGrammar),
805     rDocument( rDoc ),
806     pPrevious(nullptr),
807     pNext(nullptr),
808     pPreviousTrack(nullptr),
809     pNextTrack(nullptr),
810     aPos(rPos)
811 {
812     pCode = rCell.pCode->Clone().release();
813 
814     //  set back any errors and recompile
815     //  not in the Clipboard - it must keep the received error flag
816     //  Special Length=0: as bad cells are generated, then they are also retained
817     if ( pCode->GetCodeError() != FormulaError::NONE && !rDocument.IsClipboard() && pCode->GetLen() )
818     {
819         pCode->SetCodeError( FormulaError::NONE );
820         bCompile = true;
821     }
822     // Compile ColRowNames on URM_MOVE/URM_COPY _after_ UpdateReference !
823     bool bCompileLater = false;
824     bool bClipMode = rCell.rDocument.IsClipboard();
825 
826     //update ScNameTokens
827     if (!rDocument.IsClipOrUndo() || rDoc.IsUndo())
828     {
829         if (!rDocument.IsClipboardSource() || aPos.Tab() != rCell.aPos.Tab())
830         {
831             bool bGlobalNamesToLocal = ((nCloneFlags & ScCloneFlags::NamesToLocal) != ScCloneFlags::Default);
832             formula::FormulaToken* pToken = nullptr;
833             formula::FormulaTokenArrayPlainIterator aIter(*pCode);
834             while((pToken = aIter.GetNextName())!= nullptr)
835             {
836                 OpCode eOpCode = pToken->GetOpCode();
837                 if (eOpCode == ocName)
838                     adjustRangeName(pToken, rDoc, rCell.rDocument, aPos, rCell.aPos, bGlobalNamesToLocal);
839                 else if (eOpCode == ocDBArea || eOpCode == ocTableRef)
840                     adjustDBRange(pToken, rDoc, rCell.rDocument);
841             }
842         }
843 
844         bool bCopyBetweenDocs = rDocument.GetPool() != rCell.rDocument.GetPool();
845         if (bCopyBetweenDocs && !(nCloneFlags & ScCloneFlags::NoMakeAbsExternal))
846         {
847             pCode->ReadjustAbsolute3DReferences(rCell.rDocument, rDoc, rCell.aPos);
848         }
849 
850         pCode->AdjustAbsoluteRefs( rCell.rDocument, rCell.aPos, aPos, bCopyBetweenDocs );
851     }
852 
853     if (!rDocument.IsClipOrUndo())
854     {
855         if (&rDocument.GetSharedStringPool() != &rCell.rDocument.GetSharedStringPool())
856             pCode->ReinternStrings( rDocument.GetSharedStringPool());
857         pCode->AdjustReferenceOnCopy( aPos);
858     }
859 
860     if( !bCompile )
861     {   // Name references with references and ColRowNames
862         formula::FormulaTokenArrayPlainIterator aIter(*pCode);
863         for (;;)
864         {
865             formula::FormulaToken* t = aIter.GetNextReferenceOrName();
866             if (!t || bCompile)
867                 break;
868             if ( t->IsExternalRef() )
869             {
870                 // External name, cell, and area references.
871                 bCompile = true;
872             }
873             else if ( t->GetType() == svIndex )
874             {
875                 const ScRangeData* pRangeData = rDoc.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
876                 if( pRangeData )
877                 {
878                     if( pRangeData->HasReferences() )
879                         bCompile = true;
880                 }
881                 else
882                     bCompile = true;    // invalid reference!
883             }
884             else if ( t->GetOpCode() == ocColRowName )
885             {
886                 bCompile = true;        // new lookup needed
887                 bCompileLater = bClipMode;
888             }
889         }
890     }
891     if( bCompile )
892     {
893         if ( !bCompileLater && bClipMode )
894         {
895             // Merging ranges needs the actual positions after UpdateReference.
896             // ColRowNames and TableRefs need new lookup after positions are
897             // adjusted.
898             bCompileLater = pCode->HasOpCode( ocRange) || pCode->HasOpCode( ocColRowName) ||
899                 pCode->HasOpCode( ocTableRef);
900         }
901         if ( !bCompileLater )
902         {
903             // bNoListening, not at all if in Clipboard/Undo,
904             // and not from Clipboard either, instead after Insert(Clone) and UpdateReference.
905             CompileTokenArray( true );
906         }
907     }
908 
909     if( nCloneFlags & ScCloneFlags::StartListening )
910         StartListeningTo( rDoc );
911 
912     if (bSubTotal)
913         rDocument.AddSubTotalCell(this);
914 }
915 
~ScFormulaCell()916 ScFormulaCell::~ScFormulaCell()
917 {
918     rDocument.RemoveFromFormulaTrack( this );
919     rDocument.RemoveFromFormulaTree( this );
920     rDocument.RemoveSubTotalCell(this);
921     if (pCode->HasOpCode(ocMacro))
922         rDocument.GetMacroManager()->RemoveDependentCell(this);
923 
924     if (rDocument.HasExternalRefManager())
925         rDocument.GetExternalRefManager()->removeRefCell(this);
926 
927     if (!mxGroup || !mxGroup->mpCode)
928         // Formula token is not shared.
929         delete pCode;
930 
931     if (mxGroup && mxGroup->mpTopCell == this)
932         mxGroup->mpTopCell = nullptr;
933 }
934 
Clone() const935 ScFormulaCell* ScFormulaCell::Clone() const
936 {
937     return new ScFormulaCell(*this, rDocument, aPos);
938 }
939 
Clone(const ScAddress & rPos) const940 ScFormulaCell* ScFormulaCell::Clone( const ScAddress& rPos ) const
941 {
942     return new ScFormulaCell(*this, rDocument, rPos, ScCloneFlags::Default);
943 }
944 
GetHash() const945 size_t ScFormulaCell::GetHash() const
946 {
947     return pCode->GetHash();
948 }
949 
GetFormula(const FormulaGrammar::Grammar eGrammar,ScInterpreterContext * pContext) const950 OUString ScFormulaCell::GetFormula( const FormulaGrammar::Grammar eGrammar, ScInterpreterContext* pContext ) const
951 {
952     if( pCode->GetCodeError() != FormulaError::NONE && !pCode->GetLen() )
953     {
954         return ScGlobal::GetErrorString(pCode->GetCodeError());
955     }
956     OUStringBuffer buffer;
957     if( cMatrixFlag == ScMatrixMode::Reference )
958     {
959         // Reference to another cell that contains a matrix formula.
960         formula::FormulaTokenArrayPlainIterator aIter(*pCode);
961         formula::FormulaToken* p = aIter.GetNextReferenceRPN();
962         if( p )
963         {
964             /* FIXME: original GetFormula() code obtained
965              * pCell only if (!IsInChangeTrack()),
966              * GetEnglishFormula() omitted that test.
967              * Can we live without in all cases? */
968             ScFormulaCell* pCell = nullptr;
969             ScSingleRefData& rRef = *p->GetSingleRef();
970             ScAddress aAbs = rRef.toAbs(rDocument, aPos);
971             if (rDocument.ValidAddress(aAbs))
972                 pCell = rDocument.GetFormulaCell(aAbs);
973 
974             if (pCell)
975             {
976                 return pCell->GetFormula( eGrammar, pContext );
977             }
978             else
979             {
980                 ScCompiler aComp( rDocument, aPos, *pCode, eGrammar, false, false, pContext );
981                 aComp.CreateStringFromTokenArray( buffer );
982             }
983         }
984         else
985         {
986             OSL_FAIL("ScFormulaCell::GetFormula: not a matrix");
987         }
988     }
989     else
990     {
991         ScCompiler aComp( rDocument, aPos, *pCode, eGrammar, false, false, pContext );
992         aComp.CreateStringFromTokenArray( buffer );
993     }
994 
995     buffer.insert( 0, '=');
996     if( cMatrixFlag != ScMatrixMode::NONE )
997     {
998         buffer.insert( 0, '{');
999         buffer.append( '}');
1000     }
1001     return buffer.makeStringAndClear();
1002 }
1003 
GetFormula(sc::CompileFormulaContext & rCxt,ScInterpreterContext * pContext) const1004 OUString ScFormulaCell::GetFormula( sc::CompileFormulaContext& rCxt, ScInterpreterContext* pContext ) const
1005 {
1006     OUStringBuffer aBuf;
1007     if (pCode->GetCodeError() != FormulaError::NONE && !pCode->GetLen())
1008     {
1009         ScTokenArray aCode(rCxt.getDoc());
1010         aCode.AddToken( FormulaErrorToken( pCode->GetCodeError()));
1011         ScCompiler aComp(rCxt, aPos, aCode, false, false, pContext);
1012         aComp.CreateStringFromTokenArray(aBuf);
1013         return aBuf.makeStringAndClear();
1014     }
1015     else if( cMatrixFlag == ScMatrixMode::Reference )
1016     {
1017         // Reference to another cell that contains a matrix formula.
1018         formula::FormulaTokenArrayPlainIterator aIter(*pCode);
1019         formula::FormulaToken* p = aIter.GetNextReferenceRPN();
1020         if( p )
1021         {
1022             /* FIXME: original GetFormula() code obtained
1023              * pCell only if (!IsInChangeTrack()),
1024              * GetEnglishFormula() omitted that test.
1025              * Can we live without in all cases? */
1026             ScFormulaCell* pCell = nullptr;
1027             ScSingleRefData& rRef = *p->GetSingleRef();
1028             ScAddress aAbs = rRef.toAbs(rDocument, aPos);
1029             if (rDocument.ValidAddress(aAbs))
1030                 pCell = rDocument.GetFormulaCell(aAbs);
1031 
1032             if (pCell)
1033             {
1034                 return pCell->GetFormula(rCxt);
1035             }
1036             else
1037             {
1038                 ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext);
1039                 aComp.CreateStringFromTokenArray(aBuf);
1040             }
1041         }
1042         else
1043         {
1044             OSL_FAIL("ScFormulaCell::GetFormula: not a matrix");
1045         }
1046     }
1047     else
1048     {
1049         ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext);
1050         aComp.CreateStringFromTokenArray(aBuf);
1051     }
1052 
1053     aBuf.insert( 0, '=');
1054     if( cMatrixFlag != ScMatrixMode::NONE )
1055     {
1056         aBuf.insert( 0, '{');
1057         aBuf.append( '}');
1058     }
1059 
1060     return aBuf.makeStringAndClear();
1061 }
1062 
GetResultDimensions(SCSIZE & rCols,SCSIZE & rRows)1063 void ScFormulaCell::GetResultDimensions( SCSIZE& rCols, SCSIZE& rRows )
1064 {
1065     MaybeInterpret();
1066 
1067     if (pCode->GetCodeError() == FormulaError::NONE && aResult.GetType() == svMatrixCell)
1068     {
1069         const ScMatrix* pMat = aResult.GetToken()->GetMatrix();
1070         if (pMat)
1071         {
1072             pMat->GetDimensions( rCols, rRows );
1073             if (pCode->IsHyperLink())
1074             {
1075                 // Row 2 element is the URL that is not to be displayed and the
1076                 // result dimension not to be extended.
1077                 assert(rRows == 2);
1078                 rRows = 1;
1079             }
1080             return;
1081         }
1082     }
1083     rCols = 0;
1084     rRows = 0;
1085 }
1086 
ResetDirty()1087 void ScFormulaCell::ResetDirty() { bDirty = bTableOpDirty = mbPostponedDirty = false; }
SetNeedsListening(bool bVar)1088 void ScFormulaCell::SetNeedsListening( bool bVar ) { bNeedListening = bVar; }
1089 
SetNeedsDirty(bool bVar)1090 void ScFormulaCell::SetNeedsDirty( bool bVar )
1091 {
1092     mbPostponedDirty = bVar;
1093 }
1094 
SetNeedNumberFormat(bool bVal)1095 void ScFormulaCell::SetNeedNumberFormat( bool bVal )
1096 {
1097     mbNeedsNumberFormat = mbAllowNumberFormatChange = bVal;
1098 }
1099 
Compile(const OUString & rFormula,bool bNoListening,const FormulaGrammar::Grammar eGrammar)1100 void ScFormulaCell::Compile( const OUString& rFormula, bool bNoListening,
1101                             const FormulaGrammar::Grammar eGrammar )
1102 {
1103     if ( rDocument.IsClipOrUndo() )
1104         return;
1105     bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
1106     if ( bWasInFormulaTree )
1107         rDocument.RemoveFromFormulaTree( this );
1108     // pCode may not deleted for queries, but must be empty
1109     if ( pCode )
1110         pCode->Clear();
1111     ScTokenArray* pCodeOld = pCode;
1112     ScCompiler aComp( rDocument, aPos, eGrammar);
1113     pCode = aComp.CompileString( rFormula ).release();
1114     assert(!mxGroup);
1115     delete pCodeOld;
1116     if( pCode->GetCodeError() == FormulaError::NONE )
1117     {
1118         if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() && rFormula == aResult.GetHybridFormula() )
1119         {   // not recursive CompileTokenArray/Compile/CompileTokenArray
1120             if ( rFormula[0] == '=' )
1121                 pCode->AddBad( rFormula.copy(1) );
1122             else
1123                 pCode->AddBad( rFormula );
1124         }
1125         bCompile = true;
1126         CompileTokenArray( bNoListening );
1127     }
1128     else
1129         bChanged = true;
1130 
1131     if ( bWasInFormulaTree )
1132         rDocument.PutInFormulaTree( this );
1133 }
1134 
Compile(sc::CompileFormulaContext & rCxt,const OUString & rFormula,bool bNoListening)1135 void ScFormulaCell::Compile(
1136     sc::CompileFormulaContext& rCxt, const OUString& rFormula, bool bNoListening )
1137 {
1138     if ( rDocument.IsClipOrUndo() )
1139         return;
1140     bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
1141     if ( bWasInFormulaTree )
1142         rDocument.RemoveFromFormulaTree( this );
1143     // pCode may not deleted for queries, but must be empty
1144     if ( pCode )
1145         pCode->Clear();
1146     ScTokenArray* pCodeOld = pCode;
1147     ScCompiler aComp(rCxt, aPos);
1148     pCode = aComp.CompileString( rFormula ).release();
1149     assert(!mxGroup);
1150     delete pCodeOld;
1151     if( pCode->GetCodeError() == FormulaError::NONE )
1152     {
1153         if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() && rFormula == aResult.GetHybridFormula() )
1154         {   // not recursive CompileTokenArray/Compile/CompileTokenArray
1155             if ( rFormula[0] == '=' )
1156                 pCode->AddBad( rFormula.copy(1) );
1157             else
1158                 pCode->AddBad( rFormula );
1159         }
1160         bCompile = true;
1161         CompileTokenArray(rCxt, bNoListening);
1162     }
1163     else
1164         bChanged = true;
1165 
1166     if ( bWasInFormulaTree )
1167         rDocument.PutInFormulaTree( this );
1168 }
1169 
CompileTokenArray(bool bNoListening)1170 void ScFormulaCell::CompileTokenArray( bool bNoListening )
1171 {
1172     // Not already compiled?
1173     if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
1174     {
1175         Compile( aResult.GetHybridFormula(), bNoListening, eTempGrammar);
1176     }
1177     else if( bCompile && !rDocument.IsClipOrUndo() && pCode->GetCodeError() == FormulaError::NONE )
1178     {
1179         // RPN length may get changed
1180         bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
1181         if ( bWasInFormulaTree )
1182             rDocument.RemoveFromFormulaTree( this );
1183 
1184         // Loading from within filter? No listening yet!
1185         if( rDocument.IsInsertingFromOtherDoc() )
1186             bNoListening = true;
1187 
1188         if( !bNoListening && pCode->GetCodeLen() )
1189             EndListeningTo( rDocument );
1190         ScCompiler aComp(rDocument, aPos, *pCode, rDocument.GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE);
1191         bSubTotal = aComp.CompileTokenArray();
1192         if( pCode->GetCodeError() == FormulaError::NONE )
1193         {
1194             nFormatType = aComp.GetNumFormatType();
1195             bChanged = true;
1196             aResult.SetToken( nullptr);
1197             bCompile = false;
1198             if ( !bNoListening )
1199                 StartListeningTo( rDocument );
1200         }
1201         if ( bWasInFormulaTree )
1202             rDocument.PutInFormulaTree( this );
1203 
1204         if (bSubTotal)
1205             rDocument.AddSubTotalCell(this);
1206     }
1207 }
1208 
CompileTokenArray(sc::CompileFormulaContext & rCxt,bool bNoListening)1209 void ScFormulaCell::CompileTokenArray( sc::CompileFormulaContext& rCxt, bool bNoListening )
1210 {
1211     // Not already compiled?
1212     if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
1213     {
1214         rCxt.setGrammar(eTempGrammar);
1215         Compile(rCxt, aResult.GetHybridFormula(), bNoListening);
1216     }
1217     else if( bCompile && !rDocument.IsClipOrUndo() && pCode->GetCodeError() == FormulaError::NONE)
1218     {
1219         // RPN length may get changed
1220         bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
1221         if ( bWasInFormulaTree )
1222             rDocument.RemoveFromFormulaTree( this );
1223 
1224         // Loading from within filter? No listening yet!
1225         if( rDocument.IsInsertingFromOtherDoc() )
1226             bNoListening = true;
1227 
1228         if( !bNoListening && pCode->GetCodeLen() )
1229             EndListeningTo( rDocument );
1230         ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
1231         bSubTotal = aComp.CompileTokenArray();
1232         if( pCode->GetCodeError() == FormulaError::NONE )
1233         {
1234             nFormatType = aComp.GetNumFormatType();
1235             bChanged = true;
1236             aResult.SetToken( nullptr);
1237             bCompile = false;
1238             if ( !bNoListening )
1239                 StartListeningTo( rDocument );
1240         }
1241         if ( bWasInFormulaTree )
1242             rDocument.PutInFormulaTree( this );
1243 
1244         if (bSubTotal)
1245             rDocument.AddSubTotalCell(this);
1246     }
1247 }
1248 
CompileXML(sc::CompileFormulaContext & rCxt,ScProgress & rProgress)1249 void ScFormulaCell::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress )
1250 {
1251     if ( cMatrixFlag == ScMatrixMode::Reference )
1252     {   // is already token code via ScDocFunc::EnterMatrix, ScDocument::InsertMatrixFormula
1253         // just establish listeners
1254         StartListeningTo( rDocument );
1255         return ;
1256     }
1257 
1258     // Error constant formula cell stays as is.
1259     if (!pCode->GetLen() && pCode->GetCodeError() != FormulaError::NONE)
1260         return;
1261 
1262     // Compilation changes RPN count, remove and reinsert to FormulaTree if it
1263     // was in to update its count.
1264     bool bWasInFormulaTree = rDocument.IsInFormulaTree( this);
1265     if (bWasInFormulaTree)
1266         rDocument.RemoveFromFormulaTree( this);
1267     rCxt.setGrammar(eTempGrammar);
1268     ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
1269     OUString aFormula, aFormulaNmsp;
1270     aComp.CreateStringFromXMLTokenArray( aFormula, aFormulaNmsp );
1271     rDocument.DecXMLImportedFormulaCount( aFormula.getLength() );
1272     rProgress.SetStateCountDownOnPercent( rDocument.GetXMLImportedFormulaCount() );
1273     // pCode may not deleted for queries, but must be empty
1274     pCode->Clear();
1275 
1276     bool bDoCompile = true;
1277 
1278     if ( !mxGroup && aFormulaNmsp.isEmpty() ) // optimization
1279     {
1280         ScAddress aPreviousCell( aPos );
1281         aPreviousCell.IncRow( -1 );
1282         ScFormulaCell *pPreviousCell = rDocument.GetFormulaCell( aPreviousCell );
1283         if (pPreviousCell && pPreviousCell->GetCode()->IsShareable())
1284         {
1285             // Build formula string using the tokens from the previous cell,
1286             // but use the current cell position.
1287             ScCompiler aBackComp( rCxt, aPos, *(pPreviousCell->pCode) );
1288             OUStringBuffer aShouldBeBuf;
1289             aBackComp.CreateStringFromTokenArray( aShouldBeBuf );
1290 
1291             // The initial '=' is optional in ODFF.
1292             const sal_Int32 nLeadingEqual = (aFormula.getLength() > 0 && aFormula[0] == '=') ? 1 : 0;
1293             if (aFormula.getLength() == aShouldBeBuf.getLength() + nLeadingEqual &&
1294                     aFormula.match( aShouldBeBuf, nLeadingEqual))
1295             {
1296                 // Put them in the same formula group.
1297                 ScFormulaCellGroupRef xGroup = pPreviousCell->GetCellGroup();
1298                 if (!xGroup) // Last cell is not grouped yet. Start a new group.
1299                     xGroup = pPreviousCell->CreateCellGroup(1, false);
1300                 ++xGroup->mnLength;
1301                 SetCellGroup( xGroup );
1302 
1303                 // Do setup here based on previous cell.
1304 
1305                 nFormatType = pPreviousCell->nFormatType;
1306                 bSubTotal = pPreviousCell->bSubTotal;
1307                 bChanged = true;
1308                 bCompile = false;
1309 
1310                 if (bSubTotal)
1311                     rDocument.AddSubTotalCell(this);
1312 
1313                 bDoCompile = false;
1314                 pCode = pPreviousCell->pCode;
1315                 if (pPreviousCell->mbIsExtRef)
1316                     rDocument.GetExternalRefManager()->insertRefCellFromTemplate( pPreviousCell, this );
1317             }
1318         }
1319     }
1320 
1321     if (bDoCompile)
1322     {
1323         ScTokenArray* pCodeOld = pCode;
1324         pCode = aComp.CompileString( aFormula, aFormulaNmsp ).release();
1325         assert(!mxGroup);
1326         delete pCodeOld;
1327 
1328         if( pCode->GetCodeError() == FormulaError::NONE )
1329         {
1330             if ( !pCode->GetLen() )
1331             {
1332                 if ( !aFormula.isEmpty() && aFormula[0] == '=' )
1333                     pCode->AddBad( aFormula.copy( 1 ) );
1334                 else
1335                     pCode->AddBad( aFormula );
1336             }
1337             bSubTotal = aComp.CompileTokenArray();
1338             if( pCode->GetCodeError() == FormulaError::NONE )
1339             {
1340                 nFormatType = aComp.GetNumFormatType();
1341                 bChanged = true;
1342                 bCompile = false;
1343             }
1344 
1345             if (bSubTotal)
1346                 rDocument.AddSubTotalCell(this);
1347         }
1348         else
1349             bChanged = true;
1350     }
1351 
1352     //  After loading, it must be known if ocDde/ocWebservice is in any formula
1353     //  (for external links warning, CompileXML is called at the end of loading XML file)
1354     rDocument.CheckLinkFormulaNeedingCheck(*pCode);
1355 
1356     //volatile cells must be added here for import
1357     if( !pCode->IsRecalcModeNormal() || pCode->IsRecalcModeForced())
1358     {
1359         // During load, only those cells that are marked explicitly dirty get
1360         // recalculated.  So we need to set it dirty here.
1361         SetDirtyVar();
1362         rDocument.AppendToFormulaTrack(this);
1363         // Do not call TrackFormulas() here, not all listeners may have been
1364         // established, postponed until ScDocument::CompileXML() finishes.
1365     }
1366     else if (bWasInFormulaTree)
1367         rDocument.PutInFormulaTree(this);
1368 }
1369 
CalcAfterLoad(sc::CompileFormulaContext & rCxt,bool bStartListening)1370 void ScFormulaCell::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening )
1371 {
1372     bool bNewCompiled = false;
1373     // If a Calc 1.0-doc is read, we have a result, but no token array
1374     if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
1375     {
1376         rCxt.setGrammar(eTempGrammar);
1377         Compile(rCxt, aResult.GetHybridFormula(), true);
1378         aResult.SetToken( nullptr);
1379         bDirty = true;
1380         bNewCompiled = true;
1381     }
1382     // The RPN array is not created when a Calc 3.0-Doc has been read as the Range Names exist until now.
1383     if( pCode->GetLen() && !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE )
1384     {
1385         ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
1386         bSubTotal = aComp.CompileTokenArray();
1387         nFormatType = aComp.GetNumFormatType();
1388         bDirty = true;
1389         bCompile = false;
1390         bNewCompiled = true;
1391 
1392         if (bSubTotal)
1393             rDocument.AddSubTotalCell(this);
1394     }
1395 
1396     // On OS/2 with broken FPU exception, we can somehow store /0 without Err503. Later on in
1397     // the BLC Lib NumberFormatter crashes when doing a fabs (NAN) (# 32739 #).
1398     // We iron this out here for all systems, such that we also have an Err503 here.
1399     if ( aResult.IsValue() && !std::isfinite( aResult.GetDouble() ) )
1400     {
1401         OSL_FAIL("Formula cell INFINITY!!! Where does this document come from?");
1402         aResult.SetResultError( FormulaError::IllegalFPOperation );
1403         bDirty = true;
1404     }
1405 
1406     // DoubleRefs for binary operators were always a Matrix before version v5.0.
1407     // Now this is only the case when in an array formula, otherwise it's an implicit intersection
1408     if ( ScDocument::GetSrcVersion() < SC_MATRIX_DOUBLEREF &&
1409             GetMatrixFlag() == ScMatrixMode::NONE && pCode->HasMatrixDoubleRefOps() )
1410     {
1411         cMatrixFlag = ScMatrixMode::Formula;
1412         SetMatColsRows( 1, 1);
1413     }
1414 
1415     // Do the cells need to be calculated? After Load cells can contain an error code, and then start
1416     // the listener and Recalculate (if needed) if not ScRecalcMode::NORMAL
1417     if( !bNewCompiled || pCode->GetCodeError() == FormulaError::NONE )
1418     {
1419         if (bStartListening)
1420             StartListeningTo(rDocument);
1421 
1422         if( !pCode->IsRecalcModeNormal() )
1423             bDirty = true;
1424     }
1425     if ( pCode->IsRecalcModeAlways() )
1426     {   // random(), today(), now() always stay in the FormulaTree, so that they are calculated
1427         // for each F9
1428         bDirty = true;
1429     }
1430     // No SetDirty yet, as no all Listeners are known yet (only in SetDirtyAfterLoad)
1431 }
1432 
MarkUsedExternalReferences()1433 bool ScFormulaCell::MarkUsedExternalReferences()
1434 {
1435     return pCode && rDocument.MarkUsedExternalReferences(*pCode, aPos);
1436 }
1437 
1438 namespace {
1439 class RecursionCounter
1440 {
1441     ScRecursionHelper&  rRec;
1442     bool                bStackedInIteration;
1443 #if defined DBG_UTIL && !defined NDEBUG
1444     const ScFormulaCell* cell;
1445 #endif
1446 public:
RecursionCounter(ScRecursionHelper & r,ScFormulaCell * p)1447     RecursionCounter( ScRecursionHelper& r, ScFormulaCell* p )
1448         : rRec(r)
1449 #if defined DBG_UTIL && !defined NDEBUG
1450         , cell(p)
1451 #endif
1452     {
1453         bStackedInIteration = rRec.IsDoingIteration();
1454         if (bStackedInIteration)
1455             rRec.GetRecursionInIterationStack().push( p);
1456         rRec.IncRecursionCount();
1457     }
~RecursionCounter()1458     ~RecursionCounter()
1459     {
1460         rRec.DecRecursionCount();
1461         if (bStackedInIteration)
1462         {
1463 #if defined DBG_UTIL && !defined NDEBUG
1464             assert(rRec.GetRecursionInIterationStack().top() == cell);
1465 #endif
1466             rRec.GetRecursionInIterationStack().pop();
1467         }
1468     }
1469 };
1470 
1471 // Forced calculation: OpenCL and threads require formula groups, so force even single cells to be a "group".
1472 // Remove the group again at the end, since there are some places throughout the code
1473 // that do not handle well groups with just 1 cell. Remove the groups only when the recursion level
1474 // reaches 0 again (groups contain some info such as disabling threading because of cycles, so removing
1475 // a group immediately would remove the info), for this reason affected cells are stored in the recursion
1476 // helper.
1477 struct TemporaryCellGroupMaker
1478 {
TemporaryCellGroupMaker__anonb316ae920211::TemporaryCellGroupMaker1479     TemporaryCellGroupMaker( ScFormulaCell* cell, bool enable )
1480         : mCell( cell )
1481         , mEnabled( enable )
1482     {
1483         if( mEnabled && mCell->GetCellGroup() == nullptr )
1484         {
1485             mCell->CreateCellGroup( 1, false );
1486             mCell->GetDocument().GetRecursionHelper().AddTemporaryGroupCell( mCell );
1487         }
1488     }
~TemporaryCellGroupMaker__anonb316ae920211::TemporaryCellGroupMaker1489     ~TemporaryCellGroupMaker() COVERITY_NOEXCEPT_FALSE
1490     {
1491         if( mEnabled )
1492             mCell->GetDocument().GetRecursionHelper().CleanTemporaryGroupCells();
1493     }
1494     ScFormulaCell* mCell;
1495     const bool mEnabled;
1496 };
1497 
1498 } // namespace
1499 
Interpret(SCROW nStartOffset,SCROW nEndOffset)1500 bool ScFormulaCell::Interpret(SCROW nStartOffset, SCROW nEndOffset)
1501 {
1502     ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
1503     bool bGroupInterpreted = false;
1504 
1505     // The result would possibly depend on a cell without a valid value, bail out
1506     // the entire dependency computation.
1507     if (rRecursionHelper.IsAbortingDependencyComputation())
1508         return false;
1509 
1510     if ((mxGroup && !rRecursionHelper.CheckFGIndependence(mxGroup.get())) || !rRecursionHelper.AreGroupsIndependent())
1511         return bGroupInterpreted;
1512 
1513     static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType();
1514     TemporaryCellGroupMaker cellGroupMaker( this, forceType != ForceCalculationNone && forceType != ForceCalculationCore );
1515 
1516     ScFormulaCell* pTopCell = mxGroup ? mxGroup->mpTopCell : this;
1517 
1518     if (pTopCell->mbSeenInPath && rRecursionHelper.GetDepComputeLevel() &&
1519         rRecursionHelper.AnyCycleMemberInDependencyEvalMode(pTopCell))
1520     {
1521         // This call arose from a dependency calculation and we just found a cycle.
1522         // This will mark all elements in the cycle as parts-of-cycle.
1523         ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pTopCell);
1524         // Reaching here does not necessarily mean a circular reference, so don't set Err:522 here yet.
1525         // If there is a genuine circular reference, it will be marked so when all groups
1526         // in the cycle get out of dependency evaluation mode.
1527         // But returning without calculation a new value means other cells depending
1528         // on this one would use a possibly invalid value, so ensure the dependency
1529         // computation is aborted without resetting the dirty flag of any cell.
1530         rRecursionHelper.AbortDependencyComputation();
1531         return bGroupInterpreted;
1532     }
1533 
1534 #if DEBUG_CALCULATION
1535     static bool bDebugCalculationInit = true;
1536     if (bDebugCalculationInit)
1537     {
1538         aDC.maTrigger = aDebugCalculationTriggerAddress;
1539         aDC.mbPrintResults = true;
1540         bDebugCalculationInit = false;
1541     }
1542     DebugCalculationStacker aDebugEntry(aPos, rDocument);
1543 #endif
1544 
1545     if (!IsDirtyOrInTableOpDirty() || rRecursionHelper.IsInReturn())
1546         return bGroupInterpreted;     // no double/triple processing
1547 
1548     //FIXME:
1549     //  If the call originates from a Reschedule in DdeLink update, leave dirty
1550     //  Better: Do a Dde Link Update without Reschedule or do it completely asynchronously!
1551     if ( rDocument.IsInDdeLinkUpdate() )
1552         return bGroupInterpreted;
1553 
1554     if (bRunning)
1555     {
1556         if (!rDocument.GetDocOptions().IsIter())
1557         {
1558             aResult.SetResultError( FormulaError::CircularReference );
1559             return bGroupInterpreted;
1560         }
1561 
1562         if (aResult.GetResultError() == FormulaError::CircularReference)
1563             aResult.SetResultError( FormulaError::NONE );
1564 
1565         // Start or add to iteration list.
1566         if (!rRecursionHelper.IsDoingIteration() ||
1567                 !rRecursionHelper.GetRecursionInIterationStack().top()->bIsIterCell)
1568             rRecursionHelper.SetInIterationReturn( true);
1569 
1570         return bGroupInterpreted;
1571     }
1572     // no multiple interprets for GetErrCode, IsValue, GetValue and
1573     // different entry point recursions. Would also lead to premature
1574     // convergence in iterations.
1575     if (rRecursionHelper.GetIteration() && nSeenInIteration ==
1576             rRecursionHelper.GetIteration())
1577         return bGroupInterpreted;
1578 
1579     bool bOldRunning = bRunning;
1580     if (rRecursionHelper.GetRecursionCount() > MAXRECURSION)
1581     {
1582         bRunning = true;
1583         rRecursionHelper.SetInRecursionReturn( true);
1584     }
1585     else
1586     {
1587         rDocument.IncInterpretLevel();
1588 
1589 #if DEBUG_CALCULATION
1590         aDC.enterGroup();
1591 #endif
1592         bool bPartOfCycleBefore = mxGroup && mxGroup->mbPartOfCycle;
1593         bGroupInterpreted = InterpretFormulaGroup(nStartOffset, nEndOffset);
1594         bool bPartOfCycleAfter = mxGroup && mxGroup->mbPartOfCycle;
1595 
1596 #if DEBUG_CALCULATION
1597         aDC.leaveGroup();
1598 #endif
1599         if (!bGroupInterpreted)
1600         {
1601             // This call resulted from a dependency calculation for a multigroup-threading attempt,
1602             // but found dependency among the groups.
1603             if (!rRecursionHelper.AreGroupsIndependent())
1604             {
1605                 rDocument.DecInterpretLevel();
1606                 return bGroupInterpreted;
1607             }
1608             // Dependency calc inside InterpretFormulaGroup() failed due to
1609             // detection of a cycle and there are parent FG's in the cycle.
1610             // Skip InterpretTail() in such cases, only run InterpretTail for the "cycle-starting" FG
1611             if (!bPartOfCycleBefore && bPartOfCycleAfter && rRecursionHelper.AnyParentFGInCycle())
1612             {
1613                 rDocument.DecInterpretLevel();
1614                 return bGroupInterpreted;
1615             }
1616 
1617             ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this);
1618             ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
1619             InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_NORMAL);
1620         }
1621 
1622         rDocument.DecInterpretLevel();
1623     }
1624 
1625     // While leaving a recursion or iteration stack, insert its cells to the
1626     // recursion list in reverse order.
1627     if (rRecursionHelper.IsInReturn())
1628     {
1629         bool bFreeFlyingInserted = false;
1630         if (rRecursionHelper.GetRecursionCount() > 0 || !rRecursionHelper.IsDoingRecursion())
1631         {
1632             rRecursionHelper.Insert( this, bOldRunning, aResult);
1633             bFreeFlyingInserted = mbFreeFlying;
1634         }
1635         bool bIterationFromRecursion = false;
1636         bool bResumeIteration = false;
1637         do
1638         {
1639             if ((rRecursionHelper.IsInIterationReturn() &&
1640                         rRecursionHelper.GetRecursionCount() == 0 &&
1641                         !rRecursionHelper.IsDoingIteration()) ||
1642                     bIterationFromRecursion || bResumeIteration)
1643             {
1644                 bool & rDone = rRecursionHelper.GetConvergingReference();
1645                 rDone = false;
1646                 if (!bIterationFromRecursion && bResumeIteration)
1647                 {
1648                     bResumeIteration = false;
1649                     // Resuming iteration expands the range.
1650                     ScFormulaRecursionList::const_iterator aOldStart(
1651                             rRecursionHelper.GetLastIterationStart());
1652                     rRecursionHelper.ResumeIteration();
1653                     // Mark new cells being in iteration.
1654                     for (ScFormulaRecursionList::const_iterator aIter(
1655                                 rRecursionHelper.GetIterationStart()); aIter !=
1656                             aOldStart; ++aIter)
1657                     {
1658                         ScFormulaCell* pIterCell = (*aIter).pCell;
1659                         pIterCell->bIsIterCell = true;
1660                     }
1661                     // Mark older cells dirty again, in case they converted
1662                     // without accounting for all remaining cells in the circle
1663                     // that weren't touched so far, e.g. conditional. Restore
1664                     // backupped result.
1665                     sal_uInt16 nIteration = rRecursionHelper.GetIteration();
1666                     for (ScFormulaRecursionList::const_iterator aIter(
1667                                 aOldStart); aIter !=
1668                             rRecursionHelper.GetIterationEnd(); ++aIter)
1669                     {
1670                         ScFormulaCell* pIterCell = (*aIter).pCell;
1671                         if (pIterCell->nSeenInIteration == nIteration)
1672                         {
1673                             if (!pIterCell->bDirty || aIter == aOldStart)
1674                             {
1675                                 pIterCell->aResult = (*aIter).aPreviousResult;
1676                             }
1677                             --pIterCell->nSeenInIteration;
1678                         }
1679                         pIterCell->bDirty = true;
1680                     }
1681                 }
1682                 else
1683                 {
1684                     bResumeIteration = false;
1685                     // Close circle once. If 'this' is self-referencing only
1686                     // (e.g. counter or self-adder) then it is already
1687                     // implicitly closed.
1688                     /* TODO: does this even make sense anymore? The last cell
1689                      * added above with rRecursionHelper.Insert() should always
1690                      * be 'this', shouldn't it? */
1691                     if (rRecursionHelper.GetList().size() > 1)
1692                     {
1693                         ScFormulaCell* pLastCell = rRecursionHelper.GetList().back().pCell;
1694                         if (pLastCell != this)
1695                         {
1696                             rDocument.IncInterpretLevel();
1697                             ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
1698                             pLastCell->InterpretTail(
1699                                 *aContextGetterGuard.GetInterpreterContext(), SCITP_CLOSE_ITERATION_CIRCLE);
1700                             rDocument.DecInterpretLevel();
1701                         }
1702                     }
1703                     // Start at 1, init things.
1704                     rRecursionHelper.StartIteration();
1705                     // Mark all cells being in iteration. Reset results to
1706                     // original values, formula cells have been interpreted
1707                     // already, discard that step.
1708                     for (ScFormulaRecursionList::const_iterator aIter(
1709                                 rRecursionHelper.GetIterationStart()); aIter !=
1710                             rRecursionHelper.GetIterationEnd(); ++aIter)
1711                     {
1712                         ScFormulaCell* pIterCell = (*aIter).pCell;
1713                         pIterCell->aResult = (*aIter).aPreviousResult;
1714                         pIterCell->bIsIterCell = true;
1715                     }
1716                 }
1717                 bIterationFromRecursion = false;
1718                 sal_uInt16 nIterMax = rDocument.GetDocOptions().GetIterCount();
1719                 for ( ; rRecursionHelper.GetIteration() <= nIterMax && !rDone;
1720                         rRecursionHelper.IncIteration())
1721                 {
1722                     rDone = false;
1723                     bool bFirst = true;
1724                     for ( ScFormulaRecursionList::iterator aIter(
1725                                 rRecursionHelper.GetIterationStart()); aIter !=
1726                             rRecursionHelper.GetIterationEnd() &&
1727                             !rRecursionHelper.IsInReturn(); ++aIter)
1728                     {
1729                         ScFormulaCell* pIterCell = (*aIter).pCell;
1730                         if (pIterCell->IsDirtyOrInTableOpDirty() &&
1731                                 rRecursionHelper.GetIteration() !=
1732                                 pIterCell->GetSeenInIteration())
1733                         {
1734                             (*aIter).aPreviousResult = pIterCell->aResult;
1735                             rDocument.IncInterpretLevel();
1736                             ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
1737                             pIterCell->InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_FROM_ITERATION);
1738                             rDocument.DecInterpretLevel();
1739                         }
1740                         if (bFirst)
1741                         {
1742                             rDone = !pIterCell->IsDirtyOrInTableOpDirty();
1743                             bFirst = false;
1744                         }
1745                         else if (rDone)
1746                         {
1747                             rDone = !pIterCell->IsDirtyOrInTableOpDirty();
1748                         }
1749                     }
1750                     if (rRecursionHelper.IsInReturn())
1751                     {
1752                         bResumeIteration = true;
1753                         break;  // for
1754                         // Don't increment iteration.
1755                     }
1756                 }
1757                 if (!bResumeIteration)
1758                 {
1759                     if (rDone)
1760                     {
1761                         for (ScFormulaRecursionList::const_iterator aIter(
1762                                     rRecursionHelper.GetIterationStart());
1763                                 aIter != rRecursionHelper.GetIterationEnd();
1764                                 ++aIter)
1765                         {
1766                             ScFormulaCell* pIterCell = (*aIter).pCell;
1767                             pIterCell->bIsIterCell = false;
1768                             pIterCell->nSeenInIteration = 0;
1769                             pIterCell->bRunning = (*aIter).bOldRunning;
1770                         }
1771                     }
1772                     else
1773                     {
1774                         for (ScFormulaRecursionList::const_iterator aIter(
1775                                     rRecursionHelper.GetIterationStart());
1776                                 aIter != rRecursionHelper.GetIterationEnd();
1777                                 ++aIter)
1778                         {
1779                             ScFormulaCell* pIterCell = (*aIter).pCell;
1780                             pIterCell->bIsIterCell = false;
1781                             pIterCell->nSeenInIteration = 0;
1782                             pIterCell->bRunning = (*aIter).bOldRunning;
1783                             pIterCell->ResetDirty();
1784                             // The difference to Excel is that Excel does not
1785                             // produce an error for non-convergence thus a
1786                             // delta of 0.001 still works to execute the
1787                             // maximum number of iterations and display the
1788                             // results no matter if the result anywhere reached
1789                             // near delta, but also never indicates whether the
1790                             // result actually makes sense in case of
1791                             // non-counter context. Calc does check the delta
1792                             // in every case. If we wanted to support what
1793                             // Excel does then add another option "indicate
1794                             // non-convergence error" (default on) and execute
1795                             // the following block only if set.
1796 #if 1
1797                             // If one cell didn't converge, all cells of this
1798                             // circular dependency don't, no matter whether
1799                             // single cells did.
1800                             pIterCell->aResult.SetResultError( FormulaError::NoConvergence);
1801                             pIterCell->bChanged = true;
1802 #endif
1803                         }
1804                     }
1805                     // End this iteration and remove entries.
1806                     rRecursionHelper.EndIteration();
1807                     bResumeIteration = rRecursionHelper.IsDoingIteration();
1808                 }
1809             }
1810             if (rRecursionHelper.IsInRecursionReturn() &&
1811                     rRecursionHelper.GetRecursionCount() == 0 &&
1812                     !rRecursionHelper.IsDoingRecursion())
1813             {
1814                 bIterationFromRecursion = false;
1815                 // Iterate over cells known so far, start with the last cell
1816                 // encountered, inserting new cells if another recursion limit
1817                 // is reached. Repeat until solved.
1818                 rRecursionHelper.SetDoingRecursion( true);
1819                 do
1820                 {
1821                     rRecursionHelper.SetInRecursionReturn( false);
1822                     for (ScFormulaRecursionList::const_iterator aIter(
1823                                 rRecursionHelper.GetIterationStart());
1824                             !rRecursionHelper.IsInReturn() && aIter !=
1825                             rRecursionHelper.GetIterationEnd(); ++aIter)
1826                     {
1827                         ScFormulaCell* pCell = (*aIter).pCell;
1828                         if (pCell->IsDirtyOrInTableOpDirty())
1829                         {
1830                             rDocument.IncInterpretLevel();
1831                             ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
1832                             pCell->InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_NORMAL);
1833                             rDocument.DecInterpretLevel();
1834                             if (!pCell->IsDirtyOrInTableOpDirty() && !pCell->IsIterCell())
1835                                 pCell->bRunning = (*aIter).bOldRunning;
1836                         }
1837                     }
1838                 } while (rRecursionHelper.IsInRecursionReturn());
1839                 rRecursionHelper.SetDoingRecursion( false);
1840                 if (rRecursionHelper.IsInIterationReturn())
1841                 {
1842                     if (!bResumeIteration)
1843                         bIterationFromRecursion = true;
1844                 }
1845                 else if (bResumeIteration ||
1846                         rRecursionHelper.IsDoingIteration())
1847                     rRecursionHelper.GetList().erase(
1848                             rRecursionHelper.GetIterationStart(),
1849                             rRecursionHelper.GetLastIterationStart());
1850                 else
1851                     rRecursionHelper.Clear();
1852             }
1853         } while (bIterationFromRecursion || bResumeIteration);
1854 
1855         if (bFreeFlyingInserted)
1856         {
1857             // Remove this from recursion list, it may get deleted.
1858             // It additionally also should mean that the recursion/iteration
1859             // ends here as it must had been triggered by this free-flying
1860             // out-of-sheets cell
1861             const bool bOnlyThis = (rRecursionHelper.GetList().size() == 1);
1862             rRecursionHelper.GetList().remove_if([this](const ScFormulaRecursionEntry& r){return r.pCell == this;});
1863             if (bOnlyThis)
1864             {
1865                 assert(rRecursionHelper.GetList().empty());
1866                 if (rRecursionHelper.GetList().empty())
1867                     rRecursionHelper.EndIteration();
1868             }
1869         }
1870     }
1871 
1872 #if DEBUG_CALCULATION
1873     FormulaError nErr = aResult.GetResultError();
1874     if (nErr != FormulaError::NONE)
1875         aDC.storeResultError( nErr);
1876     else if (aResult.IsValue())
1877         aDC.storeResult( aResult.GetDouble());
1878     else
1879         aDC.storeResult( aResult.GetString());
1880 #endif
1881 
1882     return bGroupInterpreted;
1883 }
1884 
InterpretTail(ScInterpreterContext & rContext,ScInterpretTailParameter eTailParam)1885 void ScFormulaCell::InterpretTail( ScInterpreterContext& rContext, ScInterpretTailParameter eTailParam )
1886 {
1887     RecursionCounter aRecursionCounter( rDocument.GetRecursionHelper(), this);
1888     // TODO If this cell is not an iteration cell, add it to the list of iteration cells?
1889     if(bIsIterCell)
1890         nSeenInIteration = rDocument.GetRecursionHelper().GetIteration();
1891     if( !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE )
1892     {
1893         // #i11719# no RPN and no error and no token code but result string present
1894         // => interpretation of this cell during name-compilation and unknown names
1895         // => can't exchange underlying code array in CompileTokenArray() /
1896         // Compile() because interpreter's token iterator would crash or pCode
1897         // would be deleted twice if this cell was interpreted during
1898         // compilation.
1899         // This should only be a temporary condition and, since we set an
1900         // error, if ran into it again we'd bump into the dirty-clearing
1901         // condition further down.
1902         if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
1903         {
1904             pCode->SetCodeError( FormulaError::NoCode );
1905             // This is worth an assertion; if encountered in daily work
1906             // documents we might need another solution. Or just confirm correctness.
1907             return;
1908         }
1909         CompileTokenArray();
1910     }
1911 
1912     if( pCode->GetCodeLen() )
1913     {
1914         std::unique_ptr<ScInterpreter> pScopedInterpreter;
1915         ScInterpreter* pInterpreter;
1916         if (rContext.pInterpreter)
1917         {
1918             pInterpreter = rContext.pInterpreter;
1919             pInterpreter->Init(this, aPos, *pCode);
1920         }
1921         else
1922         {
1923             pScopedInterpreter.reset(new ScInterpreter( this, rDocument, rContext, aPos, *pCode ));
1924             pInterpreter = pScopedInterpreter.get();
1925         }
1926 
1927         FormulaError nOldErrCode = aResult.GetResultError();
1928         if ( nSeenInIteration == 0 )
1929         {   // Only the first time
1930             // With bChanged=false, if a newly compiled cell has a result of
1931             // 0.0, no change is detected and the cell will not be repainted.
1932             // bChanged = false;
1933             aResult.SetResultError( FormulaError::NONE );
1934         }
1935 
1936         switch ( aResult.GetResultError() )
1937         {
1938             case FormulaError::CircularReference :     // will be determined again if so
1939                 aResult.SetResultError( FormulaError::NONE );
1940             break;
1941             default: break;
1942         }
1943 
1944         bool bOldRunning = bRunning;
1945         bRunning = true;
1946         pInterpreter->Interpret();
1947         if (rDocument.GetRecursionHelper().IsInReturn() && eTailParam != SCITP_CLOSE_ITERATION_CIRCLE)
1948         {
1949             if (nSeenInIteration > 0)
1950                 --nSeenInIteration;     // retry when iteration is resumed
1951 
1952             if ( aResult.GetType() == formula::svUnknown )
1953                 aResult.SetToken( pInterpreter->GetResultToken().get() );
1954 
1955             return;
1956         }
1957         bRunning = bOldRunning;
1958 
1959         // The result may be invalid or depend on another invalid result, just abort
1960         // without updating the cell value. Since the dirty flag will not be reset,
1961         // the proper value will be computed later.
1962         if(rDocument.GetRecursionHelper().IsAbortingDependencyComputation())
1963             return;
1964 
1965         // #i102616# For single-sheet saving consider only content changes, not format type,
1966         // because format type isn't set on loading (might be changed later)
1967         bool bContentChanged = false;
1968 
1969         // Do not create a HyperLink() cell if the formula results in an error.
1970         if( pInterpreter->GetError() != FormulaError::NONE && pCode->IsHyperLink())
1971             pCode->SetHyperLink(false);
1972 
1973         if( pInterpreter->GetError() != FormulaError::NONE && pInterpreter->GetError() != FormulaError::CircularReference)
1974         {
1975             bChanged = true;
1976 
1977             if (pInterpreter->GetError() == FormulaError::RetryCircular)
1978             {
1979                 // Array formula matrix calculation corner case. Keep dirty
1980                 // state, do not remove from formula tree or anything else, but
1981                 // store FormulaError::CircularReference in case this cell does not get
1982                 // recalculated.
1983                 aResult.SetResultError( FormulaError::CircularReference);
1984                 return;
1985             }
1986 
1987             ResetDirty();
1988         }
1989 
1990         if (eTailParam == SCITP_FROM_ITERATION && IsDirtyOrInTableOpDirty())
1991         {
1992             bool bIsValue = aResult.IsValue();  // the previous type
1993             // Did it converge?
1994             if ((bIsValue && pInterpreter->GetResultType() == svDouble && fabs(
1995                             pInterpreter->GetNumResult() - aResult.GetDouble()) <=
1996                         rDocument.GetDocOptions().GetIterEps()) ||
1997                     (!bIsValue && pInterpreter->GetResultType() == svString &&
1998                      pInterpreter->GetStringResult() == aResult.GetString()))
1999             {
2000                 // A convergence in the first iteration doesn't necessarily
2001                 // mean that it's done, it may be as not all related cells
2002                 // of a circle changed their values yet. If the set really
2003                 // converges it will do so also during the next iteration. This
2004                 // fixes situations like of #i44115#. If this wasn't wanted an
2005                 // initial "uncalculated" value would be needed for all cells
2006                 // of a circular dependency => graph needed before calculation.
2007                 if (nSeenInIteration > 1 ||
2008                         rDocument.GetDocOptions().GetIterCount() == 1)
2009                 {
2010                     ResetDirty();
2011                 }
2012             }
2013         }
2014 
2015         // New error code?
2016         if( pInterpreter->GetError() != nOldErrCode )
2017         {
2018             bChanged = true;
2019             // bContentChanged only has to be set if the file content would be changed
2020             if ( aResult.GetCellResultType() != svUnknown )
2021                 bContentChanged = true;
2022         }
2023 
2024         ScFormulaResult aNewResult( pInterpreter->GetResultToken().get());
2025 
2026         // For IF() and other jumps or changed formatted source data the result
2027         // format may change for different runs, e.g. =IF(B1,B1) with first
2028         // B1:0 boolean FALSE next B1:23 numeric 23, we don't want the 23
2029         // displayed as TRUE. Do not force a general format though if
2030         // mbNeedsNumberFormat is set (because there was a general format..).
2031         // Note that nFormatType may be out of sync here if a format was
2032         // applied or cleared after the last run, but obtaining the current
2033         // format always just to check would be expensive. There may be
2034         // cases where the format should be changed but is not. If that turns
2035         // out to be a real problem then obtain the current format type after
2036         // the initial check when needed.
2037         bool bForceNumberFormat = (mbAllowNumberFormatChange && !mbNeedsNumberFormat &&
2038                 !SvNumberFormatter::IsCompatible( nFormatType, pInterpreter->GetRetFormatType()));
2039 
2040         // We have some requirements additionally to IsCompatible().
2041         // * Do not apply a NumberFormat::LOGICAL if the result value is not
2042         //   1.0 or 0.0
2043         // * Do not override an already set numeric number format if the result
2044         //   is of type NumberFormat::LOGICAL, it could be user applied.
2045         //   On the other hand, for an empty jump path instead of FALSE an
2046         //   unexpected for example 0% could be displayed. YMMV.
2047         // * Never override a non-standard number format that indicates user
2048         //   applied.
2049         // * NumberFormat::TEXT does not force a change.
2050         if (bForceNumberFormat)
2051         {
2052             sal_uInt32 nOldFormatIndex = NUMBERFORMAT_ENTRY_NOT_FOUND;
2053             const SvNumFormatType nRetType = pInterpreter->GetRetFormatType();
2054             if (nRetType == SvNumFormatType::LOGICAL)
2055             {
2056                 double fVal = aNewResult.GetDouble();
2057                 if (fVal != 1.0 && fVal != 0.0)
2058                     bForceNumberFormat = false;
2059                 else
2060                 {
2061                     nOldFormatIndex = rDocument.GetNumberFormat( rContext, aPos);
2062                     nFormatType = rContext.GetFormatTable()->GetType( nOldFormatIndex);
2063                     switch (nFormatType)
2064                     {
2065                         case SvNumFormatType::PERCENT:
2066                         case SvNumFormatType::CURRENCY:
2067                         case SvNumFormatType::SCIENTIFIC:
2068                         case SvNumFormatType::FRACTION:
2069                             bForceNumberFormat = false;
2070                         break;
2071                         case SvNumFormatType::NUMBER:
2072                             if ((nOldFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
2073                                 bForceNumberFormat = false;
2074                         break;
2075                         default: break;
2076                     }
2077                 }
2078             }
2079             else if (nRetType == SvNumFormatType::TEXT)
2080             {
2081                 bForceNumberFormat = false;
2082             }
2083             if (bForceNumberFormat)
2084             {
2085                 if (nOldFormatIndex == NUMBERFORMAT_ENTRY_NOT_FOUND)
2086                 {
2087                     nOldFormatIndex = rDocument.GetNumberFormat( rContext, aPos);
2088                     nFormatType = rContext.GetFormatTable()->GetType( nOldFormatIndex);
2089                 }
2090                 if (nOldFormatIndex !=
2091                         ScGlobal::GetStandardFormat(rContext, nOldFormatIndex, nFormatType))
2092                     bForceNumberFormat = false;
2093             }
2094         }
2095 
2096         if( mbNeedsNumberFormat || bForceNumberFormat )
2097         {
2098             bool bSetFormat = true;
2099             const SvNumFormatType nOldFormatType = nFormatType;
2100             nFormatType = pInterpreter->GetRetFormatType();
2101             sal_uInt32 nFormatIndex = pInterpreter->GetRetFormatIndex();
2102 
2103             if (nFormatType == SvNumFormatType::TEXT)
2104             {
2105                 // Don't set text format as hard format.
2106                 bSetFormat = false;
2107             }
2108             else if (nFormatType == SvNumFormatType::LOGICAL && cMatrixFlag != ScMatrixMode::NONE)
2109             {
2110                 // In a matrix range do not set an (inherited) logical format
2111                 // as hard format if the value does not represent a strict TRUE
2112                 // or FALSE value. But do set for a top left error value so
2113                 // following matrix cells can inherit for non-error values.
2114                 // This solves a problem with IF() expressions in array context
2115                 // where incidentally the top left element results in logical
2116                 // type but some others don't. It still doesn't solve the
2117                 // reverse case though, where top left is not logical type but
2118                 // some other elements should be. We'd need to transport type
2119                 // or format information on arrays.
2120                 StackVar eNewCellResultType = aNewResult.GetCellResultType();
2121                 if (eNewCellResultType != svError || cMatrixFlag == ScMatrixMode::Reference)
2122                 {
2123                     if (eNewCellResultType != svDouble)
2124                     {
2125                         bSetFormat = false;
2126                         nFormatType = nOldFormatType;   // that? or number?
2127                     }
2128                     else
2129                     {
2130                         double fVal = aNewResult.GetDouble();
2131                         if (fVal != 1.0 && fVal != 0.0)
2132                         {
2133                             bSetFormat = false;
2134                             nFormatType = SvNumFormatType::NUMBER;
2135                         }
2136                     }
2137                 }
2138             }
2139 
2140             if (bSetFormat && (bForceNumberFormat || ((nFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) == 0)))
2141                 nFormatIndex = ScGlobal::GetStandardFormat(rContext, nFormatIndex, nFormatType);
2142 
2143             // Do not replace a General format (which was the reason why
2144             // mbNeedsNumberFormat was set) with a General format.
2145             // 1. setting a format has quite some overhead in the
2146             // ScPatternAttr/ScAttrArray handling, even if identical.
2147             // 2. the General formats may be of different locales.
2148             // XXX if mbNeedsNumberFormat was set even if the current format
2149             // was not General then we'd have to obtain the current format here
2150             // and check at least the types.
2151             const bool bSetNumberFormat = bSetFormat && (bForceNumberFormat || ((nFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0));
2152             if (bSetNumberFormat && !rDocument.IsInLayoutStrings())
2153             {
2154                 // set number format explicitly
2155                 if (!rDocument.IsThreadedGroupCalcInProgress())
2156                     rDocument.SetNumberFormat( aPos, nFormatIndex );
2157                 else
2158                 {
2159                     // SetNumberFormat() is not thread-safe (modifies ScAttrArray), delay the work
2160                     // to the main thread. Since thread calculations operate on formula groups,
2161                     // it's enough to store just the row.
2162                     DelayedSetNumberFormat data = { aPos.Col(), aPos.Row(), nFormatIndex };
2163                     rContext.maDelayedSetNumberFormat.push_back( data );
2164                 }
2165                 bChanged = true;
2166             }
2167 
2168             // Currently (2019-05-10) nothing else can cope with a duration
2169             // format type, change to time as it was before.
2170             if (nFormatType == SvNumFormatType::DURATION)
2171                 nFormatType = SvNumFormatType::TIME;
2172 
2173             mbNeedsNumberFormat = false;
2174         }
2175 
2176         // In case of changes just obtain the result, no temporary and
2177         // comparison needed anymore.
2178         if (bChanged)
2179         {
2180             // #i102616# Compare anyway if the sheet is still marked unchanged for single-sheet saving
2181             // Also handle special cases of initial results after loading.
2182             if ( !bContentChanged && rDocument.IsStreamValid(aPos.Tab()) )
2183             {
2184                 StackVar eOld = aResult.GetCellResultType();
2185                 StackVar eNew = aNewResult.GetCellResultType();
2186                 if ( eOld == svUnknown && ( eNew == svError || ( eNew == svDouble && aNewResult.GetDouble() == 0.0 ) ) )
2187                 {
2188                     // ScXMLTableRowCellContext::EndElement doesn't call SetFormulaResultDouble for 0
2189                     // -> no change
2190                 }
2191                 else
2192                 {
2193                     if ( eOld == svHybridCell )     // string result from SetFormulaResultString?
2194                         eOld = svString;            // ScHybridCellToken has a valid GetString method
2195 
2196                     // #i106045# use approxEqual to compare with stored value
2197                     bContentChanged = (eOld != eNew ||
2198                             (eNew == svDouble && !rtl::math::approxEqual( aResult.GetDouble(), aNewResult.GetDouble() )) ||
2199                             (eNew == svString && aResult.GetString() != aNewResult.GetString()));
2200                 }
2201             }
2202 
2203             aResult.SetToken( pInterpreter->GetResultToken().get() );
2204         }
2205         else
2206         {
2207             StackVar eOld = aResult.GetCellResultType();
2208             StackVar eNew = aNewResult.GetCellResultType();
2209             bChanged = (eOld != eNew ||
2210                     (eNew == svDouble && aResult.GetDouble() != aNewResult.GetDouble()) ||
2211                     (eNew == svString && aResult.GetString() != aNewResult.GetString()));
2212 
2213             // #i102616# handle special cases of initial results after loading
2214             // (only if the sheet is still marked unchanged)
2215             if ( bChanged && !bContentChanged && rDocument.IsStreamValid(aPos.Tab()) )
2216             {
2217                 if ((eOld == svUnknown && (eNew == svError || (eNew == svDouble && aNewResult.GetDouble() == 0.0))) ||
2218                         ((eOld == svHybridCell) &&
2219                          eNew == svString && aResult.GetString() == aNewResult.GetString()) ||
2220                         (eOld == svDouble && eNew == svDouble &&
2221                          rtl::math::approxEqual( aResult.GetDouble(), aNewResult.GetDouble())))
2222                 {
2223                     // no change, see above
2224                 }
2225                 else
2226                     bContentChanged = true;
2227             }
2228 
2229             aResult.Assign( aNewResult);
2230         }
2231 
2232         // Precision as shown?
2233         if ( aResult.IsValue() && pInterpreter->GetError() == FormulaError::NONE
2234           && rDocument.GetDocOptions().IsCalcAsShown()
2235           && nFormatType != SvNumFormatType::DATE
2236           && nFormatType != SvNumFormatType::TIME
2237           && nFormatType != SvNumFormatType::DATETIME )
2238         {
2239             sal_uInt32 nFormat = rDocument.GetNumberFormat( rContext, aPos );
2240             aResult.SetDouble( rDocument.RoundValueAsShown(
2241                         aResult.GetDouble(), nFormat, &rContext));
2242         }
2243         if (eTailParam == SCITP_NORMAL)
2244         {
2245             ResetDirty();
2246         }
2247         if( aResult.GetMatrix() )
2248         {
2249             // If the formula wasn't entered as a matrix formula, live on with
2250             // the upper left corner and let reference counting delete the matrix.
2251             if( cMatrixFlag != ScMatrixMode::Formula && !pCode->IsHyperLink() )
2252                 aResult.SetToken( aResult.GetCellResultToken().get());
2253         }
2254         if ( aResult.IsValue() && !std::isfinite( aResult.GetDouble() ) )
2255         {
2256             // Coded double error may occur via filter import.
2257             FormulaError nErr = GetDoubleErrorValue( aResult.GetDouble());
2258             aResult.SetResultError( nErr);
2259             bChanged = bContentChanged = true;
2260         }
2261 
2262         if (bContentChanged && rDocument.IsStreamValid(aPos.Tab()))
2263         {
2264             // pass bIgnoreLock=true, because even if called from pending row height update,
2265             // a changed result must still reset the stream flag
2266             rDocument.SetStreamValid(aPos.Tab(), false, true);
2267         }
2268         if ( !rDocument.IsThreadedGroupCalcInProgress() && !pCode->IsRecalcModeAlways() )
2269             rDocument.RemoveFromFormulaTree( this );
2270 
2271         //  FORCED cells also immediately tested for validity (start macro possibly)
2272 
2273         if ( pCode->IsRecalcModeForced() )
2274         {
2275             sal_uInt32 nValidation = rDocument.GetAttr(
2276                     aPos.Col(), aPos.Row(), aPos.Tab(), ATTR_VALIDDATA )->GetValue();
2277             if ( nValidation )
2278             {
2279                 const ScValidationData* pData = rDocument.GetValidationEntry( nValidation );
2280                 ScRefCellValue aTmpCell(this);
2281                 if ( pData && !pData->IsDataValid(aTmpCell, aPos))
2282                     pData->DoCalcError( this );
2283             }
2284         }
2285 
2286         // Reschedule slows the whole thing down considerably, thus only execute on percent change
2287         if (!rDocument.IsThreadedGroupCalcInProgress())
2288         {
2289             ScProgress *pProgress = ScProgress::GetInterpretProgress();
2290             if (pProgress && pProgress->Enabled())
2291             {
2292                 pProgress->SetStateCountDownOnPercent(
2293                     rDocument.GetFormulaCodeInTree()/MIN_NO_CODES_PER_PROGRESS_UPDATE );
2294             }
2295 
2296             switch (pInterpreter->GetVolatileType())
2297             {
2298                 case ScInterpreter::VOLATILE:
2299                     // Volatile via built-in volatile functions.  No actions needed.
2300                 break;
2301                 case ScInterpreter::VOLATILE_MACRO:
2302                     // The formula contains a volatile macro.
2303                     pCode->SetExclusiveRecalcModeAlways();
2304                     rDocument.PutInFormulaTree(this);
2305                     StartListeningTo(rDocument);
2306                 break;
2307                 case ScInterpreter::NOT_VOLATILE:
2308                     if (pCode->IsRecalcModeAlways())
2309                     {
2310                         // The formula was previously volatile, but no more.
2311                         EndListeningTo(rDocument);
2312                         pCode->SetExclusiveRecalcModeNormal();
2313                     }
2314                     else
2315                     {
2316                         // non-volatile formula.  End listening to the area in case
2317                         // it's listening due to macro module change.
2318                         rDocument.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
2319                     }
2320                     rDocument.RemoveFromFormulaTree(this);
2321                 break;
2322                 default:
2323                     ;
2324             }
2325         }
2326     }
2327     else
2328     {
2329         // Cells with compiler errors should not be marked dirty forever
2330         OSL_ENSURE( pCode->GetCodeError() != FormulaError::NONE, "no RPN code and no errors ?!?!" );
2331         ResetDirty();
2332     }
2333 
2334     pCode->ClearRecalcModeMustAfterImport();
2335 }
2336 
HandleStuffAfterParallelCalculation(ScInterpreter * pInterpreter)2337 void ScFormulaCell::HandleStuffAfterParallelCalculation(ScInterpreter* pInterpreter)
2338 {
2339     aResult.HandleStuffAfterParallelCalculation();
2340 
2341     if( !pCode->GetCodeLen() )
2342         return;
2343 
2344     if ( !pCode->IsRecalcModeAlways() )
2345         rDocument.RemoveFromFormulaTree( this );
2346 
2347     std::unique_ptr<ScInterpreter> pScopedInterpreter;
2348     if (pInterpreter)
2349         pInterpreter->Init(this, aPos, *pCode);
2350     else
2351     {
2352         pScopedInterpreter.reset(new ScInterpreter( this, rDocument, rDocument.GetNonThreadedContext(), aPos, *pCode ));
2353         pInterpreter = pScopedInterpreter.get();
2354     }
2355 
2356     switch (pInterpreter->GetVolatileType())
2357     {
2358         case ScInterpreter::VOLATILE_MACRO:
2359             // The formula contains a volatile macro.
2360             pCode->SetExclusiveRecalcModeAlways();
2361             rDocument.PutInFormulaTree(this);
2362             StartListeningTo(rDocument);
2363         break;
2364         case ScInterpreter::NOT_VOLATILE:
2365             if (pCode->IsRecalcModeAlways())
2366             {
2367                 // The formula was previously volatile, but no more.
2368                 EndListeningTo(rDocument);
2369                 pCode->SetExclusiveRecalcModeNormal();
2370             }
2371             else
2372             {
2373                 // non-volatile formula.  End listening to the area in case
2374                 // it's listening due to macro module change.
2375                 rDocument.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
2376             }
2377             rDocument.RemoveFromFormulaTree(this);
2378         break;
2379         default:
2380             ;
2381     }
2382 }
2383 
SetCompile(bool bVal)2384 void ScFormulaCell::SetCompile( bool bVal )
2385 {
2386     bCompile = bVal;
2387 }
2388 
SetMatColsRows(SCCOL nCols,SCROW nRows)2389 void ScFormulaCell::SetMatColsRows( SCCOL nCols, SCROW nRows )
2390 {
2391     ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellTokenNonConst();
2392     if (pMat)
2393         pMat->SetMatColsRows( nCols, nRows );
2394     else if (nCols || nRows)
2395     {
2396         aResult.SetToken( new ScMatrixFormulaCellToken( nCols, nRows));
2397         // Setting the new token actually forces an empty result at this top
2398         // left cell, so have that recalculated.
2399         SetDirty();
2400     }
2401 }
2402 
GetMatColsRows(SCCOL & nCols,SCROW & nRows) const2403 void ScFormulaCell::GetMatColsRows( SCCOL & nCols, SCROW & nRows ) const
2404 {
2405     const ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellToken();
2406     if (pMat)
2407         pMat->GetMatColsRows( nCols, nRows);
2408     else
2409     {
2410         nCols = 0;
2411         nRows = 0;
2412     }
2413 }
2414 
SetInChangeTrack(bool bVal)2415 void ScFormulaCell::SetInChangeTrack( bool bVal )
2416 {
2417     bInChangeTrack = bVal;
2418 }
2419 
Notify(const SfxHint & rHint)2420 void ScFormulaCell::Notify( const SfxHint& rHint )
2421 {
2422     if (rDocument.IsInDtorClear())
2423         return;
2424 
2425     const SfxHintId nHint = rHint.GetId();
2426     if (nHint == SfxHintId::ScReference)
2427     {
2428         const sc::RefHint& rRefHint = static_cast<const sc::RefHint&>(rHint);
2429 
2430         switch (rRefHint.getType())
2431         {
2432             case sc::RefHint::ColumnReordered:
2433             {
2434                 const sc::RefColReorderHint& rRefColReorder =
2435                     static_cast<const sc::RefColReorderHint&>(rRefHint);
2436                 if (!IsShared() || IsSharedTop())
2437                     pCode->MoveReferenceColReorder(
2438                         aPos, rRefColReorder.getTab(),
2439                         rRefColReorder.getStartRow(),
2440                         rRefColReorder.getEndRow(),
2441                         rRefColReorder.getColMap());
2442             }
2443             break;
2444             case sc::RefHint::RowReordered:
2445             {
2446                 const sc::RefRowReorderHint& rRefRowReorder =
2447                     static_cast<const sc::RefRowReorderHint&>(rRefHint);
2448                 if (!IsShared() || IsSharedTop())
2449                     pCode->MoveReferenceRowReorder(
2450                         aPos, rRefRowReorder.getTab(),
2451                         rRefRowReorder.getStartColumn(),
2452                         rRefRowReorder.getEndColumn(),
2453                         rRefRowReorder.getRowMap());
2454             }
2455             break;
2456             case sc::RefHint::StartListening:
2457             {
2458                 StartListeningTo(rDocument);
2459             }
2460             break;
2461             case sc::RefHint::StopListening:
2462             {
2463                 EndListeningTo(rDocument);
2464             }
2465             break;
2466             default:
2467                 ;
2468         }
2469 
2470         return;
2471     }
2472 
2473     if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
2474         return;
2475 
2476     if (!(nHint == SfxHintId::ScDataChanged || nHint == SfxHintId::ScTableOpDirty || (bSubTotal && nHint == SfxHintId::ScHiddenRowsChanged)))
2477         return;
2478 
2479     bool bForceTrack = false;
2480     if ( nHint == SfxHintId::ScTableOpDirty )
2481     {
2482         bForceTrack = !bTableOpDirty;
2483         if ( !bTableOpDirty )
2484         {
2485             rDocument.AddTableOpFormulaCell( this );
2486             bTableOpDirty = true;
2487         }
2488     }
2489     else
2490     {
2491         bForceTrack = !bDirty;
2492         SetDirtyVar();
2493     }
2494     // Don't remove from FormulaTree to put in FormulaTrack to
2495     // put in FormulaTree again and again, only if necessary.
2496     // Any other means except ScRecalcMode::ALWAYS by which a cell could
2497     // be in FormulaTree if it would notify other cells through
2498     // FormulaTrack which weren't in FormulaTrack/FormulaTree before?!?
2499     // Yes. The new TableOpDirty made it necessary to have a
2500     // forced mode where formulas may still be in FormulaTree from
2501     // TableOpDirty but have to notify dependents for normal dirty.
2502     if ( (bForceTrack || !rDocument.IsInFormulaTree( this )
2503             || pCode->IsRecalcModeAlways())
2504             && !rDocument.IsInFormulaTrack( this ) )
2505         rDocument.AppendToFormulaTrack( this );
2506 }
2507 
Query(SvtListener::QueryBase & rQuery) const2508 void ScFormulaCell::Query( SvtListener::QueryBase& rQuery ) const
2509 {
2510     switch (rQuery.getId())
2511     {
2512         case SC_LISTENER_QUERY_FORMULA_GROUP_POS:
2513         {
2514             sc::RefQueryFormulaGroup& rRefQuery =
2515                 static_cast<sc::RefQueryFormulaGroup&>(rQuery);
2516             if (IsShared())
2517                 rRefQuery.add(aPos);
2518         }
2519         break;
2520         default:
2521             ;
2522     }
2523 }
2524 
SetDirty(bool bDirtyFlag)2525 void ScFormulaCell::SetDirty( bool bDirtyFlag )
2526 {
2527     if (IsInChangeTrack())
2528         return;
2529 
2530     if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
2531     {
2532         SetDirtyVar();
2533         rDocument.SetStreamValid(aPos.Tab(), false);
2534         return;
2535     }
2536 
2537     // Avoid multiple formula tracking in Load() and in CompileAll()
2538     // after CopyScenario() and CopyBlockFromClip().
2539     // If unconditional formula tracking is needed, set bDirty=false
2540     // before calling SetDirty(), for example in CompileTokenArray().
2541     if ( !bDirty || mbPostponedDirty || !rDocument.IsInFormulaTree( this ) )
2542     {
2543         if( bDirtyFlag )
2544             SetDirtyVar();
2545         rDocument.AppendToFormulaTrack( this );
2546 
2547         // While loading a document listeners have not been established yet.
2548         // Tracking would remove this cell from the FormulaTrack and add it to
2549         // the FormulaTree, once in there it would be assumed that its
2550         // dependents already had been tracked and it would be skipped on a
2551         // subsequent notify. Postpone tracking until all listeners are set.
2552         if (!rDocument.IsImportingXML() && !rDocument.IsInsertingFromOtherDoc())
2553             rDocument.TrackFormulas();
2554     }
2555 
2556     rDocument.SetStreamValid(aPos.Tab(), false);
2557 }
2558 
SetDirtyVar()2559 void ScFormulaCell::SetDirtyVar()
2560 {
2561     bDirty = true;
2562     mbPostponedDirty = false;
2563     if (mxGroup && mxGroup->meCalcState == sc::GroupCalcRunning)
2564     {
2565         mxGroup->meCalcState = sc::GroupCalcEnabled;
2566         mxGroup->mbPartOfCycle = false;
2567     }
2568 
2569     // mark the sheet of this cell to be calculated
2570     //#FIXME do we need to revert this remnant of old fake vba events? rDocument.AddCalculateTable( aPos.Tab() );
2571 }
2572 
SetDirtyAfterLoad()2573 void ScFormulaCell::SetDirtyAfterLoad()
2574 {
2575     bDirty = true;
2576     if ( rDocument.GetHardRecalcState() == ScDocument::HardRecalcState::OFF )
2577         rDocument.PutInFormulaTree( this );
2578 }
2579 
ResetTableOpDirtyVar()2580 void ScFormulaCell::ResetTableOpDirtyVar()
2581 {
2582     bTableOpDirty = false;
2583 }
2584 
SetTableOpDirty()2585 void ScFormulaCell::SetTableOpDirty()
2586 {
2587     if ( IsInChangeTrack() )
2588         return;
2589 
2590     if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
2591         bTableOpDirty = true;
2592     else
2593     {
2594         if ( !bTableOpDirty || !rDocument.IsInFormulaTree( this ) )
2595         {
2596             if ( !bTableOpDirty )
2597             {
2598                 rDocument.AddTableOpFormulaCell( this );
2599                 bTableOpDirty = true;
2600             }
2601             rDocument.AppendToFormulaTrack( this );
2602             rDocument.TrackFormulas( SfxHintId::ScTableOpDirty );
2603         }
2604     }
2605 }
2606 
SetResultDouble(double n)2607 void ScFormulaCell::SetResultDouble( double n )
2608 {
2609     aResult.SetDouble(n);
2610 }
2611 
SetResultToken(const formula::FormulaToken * pToken)2612 void ScFormulaCell::SetResultToken( const formula::FormulaToken* pToken )
2613 {
2614     aResult.SetToken(pToken);
2615 }
2616 
GetResultString() const2617 const svl::SharedString & ScFormulaCell::GetResultString() const
2618 {
2619     return aResult.GetString();
2620 }
2621 
HasHybridStringResult() const2622 bool ScFormulaCell::HasHybridStringResult() const
2623 {
2624     return aResult.GetType() == formula::svHybridCell && !aResult.GetString().isEmpty();
2625 }
2626 
SetResultMatrix(SCCOL nCols,SCROW nRows,const ScConstMatrixRef & pMat,const formula::FormulaToken * pUL)2627 void ScFormulaCell::SetResultMatrix( SCCOL nCols, SCROW nRows, const ScConstMatrixRef& pMat, const formula::FormulaToken* pUL )
2628 {
2629     aResult.SetMatrix(nCols, nRows, pMat, pUL);
2630 }
2631 
SetErrCode(FormulaError n)2632 void ScFormulaCell::SetErrCode( FormulaError n )
2633 {
2634     /* FIXME: check the numerous places where ScTokenArray::GetCodeError() is
2635      * used whether it is solely for transport of a simple result error and get
2636      * rid of that abuse. */
2637     pCode->SetCodeError( n );
2638     // Hard set errors are transported as result type value per convention,
2639     // e.g. via clipboard. ScFormulaResult::IsValue() and
2640     // ScFormulaResult::GetDouble() handle that.
2641     aResult.SetResultError( n );
2642 }
2643 
SetResultError(FormulaError n)2644 void ScFormulaCell::SetResultError( FormulaError n )
2645 {
2646     aResult.SetResultError( n );
2647 }
2648 
AddRecalcMode(ScRecalcMode nBits)2649 void ScFormulaCell::AddRecalcMode( ScRecalcMode nBits )
2650 {
2651     if ( (nBits & ScRecalcMode::EMask) != ScRecalcMode::NORMAL )
2652         SetDirtyVar();
2653     pCode->AddRecalcMode( nBits );
2654 }
2655 
SetHybridDouble(double n)2656 void ScFormulaCell::SetHybridDouble( double n )
2657 {
2658     aResult.SetHybridDouble( n);
2659 }
2660 
SetHybridString(const svl::SharedString & r)2661 void ScFormulaCell::SetHybridString( const svl::SharedString& r )
2662 {
2663     aResult.SetHybridString( r);
2664 }
2665 
SetHybridEmptyDisplayedAsString()2666 void ScFormulaCell::SetHybridEmptyDisplayedAsString()
2667 {
2668     aResult.SetHybridEmptyDisplayedAsString();
2669 }
2670 
SetHybridFormula(const OUString & r,const formula::FormulaGrammar::Grammar eGrammar)2671 void ScFormulaCell::SetHybridFormula( const OUString& r,
2672                                     const formula::FormulaGrammar::Grammar eGrammar )
2673 {
2674     aResult.SetHybridFormula( r); eTempGrammar = eGrammar;
2675 }
2676 
GetHybridFormula() const2677 OUString ScFormulaCell::GetHybridFormula() const
2678 {
2679     return aResult.GetHybridFormula();
2680 }
2681 
2682 // Dynamically create the URLField on a mouse-over action on a hyperlink() cell.
GetURLResult(OUString & rURL,OUString & rCellText)2683 void ScFormulaCell::GetURLResult( OUString& rURL, OUString& rCellText )
2684 {
2685     OUString aCellString;
2686 
2687     const Color* pColor;
2688 
2689     // Cell Text uses the Cell format while the URL uses
2690     // the default format for the type.
2691     const sal_uInt32 nCellFormat = rDocument.GetNumberFormat( aPos );
2692     ScInterpreterContext& rContext = rDocument.GetNonThreadedContext();
2693 
2694     const sal_uInt32 nURLFormat = ScGlobal::GetStandardFormat(rContext, nCellFormat, SvNumFormatType::NUMBER);
2695 
2696     if ( IsValue() )
2697     {
2698         double fValue = GetValue();
2699         rContext.NFGetOutputString( fValue, nCellFormat, rCellText, &pColor );
2700     }
2701     else
2702     {
2703         aCellString = GetString().getString();
2704         rContext.NFGetOutputString( aCellString, nCellFormat, rCellText, &pColor );
2705     }
2706     ScConstMatrixRef xMat( aResult.GetMatrix());
2707     if (xMat)
2708     {
2709         // determine if the matrix result is a string or value.
2710         if (!xMat->IsValue(0, 1))
2711             rURL = xMat->GetString(0, 1).getString();
2712         else
2713             rContext.NFGetOutputString(
2714                 xMat->GetDouble(0, 1), nURLFormat, rURL, &pColor);
2715     }
2716 
2717     if(rURL.isEmpty())
2718     {
2719         if(IsValue())
2720             rContext.NFGetOutputString( GetValue(), nURLFormat, rURL, &pColor );
2721         else
2722             rContext.NFGetOutputString( aCellString, nURLFormat, rURL, &pColor );
2723     }
2724 }
2725 
IsMultilineResult()2726 bool ScFormulaCell::IsMultilineResult()
2727 {
2728     if (!IsValue())
2729         return aResult.IsMultiline();
2730     return false;
2731 }
2732 
IsHyperLinkCell() const2733 bool ScFormulaCell::IsHyperLinkCell() const
2734 {
2735     return pCode && pCode->IsHyperLink();
2736 }
2737 
CreateURLObject()2738 std::unique_ptr<EditTextObject> ScFormulaCell::CreateURLObject()
2739 {
2740     OUString aCellText;
2741     OUString aURL;
2742     GetURLResult( aURL, aCellText );
2743 
2744     return ScEditUtil::CreateURLObjectFromURL( rDocument, aURL, aCellText );
2745 }
2746 
IsEmpty()2747 bool ScFormulaCell::IsEmpty()
2748 {
2749     MaybeInterpret();
2750     return aResult.GetCellResultType() == formula::svEmptyCell;
2751 }
2752 
IsEmptyDisplayedAsString()2753 bool ScFormulaCell::IsEmptyDisplayedAsString()
2754 {
2755     MaybeInterpret();
2756     return aResult.IsEmptyDisplayedAsString();
2757 }
2758 
IsValue()2759 bool ScFormulaCell::IsValue()
2760 {
2761     MaybeInterpret();
2762     return aResult.IsValue();
2763 }
2764 
IsValueNoError()2765 bool ScFormulaCell::IsValueNoError()
2766 {
2767     MaybeInterpret();
2768     if (pCode->GetCodeError() != FormulaError::NONE)
2769         return false;
2770 
2771     return aResult.IsValueNoError();
2772 }
2773 
IsValueNoError() const2774 bool ScFormulaCell::IsValueNoError() const
2775 {
2776     if (NeedsInterpret())
2777         // false if the cell is dirty & needs to be interpreted.
2778         return false;
2779 
2780     if (pCode->GetCodeError() != FormulaError::NONE)
2781         return false;
2782 
2783     return aResult.IsValueNoError();
2784 }
2785 
GetValue()2786 double ScFormulaCell::GetValue()
2787 {
2788     MaybeInterpret();
2789     return GetRawValue();
2790 }
2791 
GetString()2792 const svl::SharedString & ScFormulaCell::GetString()
2793 {
2794     MaybeInterpret();
2795     return GetRawString();
2796 }
2797 
GetRawValue() const2798 double ScFormulaCell::GetRawValue() const
2799 {
2800     if ((pCode->GetCodeError() == FormulaError::NONE) &&
2801             aResult.GetResultError() == FormulaError::NONE)
2802         return aResult.GetDouble();
2803     return 0.0;
2804 }
2805 
GetRawString() const2806 const svl::SharedString & ScFormulaCell::GetRawString() const
2807 {
2808     if ((pCode->GetCodeError() == FormulaError::NONE) &&
2809             aResult.GetResultError() == FormulaError::NONE)
2810         return aResult.GetString();
2811 
2812     return svl::SharedString::getEmptyString();
2813 }
2814 
GetMatrix()2815 const ScMatrix* ScFormulaCell::GetMatrix()
2816 {
2817     if ( rDocument.GetAutoCalc() )
2818     {
2819         if( IsDirtyOrInTableOpDirty()
2820         // Was stored !bDirty but an accompanying matrix cell was bDirty?
2821         || (!bDirty && cMatrixFlag == ScMatrixMode::Formula && !aResult.GetMatrix()))
2822             Interpret();
2823     }
2824     return aResult.GetMatrix().get();
2825 }
2826 
GetMatrixOrigin(const ScDocument & rDoc,ScAddress & rPos) const2827 bool ScFormulaCell::GetMatrixOrigin( const ScDocument& rDoc, ScAddress& rPos ) const
2828 {
2829     switch ( cMatrixFlag )
2830     {
2831         case ScMatrixMode::Formula :
2832             rPos = aPos;
2833             return true;
2834         case ScMatrixMode::Reference :
2835         {
2836             formula::FormulaTokenArrayPlainIterator aIter(*pCode);
2837             formula::FormulaToken* t = aIter.GetNextReferenceRPN();
2838             if( t )
2839             {
2840                 ScSingleRefData& rRef = *t->GetSingleRef();
2841                 ScAddress aAbs = rRef.toAbs(rDoc, aPos);
2842                 if (rDoc.ValidAddress(aAbs))
2843                 {
2844                     rPos = aAbs;
2845                     return true;
2846                 }
2847             }
2848         }
2849         break;
2850         default: break;
2851     }
2852     return false;
2853 }
2854 
GetMatrixEdge(const ScDocument & rDoc,ScAddress & rOrgPos) const2855 sc::MatrixEdge ScFormulaCell::GetMatrixEdge( const ScDocument& rDoc, ScAddress& rOrgPos ) const
2856 {
2857     switch ( cMatrixFlag )
2858     {
2859         case ScMatrixMode::Formula :
2860         case ScMatrixMode::Reference :
2861         {
2862             static thread_local SCCOL nC;
2863             static thread_local SCROW nR;
2864             ScAddress aOrg;
2865             if ( !GetMatrixOrigin( rDoc, aOrg ) )
2866                 return sc::MatrixEdge::Nothing;
2867             if ( aOrg != rOrgPos )
2868             {   // First time or a different matrix than last time.
2869                 rOrgPos = aOrg;
2870                 const ScFormulaCell* pFCell;
2871                 if ( cMatrixFlag == ScMatrixMode::Reference )
2872                     pFCell = rDocument.GetFormulaCell(aOrg);
2873                 else
2874                     pFCell = this;      // this ScMatrixMode::Formula
2875                 // There's only one this, don't compare pFCell==this.
2876                 if (pFCell && pFCell->cMatrixFlag == ScMatrixMode::Formula)
2877                 {
2878                     pFCell->GetMatColsRows( nC, nR );
2879                     if ( nC == 0 || nR == 0 )
2880                     {
2881                         // No ScMatrixFormulaCellToken available yet, calculate new.
2882                         nC = 1;
2883                         nR = 1;
2884                         ScAddress aTmpOrg;
2885                         ScFormulaCell* pCell;
2886                         ScAddress aAdr( aOrg );
2887                         aAdr.IncCol();
2888                         bool bCont = true;
2889                         do
2890                         {
2891                             pCell = rDocument.GetFormulaCell(aAdr);
2892                             if (pCell && pCell->cMatrixFlag == ScMatrixMode::Reference &&
2893                                 pCell->GetMatrixOrigin(rDocument, aTmpOrg) && aTmpOrg == aOrg)
2894                             {
2895                                 nC++;
2896                                 aAdr.IncCol();
2897                             }
2898                             else
2899                                 bCont = false;
2900                         } while ( bCont );
2901                         aAdr = aOrg;
2902                         aAdr.IncRow();
2903                         bCont = true;
2904                         do
2905                         {
2906                             pCell = rDocument.GetFormulaCell(aAdr);
2907                             if (pCell && pCell->cMatrixFlag == ScMatrixMode::Reference &&
2908                                 pCell->GetMatrixOrigin(rDocument, aTmpOrg) && aTmpOrg == aOrg)
2909                             {
2910                                 nR++;
2911                                 aAdr.IncRow();
2912                             }
2913                             else
2914                                 bCont = false;
2915                         } while ( bCont );
2916 
2917                         const_cast<ScFormulaCell*>(pFCell)->SetMatColsRows(nC, nR);
2918                     }
2919                 }
2920                 else
2921                 {
2922 #if OSL_DEBUG_LEVEL > 0
2923                     SAL_WARN( "sc", "broken Matrix, no MatFormula at origin, Pos: "
2924                                 << aPos.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
2925                                 << ", MatOrg: "
2926                                 << aOrg.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument) );
2927 #endif
2928                     return sc::MatrixEdge::Nothing;
2929                 }
2930             }
2931             // here we are, healthy and clean, somewhere in between
2932             SCCOL dC = aPos.Col() - aOrg.Col();
2933             SCROW dR = aPos.Row() - aOrg.Row();
2934             sc::MatrixEdge nEdges = sc::MatrixEdge::Nothing;
2935             if ( dC >= 0 && dR >= 0 && dC < nC && dR < nR )
2936             {
2937                 if ( dC == 0 )
2938                     nEdges |= sc::MatrixEdge::Left;
2939                 if ( dC+1 == nC )
2940                     nEdges |= sc::MatrixEdge::Right;
2941                 if ( dR == 0 )
2942                     nEdges |= sc::MatrixEdge::Top;
2943                 if ( dR+1 == nR )
2944                     nEdges |= sc::MatrixEdge::Bottom;
2945                 if ( nEdges == sc::MatrixEdge::Nothing )
2946                     nEdges = sc::MatrixEdge::Inside;
2947             }
2948             else
2949             {
2950                 SAL_WARN( "sc", "broken Matrix, Pos: "
2951                     << aPos.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
2952                     << ", MatOrg: "
2953                     << aOrg.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
2954                     << ", MatCols: " << static_cast<sal_Int32>( nC )
2955                     << ", MatRows: " << static_cast<sal_Int32>( nR )
2956                     << ", DiffCols: " << static_cast<sal_Int32>( dC )
2957                     << ", DiffRows: " << static_cast<sal_Int32>( dR ));
2958             }
2959             return nEdges;
2960         }
2961         default:
2962             return sc::MatrixEdge::Nothing;
2963     }
2964 }
2965 
GetErrCode()2966 FormulaError ScFormulaCell::GetErrCode()
2967 {
2968     MaybeInterpret();
2969 
2970     /* FIXME: If ScTokenArray::SetCodeError() was really only for code errors
2971      * and not also abused for signaling other error conditions we could bail
2972      * out even before attempting to interpret broken code. */
2973     FormulaError nErr =  pCode->GetCodeError();
2974     if (nErr != FormulaError::NONE)
2975         return nErr;
2976     return aResult.GetResultError();
2977 }
2978 
GetRawError() const2979 FormulaError ScFormulaCell::GetRawError() const
2980 {
2981     FormulaError nErr =  pCode->GetCodeError();
2982     if (nErr != FormulaError::NONE)
2983         return nErr;
2984     return aResult.GetResultError();
2985 }
2986 
GetErrorOrValue(FormulaError & rErr,double & rVal)2987 bool ScFormulaCell::GetErrorOrValue( FormulaError& rErr, double& rVal )
2988 {
2989     MaybeInterpret();
2990 
2991     rErr = pCode->GetCodeError();
2992     if (rErr != FormulaError::NONE)
2993         return true;
2994 
2995     return aResult.GetErrorOrDouble(rErr, rVal);
2996 }
2997 
GetResult()2998 sc::FormulaResultValue ScFormulaCell::GetResult()
2999 {
3000     MaybeInterpret();
3001 
3002     FormulaError nErr = pCode->GetCodeError();
3003     if (nErr != FormulaError::NONE)
3004         return sc::FormulaResultValue(nErr);
3005 
3006     return aResult.GetResult();
3007 }
3008 
GetResult() const3009 sc::FormulaResultValue ScFormulaCell::GetResult() const
3010 {
3011     FormulaError nErr = pCode->GetCodeError();
3012     if (nErr != FormulaError::NONE)
3013         return sc::FormulaResultValue(nErr);
3014 
3015     return aResult.GetResult();
3016 }
3017 
HasOneReference(ScRange & r) const3018 bool ScFormulaCell::HasOneReference( ScRange& r ) const
3019 {
3020     formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3021     formula::FormulaToken* p = aIter.GetNextReferenceRPN();
3022     if( p && !aIter.GetNextReferenceRPN() )        // only one!
3023     {
3024         SingleDoubleRefProvider aProv( *p );
3025         r.aStart = aProv.Ref1.toAbs(rDocument, aPos);
3026         r.aEnd = aProv.Ref2.toAbs(rDocument, aPos);
3027         return true;
3028     }
3029     else
3030         return false;
3031 }
3032 
3033 bool
HasRefListExpressibleAsOneReference(ScRange & rRange) const3034 ScFormulaCell::HasRefListExpressibleAsOneReference(ScRange& rRange) const
3035 {
3036     /* If there appears just one reference in the formula, it's the same
3037        as HasOneReference(). If there are more of them, they can denote
3038        one range if they are (sole) arguments of one function.
3039        Union of these references must form one range and their
3040        intersection must be empty set.
3041     */
3042 
3043     // Detect the simple case of exactly one reference in advance without all
3044     // overhead.
3045     // #i107741# Doing so actually makes outlines using SUBTOTAL(x;reference)
3046     // work again, where the function does not have only references.
3047     if (HasOneReference( rRange))
3048         return true;
3049 
3050     // Get first reference, if any
3051     formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3052     formula::FormulaToken* const pFirstReference(aIter.GetNextReferenceRPN());
3053     if (pFirstReference)
3054     {
3055         // Collect all consecutive references, starting by the one
3056         // already found
3057         std::vector<formula::FormulaToken*> aReferences { pFirstReference };
3058         FormulaToken* pToken(aIter.NextRPN());
3059         FormulaToken* pFunction(nullptr);
3060         while (pToken)
3061         {
3062             if (lcl_isReference(*pToken))
3063             {
3064                 aReferences.push_back(pToken);
3065                 pToken = aIter.NextRPN();
3066             }
3067             else
3068             {
3069                 if (pToken->IsFunction())
3070                 {
3071                     pFunction = pToken;
3072                 }
3073                 break;
3074             }
3075         }
3076         if (pFunction && !aIter.GetNextReferenceRPN()
3077                 && (pFunction->GetParamCount() == aReferences.size()))
3078         {
3079             return lcl_refListFormsOneRange(rDocument, aPos, aReferences, rRange);
3080         }
3081     }
3082     return false;
3083 }
3084 
HasRelNameReference() const3085 ScFormulaCell::RelNameRef ScFormulaCell::HasRelNameReference() const
3086 {
3087     RelNameRef eRelNameRef = RelNameRef::NONE;
3088     formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3089     formula::FormulaToken* t;
3090     while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
3091     {
3092         switch (t->GetType())
3093         {
3094             case formula::svSingleRef:
3095                 if (t->GetSingleRef()->IsRelName() && eRelNameRef == RelNameRef::NONE)
3096                     eRelNameRef = RelNameRef::SINGLE;
3097             break;
3098             case formula::svDoubleRef:
3099                 if (t->GetDoubleRef()->Ref1.IsRelName() || t->GetDoubleRef()->Ref2.IsRelName())
3100                     // May originate from individual cell names, in which case
3101                     // it needs recompilation.
3102                     return RelNameRef::DOUBLE;
3103                 /* TODO: have an extra flag at ScComplexRefData if range was
3104                  * extended? or too cumbersome? might narrow recompilation to
3105                  * only needed cases.
3106                  * */
3107             break;
3108             default:
3109                 ;   // nothing
3110         }
3111     }
3112     return eRelNameRef;
3113 }
3114 
UpdatePosOnShift(const sc::RefUpdateContext & rCxt)3115 bool ScFormulaCell::UpdatePosOnShift( const sc::RefUpdateContext& rCxt )
3116 {
3117     if (rCxt.meMode != URM_INSDEL)
3118         // Just in case...
3119         return false;
3120 
3121     if (!rCxt.mnColDelta && !rCxt.mnRowDelta && !rCxt.mnTabDelta)
3122         // No movement.
3123         return false;
3124 
3125     if (!rCxt.maRange.Contains(aPos))
3126         return false;
3127 
3128     // This formula cell itself is being shifted during cell range
3129     // insertion or deletion. Update its position.
3130     ScAddress aErrorPos( ScAddress::UNINITIALIZED );
3131     if (!aPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, rCxt.mrDoc))
3132     {
3133         assert(!"can't move ScFormulaCell");
3134     }
3135 
3136     return true;
3137 }
3138 
3139 namespace {
3140 
3141 /**
3142  * Check if we need to re-compile column or row names.
3143  */
checkCompileColRowName(const sc::RefUpdateContext & rCxt,ScDocument & rDoc,const ScTokenArray & rCode,const ScAddress & aOldPos,const ScAddress & aPos,bool bValChanged)3144 bool checkCompileColRowName(
3145     const sc::RefUpdateContext& rCxt, ScDocument& rDoc, const ScTokenArray& rCode,
3146     const ScAddress& aOldPos, const ScAddress& aPos, bool bValChanged)
3147 {
3148     switch (rCxt.meMode)
3149     {
3150         case URM_INSDEL:
3151         {
3152             if (rCxt.mnColDelta <= 0 && rCxt.mnRowDelta <= 0)
3153                 return false;
3154 
3155             formula::FormulaTokenArrayPlainIterator aIter(rCode);
3156             formula::FormulaToken* t;
3157             ScRangePairList* pColList = rDoc.GetColNameRanges();
3158             ScRangePairList* pRowList = rDoc.GetRowNameRanges();
3159             while ((t = aIter.GetNextColRowName()) != nullptr)
3160             {
3161                 ScSingleRefData& rRef = *t->GetSingleRef();
3162                 if (rCxt.mnRowDelta > 0 && rRef.IsColRel())
3163                 {   // ColName
3164                     ScAddress aAdr = rRef.toAbs(rDoc, aPos);
3165                     ScRangePair* pR = pColList->Find( aAdr );
3166                     if ( pR )
3167                     {   // defined
3168                         if (pR->GetRange(1).aStart.Row() == rCxt.maRange.aStart.Row())
3169                             return true;
3170                     }
3171                     else
3172                     {   // on the fly
3173                         if (aAdr.Row() + 1 == rCxt.maRange.aStart.Row())
3174                             return true;
3175                     }
3176                 }
3177                 if (rCxt.mnColDelta > 0 && rRef.IsRowRel())
3178                 {   // RowName
3179                     ScAddress aAdr = rRef.toAbs(rDoc, aPos);
3180                     ScRangePair* pR = pRowList->Find( aAdr );
3181                     if ( pR )
3182                     {   // defined
3183                         if ( pR->GetRange(1).aStart.Col() == rCxt.maRange.aStart.Col())
3184                             return true;
3185                     }
3186                     else
3187                     {   // on the fly
3188                         if (aAdr.Col() + 1 == rCxt.maRange.aStart.Col())
3189                             return true;
3190                     }
3191                 }
3192             }
3193         }
3194         break;
3195         case URM_MOVE:
3196         {   // Recompile for Move/D&D when ColRowName was moved or this Cell
3197             // points to one and was moved.
3198             bool bMoved = (aPos != aOldPos);
3199             if (bMoved)
3200                 return true;
3201 
3202             formula::FormulaTokenArrayPlainIterator aIter(rCode);
3203             const formula::FormulaToken* t = aIter.GetNextColRowName();
3204             for (; t; t = aIter.GetNextColRowName())
3205             {
3206                 const ScSingleRefData& rRef = *t->GetSingleRef();
3207                 ScAddress aAbs = rRef.toAbs(rDoc, aPos);
3208                 if (rDoc.ValidAddress(aAbs))
3209                 {
3210                     if (rCxt.maRange.Contains(aAbs))
3211                         return true;
3212                 }
3213             }
3214         }
3215         break;
3216         case URM_COPY:
3217             return bValChanged;
3218         default:
3219             ;
3220     }
3221 
3222     return false;
3223 }
3224 
setOldCodeToUndo(ScDocument & rUndoDoc,const ScAddress & aUndoPos,const ScTokenArray * pOldCode,FormulaGrammar::Grammar eTempGrammar,ScMatrixMode cMatrixFlag)3225 void setOldCodeToUndo(
3226     ScDocument& rUndoDoc, const ScAddress& aUndoPos, const ScTokenArray* pOldCode, FormulaGrammar::Grammar eTempGrammar, ScMatrixMode cMatrixFlag)
3227 {
3228     // Copy the cell to aUndoPos, which is its current position in the document,
3229     // so this works when UpdateReference is called before moving the cells
3230     // (InsertCells/DeleteCells - aPos is changed above) as well as when UpdateReference
3231     // is called after moving the cells (MoveBlock/PasteFromClip - aOldPos is changed).
3232 
3233     // If there is already a formula cell in the undo document, don't overwrite it,
3234     // the first (oldest) is the important cell.
3235     if (rUndoDoc.GetCellType(aUndoPos) == CELLTYPE_FORMULA)
3236         return;
3237 
3238     ScFormulaCell* pFCell =
3239         new ScFormulaCell(
3240             rUndoDoc, aUndoPos, pOldCode ? *pOldCode : ScTokenArray(rUndoDoc), eTempGrammar, cMatrixFlag);
3241 
3242     pFCell->SetResultToken(nullptr);  // to recognize it as changed later (Cut/Paste!)
3243     rUndoDoc.SetFormulaCell(aUndoPos, pFCell);
3244 }
3245 
3246 }
3247 
UpdateReferenceOnShift(const sc::RefUpdateContext & rCxt,ScDocument * pUndoDoc,const ScAddress * pUndoCellPos)3248 bool ScFormulaCell::UpdateReferenceOnShift(
3249     const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
3250 {
3251     if (rCxt.meMode != URM_INSDEL)
3252         // Just in case...
3253         return false;
3254 
3255     bool bCellStateChanged = false;
3256     ScAddress aUndoPos( aPos );         // position for undo cell in pUndoDoc
3257     if ( pUndoCellPos )
3258         aUndoPos = *pUndoCellPos;
3259     ScAddress aOldPos( aPos );
3260     bCellStateChanged = UpdatePosOnShift(rCxt);
3261 
3262     // Check presence of any references or column row names.
3263     bool bHasRefs = pCode->HasReferences();
3264     bool bHasColRowNames = false;
3265     if (!bHasRefs)
3266     {
3267         bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
3268         bHasRefs = bHasColRowNames;
3269     }
3270     bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
3271 
3272     if (!bHasRefs && !bOnRefMove)
3273         // This formula cell contains no references, nor needs recalculating
3274         // on reference update. Bail out.
3275         return bCellStateChanged;
3276 
3277     std::unique_ptr<ScTokenArray> pOldCode;
3278     if (pUndoDoc)
3279         pOldCode = pCode->Clone();
3280 
3281     bool bValChanged = false;
3282     bool bRefModified = false;
3283     bool bRecompile = bCompile;
3284 
3285     if (bHasRefs)
3286     {
3287         // Update cell or range references.
3288         sc::RefUpdateResult aRes = pCode->AdjustReferenceOnShift(rCxt, aOldPos);
3289         bRefModified = aRes.mbReferenceModified;
3290         bValChanged = aRes.mbValueChanged;
3291         if (aRes.mbNameModified)
3292             bRecompile = true;
3293     }
3294 
3295     if (bValChanged || bRefModified)
3296         bCellStateChanged = true;
3297 
3298     if (bOnRefMove)
3299         // Cell may reference itself, e.g. ocColumn, ocRow without parameter
3300         bOnRefMove = (bValChanged || (aPos != aOldPos) || bRefModified);
3301 
3302     bool bNewListening = false;
3303     bool bInDeleteUndo = false;
3304 
3305     if (bHasRefs)
3306     {
3307         // Upon Insert ColRowNames have to be recompiled in case the
3308         // insertion occurs right in front of the range.
3309         if (bHasColRowNames && !bRecompile)
3310             bRecompile = checkCompileColRowName(rCxt, rDocument, *pCode, aOldPos, aPos, bValChanged);
3311 
3312         ScChangeTrack* pChangeTrack = rDocument.GetChangeTrack();
3313         bInDeleteUndo = (pChangeTrack && pChangeTrack->IsInDeleteUndo());
3314 
3315         // RelNameRefs are always moved
3316         bool bHasRelName = false;
3317         if (!bRecompile)
3318         {
3319             RelNameRef eRelNameRef = HasRelNameReference();
3320             bHasRelName = (eRelNameRef != RelNameRef::NONE);
3321             bRecompile = (eRelNameRef == RelNameRef::DOUBLE);
3322         }
3323         // Reference changed and new listening needed?
3324         // Except in Insert/Delete without specialities.
3325         bNewListening = (bRefModified || bRecompile
3326                 || (bValChanged && bInDeleteUndo) || bHasRelName);
3327 
3328         if ( bNewListening )
3329             EndListeningTo(rDocument, pOldCode.get(), aOldPos);
3330     }
3331 
3332     // NeedDirty for changes except for Copy and Move/Insert without RelNames
3333     bool bNeedDirty = (bValChanged || bRecompile || bOnRefMove);
3334 
3335     if (pUndoDoc && (bValChanged || bOnRefMove))
3336         setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
3337 
3338     bCompile |= bRecompile;
3339     if (bCompile)
3340     {
3341         CompileTokenArray( bNewListening ); // no Listening
3342         bNeedDirty = true;
3343     }
3344 
3345     if ( !bInDeleteUndo )
3346     {   // In ChangeTrack Delete-Reject listeners are established in
3347         // InsertCol/InsertRow
3348         if ( bNewListening )
3349         {
3350             // Inserts/Deletes re-establish listeners after all
3351             // UpdateReference calls.
3352             // All replaced shared formula listeners have to be
3353             // established after an Insert or Delete. Do nothing here.
3354             SetNeedsListening( true);
3355         }
3356     }
3357 
3358     if (bNeedDirty)
3359     {   // Cut off references, invalid or similar?
3360         // Postpone SetDirty() until all listeners have been re-established in
3361         // Inserts/Deletes.
3362         mbPostponedDirty = true;
3363     }
3364 
3365     return bCellStateChanged;
3366 }
3367 
UpdateReferenceOnMove(const sc::RefUpdateContext & rCxt,ScDocument * pUndoDoc,const ScAddress * pUndoCellPos)3368 bool ScFormulaCell::UpdateReferenceOnMove(
3369     const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
3370 {
3371     if (rCxt.meMode != URM_MOVE)
3372         return false;
3373 
3374     ScAddress aUndoPos( aPos );         // position for undo cell in pUndoDoc
3375     if ( pUndoCellPos )
3376         aUndoPos = *pUndoCellPos;
3377     ScAddress aOldPos( aPos );
3378 
3379     bool bCellInMoveTarget = rCxt.maRange.Contains(aPos);
3380 
3381     if ( bCellInMoveTarget )
3382     {
3383         // The cell is being moved or copied to a new position. I guess the
3384         // position has been updated prior to this call?  Determine
3385         // its original position before the move which will be used to adjust
3386         // relative references later.
3387         aOldPos.Set(aPos.Col() - rCxt.mnColDelta, aPos.Row() - rCxt.mnRowDelta, aPos.Tab() - rCxt.mnTabDelta);
3388     }
3389 
3390     // Check presence of any references or column row names.
3391     bool bHasRefs = pCode->HasReferences();
3392     bool bHasColRowNames = false;
3393     if (!bHasRefs)
3394     {
3395         bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
3396         bHasRefs = bHasColRowNames;
3397     }
3398     bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
3399 
3400     if (!bHasRefs && !bOnRefMove)
3401         // This formula cell contains no references, nor needs recalculating
3402         // on reference update. Bail out.
3403         return false;
3404 
3405     bool bCellStateChanged = false;
3406     std::unique_ptr<ScTokenArray> pOldCode;
3407     if (pUndoDoc)
3408         pOldCode = pCode->Clone();
3409 
3410     bool bValChanged = false;
3411     bool bRefModified = false;
3412 
3413     if (bHasRefs)
3414     {
3415         // Update cell or range references.
3416         sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMove(rCxt, aOldPos, aPos);
3417         bRefModified = aRes.mbReferenceModified || aRes.mbNameModified;
3418         bValChanged = aRes.mbValueChanged;
3419         if (aRes.mbNameModified)
3420             // Re-compile to get the RPN token regenerated to reflect updated names.
3421             bCompile = true;
3422     }
3423 
3424     if (bValChanged || bRefModified)
3425         bCellStateChanged = true;
3426 
3427     if (bOnRefMove)
3428         // Cell may reference itself, e.g. ocColumn, ocRow without parameter
3429         bOnRefMove = (bValChanged || (aPos != aOldPos));
3430 
3431     bool bColRowNameCompile = false;
3432     bool bHasRelName = false;
3433     bool bNewListening = false;
3434     bool bInDeleteUndo = false;
3435 
3436     if (bHasRefs)
3437     {
3438         // Upon Insert ColRowNames have to be recompiled in case the
3439         // insertion occurs right in front of the range.
3440         if (bHasColRowNames)
3441             bColRowNameCompile = checkCompileColRowName(rCxt, rDocument, *pCode, aOldPos, aPos, bValChanged);
3442 
3443         ScChangeTrack* pChangeTrack = rDocument.GetChangeTrack();
3444         bInDeleteUndo = (pChangeTrack && pChangeTrack->IsInDeleteUndo());
3445 
3446         // RelNameRefs are always moved
3447         RelNameRef eRelNameRef = HasRelNameReference();
3448         bHasRelName = (eRelNameRef != RelNameRef::NONE);
3449         bCompile |= (eRelNameRef == RelNameRef::DOUBLE);
3450         // Reference changed and new listening needed?
3451         // Except in Insert/Delete without specialties.
3452         bNewListening = (bRefModified || bColRowNameCompile
3453                 || bValChanged || bHasRelName)
3454             // #i36299# Don't duplicate action during cut&paste / drag&drop
3455             // on a cell in the range moved, start/end listeners is done
3456             // via ScDocument::DeleteArea() and ScDocument::CopyFromClip().
3457             && !(rDocument.IsInsertingFromOtherDoc() && rCxt.maRange.Contains(aPos));
3458 
3459         if ( bNewListening )
3460             EndListeningTo(rDocument, pOldCode.get(), aOldPos);
3461     }
3462 
3463     bool bNeedDirty = false;
3464     // NeedDirty for changes except for Copy and Move/Insert without RelNames
3465     if ( bRefModified || bColRowNameCompile ||
3466          (bValChanged && bHasRelName ) || bOnRefMove)
3467         bNeedDirty = true;
3468 
3469     if (pUndoDoc && !bCellInMoveTarget && (bValChanged || bRefModified || bOnRefMove))
3470         setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
3471 
3472     bValChanged = false;
3473 
3474     bCompile = (bCompile || bValChanged || bColRowNameCompile);
3475     if ( bCompile )
3476     {
3477         CompileTokenArray( bNewListening ); // no Listening
3478         bNeedDirty = true;
3479     }
3480 
3481     if ( !bInDeleteUndo )
3482     {   // In ChangeTrack Delete-Reject listeners are established in
3483         // InsertCol/InsertRow
3484         if ( bNewListening )
3485         {
3486             StartListeningTo( rDocument );
3487         }
3488     }
3489 
3490     if (bNeedDirty)
3491     {   // Cut off references, invalid or similar?
3492         sc::AutoCalcSwitch aACSwitch(rDocument, false);
3493         SetDirty();
3494     }
3495 
3496     return bCellStateChanged;
3497 }
3498 
UpdateReferenceOnCopy(const sc::RefUpdateContext & rCxt,ScDocument * pUndoDoc,const ScAddress * pUndoCellPos)3499 bool ScFormulaCell::UpdateReferenceOnCopy(
3500     const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
3501 {
3502     if (rCxt.meMode != URM_COPY)
3503         return false;
3504 
3505     ScAddress aUndoPos( aPos );         // position for undo cell in pUndoDoc
3506     if ( pUndoCellPos )
3507         aUndoPos = *pUndoCellPos;
3508     ScAddress aOldPos( aPos );
3509 
3510     if (rCxt.maRange.Contains(aPos))
3511     {
3512         // The cell is being moved or copied to a new position. I guess the
3513         // position has been updated prior to this call?  Determine
3514         // its original position before the move which will be used to adjust
3515         // relative references later.
3516         aOldPos.Set(aPos.Col() - rCxt.mnColDelta, aPos.Row() - rCxt.mnRowDelta, aPos.Tab() - rCxt.mnTabDelta);
3517     }
3518 
3519     // Check presence of any references or column row names.
3520     bool bHasRefs = pCode->HasReferences();
3521     bool bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
3522     bHasRefs = bHasRefs || bHasColRowNames;
3523     bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
3524 
3525     if (!bHasRefs && !bOnRefMove)
3526         // This formula cell contains no references, nor needs recalculating
3527         // on reference update. Bail out.
3528         return false;
3529 
3530     std::unique_ptr<ScTokenArray> pOldCode;
3531     if (pUndoDoc)
3532         pOldCode = pCode->Clone();
3533 
3534     if (bOnRefMove)
3535         // Cell may reference itself, e.g. ocColumn, ocRow without parameter
3536         bOnRefMove = (aPos != aOldPos);
3537 
3538     bool bNeedDirty = bOnRefMove;
3539 
3540     if (pUndoDoc && bOnRefMove)
3541         setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
3542 
3543     if (bCompile)
3544     {
3545         CompileTokenArray(); // no Listening
3546         bNeedDirty = true;
3547     }
3548 
3549     if (bNeedDirty)
3550     {   // Cut off references, invalid or similar?
3551         sc::AutoCalcSwitch aACSwitch(rDocument, false);
3552         SetDirty();
3553     }
3554 
3555     return false;
3556 }
3557 
UpdateReference(const sc::RefUpdateContext & rCxt,ScDocument * pUndoDoc,const ScAddress * pUndoCellPos)3558 bool ScFormulaCell::UpdateReference(
3559     const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
3560 {
3561     if (rDocument.IsClipOrUndo())
3562         return false;
3563 
3564     if (mxGroup && mxGroup->mpTopCell != this)
3565     {
3566         // This is not a top cell of a formula group. Don't update references.
3567 
3568         switch (rCxt.meMode)
3569         {
3570             case URM_INSDEL:
3571                 return UpdatePosOnShift(rCxt);
3572             default:
3573                 ;
3574         }
3575         return false;
3576     }
3577 
3578     switch (rCxt.meMode)
3579     {
3580         case URM_INSDEL:
3581             return UpdateReferenceOnShift(rCxt, pUndoDoc, pUndoCellPos);
3582         case URM_MOVE:
3583             return UpdateReferenceOnMove(rCxt, pUndoDoc, pUndoCellPos);
3584         case URM_COPY:
3585             return UpdateReferenceOnCopy(rCxt, pUndoDoc, pUndoCellPos);
3586         default:
3587             ;
3588     }
3589 
3590     return false;
3591 }
3592 
UpdateInsertTab(const sc::RefUpdateInsertTabContext & rCxt)3593 void ScFormulaCell::UpdateInsertTab( const sc::RefUpdateInsertTabContext& rCxt )
3594 {
3595     // Adjust tokens only when it's not grouped or grouped top cell.
3596     bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
3597     bool bPosChanged = (rCxt.mnInsertPos <= aPos.Tab());
3598     if (rDocument.IsClipOrUndo() || !pCode->HasReferences())
3599     {
3600         if (bPosChanged)
3601             aPos.IncTab(rCxt.mnSheets);
3602 
3603         return;
3604     }
3605 
3606     EndListeningTo( rDocument );
3607     ScAddress aOldPos = aPos;
3608     // IncTab _after_ EndListeningTo and _before_ Compiler UpdateInsertTab!
3609     if (bPosChanged)
3610         aPos.IncTab(rCxt.mnSheets);
3611 
3612     if (!bAdjustCode)
3613         return;
3614 
3615     sc::RefUpdateResult aRes = pCode->AdjustReferenceOnInsertedTab(rCxt, aOldPos);
3616     if (aRes.mbNameModified)
3617         // Re-compile after new sheet(s) have been inserted.
3618         bCompile = true;
3619 
3620     // no StartListeningTo because the new sheets have not been inserted yet.
3621 }
3622 
UpdateDeleteTab(const sc::RefUpdateDeleteTabContext & rCxt)3623 void ScFormulaCell::UpdateDeleteTab( const sc::RefUpdateDeleteTabContext& rCxt )
3624 {
3625     // Adjust tokens only when it's not grouped or grouped top cell.
3626     bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
3627     bool bPosChanged = (aPos.Tab() >= rCxt.mnDeletePos + rCxt.mnSheets);
3628     if (rDocument.IsClipOrUndo() || !pCode->HasReferences())
3629     {
3630         if (bPosChanged)
3631             aPos.IncTab(-1*rCxt.mnSheets);
3632         return;
3633     }
3634 
3635     EndListeningTo( rDocument );
3636     // IncTab _after_ EndListeningTo and _before_ Compiler UpdateDeleteTab!
3637     ScAddress aOldPos = aPos;
3638     if (bPosChanged)
3639         aPos.IncTab(-1*rCxt.mnSheets);
3640 
3641     if (!bAdjustCode)
3642         return;
3643 
3644     sc::RefUpdateResult aRes = pCode->AdjustReferenceOnDeletedTab(rCxt, aOldPos);
3645     if (aRes.mbNameModified)
3646         // Re-compile after sheet(s) have been deleted.
3647         bCompile = true;
3648 }
3649 
UpdateMoveTab(const sc::RefUpdateMoveTabContext & rCxt,SCTAB nTabNo)3650 void ScFormulaCell::UpdateMoveTab( const sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo )
3651 {
3652     // Adjust tokens only when it's not grouped or grouped top cell.
3653     bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
3654 
3655     if (!pCode->HasReferences() || rDocument.IsClipOrUndo())
3656     {
3657         aPos.SetTab(nTabNo);
3658         return;
3659     }
3660 
3661     EndListeningTo(rDocument);
3662     ScAddress aOldPos = aPos;
3663     // SetTab _after_ EndListeningTo and _before_ Compiler UpdateMoveTab !
3664     aPos.SetTab(nTabNo);
3665 
3666     // no StartListeningTo because pTab[nTab] not yet correct!
3667 
3668     if (!bAdjustCode)
3669         return;
3670 
3671     sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMovedTab(rCxt, aOldPos);
3672     if (aRes.mbNameModified)
3673         // Re-compile after sheet(s) have been deleted.
3674         bCompile = true;
3675 }
3676 
UpdateInsertTabAbs(SCTAB nTable)3677 void ScFormulaCell::UpdateInsertTabAbs(SCTAB nTable)
3678 {
3679     if (rDocument.IsClipOrUndo())
3680         return;
3681 
3682     bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
3683     if (!bAdjustCode)
3684         return;
3685 
3686     formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3687     formula::FormulaToken* p = aIter.GetNextReferenceRPN();
3688     while (p)
3689     {
3690         ScSingleRefData& rRef1 = *p->GetSingleRef();
3691         if (!rRef1.IsTabRel() && nTable <= rRef1.Tab())
3692             rRef1.IncTab(1);
3693         if (p->GetType() == formula::svDoubleRef)
3694         {
3695             ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2;
3696             if (!rRef2.IsTabRel() && nTable <= rRef2.Tab())
3697                 rRef2.IncTab(1);
3698         }
3699         p = aIter.GetNextReferenceRPN();
3700     }
3701 }
3702 
TestTabRefAbs(SCTAB nTable)3703 bool ScFormulaCell::TestTabRefAbs(SCTAB nTable)
3704 {
3705     if (rDocument.IsClipOrUndo())
3706         return false;
3707 
3708     bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
3709     if (!bAdjustCode)
3710         return false;
3711 
3712     bool bRet = false;
3713     formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3714     formula::FormulaToken* p = aIter.GetNextReferenceRPN();
3715     while (p)
3716     {
3717         ScSingleRefData& rRef1 = *p->GetSingleRef();
3718         if (!rRef1.IsTabRel())
3719         {
3720             if (nTable != rRef1.Tab())
3721                 bRet = true;
3722             else if (nTable != aPos.Tab())
3723                 rRef1.SetAbsTab(aPos.Tab());
3724         }
3725         if (p->GetType() == formula::svDoubleRef)
3726         {
3727             ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2;
3728             if (!rRef2.IsTabRel())
3729             {
3730                 if(nTable != rRef2.Tab())
3731                     bRet = true;
3732                 else if (nTable != aPos.Tab())
3733                     rRef2.SetAbsTab(aPos.Tab());
3734             }
3735         }
3736         p = aIter.GetNextReferenceRPN();
3737     }
3738     return bRet;
3739 }
3740 
UpdateCompile(bool bForceIfNameInUse)3741 void ScFormulaCell::UpdateCompile( bool bForceIfNameInUse )
3742 {
3743     if ( bForceIfNameInUse && !bCompile )
3744         bCompile = pCode->HasNameOrColRowName();
3745     if ( bCompile )
3746         pCode->SetCodeError( FormulaError::NONE );   // make sure it will really be compiled
3747     CompileTokenArray();
3748 }
3749 
lcl_TransposeReference(ScSingleRefData & rRef)3750 static void lcl_TransposeReference(ScSingleRefData& rRef)
3751 {
3752     // References to or over filtered rows are not adjusted
3753     // analog to the normal (non-transposed) case
3754     SCCOLROW nTemp = rRef.Col();
3755     rRef.SetRelCol(rRef.Row());
3756     rRef.SetRelRow(nTemp);
3757 }
3758 
3759 // Reference transposition is only called in Clipboard Document
TransposeReference()3760 void ScFormulaCell::TransposeReference()
3761 {
3762     bool bFound = false;
3763     formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3764     formula::FormulaToken* t;
3765     while ( ( t = aIter.GetNextReference() ) != nullptr )
3766     {
3767         ScSingleRefData& rRef1 = *t->GetSingleRef();
3768         if ( rRef1.IsColRel() && rRef1.IsRowRel() )
3769         {
3770             bool bDouble = (t->GetType() == formula::svDoubleRef);
3771             ScSingleRefData& rRef2 = (bDouble ? t->GetDoubleRef()->Ref2 : rRef1);
3772             if ( !bDouble || (rRef2.IsColRel() && rRef2.IsRowRel()) )
3773             {
3774                 lcl_TransposeReference(rRef1);
3775 
3776                 if ( bDouble )
3777                     lcl_TransposeReference(rRef2);
3778 
3779                 bFound = true;
3780             }
3781         }
3782     }
3783 
3784     if (bFound)
3785         bCompile = true;
3786 }
3787 
UpdateTranspose(const ScRange & rSource,const ScAddress & rDest,ScDocument * pUndoDoc)3788 void ScFormulaCell::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest,
3789                                         ScDocument* pUndoDoc )
3790 {
3791     EndListeningTo( rDocument );
3792 
3793     ScAddress aOldPos = aPos;
3794     bool bPosChanged = false; // Whether this cell has been moved
3795 
3796     // Dest range is transposed
3797     ScRange aDestRange( rDest, ScAddress(
3798                 static_cast<SCCOL>(rDest.Col() + rSource.aEnd.Row() - rSource.aStart.Row()),
3799                 static_cast<SCROW>(rDest.Row() + rSource.aEnd.Col() - rSource.aStart.Col()),
3800                 rDest.Tab() + rSource.aEnd.Tab() - rSource.aStart.Tab() ) );
3801 
3802     // cell within range
3803     if ( aDestRange.Contains( aOldPos ) )
3804     {
3805         // References of these cells were not changed by ScTokenArray::AdjustReferenceOnMove()
3806         // Count back Positions
3807         SCCOL nRelPosX = aOldPos.Col();
3808         SCROW nRelPosY = aOldPos.Row();
3809         SCTAB nRelPosZ = aOldPos.Tab();
3810         ScRefUpdate::DoTranspose( nRelPosX, nRelPosY, nRelPosZ, rDocument, aDestRange, rSource.aStart );
3811         aOldPos.Set( nRelPosX, nRelPosY, nRelPosZ );
3812         bPosChanged = true;
3813     }
3814 
3815     std::unique_ptr<ScTokenArray> pOld;
3816     if (pUndoDoc)
3817         pOld = pCode->Clone();
3818     bool bRefChanged = false;
3819 
3820     formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3821     formula::FormulaToken* t;
3822     while( (t = aIter.GetNextReferenceOrName()) != nullptr )
3823     {
3824         if( t->GetOpCode() == ocName )
3825         {
3826             const ScRangeData* pName = rDocument.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
3827             if (pName && pName->IsModified())
3828                 bRefChanged = true;
3829         }
3830         else if( t->GetType() != svIndex )
3831         {
3832             SingleDoubleRefModifier aMod(*t);
3833             ScComplexRefData& rRef = aMod.Ref();
3834             ScRange aAbs = rRef.toAbs(rDocument, aOldPos);
3835             bool bMod = (ScRefUpdate::UpdateTranspose(rDocument, rSource, rDest, aAbs) != UR_NOTHING || bPosChanged);
3836             if (bMod)
3837             {
3838                 rRef.SetRange(rDocument.GetSheetLimits(), aAbs, aPos); // based on the new anchor position.
3839                 bRefChanged = true;
3840 
3841                 // Absolute sheet reference => set 3D flag.
3842                 // More than one sheet referenced => has to have both 3D flags.
3843                 // If end part has 3D flag => start part must have it too.
3844                 // The same behavior as in ScTokenArray::AdjustReferenceOnMove() is used for 3D-Flags.
3845                 rRef.Ref2.SetFlag3D(aAbs.aStart.Tab() != aAbs.aEnd.Tab() || !rRef.Ref2.IsTabRel());
3846                 rRef.Ref1.SetFlag3D(
3847                     (rSource.aStart.Tab() != rDest.Tab() && !bPosChanged)
3848                     || !rRef.Ref1.IsTabRel() || rRef.Ref2.IsFlag3D());
3849             }
3850         }
3851     }
3852 
3853     if (bRefChanged)
3854     {
3855         if (pUndoDoc)
3856         {
3857             // Similar to setOldCodeToUndo(), but it cannot be used due to the check
3858             // pUndoDoc->GetCellType(aPos) == CELLTYPE_FORMULA
3859             ScFormulaCell* pFCell = new ScFormulaCell(
3860                     *pUndoDoc, aPos, pOld ? *pOld : ScTokenArray(*pUndoDoc), eTempGrammar, cMatrixFlag);
3861 
3862             pFCell->aResult.SetToken( nullptr);  // to recognize it as changed later (Cut/Paste!)
3863             pUndoDoc->SetFormulaCell(aPos, pFCell);
3864         }
3865 
3866         bCompile = true;
3867         CompileTokenArray(); // also call StartListeningTo
3868         SetDirty();
3869     }
3870     else
3871         StartListeningTo( rDocument ); // Listener as previous
3872 }
3873 
UpdateGrow(const ScRange & rArea,SCCOL nGrowX,SCROW nGrowY)3874 void ScFormulaCell::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
3875 {
3876     EndListeningTo( rDocument );
3877 
3878     bool bRefChanged = false;
3879 
3880     formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3881     formula::FormulaToken* t;
3882 
3883     while( (t = aIter.GetNextReferenceOrName()) != nullptr )
3884     {
3885         if( t->GetOpCode() == ocName )
3886         {
3887             const ScRangeData* pName = rDocument.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
3888             if (pName && pName->IsModified())
3889                 bRefChanged = true;
3890         }
3891         else if( t->GetType() != svIndex )
3892         {
3893             SingleDoubleRefModifier aMod(*t);
3894             ScComplexRefData& rRef = aMod.Ref();
3895             ScRange aAbs = rRef.toAbs(rDocument, aPos);
3896             bool bMod = (ScRefUpdate::UpdateGrow(rArea, nGrowX, nGrowY, aAbs) != UR_NOTHING);
3897             if (bMod)
3898             {
3899                 rRef.SetRange(rDocument.GetSheetLimits(), aAbs, aPos);
3900                 bRefChanged = true;
3901             }
3902         }
3903     }
3904 
3905     if (bRefChanged)
3906     {
3907         bCompile = true;
3908         CompileTokenArray(); // Also call StartListeningTo
3909         SetDirty();
3910     }
3911     else
3912         StartListeningTo( rDocument ); // Listener as previous
3913 }
3914 
3915 // See also ScDocument::FindRangeNamesReferencingSheet()
lcl_FindRangeNamesInUse(sc::UpdatedRangeNames & rIndexes,const ScTokenArray * pCode,const ScDocument & rDoc,int nRecursion)3916 static void lcl_FindRangeNamesInUse(sc::UpdatedRangeNames& rIndexes, const ScTokenArray* pCode, const ScDocument& rDoc,
3917         int nRecursion)
3918 {
3919     FormulaTokenArrayPlainIterator aIter(*pCode);
3920     for (FormulaToken* p = aIter.First(); p; p = aIter.Next())
3921     {
3922         if (p->GetOpCode() == ocName)
3923         {
3924             sal_uInt16 nTokenIndex = p->GetIndex();
3925             SCTAB nTab = p->GetSheet();
3926             rIndexes.setUpdatedName( nTab, nTokenIndex);
3927 
3928             if (nRecursion < 126)   // whatever... 42*3
3929             {
3930                 ScRangeData* pSubName = rDoc.FindRangeNameBySheetAndIndex( nTab, nTokenIndex);
3931                 if (pSubName)
3932                     lcl_FindRangeNamesInUse(rIndexes, pSubName->GetCode(), rDoc, nRecursion+1);
3933             }
3934         }
3935     }
3936 }
3937 
FindRangeNamesInUse(sc::UpdatedRangeNames & rIndexes) const3938 void ScFormulaCell::FindRangeNamesInUse(sc::UpdatedRangeNames& rIndexes) const
3939 {
3940     lcl_FindRangeNamesInUse( rIndexes, pCode, rDocument, 0);
3941 }
3942 
SetChanged(bool b)3943 void ScFormulaCell::SetChanged(bool b)
3944 {
3945     bChanged = b;
3946 }
3947 
SetCode(std::unique_ptr<ScTokenArray> pNew)3948 void ScFormulaCell::SetCode( std::unique_ptr<ScTokenArray> pNew )
3949 {
3950     assert(!mxGroup); // Don't call this if it's shared.
3951     delete pCode;
3952     pCode = pNew.release(); // takes ownership.
3953 }
3954 
SetRunning(bool bVal)3955 void ScFormulaCell::SetRunning( bool bVal )
3956 {
3957     bRunning = bVal;
3958 }
3959 
CompileDBFormula(sc::CompileFormulaContext & rCxt)3960 void ScFormulaCell::CompileDBFormula( sc::CompileFormulaContext& rCxt )
3961 {
3962     FormulaTokenArrayPlainIterator aIter(*pCode);
3963     for( FormulaToken* p = aIter.First(); p; p = aIter.Next() )
3964     {
3965         OpCode eOp = p->GetOpCode();
3966         if ( eOp == ocDBArea || eOp == ocTableRef )
3967         {
3968             bCompile = true;
3969             CompileTokenArray(rCxt);
3970             SetDirty();
3971             break;
3972         }
3973     }
3974 }
3975 
CompileColRowNameFormula(sc::CompileFormulaContext & rCxt)3976 void ScFormulaCell::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt )
3977 {
3978     FormulaTokenArrayPlainIterator aIter(*pCode);
3979     for ( FormulaToken* p = aIter.First(); p; p = aIter.Next() )
3980     {
3981         if ( p->GetOpCode() == ocColRowName )
3982         {
3983             bCompile = true;
3984             CompileTokenArray(rCxt);
3985             SetDirty();
3986             break;
3987         }
3988     }
3989 }
3990 
SetPrevious(ScFormulaCell * pF)3991 void            ScFormulaCell::SetPrevious( ScFormulaCell* pF )    { pPrevious = pF; }
SetNext(ScFormulaCell * pF)3992 void            ScFormulaCell::SetNext( ScFormulaCell* pF )        { pNext = pF; }
SetPreviousTrack(ScFormulaCell * pF)3993 void            ScFormulaCell::SetPreviousTrack( ScFormulaCell* pF )   { pPreviousTrack = pF; }
SetNextTrack(ScFormulaCell * pF)3994 void            ScFormulaCell::SetNextTrack( ScFormulaCell* pF )       { pNextTrack = pF; }
3995 
CreateCellGroup(SCROW nLen,bool bInvariant)3996 ScFormulaCellGroupRef ScFormulaCell::CreateCellGroup( SCROW nLen, bool bInvariant )
3997 {
3998     if (mxGroup)
3999     {
4000         // You can't create a new group if the cell is already a part of a group.
4001         // Is this a sign of some inconsistent or incorrect data structures? Or normal?
4002         SAL_INFO("sc.opencl", "You can't create a new group if the cell is already a part of a group");
4003         return ScFormulaCellGroupRef();
4004     }
4005 
4006     mxGroup.reset(new ScFormulaCellGroup);
4007     mxGroup->mpTopCell = this;
4008     mxGroup->mbInvariant = bInvariant;
4009     mxGroup->mnLength = nLen;
4010     mxGroup->mpCode = std::move(*pCode); // Move this to the shared location.
4011     delete pCode;
4012     pCode = &*mxGroup->mpCode;
4013     return mxGroup;
4014 }
4015 
SetCellGroup(const ScFormulaCellGroupRef & xRef)4016 void ScFormulaCell::SetCellGroup( const ScFormulaCellGroupRef &xRef )
4017 {
4018     if (!xRef)
4019     {
4020         // Make this cell a non-grouped cell.
4021         if (mxGroup)
4022             pCode = mxGroup->mpCode->Clone().release();
4023 
4024         mxGroup = xRef;
4025         return;
4026     }
4027 
4028     // Group object has shared token array.
4029     if (!mxGroup)
4030         // Currently not shared. Delete the existing token array first.
4031         delete pCode;
4032 
4033     mxGroup = xRef;
4034     pCode = &*mxGroup->mpCode;
4035     mxGroup->mnWeight = 0;      // invalidate
4036 }
4037 
CompareByTokenArray(const ScFormulaCell & rOther) const4038 ScFormulaCell::CompareState ScFormulaCell::CompareByTokenArray( const ScFormulaCell& rOther ) const
4039 {
4040     // no Matrix formulae yet.
4041     if ( GetMatrixFlag() != ScMatrixMode::NONE )
4042         return NotEqual;
4043 
4044     // are these formulas at all similar ?
4045     if ( GetHash() != rOther.GetHash() )
4046         return NotEqual;
4047 
4048     if (!pCode->IsShareable() || !rOther.pCode->IsShareable())
4049         return NotEqual;
4050 
4051     FormulaToken **pThis = pCode->GetCode();
4052     sal_uInt16     nThisLen = pCode->GetCodeLen();
4053     FormulaToken **pOther = rOther.pCode->GetCode();
4054     sal_uInt16     nOtherLen = rOther.pCode->GetCodeLen();
4055 
4056     if ( !pThis || !pOther )
4057     {
4058         // Error: no compiled code for cells !"
4059         return NotEqual;
4060     }
4061 
4062     if ( nThisLen != nOtherLen )
4063         return NotEqual;
4064 
4065     // No tokens can be an error cell so check error code, otherwise we could
4066     // end up with a series of equal error values instead of individual error
4067     // values. Also if for any reason different errors are set even if all
4068     // tokens are equal, the cells are not equal.
4069     if (pCode->GetCodeError() != rOther.pCode->GetCodeError())
4070         return NotEqual;
4071 
4072     bool bInvariant = true;
4073 
4074     // check we are basically the same function
4075     for ( sal_uInt16 i = 0; i < nThisLen; i++ )
4076     {
4077         formula::FormulaToken *pThisTok = pThis[i];
4078         formula::FormulaToken *pOtherTok = pOther[i];
4079 
4080         if ( pThisTok->GetType() != pOtherTok->GetType() ||
4081              pThisTok->GetOpCode() != pOtherTok->GetOpCode() ||
4082              pThisTok->GetParamCount() != pOtherTok->GetParamCount() )
4083         {
4084             // Incompatible type, op-code or param counts.
4085             return NotEqual;
4086         }
4087 
4088         switch (pThisTok->GetType())
4089         {
4090             case formula::svMatrix:
4091             case formula::svExternalSingleRef:
4092             case formula::svExternalDoubleRef:
4093                 // Ignoring matrix and external references for now.
4094                 return NotEqual;
4095 
4096             case formula::svSingleRef:
4097             {
4098                 // Single cell reference.
4099                 const ScSingleRefData& rRef = *pThisTok->GetSingleRef();
4100                 if (rRef != *pOtherTok->GetSingleRef())
4101                     return NotEqual;
4102 
4103                 if (rRef.IsRowRel())
4104                     bInvariant = false;
4105             }
4106             break;
4107             case formula::svDoubleRef:
4108             {
4109                 // Range reference.
4110                 const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef();
4111                 const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2();
4112                 if (rRef1 != *pOtherTok->GetSingleRef())
4113                     return NotEqual;
4114 
4115                 if (rRef2 != *pOtherTok->GetSingleRef2())
4116                     return NotEqual;
4117 
4118                 if (rRef1.IsRowRel())
4119                     bInvariant = false;
4120 
4121                 if (rRef2.IsRowRel())
4122                     bInvariant = false;
4123             }
4124             break;
4125             case formula::svDouble:
4126             {
4127                 if(!rtl::math::approxEqual(pThisTok->GetDouble(), pOtherTok->GetDouble()))
4128                     return NotEqual;
4129             }
4130             break;
4131             case formula::svString:
4132             {
4133                 if(pThisTok->GetString() != pOtherTok->GetString())
4134                     return NotEqual;
4135             }
4136             break;
4137             case formula::svIndex:
4138             {
4139                 if(pThisTok->GetIndex() != pOtherTok->GetIndex() || pThisTok->GetSheet() != pOtherTok->GetSheet())
4140                     return NotEqual;
4141             }
4142             break;
4143             case formula::svByte:
4144             {
4145                 if(pThisTok->GetByte() != pOtherTok->GetByte())
4146                     return NotEqual;
4147             }
4148             break;
4149             case formula::svExternal:
4150             {
4151                 if (pThisTok->GetExternal() != pOtherTok->GetExternal())
4152                     return NotEqual;
4153 
4154                 if (pThisTok->GetByte() != pOtherTok->GetByte())
4155                     return NotEqual;
4156             }
4157             break;
4158             case formula::svError:
4159             {
4160                 if (pThisTok->GetError() != pOtherTok->GetError())
4161                     return NotEqual;
4162             }
4163             break;
4164             default:
4165                 ;
4166         }
4167     }
4168 
4169     // If still the same, check lexical names as different names may result in
4170     // identical RPN code.
4171 
4172     pThis = pCode->GetArray();
4173     nThisLen = pCode->GetLen();
4174     pOther = rOther.pCode->GetArray();
4175     nOtherLen = rOther.pCode->GetLen();
4176 
4177     if ( !pThis || !pOther )
4178     {
4179         // Error: no code for cells !"
4180         return NotEqual;
4181     }
4182 
4183     if ( nThisLen != nOtherLen )
4184         return NotEqual;
4185 
4186     for ( sal_uInt16 i = 0; i < nThisLen; i++ )
4187     {
4188         formula::FormulaToken *pThisTok = pThis[i];
4189         formula::FormulaToken *pOtherTok = pOther[i];
4190 
4191         if ( pThisTok->GetType() != pOtherTok->GetType() ||
4192              pThisTok->GetOpCode() != pOtherTok->GetOpCode() ||
4193              pThisTok->GetParamCount() != pOtherTok->GetParamCount() )
4194         {
4195             // Incompatible type, op-code or param counts.
4196             return NotEqual;
4197         }
4198 
4199         switch (pThisTok->GetType())
4200         {
4201             // ScCompiler::HandleIIOpCode() may optimize some refs only in RPN code,
4202             // resulting in identical RPN references that could lead to creating
4203             // a formula group from formulas that should not be merged into a group,
4204             // so check also the formula itself.
4205             case formula::svSingleRef:
4206             {
4207                 // Single cell reference.
4208                 const ScSingleRefData& rRef = *pThisTok->GetSingleRef();
4209                 if (rRef != *pOtherTok->GetSingleRef())
4210                     return NotEqual;
4211 
4212                 if (rRef.IsRowRel())
4213                     bInvariant = false;
4214             }
4215             break;
4216             case formula::svDoubleRef:
4217             {
4218                 // Range reference.
4219                 const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef();
4220                 const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2();
4221                 if (rRef1 != *pOtherTok->GetSingleRef())
4222                     return NotEqual;
4223 
4224                 if (rRef2 != *pOtherTok->GetSingleRef2())
4225                     return NotEqual;
4226 
4227                 if (rRef1.IsRowRel())
4228                     bInvariant = false;
4229 
4230                 if (rRef2.IsRowRel())
4231                     bInvariant = false;
4232             }
4233             break;
4234             // All index tokens are names. Different categories already had
4235             // different OpCode values.
4236             case formula::svIndex:
4237                 {
4238                     if (pThisTok->GetIndex() != pOtherTok->GetIndex())
4239                         return NotEqual;
4240                     switch (pThisTok->GetOpCode())
4241                     {
4242                         case ocTableRef:
4243                             // nothing, sheet value assumed as -1, silence
4244                             // ScTableRefToken::GetSheet() SAL_WARN about
4245                             // unhandled
4246                             ;
4247                             break;
4248                         default:    // ocName, ocDBArea
4249                             if (pThisTok->GetSheet() != pOtherTok->GetSheet())
4250                                 return NotEqual;
4251                     }
4252                 }
4253                 break;
4254             default:
4255                 ;
4256         }
4257     }
4258 
4259     return bInvariant ? EqualInvariant : EqualRelativeRef;
4260 }
4261 
4262 namespace {
4263 
4264 // Split N into optimally equal-sized pieces, each not larger than K.
4265 // Return value P is number of pieces. A returns the number of pieces
4266 // one larger than N/P, 0..P-1.
4267 
splitup(int N,int K,int & A)4268 int splitup(int N, int K, int& A)
4269 {
4270     assert(N > 0);
4271     assert(K > 0);
4272 
4273     A = 0;
4274 
4275     if (N <= K)
4276         return 1;
4277 
4278     const int ideal_num_parts = N / K;
4279     if (ideal_num_parts * K == N)
4280         return ideal_num_parts;
4281 
4282     const int num_parts = ideal_num_parts + 1;
4283     const int nominal_part_size = N / num_parts;
4284 
4285     A = N - num_parts * nominal_part_size;
4286 
4287     return num_parts;
4288 }
4289 
4290 struct ScDependantsCalculator
4291 {
4292     ScDocument& mrDoc;
4293     const ScTokenArray& mrCode;
4294     const ScFormulaCellGroupRef& mxGroup;
4295     const SCROW mnLen;
4296     const ScAddress& mrPos;
4297     const bool mFromFirstRow;
4298     const SCROW mnStartOffset;
4299     const SCROW mnEndOffset;
4300     const SCROW mnSpanLen;
4301 
ScDependantsCalculator__anonb316ae920511::ScDependantsCalculator4302     ScDependantsCalculator(ScDocument& rDoc, const ScTokenArray& rCode, const ScFormulaCell& rCell,
4303             const ScAddress& rPos, bool fromFirstRow, SCROW nStartOffset, SCROW nEndOffset) :
4304         mrDoc(rDoc),
4305         mrCode(rCode),
4306         mxGroup(rCell.GetCellGroup()),
4307         mnLen(mxGroup->mnLength),
4308         mrPos(rPos),
4309         // ScColumn::FetchVectorRefArray() always fetches data from row 0, even if the data is used
4310         // only from further rows. This data fetching could also lead to Interpret() calls, so
4311         // in OpenCL mode the formula in practice depends on those cells too.
4312         mFromFirstRow(fromFirstRow),
4313         mnStartOffset(nStartOffset),
4314         mnEndOffset(nEndOffset),
4315         mnSpanLen(nEndOffset - nStartOffset + 1)
4316     {
4317     }
4318 
4319     // FIXME: copy-pasted from ScGroupTokenConverter. factor out somewhere else
4320     // (note already modified a bit, mFromFirstRow)
4321 
4322     // I think what this function does is to check whether the relative row reference nRelRow points
4323     // to a row that is inside the range of rows covered by the formula group.
4324 
isSelfReferenceRelative__anonb316ae920511::ScDependantsCalculator4325     bool isSelfReferenceRelative(const ScAddress& rRefPos, SCROW nRelRow)
4326     {
4327         if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
4328             return false;
4329 
4330         SCROW nEndRow = mrPos.Row() + mnLen - 1;
4331 
4332         if (nRelRow <= 0)
4333         {
4334             SCROW nTest = nEndRow;
4335             nTest += nRelRow;
4336             if (nTest >= mrPos.Row())
4337                 return true;
4338         }
4339         else
4340         {
4341             SCROW nTest = mrPos.Row(); // top row.
4342             nTest += nRelRow;
4343             if (nTest <= nEndRow)
4344                 return true;
4345             // If pointing below the formula, it's always included if going from first row.
4346             if (mFromFirstRow)
4347                 return true;
4348         }
4349 
4350         return false;
4351     }
4352 
4353     // FIXME: another copy-paste
4354 
4355     // And this correspondingly checks whether an absolute row is inside the range of rows covered
4356     // by the formula group.
4357 
isSelfReferenceAbsolute__anonb316ae920511::ScDependantsCalculator4358     bool isSelfReferenceAbsolute(const ScAddress& rRefPos)
4359     {
4360         if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
4361             return false;
4362 
4363         SCROW nEndRow = mrPos.Row() + mnLen - 1;
4364 
4365         if (rRefPos.Row() < mrPos.Row())
4366             return false;
4367 
4368         // If pointing below the formula, it's always included if going from first row.
4369         if (rRefPos.Row() > nEndRow && !mFromFirstRow)
4370             return false;
4371 
4372         return true;
4373     }
4374 
4375     // Checks if the doubleref engulfs all of formula group cells
4376     // Note : does not check if there is a partial overlap, that can be done by calling
4377     //        isSelfReference[Absolute|Relative]() on both the start and end of the double ref
isDoubleRefSpanGroupRange__anonb316ae920511::ScDependantsCalculator4378     bool isDoubleRefSpanGroupRange(const ScRange& rAbs, bool bIsRef1RowRel, bool bIsRef2RowRel)
4379     {
4380         if (rAbs.aStart.Col() > mrPos.Col() || rAbs.aEnd.Col() < mrPos.Col()
4381             || rAbs.aStart.Tab() > mrPos.Tab() || rAbs.aEnd.Tab() < mrPos.Tab())
4382         {
4383             return false;
4384         }
4385 
4386         SCROW nStartRow    = mrPos.Row();
4387         SCROW nEndRow      = nStartRow + mnLen - 1;
4388         SCROW nRefStartRow = rAbs.aStart.Row();
4389         SCROW nRefEndRow   = rAbs.aEnd.Row();
4390 
4391         if (bIsRef1RowRel && bIsRef2RowRel &&
4392             ((nRefStartRow <= nStartRow && nRefEndRow >= nEndRow) ||
4393              ((nRefStartRow + mnLen - 1) <= nStartRow &&
4394               (nRefEndRow + mnLen - 1) >= nEndRow)))
4395             return true;
4396 
4397         if (!bIsRef1RowRel && nRefStartRow <= nStartRow &&
4398             (nRefEndRow >= nEndRow || (nRefEndRow + mnLen - 1) >= nEndRow))
4399             return true;
4400 
4401         if (!bIsRef2RowRel &&
4402             nRefStartRow <= nStartRow && nRefEndRow >= nEndRow)
4403             return true;
4404 
4405         // If going from first row, the referenced range must be entirely above the formula,
4406         // otherwise the formula would be included.
4407         if (mFromFirstRow && nRefEndRow >= nStartRow)
4408             return true;
4409 
4410         return false;
4411     }
4412 
4413     // FIXME: another copy-paste
trimLength__anonb316ae920511::ScDependantsCalculator4414     SCROW trimLength(SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCROW nRowLen)
4415     {
4416         SCROW nLastRow = nRow + nRowLen - 1; // current last row.
4417         nLastRow = mrDoc.GetLastDataRow(nTab, nCol1, nCol2, nLastRow);
4418         if (nLastRow < (nRow + nRowLen - 1))
4419         {
4420             // This can end up negative! Was that the original intent, or
4421             // is it accidental? Was it not like that originally but the
4422             // surrounding conditions changed?
4423             nRowLen = nLastRow - nRow + 1;
4424             // Anyway, let's assume it doesn't make sense to return a
4425             // negative or zero value here.
4426             if (nRowLen <= 0)
4427                 nRowLen = 1;
4428         }
4429         else if (nLastRow == 0)
4430             // Column is empty.
4431             nRowLen = 1;
4432 
4433         return nRowLen;
4434     }
4435 
4436     // Because Lookup will extend the Result Vector under certain circumstances listed at:
4437     // https://wiki.documentfoundation.org/Documentation/Calc_Functions/LOOKUP
4438     // then if the Lookup has a Result Vector only accept the Lookup for parallelization
4439     // of the Result Vector has the same dimensions as the Search Vector.
LookupResultVectorMismatch__anonb316ae920511::ScDependantsCalculator4440     bool LookupResultVectorMismatch(sal_Int32 nTokenIdx)
4441     {
4442         if (nTokenIdx >= 3)
4443         {
4444             FormulaToken** pRPNArray = mrCode.GetCode();
4445             if (pRPNArray[nTokenIdx - 1]->GetOpCode() == ocPush &&   // <- result vector
4446                 pRPNArray[nTokenIdx - 2]->GetOpCode() == ocPush &&   // <- search vector
4447                 pRPNArray[nTokenIdx - 2]->GetType() == svDoubleRef &&
4448                 pRPNArray[nTokenIdx - 3]->GetOpCode() == ocPush)     // <- search criterion
4449             {
4450                 auto res = pRPNArray[nTokenIdx - 1];
4451                 // If Result vector is just a single cell reference
4452                 // LOOKUP extends it as a column vector.
4453                 if (res->GetType() == svSingleRef)
4454                     return true;
4455 
4456                 // If Result vector is a cell range and the match position
4457                 // falls outside its length, it gets automatically extended
4458                 // to the length of Search vector, but in the direction of
4459                 // Result vector.
4460                 if (res->GetType() == svDoubleRef)
4461                 {
4462                     ScComplexRefData aRef1 = *res->GetDoubleRef();
4463                     ScComplexRefData aRef2 = *pRPNArray[nTokenIdx - 2]->GetDoubleRef();
4464                     ScRange resultRange = aRef1.toAbs(mrDoc, mrPos);
4465                     ScRange sourceRange = aRef2.toAbs(mrDoc, mrPos);
4466 
4467                     SCROW nResultRows = resultRange.aEnd.Row() - resultRange.aStart.Row();
4468                     SCROW nSourceRows = sourceRange.aEnd.Row() - sourceRange.aStart.Row();
4469                     if (nResultRows != nSourceRows)
4470                         return true;
4471 
4472                     SCCOL nResultCols = resultRange.aEnd.Col() - resultRange.aStart.Col();
4473                     SCCOL nSourceCols = sourceRange.aEnd.Col() - sourceRange.aStart.Col();
4474                     if (nResultCols != nSourceCols)
4475                         return true;
4476                 }
4477             }
4478         }
4479         return false;
4480     }
4481 
DoIt__anonb316ae920511::ScDependantsCalculator4482     bool DoIt(ScRangeList* pSuccessfulDependencies, ScAddress* pDirtiedAddress)
4483     {
4484         // Partially from ScGroupTokenConverter::convert in sc/source/core/data/grouptokenconverter.cxx
4485 
4486         ScRangeList aRangeList;
4487 
4488         // Self references should be checked by considering the entire formula-group not just the provided span.
4489         bool bHasSelfReferences = false;
4490         bool bInDocShellRecalc = mrDoc.IsInDocShellRecalc();
4491 
4492         FormulaToken** pRPNArray = mrCode.GetCode();
4493         sal_uInt16 nCodeLen = mrCode.GetCodeLen();
4494         for (sal_Int32 nTokenIdx = nCodeLen-1; nTokenIdx >= 0; --nTokenIdx)
4495         {
4496             auto p = pRPNArray[nTokenIdx];
4497             if (!bInDocShellRecalc)
4498             {
4499                 // The dependency evaluator evaluates all arguments of IF/IFS/SWITCH irrespective
4500                 // of the result of the condition expression.
4501                 // This is a perf problem if we *don't* intent on recalc'ing all dirty cells
4502                 // in the document. So let's disable threading and stop dependency evaluation if
4503                 // the call did not originate from ScDocShell::DoRecalc()/ScDocShell::DoHardRecalc()
4504                 // for formulae with IF/IFS/SWITCH
4505                 OpCode nOpCode = p->GetOpCode();
4506                 if (nOpCode == ocIf || nOpCode == ocIfs_MS || nOpCode == ocSwitch_MS)
4507                     return false;
4508             }
4509 
4510             if (p->GetOpCode() == ocLookup && LookupResultVectorMismatch(nTokenIdx))
4511             {
4512                 SAL_INFO("sc.core.formulacell", "Lookup Result Vector size doesn't match Search Vector");
4513                 return false;
4514             }
4515 
4516             if (p->GetOpCode() == ocRange)
4517             {
4518                 // We are just looking at svSingleRef/svDoubleRef, so we will miss that ocRange constructs
4519                 // a range from its arguments, and only examining the individual args doesn't capture the
4520                 // true range of dependencies
4521                 SAL_WARN("sc.core.formulacell", "dynamic range, dropping as candidate for parallelizing");
4522                 return false;
4523             }
4524 
4525             switch (p->GetType())
4526             {
4527             case svSingleRef:
4528                 {
4529                     ScSingleRefData aRef = *p->GetSingleRef(); // =Sheet1!A1
4530                     if( aRef.IsDeleted())
4531                         return false;
4532                     ScAddress aRefPos = aRef.toAbs(mrDoc, mrPos);
4533 
4534                     if (!mrDoc.HasTable(aRefPos.Tab()))
4535                         return false; // or true?
4536 
4537                     if (aRef.IsRowRel())
4538                     {
4539                         if (isSelfReferenceRelative(aRefPos, aRef.Row()))
4540                         {
4541                             bHasSelfReferences = true;
4542                             continue;
4543                         }
4544 
4545                         // Trim data array length to actual data range.
4546                         SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row() + mnStartOffset, mnSpanLen);
4547 
4548                         aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row() + mnStartOffset, aRefPos.Tab(),
4549                                                 aRefPos.Col(), aRefPos.Row() + mnStartOffset + nTrimLen - 1, aRefPos.Tab()));
4550                     }
4551                     else
4552                     {
4553                         if (isSelfReferenceAbsolute(aRefPos))
4554                         {
4555                             bHasSelfReferences = true;
4556                             continue;
4557                         }
4558 
4559                         aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row(), aRefPos.Tab()));
4560                     }
4561                 }
4562                 break;
4563             case svDoubleRef:
4564                 {
4565                     ScComplexRefData aRef = *p->GetDoubleRef();
4566                     if( aRef.IsDeleted())
4567                         return false;
4568                     ScRange aAbs = aRef.toAbs(mrDoc, mrPos);
4569 
4570                     // Multiple sheet
4571                     if (aAbs.aStart.Tab() != aAbs.aEnd.Tab())
4572                         return false;
4573 
4574                     bool bIsRef1RowRel = aRef.Ref1.IsRowRel();
4575                     // Check for self reference.
4576                     if (bIsRef1RowRel)
4577                     {
4578                         if (isSelfReferenceRelative(aAbs.aStart, aRef.Ref1.Row()))
4579                         {
4580                             bHasSelfReferences = true;
4581                             continue;
4582                         }
4583                     }
4584                     else if (isSelfReferenceAbsolute(aAbs.aStart))
4585                     {
4586                         bHasSelfReferences = true;
4587                         continue;
4588                     }
4589 
4590                     bool bIsRef2RowRel = aRef.Ref2.IsRowRel();
4591                     if (bIsRef2RowRel)
4592                     {
4593                         if (isSelfReferenceRelative(aAbs.aEnd, aRef.Ref2.Row()))
4594                         {
4595                             bHasSelfReferences = true;
4596                             continue;
4597                         }
4598                     }
4599                     else if (isSelfReferenceAbsolute(aAbs.aEnd))
4600                     {
4601                         bHasSelfReferences = true;
4602                         continue;
4603                     }
4604 
4605                     if (isDoubleRefSpanGroupRange(aAbs, bIsRef1RowRel, bIsRef2RowRel))
4606                     {
4607                         bHasSelfReferences = true;
4608                         continue;
4609                     }
4610 
4611                     SCROW nFirstRefStartRow = bIsRef1RowRel ? aAbs.aStart.Row() + mnStartOffset : aAbs.aStart.Row();
4612                     SCROW nLastRefEndRow =  bIsRef2RowRel ? aAbs.aEnd.Row() + mnEndOffset : aAbs.aEnd.Row();
4613 
4614                     SCROW nFirstRefEndRow = bIsRef1RowRel ? aAbs.aStart.Row() + mnEndOffset : aAbs.aStart.Row();
4615                     SCROW nLastRefStartRow =  bIsRef2RowRel ? aAbs.aEnd.Row() + mnStartOffset : aAbs.aEnd.Row();
4616 
4617                     // The first row that will be referenced through the doubleref.
4618                     SCROW nFirstRefRow = std::min(nFirstRefStartRow, nLastRefStartRow);
4619                     // The last row that will be referenced through the doubleref.
4620                     SCROW nLastRefRow =  std::max(nLastRefEndRow, nFirstRefEndRow);
4621 
4622                     // Number of rows to be evaluated from nFirstRefRow.
4623                     SCROW nArrayLength = nLastRefRow - nFirstRefRow + 1;
4624                     assert(nArrayLength > 0);
4625 
4626                     // Trim trailing empty rows.
4627                     nArrayLength = trimLength(aAbs.aStart.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), nFirstRefRow, nArrayLength);
4628 
4629                     aRangeList.Join(ScRange(aAbs.aStart.Col(), nFirstRefRow, aAbs.aStart.Tab(),
4630                                aAbs.aEnd.Col(), nFirstRefRow + nArrayLength - 1, aAbs.aEnd.Tab()));
4631                 }
4632                 break;
4633             default:
4634                 break;
4635             }
4636         }
4637 
4638         // Compute dependencies irrespective of the presence of any self references.
4639         // These dependencies would get computed via InterpretTail anyway when we disable group calc, so let's do it now.
4640         // The advantage is that the FG's get marked for cycles early if present, and can avoid lots of complications.
4641         for (size_t i = 0; i < aRangeList.size(); ++i)
4642         {
4643             const ScRange & rRange = aRangeList[i];
4644             assert(rRange.aStart.Tab() == rRange.aEnd.Tab());
4645             for (auto nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); nCol++)
4646             {
4647                 SCROW nStartRow = rRange.aStart.Row();
4648                 SCROW nLength = rRange.aEnd.Row() - rRange.aStart.Row() + 1;
4649                 if( mFromFirstRow )
4650                 {   // include also all previous rows
4651                     nLength += nStartRow;
4652                     nStartRow = 0;
4653                 }
4654                 if (!mrDoc.HandleRefArrayForParallelism(ScAddress(nCol, nStartRow, rRange.aStart.Tab()),
4655                                                         nLength, mxGroup, pDirtiedAddress))
4656                     return false;
4657             }
4658         }
4659 
4660         if (bHasSelfReferences)
4661             mxGroup->mbPartOfCycle = true;
4662 
4663         if (pSuccessfulDependencies && !bHasSelfReferences)
4664             *pSuccessfulDependencies = aRangeList;
4665 
4666         return !bHasSelfReferences;
4667     }
4668 };
4669 
4670 } // anonymous namespace
4671 
InterpretFormulaGroup(SCROW nStartOffset,SCROW nEndOffset)4672 bool ScFormulaCell::InterpretFormulaGroup(SCROW nStartOffset, SCROW nEndOffset)
4673 {
4674     if (!mxGroup || !pCode)
4675         return false;
4676 
4677     auto aScope = sc::FormulaLogger::get().enterGroup(rDocument, *this);
4678     ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
4679 
4680     if (mxGroup->mbPartOfCycle)
4681     {
4682         aScope.addMessage(u"This formula-group is part of a cycle"_ustr);
4683         return false;
4684     }
4685 
4686     if (mxGroup->meCalcState == sc::GroupCalcDisabled)
4687     {
4688         static constexpr OUStringLiteral MESSAGE = u"group calc disabled";
4689         aScope.addMessage(MESSAGE);
4690         return false;
4691     }
4692 
4693     // Use SC_FORCE_CALCULATION=opencl/threads to force calculation e.g. for unittests
4694     static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType();
4695     if (forceType == ForceCalculationCore
4696         || ( GetWeight() < ScInterpreter::GetGlobalConfig().mnOpenCLMinimumFormulaGroupSize
4697             && forceType != ForceCalculationOpenCL
4698             && forceType != ForceCalculationThreads))
4699     {
4700         mxGroup->meCalcState = sc::GroupCalcDisabled;
4701         aScope.addGroupSizeThresholdMessage(*this);
4702         return false;
4703     }
4704 
4705     if (cMatrixFlag != ScMatrixMode::NONE)
4706     {
4707         mxGroup->meCalcState = sc::GroupCalcDisabled;
4708         aScope.addMessage(u"matrix skipped"_ustr);
4709         return false;
4710     }
4711 
4712     if( forceType != ForceCalculationNone )
4713     {
4714         // ScConditionEntry::Interpret() creates a temporary cell and interprets it
4715         // without it actually being in the document at the specified position.
4716         // That would confuse opencl/threading code, as they refer to the cell group
4717         // also using the position. This is normally not triggered (single cells
4718         // are normally not in a cell group), but if forced, check for this explicitly.
4719         if( rDocument.GetFormulaCell( aPos ) != this )
4720         {
4721             mxGroup->meCalcState = sc::GroupCalcDisabled;
4722             aScope.addMessage(u"cell not in document"_ustr);
4723             return false;
4724         }
4725     }
4726 
4727     // Get rid of -1's in offsets (defaults) or any invalid offsets.
4728     SCROW nMaxOffset = mxGroup->mnLength - 1;
4729     nStartOffset = nStartOffset < 0 ? 0 : std::min(nStartOffset, nMaxOffset);
4730     nEndOffset = nEndOffset < 0 ? nMaxOffset : std::min(nEndOffset, nMaxOffset);
4731 
4732     if (nEndOffset < nStartOffset)
4733     {
4734         nStartOffset = 0;
4735         nEndOffset = nMaxOffset;
4736     }
4737 
4738     if (nEndOffset == nStartOffset && forceType == ForceCalculationNone)
4739         return false; // Do not use threads for a single row.
4740 
4741     // Guard against endless recursion of Interpret() calls, for this to work
4742     // ScFormulaCell::InterpretFormulaGroup() must never be called through
4743     // anything else than ScFormulaCell::Interpret(), same as
4744     // ScFormulaCell::InterpretTail()
4745     RecursionCounter aRecursionCounter( rRecursionHelper, this);
4746 
4747     bool bDependencyComputed = false;
4748     bool bDependencyCheckFailed = false;
4749 
4750     // Preference order: First try OpenCL, then threading.
4751     // TODO: Do formula-group span computation for OCL too if nStartOffset/nEndOffset are non default.
4752     if( InterpretFormulaGroupOpenCL(aScope, bDependencyComputed, bDependencyCheckFailed))
4753         return true;
4754 
4755     if( InterpretFormulaGroupThreading(aScope, bDependencyComputed, bDependencyCheckFailed, nStartOffset, nEndOffset))
4756         return true;
4757 
4758     return false;
4759 }
4760 
CheckComputeDependencies(sc::FormulaLogger::GroupScope & rScope,bool fromFirstRow,SCROW nStartOffset,SCROW nEndOffset,bool bCalcDependencyOnly,ScRangeList * pSuccessfulDependencies,ScAddress * pDirtiedAddress)4761 bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow,
4762                                              SCROW nStartOffset, SCROW nEndOffset,
4763                                              bool bCalcDependencyOnly,
4764                                              ScRangeList* pSuccessfulDependencies,
4765                                              ScAddress* pDirtiedAddress)
4766 {
4767     ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
4768     // iterate over code in the formula ...
4769     // ensure all input is pre-calculated -
4770     // to avoid writing during the calculation
4771     if (bCalcDependencyOnly)
4772     {
4773         // Let's not use "ScFormulaGroupDependencyComputeGuard" here as there is no corresponding
4774         // "ScFormulaGroupCycleCheckGuard" for this formula-group.
4775         // (We can only reach here from a multi-group dependency evaluation attempt).
4776         // (These two have to be in pairs always for any given formula-group)
4777         ScDependantsCalculator aCalculator(rDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset);
4778         return aCalculator.DoIt(pSuccessfulDependencies, pDirtiedAddress);
4779     }
4780 
4781     bool bOKToParallelize = false;
4782     {
4783         ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this);
4784         if (mxGroup->mbPartOfCycle)
4785         {
4786             mxGroup->meCalcState = sc::GroupCalcDisabled;
4787             rScope.addMessage(u"found circular formula-group dependencies"_ustr);
4788             return false;
4789         }
4790 
4791         ScFormulaGroupDependencyComputeGuard aDepComputeGuard(rRecursionHelper);
4792         ScDependantsCalculator aCalculator(rDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset);
4793         bOKToParallelize = aCalculator.DoIt(pSuccessfulDependencies, pDirtiedAddress);
4794 
4795     }
4796 
4797     if (rRecursionHelper.IsInRecursionReturn())
4798     {
4799         mxGroup->meCalcState = sc::GroupCalcDisabled;
4800         rScope.addMessage(u"Recursion limit reached, cannot thread this formula group now"_ustr);
4801         return false;
4802     }
4803 
4804     if (mxGroup->mbPartOfCycle)
4805     {
4806         mxGroup->meCalcState = sc::GroupCalcDisabled;
4807         rScope.addMessage(u"found circular formula-group dependencies"_ustr);
4808         return false;
4809     }
4810 
4811     if (!rRecursionHelper.AreGroupsIndependent())
4812     {
4813         // This call resulted from a dependency calculation for a multigroup-threading attempt,
4814         // but found dependency among the groups.
4815         rScope.addMessage(u"multi-group-dependency failed"_ustr);
4816         return false;
4817     }
4818 
4819     if (!bOKToParallelize)
4820     {
4821         mxGroup->meCalcState = sc::GroupCalcDisabled;
4822         rScope.addMessage(u"could not do new dependencies calculation thing"_ustr);
4823         return false;
4824     }
4825 
4826     return true;
4827 }
4828 
lcl_probeLeftOrRightFGs(const ScFormulaCellGroupRef & xGroup,const ScDocument & rDoc,o3tl::sorted_vector<ScFormulaCellGroup * > & rFGSet,std::map<SCCOL,ScFormulaCell * > & rFGMap,bool bLeft)4829 static SCCOL lcl_probeLeftOrRightFGs(const ScFormulaCellGroupRef& xGroup, const ScDocument& rDoc,
4830                                      o3tl::sorted_vector<ScFormulaCellGroup*>& rFGSet,
4831                                      std::map<SCCOL, ScFormulaCell*>& rFGMap, bool bLeft)
4832 {
4833     const SCROW nLen = xGroup->mnLength;
4834     const sal_Int32 nWt = xGroup->mnWeight;
4835     ScAddress aAddr(xGroup->mpTopCell->aPos);
4836 
4837     SCCOL nColRet = aAddr.Col();
4838 
4839     const SCCOL nMaxCol = rDoc.GetAllocatedColumnsCount(aAddr.Tab()) - 1;
4840     if (bLeft)
4841         --nColRet;
4842     else
4843         ++nColRet;
4844 
4845     while (nColRet >= 0 && nColRet <= nMaxCol)
4846     {
4847         aAddr.SetCol(nColRet);
4848         const ScFormulaCell* pCell = rDoc.GetFormulaCell(aAddr);
4849         if (!pCell)
4850             break;
4851 
4852         if (!pCell->NeedsInterpret())
4853             break;
4854 
4855         const ScFormulaCellGroupRef& xNGroup = pCell->GetCellGroup();
4856         if (!xNGroup)
4857             break;
4858 
4859         if (!pCell->GetCode()->IsEnabledForThreading())
4860             break;
4861 
4862         if (xNGroup->mpTopCell->aPos.Row() != aAddr.Row())
4863             break;
4864 
4865         const SCROW nNLen = xNGroup->mnLength;
4866         const sal_Int32 nNWt = pCell->GetWeight();
4867         if (nNLen != nLen || nNWt != nWt)
4868             break;
4869 
4870         rFGSet.insert(xNGroup.get());
4871         rFGMap[nColRet] = xNGroup->mpTopCell;
4872 
4873         if (bLeft)
4874             --nColRet;
4875         else
4876             ++nColRet;
4877     }
4878 
4879     if (bLeft)
4880         ++nColRet;
4881     else
4882         --nColRet;
4883 
4884     return nColRet;
4885 }
4886 
4887 // To be called only from InterpretFormulaGroup().
InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope & aScope,bool & bDependencyComputed,bool & bDependencyCheckFailed,SCROW nStartOffset,SCROW nEndOffset)4888 bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope& aScope,
4889                                                    bool& bDependencyComputed,
4890                                                    bool& bDependencyCheckFailed,
4891                                                    SCROW nStartOffset,
4892                                                    SCROW nEndOffset)
4893 {
4894     static const bool bThreadingProhibited = std::getenv("SC_NO_THREADED_CALCULATION");
4895     if (!bDependencyCheckFailed && !bThreadingProhibited &&
4896         pCode->IsEnabledForThreading() &&
4897         ScCalcConfig::isThreadingEnabled())
4898     {
4899         ScRangeList aOrigDependencies;
4900         if(!bDependencyComputed && !CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset, false, &aOrigDependencies))
4901         {
4902             bDependencyComputed = true;
4903             bDependencyCheckFailed = true;
4904             return false;
4905         }
4906 
4907         bDependencyComputed = true;
4908 
4909         // Then do the threaded calculation
4910 
4911         class Executor : public comphelper::ThreadTask
4912         {
4913         private:
4914             const unsigned mnThisThread;
4915             const unsigned mnThreadsTotal;
4916             ScDocument* mpDocument;
4917             ScInterpreterContext* mpContext;
4918             const ScAddress& mrTopPos;
4919             SCCOL mnStartCol;
4920             SCCOL mnEndCol;
4921             SCROW mnStartOffset;
4922             SCROW mnEndOffset;
4923 
4924         public:
4925             Executor(const std::shared_ptr<comphelper::ThreadTaskTag>& rTag,
4926                      unsigned nThisThread,
4927                      unsigned nThreadsTotal,
4928                      ScDocument* pDocument2,
4929                      ScInterpreterContext* pContext,
4930                      const ScAddress& rTopPos,
4931                      SCCOL nStartCol,
4932                      SCCOL nEndCol,
4933                      SCROW nStartOff,
4934                      SCROW nEndOff) :
4935                 comphelper::ThreadTask(rTag),
4936                 mnThisThread(nThisThread),
4937                 mnThreadsTotal(nThreadsTotal),
4938                 mpDocument(pDocument2),
4939                 mpContext(pContext),
4940                 mrTopPos(rTopPos),
4941                 mnStartCol(nStartCol),
4942                 mnEndCol(nEndCol),
4943                 mnStartOffset(nStartOff),
4944                 mnEndOffset(nEndOff)
4945             {
4946             }
4947 
4948             virtual void doWork() override
4949             {
4950                 ScRange aCalcRange(mnStartCol, mrTopPos.Row() + mnStartOffset, mrTopPos.Tab(),
4951                                    mnEndCol, mrTopPos.Row() + mnEndOffset, mrTopPos.Tab());
4952                 mpDocument->CalculateInColumnInThread(*mpContext, aCalcRange, mnThisThread, mnThreadsTotal);
4953             }
4954 
4955         };
4956 
4957         SvNumberFormatter* pNonThreadedFormatter = rDocument.GetNonThreadedContext().GetFormatTable();
4958 
4959         comphelper::ThreadPool& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool());
4960         sal_Int32 nThreadCount = rThreadPool.getWorkerCount();
4961 
4962         SAL_INFO("sc.threaded", "Running " << nThreadCount << " threads");
4963 
4964         o3tl::sorted_vector<ScFormulaCellGroup*> aFGSet;
4965         std::map<SCCOL, ScFormulaCell*> aFGMap;
4966         aFGSet.insert(mxGroup.get());
4967 
4968         ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
4969         SCCOL nColStart = aPos.Col();
4970         SCCOL nColEnd = nColStart;
4971         if (!rRecursionHelper.HasFormulaGroupSet() && rDocument.IsInDocShellRecalc())
4972         {
4973             nColStart = lcl_probeLeftOrRightFGs(mxGroup, rDocument, aFGSet, aFGMap, true);
4974             nColEnd = lcl_probeLeftOrRightFGs(mxGroup, rDocument, aFGSet, aFGMap, false);
4975         }
4976 
4977         bool bFGOK = true;
4978         ScAddress aDirtiedAddress(ScAddress::INITIALIZE_INVALID);
4979         if (nColStart != nColEnd)
4980         {
4981             ScCheckIndependentFGGuard aGuard(rRecursionHelper, &aFGSet);
4982             for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol)
4983             {
4984                 if (nCurrCol == aPos.Col())
4985                     continue;
4986 
4987                 bFGOK = aFGMap[nCurrCol]->CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset,
4988                                                                    true, nullptr, &aDirtiedAddress);
4989                 if (!bFGOK || !aGuard.AreGroupsIndependent())
4990                 {
4991                     nColEnd = nColStart = aPos.Col();
4992                     break;
4993                 }
4994             }
4995         }
4996 
4997         // tdf#156677 it is possible that if a check of a column in the new range fails that the check has
4998         // now left a cell that the original range depended on in a Dirty state. So if the dirtied cell
4999         // was part of the original dependencies re-run the initial CheckComputeDependencies to fix it.
5000         if (!bFGOK && aDirtiedAddress.IsValid() && aOrigDependencies.Find(aDirtiedAddress))
5001         {
5002             SAL_WARN("sc.core.formulacell", "rechecking dependencies due to a dirtied cell during speculative probe");
5003             const bool bRedoEntryCheckSucceeded = CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset);
5004             assert(bRedoEntryCheckSucceeded && "if it worked on the original range it should work again on that range");
5005             (void)bRedoEntryCheckSucceeded;
5006         }
5007 
5008         std::vector<std::unique_ptr<ScInterpreter>> aInterpreters(nThreadCount);
5009         {
5010             assert(!rDocument.IsThreadedGroupCalcInProgress());
5011             rDocument.SetThreadedGroupCalcInProgress(true);
5012 
5013             ScMutationDisable aGuard(rDocument, ScMutationGuardFlags::CORE);
5014 
5015             // Here we turn off ref-counting for the contents of pCode on the basis
5016             // that pCode is not modified by interpreting and when interpreting is
5017             // complete all token refcounts will be back to their initial ref count
5018             FormulaToken** pArray = pCode->GetArray();
5019             for (sal_uInt16 i = 0, n = pCode->GetLen(); i < n; ++i)
5020                 pArray[i]->SetRefCntPolicy(RefCntPolicy::None);
5021 
5022             // Start nThreadCount new threads
5023             std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag();
5024             ScThreadedInterpreterContextGetterGuard aContextGetterGuard(nThreadCount, rDocument, pNonThreadedFormatter);
5025             ScInterpreterContext* context = nullptr;
5026 
5027             for (int i = 0; i < nThreadCount; ++i)
5028             {
5029                 context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i);
5030                 assert(!context->pInterpreter);
5031                 aInterpreters[i].reset(new ScInterpreter(this, rDocument, *context, mxGroup->mpTopCell->aPos, *pCode, true));
5032                 context->pInterpreter = aInterpreters[i].get();
5033                 rDocument.SetupContextFromNonThreadedContext(*context, i);
5034                 rThreadPool.pushTask(std::make_unique<Executor>(aTag, i, nThreadCount, &rDocument, context, mxGroup->mpTopCell->aPos,
5035                                                                 nColStart, nColEnd, nStartOffset, nEndOffset));
5036             }
5037 
5038             SAL_INFO("sc.threaded", "Waiting for threads to finish work");
5039             // Do not join the threads here. They will get joined in ScDocument destructor
5040             // if they don't get joined from elsewhere before (via ThreadPool::waitUntilDone).
5041             rThreadPool.waitUntilDone(aTag, false);
5042 
5043             // Drop any caches that reference Tokens before restoring ref counting policy
5044             for (int i = 0; i < nThreadCount; ++i)
5045                 aInterpreters[i]->DropTokenCaches();
5046 
5047             for (sal_uInt16 i = 0, n = pCode->GetLen(); i < n; ++i)
5048                 pArray[i]->SetRefCntPolicy(RefCntPolicy::ThreadSafe);
5049 
5050             rDocument.SetThreadedGroupCalcInProgress(false);
5051 
5052             for (int i = 0; i < nThreadCount; ++i)
5053             {
5054                 context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i);
5055                 // This is intentionally done in this main thread in order to avoid locking.
5056                 rDocument.MergeContextBackIntoNonThreadedContext(*context, i);
5057                 context->pInterpreter = nullptr;
5058             }
5059 
5060             SAL_INFO("sc.threaded", "Done");
5061         }
5062 
5063         ScAddress aStartPos(mxGroup->mpTopCell->aPos);
5064         SCROW nSpanLen = nEndOffset - nStartOffset + 1;
5065         aStartPos.SetRow(aStartPos.Row() + nStartOffset);
5066         // Reuse one of the previously allocated interpreter objects here.
5067         rDocument.HandleStuffAfterParallelCalculation(nColStart, nColEnd, aStartPos.Row(), nSpanLen,
5068                                                        aStartPos.Tab(), aInterpreters[0].get());
5069 
5070         return true;
5071     }
5072 
5073     return false;
5074 }
5075 
5076 // To be called only from InterpretFormulaGroup().
InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope & aScope,bool & bDependencyComputed,bool & bDependencyCheckFailed)5077 bool ScFormulaCell::InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope& aScope,
5078                                                 bool& bDependencyComputed,
5079                                                 bool& bDependencyCheckFailed)
5080 {
5081     bool bCanVectorize = pCode->IsEnabledForOpenCL();
5082     switch (pCode->GetVectorState())
5083     {
5084         case FormulaVectorEnabled:
5085         case FormulaVectorCheckReference:
5086         break;
5087 
5088         // Not good.
5089         case FormulaVectorDisabledByOpCode:
5090             aScope.addMessage(u"group calc disabled due to vector state (non-vector-supporting opcode)"_ustr);
5091             break;
5092         case FormulaVectorDisabledByStackVariable:
5093             aScope.addMessage(u"group calc disabled due to vector state (non-vector-supporting stack variable)"_ustr);
5094             break;
5095         case FormulaVectorDisabledNotInSubSet:
5096             aScope.addMessage(u"group calc disabled due to vector state (opcode not in subset)"_ustr);
5097             break;
5098         case FormulaVectorDisabled:
5099         case FormulaVectorUnknown:
5100         default:
5101             aScope.addMessage(u"group calc disabled due to vector state (unknown)"_ustr);
5102             return false;
5103     }
5104 
5105     if (!bCanVectorize)
5106         return false;
5107 
5108     if (!ScCalcConfig::isOpenCLEnabled())
5109     {
5110         aScope.addMessage(u"opencl not enabled"_ustr);
5111         return false;
5112     }
5113 
5114     // TableOp does tricks with using a cell with different values, just bail out.
5115     if(rDocument.IsInInterpreterTableOp())
5116         return false;
5117 
5118     if (bDependencyCheckFailed)
5119         return false;
5120 
5121     if(!bDependencyComputed && !CheckComputeDependencies(aScope, true, 0, mxGroup->mnLength - 1))
5122     {
5123         bDependencyComputed = true;
5124         bDependencyCheckFailed = true;
5125         return false;
5126     }
5127 
5128     bDependencyComputed = true;
5129 
5130     // TODO : Disable invariant formula group interpretation for now in order
5131     // to get implicit intersection to work.
5132     if (mxGroup->mbInvariant && false)
5133         return InterpretInvariantFormulaGroup();
5134 
5135     int nMaxGroupLength = INT_MAX;
5136 
5137 #ifdef _WIN32
5138     // Heuristic: Certain old low-end OpenCL implementations don't
5139     // work for us with too large group lengths. 1000 was determined
5140     // empirically to be a good compromise.
5141     if (openclwrapper::gpuEnv.mbNeedsTDRAvoidance)
5142         nMaxGroupLength = 1000;
5143 #endif
5144 
5145     if (std::getenv("SC_MAX_GROUP_LENGTH"))
5146         nMaxGroupLength = std::atoi(std::getenv("SC_MAX_GROUP_LENGTH"));
5147 
5148     int nNumOnePlus;
5149     const int nNumParts = splitup(GetSharedLength(), nMaxGroupLength, nNumOnePlus);
5150 
5151     int nOffset = 0;
5152     int nCurChunkSize;
5153     ScAddress aOrigPos = mxGroup->mpTopCell->aPos;
5154     for (int i = 0; i < nNumParts; i++, nOffset += nCurChunkSize)
5155     {
5156         nCurChunkSize = GetSharedLength()/nNumParts + (i < nNumOnePlus ? 1 : 0);
5157 
5158         ScFormulaCellGroupRef xGroup;
5159 
5160         if (nNumParts == 1)
5161             xGroup = mxGroup;
5162         else
5163         {
5164             // Ugly hack
5165             xGroup = new ScFormulaCellGroup();
5166             xGroup->mpTopCell = mxGroup->mpTopCell;
5167             xGroup->mpTopCell->aPos = aOrigPos;
5168             xGroup->mpTopCell->aPos.IncRow(nOffset);
5169             xGroup->mbInvariant = mxGroup->mbInvariant;
5170             xGroup->mnLength = nCurChunkSize;
5171             xGroup->mpCode = std::move(mxGroup->mpCode); // temporarily transfer
5172         }
5173 
5174         ScTokenArray aCode(rDocument);
5175         ScGroupTokenConverter aConverter(aCode, rDocument, *this, xGroup->mpTopCell->aPos);
5176         // TODO avoid this extra compilation
5177         ScCompiler aComp( rDocument, xGroup->mpTopCell->aPos, *pCode, formula::FormulaGrammar::GRAM_UNSPECIFIED, true, cMatrixFlag != ScMatrixMode::NONE );
5178         aComp.CompileTokenArray();
5179         if (aComp.HasUnhandledPossibleImplicitIntersections() || !aConverter.convert(*pCode, aScope))
5180         {
5181             if(aComp.HasUnhandledPossibleImplicitIntersections())
5182             {
5183                 SAL_INFO("sc.opencl", "group " << xGroup->mpTopCell->aPos << " has unhandled implicit intersections, disabling");
5184 #ifdef DBG_UTIL
5185                 for( const OpCode opcode : aComp.UnhandledPossibleImplicitIntersectionsOpCodes())
5186                 {
5187                     SAL_INFO("sc.opencl", "unhandled implicit intersection opcode "
5188                         << formula::FormulaCompiler().GetOpCodeMap(com::sun::star::sheet::FormulaLanguage::ENGLISH)->getSymbol(opcode)
5189                         << "(" << int(opcode) << ")");
5190                 }
5191 #endif
5192             }
5193             else
5194                 SAL_INFO("sc.opencl", "conversion of group " << xGroup->mpTopCell->aPos << " failed, disabling");
5195 
5196             mxGroup->meCalcState = sc::GroupCalcDisabled;
5197 
5198             // Undo the hack above
5199             if (nNumParts > 1)
5200             {
5201                 mxGroup->mpTopCell->aPos = aOrigPos;
5202                 xGroup->mpTopCell = nullptr;
5203                 mxGroup->mpCode = std::move(xGroup->mpCode);
5204             }
5205 
5206             aScope.addMessage(u"group token conversion failed"_ustr);
5207             return false;
5208         }
5209 
5210         // The converted code does not have RPN tokens yet.  The interpreter will
5211         // generate them.
5212         xGroup->meCalcState = mxGroup->meCalcState = sc::GroupCalcRunning;
5213         sc::FormulaGroupInterpreter *pInterpreter = sc::FormulaGroupInterpreter::getStatic();
5214 
5215         if (pInterpreter == nullptr ||
5216             !pInterpreter->interpret(rDocument, xGroup->mpTopCell->aPos, xGroup, aCode))
5217         {
5218             SAL_INFO("sc.opencl", "interpreting group " << mxGroup->mpTopCell->aPos
5219                 << " (state " << static_cast<int>(mxGroup->meCalcState) << ") failed, disabling");
5220             mxGroup->meCalcState = sc::GroupCalcDisabled;
5221 
5222             // Undo the hack above
5223             if (nNumParts > 1)
5224             {
5225                 mxGroup->mpTopCell->aPos = aOrigPos;
5226                 xGroup->mpTopCell = nullptr;
5227                 mxGroup->mpCode = std::move(xGroup->mpCode);
5228             }
5229 
5230             aScope.addMessage(u"group interpretation unsuccessful"_ustr);
5231             return false;
5232         }
5233 
5234         aScope.setCalcComplete();
5235 
5236         if (nNumParts > 1)
5237         {
5238             xGroup->mpTopCell = nullptr;
5239             mxGroup->mpCode = std::move(xGroup->mpCode);
5240         }
5241     }
5242 
5243     if (nNumParts > 1)
5244         mxGroup->mpTopCell->aPos = aOrigPos;
5245     mxGroup->meCalcState = sc::GroupCalcEnabled;
5246     return true;
5247 }
5248 
InterpretInvariantFormulaGroup()5249 bool ScFormulaCell::InterpretInvariantFormulaGroup()
5250 {
5251     if (pCode->GetVectorState() == FormulaVectorCheckReference)
5252     {
5253         // An invariant group should only have absolute row references, and no
5254         // external references are allowed.
5255 
5256         ScTokenArray aCode(rDocument);
5257         FormulaTokenArrayPlainIterator aIter(*pCode);
5258         for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
5259         {
5260             switch (p->GetType())
5261             {
5262                 case svSingleRef:
5263                 {
5264                     ScSingleRefData aRef = *p->GetSingleRef();
5265                     ScAddress aRefPos = aRef.toAbs(rDocument, aPos);
5266                     formula::FormulaTokenRef pNewToken = rDocument.ResolveStaticReference(aRefPos);
5267                     if (!pNewToken)
5268                         return false;
5269 
5270                     aCode.AddToken(*pNewToken);
5271                 }
5272                 break;
5273                 case svDoubleRef:
5274                 {
5275                     ScComplexRefData aRef = *p->GetDoubleRef();
5276                     ScRange aRefRange = aRef.toAbs(rDocument, aPos);
5277                     formula::FormulaTokenRef pNewToken = rDocument.ResolveStaticReference(aRefRange);
5278                     if (!pNewToken)
5279                         return false;
5280 
5281                     aCode.AddToken(*pNewToken);
5282                 }
5283                 break;
5284                 default:
5285                     aCode.AddToken(*p);
5286             }
5287         }
5288 
5289         ScCompiler aComp(rDocument, aPos, aCode, rDocument.GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE);
5290         aComp.CompileTokenArray(); // Create RPN token array.
5291         ScInterpreter aInterpreter(this, rDocument, rDocument.GetNonThreadedContext(), aPos, aCode);
5292         aInterpreter.Interpret();
5293         aResult.SetToken(aInterpreter.GetResultToken().get());
5294     }
5295     else
5296     {
5297         // Formula contains no references.
5298         ScInterpreter aInterpreter(this, rDocument, rDocument.GetNonThreadedContext(), aPos, *pCode);
5299         aInterpreter.Interpret();
5300         aResult.SetToken(aInterpreter.GetResultToken().get());
5301     }
5302 
5303     for ( sal_Int32 i = 0; i < mxGroup->mnLength; i++ )
5304     {
5305         ScAddress aTmpPos = aPos;
5306         aTmpPos.SetRow(mxGroup->mpTopCell->aPos.Row() + i);
5307         ScFormulaCell* pCell = rDocument.GetFormulaCell(aTmpPos);
5308         if (!pCell)
5309         {
5310             SAL_WARN("sc.core.formulacell", "GetFormulaCell not found");
5311             continue;
5312         }
5313 
5314         // FIXME: this set of horrors is unclear to me ... certainly
5315         // the above GetCell is profoundly nasty & slow ...
5316         // Ensure the cell truly has a result:
5317         pCell->aResult = aResult;
5318         pCell->ResetDirty();
5319         pCell->SetChanged(true);
5320     }
5321 
5322     return true;
5323 }
5324 
5325 namespace {
5326 
startListeningArea(ScFormulaCell * pCell,ScDocument & rDoc,const ScAddress & rPos,const formula::FormulaToken & rToken)5327 void startListeningArea(
5328     ScFormulaCell* pCell, ScDocument& rDoc, const ScAddress& rPos, const formula::FormulaToken& rToken)
5329 {
5330     const ScSingleRefData& rRef1 = *rToken.GetSingleRef();
5331     const ScSingleRefData& rRef2 = *rToken.GetSingleRef2();
5332     ScAddress aCell1 = rRef1.toAbs(rDoc, rPos);
5333     ScAddress aCell2 = rRef2.toAbs(rDoc, rPos);
5334     if (!(aCell1.IsValid() && aCell2.IsValid()))
5335         return;
5336 
5337     if (rToken.GetOpCode() == ocColRowNameAuto)
5338     {   // automagically
5339         if ( rRef1.IsColRel() )
5340         {   // ColName
5341             aCell2.SetRow(rDoc.MaxRow());
5342         }
5343         else
5344         {   // RowName
5345             aCell2.SetCol(rDoc.MaxCol());
5346         }
5347     }
5348     rDoc.StartListeningArea(ScRange(aCell1, aCell2), false, pCell);
5349 }
5350 
5351 }
5352 
StartListeningTo(ScDocument & rDoc)5353 void ScFormulaCell::StartListeningTo( ScDocument& rDoc )
5354 {
5355     if (mxGroup)
5356         mxGroup->endAllGroupListening(rDoc);
5357 
5358     if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack())
5359         return;
5360 
5361     rDoc.SetDetectiveDirty(true);  // It has changed something
5362 
5363     ScTokenArray* pArr = GetCode();
5364     if( pArr->IsRecalcModeAlways() )
5365     {
5366         rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this);
5367         SetNeedsListening( false);
5368         return;
5369     }
5370 
5371     formula::FormulaTokenArrayPlainIterator aIter(*pArr);
5372     formula::FormulaToken* t;
5373     while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
5374     {
5375         switch (t->GetType())
5376         {
5377             case svSingleRef:
5378             {
5379                 ScAddress aCell =  t->GetSingleRef()->toAbs(rDocument, aPos);
5380                 if (aCell.IsValid())
5381                     rDoc.StartListeningCell(aCell, this);
5382             }
5383             break;
5384             case svDoubleRef:
5385                 startListeningArea(this, rDoc, aPos, *t);
5386             break;
5387             default:
5388                 ;   // nothing
5389         }
5390     }
5391     SetNeedsListening( false);
5392 }
5393 
StartListeningTo(sc::StartListeningContext & rCxt)5394 void ScFormulaCell::StartListeningTo( sc::StartListeningContext& rCxt )
5395 {
5396     ScDocument& rDoc = rCxt.getDoc();
5397 
5398     if (mxGroup)
5399         mxGroup->endAllGroupListening(rDoc);
5400 
5401     if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack())
5402         return;
5403 
5404     rDoc.SetDetectiveDirty(true);  // It has changed something
5405 
5406     ScTokenArray* pArr = GetCode();
5407     if( pArr->IsRecalcModeAlways() )
5408     {
5409         rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this);
5410         SetNeedsListening( false);
5411         return;
5412     }
5413 
5414     formula::FormulaTokenArrayPlainIterator aIter(*pArr);
5415     formula::FormulaToken* t;
5416     while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
5417     {
5418         switch (t->GetType())
5419         {
5420             case svSingleRef:
5421             {
5422                 ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aPos);
5423                 if (aCell.IsValid())
5424                     rDoc.StartListeningCell(rCxt, aCell, *this);
5425             }
5426             break;
5427             case svDoubleRef:
5428                 startListeningArea(this, rDoc, aPos, *t);
5429             break;
5430             default:
5431                 ;   // nothing
5432         }
5433     }
5434     SetNeedsListening( false);
5435 }
5436 
5437 namespace {
5438 
endListeningArea(ScFormulaCell * pCell,ScDocument & rDoc,const ScAddress & rPos,const formula::FormulaToken & rToken)5439 void endListeningArea(
5440     ScFormulaCell* pCell, ScDocument& rDoc, const ScAddress& rPos, const formula::FormulaToken& rToken)
5441 {
5442     const ScSingleRefData& rRef1 = *rToken.GetSingleRef();
5443     const ScSingleRefData& rRef2 = *rToken.GetSingleRef2();
5444     ScAddress aCell1 = rRef1.toAbs(rDoc, rPos);
5445     ScAddress aCell2 = rRef2.toAbs(rDoc, rPos);
5446     if (!(aCell1.IsValid() && aCell2.IsValid()))
5447         return;
5448 
5449     if (rToken.GetOpCode() == ocColRowNameAuto)
5450     {   // automagically
5451         if ( rRef1.IsColRel() )
5452         {   // ColName
5453             aCell2.SetRow(rDoc.MaxRow());
5454         }
5455         else
5456         {   // RowName
5457             aCell2.SetCol(rDoc.MaxCol());
5458         }
5459     }
5460 
5461     rDoc.EndListeningArea(ScRange(aCell1, aCell2), false, pCell);
5462 }
5463 
5464 }
5465 
EndListeningTo(ScDocument & rDoc,ScTokenArray * pArr,ScAddress aCellPos)5466 void ScFormulaCell::EndListeningTo( ScDocument& rDoc, ScTokenArray* pArr,
5467         ScAddress aCellPos )
5468 {
5469     if (mxGroup)
5470         mxGroup->endAllGroupListening(rDoc);
5471 
5472     if (rDoc.IsClipOrUndo() || IsInChangeTrack())
5473         return;
5474 
5475     if (!HasBroadcaster())
5476         return;
5477 
5478     rDoc.SetDetectiveDirty(true);  // It has changed something
5479 
5480     if ( GetCode()->IsRecalcModeAlways() )
5481     {
5482         rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
5483         return;
5484     }
5485 
5486     if (!pArr)
5487     {
5488         pArr = GetCode();
5489         aCellPos = aPos;
5490     }
5491     formula::FormulaTokenArrayPlainIterator aIter(*pArr);
5492     formula::FormulaToken* t;
5493     while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
5494     {
5495         switch (t->GetType())
5496         {
5497             case svSingleRef:
5498             {
5499                 ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aCellPos);
5500                 if (aCell.IsValid())
5501                     rDoc.EndListeningCell(aCell, this);
5502             }
5503             break;
5504             case svDoubleRef:
5505                 endListeningArea(this, rDoc, aCellPos, *t);
5506             break;
5507             default:
5508                 ;   // nothing
5509         }
5510     }
5511 }
5512 
EndListeningTo(sc::EndListeningContext & rCxt)5513 void ScFormulaCell::EndListeningTo( sc::EndListeningContext& rCxt )
5514 {
5515     if (mxGroup)
5516         mxGroup->endAllGroupListening(rCxt.getDoc());
5517 
5518     if (rCxt.getDoc().IsClipOrUndo() || IsInChangeTrack())
5519         return;
5520 
5521     if (!HasBroadcaster())
5522         return;
5523 
5524     ScDocument& rDoc = rCxt.getDoc();
5525     rDoc.SetDetectiveDirty(true);  // It has changed something
5526 
5527     ScTokenArray* pArr = rCxt.getOldCode();
5528     ScAddress aCellPos = rCxt.getOldPosition(aPos);
5529     if (!pArr)
5530         pArr = pCode;
5531 
5532     if (pArr->IsRecalcModeAlways())
5533     {
5534         rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
5535         return;
5536     }
5537 
5538     formula::FormulaTokenArrayPlainIterator aIter(*pArr);
5539     formula::FormulaToken* t;
5540     while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
5541     {
5542         switch (t->GetType())
5543         {
5544             case svSingleRef:
5545             {
5546                 ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aCellPos);
5547                 if (aCell.IsValid())
5548                     rDoc.EndListeningCell(rCxt, aCell, *this);
5549             }
5550             break;
5551             case svDoubleRef:
5552                 endListeningArea(this, rDoc, aCellPos, *t);
5553             break;
5554             default:
5555                 ;   // nothing
5556         }
5557     }
5558 }
5559 
IsShared() const5560 bool ScFormulaCell::IsShared() const
5561 {
5562     return bool(mxGroup);
5563 }
5564 
IsSharedTop() const5565 bool ScFormulaCell::IsSharedTop() const
5566 {
5567     if (!mxGroup)
5568         return false;
5569 
5570     return mxGroup->mpTopCell == this;
5571 }
5572 
GetSharedTopRow() const5573 SCROW ScFormulaCell::GetSharedTopRow() const
5574 {
5575     return mxGroup ? mxGroup->mpTopCell->aPos.Row() : -1;
5576 }
5577 
GetSharedLength() const5578 SCROW ScFormulaCell::GetSharedLength() const
5579 {
5580     return mxGroup ? mxGroup->mnLength : 0;
5581 }
5582 
GetWeight() const5583 sal_Int32 ScFormulaCell::GetWeight() const
5584 {
5585     if (!mxGroup)
5586         return 1;
5587 
5588     if (mxGroup->mnWeight > 0)
5589         return mxGroup->mnWeight;
5590 
5591     double nSharedCodeWeight = GetSharedCode()->GetWeight();
5592     double nResult = nSharedCodeWeight * GetSharedLength();
5593     if (nResult < SAL_MAX_INT32)
5594         mxGroup->mnWeight = nResult;
5595     else
5596         mxGroup->mnWeight = SAL_MAX_INT32;
5597 
5598     return mxGroup->mnWeight;
5599 }
5600 
GetSharedCode()5601 ScTokenArray* ScFormulaCell::GetSharedCode()
5602 {
5603     return mxGroup ? &*mxGroup->mpCode : nullptr;
5604 }
5605 
GetSharedCode() const5606 const ScTokenArray* ScFormulaCell::GetSharedCode() const
5607 {
5608     return mxGroup ? &*mxGroup->mpCode : nullptr;
5609 }
5610 
SyncSharedCode()5611 void ScFormulaCell::SyncSharedCode()
5612 {
5613     if (!mxGroup)
5614         // Not a shared formula cell.
5615         return;
5616 
5617     pCode = &*mxGroup->mpCode;
5618 }
5619 
5620 #if DUMP_COLUMN_STORAGE
5621 
Dump() const5622 void ScFormulaCell::Dump() const
5623 {
5624     cout << "-- formula cell (" << aPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDocument) << ")" << endl;
5625     cout << "  * shared: " << (mxGroup ? "true" : "false") << endl;
5626     if (mxGroup)
5627     {
5628         cout << "    * shared length: " << mxGroup->mnLength << endl;
5629         cout << "    * shared calc state: " << mxGroup->meCalcState << endl;
5630     }
5631 
5632     sc::TokenStringContext aCxt(rDocument, rDocument.GetGrammar());
5633     cout << "  * code: " << pCode->CreateString(aCxt, aPos) << endl;
5634 
5635     FormulaError nErrCode = pCode->GetCodeError();
5636     cout << "  * code error: ";
5637     if (nErrCode == FormulaError::NONE)
5638         cout << "(none)";
5639     else
5640     {
5641         OUString aStr = ScGlobal::GetErrorString(nErrCode);
5642         cout << "  * code error: " << aStr << " (" << int(nErrCode) << ")";
5643     }
5644     cout << endl;
5645 
5646     cout << "  * result: ";
5647     sc::FormulaResultValue aRV = aResult.GetResult();
5648     switch (aRV.meType)
5649     {
5650         case sc::FormulaResultValue::Value:
5651             cout << aRV.mfValue << " (value)";
5652             break;
5653         case sc::FormulaResultValue::String:
5654             cout << aRV.maString.getString() << " (string)";
5655             break;
5656         case sc::FormulaResultValue::Error:
5657             cout << ScGlobal::GetErrorString(aRV.mnError) << " (error: " << int(aRV.mnError) << ")";
5658             break;
5659         case sc::FormulaResultValue::Invalid:
5660             cout << "(invalid)";
5661             break;
5662         default:
5663             ;
5664     }
5665     cout << endl;
5666 }
5667 
5668 #endif
5669 
5670 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
5671