1
0
Fork 0
libreoffice/basic/source/classes/image.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

697 lines
25 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <tools/stream.hxx>
#include <tools/tenccvt.hxx>
#include <osl/thread.h>
#include <o3tl/safeint.hxx>
#include <sal/log.hxx>
#include <basic/sbx.hxx>
#include <sb.hxx>
#include <sbxprop.hxx>
#include <string.h>
#include <image.hxx>
#include <codegen.hxx>
#include <memory>
#include <string_view>
SbiImage::SbiImage()
: bError(false)
, nFlags(SbiImageFlags::NONE)
, nStringSize(0)
, nDimBase(0)
, eCharSet(osl_getThreadTextEncoding())
, nStringIdx(0)
, nStringOff(0)
, bInit(false)
, bFirstInit(true)
{
}
SbiImage::~SbiImage()
{
}
void SbiImage::Clear()
{
mvStringOffsets.clear();
pStrings.reset();
aCode.clear();
aLegacyPCode.clear();
nFlags = SbiImageFlags::NONE;
nStringSize= 0;
eCharSet = osl_getThreadTextEncoding();
nDimBase = 0;
bError = false;
}
// Open Record
static sal_uInt64 SbiOpenRecord( SvStream& r, FileOffset nSignature, sal_uInt16 nElem )
{
sal_uInt64 nPos = r.Tell();
r.WriteUInt16( static_cast<sal_uInt16>( nSignature ) )
.WriteInt32( 0 ).WriteUInt16( nElem );
return nPos;
}
// Close Record
static void SbiCloseRecord( SvStream& r, sal_uInt64 nOff )
{
sal_uInt64 nPos = r.Tell();
r.Seek( nOff + 2 );
r.WriteInt32(nPos - nOff - 8 );
r.Seek( nPos );
}
constexpr sal_uInt32 nUnicodeDataMagicNumber = 0x556E6920; // "Uni " BE
static bool GetToUnicodePoolData(SvStream& r, sal_uInt64 nLen, sal_uInt64 nNext)
{
const auto nPos = r.Tell();
// Check space for legacy data, magic number and Unicode data
bool bResult = nPos + nLen + sizeof(sal_uInt32) + nLen * sizeof(sal_Unicode) <= nNext;
if (bResult)
{
r.SeekRel(nLen); // Skip legacy data
sal_uInt32 nMagic = 0;
r.ReadUInt32(nMagic);
if (nMagic != nUnicodeDataMagicNumber)
{
r.Seek(nPos); // return
bResult = false;
}
}
return bResult;
}
bool SbiImage::Load( SvStream& r, sal_uInt32& nVersion )
{
sal_uInt16 nSign, nCount;
sal_uInt32 nLen;
Clear();
// Read Master-Record
r.ReadUInt16( nSign ).ReadUInt32( nLen ).ReadUInt16( nCount );
sal_uInt64 nLast = r.Tell() + nLen;
bool bBadVer = false;
if( nSign == static_cast<sal_uInt16>( FileOffset::Module ) )
{
sal_uInt32 nCharSet; // System charset
sal_uInt32 lDimBase;
sal_uInt16 nTmpFlags;
sal_uInt16 nReserved1;
sal_uInt32 nReserved2;
sal_uInt32 nReserved3;
r.ReadUInt32( nVersion ).ReadUInt32( nCharSet ).ReadUInt32( lDimBase )
.ReadUInt16( nTmpFlags ).ReadUInt16( nReserved1 ).ReadUInt32( nReserved2 ).ReadUInt32( nReserved3 );
nFlags = static_cast<SbiImageFlags>(nTmpFlags);
eCharSet = nCharSet;
eCharSet = GetSOLoadTextEncoding( eCharSet );
bBadVer = ( nVersion > B_IMG_VERSION_13 );
nDimBase = static_cast<sal_uInt16>(lDimBase);
}
bool bLegacy = ( nVersion < B_IMG_VERSION_12 );
sal_uInt64 nNext;
while( ( nNext = r.Tell() ) < nLast )
{
r.ReadUInt16( nSign ).ReadUInt32( nLen ).ReadUInt16( nCount );
nNext += nLen + 8;
if( r.GetError() == ERRCODE_NONE )
{
switch( static_cast<FileOffset>( nSign ) )
{
case FileOffset::Name:
aName = r.ReadUniOrByteString(eCharSet);
break;
case FileOffset::Comment:
aComment = r.ReadUniOrByteString(eCharSet );
break;
case FileOffset::Source:
{
aOUSource = r.ReadUniOrByteString(eCharSet);
break;
}
case FileOffset::ExtSource:
{
//assuming an empty string with just the lead 32bit/16bit len indicator
const size_t nMinStringSize = (eCharSet == RTL_TEXTENCODING_UNICODE) ? 4 : 2;
const sal_uInt64 nMaxStrings = r.remainingSize() / nMinStringSize;
if (nCount > nMaxStrings)
{
SAL_WARN("basic", "Parsing error: " << nMaxStrings <<
" max possible entries, but " << nCount << " claimed, truncating");
nCount = nMaxStrings;
}
for( sal_uInt16 j = 0; j < nCount; ++j)
{
aOUSource += r.ReadUniOrByteString(eCharSet);
}
break;
}
case FileOffset::PCode:
if( bBadVer ) break;
aCode.resize(nLen);
r.ReadBytes(aCode.data(), aCode.size());
if ( bLegacy )
{
aLegacyPCode = std::move(aCode);
PCodeBuffConvertor<sal_uInt16, sal_uInt32> aLegacyToNew(aLegacyPCode.data(),
aLegacyPCode.size());
aLegacyToNew.convert();
aCode = aLegacyToNew.GetBuffer();
// we don't release the legacy buffer
// right now, that's because the module
// needs it to fix up the method
// nStart members. When that is done
// the module can release the buffer
// or it can wait until this routine
// is called again or when this class // destructs all of which will trigger
// release of the buffer.
}
break;
case FileOffset::Publics:
case FileOffset::PoolDir:
case FileOffset::SymPool:
case FileOffset::LineRanges:
break;
case FileOffset::StringPool:
{
// the data layout is: nCount of 32-bit offsets into both legacy 1-byte char stream
// and resulting char buffer (1:1 correspondence assumed; 16 of 32 bits used);
// 32-bit length N of following 1-byte char stream (16 bits used); N bytes of 1-byte
// char stream; then optional magic number and stream of N sal_Unicode characters.
if( bBadVer ) break;
//assuming an empty string with just the lead 32bit len indicator
const sal_uInt64 nMinStringSize = 4;
const sal_uInt64 nMaxStrings = r.remainingSize() / nMinStringSize;
if (nCount > nMaxStrings)
{
SAL_WARN("basic", "Parsing error: " << nMaxStrings <<
" max possible entries, but " << nCount << " claimed, truncating");
nCount = nMaxStrings;
}
MakeStrings( nCount );
for (size_t i = 0; i < mvStringOffsets.size() && r.good(); i++)
{
sal_uInt32 nOff;
r.ReadUInt32( nOff );
mvStringOffsets[ i ] = static_cast<sal_uInt16>(nOff);
}
r.ReadUInt32( nLen );
if (r.good())
{
pStrings.reset(new sal_Unicode[ nLen ]);
nStringSize = static_cast<sal_uInt16>(nLen);
if (GetToUnicodePoolData(r, nLen, nNext))
{
OUString s = read_uInt16s_ToOUString(r, nLen);
memcpy(pStrings.get(), s.getStr(), s.getLength() * sizeof(sal_Unicode));
}
else
{
std::unique_ptr<char[]> pByteStrings(new char[nLen]);
r.ReadBytes(pByteStrings.get(), nLen);
for (size_t j = 0; j < mvStringOffsets.size(); j++)
{
sal_uInt16 nOff2 = static_cast<sal_uInt16>(mvStringOffsets[j]);
OUString aStr(pByteStrings.get() + nOff2, strlen(pByteStrings.get() + nOff2), eCharSet);
memcpy(pStrings.get() + nOff2, aStr.getStr(), (aStr.getLength() + 1) * sizeof(sal_Unicode));
}
}
}
break;
}
case FileOffset::UserTypes:
{
//assuming an empty string with just the lead 32bit/16bit len indicator
const size_t nMinStringSize = (eCharSet == RTL_TEXTENCODING_UNICODE) ? 4 : 2;
const sal_uInt64 nMinRecordSize = nMinStringSize + sizeof(sal_Int16);
const sal_uInt64 nMaxRecords = r.remainingSize() / nMinRecordSize;
if (nCount > nMaxRecords)
{
SAL_WARN("basic", "Parsing error: " << nMaxRecords <<
" max possible entries, but " << nCount << " claimed, truncating");
nCount = nMaxRecords;
}
// User defined types; ref.: SbiParser::DefType
for (sal_uInt16 i = 0; i < nCount; i++)
{
OUString aTypeName = r.ReadUniOrByteString(eCharSet);
sal_uInt16 nTypeMembers;
r.ReadUInt16(nTypeMembers);
const sal_uInt64 nMaxTypeMembers = r.remainingSize() / 8;
if (nTypeMembers > nMaxTypeMembers)
{
SAL_WARN("basic", "Parsing error: " << nMaxTypeMembers <<
" max possible entries, but " << nTypeMembers << " claimed, truncating");
nTypeMembers = nMaxTypeMembers;
}
SbxObject *pType = new SbxObject(aTypeName);
SbxArray *pTypeMembers = pType->GetProperties();
for (sal_uInt16 j = 0; j < nTypeMembers; j++)
{
OUString aMemberName = r.ReadUniOrByteString(eCharSet);
sal_Int16 aIntMemberType;
r.ReadInt16(aIntMemberType);
SbxDataType aMemberType = static_cast< SbxDataType > ( aIntMemberType );
SbxProperty *pTypeElem = new SbxProperty( aMemberName, aMemberType );
sal_uInt32 aIntFlag;
r.ReadUInt32(aIntFlag);
SbxFlagBits nElemFlags = static_cast< SbxFlagBits > ( aIntFlag );
pTypeElem->SetFlags(nElemFlags);
sal_Int16 hasObject;
r.ReadInt16(hasObject);
if (hasObject == 1)
{
if(aMemberType == SbxOBJECT)
{
// nested user defined types
// declared before use, so it is ok to reference it by name on load
OUString aNestedTypeName = r.ReadUniOrByteString(eCharSet);
SbxObject* pNestedTypeObj = static_cast< SbxObject* >( rTypes->Find( aNestedTypeName, SbxClassType::Object ) );
if (pNestedTypeObj)
{
SbxObjectRef pCloneObj = cloneTypeObjectImpl( *pNestedTypeObj );
pTypeElem->PutObject( pCloneObj.get() );
}
}
else
{
// an array
SbxDimArray* pArray = new SbxDimArray(
static_cast<SbxDataType>(aMemberType & 0x0FFF));
sal_Int16 isFixedSize;
r.ReadInt16(isFixedSize);
if (isFixedSize == 1)
pArray->setHasFixedSize( true );
sal_Int32 nDims;
r.ReadInt32(nDims);
for (sal_Int32 d = 0; d < nDims; d++)
{
sal_Int32 lBound;
sal_Int32 uBound;
r.ReadInt32(lBound).ReadInt32(uBound);
pArray->unoAddDim(lBound, uBound);
}
const SbxFlagBits nSavFlags = pTypeElem->GetFlags();
// need to reset the FIXED flag
// when calling PutObject ( because the type will not match Object )
pTypeElem->ResetFlag(SbxFlagBits::Fixed);
pTypeElem->PutObject( pArray );
pTypeElem->SetFlags(nSavFlags);
}
}
pTypeMembers->Insert(pTypeElem, pTypeMembers->Count());
}
pType->Remove( u"Name"_ustr, SbxClassType::DontCare );
pType->Remove( u"Parent"_ustr, SbxClassType::DontCare );
AddType(pType);
}
break;
}
case FileOffset::ModEnd:
goto done;
default:
break;
}
}
else
{
break;
}
r.Seek( nNext );
}
done:
r.Seek( nLast );
if (!r.good())
{
bError = true;
}
return !bError;
}
bool SbiImage::Save( SvStream& r, sal_uInt32 nVer )
{
// First of all the header
sal_uInt64 nStart = SbiOpenRecord( r, FileOffset::Module, 1 );
sal_uInt64 nPos;
eCharSet = GetSOStoreTextEncoding( eCharSet );
r .WriteInt32( nVer )
.WriteInt32( eCharSet )
.WriteInt32( nDimBase )
.WriteInt16( static_cast<sal_uInt16>(nFlags) )
.WriteInt16( 0 )
.WriteInt32( 0 )
.WriteInt32( 0 );
// Name?
if (!aName.isEmpty() && r.good())
{
nPos = SbiOpenRecord( r, FileOffset::Name, 1 );
r.WriteUniOrByteString( aName, eCharSet );
SbiCloseRecord( r, nPos );
}
// Comment?
if (!aComment.isEmpty() && r.good())
{
nPos = SbiOpenRecord( r, FileOffset::Comment, 1 );
r.WriteUniOrByteString( aComment, eCharSet );
SbiCloseRecord( r, nPos );
}
// Source?
if (!aOUSource.isEmpty() && r.good())
{
nPos = SbiOpenRecord( r, FileOffset::Source, 1 );
r.WriteUniOrByteString( aOUSource, eCharSet );
SbiCloseRecord( r, nPos );
}
// Binary data?
if (aCode.size() && r.good())
{
nPos = SbiOpenRecord( r, FileOffset::PCode, 1 );
r.WriteBytes(aCode.data(), aCode.size());
SbiCloseRecord( r, nPos );
}
// String-Pool?
if( !mvStringOffsets.empty() )
{
nPos = SbiOpenRecord( r, FileOffset::StringPool, mvStringOffsets.size() );
// For every String:
// sal_uInt32 Offset of the Strings in the Stringblock
for (size_t i = 0; i < mvStringOffsets.size() && r.good(); i++)
{
r.WriteUInt32( mvStringOffsets[ i ] );
}
// Then the String-Block
std::unique_ptr<char[]> pByteStrings(new char[ nStringSize ]);
for( size_t i = 0; i < mvStringOffsets.size(); i++ )
{
sal_uInt16 nOff = static_cast<sal_uInt16>(mvStringOffsets[ i ]);
OString aStr(OUStringToOString(std::u16string_view(pStrings.get() + nOff), eCharSet));
memcpy( pByteStrings.get() + nOff, aStr.getStr(), (aStr.getLength() + 1) * sizeof( char ) );
}
r.WriteUInt32( nStringSize );
r.WriteBytes(pByteStrings.get(), nStringSize);
pByteStrings.reset();
// Now write magic number and store the same data in UTF-16; this is backward compatible:
// old readers will not read this data after having read legacy data, and will proceed
// straight to the end of the record. So no version restriction here.
r.WriteUInt32(nUnicodeDataMagicNumber);
r.WriteUnicodeOrByteText(std::u16string_view(pStrings.get(), nStringSize),
RTL_TEXTENCODING_UNICODE);
SbiCloseRecord( r, nPos );
}
// User defined types
if ( rTypes.is() )
{
sal_uInt32 nTypes = rTypes->Count();
assert(nTypes <= std::numeric_limits<sal_uInt16>::max());
if (nTypes > 0 )
{
nPos = SbiOpenRecord( r, FileOffset::UserTypes, sal::static_int_cast<sal_uInt16>(nTypes) );
for (sal_uInt32 i = 0; i < nTypes; i++)
{
SbxObject* pType = static_cast<SbxObject*>(rTypes->Get(i));
OUString aTypeName = pType->GetClassName();
r.WriteUniOrByteString( aTypeName, eCharSet );
SbxArray *pTypeMembers = pType->GetProperties();
sal_uInt32 nTypeMembers = pTypeMembers->Count();
assert(nTypeMembers <= std::numeric_limits<sal_uInt16>::max());
r.WriteInt16(sal::static_int_cast<sal_uInt16>(nTypeMembers));
for (sal_uInt32 j = 0; j < nTypeMembers; j++)
{
SbxProperty* pTypeElem = static_cast<SbxProperty*>(pTypeMembers->Get(j));
const OUString& aElemName = pTypeElem->GetName();
r.WriteUniOrByteString( aElemName, eCharSet );
SbxDataType dataType = pTypeElem->GetType();
r.WriteInt16(dataType);
SbxFlagBits nElemFlags = pTypeElem->GetFlags();
r.WriteUInt32(static_cast< sal_uInt32 > (nElemFlags) );
SbxBase* pElemObject = pTypeElem->GetObject();
if (pElemObject)
{
r.WriteInt16(1); // has elem Object
if( dataType == SbxOBJECT )
{
// nested user defined types
// declared before use, so it is ok to reference it by name on load
SbxObject* pNestedType = static_cast< SbxObject* > ( pElemObject );
r.WriteUniOrByteString( pNestedType->GetClassName(), eCharSet );
}
else
{
// an array
SbxDimArray* pArray = static_cast< SbxDimArray* > ( pElemObject );
bool bFixedSize = pArray->hasFixedSize();
if (bFixedSize)
r.WriteInt16(1);
else
r.WriteInt16(0);
sal_Int32 nDims = pArray->GetDims();
r.WriteInt32(nDims);
for (sal_Int32 d = 1; d <= nDims; d++)
{
sal_Int32 lBound;
sal_Int32 uBound;
pArray->GetDim(d, lBound, uBound);
r.WriteInt32(lBound).WriteInt32(uBound);
}
}
}
else
r.WriteInt16(0); // no elem Object
}
}
SbiCloseRecord( r, nPos );
}
}
// Set overall length
SbiCloseRecord( r, nStart );
if (!r.good())
{
bError = true;
}
return !bError;
}
void SbiImage::MakeStrings( short nSize )
{
nStringIdx = 0;
nStringOff = 0;
nStringSize = 1024;
pStrings.reset( new sal_Unicode[ nStringSize ]);
mvStringOffsets.resize(nSize);
if (nSize != 0) {
memset( mvStringOffsets.data(), 0, nSize * sizeof( sal_uInt32 ) );
}
memset( pStrings.get(), 0, nStringSize * sizeof( sal_Unicode ) );
}
// Add a string to StringPool. The String buffer is dynamically
// growing in 1K-Steps
void SbiImage::AddString( const OUString& r )
{
if( nStringIdx >= mvStringOffsets.size() )
{
bError = true;
}
if( bError )
return;
sal_Int32 len = r.getLength() + 1;
sal_uInt32 needed = nStringOff + len;
if( needed > 0xFFFFFF00 )
{
bError = true; // out of mem!
}
else if( needed > nStringSize )
{
sal_uInt32 nNewLen = needed + 1024;
nNewLen &= 0xFFFFFC00; // trim to 1K border
std::unique_ptr<sal_Unicode[]> p(new sal_Unicode[nNewLen]);
memcpy( p.get(), pStrings.get(), nStringSize * sizeof( sal_Unicode ) );
pStrings = std::move(p);
nStringSize = sal::static_int_cast< sal_uInt16 >(nNewLen);
}
if( !bError )
{
mvStringOffsets[ nStringIdx++ ] = nStringOff;
memcpy( pStrings.get() + nStringOff, r.getStr(), len * sizeof( sal_Unicode ) );
nStringOff = nStringOff + len;
// Last String? The update the size of the buffer
if( nStringIdx >= mvStringOffsets.size() )
{
nStringSize = nStringOff;
}
}
}
// Add code block
// The block was fetched by the compiler from class SbBuffer and
// is already created with new. Additionally it contains all Integers
// in Big Endian format, so can be directly read/written.
void SbiImage::AddCode(std::vector<sal_uInt8>&& v)
{
aCode = std::move(v);
}
// Add user type
void SbiImage::AddType(SbxObject const * pObject)
{
if( !rTypes.is() )
{
rTypes = new SbxArray;
}
SbxObject *pCopyObject = new SbxObject(*pObject);
rTypes->Insert(pCopyObject, rTypes->Count());
}
void SbiImage::AddEnum(SbxObject* pObject) // Register enum type
{
if( !rEnums.is() )
{
rEnums = new SbxArray;
}
rEnums->Insert(pObject, rEnums->Count());
}
// Note: IDs start with 1
OUString SbiImage::GetString( sal_uInt32 nId, SbxDataType *eType ) const
{
if( nId && nId <= mvStringOffsets.size() )
{
sal_uInt32 nOff = mvStringOffsets[ nId - 1 ];
sal_Unicode* pStr = pStrings.get() + nOff;
sal_uInt32 nNextOff = (nId < mvStringOffsets.size()) ? mvStringOffsets[ nId ] : nStringSize;
sal_uInt32 nLen = nNextOff - nOff - 1;
// #i42467: Special treatment for vbNullChar
if (*pStr == 0)
{
if( nLen == 1 )
{
return OUString( u'\0');
}
}
else
{
// tdf#143707 - check if the data type character was added after the string termination
// symbol. It was added in basic/source/comp/symtbl.cxx.
OUString aOUStr(pStr);
if (eType != nullptr)
{
*eType = SbxSTRING;
if (o3tl::make_unsigned(aOUStr.getLength()) < nLen)
{
const sal_Unicode pTypeChar = *(pStrings.get() + nOff + aOUStr.getLength() + 1);
switch (pTypeChar)
{
// 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 = SbxDOUBLE; break;
case '@': *eType = SbxCURRENCY; break;
// tdf#142460 - properly handle boolean values in string pool
case 'b': *eType = SbxBOOL; break;
}
}
}
return aOUStr;
}
}
return OUString();
}
const SbxObject* SbiImage::FindType (const OUString& aTypeName) const
{
return rTypes.is() ? static_cast<SbxObject*>(rTypes->Find(aTypeName,SbxClassType::Object)) : nullptr;
}
sal_uInt16 SbiImage::CalcLegacyOffset( sal_Int32 nOffset )
{
return SbiCodeGen::calcLegacyOffSet(aCode.data(), nOffset);
}
sal_uInt32 SbiImage::CalcNewOffset( sal_Int16 nOffset )
{
return SbiCodeGen::calcNewOffSet(aLegacyPCode.data(), nOffset);
}
void SbiImage::ReleaseLegacyBuffer()
{
aLegacyPCode.clear();
}
bool SbiImage::ExceedsLegacyLimits()
{
return (nStringSize > 0xFF00) || (CalcLegacyOffset(aCode.size()) > 0xFF00);
}
bool SbiImage::ExceedsImgVersion12Limits()
{
const sal_Int16 nMax = std::numeric_limits<sal_Int16>::max();
return nStringSize >= nMax || CalcLegacyOffset(aCode.size()) >= nMax;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */