From 48ce354351d58f4a63c45d0c56156ff1268bd050 Mon Sep 17 00:00:00 2001
From: Eike Rathke <erack@redhat.com>
Date: Thu, 16 Feb 2023 20:20:31 +0100
Subject: [PATCH] Obtain actual 0-parameter count for OR(), AND() and
 1-parameter functions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

OR and AND for legacy infix notation are classified as binary
operators but in fact are functions with parameter count. In case
no argument is supplied, GetByte() returns 0 and for that case the
implicit binary operator 2 parameters were wrongly assumed.
Similar for functions expecting 1 parameter, without argument 1
was assumed. For "real" unary and binary operators the compiler
already checks parameters. Omit OR and AND and 1-parameter
functions from this implicit assumption and return the actual 0
count.

Change-Id: Ie05398c112a98021ac2875cf7b6de994aee9d882
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147173
Reviewed-by: Eike Rathke <erack@redhat.com>
Tested-by: Jenkins
(cherry picked from commit e7ce9bddadb2db222eaa5f594ef1de2e36d57e5c)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147129
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
(cherry picked from commit d6599a2af131994487d2d9223a4fd32a8c3ddc49)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147132
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Caolán McNamara <caolanm@redhat.com>
---
 formula/source/core/api/token.cxx | 13 +++++--------
 sc/source/core/tool/interpr4.cxx  | 10 +++++++++-
 2 files changed, 14 insertions(+), 9 deletions(-)

From 48ce354351d58f4a63c45d0c56156ff1268bd050 Mon Sep 17 00:00:00 2001
From: Eike Rathke <erack@redhat.com>
Date: Thu, 16 Feb 2023 20:20:31 +0100
Subject: [PATCH] Obtain actual 0-parameter count for OR(), AND() and
 1-parameter functions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

OR and AND for legacy infix notation are classified as binary
operators but in fact are functions with parameter count. In case
no argument is supplied, GetByte() returns 0 and for that case the
implicit binary operator 2 parameters were wrongly assumed.
Similar for functions expecting 1 parameter, without argument 1
was assumed. For "real" unary and binary operators the compiler
already checks parameters. Omit OR and AND and 1-parameter
functions from this implicit assumption and return the actual 0
count.

Change-Id: Ie05398c112a98021ac2875cf7b6de994aee9d882
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147173
Reviewed-by: Eike Rathke <erack@redhat.com>
Tested-by: Jenkins
(cherry picked from commit e7ce9bddadb2db222eaa5f594ef1de2e36d57e5c)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147129
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
(cherry picked from commit d6599a2af131994487d2d9223a4fd32a8c3ddc49)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147132
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Caolán McNamara <caolanm@redhat.com>
---
 formula/source/core/api/token.cxx | 13 +++++--------
 sc/source/core/tool/interpr4.cxx  | 10 +++++++++-
 2 files changed, 14 insertions(+), 9 deletions(-)

From ef9af49e1060bfb2a15db81ba10703ebd520bbf6 Mon Sep 17 00:00:00 2001
From: Eike Rathke <erack@redhat.com>
Date: Mon, 27 Feb 2023 16:10:06 +0100
Subject: [PATCH] Always push a result, even if it's only an error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

PERCENTILE() and QUARTILE() if an error was passed as argument (or
an error encountered during obtaining arguments) omitted to push
an error result, only setting the error.

Fallout from

    commit f336f63da900d76c2bf6e5690f1c8a7bd15a0aa2
    CommitDate: Thu Mar 3 16:28:59 2016 +0000

        tdf#94635 Add FORECAST.ETS functions to Calc

Change-Id: I23e276fb0ce735cfd6383cc963446499dcf819f4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147922
Reviewed-by: Eike Rathke <erack@redhat.com>
Tested-by: Jenkins
(cherry picked from commit 64914560e279c71ff1233f4bab851e2a292797e6)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147901
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
Tested-by: Christian Lohmaier <lohmaier+LibreOffice@googlemail.com>
Reviewed-by: Christian Lohmaier <lohmaier+LibreOffice@googlemail.com>
---
 sc/source/core/tool/interpr3.cxx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

--- a/formula/source/core/api/token.cxx
+++ b/formula/source/core/api/token.cxx
@@ -105,17 +105,14 @@ sal_uInt8 FormulaToken::GetParamCount()
         return 0;       // parameters and specials
                         // ocIf... jump commands not for FAP, have cByte then
 //2do: bool parameter whether FAP or not?
-    else if ( GetByte() )
+    else if (GetByte())
         return GetByte();   // all functions, also ocExternal and ocMacro
-    else if (SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP)
-        return 2;           // binary
-    else if ((SC_OPCODE_START_UN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP)
-            || eOp == ocPercentSign)
-        return 1;           // unary
+    else if (SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP && eOp != ocAnd && eOp != ocOr)
+        return 2;           // binary operators, compiler checked; OR and AND legacy but are functions
+    else if ((SC_OPCODE_START_UN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP) || eOp == ocPercentSign)
+        return 1;           // unary operators, compiler checked
     else if (SC_OPCODE_START_NO_PAR <= eOp && eOp < SC_OPCODE_STOP_NO_PAR)
         return 0;           // no parameter
-    else if (SC_OPCODE_START_1_PAR <= eOp && eOp < SC_OPCODE_STOP_1_PAR)
-        return 1;           // one parameter
     else if (FormulaCompiler::IsOpCodeJumpCommand( eOp ))
         return 1;           // only the condition counts as parameter
     else
--- a/sc/source/core/inc/interpre.hxx
+++ b/sc/source/core/inc/interpre.hxx
@@ -237,6 +237,7 @@ private:
     inline bool MustHaveParamCount( short nAct, short nMust );
     inline bool MustHaveParamCount( short nAct, short nMust, short nMax );
     inline bool MustHaveParamCountMin( short nAct, short nMin );
+    inline bool MustHaveParamCountMinWithStackCheck( short nAct, short nMin );
     void PushParameterExpected();
     void PushIllegalParameter();
     void PushIllegalArgument();
@@ -1064,6 +1065,17 @@ inline bool ScInterpreter::MustHaveParam
     return false;
 }
 
+inline bool ScInterpreter::MustHaveParamCountMinWithStackCheck( short nAct, short nMin )
+{
+    assert(sp >= nAct);
+    if (sp < nAct)
+    {
+        PushParameterExpected();
+        return false;
+    }
+    return MustHaveParamCountMin( nAct, nMin);
+}
+
 inline bool ScInterpreter::CheckStringPositionArgument( double & fVal )
 {
     if (!rtl::math::isFinite( fVal))
--- a/sc/source/core/tool/interpr1.cxx
+++ b/sc/source/core/tool/interpr1.cxx
@@ -7384,7 +7384,7 @@ void ScInterpreter::ScVLookup()
 void ScInterpreter::ScSubTotal()
 {
     sal_uInt8 nParamCount = GetByte();
-    if ( MustHaveParamCountMin( nParamCount, 2 ) )
+    if ( MustHaveParamCountMinWithStackCheck( nParamCount, 2 ) )
     {
         // We must fish the 1st parameter deep from the stack! And push it on top.
         const FormulaToken* p = pStack[ sp - nParamCount ];
@@ -7431,7 +7431,7 @@ void ScInterpreter::ScSubTotal()
 void ScInterpreter::ScAggregate()
 {
     sal_uInt8 nParamCount = GetByte();
-    if ( MustHaveParamCountMin( nParamCount, 3 ) )
+    if ( MustHaveParamCountMinWithStackCheck( nParamCount, 3 ) )
     {
         // fish the 1st parameter from the stack and push it on top.
         const FormulaToken* p = pStack[ sp - nParamCount ];
--- a/sc/source/core/tool/interpr3.cxx
+++ b/sc/source/core/tool/interpr3.cxx
@@ -3462,7 +3462,7 @@ void ScInterpreter::ScPercentile( bool b
     GetNumberSequenceArray( 1, aArray, false );
     if ( aArray.empty() || nGlobalError != FormulaError::NONE )
     {
-        SetError( FormulaError::NoValue );
+        PushNoValue();
         return;
     }
     if ( bInclusive )
@@ -3485,7 +3485,7 @@ void ScInterpreter::ScQuartile( bool bIn
     GetNumberSequenceArray( 1, aArray, false );
     if ( aArray.empty() || nGlobalError != FormulaError::NONE )
     {
-        SetError( FormulaError::NoValue );
+        PushNoValue();
         return;
     }
     if ( bInclusive )
--- a/sc/source/core/tool/interpr4.cxx
+++ b/sc/source/core/tool/interpr4.cxx
@@ -4029,11 +4029,19 @@ StackVar ScInterpreter::Interpret()
                     eOp = ocNone;       // JumpMatrix created
                     nStackBase = sp;
                 }
-                else
+                else if (sp >= pCur->GetParamCount())
                     nStackBase = sp - pCur->GetParamCount();
+                else
+                {
+                    SAL_WARN("sc.core", "Stack anomaly at " << aPos.Format(
+                                ScRefFlags::VALID | ScRefFlags::FORCE_DOC | ScRefFlags::TAB_3D, pDok)
+                            << "  eOp: " << static_cast<int>(eOp)
+                            << "  params: " << static_cast<int>(pCur->GetParamCount())
+                            << "  nStackBase: " << nStackBase << "  sp: " << sp);
+                    nStackBase = sp;
+                    assert(!"underflow");
+                }
             }
-            if ( nStackBase > sp )
-                nStackBase = sp;        // underflow?!?
 
             switch( eOp )
             {
