From 50e0a86bd6117a8cadfafc9e03f6baa633fa43b5 Mon Sep 17 00:00:00 2001
From: Hatem ElKharashy <hatem.elkharashy@qt.io>
Date: Wed, 22 Apr 2026 08:57:44 +0300
Subject: [PATCH] Fix resolving paint servers going through stack overflow

The parsing function resolvePaintServers() goes through the whole
node structure and calls itself recursively to resolve paint
servers which is not needed. Instead, store the styles that
need to be resolved and go through them. This avoids causing
a stack overflow.

This branch still contained calls to the function
structureNode->styleProperty(). Those were already replaced in
6220bd28997686804f613f824216ef64c1974679 which was not picked back
to stable branches. To resolve the merge conflicts and also fix
the behavior, we needed to insert lines which remove trailing hash
marks from IDs, as structureNode->styleProperty() previously did.
In the dev branch, those are not needed anymore because
56b58677bafb0941754f0d6c807d0fe87d87d5c1 refactored the handling of
all IDs.

Task-number: QTBUG-145916
Change-Id: Ie225bb51461f051b70b395f985b03006ccf9415c
Reviewed-by: Robert Löhning <robert.loehning@qt.io>
(cherry picked from commit b702c4b3ac0fea5758452ef98de5d8b87ccb0c27)
Reviewed-by: Hatem ElKharashy <hatem.elkharashy@qt.io>
---

diff --git a/src/svg/qsvghandler.cpp b/src/svg/qsvghandler.cpp
index a61f52d..d93d9ab 100644
--- a/src/svg/qsvghandler.cpp
+++ b/src/svg/qsvghandler.cpp
@@ -581,7 +581,7 @@
                 } else {
                     QString id = idFromUrl(value).toString();
                     prop->setPaintStyleId(id);
-                    prop->setPaintStyleResolved(false);
+                    handler->pushUnresolvedStyle(prop);
                 }
             } else if (attributes.fill != QLatin1String("none")) {
                 QColor color;
@@ -746,7 +746,7 @@
                     } else {
                         QString id = idFromUrl(value).toString();
                         prop->setPaintStyleId(id);
-                        prop->setPaintStyleResolved(false);
+                        handler->pushUnresolvedStyle(prop);
                     }
             } else if (attributes.stroke != QLatin1String("none")) {
                 QColor color;
@@ -3657,7 +3657,7 @@
             break;
         }
     }
-    resolvePaintServers(m_doc);
+    resolvePaintServers();
     resolveNodes();
     if (detectCyclesAndWarn(m_doc)) {
         delete m_doc;
@@ -3891,32 +3891,27 @@
     return ((localName == QLatin1String("svg")) && (node != Doc));
 }
 
-void QSvgHandler::resolvePaintServers(QSvgNode *node, int nestedDepth)
+void QSvgHandler::resolvePaintServers()
 {
-    if (!node || (node->type() != QSvgNode::Doc && node->type() != QSvgNode::Group
-        && node->type() != QSvgNode::Defs && node->type() != QSvgNode::Switch)) {
-        return;
-    }
-
-    QSvgStructureNode *structureNode = static_cast<QSvgStructureNode *>(node);
-
-    for (auto &node : structureNode->renderers()) {
-        QSvgFillStyle *fill = static_cast<QSvgFillStyle *>(node->styleProperty(QSvgStyleProperty::FILL));
-        if (fill && !fill->isPaintStyleResolved()) {
+    for (QSvgStyleProperty *prop : std::as_const(m_unresolvedStyles)) {
+        if (prop->type() == QSvgStyleProperty::FILL) {
+            QSvgFillStyle *fill = static_cast<QSvgFillStyle *>(prop);
             QString id = fill->paintStyleId();
-            QSvgPaintStyleProperty *style = structureNode->styleProperty(id);
+            if (id.startsWith(QLatin1Char('#')))
+                id.slice(1);
+            QSvgPaintStyleProperty *style = m_doc->namedStyle(id);
             if (style) {
                 fill->setFillStyle(style);
             } else {
                 qCWarning(lcSvgHandler, "%s", msgCouldNotResolveProperty(id, xml).constData());
                 fill->setBrush(Qt::NoBrush);
             }
-        }
-
-        QSvgStrokeStyle *stroke = static_cast<QSvgStrokeStyle *>(node->styleProperty(QSvgStyleProperty::STROKE));
-        if (stroke && !stroke->isPaintStyleResolved()) {
+        } else if (prop->type() == QSvgStyleProperty::STROKE) {
+            QSvgStrokeStyle *stroke = static_cast<QSvgStrokeStyle *>(prop);
             QString id = stroke->paintStyleId();
-            QSvgPaintStyleProperty *style = structureNode->styleProperty(id);
+            if (id.startsWith(QLatin1Char('#')))
+                id.slice(1);
+            QSvgPaintStyleProperty *style = m_doc->namedStyle(id);
             if (style) {
                 stroke->setStyle(style);
             } else {
@@ -3924,10 +3919,9 @@
                 stroke->setStroke(Qt::NoBrush);
             }
         }
-
-        if (nestedDepth < 2048)
-            resolvePaintServers(node.get(), nestedDepth + 1);
     }
+
+    m_unresolvedStyles.clear();
 }
 
 void QSvgHandler::resolveNodes()
@@ -4050,6 +4044,11 @@
         return QColor(0, 0, 0);
 }
 
+void QSvgHandler::pushUnresolvedStyle(QSvgStyleProperty *prop)
+{
+    m_unresolvedStyles.append(prop);
+}
+
 #ifndef QT_NO_CSSPARSER
 
 void QSvgHandler::setInStyle(bool b)
diff --git a/src/svg/qsvghandler_p.h b/src/svg/qsvghandler_p.h
index 2b8af94..a151302 100644
--- a/src/svg/qsvghandler_p.h
+++ b/src/svg/qsvghandler_p.h
@@ -67,6 +67,8 @@
     void popColor();
     QColor currentColor() const;
 
+    void pushUnresolvedStyle(QSvgStyleProperty *prop);
+
 #ifndef QT_NO_CSSPARSER
     void setInStyle(bool b);
     bool inStyle() const;
@@ -100,6 +102,7 @@
     // - <use> nodes which haven't been resolved yet.
     // - <filter> nodes to be checked for unsupported filter primitives.
     QList<QSvgNode *> m_toBeResolved;
+    QList<QSvgStyleProperty *> m_unresolvedStyles;
 
     enum CurrentNode
     {
@@ -131,7 +134,7 @@
     QSvgCssHandler m_cssHandler;
 #endif
     void parse();
-    void resolvePaintServers(QSvgNode *node, int nestedDepth = 0);
+    void resolvePaintServers();
     void resolveNodes();
 
     QPen m_defaultPen;
diff --git a/src/svg/qsvgstyle.cpp b/src/svg/qsvgstyle.cpp
index ef83d52..a288bd2 100644
--- a/src/svg/qsvgstyle.cpp
+++ b/src/svg/qsvgstyle.cpp
@@ -93,7 +93,6 @@
     , m_oldFillRule(Qt::WindingFill)
     , m_fillOpacity(1.0)
     , m_oldFillOpacity(0)
-    , m_paintStyleResolved(1)
     , m_fillRuleSet(0)
     , m_fillOpacitySet(0)
     , m_fillSet(0)
@@ -248,7 +247,6 @@
     , m_strokeDashOffset(0)
     , m_oldStrokeDashOffset(0)
     , m_style(0)
-    , m_paintStyleResolved(1)
     , m_vectorEffect(0)
     , m_oldVectorEffect(0)
     , m_strokeSet(0)
diff --git a/src/svg/qsvgstyle_p.h b/src/svg/qsvgstyle_p.h
index 078e0b7..89a8d9d 100644
--- a/src/svg/qsvgstyle_p.h
+++ b/src/svg/qsvgstyle_p.h
@@ -259,16 +259,6 @@
         return m_paintStyleId;
     }
 
-    void setPaintStyleResolved(bool resolved)
-    {
-        m_paintStyleResolved = resolved;
-    }
-
-    bool isPaintStyleResolved() const
-    {
-        return m_paintStyleResolved;
-    }
-
 private:
     // fill            v 	v 	'inherit' | <Paint.datatype>
     // fill-opacity    v 	v 	'inherit' | <OpacityValue.datatype>
@@ -282,7 +272,6 @@
     qreal m_oldFillOpacity;
 
     QString m_paintStyleId;
-    uint m_paintStyleResolved : 1;
 
     uint m_fillRuleSet : 1;
     uint m_fillOpacitySet : 1;
@@ -483,16 +472,6 @@
         return m_paintStyleId;
     }
 
-    void setPaintStyleResolved(bool resolved)
-    {
-        m_paintStyleResolved = resolved;
-    }
-
-    bool isPaintStyleResolved() const
-    {
-        return m_paintStyleResolved;
-    }
-
     QPen stroke() const
     {
         return m_stroke;
@@ -516,7 +495,6 @@
 
     QSvgRefCounter<QSvgPaintStyleProperty> m_style;
     QString m_paintStyleId;
-    uint m_paintStyleResolved : 1;
     uint m_vectorEffect : 1;
     uint m_oldVectorEffect : 1;
 
diff --git a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp
index 329767b..308d99a 100644
--- a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp
+++ b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp
@@ -178,7 +178,7 @@
 void tst_QSvgRenderer::inexistentUrl()
 {
     const char *src = "<svg><g><path d=\"M0 0\" style=\"stroke:url(#inexistent)\"/></g></svg>";
-    QTest::ignoreMessage(QtWarningMsg, "<input>:1:66: Could not resolve property: #inexistent");
+    QTest::ignoreMessage(QtWarningMsg, "<input>:1:66: Could not resolve property: inexistent");
 
     QByteArray data(src);
     QSvgRenderer renderer(data);
@@ -2258,7 +2258,7 @@
 
     QTest::ignoreMessage(QtWarningMsg, "<input>:2:68: Could not add child element to parent "
                                        "element because the types are incorrect.");
-    QTest::ignoreMessage(QtWarningMsg, "<input>:4:28: Could not resolve property: #ptn");
+    QTest::ignoreMessage(QtWarningMsg, "<input>:4:28: Could not resolve property: ptn");
 
     QSvgRenderer renderer(svg);
     QPainter painter(&image);
