fixed endless recursion in context analysis

when context analysis is needed and an element in context has multiple
cardinality and contains empty paths (e.g. ?-quantified) the quantified
element will be recursed endlessly without the token analysis path even
being done (since there is always an empty path) - this also applies to
unordered groups.

recognizing empty paths is not trivial because of recursive rules.

solution: save "call stack" (actually just a set of visited elements)
during recursions in context analysis. if an element is seen multiple
times we check if there is any progress in the analysis paths. if not
this is an endless recursion
-> throw exception
This commit is contained in:
overflowerror 2022-01-15 16:56:09 +01:00
parent a1b9bb9a59
commit 623f4b69ae
2 changed files with 59 additions and 1 deletions
org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/parser/antlr/hoisting

View file

@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2022 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions;
import org.eclipse.xtext.AbstractRule;
/**
* @author overflow - Initial contribution and API
*/
public class EmptyRecursionPathInContextAnalysisException extends HoistingException {
private static final long serialVersionUID = 3820353828411603508L;
public EmptyRecursionPathInContextAnalysisException() {
super();
}
public EmptyRecursionPathInContextAnalysisException(String message, AbstractRule rule) {
super(message, rule);
}
public EmptyRecursionPathInContextAnalysisException(String message, Throwable cause, AbstractRule rule) {
super(message, cause, rule);
}
public EmptyRecursionPathInContextAnalysisException(String message, Throwable cause) {
super(message, cause);
}
public EmptyRecursionPathInContextAnalysisException(String message) {
super(message);
}
public EmptyRecursionPathInContextAnalysisException(Throwable cause, AbstractRule rule) {
super(cause, rule);
}
public EmptyRecursionPathInContextAnalysisException(Throwable cause) {
super(cause);
}
}

View file

@ -36,6 +36,7 @@ import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.UnorderedGroup;
import org.eclipse.xtext.util.XtextSwitch;
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.HoistingConfiguration;
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.EmptyRecursionPathInContextAnalysisException;
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.NestedPrefixAlternativesException;
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.SymbolicAnalysisFailedException;
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.TokenAnalysisAbortedException;
@ -153,6 +154,10 @@ public class TokenAnalysis {
}
private TokenAnalysisPaths getTokenPathsContext(AbstractElement last, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) {
return getTokenPathsContext(last, prefix, shortcutEndlessLoops, new HashSet<>());
}
private TokenAnalysisPaths getTokenPathsContext(AbstractElement last, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops, Set<AbstractElement> callStack) {
log.info("get context for: " + abstractElementToShortString(last));
List<AbstractElement> context = getNextElementsInContext(last);
@ -170,9 +175,15 @@ public class TokenAnalysis {
for (AbstractElement element : context) {
log.info("context element: " + abstractElementToShortString(element));
TokenAnalysisPaths path = new TokenAnalysisPaths(prefix);
path.resetProgress();
path = getTokenPaths(element, path, false, false, shortcutEndlessLoops);
if (!path.isDone() && element != null) {
path = getTokenPathsContext(element, path, shortcutEndlessLoops);
if (callStack.contains(element) && !path.hasProgress()) {
throw new EmptyRecursionPathInContextAnalysisException("no progress in recursion");
}
Set<AbstractElement> localCallStack = new HashSet<>(callStack);
localCallStack.add(element);
path = getTokenPathsContext(element, path, shortcutEndlessLoops, localCallStack);
}
if (path.isDone()) {
result = result.merge(path);