From b2136946976a58ad939e0dfcd1880aeb9dc00978 Mon Sep 17 00:00:00 2001
From: Hatem ElKharashy <hatem.elkharashy@qt.io>
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 <robert.loehning@qt.io>
(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<QSvgStructureNode *>(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<QSvgNode*> renderers() const { return m_renderers; }
+
+protected:
+    void releaseDescendants();
+
 protected:
     QList<QSvgNode*>          m_renderers;
     QHash<QString, QSvgNode*> 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)
 {
