From 691e4a67b4270529f7f5e330d8c88f01a007f6dc Mon Sep 17 00:00:00 2001 From: Hatem ElKharashy Date: Wed, 22 Apr 2026 13:46:13 +0300 Subject: [PATCH] Fix stack overflow in detectCycles The function calls itself recursively to detect cycles in a parsed SVG file. This can cause a stack overflow in a deeply nested files. Fixes: QTBUG-145916 Change-Id: I63a1c945caf5de1e72cad440d433e30b36103e53 Reviewed-by: Robert Löhning (cherry picked from commit d8ff98f5ec716a5441fd88bfb8eb1f7709a8cd6e) Reviewed-by: Qt Cherry-pick Bot (cherry picked from commit b39e6ef97f22b1663c518c05f8e3d347d7f7749d) (cherry picked from commit 2def84b0796ceb721ffdfd8f9c9605813ff95dc3) Reviewed-by: Hatem ElKharashy --- diff --git a/src/svg/qsvghandler.cpp b/src/svg/qsvghandler.cpp index f7a50c4..90badee 100644 --- a/src/svg/qsvghandler.cpp +++ b/src/svg/qsvghandler.cpp @@ -4662,7 +4662,7 @@ parse(); } -static bool detectPatternCycles(const QSvgNode *node, QList active = {}) +static bool detectPatternCycles(const QSvgNode *node, QList &active) { QSvgFillStyle *fillStyle = static_cast (node->styleProperty(QSvgStyleProperty::FILL)); @@ -4683,53 +4683,83 @@ return false; } -static bool detectCycles(const QSvgNode *node, QList active = {}) +/* The function goes through a node and its descendants to + * find any circular references in the parsed SVG file. It + * is important for this to happen non-recursively to avoid + * stack overflows. + * The function maintains two lists of nodes. The active list + * is used to track patterns and uses because these are the nodes + * that can reference or be referenced by other nodes. + * Example : + * + * + * + * + * The other list of nodes is a stack to traverse the tree + * non-recursively, the std::pair stored in the stack will + * indicate whether a pattern or use has been visited and + * added to the active list or not. If the bool is set to true, + * this element can be popped out from the active list. */ +static bool detectCycles(const QSvgNode *n) { - if (Q_UNLIKELY(!node)) + if (Q_UNLIKELY(!n)) return false; - switch (node->type()) { - case QSvgNode::Doc: - case QSvgNode::Group: - case QSvgNode::Defs: - case QSvgNode::Pattern: - { - if (node->type() == QSvgNode::Pattern) - active.append(node); - auto *g = static_cast(node); - for (auto *r : g->renderers()) { - if (detectCycles(r, active)) - return true; - } - } - break; - case QSvgNode::Use: - { - if (active.contains(node)) - return true; + QList active; + using NodeState = std::pair; + QStack nodes; + nodes.push({n, false}); - auto *u = static_cast(node); - auto *target = u->link(); - if (target) { - active.append(u); - if (detectCycles(target, active)) - return true; + while (!nodes.isEmpty()) { + auto current = nodes.pop(); + if (current.second) { + Q_ASSERT(!active.isEmpty() && current.first == active.back()); + active.pop_back(); + continue; } - } - break; - case QSvgNode::Rect: - case QSvgNode::Ellipse: - case QSvgNode::Circle: - case QSvgNode::Line: - case QSvgNode::Path: - case QSvgNode::Polygon: - case QSvgNode::Polyline: - case QSvgNode::Tspan: - if (detectPatternCycles(node, active)) - return true; + + switch (current.first->type()) { + case QSvgNode::Doc: + case QSvgNode::Group: + case QSvgNode::Defs: + case QSvgNode::Pattern: + { + if (current.first->type() == QSvgNode::Pattern) { + active.append(current.first); + nodes.push({current.first, true}); + } + auto *g = static_cast(current.first); + for (auto it = g->renderers().crbegin(); it != g->renderers().crend(); it++) + nodes.push({*it, false}); + } break; - default: + case QSvgNode::Use: + { + if (active.contains(current.first)) + return true; + auto *u = static_cast(current.first); + auto *target = u->link(); + if (target) { + active.append(u); + nodes.push({u, true}); + nodes.push({target, false}); + } + } break; + case QSvgNode::Rect: + case QSvgNode::Ellipse: + case QSvgNode::Circle: + case QSvgNode::Line: + case QSvgNode::Path: + case QSvgNode::Polygon: + case QSvgNode::Polyline: + case QSvgNode::Tspan: + if (detectPatternCycles(current.first, active)) + return true; + break; + default: + break; + } } return false; }