diff --git a/src/xml/dom/qdom.cpp b/src/xml/dom/qdom.cpp index 64f32030c87..834ab243cbf 100644 --- a/src/xml/dom/qdom.cpp +++ b/src/xml/dom/qdom.cpp @@ -1349,16 +1349,40 @@ void QDomNodePrivate::normalize() qNormalizeNode(this); } -/*! \internal - \a depth is used for indentation, it seems. - */ -void QDomNodePrivate::save(QTextStream& s, int depth, int indent) const +void QDomNodePrivate::saveSubTree(const QDomNodePrivate *n, QTextStream &s, + int depth, int indent) const { - const QDomNodePrivate* n = first; - while (n) { - n->save(s, depth, indent); - n = n->next; + if (!n) + return; + + const QDomNodePrivate *root = n->first; + n->save(s, depth, indent); + if (root) { + const int branchDepth = depth + 1; + int layerDepth = 0; + while (root) { + root->save(s, layerDepth + branchDepth, indent); + // A flattened (non-recursive) depth-first walk through the node tree. + if (root->first) { + layerDepth ++; + root = root->first; + continue; + } + root->afterSave(s, layerDepth + branchDepth, indent); + const QDomNodePrivate *prev = root; + root = root->next; + // Close QDomElementPrivate groups + while (!root && prev && (layerDepth > 0)) { + root = prev->parent(); + layerDepth --; + root->afterSave(s, layerDepth + branchDepth, indent); + prev = root; + root = root->next; + } + } + Q_ASSERT(layerDepth == 0); } + n->afterSave(s, depth, indent); } void QDomNodePrivate::setLocation(int lineNumber, int columnNumber) @@ -2146,7 +2170,7 @@ void QDomNode::save(QTextStream& stream, int indent, EncodingPolicy encodingPoli if (isDocument()) static_cast(impl)->saveDocument(stream, indent, encodingPolicy); else - IMPL->save(stream, 1, indent); + IMPL->saveSubTree(IMPL, stream, 1, indent); } /*! @@ -3064,11 +3088,11 @@ void QDomDocumentTypePrivate::save(QTextStream& s, int, int indent) const auto it2 = notations->map.constBegin(); for (; it2 != notations->map.constEnd(); ++it2) - it2.value()->save(s, 0, indent); + it2.value()->saveSubTree(it2.value(), s, 0, indent); auto it = entities->map.constBegin(); for (; it != entities->map.constEnd(); ++it) - it.value()->save(s, 0, indent); + it.value()->saveSubTree(it.value(), s, 0, indent); s << ']'; } @@ -4121,13 +4145,25 @@ void QDomElementPrivate::save(QTextStream& s, int depth, int indent) const if (indent != -1) s << Qt::endl; } - QDomNodePrivate::save(s, depth + 1, indent); if (!last->isText()) - s << QString(indent < 1 ? 0 : depth * indent, u' '); - - s << "'; } else { s << "/>"; } +} + +void QDomElementPrivate::afterSave(QTextStream &s, int depth, int indent) const +{ + if (last) { + QString qName(name); + + if (!prefix.isEmpty()) + qName = prefix + u':' + name; + + if (!last->isText()) + s << QString(indent < 1 ? 0 : depth * indent, u' '); + + s << "'; + } + if (!(next && next->isText())) { /* -1 disables new lines. */ if (indent != -1) @@ -5908,7 +5944,7 @@ void QDomDocumentPrivate::saveDocument(QTextStream& s, const int indent, QDomNod type->save(s, 0, indent); doc = true; } - n->save(s, 0, indent); + n->saveSubTree(n, s, 0, indent); n = n->next; } } @@ -5935,8 +5971,8 @@ void QDomDocumentPrivate::saveDocument(QTextStream& s, const int indent, QDomNod } // Now we serialize all the nodes after the faked XML declaration(the PI). - while(startNode) { - startNode->save(s, 0, indent); + while (startNode) { + startNode->saveSubTree(startNode, s, 0, indent); startNode = startNode->next; } } diff --git a/src/xml/dom/qdom_p.h b/src/xml/dom/qdom_p.h index 507852b89e6..55f5b2332d3 100644 --- a/src/xml/dom/qdom_p.h +++ b/src/xml/dom/qdom_p.h @@ -109,7 +109,9 @@ public: virtual QDomNode::NodeType nodeType() const { return QDomNode::BaseNode; } - virtual void save(QTextStream &, int, int) const; + void saveSubTree(const QDomNodePrivate *n, QTextStream &s, int depth, int indent) const; + virtual void save(QTextStream &, int, int) const {} + virtual void afterSave(QTextStream &, int, int) const {} void setLocation(int lineNumber, int columnNumber); @@ -328,6 +330,7 @@ public: QDomNode::NodeType nodeType() const override { return QDomNode::ElementNode; } QDomNodePrivate *cloneNode(bool deep = true) override; virtual void save(QTextStream &s, int, int) const override; + virtual void afterSave(QTextStream &s, int, int) const override; // Variables QDomNamedNodeMapPrivate *m_attr; diff --git a/tests/auto/xml/dom/qdom/testdata/deeply-nested.svg b/tests/auto/xml/dom/qdom/testdata/deeply-nested.svg new file mode 100644 index 00000000000..85696a314ad --- /dev/null +++ b/tests/auto/xml/dom/qdom/testdata/deeply-nested.svg @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/auto/xml/dom/qdom/tst_qdom.cpp b/tests/auto/xml/dom/qdom/tst_qdom.cpp index 3a6fc1356c3..1501b122341 100644 --- a/tests/auto/xml/dom/qdom/tst_qdom.cpp +++ b/tests/auto/xml/dom/qdom/tst_qdom.cpp @@ -114,6 +114,7 @@ private slots: void QTBUG49113_dontCrashWithNegativeIndex() const; void standalone(); void splitTextLeakMemory() const; + void noCrashOnDeepNesting() const; void cleanupTestCase() const; @@ -2337,5 +2338,21 @@ void tst_QDom::splitTextLeakMemory() const QCOMPARE(end.impl->ref.loadRelaxed(), 2); } +// The fix of QTBUG-131151 crash +void tst_QDom::noCrashOnDeepNesting() const +{ + const QString prefix = QFINDTESTDATA("testdata/"); + if (prefix.isEmpty()) + QFAIL("Cannot find testdata directory!"); + + QFile file(prefix + "/deeply-nested.svg"); + QVERIFY(file.open(QIODevice::ReadOnly)); + QDomDocument doc; + doc.setContent(&file); + QByteArray array = doc.toByteArray(); + QVERIFY(array.size()); + file.close(); +} + QTEST_MAIN(tst_QDom) #include "tst_qdom.moc"