# UNDF: UNDF-2026-000001136 # openoffice-0001: XclExpXFBuffer::AddBorderAndFill O(N²) std::find_if on maBorders/maFills # # In main/sc/source/filter/excel/xestyle.cxx, AddBorderAndFill() is called once # per XF record during .xlsx export (via AppendXFIndex). Each call performs two # linear scans: one over maBorders (XclExpCellBorder vector) and one over # maFills (XclExpCellArea vector). With N unique XF styles the total work is # O(N²): N calls × O(N) scan each. # # SaveXFXml() also does two find_if passes per XF to locate the index — same # O(N) per XF, so O(N²) total over all XF records being saved. # # A large spreadsheet with many unique cell styles (borders + fills) triggers # this path heavily. EXC_XF_MAXCOUNT caps at 4050, giving up to ~8M comparisons # for the border list and ~8M for fills in the worst case — 16M total where 4050 # O(1) lookups would suffice. # # Fix: add parallel unordered_map<> index tables (maBorderIndex, maFillIndex) # that map a packed key derived from each struct's fields to the insertion index. # AddBorderAndFill does a hash lookup before push_back; SaveXFXml reads the index # directly instead of scanning. # # Severity: MEDIUM-HIGH — O(N²) on .xlsx export with diverse cell styles; # EXC_XF_MAXCOUNT = 4050 so worst case ~16M field comparisons → ~O(1) with map. # Ratio: ~4050x at the maximum style count. --- a/main/sc/source/filter/excel/xestyle.cxx +++ b/main/sc/source/filter/excel/xestyle.cxx @@ -2360,6 +2360,20 @@ maBorders(), maFills(), + maBorderIndex(), + maFillIndex(), maXclExpXFMap() { } +// Pack XclExpCellBorder fields into a uint64_t key for O(1) lookup. +static sal_uInt64 lcl_BorderKey( const XclExpCellBorder& r ) +{ + return (sal_uInt64(r.mnLeftColor) << 48) + ^ (sal_uInt64(r.mnRightColor) << 40) + ^ (sal_uInt64(r.mnTopColor) << 32) + ^ (sal_uInt64(r.mnBottomColor) << 24) + ^ (sal_uInt64(r.mnDiagColor) << 16) + ^ (sal_uInt64(r.mnLeftLine) << 12) + ^ (sal_uInt64(r.mnRightLine) << 8) + ^ (sal_uInt64(r.mnTopLine) << 4) + ^ (sal_uInt64(r.mnBottomLine)) + ^ (sal_uInt64(r.mbDiagTLtoBR) << 60) + ^ (sal_uInt64(r.mbDiagBLtoTR) << 61) + ^ (sal_uInt64(r.mnLeftColorId) * 0x9e3779b9ULL) + ^ (sal_uInt64(r.mnRightColorId) * 0x6c62272eULL) + ^ (sal_uInt64(r.mnTopColorId) * 0x517cc1b7ULL) + ^ (sal_uInt64(r.mnBottomColorId) * 0x27d4eb2fULL) + ^ (sal_uInt64(r.mnDiagColorId) * 0xb492b66fULL); +} + +// Pack XclExpCellArea fields into a uint64_t key for O(1) lookup. +static sal_uInt64 lcl_FillKey( const XclExpCellArea& r ) +{ + return (sal_uInt64(r.mnForeColor) << 48) + ^ (sal_uInt64(r.mnBackColor) << 32) + ^ (sal_uInt64(r.mnPattern) << 16) + ^ (sal_uInt64(r.mnForeColorId) * 0x9e3779b9ULL) + ^ (sal_uInt64(r.mnBackColorId) * 0x6c62272eULL); +} + void XclExpXFBuffer::AddBorderAndFill( const XclExpXF& rXF ) { - if( std::find_if( maBorders.begin(), maBorders.end(), XclExpBorderPred( rXF.GetBorderData() ) ) == maBorders.end() ) - { + sal_uInt64 nBorderKey = lcl_BorderKey( rXF.GetBorderData() ); + if( maBorderIndex.find( nBorderKey ) == maBorderIndex.end() ) + { + maBorderIndex[ nBorderKey ] = static_cast( maBorders.size() ); maBorders.push_back( rXF.GetBorderData() ); } - if( std::find_if( maFills.begin(), maFills.end(), XclExpFillPred( rXF.GetAreaData() ) ) == maFills.end() ) - { + sal_uInt64 nFillKey = lcl_FillKey( rXF.GetAreaData() ); + if( maFillIndex.find( nFillKey ) == maFillIndex.end() ) + { + maFillIndex[ nFillKey ] = static_cast( maFills.size() ); maFills.push_back( rXF.GetAreaData() ); } } void XclExpXFBuffer::SaveXFXml( XclExpXmlStream& rStrm, XclExpXF& rXF ) { - XclExpBorderList::iterator aBorderPos = - std::find_if( maBorders.begin(), maBorders.end(), XclExpBorderPred( rXF.GetBorderData() ) ); - DBG_ASSERT( aBorderPos != maBorders.end(), "XclExpXFBuffer::SaveXml - Invalid @borderId!" ); - XclExpFillList::iterator aFillPos = - std::find_if( maFills.begin(), maFills.end(), XclExpFillPred( rXF.GetAreaData() ) ); - DBG_ASSERT( aFillPos != maFills.end(), "XclExpXFBuffer::SaveXml - Invalid @fillId!" ); - - sal_Int32 nBorderId = 0, nFillId = 0; - if( aBorderPos != maBorders.end() ) - nBorderId = std::distance( maBorders.begin(), aBorderPos ); - if( aFillPos != maFills.end() ) - nFillId = std::distance( maFills.begin(), aFillPos ); + sal_Int32 nBorderId = 0, nFillId = 0; + { + auto it = maBorderIndex.find( lcl_BorderKey( rXF.GetBorderData() ) ); + DBG_ASSERT( it != maBorderIndex.end(), "XclExpXFBuffer::SaveXml - Invalid @borderId!" ); + if( it != maBorderIndex.end() ) + nBorderId = static_cast( it->second ); + } + { + auto it = maFillIndex.find( lcl_FillKey( rXF.GetAreaData() ) ); + DBG_ASSERT( it != maFillIndex.end(), "XclExpXFBuffer::SaveXml - Invalid @fillId!" ); + if( it != maFillIndex.end() ) + nFillId = static_cast( it->second ); + } rXF.SetXmlIds( nBorderId, nFillId ); rXF.SaveXml( rStrm ); } --- a/main/sc/source/filter/inc/xestyle.hxx +++ b/main/sc/source/filter/inc/xestyle.hxx @@ -757,6 +757,8 @@ + #include + typedef ::std::vector< XclExpCellBorder > XclExpBorderList; typedef ::std::vector< XclExpCellArea > XclExpFillList; + typedef ::std::unordered_map< sal_uInt64, sal_uInt32 > XclExpIndexMap; XclExpBorderList maBorders; /// List of borders used by XF records XclExpFillList maFills; /// List of fills used by XF records + XclExpIndexMap maBorderIndex; /// Hash map: border key → index in maBorders + XclExpIndexMap maFillIndex; /// Hash map: fill key → index in maFills