tdf#131296: get numeric value with its data type in StepLOADNC

In order to load numeric values, generate SbiOpcode::NUMBER_ opcodes
including the numeric value and its data type instead of SbiOpcode::CONST_.

The numeric value and its data type will be restored in
SbiRuntime::StepLOADNC. When the new compiled code is stored in documents,
e.g. password-protected libraries, leagcy LO versions will just read up to
non-numeric characters, thus correctly obtaining number value and ignoring
the type, so the change is backward-compatible.

To interpret legacy compiled code, old treatment of SbiRuntime::StepLOADI
is restored, reverting commit 0b4f8bf571baf2ccd5a8aafdc4deb41867420be3.
This change reimplements the fix for tdf#129596.

Change-Id: I46ebfc77f9bea69479968950c0fb7264e4a7ab27
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/90858
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
diff --git a/basic/source/comp/exprgen.cxx b/basic/source/comp/exprgen.cxx
index 3981bef..6c60c02 100644
--- a/basic/source/comp/exprgen.cxx
+++ b/basic/source/comp/exprgen.cxx
@@ -72,16 +72,14 @@ void SbiExprNode::Gen( SbiCodeGen& rGen, RecursiveMode eRecMode )
        case SbxEMPTY:
            rGen.Gen( SbiOpcode::EMPTY_ );
            break;
        case SbxLONG:
        case SbxINTEGER:
            nStringId = rGen.GetParser()->aGblStrings.Add(nVal, eType);
            rGen.Gen( SbiOpcode::CONST_, nStringId );
            break;
        case SbxSTRING:
            nStringId = rGen.GetParser()->aGblStrings.Add( aStrVal );
            rGen.Gen( SbiOpcode::SCONST_, nStringId );
            break;
        default:
            // tdf#131296 - generate SbiOpcode::NUMBER_ instead of SbiOpcode::CONST_
            // for SbxINTEGER and SbxLONG including their numeric value and its data type,
            // which will be restored in SbiRuntime::StepLOADNC.
            nStringId = rGen.GetParser()->aGblStrings.Add( nVal, eType );
            rGen.Gen( SbiOpcode::NUMBER_, nStringId );
            break;
diff --git a/basic/source/comp/symtbl.cxx b/basic/source/comp/symtbl.cxx
index b05575a..ac6f1ec 100644
--- a/basic/source/comp/symtbl.cxx
+++ b/basic/source/comp/symtbl.cxx
@@ -66,10 +66,13 @@ short SbiStringPool::Add( double n, SbxDataType t )
    char buf[ 40 ];
    switch( t )
    {
        case SbxINTEGER: snprintf( buf, sizeof(buf), "%d", static_cast<short>(n) ); break;
        case SbxLONG:    snprintf( buf, sizeof(buf), "%ld", static_cast<long>(n) ); break;
        case SbxSINGLE:  snprintf( buf, sizeof(buf), "%.6g", static_cast<float>(n) ); break;
        case SbxDOUBLE:  snprintf( buf, sizeof(buf), "%.16g", n ); break;
        // tdf#131296 - store numeric value including its type character
        // See GetSuffixType in basic/source/comp/scanner.cxx for type characters
        case SbxINTEGER: snprintf( buf, sizeof(buf), "%d%%", static_cast<short>(n) ); break;
        case SbxLONG:    snprintf( buf, sizeof(buf), "%ld&", static_cast<long>(n) ); break;
        case SbxSINGLE:  snprintf( buf, sizeof(buf), "%.6g!", static_cast<float>(n) ); break;
        case SbxDOUBLE:  snprintf( buf, sizeof(buf), "%.16g", n ); break; // default processing in SbiRuntime::StepLOADNC - no type character
        case SbxCURRENCY: snprintf(buf, sizeof(buf), "%.16g@", n); break;
        default: break;
    }
    return Add( OUString::createFromAscii( buf ) );
diff --git a/basic/source/runtime/runtime.cxx b/basic/source/runtime/runtime.cxx
index ffd03f2..15b45ec 100644
--- a/basic/source/runtime/runtime.cxx
+++ b/basic/source/runtime/runtime.cxx
@@ -2773,8 +2773,6 @@ void SbiRuntime::StepERROR()

void SbiRuntime::StepLOADNC( sal_uInt32 nOp1 )
{
    SbxVariable* p = new SbxVariable( SbxDOUBLE );

    // #57844 use localized function
    OUString aStr = pImg->GetString( static_cast<short>( nOp1 ) );
    // also allow , !!!
@@ -2783,8 +2781,24 @@ void SbiRuntime::StepLOADNC( sal_uInt32 nOp1 )
    {
        aStr = aStr.replaceAt(iComma, 1, ".");
    }
    double n = ::rtl::math::stringToDouble( aStr, '.', ',' );
    sal_Int32 nParseEnd = 0;
    rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
    double n = ::rtl::math::stringToDouble( aStr, '.', ',', &eStatus, &nParseEnd );

    // tdf#131296 - retrieve data type put in SbiExprNode::Gen
    SbxDataType eType = SbxDOUBLE;
    if ( nParseEnd < aStr.getLength() )
    {
        switch ( aStr[nParseEnd] )
        {
            // See GetSuffixType in basic/source/comp/scanner.cxx for type characters
            case '%': eType = SbxINTEGER; break;
            case '&': eType = SbxLONG; break;
            case '!': eType = SbxSINGLE; break;
            case '@': eType = SbxCURRENCY; break;
        }
    }
    SbxVariable* p = new SbxVariable( eType );
    p->PutDouble( n );
    PushVar( p );
}
@@ -2799,22 +2813,13 @@ void SbiRuntime::StepLOADSC( sal_uInt32 nOp1 )
}

// Immediate Load (+value)
// The opcode is not generated in SbiExprNode::Gen anymore; used for legacy images

void SbiRuntime::StepLOADI( sal_uInt32 nOp1 )
{
    SbxVariable* p = new SbxVariable;

    OUString aStr = pImg->GetString(static_cast<short>(nOp1));
    double n = ::rtl::math::stringToDouble(aStr, '.', ',');
    if (n >= SbxMININT && n <= SbxMAXINT)
    {
        p->PutInteger(static_cast<sal_Int16>(n));
    }
    else
    {
        p->PutLong(static_cast<sal_Int32>(n));
    }
    PushVar(p);
    p->PutInteger( static_cast<sal_Int16>( nOp1 ) );
    PushVar( p );
}

// store a named argument in Argv (+Arg-no. from 1!)
diff --git a/sc/qa/extras/macros-test.cxx b/sc/qa/extras/macros-test.cxx
index f887a95..3801a99 100644
--- a/sc/qa/extras/macros-test.cxx
+++ b/sc/qa/extras/macros-test.cxx
@@ -37,6 +37,8 @@ public:
    void testPasswordProtectedStarBasic();
    void testRowColumn();
    void testPasswordProtectedUnicodeString();
    void testTdf131296_legacy();
    void testTdf131296_new();

    CPPUNIT_TEST_SUITE(ScMacrosTest);
    CPPUNIT_TEST(testStarBasic);
@@ -45,6 +47,8 @@ public:
    CPPUNIT_TEST(testPasswordProtectedStarBasic);
    CPPUNIT_TEST(testRowColumn);
    CPPUNIT_TEST(testPasswordProtectedUnicodeString);
    CPPUNIT_TEST(testTdf131296_legacy);
    CPPUNIT_TEST(testTdf131296_new);

    CPPUNIT_TEST_SUITE_END();
};
@@ -453,6 +457,85 @@ void ScMacrosTest::testPasswordProtectedUnicodeString()
    xCloseable->close(true);
}

void ScMacrosTest::testTdf131296_legacy()
{
    // For legacy password-protected library images, we must correctly get the constants' values,
    // and also - for Integer - the type.
    const std::vector<std::pair<OUString, OUString>> aTests({
        { "TestIntConst", "Integer: 123" },
        { "TestLongConst", "Double: 123" },
        { "TestSingleConst", "Double: 123" },
        { "TestDoubleConst", "Double: 123" },
    });

    OUString aFileName;
    createFileURL("tdf131296_legacy.ods", aFileName);
    auto xComponent = loadFromDesktop(aFileName, "com.sun.star.sheet.SpreadsheetDocument");
    CPPUNIT_ASSERT(xComponent);

    {
        Any aRet;
        Sequence<sal_Int16> aOutParamIndex;
        Sequence<Any> aOutParam;
        Sequence<uno::Any> aParams;

        for (auto& [sTestName, sExpected] : aTests)
        {
            SfxObjectShell::CallXScript(xComponent,
                                        "vnd.sun.Star.script:Protected.Module1." + sTestName
                                            + "?language=Basic&location=document",
                                        aParams, aRet, aOutParamIndex, aOutParam);

            OUString aReturnValue;
            aRet >>= aReturnValue;
            CPPUNIT_ASSERT_EQUAL_MESSAGE(sTestName.toUtf8().getStr(), sExpected, aReturnValue);
        }
    }

    css::uno::Reference<css::util::XCloseable> xCloseable(xComponent, css::uno::UNO_QUERY_THROW);
    xCloseable->close(true);
}

void ScMacrosTest::testTdf131296_new()
{
    // For new password-protected library images, we must correctly get both the constants' values
    // and their types.
    const std::vector<std::pair<OUString, OUString>> aTests({
        { "TestIntConst", "Integer: 123" },
        { "TestLongConst", "Long: 123" },
        { "TestSingleConst", "Single: 123" },
        { "TestDoubleConst", "Double: 123" },
        { "TestCurrencyConst", "Currency: 123.0000" },
    });

    OUString aFileName;
    createFileURL("tdf131296_new.ods", aFileName);
    auto xComponent = loadFromDesktop(aFileName, "com.sun.star.sheet.SpreadsheetDocument");
    CPPUNIT_ASSERT(xComponent);

    {
        Any aRet;
        Sequence<sal_Int16> aOutParamIndex;
        Sequence<Any> aOutParam;
        Sequence<uno::Any> aParams;

        for (auto& [sTestName, sExpected] : aTests)
        {
            SfxObjectShell::CallXScript(xComponent,
                                        "vnd.sun.Star.script:Protected.Module1." + sTestName
                                            + "?language=Basic&location=document",
                                        aParams, aRet, aOutParamIndex, aOutParam);

            OUString aReturnValue;
            aRet >>= aReturnValue;
            CPPUNIT_ASSERT_EQUAL_MESSAGE(sTestName.toUtf8().getStr(), sExpected, aReturnValue);
        }
    }

    css::uno::Reference<css::util::XCloseable> xCloseable(xComponent, css::uno::UNO_QUERY_THROW);
    xCloseable->close(true);
}

ScMacrosTest::ScMacrosTest()
      : UnoApiTest("/sc/qa/extras/testdocuments")
{
diff --git a/sc/qa/extras/testdocuments/tdf131296_legacy.ods b/sc/qa/extras/testdocuments/tdf131296_legacy.ods
new file mode 100644
index 0000000..43ebdeb
--- /dev/null
+++ b/sc/qa/extras/testdocuments/tdf131296_legacy.ods
Binary files differ
diff --git a/sc/qa/extras/testdocuments/tdf131296_new.ods b/sc/qa/extras/testdocuments/tdf131296_new.ods
new file mode 100644
index 0000000..9c0161c
--- /dev/null
+++ b/sc/qa/extras/testdocuments/tdf131296_new.ods
Binary files differ