Skip to content

Commit 588658c

Browse files
authored
Merge pull request #31 from 1Jul1an/fix/long-constraint-stack-overflow
Use iterative traversal for deeply nested constraints
2 parents 3dcb9c5 + 2cbac22 commit 588658c

3 files changed

Lines changed: 145 additions & 20 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package de.vill.main;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.Deque;
5+
6+
import org.antlr.v4.runtime.tree.ErrorNode;
7+
import org.antlr.v4.runtime.tree.ParseTree;
8+
import org.antlr.v4.runtime.tree.ParseTreeListener;
9+
import org.antlr.v4.runtime.tree.ParseTreeWalker;
10+
import org.antlr.v4.runtime.tree.RuleNode;
11+
import org.antlr.v4.runtime.tree.TerminalNode;
12+
13+
/**
14+
* Iterative variant of ANTLR's ParseTreeWalker.
15+
*
16+
* ANTLR's default ParseTreeWalker recursively walks the parse tree.
17+
* Deeply nested constraints such as F0 | F1 | ... | F6999 can therefore
18+
* overflow the Java call stack before the UVL model is constructed.
19+
*/
20+
final class IterativeParseTreeWalker extends ParseTreeWalker {
21+
22+
@Override
23+
public void walk(ParseTreeListener listener, ParseTree tree) {
24+
final Deque<Frame> stack = new ArrayDeque<>();
25+
stack.push(new Frame(tree));
26+
27+
while (!stack.isEmpty()) {
28+
final Frame frame = stack.peek();
29+
final ParseTree current = frame.tree;
30+
31+
if (current instanceof ErrorNode) {
32+
listener.visitErrorNode((ErrorNode) current);
33+
stack.pop();
34+
continue;
35+
}
36+
37+
if (current instanceof TerminalNode) {
38+
listener.visitTerminal((TerminalNode) current);
39+
stack.pop();
40+
continue;
41+
}
42+
43+
final RuleNode ruleNode = (RuleNode) current;
44+
45+
if (!frame.entered) {
46+
enterRule(listener, ruleNode);
47+
frame.entered = true;
48+
}
49+
50+
if (frame.nextChildIndex < current.getChildCount()) {
51+
stack.push(new Frame(current.getChild(frame.nextChildIndex)));
52+
frame.nextChildIndex++;
53+
} else {
54+
exitRule(listener, ruleNode);
55+
stack.pop();
56+
}
57+
}
58+
}
59+
60+
private static final class Frame {
61+
private final ParseTree tree;
62+
private boolean entered;
63+
private int nextChildIndex;
64+
65+
private Frame(ParseTree tree) {
66+
this.tree = tree;
67+
}
68+
}
69+
}

src/main/java/de/vill/main/UVLModelFactory.java

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.antlr.v4.runtime.ConsoleErrorListener;
3434
import org.antlr.v4.runtime.RecognitionException;
3535
import org.antlr.v4.runtime.Recognizer;
36-
import org.antlr.v4.runtime.tree.ParseTreeWalker;
3736

3837
import java.io.IOException;
3938
import java.nio.file.FileSystems;
@@ -134,7 +133,8 @@ public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int
134133
});
135134

136135
UVLListener uvlListener = createUVLListener();
137-
ParseTreeWalker walker = new ParseTreeWalker();
136+
IterativeParseTreeWalker walker = new IterativeParseTreeWalker();
137+
138138
walker.walk(uvlListener, UVLJavaParser.constraintLine());
139139

140140
return uvlListener.getConstraint();
@@ -314,8 +314,10 @@ public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int
314314
});
315315

316316

317+
317318
UVLListener uvlListener = createUVLListener();
318-
ParseTreeWalker walker = new ParseTreeWalker();
319+
IterativeParseTreeWalker walker = new IterativeParseTreeWalker();
320+
319321
walker.walk(uvlListener, UVLJavaParser.featureModel());
320322
FeatureModel featureModel = null;
321323

@@ -521,28 +523,38 @@ private void validateTypeLevelConstraints(final FeatureModel featureModel) {
521523
}
522524

523525
private boolean validateTypeLevelConstraint(final Constraint constraint) {
524-
boolean result = true;
525-
if (constraint instanceof ExpressionConstraint) {
526-
String leftReturnType = ((ExpressionConstraint) constraint).getLeft().getReturnType();
527-
String rightReturnType = ((ExpressionConstraint) constraint).getRight().getReturnType();
526+
final Deque<Constraint> stack = new ArrayDeque<>();
527+
stack.push(constraint);
528528

529-
if (!(leftReturnType.equalsIgnoreCase(Constants.TRUE) || rightReturnType.equalsIgnoreCase(Constants.TRUE))) {
530-
// if not attribute constraint
531-
result = result && ((ExpressionConstraint) constraint).getLeft().getReturnType().equalsIgnoreCase(((ExpressionConstraint) constraint).getRight().getReturnType());
532-
}
533-
if (!result) {
534-
return false;
535-
}
536-
for (final Expression expr: ((ExpressionConstraint) constraint).getExpressionSubParts()) {
537-
result = result && validateTypeLevelExpression(expr);
529+
while (!stack.isEmpty()) {
530+
final Constraint current = stack.pop();
531+
532+
if (current instanceof ExpressionConstraint) {
533+
final ExpressionConstraint expressionConstraint = (ExpressionConstraint) current;
534+
535+
final String leftReturnType = expressionConstraint.getLeft().getReturnType();
536+
final String rightReturnType = expressionConstraint.getRight().getReturnType();
537+
538+
if (!(leftReturnType.equalsIgnoreCase(Constants.TRUE) || rightReturnType.equalsIgnoreCase(Constants.TRUE))) {
539+
if (!leftReturnType.equalsIgnoreCase(rightReturnType)) {
540+
return false;
541+
}
542+
}
543+
544+
for (final Expression expr : expressionConstraint.getExpressionSubParts()) {
545+
if (!validateTypeLevelExpression(expr)) {
546+
return false;
547+
}
548+
}
538549
}
539-
}
540550

541-
for (final Constraint subCons: constraint.getConstraintSubParts()) {
542-
result = result && validateTypeLevelConstraint(subCons);
551+
final List<Constraint> subConstraints = current.getConstraintSubParts();
552+
for (int i = subConstraints.size() - 1; i >= 0; i--) {
553+
stack.push(subConstraints.get(i));
554+
}
543555
}
544556

545-
return result;
557+
return true;
546558
}
547559

548560
private boolean validateTypeLevelExpression(final Expression expression) {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package de.vill.parsing;
2+
3+
import de.vill.main.UVLModelFactory;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
7+
8+
class LongConstraintParsingTest {
9+
10+
@Test
11+
void parsesLongOrConstraint() {
12+
final int numberOfLiterals = 7000;
13+
final String model = createModel(numberOfLiterals);
14+
15+
final UVLModelFactory factory = new UVLModelFactory();
16+
17+
assertDoesNotThrow(() -> factory.parse(model));
18+
}
19+
20+
private String createModel(int numberOfLiterals) {
21+
final StringBuilder builder = new StringBuilder();
22+
23+
builder.append("features\n");
24+
builder.append(" Root\n");
25+
builder.append(" optional\n");
26+
27+
for (int i = 0; i < numberOfLiterals; i++) {
28+
builder.append(" F").append(i).append("\n");
29+
}
30+
31+
builder.append("constraints\n");
32+
builder.append(" ");
33+
34+
for (int i = 0; i < numberOfLiterals; i++) {
35+
if (i > 0) {
36+
builder.append(" | ");
37+
}
38+
builder.append("F").append(i);
39+
}
40+
41+
builder.append("\n");
42+
return builder.toString();
43+
}
44+
}

0 commit comments

Comments
 (0)