From f91c48650443262db9be37d5239ebe0db18e9555 Mon Sep 17 00:00:00 2001
From: Fabian Kosmale <fabian.kosmale@qt.io>
Date: Tue, 21 Jul 2026 16:18:48 +0200
Subject: [PATCH] QXmlStreamReader: Avoid stack-exhaustion in readElementText

The current algorithm calls the function recursively, which can be
problematic if one encounters deeply nested elements.
There is no need for recursion here, though: We can just keep track of
the nesting level in the loop instead.
Amends the start of the public history.
While modifying the control structure anyway, move the result
declaration out of the conditional, so that NRVO can apply.

Fixes: QTBUG-148482
Pick-to: 6.8 6.5 5.15
Change-Id: I9ec0aa9339f586737609aa031e882e163ac02caa
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
(cherry picked from commit 50cc08f278c6961d00b5b8bae408d4ccc2fbca4d)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit a10fd56403aceedbac6d81075cfe4a646e67c036)
---

diff --git a/src/corelib/serialization/qxmlstream.cpp b/src/corelib/serialization/qxmlstream.cpp
index 6656a0c..ff524d7 100644
--- a/src/corelib/serialization/qxmlstream.cpp
+++ b/src/corelib/serialization/qxmlstream.cpp
@@ -2456,14 +2456,16 @@
     Q_D(QXmlStreamReader);
     if (isStartElement()) {
         QString result;
-        forever {
+        qsizetype nestingLevel = 1;
+        do {
             switch (readNext()) {
             case Characters:
             case EntityReference:
                 result.insert(result.size(), d->text);
                 break;
             case EndElement:
-                return result;
+                --nestingLevel;
+                break;
             case ProcessingInstruction:
             case Comment:
                 break;
@@ -2472,7 +2474,7 @@
                     skipCurrentElement();
                     break;
                 } else if (behaviour == IncludeChildElements) {
-                    result += readElementText(behaviour);
+                    ++nestingLevel;
                     break;
                 }
                 Q_FALLTHROUGH();
@@ -2483,7 +2485,8 @@
                     return result;
                 }
             }
-        }
+        } while (nestingLevel);
+        return result;
     }
     return QString();
 }
diff --git a/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp b/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp
index 071f9cf..19d26d6 100644
--- a/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp
+++ b/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp
@@ -654,6 +654,7 @@
     void tokenErrorHandling() const;
     void checkStreamNotationDeclarations() const;
     void checkStreamEntityDeclarations() const;
+    void readElementTextDeepNesting() const;
 
 private:
     static QByteArray readFile(const QString &filename);
@@ -3104,4 +3105,28 @@
         QT_TEST_EQUALITY_OPS(entity, entityDeclarations.at(1), true);
     }
 }
+
+void tst_QXmlStream::readElementTextDeepNesting() const
+{
+    // readElementText(IncludeChildElements) must not recurse per nested element: deep nesting
+    // would otherwise exhaust the stack.
+    constexpr qsizetype depth = 300'000;
+
+    QByteArray xml;
+    xml.reserve(depth * 7 + 8);
+    for (qsizetype i = 0; i < depth; ++i)
+        xml += "<a>";
+    xml += 'x';
+    for (qsizetype i = 0; i < depth; ++i)
+        xml += "</a>";
+
+    QString result;
+    QXmlStreamReader reader(xml);
+    if (reader.readNextStartElement())
+        result = reader.readElementText(QXmlStreamReader::IncludeChildElements);
+    QVERIFY(!reader.hasError());
+
+    QCOMPARE(result, "x"_L1);
+}
+
 #include "tst_qxmlstream.moc"
