From ea44b50c6e61104cadd6b7c8ede92a4108634232 Mon Sep 17 00:00:00 2001
From: Robert Löhning <robert.loehning@qt.io>
Date: Wed, 18 Jun 2025 16:02:32 +0200
Subject: [PATCH] Replace check for endless recursion when loading

The old check parsed the tree of SvgNodes again and again which lead to
quadratic complexity. Instead, set and check a bool where the recursion
may actually happen which is faster and only has linear complexity.

Partially reverts 0332df304f013ded362537c1f61556098b875352

I chose to have the check in QSvgPattern::renderPattern() because:

- It not only appears in the recursive backtrace of the stack-overflow
  which was fixed using the qudratic check, but also in the backtrace
  of another, still unfixed stack overflow. That way, both can be fixed
  by the same patch. Credit to OSS-Fuzz for finding them.
- The function already had some error checking and returns a default
  value when it cannot render the content. In the same way, I can return
  a QImage of the right size but without any content when the endless
  recursion is about to happen.

[ChangeLog] Speed up loading by replacing check for cyclic elements
[ChangeLog] Fix stack overflow when an element references its child
element using url()

Fixes: QTBUG-137553
Pick-to: 6.8
Change-Id: If011c15fde50dcefeb653d1d5995ff1347e7b5ac
Reviewed-by: Hatem ElKharashy <hatem.elkharashy@qt.io>
(cherry picked from commit 9e5bed9584ab65d56cd5fbac0471e06e37a54412)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 90a5331640bb760b0114a7ea4e08b9e42b03e082)
---

diff --git a/src/svg/qsvghandler.cpp b/src/svg/qsvghandler.cpp
index 48f9f0b..c871a20 100644
--- a/src/svg/qsvghandler.cpp
+++ b/src/svg/qsvghandler.cpp
@@ -4647,8 +4647,7 @@
             // this point is to do what everyone else seems to do and
             // ignore the reported namespaceUri completely.
             if (remainingUnfinishedElements
-                    && startElement(xml->name().toString(), xml->attributes())
-                    && !detectCyclesAndWarn(m_doc)) {
+                    && startElement(xml->name().toString(), xml->attributes())) {
                 --remainingUnfinishedElements;
             } else {
                 delete m_doc;
diff --git a/src/svg/qsvgstructure.cpp b/src/svg/qsvgstructure.cpp
index e08f1bc..d073b1c 100644
--- a/src/svg/qsvgstructure.cpp
+++ b/src/svg/qsvgstructure.cpp
@@ -800,6 +800,7 @@
     m_rect(bounds),
     m_viewBox(viewBox),
     m_contentUnits(contentUnits),
+    m_isRendering(false),
     m_transform(transform)
 
 {
@@ -885,6 +886,13 @@
     }
     pattern.fill(Qt::transparent);
 
+    if (m_isRendering) {
+        qCWarning(lcSvgDraw) << "The pattern is trying to render itself recursively. "
+                                "Returning a transparent QImage of the right size.";
+        return pattern;
+    }
+    QScopedValueRollback<bool> guard(m_isRendering, true);
+
     // Draw the pattern using our QPainter.
     QPainter patternPainter(&pattern);
     QSvgExtraStates patternStates;
diff --git a/src/svg/qsvgstructure_p.h b/src/svg/qsvgstructure_p.h
index 63a8396..f95508f 100644
--- a/src/svg/qsvgstructure_p.h
+++ b/src/svg/qsvgstructure_p.h
@@ -241,6 +241,7 @@
     QSvgRectF m_rect;
     QRectF m_viewBox;
     QtSvg::UnitTypes m_contentUnits;
+    mutable bool m_isRendering;
     QTransform m_transform;
 };
 
diff --git a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp
index b212ad3..56f6664 100644
--- a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp
+++ b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp
@@ -1809,6 +1809,9 @@
     // resulted in stack overflow
     QTest::newRow("cyclic-reference") // id=42532991
             << R"-(<svg><pattern height="3" width="9" id="c"><path d="v4T1-" stroke="url(#c)"><symbol>)-"_ba;
+    // resulted in stack overflow
+    QTest::newRow("cyclic-reference-from-parent") // id=390467765
+            << R"-(<svg stroke="url(#c)"><pattern height="2" width="4" id="c"/><path stroke="#F00" d="v2"/></svg>)-"_ba;
 }
 
 void tst_QSvgRenderer::ossFuzzLoad()
