From 855da692bcee086508d6f04dfbb405b59d5f6afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= Date: Wed, 10 Jun 2026 13:54:41 +0000 Subject: [PATCH] crashtesting: unshare formula cells the matrix import overwrites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ScDocumentImport::setMatrixCells places the matrix master and its reference cells with raw cell-store set() calls. set() destroys the cell it overwrites with no shared-formula bookkeeping, so a group whose member dies that way is left with a stale length, or with a dead top cell that the next Interpret of a surviving member dereferences (formulacell.cxx:1560). The xlsx import builds exactly such groups from stacked dynamic arrays: the empty spill placeholders of adjacent rows compile to identical empty token arrays, and applyCellFormulas puts vertical runs of identical formulas into one group. Materialising the upper row's array then destroys the lower row's group top. A member surviving its top cell's destruction became reachable since: commit e4476c8072af0d825578f5222935718a159bb383 Date: Wed May 6 23:33:16 2026 +0900 sc: Preserve #SPILL! state through XLSX and ODS import which keeps the cells of a spill-flagged array's range alive instead of overwriting them all. Detach the doomed cell from its group before each set(). Reproduced with forum-mso-en4-756517.xlsx and forum-mso-en4-756511.xlsx from interop-test-files, soffice --headless --convert-to xlsx, dbgutil build. Signed-off-by: Caolán McNamara Change-Id: Ic347cee8c30b0d93919173a85fdd42cfd8a208e9 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4723 Reviewed-by: Tomaž Vajngerl Tested-by: Jenkins CPCI (cherry picked from commit 2f74f677ce588c1ecbf3370c08a85ef94d49313e) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207397 Tested-by: Jenkins Reviewed-by: Xisco Fauli --- sc/source/core/data/documentimport.cxx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sc/source/core/data/documentimport.cxx b/sc/source/core/data/documentimport.cxx index fb9318e3f84e..82289c339d7d 100644 --- a/sc/source/core/data/documentimport.cxx +++ b/sc/source/core/data/documentimport.cxx @@ -46,6 +46,19 @@ struct TabAttr std::vector maCols; }; +// The cell store's set() destroys the cell it overwrites with no shared +// formula bookkeeping, so a group whose member dies that way is left with a +// stale length, or with a dead top cell that the next Interpret of a +// surviving member dereferences. Detach the doomed cell from its group first. +void unshareDoomedCell(sc::CellStoreType& rCells, const sc::CellStoreType::iterator& itHint, SCROW nRow) +{ + sc::CellStoreType::position_type aPos = rCells.position(itHint, nRow); + if (aPos.first->type != sc::element_type_formula) + return; + ScFormulaCell& rCell = *sc::formula_block::at(*aPos.first->data, aPos.second); + sc::SharedFormulaUtil::unshareFormulaCell(aPos, rCell); +} + } struct ScDocumentImportImpl @@ -423,6 +436,7 @@ void ScDocumentImport::setMatrixCells( mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pCell->GetCode()); + unshareDoomedCell(rCells, pBlockPos->miCellPos, rBasePos.Row()); pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rBasePos.Row(), pCell); @@ -460,6 +474,7 @@ void ScDocumentImport::setMatrixCells( *ScRefTokenHelper::getSingleRef(t) = aRefData; ScTokenArray aTokArr(aArr.CloneValue()); pCell = new ScFormulaCell(mpImpl->mrDoc, aPos, aTokArr, eGram, ScMatrixMode::Reference); + unshareDoomedCell(rCells, pBlockPos->miCellPos, aPos.Row()); pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, aPos.Row(), pCell); } @@ -480,6 +495,7 @@ void ScDocumentImport::setMatrixCells( *ScRefTokenHelper::getSingleRef(t) = aRefData; ScTokenArray aTokArr(aArr.CloneValue()); pCell = new ScFormulaCell(mpImpl->mrDoc, aPos, aTokArr, eGram, ScMatrixMode::Reference); + unshareDoomedCell(rColCells, pBlockPos->miCellPos, aPos.Row()); pBlockPos->miCellPos = rColCells.set(pBlockPos->miCellPos, aPos.Row(), pCell); }