{"record":{"id":"5ce962c616b74659","repo":"oracle/graal","slug":"empty-path-with-look-ahead-assertion-in-expression","errorCode":null,"errorMessage":"empty path with look-ahead assertion in expression with bounded quantifier","messagePattern":"empty path with look-ahead assertion in expression with bounded quantifier","errorType":"exception","errorClass":"UnsupportedRegexException","httpStatus":null,"severity":"error","filePath":"regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/NFATraversalRegexASTVisitor.java","lineNumber":620,"sourceCode":"                        throw new UnsupportedRegexException(\"Cannot compile regex with empty state to DFA/NFA\");\n                    }\n                    popGroupExit();\n                    cur = curTerm;\n                    // Set the current group node as the path's target to indicate we want to\n                    // generate an EMPTY_STATE for it. The empty state allows the backtracking\n                    // engine to loop without consuming characters.\n                    curPath.add(PathElement.create(cur));\n                    return true;\n                }\n                if (isBuildingDFA() && curGroup.isMandatoryQuantifier() && !lookAroundsOnPath.isEmpty()) {\n                    for (int i = curPath.length() - 1; i >= 0; i--) {\n                        long element = curPath.get(i);\n                        RegexASTNode node = pathGetNode(element);\n                        if (PathElement.isGroupEnter(element) && node == curGroup) {\n                            break;\n                        }\n                        if (node.isLookAheadAssertion()) {\n                            throw new UnsupportedRegexException(\"empty path with look-ahead assertion in expression with bounded quantifier\");\n                        }\n                    }\n                }\n                // otherwise, retreat.\n                return retreat();\n            }\n            Sequence parentSeq = (Sequence) curTerm.getParent();\n            if (curTerm == (forward ? parentSeq.getLastTerm() : parentSeq.getFirstTerm())) {\n                final Group parentGroup = parentSeq.getParent();\n                pushGroupExit(parentGroup);\n                if (parentGroup.isLoop()) {\n                    cur = parentGroup;\n                    return false;\n                }\n                curTerm = parentGroup;\n            } else {\n                cur = parentSeq.getTerms().get(curTerm.getSeqIndex() + (forward ? 1 : -1));\n                return false;","sourceCodeStart":602,"sourceCodeEnd":638,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/NFATraversalRegexASTVisitor.java#L602-L638","documentation":"Thrown by NFATraversalRegexASTVisitor when, while building the DFA, it finds a look-ahead assertion sitting on a path that consumes no characters inside a mandatory quantifier loop. Scanning backwards from the group-enter of curGroup to the current node, any look-ahead node on that zero-width path makes the transition constraints unresolvable for the linear engine, so the regex is rejected.","triggerScenarios":"Compiling in DFA mode a pattern like ((?=x)){3} or (a?(?=y)){2,4} — a mandatory loop containing a look-ahead that can be reached without consuming input.","commonSituations":"Validation grammars that repeat assertion-only groups (e.g. ((?=.*\\d)){2}); wrapping look-aheads in counted or mandatory groups for readability; generated patterns that quantifier-wrap every assertion.","solutions":["Pull the look-ahead out of the quantified group: ((?=x)){3} is equivalent to (?=x), so write it once.","Ensure the loop body consumes at least one character, e.g. ((?:.)(?=x)){3} instead of ((?=x)){3}.","Rely on backtracking-engine fallback, which supports this construct.","Validate the assertion in application code after a candidate match."],"exampleFix":"// before\nString pattern = \"((?=.*\\d)){2}\\w+\";\n\n// after\nString pattern = \"(?=.*\\d)\\w+\"; // assertion applied once, outside the quantifier","handlingStrategy":"try-catch","validationCode":"// detect a look-ahead wrapped in a quantified group: ((?=...)){n} / ((?=...))+\nif (Pattern.compile(\"\\\\(\\\\(\\\\?=\").matcher(pattern).find()) throw new IllegalArgumentException(\"quantified look-ahead group; unsupported in linear engine\");","typeGuard":"null","tryCatchPattern":"try { compileLinear(pattern); } catch (UnsupportedRegexException e) { compileBacktracking(pattern); } // or hoist the look-ahead out of the quantifier","preventionTips":["Never wrap a bare look-ahead in a mandatory or counted group.","Give assertion-containing loops a consuming element.","Apply look-aheads once, outside quantifiers."],"tags":["regex","tregex","look-ahead","quantifiers","dfa"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}