From 2bdc2aef5c02e0eb591b3aa8d9d7e3df00df657c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= Date: Tue, 15 Sep 2026 22:48:49 +0000 Subject: [PATCH] crashtesting: buffer a shape's end only if its start is buffered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit seen on converting interop-test-files/doc/forum-mso-en4-218597.doc to rtf and reloading the export, which aborts with: soffice.bin: sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx:10493: void writerfilter::dmapper::DomainMapper_Impl::substream(Id, const writerfilter::Reference::Pointer_t&): Assertion `m_aContextStack.size() == contextSize' failed. #12 writerfilter::dmapper::DomainMapper_Impl::substream at engine/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx:10494 #13 writerfilter::dmapper::DomainMapper::lcl_substream at engine/sw/source/writerfilter/dmapper/DomainMapper.cxx:5039 #14 writerfilter::LoggedStream::substream at engine/sw/source/writerfilter/dmapper/LoggedResources.cxx:275 #15 writerfilter::rtftok::RTFDocumentImpl::resolveSubstream at engine/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx:407 #16 writerfilter::rtftok::RTFDocumentImpl::resolveSubstream at engine/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx:379 #17 writerfilter::rtftok::RTFDocumentImpl::sectBreak at engine/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx:778 #18 writerfilter::rtftok::RTFDocumentImpl::dispatchSymbol at engine/sw/source/writerfilter/rtftok/rtfdispatchsymbol.cxx:217 In the header \intbl makes the table buffer current, so \shptxt buffers the shape start instead of sending it to the dmapper. A paragraph mark inside the shape text finds no table properties in that buffer and decides it is not in a table. Replaying the buffer then sends the start to the dmapper, and the start pushes a paragraph context. When the shape text group ends the current buffer is still set, so the end is appended to the buffer that was just drained. Nothing replays that buffer again, so the paragraph context stays on the stack and the header substream ends deeper than it began. The start and the end have tested the current buffer separately since: commit 818d80eb0cee65d1bcc4fdb74b32a7c42cc1336e Date: Fri Jun 28 16:47:49 2013 +0200 RTF import: support shapes inside tables so remember whether the shape is open in the dmapper, and send its end there directly when it is Signed-off-by: Caolán McNamara Change-Id: Ifb94f77e3db4d3fcc6041a7927e88c62d9d970a4 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12757 Reviewed-by: Miklos Vajna Tested-by: Jenkins CPCI (cherry picked from commit 306c1f840c413b352940513c83d1498d599b8359) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211313 Reviewed-by: Xisco Fauli Tested-by: Jenkins --- .../rtftok/data/shape-in-header-table-row.rtf | 1 + sw/qa/writerfilter/rtftok/rtfdocumentimpl.cxx | 14 ++++++++++++++ .../writerfilter/rtftok/rtfdocumentimpl.cxx | 6 ++++-- sw/source/writerfilter/rtftok/rtfsdrimport.cxx | 17 ++++++++++++++--- sw/source/writerfilter/rtftok/rtfsdrimport.hxx | 5 +++++ 5 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 sw/qa/writerfilter/rtftok/data/shape-in-header-table-row.rtf diff --git a/sw/qa/writerfilter/rtftok/data/shape-in-header-table-row.rtf b/sw/qa/writerfilter/rtftok/data/shape-in-header-table-row.rtf new file mode 100644 index 000000000000..188f6f4015d8 --- /dev/null +++ b/sw/qa/writerfilter/rtftok/data/shape-in-header-table-row.rtf @@ -0,0 +1 @@ +{\rtf1{\header\pard\intbl{\shp{\*\shpinst{\shptxt A{\par}}}}\cell\row}\par} diff --git a/sw/qa/writerfilter/rtftok/rtfdocumentimpl.cxx b/sw/qa/writerfilter/rtftok/rtfdocumentimpl.cxx index d89ffe382206..23c1c8fda9c2 100644 --- a/sw/qa/writerfilter/rtftok/rtfdocumentimpl.cxx +++ b/sw/qa/writerfilter/rtftok/rtfdocumentimpl.cxx @@ -134,6 +134,20 @@ CPPUNIT_TEST_FIXTURE(Test, testImageSizeInShapeText) // i.e. the size of the icon was inherited from the containing shape, losing the own size. CPPUNIT_ASSERT_EQUAL(nExpected, rImageSize.GetWidth()); } + +CPPUNIT_TEST_FIXTURE(Test, testShapeInHeaderTableRow) +{ + // Given a header with a shape in a table row, and a paragraph mark inside the shape text: + // When loading this document: + createSwDoc("shape-in-header-table-row.rtf"); + + // Then make sure the document loads and the shape is there: + // Without the accompanying fix in place, this test would have failed with an assertion + // failure in DomainMapper_Impl::substream(), because the header left the context stack one + // level deeper than it found it. + SwDoc* pDoc = getSwDoc(); + CPPUNIT_ASSERT_EQUAL(static_cast(1), pDoc->GetSpzFrameFormats()->size()); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx b/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx index 32dcd9b58e76..4b067be59182 100644 --- a/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx +++ b/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx @@ -2013,7 +2013,7 @@ void RTFDocumentImpl::replayBuffer(RTFBuffer_t& rBuffer, RTFSprms* const pSprms, uno::Reference xShape; std::get<1>(aTuple)->getAny() >>= xShape; if (xShape.is()) - Mapper().startShape(xShape); + m_pSdrImport->open(xShape); } else if (std::get<0>(aTuple) == RTFBufferTypes::ResolveSubstream) { @@ -3813,7 +3813,9 @@ void RTFDocumentImpl::afterPopState(RTFParserState& rState) && !m_aStates.top().getDrawingObject().getHadShapeText()) { m_aStates.top().setHadShapeText(true); - if (!m_aStates.top().getCurrentBuffer()) + // A replay can send a buffered start before the group ends, and the end + // has to follow it. + if (m_pSdrImport->isOpenInMapper() || !m_aStates.top().getCurrentBuffer()) m_pSdrImport->close(); else m_aStates.top().getCurrentBuffer()->emplace_back(RTFBufferTypes::EndShape, diff --git a/sw/source/writerfilter/rtftok/rtfsdrimport.cxx b/sw/source/writerfilter/rtftok/rtfsdrimport.cxx index 880e5bd9b085..c063fb4c5138 100644 --- a/sw/source/writerfilter/rtftok/rtfsdrimport.cxx +++ b/sw/source/writerfilter/rtftok/rtfsdrimport.cxx @@ -65,6 +65,7 @@ RTFSdrImport::RTFSdrImport(RTFDocumentImpl& rDocument, , m_bTextFrame(false) , m_bTextGraphicObject(false) , m_bFakePict(false) + , m_bOpenInMapper(false) { if (xDstDoc) m_aParents.push(xDstDoc->getDrawPage()); @@ -1165,10 +1166,10 @@ void RTFSdrImport::resolve(RTFShape& rShape, bool bClose, ShapeOrPict const shap // cells exist - resolvePict() defers its own output the same way. if (xShape.is() && !m_rImport.bufferShapeInsertion(xShape, bClose)) { - m_rImport.Mapper().startShape(xShape); + open(xShape); if (bClose) { - m_rImport.Mapper().endShape(); + close(); } } @@ -1186,7 +1187,17 @@ void RTFSdrImport::resolve(RTFShape& rShape, bool bClose, ShapeOrPict const shap m_xShape = std::move(xShape); } -void RTFSdrImport::close() { m_rImport.Mapper().endShape(); } +void RTFSdrImport::open(uno::Reference const& xShape) +{ + m_rImport.Mapper().startShape(xShape); + m_bOpenInMapper = true; +} + +void RTFSdrImport::close() +{ + m_rImport.Mapper().endShape(); + m_bOpenInMapper = false; +} void RTFSdrImport::append(std::u16string_view aKey, std::u16string_view aValue) { diff --git a/sw/source/writerfilter/rtftok/rtfsdrimport.hxx b/sw/source/writerfilter/rtftok/rtfsdrimport.hxx index e9f940f1ac6b..63f0acc7e399 100644 --- a/sw/source/writerfilter/rtftok/rtfsdrimport.hxx +++ b/sw/source/writerfilter/rtftok/rtfsdrimport.hxx @@ -53,6 +53,8 @@ public: PICT }; void resolve(RTFShape& rShape, bool bClose, ShapeOrPict shapeOrPict); + /// Send the start of a shape to the dmapper. It stays open there until its end is sent. + void open(css::uno::Reference const& xShape); void close(); void append(std::u16string_view aKey, std::u16string_view aValue); /// Append property on the current parent. @@ -78,6 +80,7 @@ public: void popParent(); css::uno::Reference const& getCurrentShape() const { return m_xShape; } bool isFakePict() const { return m_bFakePict; } + bool isOpenInMapper() const { return m_bOpenInMapper; } bool isTextGraphicObject() const { return m_bTextGraphicObject; } private: @@ -98,6 +101,8 @@ private: bool m_bTextGraphicObject; /// if inside \pict, but actually it's a shape (not a picture) bool m_bFakePict; + /// If a shape start has gone to the dmapper and its end has not. + bool m_bOpenInMapper; std::stack m_aGraphicZOrderHelpers; }; } // namespace writerfilter::rtftok