diff --git a/libs/grok/src/main/java/org/opensearch/grok/Grok.java b/libs/grok/src/main/java/org/opensearch/grok/Grok.java index 7aa3347ba4f4b..3cdc51bd398ad 100644 --- a/libs/grok/src/main/java/org/opensearch/grok/Grok.java +++ b/libs/grok/src/main/java/org/opensearch/grok/Grok.java @@ -39,11 +39,14 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.Stack; import java.util.function.Consumer; @@ -142,9 +145,9 @@ private Grok( * Entry point to recursively validate the pattern bank for circular dependencies and malformed URLs * via depth-first traversal. This implementation does not include memoization. */ - private void validatePatternBank() { + public void validatePatternBank() { for (String patternName : patternBank.keySet()) { - validatePatternBank(patternName, new Stack<>()); + validatePatternBankIterative(patternName); } } @@ -156,33 +159,72 @@ private void validatePatternBank() { * a reference to another named pattern. This method will navigate to all these named patterns and * check for a circular reference. */ - private void validatePatternBank(String patternName, Stack path) { - String pattern = patternBank.get(patternName); - boolean isSelfReference = pattern.contains("%{" + patternName + "}") || pattern.contains("%{" + patternName + ":"); - if (isSelfReference) { - throwExceptionForCircularReference(patternName, pattern); - } else if (path.contains(patternName)) { - // current pattern name is already in the path, fetch its predecessor - String prevPatternName = path.pop(); - String prevPattern = patternBank.get(prevPatternName); - throwExceptionForCircularReference(prevPatternName, prevPattern, patternName, path); - } - path.push(patternName); - for (int i = pattern.indexOf("%{"); i != -1; i = pattern.indexOf("%{", i + 1)) { - int begin = i + 2; - int syntaxEndIndex = pattern.indexOf('}', begin); - if (syntaxEndIndex == -1) { - throw new IllegalArgumentException("Malformed pattern [" + patternName + "][" + pattern + "]"); + private void validatePatternBankIterative(String patternName) { + Stack path = new Stack<>(); + Stack stack = new Stack<>(); + Set visited = new HashSet<>(); + + stack.push(new PatternState(patternName, 0)); + + while (!stack.isEmpty()) { + PatternState currentState = stack.pop(); + String currentPatternName = currentState.patternName; + int startIndex = currentState.startIndex; + + if (path.contains(currentPatternName)) { + // Current pattern name is already in the path, indicating a circular reference. + String prevPatternName = path.pop(); + String prevPattern = patternBank.get(prevPatternName); + throwExceptionForCircularReference(prevPatternName, prevPattern, currentPatternName, path); + } + + path.push(currentPatternName); + + String pattern = patternBank.get(currentPatternName); + if (pattern.contains("%{" + currentPatternName + "}") || pattern.contains("%{" + currentPatternName + ":")) { + throwExceptionForCircularReference(currentPatternName, pattern); + } + + boolean hasDependencies = false; + for (int i = startIndex; i < pattern.length(); i = pattern.indexOf("%{", i + 1)) { + if (i == -1) { + break; + } + int begin = i + 2; + int syntaxEndIndex = pattern.indexOf('}', begin); + if (syntaxEndIndex == -1) { + throw new IllegalArgumentException("Malformed pattern [" + currentPatternName + "][" + pattern + "]"); + } + int semanticNameIndex = pattern.indexOf(':', begin); + int end = syntaxEndIndex; + if (semanticNameIndex != -1) { + end = Math.min(syntaxEndIndex, semanticNameIndex); + } + String dependsOnPattern = pattern.substring(begin, end); + + if (!visited.contains(dependsOnPattern)) { + stack.push(new PatternState(currentPatternName, i + 1)); + stack.push(new PatternState(dependsOnPattern, 0)); + hasDependencies = true; + break; + } } - int semanticNameIndex = pattern.indexOf(':', begin); - int end = syntaxEndIndex; - if (semanticNameIndex != -1) { - end = Math.min(syntaxEndIndex, semanticNameIndex); + + if (!hasDependencies) { + visited.add(currentPatternName); + path.pop(); } - String dependsOnPattern = pattern.substring(begin, end); - validatePatternBank(dependsOnPattern, path); } - path.pop(); + } + + private static class PatternState { + String patternName; + int startIndex; + + PatternState(String patternName, int startIndex) { + this.patternName = patternName; + this.startIndex = startIndex; + } } private static void throwExceptionForCircularReference(String patternName, String pattern) {