Skip to content

Commit bfe2c5a

Browse files
.
1 parent 833b068 commit bfe2c5a

File tree

5 files changed

+88
-135
lines changed

5 files changed

+88
-135
lines changed

commonmark/src/main/java/org/commonmark/internal/DocumentParser.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ public class DocumentParser implements ParserState {
7676
private final List<LinkProcessor> linkProcessors;
7777
private final Set<Character> linkMarkers;
7878
private final IncludeSourceSpans includeSourceSpans;
79-
private final int maxOpenBlockParsers;
8079
private final DocumentBlockParser documentBlockParser;
8180
private final Definitions definitions = new Definitions();
8281

@@ -85,16 +84,14 @@ public class DocumentParser implements ParserState {
8584

8685
public DocumentParser(List<BlockParserFactory> blockParserFactories, InlineParserFactory inlineParserFactory,
8786
List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> delimiterProcessors,
88-
List<LinkProcessor> linkProcessors, Set<Character> linkMarkers,
89-
IncludeSourceSpans includeSourceSpans, int maxOpenBlockParsers) {
87+
List<LinkProcessor> linkProcessors, Set<Character> linkMarkers, IncludeSourceSpans includeSourceSpans) {
9088
this.blockParserFactories = blockParserFactories;
9189
this.inlineParserFactory = inlineParserFactory;
9290
this.inlineContentParserFactories = inlineContentParserFactories;
9391
this.delimiterProcessors = delimiterProcessors;
9492
this.linkProcessors = linkProcessors;
9593
this.linkMarkers = linkMarkers;
9694
this.includeSourceSpans = includeSourceSpans;
97-
this.maxOpenBlockParsers = maxOpenBlockParsers;
9895

9996
this.documentBlockParser = new DocumentBlockParser();
10097
activateBlockParser(new OpenBlockParser(documentBlockParser, 0));
@@ -464,9 +461,6 @@ private void addSourceSpans() {
464461
}
465462

466463
private BlockStartImpl findBlockStart(BlockParser blockParser) {
467-
if (openBlockParsers.size() - 1 >= maxOpenBlockParsers) {
468-
return null;
469-
}
470464
MatchedBlockParser matchedBlockParser = new MatchedBlockParserImpl(blockParser);
471465
for (BlockParserFactory blockParserFactory : blockParserFactories) {
472466
BlockStart result = blockParserFactory.tryStart(this, matchedBlockParser);

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/main/java/org/commonmark/parser/Parser.java

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public class Parser {
3737
private final InlineParserFactory inlineParserFactory;
3838
private final List<PostProcessor> postProcessors;
3939
private final IncludeSourceSpans includeSourceSpans;
40-
private final int maxOpenBlockParsers;
4140

4241
private Parser(Builder builder) {
4342
this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes);
@@ -48,7 +47,6 @@ private Parser(Builder builder) {
4847
this.linkProcessors = builder.linkProcessors;
4948
this.linkMarkers = builder.linkMarkers;
5049
this.includeSourceSpans = builder.includeSourceSpans;
51-
this.maxOpenBlockParsers = builder.maxOpenBlockParsers;
5250

5351
// Try to construct an inline parser. Invalid configuration might result in an exception, which we want to
5452
// detect as soon as possible.
@@ -108,7 +106,7 @@ public Node parseReader(Reader input) throws IOException {
108106

109107
private DocumentParser createDocumentParser() {
110108
return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParserFactories,
111-
delimiterProcessors, linkProcessors, linkMarkers, includeSourceSpans, maxOpenBlockParsers);
109+
delimiterProcessors, linkProcessors, linkMarkers, includeSourceSpans);
112110
}
113111

114112
private Node postProcess(Node document) {
@@ -131,7 +129,6 @@ public static class Builder {
131129
private Set<Class<? extends Block>> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes();
132130
private InlineParserFactory inlineParserFactory;
133131
private IncludeSourceSpans includeSourceSpans = IncludeSourceSpans.NONE;
134-
private int maxOpenBlockParsers = Integer.MAX_VALUE;
135132

136133
/**
137134
* @return the configured {@link Parser}
@@ -203,27 +200,6 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) {
203200
return this;
204201
}
205202

206-
/**
207-
* Limit how many non-document block parsers may be open at once while parsing.
208-
* <p>
209-
* Once the limit is reached, additional block starts are treated as plain text instead of
210-
* creating deeper nested block structure.
211-
* <p>
212-
* The document root parser is not counted. The default is unlimited, so callers that keep
213-
* using {@code Parser.builder().build()} preserve current behavior.
214-
*
215-
* @param maxOpenBlockParsers maximum number of open non-document block parsers, must be
216-
* zero or greater
217-
* @return {@code this}
218-
*/
219-
public Builder maxOpenBlockParsers(int maxOpenBlockParsers) {
220-
if (maxOpenBlockParsers < 0) {
221-
throw new IllegalArgumentException("maxOpenBlockParsers must be >= 0");
222-
}
223-
this.maxOpenBlockParsers = maxOpenBlockParsers;
224-
return this;
225-
}
226-
227203
/**
228204
* Add a custom block parser factory.
229205
* <p>

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
}

commonmark/src/test/java/org/commonmark/test/ParserTest.java

Lines changed: 0 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import org.commonmark.parser.*;
55
import org.commonmark.parser.block.*;
66
import org.commonmark.renderer.html.HtmlRenderer;
7-
import org.commonmark.renderer.text.TextContentRenderer;
87
import org.commonmark.testutil.TestResources;
98
import org.junit.jupiter.api.Test;
109

@@ -136,112 +135,11 @@ public void threading() throws Exception {
136135
}
137136
}
138137

139-
@Test
140-
public void maxOpenBlockParsersMustBeZeroOrGreater() {
141-
assertThatThrownBy(() ->
142-
Parser.builder().maxOpenBlockParsers(-1)).isInstanceOf(IllegalArgumentException.class);
143-
}
144-
145-
@Test
146-
public void maxOpenBlockParsersIsOptIn() {
147-
var parser = Parser.builder().build();
148-
149-
var document = parser.parse(alternatingNestedList(9));
150-
151-
assertThat(renderText(deepestStructuredParagraph(document, 9))).isEqualTo("level9");
152-
}
153-
154-
@Test
155-
public void maxOpenBlockParsersPreservesSevenLogicalListLevelsAtSeventeenBlocks() {
156-
var parser = Parser.builder().maxOpenBlockParsers(17).build();
157-
158-
var document = parser.parse(alternatingNestedList(7));
159-
160-
assertThat(renderText(deepestStructuredParagraph(document, 7))).isEqualTo("level7");
161-
}
162-
163-
@Test
164-
public void maxOpenBlockParsersPreservesEightLogicalListLevelsAtSeventeenBlocks() {
165-
var parser = Parser.builder().maxOpenBlockParsers(17).build();
166-
167-
var document = parser.parse(alternatingNestedList(8));
168-
169-
assertThat(renderText(deepestStructuredParagraph(document, 8))).isEqualTo("level8");
170-
}
171-
172-
@Test
173-
public void maxOpenBlockParsersDegradesTheNinthLogicalListLevelToPlainText() {
174-
var parser = Parser.builder().maxOpenBlockParsers(17).build();
175-
176-
var document = parser.parse(alternatingNestedList(9));
177-
var deepestParagraph = deepestStructuredParagraph(document, 8);
178-
179-
assertThat(renderText(deepestParagraph)).isEqualTo("level8\n- level9");
180-
assertThat(deepestParagraph.getNext()).isNull();
181-
}
182-
183-
@Test
184-
public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() {
185-
var parser = Parser.builder().maxOpenBlockParsers(5).build();
186-
187-
var document = parser.parse(String.join("\n",
188-
"- level1",
189-
" > level2",
190-
" > > level3",
191-
" > > > level4"));
192-
193-
var listBlock = document.getFirstChild();
194-
assertThat(listBlock).isInstanceOf(BulletList.class);
195-
196-
var listItem = listBlock.getFirstChild();
197-
var blockQuote1 = listItem.getLastChild();
198-
assertThat(blockQuote1).isInstanceOf(BlockQuote.class);
199-
200-
var blockQuote2 = blockQuote1.getLastChild();
201-
assertThat(blockQuote2).isInstanceOf(BlockQuote.class);
202-
203-
var deepestParagraph = blockQuote2.getLastChild();
204-
assertThat(deepestParagraph).isInstanceOf(Paragraph.class);
205-
assertThat(renderText(deepestParagraph)).isEqualTo("level3\n> level4");
206-
assertThat(deepestParagraph.getNext()).isNull();
207-
}
208-
209138
private String firstText(Node n) {
210139
while (!(n instanceof Text)) {
211140
assertThat(n).isNotNull();
212141
n = n.getFirstChild();
213142
}
214143
return ((Text) n).getLiteral();
215144
}
216-
217-
private Paragraph deepestStructuredParagraph(Node document, int levels) {
218-
Node node = document.getFirstChild();
219-
for (int level = 1; level <= levels; level++) {
220-
assertThat(node).isInstanceOf(ListBlock.class);
221-
var listItem = node.getFirstChild();
222-
assertThat(listItem).isNotNull();
223-
if (level == levels) {
224-
assertThat(listItem.getFirstChild()).isInstanceOf(Paragraph.class);
225-
return (Paragraph) listItem.getFirstChild();
226-
}
227-
node = listItem.getLastChild();
228-
}
229-
throw new AssertionError("unreachable");
230-
}
231-
232-
private String renderText(Node node) {
233-
return TextContentRenderer.builder().build().render(node).trim();
234-
}
235-
236-
private String alternatingNestedList(int levels) {
237-
int indent = 0;
238-
var lines = new ArrayList<String>();
239-
for (int level = 1; level <= levels; level++) {
240-
var ordered = level % 2 == 0;
241-
var marker = ordered ? "1. " : "- ";
242-
lines.add(" ".repeat(indent) + marker + "level" + level);
243-
indent += marker.length();
244-
}
245-
return String.join("\n", lines);
246-
}
247145
}

0 commit comments

Comments
 (0)