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..394e9d67ff319 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; @@ -138,51 +141,127 @@ private Grok( this.captureConfig = unmodifiableList(captureConfig); } - /** - * 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() { +// /** +// * 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() { +// for (String patternName : patternBank.keySet()) { +// validatePatternBank(patternName, new Stack<>()); +// } +// } +// +// /** +// * Checks whether patterns reference each other in a circular manner and, if so, fail with an exception. +// * Also checks for malformed pattern definitions and fails with an exception. +// *

+// * In a pattern, anything between %{ and } or : is considered +// * 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 + "]"); +// } +// int semanticNameIndex = pattern.indexOf(':', begin); +// int end = syntaxEndIndex; +// if (semanticNameIndex != -1) { +// end = Math.min(syntaxEndIndex, semanticNameIndex); +// } +// String dependsOnPattern = pattern.substring(begin, end); +// validatePatternBank(dependsOnPattern, path); +// } +// path.pop(); +// } + +// private Map patternBank = new HashMap<>(); + + public void validatePatternBank() { for (String patternName : patternBank.keySet()) { - validatePatternBank(patternName, new Stack<>()); + validatePatternBankIterative(patternName); } } - /** - * Checks whether patterns reference each other in a circular manner and, if so, fail with an exception. - * Also checks for malformed pattern definitions and fails with an exception. - *

- * In a pattern, anything between %{ and } or : is considered - * 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); } - int semanticNameIndex = pattern.indexOf(':', begin); - int end = syntaxEndIndex; - if (semanticNameIndex != -1) { - end = Math.min(syntaxEndIndex, semanticNameIndex); + + path.push(currentPatternName); + + String pattern = patternBank.get(currentPatternName); + if (pattern.contains("%{" + currentPatternName + "}") || pattern.contains("%{" + currentPatternName + ":")) { + throwExceptionForCircularReference(currentPatternName, pattern); } - String dependsOnPattern = pattern.substring(begin, end); - validatePatternBank(dependsOnPattern, path); + + 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; + } + } + + if (!hasDependencies) { + visited.add(currentPatternName); + path.pop(); + } + } + } + + private class PatternState { + String patternName; + int startIndex; + + PatternState(String patternName, int startIndex) { + this.patternName = patternName; + this.startIndex = startIndex; } - path.pop(); } private static void throwExceptionForCircularReference(String patternName, String pattern) {