From c589a788e5680975b52e838c91846a07c65202b6 Mon Sep 17 00:00:00 2001 From: Hatem ElKharashy Date: Fri, 24 Apr 2026 10:11:37 +0300 Subject: [PATCH] Fix stack overflow caused by recursive calls of destructors When a deeply nested SVG file runs on a platform with a limited stack size, recursive calls of destructors will lead to a stack overflow. This fix adds a function in QSvgDocument that will flatten the tree resulting in a stack depth of 1. This function will not be called when AssumeTrustedSource flag is set to true. Task-number: QTBUG-145916 Change-Id: I0fbdeb8222ca39c2db091ebb1ad5c5c4ad44633a Reviewed-by: Robert Löhning (cherry picked from commit 22d503993d156d89086f1a68c76670cbc7130be0) Reviewed-by: Qt Cherry-pick Bot --- diff --git a/src/svg/qsvgdocument.cpp b/src/svg/qsvgdocument.cpp index 85a692a..1505d55 100644 --- a/src/svg/qsvgdocument.cpp +++ b/src/svg/qsvgdocument.cpp @@ -44,7 +44,12 @@ } QSvgDocument::~QSvgDocument() - = default; +{ + // Only do that when AssumeTrustedSource is set to false. Otherwise, all nodes + // will be deleted by recursive calls of destructors. + if (!m_states.trustedSource) + releaseDescendants(); +} static bool hasSvgHeader(const QByteArray &buf) { diff --git a/src/svg/qsvgstructure.cpp b/src/svg/qsvgstructure.cpp index 573c64d..150814a 100644 --- a/src/svg/qsvgstructure.cpp +++ b/src/svg/qsvgstructure.cpp @@ -724,6 +724,42 @@ return prev; } +void QSvgStructureNode::releaseDescendants() +{ + // This function will release the descendants of a QSvgStructureNode from bottom to top. + // Destructors are never called recursively in this case and stack overflow will not + // happen in deeply nested trees. + // This function does not allocate any memory at the cost of sacrificing some performance to + // make it safe to be called from a destructor. + while (!m_renderers.empty()) { + auto nodes = &m_renderers; + bool isSubtree = true; + while (isSubtree) { + switch (nodes->front()->type()) { + case QSvgNode::Doc: + case QSvgNode::Defs: + case QSvgNode::Group: + case QSvgNode::Mask: + case QSvgNode::Pattern: + case QSvgNode::Symbol: + case QSvgNode::Switch: + case QSvgNode::Filter: + { + QSvgStructureNode *subtree = static_cast(nodes->front().get()); + isSubtree = !subtree->m_renderers.empty(); + if (isSubtree) + nodes = &subtree->m_renderers; + } + break; + default: + isSubtree = false; + break; + } + } + nodes->pop_front(); + } +} + QSvgMask::QSvgMask(QSvgNode *parent, QSvgRectF bounds, QtSvg::UnitTypes contentUnits) : QSvgStructureNode(parent) diff --git a/src/svg/qsvgstructure_p.h b/src/svg/qsvgstructure_p.h index 6f2d644..f0cb4e5 100644 --- a/src/svg/qsvgstructure_p.h +++ b/src/svg/qsvgstructure_p.h @@ -21,6 +21,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QSvgDocument; @@ -39,9 +41,13 @@ QRectF internalBounds(QPainter *p, QSvgExtraStates &states) const override; QRectF decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const override; QSvgNode *previousSiblingNode(QSvgNode *n) const; - const std::vector> &renderers() const { return m_renderers; } + const std::list> &renderers() const { return m_renderers; } + protected: - std::vector> m_renderers; + void releaseDescendants(); + +protected: + std::list> m_renderers; mutable bool m_recursing = false; private: