Skip to content

Commit f8f7684

Browse files
add node depth limit
1 parent e62bca1 commit f8f7684

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@
77
* call {@link #visitChildren}.
88
*/
99
public abstract class AbstractVisitor implements Visitor {
10+
private final int maxDepth;
11+
private int currentDepth;
12+
13+
public AbstractVisitor() {
14+
this(Integer.MAX_VALUE);
15+
}
16+
17+
protected AbstractVisitor(int maxDepth) {
18+
if (maxDepth < 0) {
19+
throw new IllegalArgumentException("maxDepth must be >= 0");
20+
}
21+
this.maxDepth = maxDepth;
22+
}
1023

1124
@Override
1225
public void visit(BlockQuote blockQuote) {
@@ -129,12 +142,20 @@ public void visit(CustomNode customNode) {
129142
* @param parent the parent node whose children should be visited
130143
*/
131144
protected void visitChildren(Node parent) {
145+
if (currentDepth >= maxDepth) {
146+
return;
147+
}
132148
Node node = parent.getFirstChild();
133149
while (node != null) {
134150
// A subclass of this visitor might modify the node, resulting in getNext returning a different node or no
135151
// node after visiting it. So get the next node before visiting.
136152
Node next = node.getNext();
137-
node.accept(this);
153+
currentDepth++;
154+
try {
155+
node.accept(this);
156+
} finally {
157+
currentDepth--;
158+
}
138159
node = next;
139160
}
140161
}

commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,38 @@
33
import org.commonmark.node.*;
44
import org.junit.jupiter.api.Test;
55

6+
import java.util.ArrayList;
7+
import java.util.List;
8+
69
import static org.assertj.core.api.Assertions.assertThat;
10+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
711

812
public class AbstractVisitorTest {
13+
@Test
14+
public void maxDepthMustBeZeroOrGreater() {
15+
assertThatThrownBy(() -> new RecordingVisitor(-1))
16+
.isInstanceOf(IllegalArgumentException.class);
17+
}
18+
19+
@Test
20+
public void maxDepthZeroVisitsOnlyRoot() {
21+
var paragraph = paragraphTree();
22+
var visitor = new RecordingVisitor(0);
23+
24+
paragraph.accept(visitor);
25+
26+
assertThat(visitor.visited).containsExactly("paragraph");
27+
}
28+
29+
@Test
30+
public void maxDepthOneVisitsDirectChildrenButNotGrandchildren() {
31+
var paragraph = paragraphTree();
32+
var visitor = new RecordingVisitor(1);
33+
34+
paragraph.accept(visitor);
35+
36+
assertThat(visitor.visited).containsExactly("paragraph", "emphasis", "text:tail");
37+
}
938

1039
@Test
1140
public void replacingNodeInVisitorShouldNotDestroyVisitOrder() {
@@ -34,4 +63,39 @@ private static void assertCode(String expectedLiteral, Node node) {
3463
Code code = (Code) node;
3564
assertThat(code.getLiteral()).isEqualTo(expectedLiteral);
3665
}
66+
67+
private static Paragraph paragraphTree() {
68+
var paragraph = new Paragraph();
69+
var emphasis = new Emphasis();
70+
emphasis.appendChild(new Text("nested"));
71+
paragraph.appendChild(emphasis);
72+
paragraph.appendChild(new Text("tail"));
73+
return paragraph;
74+
}
75+
76+
private static final class RecordingVisitor extends AbstractVisitor {
77+
private final List<String> visited = new ArrayList<>();
78+
79+
private RecordingVisitor(int maxDepth) {
80+
super(maxDepth);
81+
}
82+
83+
@Override
84+
public void visit(Paragraph paragraph) {
85+
visited.add("paragraph");
86+
super.visit(paragraph);
87+
}
88+
89+
@Override
90+
public void visit(Emphasis emphasis) {
91+
visited.add("emphasis");
92+
super.visit(emphasis);
93+
}
94+
95+
@Override
96+
public void visit(Text text) {
97+
visited.add("text:" + text.getLiteral());
98+
super.visit(text);
99+
}
100+
}
37101
}

0 commit comments

Comments
 (0)