From b2136946976a58ad939e0dfcd1880aeb9dc00978 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) (cherry picked from commit 22de57d6cfc7b15a0928b5310b21ab7ef1ee4c25) --- diff --git a/src/svg/qsvgstructure.cpp b/src/svg/qsvgstructure.cpp index d2a722c..fcdf00b 100644 --- a/src/svg/qsvgstructure.cpp +++ b/src/svg/qsvgstructure.cpp @@ -726,6 +726,44 @@ 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->first()); + isSubtree = !subtree->m_renderers.empty(); + if (isSubtree) + nodes = &subtree->m_renderers; + } + break; + default: + isSubtree = false; + break; + } + } + QSvgNode *node = nodes->first(); + delete node; + 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 66c2ac5..c6f07db 100644 --- a/src/svg/qsvgstructure_p.h +++ b/src/svg/qsvgstructure_p.h @@ -39,6 +39,10 @@ QRectF decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const override; QSvgNode *previousSiblingNode(QSvgNode *n) const; QList renderers() const { return m_renderers; } + +protected: + void releaseDescendants(); + protected: QList m_renderers; QHash m_scope; diff --git a/src/svg/qsvgtinydocument.cpp b/src/svg/qsvgtinydocument.cpp index 29bf184..e05c08d 100644 --- a/src/svg/qsvgtinydocument.cpp +++ b/src/svg/qsvgtinydocument.cpp @@ -37,7 +37,12 @@ } QSvgTinyDocument::~QSvgTinyDocument() - = 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) {