From 0ef62376ffd17a6ea4f5f6540afa1abc2a989681 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Fri, 14 Aug 2026 21:32:08 +0200 Subject: [PATCH 1/2] fix: walk a style's parent chain instead of recursing it, and read an xml part once Two crashes reported from Play, both on documents rather than on anything the caller does. A `w:basedOn` / `style:parent-style-name` chain was resolved one stack frame per link, so a document declaring enough styles in one chain overflowed the stack of the thread that opened it - an http worker's, which is small. Both registries now walk the chain onto a stack and build it from the root down; the cycle guard stays what it was, an entry present but still null. Reading a part through pugixml's stream loader holds it twice - the chunk list it reads into, then the contiguous buffer it parses in place. The file knows its size, so it is read once into a buffer pugixml is handed and frees. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01WACM9eXR7F5aZ22tyq2jwU --- CHANGELOG.md | 5 + src/odr/internal/odf/odf_style.cpp | 60 ++++++--- .../internal/ooxml/text/ooxml_text_style.cpp | 51 +++++--- src/odr/internal/util/xml_util.cpp | 29 ++++- test/CMakeLists.txt | 1 + .../internal/ooxml/ooxml_text_style_test.cpp | 116 ++++++++++++++++++ 6 files changed, 225 insertions(+), 37 deletions(-) create mode 100644 test/src/internal/ooxml/ooxml_text_style_test.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d3a744bcb..9a2403786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,11 @@ The release run heads these entries with the version and opens a fresh instead of in the running text. - A text document is laid out on the master page it names, so a letter template keeps the margins that leave room for its letterhead. +- A docx or odt that chains its styles deeply opens instead of taking the + process down with it: the `w:basedOn` / `style:parent-style-name` chain is + walked onto a stack rather than recursed, so its length costs no stack. +- A document's xml parts are read once instead of buffered twice on the way into + the parser, which lowers the memory opening a large one takes. ## v6.5.0 - 2026-08-10 diff --git a/src/odr/internal/odf/odf_style.cpp b/src/odr/internal/odf/odf_style.cpp index c5a0370b0..1e77108ec 100644 --- a/src/odr/internal/odf/odf_style.cpp +++ b/src/odr/internal/odf/odf_style.cpp @@ -5,8 +5,10 @@ #include #include +#include #include #include +#include namespace odr::internal::odf { @@ -603,34 +605,54 @@ Style *StyleRegistry::generate_default_style_(const std::string &name, return style.get(); } +/// Walks the `style:parent-style-name` chain onto a stack and builds it from +/// the root down; recursing it costs a stack frame per link. Style *StyleRegistry::generate_style_(const std::string &name, const pugi::xml_node node) { - // a null entry means the style is still resolving, i.e. the parent chain is - // cyclic; break it rather than recurse forever - const auto [style_it, inserted] = m_styles.try_emplace(name); - std::unique_ptr