1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <tools/stream.hxx>
21 #include <tools/tenccvt.hxx>
22 #include <osl/thread.h>
23 #include <o3tl/safeint.hxx>
24 #include <sal/log.hxx>
25 #include <basic/sbx.hxx>
27 #include <sbxprop.hxx>
30 #include <codegen.hxx>
32 #include <string_view>
36 , nFlags(SbiImageFlags::NONE
)
39 , eCharSet(osl_getThreadTextEncoding())
51 void SbiImage::Clear()
53 mvStringOffsets
.clear();
57 nFlags
= SbiImageFlags::NONE
;
59 eCharSet
= osl_getThreadTextEncoding();
65 static sal_uInt64
SbiOpenRecord( SvStream
& r
, FileOffset nSignature
, sal_uInt16 nElem
)
67 sal_uInt64 nPos
= r
.Tell();
68 r
.WriteUInt16( static_cast<sal_uInt16
>( nSignature
) )
69 .WriteInt32( 0 ).WriteUInt16( nElem
);
74 static void SbiCloseRecord( SvStream
& r
, sal_uInt64 nOff
)
76 sal_uInt64 nPos
= r
.Tell();
78 r
.WriteInt32(nPos
- nOff
- 8 );
82 constexpr sal_uInt32 nUnicodeDataMagicNumber
= 0x556E6920; // "Uni " BE
84 static bool GetToUnicodePoolData(SvStream
& r
, sal_uInt64 nLen
, sal_uInt64 nNext
)
86 const auto nPos
= r
.Tell();
87 // Check space for legacy data, magic number and Unicode data
88 bool bResult
= nPos
+ nLen
+ sizeof(sal_uInt32
) + nLen
* sizeof(sal_Unicode
) <= nNext
;
91 r
.SeekRel(nLen
); // Skip legacy data
92 sal_uInt32 nMagic
= 0;
94 if (nMagic
!= nUnicodeDataMagicNumber
)
96 r
.Seek(nPos
); // return
103 bool SbiImage::Load( SvStream
& r
, sal_uInt32
& nVersion
)
106 sal_uInt16 nSign
, nCount
;
110 // Read Master-Record
111 r
.ReadUInt16( nSign
).ReadUInt32( nLen
).ReadUInt16( nCount
);
112 sal_uInt64 nLast
= r
.Tell() + nLen
;
113 bool bBadVer
= false;
114 if( nSign
== static_cast<sal_uInt16
>( FileOffset::Module
) )
116 sal_uInt32 nCharSet
; // System charset
118 sal_uInt16 nTmpFlags
;
119 sal_uInt16 nReserved1
;
120 sal_uInt32 nReserved2
;
121 sal_uInt32 nReserved3
;
122 r
.ReadUInt32( nVersion
).ReadUInt32( nCharSet
).ReadUInt32( lDimBase
)
123 .ReadUInt16( nTmpFlags
).ReadUInt16( nReserved1
).ReadUInt32( nReserved2
).ReadUInt32( nReserved3
);
124 nFlags
= static_cast<SbiImageFlags
>(nTmpFlags
);
126 eCharSet
= GetSOLoadTextEncoding( eCharSet
);
127 bBadVer
= ( nVersion
> B_CURVERSION
);
128 nDimBase
= static_cast<sal_uInt16
>(lDimBase
);
131 bool bLegacy
= ( nVersion
< B_EXT_IMG_VERSION
);
134 while( ( nNext
= r
.Tell() ) < nLast
)
137 r
.ReadUInt16( nSign
).ReadUInt32( nLen
).ReadUInt16( nCount
);
139 if( r
.GetError() == ERRCODE_NONE
)
141 switch( static_cast<FileOffset
>( nSign
) )
143 case FileOffset::Name
:
144 aName
= r
.ReadUniOrByteString(eCharSet
);
146 case FileOffset::Comment
:
147 aComment
= r
.ReadUniOrByteString(eCharSet
);
149 case FileOffset::Source
:
151 aOUSource
= r
.ReadUniOrByteString(eCharSet
);
154 case FileOffset::ExtSource
:
156 //assuming an empty string with just the lead 32bit/16bit len indicator
157 const size_t nMinStringSize
= (eCharSet
== RTL_TEXTENCODING_UNICODE
) ? 4 : 2;
158 const sal_uInt64 nMaxStrings
= r
.remainingSize() / nMinStringSize
;
159 if (nCount
> nMaxStrings
)
161 SAL_WARN("basic", "Parsing error: " << nMaxStrings
<<
162 " max possible entries, but " << nCount
<< " claimed, truncating");
163 nCount
= nMaxStrings
;
165 for( sal_uInt16 j
= 0; j
< nCount
; ++j
)
167 aOUSource
+= r
.ReadUniOrByteString(eCharSet
);
171 case FileOffset::PCode
:
174 r
.ReadBytes(aCode
.data(), aCode
.size());
177 aLegacyPCode
= std::move(aCode
);
179 PCodeBuffConvertor
<sal_uInt16
, sal_uInt32
> aLegacyToNew(aLegacyPCode
.data(),
180 aLegacyPCode
.size());
181 aLegacyToNew
.convert();
182 aCode
= aLegacyToNew
.GetBuffer();
183 // we don't release the legacy buffer
184 // right now, that's because the module
185 // needs it to fix up the method
186 // nStart members. When that is done
187 // the module can release the buffer
188 // or it can wait until this routine
189 // is called again or when this class // destructs all of which will trigger
190 // release of the buffer.
193 case FileOffset::Publics
:
194 case FileOffset::PoolDir
:
195 case FileOffset::SymPool
:
196 case FileOffset::LineRanges
:
198 case FileOffset::StringPool
:
200 // the data layout is: nCount of 32-bit offsets into both legacy 1-byte char stream
201 // and resulting char buffer (1:1 correspondence assumed; 16 of 32 bits used);
202 // 32-bit length N of following 1-byte char stream (16 bits used); N bytes of 1-byte
203 // char stream; then optional magic number and stream of N sal_Unicode characters.
206 //assuming an empty string with just the lead 32bit len indicator
207 const sal_uInt64 nMinStringSize
= 4;
208 const sal_uInt64 nMaxStrings
= r
.remainingSize() / nMinStringSize
;
209 if (nCount
> nMaxStrings
)
211 SAL_WARN("basic", "Parsing error: " << nMaxStrings
<<
212 " max possible entries, but " << nCount
<< " claimed, truncating");
213 nCount
= nMaxStrings
;
215 MakeStrings( nCount
);
216 for (size_t i
= 0; i
< mvStringOffsets
.size() && r
.good(); i
++)
219 r
.ReadUInt32( nOff
);
220 mvStringOffsets
[ i
] = static_cast<sal_uInt16
>(nOff
);
222 r
.ReadUInt32( nLen
);
225 pStrings
.reset(new sal_Unicode
[ nLen
]);
226 nStringSize
= static_cast<sal_uInt16
>(nLen
);
228 if (GetToUnicodePoolData(r
, nLen
, nNext
))
230 OUString s
= read_uInt16s_ToOUString(r
, nLen
);
231 memcpy(pStrings
.get(), s
.getStr(), s
.getLength() * sizeof(sal_Unicode
));
235 std::unique_ptr
<char[]> pByteStrings(new char[nLen
]);
236 r
.ReadBytes(pByteStrings
.get(), nLen
);
237 for (size_t j
= 0; j
< mvStringOffsets
.size(); j
++)
239 sal_uInt16 nOff2
= static_cast<sal_uInt16
>(mvStringOffsets
[j
]);
240 OUString
aStr(pByteStrings
.get() + nOff2
, strlen(pByteStrings
.get() + nOff2
), eCharSet
);
241 memcpy(pStrings
.get() + nOff2
, aStr
.getStr(), (aStr
.getLength() + 1) * sizeof(sal_Unicode
));
247 case FileOffset::UserTypes
:
249 //assuming an empty string with just the lead 32bit/16bit len indicator
250 const size_t nMinStringSize
= (eCharSet
== RTL_TEXTENCODING_UNICODE
) ? 4 : 2;
251 const sal_uInt64 nMinRecordSize
= nMinStringSize
+ sizeof(sal_Int16
);
252 const sal_uInt64 nMaxRecords
= r
.remainingSize() / nMinRecordSize
;
253 if (nCount
> nMaxRecords
)
255 SAL_WARN("basic", "Parsing error: " << nMaxRecords
<<
256 " max possible entries, but " << nCount
<< " claimed, truncating");
257 nCount
= nMaxRecords
;
260 // User defined types; ref.: SbiParser::DefType
261 for (sal_uInt16 i
= 0; i
< nCount
; i
++)
263 OUString aTypeName
= r
.ReadUniOrByteString(eCharSet
);
265 sal_uInt16 nTypeMembers
;
266 r
.ReadUInt16(nTypeMembers
);
268 const sal_uInt64 nMaxTypeMembers
= r
.remainingSize() / 8;
269 if (nTypeMembers
> nMaxTypeMembers
)
271 SAL_WARN("basic", "Parsing error: " << nMaxTypeMembers
<<
272 " max possible entries, but " << nTypeMembers
<< " claimed, truncating");
273 nTypeMembers
= nMaxTypeMembers
;
276 SbxObject
*pType
= new SbxObject(aTypeName
);
277 SbxArray
*pTypeMembers
= pType
->GetProperties();
279 for (sal_uInt16 j
= 0; j
< nTypeMembers
; j
++)
281 OUString aMemberName
= r
.ReadUniOrByteString(eCharSet
);
283 sal_Int16 aIntMemberType
;
284 r
.ReadInt16(aIntMemberType
);
285 SbxDataType aMemberType
= static_cast< SbxDataType
> ( aIntMemberType
);
287 SbxProperty
*pTypeElem
= new SbxProperty( aMemberName
, aMemberType
);
290 r
.ReadUInt32(aIntFlag
);
291 SbxFlagBits nElemFlags
= static_cast< SbxFlagBits
> ( aIntFlag
);
293 pTypeElem
->SetFlags(nElemFlags
);
296 r
.ReadInt16(hasObject
);
300 if(aMemberType
== SbxOBJECT
)
302 // nested user defined types
303 // declared before use, so it is ok to reference it by name on load
304 OUString aNestedTypeName
= r
.ReadUniOrByteString(eCharSet
);
305 SbxObject
* pNestedTypeObj
= static_cast< SbxObject
* >( rTypes
->Find( aNestedTypeName
, SbxClassType::Object
) );
308 SbxObjectRef pCloneObj
= cloneTypeObjectImpl( *pNestedTypeObj
);
309 pTypeElem
->PutObject( pCloneObj
.get() );
315 SbxDimArray
* pArray
= new SbxDimArray(
316 static_cast<SbxDataType
>(aMemberType
& 0x0FFF));
318 sal_Int16 isFixedSize
;
319 r
.ReadInt16(isFixedSize
);
320 if (isFixedSize
== 1)
321 pArray
->setHasFixedSize( true );
325 for (sal_Int32 d
= 0; d
< nDims
; d
++)
329 r
.ReadInt32(lBound
).ReadInt32(uBound
);
330 pArray
->unoAddDim(lBound
, uBound
);
333 const SbxFlagBits nSavFlags
= pTypeElem
->GetFlags();
334 // need to reset the FIXED flag
335 // when calling PutObject ( because the type will not match Object )
336 pTypeElem
->ResetFlag(SbxFlagBits::Fixed
);
337 pTypeElem
->PutObject( pArray
);
338 pTypeElem
->SetFlags(nSavFlags
);
342 pTypeMembers
->Insert(pTypeElem
, pTypeMembers
->Count());
346 pType
->Remove( "Name", SbxClassType::DontCare
);
347 pType
->Remove( "Parent", SbxClassType::DontCare
);
353 case FileOffset::ModEnd
:
374 bool SbiImage::Save( SvStream
& r
, sal_uInt32 nVer
)
376 bool bLegacy
= ( nVer
< B_EXT_IMG_VERSION
);
378 // detect if old code exceeds legacy limits
379 // if so, then disallow save
380 if ( bLegacy
&& ExceedsLegacyLimits() )
383 aEmptyImg
.aName
= aName
;
384 aEmptyImg
.Save( r
, B_LEGACYVERSION
);
387 // First of all the header
388 sal_uInt64 nStart
= SbiOpenRecord( r
, FileOffset::Module
, 1 );
391 eCharSet
= GetSOStoreTextEncoding( eCharSet
);
394 r
.WriteInt32( B_LEGACYVERSION
);
398 r
.WriteInt32( B_CURVERSION
);
400 r
.WriteInt32( eCharSet
)
401 .WriteInt32( nDimBase
)
402 .WriteInt16( static_cast<sal_uInt16
>(nFlags
) )
408 if (!aName
.isEmpty() && r
.good())
410 nPos
= SbiOpenRecord( r
, FileOffset::Name
, 1 );
411 r
.WriteUniOrByteString( aName
, eCharSet
);
412 SbiCloseRecord( r
, nPos
);
415 if (!aComment
.isEmpty() && r
.good())
417 nPos
= SbiOpenRecord( r
, FileOffset::Comment
, 1 );
418 r
.WriteUniOrByteString( aComment
, eCharSet
);
419 SbiCloseRecord( r
, nPos
);
422 if (!aOUSource
.isEmpty() && r
.good())
424 nPos
= SbiOpenRecord( r
, FileOffset::Source
, 1 );
425 r
.WriteUniOrByteString( aOUSource
, eCharSet
);
426 SbiCloseRecord( r
, nPos
);
429 if (aCode
.size() && r
.good())
431 nPos
= SbiOpenRecord( r
, FileOffset::PCode
, 1 );
434 PCodeBuffConvertor
<sal_uInt32
, sal_uInt16
> aNewToLegacy(aCode
.data(), aCode
.size());
435 aNewToLegacy
.convert();
436 aLegacyPCode
= aNewToLegacy
.GetBuffer();
437 r
.WriteBytes(aLegacyPCode
.data(), aLegacyPCode
.size());
441 r
.WriteBytes(aCode
.data(), aCode
.size());
443 SbiCloseRecord( r
, nPos
);
446 if( !mvStringOffsets
.empty() )
448 nPos
= SbiOpenRecord( r
, FileOffset::StringPool
, mvStringOffsets
.size() );
450 // sal_uInt32 Offset of the Strings in the Stringblock
451 for (size_t i
= 0; i
< mvStringOffsets
.size() && r
.good(); i
++)
453 r
.WriteUInt32( mvStringOffsets
[ i
] );
455 // Then the String-Block
456 std::unique_ptr
<char[]> pByteStrings(new char[ nStringSize
]);
457 for( size_t i
= 0; i
< mvStringOffsets
.size(); i
++ )
459 sal_uInt16 nOff
= static_cast<sal_uInt16
>(mvStringOffsets
[ i
]);
460 OString
aStr(OUStringToOString(std::u16string_view(pStrings
.get() + nOff
), eCharSet
));
461 memcpy( pByteStrings
.get() + nOff
, aStr
.getStr(), (aStr
.getLength() + 1) * sizeof( char ) );
463 r
.WriteUInt32( nStringSize
);
464 r
.WriteBytes(pByteStrings
.get(), nStringSize
);
465 pByteStrings
.reset();
467 // Now write magic number and store the same data in UTF-16; this is backward compatible:
468 // old readers will not read this data after having read legacy data, and will proceed
469 // straight to the end of the record. So no version restriction here.
470 r
.WriteUInt32(nUnicodeDataMagicNumber
);
471 write_uInt16s_FromOUString(r
, std::u16string_view(pStrings
.get(), nStringSize
));
473 SbiCloseRecord( r
, nPos
);
475 // User defined types
478 sal_uInt32 nTypes
= rTypes
->Count();
479 assert(nTypes
<= std::numeric_limits
<sal_uInt16
>::max());
482 nPos
= SbiOpenRecord( r
, FileOffset::UserTypes
, sal::static_int_cast
<sal_uInt16
>(nTypes
) );
484 for (sal_uInt32 i
= 0; i
< nTypes
; i
++)
486 SbxObject
* pType
= static_cast<SbxObject
*>(rTypes
->Get(i
));
487 OUString aTypeName
= pType
->GetClassName();
489 r
.WriteUniOrByteString( aTypeName
, eCharSet
);
491 SbxArray
*pTypeMembers
= pType
->GetProperties();
492 sal_uInt32 nTypeMembers
= pTypeMembers
->Count();
493 assert(nTypeMembers
<= std::numeric_limits
<sal_uInt16
>::max());
495 r
.WriteInt16(sal::static_int_cast
<sal_uInt16
>(nTypeMembers
));
497 for (sal_uInt32 j
= 0; j
< nTypeMembers
; j
++)
500 SbxProperty
* pTypeElem
= static_cast<SbxProperty
*>(pTypeMembers
->Get(j
));
502 const OUString
& aElemName
= pTypeElem
->GetName();
503 r
.WriteUniOrByteString( aElemName
, eCharSet
);
505 SbxDataType dataType
= pTypeElem
->GetType();
506 r
.WriteInt16(dataType
);
508 SbxFlagBits nElemFlags
= pTypeElem
->GetFlags();
509 r
.WriteUInt32(static_cast< sal_uInt32
> (nElemFlags
) );
511 SbxBase
* pElemObject
= pTypeElem
->GetObject();
515 r
.WriteInt16(1); // has elem Object
517 if( dataType
== SbxOBJECT
)
519 // nested user defined types
520 // declared before use, so it is ok to reference it by name on load
521 SbxObject
* pNestedType
= static_cast< SbxObject
* > ( pElemObject
);
522 r
.WriteUniOrByteString( pNestedType
->GetClassName(), eCharSet
);
527 SbxDimArray
* pArray
= static_cast< SbxDimArray
* > ( pElemObject
);
529 bool bFixedSize
= pArray
->hasFixedSize();
535 sal_Int32 nDims
= pArray
->GetDims();
538 for (sal_Int32 d
= 1; d
<= nDims
; d
++)
542 pArray
->GetDim(d
, lBound
, uBound
);
543 r
.WriteInt32(lBound
).WriteInt32(uBound
);
548 r
.WriteInt16(0); // no elem Object
552 SbiCloseRecord( r
, nPos
);
555 // Set overall length
556 SbiCloseRecord( r
, nStart
);
564 void SbiImage::MakeStrings( short nSize
)
569 pStrings
.reset( new sal_Unicode
[ nStringSize
]);
570 mvStringOffsets
.resize(nSize
);
572 memset( mvStringOffsets
.data(), 0, nSize
* sizeof( sal_uInt32
) );
574 memset( pStrings
.get(), 0, nStringSize
* sizeof( sal_Unicode
) );
577 // Add a string to StringPool. The String buffer is dynamically
578 // growing in 1K-Steps
579 void SbiImage::AddString( const OUString
& r
)
581 if( nStringIdx
>= short(mvStringOffsets
.size()) )
588 sal_Int32 len
= r
.getLength() + 1;
589 sal_uInt32 needed
= nStringOff
+ len
;
590 if( needed
> 0xFFFFFF00 )
592 bError
= true; // out of mem!
594 else if( needed
> nStringSize
)
596 sal_uInt32 nNewLen
= needed
+ 1024;
597 nNewLen
&= 0xFFFFFC00; // trim to 1K border
598 std::unique_ptr
<sal_Unicode
[]> p(new sal_Unicode
[nNewLen
]);
599 memcpy( p
.get(), pStrings
.get(), nStringSize
* sizeof( sal_Unicode
) );
600 pStrings
= std::move(p
);
601 nStringSize
= sal::static_int_cast
< sal_uInt16
>(nNewLen
);
605 mvStringOffsets
[ nStringIdx
++ ] = nStringOff
;
606 memcpy( pStrings
.get() + nStringOff
, r
.getStr(), len
* sizeof( sal_Unicode
) );
607 nStringOff
= nStringOff
+ len
;
608 // Last String? The update the size of the buffer
609 if( nStringIdx
>= short(mvStringOffsets
.size()) )
611 nStringSize
= nStringOff
;
617 // The block was fetched by the compiler from class SbBuffer and
618 // is already created with new. Additionally it contains all Integers
619 // in Big Endian format, so can be directly read/written.
620 void SbiImage::AddCode(std::vector
<sal_uInt8
>&& v
)
622 aCode
= std::move(v
);
626 void SbiImage::AddType(SbxObject
const * pObject
)
630 rTypes
= new SbxArray
;
632 SbxObject
*pCopyObject
= new SbxObject(*pObject
);
633 rTypes
->Insert(pCopyObject
, rTypes
->Count());
636 void SbiImage::AddEnum(SbxObject
* pObject
) // Register enum type
640 rEnums
= new SbxArray
;
642 rEnums
->Insert(pObject
, rEnums
->Count());
645 // Note: IDs start with 1
646 OUString
SbiImage::GetString( short nId
, SbxDataType
*eType
) const
648 if( nId
&& nId
<= short(mvStringOffsets
.size()) )
650 sal_uInt32 nOff
= mvStringOffsets
[ nId
- 1 ];
651 sal_Unicode
* pStr
= pStrings
.get() + nOff
;
653 sal_uInt32 nNextOff
= (nId
< short(mvStringOffsets
.size())) ? mvStringOffsets
[ nId
] : nStringSize
;
654 sal_uInt32 nLen
= nNextOff
- nOff
- 1;
655 // #i42467: Special treatment for vbNullChar
660 return OUString( u
'\0');
665 // tdf#143707 - check if the data type character was added after the string termination
666 // symbol. It was added in basic/source/comp/symtbl.cxx.
667 OUString
aOUStr(pStr
);
668 if (eType
!= nullptr)
671 if (o3tl::make_unsigned(aOUStr
.getLength()) < nLen
)
673 const sal_Unicode pTypeChar
= *(pStrings
.get() + nOff
+ aOUStr
.getLength() + 1);
676 // See GetSuffixType in basic/source/comp/scanner.cxx for type characters
677 case '%': *eType
= SbxINTEGER
; break;
678 case '&': *eType
= SbxLONG
; break;
679 case '!': *eType
= SbxSINGLE
; break;
680 case '#': *eType
= SbxDOUBLE
; break;
681 case '@': *eType
= SbxCURRENCY
; break;
682 // tdf#142460 - properly handle boolean values in string pool
683 case 'b': *eType
= SbxBOOL
; break;
693 const SbxObject
* SbiImage::FindType (const OUString
& aTypeName
) const
695 return rTypes
.is() ? static_cast<SbxObject
*>(rTypes
->Find(aTypeName
,SbxClassType::Object
)) : nullptr;
698 sal_uInt16
SbiImage::CalcLegacyOffset( sal_Int32 nOffset
)
700 return SbiCodeGen::calcLegacyOffSet(aCode
.data(), nOffset
);
703 sal_uInt32
SbiImage::CalcNewOffset( sal_Int16 nOffset
)
705 return SbiCodeGen::calcNewOffSet(aLegacyPCode
.data(), nOffset
);
708 void SbiImage::ReleaseLegacyBuffer()
710 aLegacyPCode
.clear();
713 bool SbiImage::ExceedsLegacyLimits()
715 return (nStringSize
> 0xFF00) || (CalcLegacyOffset(aCode
.size()) > 0xFF00);
718 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */