fix(cdp): make DOMSnapshot walk iterative to bound recursion depth
The DOMSnapshot walk recursed one frame per nesting level. The MAX_NODES cap bounds the node count but not the depth, so a deeply nested linear chain (script can build thousands of nested elements) could overflow the stack before the count guard triggered. Rewrite it with an explicit stack so depth lives on the heap. Pre-order traversal and the MAX_NODES cap are preserved. Fixes #341.
This commit is contained in:
1 parent
4603c05ab2
commit
e7300c5df7
1 file changed
+16
-5
@@ -84,21 +84,32 @@ impl Interner {
|
||||
}
|
||||
|
||||
/// Pre-order DFS from the document, recording each node and its parent's index.
|
||||
/// Iterative with an explicit stack: the MAX_NODES cap bounds the node count,
|
||||
/// but recursion depth is a separate axis. A deeply nested linear chain (script
|
||||
/// can build thousands of nested elements) would recurse that many frames deep
|
||||
/// and could overflow the stack before the count guard triggers. An explicit
|
||||
/// stack keeps the depth on the heap (issue #341).
|
||||
fn walk(
|
||||
dom: &DomTree,
|
||||
id: NodeId,
|
||||
parent: i64,
|
||||
root: NodeId,
|
||||
root_parent: i64,
|
||||
order: &mut Vec<NodeId>,
|
||||
parent_idx: &mut Vec<i64>,
|
||||
) {
|
||||
// (node, parent_index). Children are pushed in reverse so they pop in
|
||||
// document order, preserving the original pre-order traversal.
|
||||
let mut stack: Vec<(NodeId, i64)> = vec![(root, root_parent)];
|
||||
while let Some((id, parent)) = stack.pop() {
|
||||
if order.len() >= MAX_NODES {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
let my = order.len() as i64;
|
||||
order.push(id);
|
||||
parent_idx.push(parent);
|
||||
for child in dom.children(id) {
|
||||
walk(dom, child, my, order, parent_idx);
|
||||
let children: Vec<NodeId> = dom.children(id);
|
||||
for child in children.into_iter().rev() {
|
||||
stack.push((child, my));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user