{"record":{"id":"b0cecd1328e555b4","repo":"oracle/graal","slug":"nested-look-behind-assertions-b0cecd","errorCode":null,"errorMessage":"nested look-behind assertions","messagePattern":"nested look-behind assertions","errorType":"exception","errorClass":"UnsupportedRegexException","httpStatus":null,"severity":"error","filePath":"regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexAST.java","lineNumber":562,"sourceCode":"     *  -> result: /(?:[_any_][_any_](?:|[_any_](?:|[_any_])))(?<=ab)/\n     *      -> the non-optional [_any_] - matchers will be used if fromIndex > 0,\n     *                                    the optional matchers will always be used\n     * }\n     *\n     * The non-optional [_any_] - matchers are also called \"hard prefix states\" in other parts of\n     * the code. They make sure we only match look-behinds and not parts of the main expression when\n     * starting a match before the fromIndex-parameter. For example, when matching a regex\n     * /(?<=ab)c|ab/ on string \"abc\" with fromIndex 2, we would first rewind the index to 0 in order\n     * to match the look-behind, but we must make sure not to match the literal \"ab\" of the main\n     * expression's second branch until we reach index 2 again.\n     */\n    public void createPrefix() {\n        if (root.startsWithCaret() || properties.hasNonLiteralLookBehindAssertions()) {\n            wrappedRoot = root;\n            return;\n        }\n        if (properties.hasNestedLookBehindAssertions()) {\n            throw new UnsupportedRegexException(\"nested look-behind assertions\");\n        }\n        final int prefixLength = root.getPrefixLengthMax();\n        if (prefixLength == 0) {\n            wrappedRoot = root;\n            return;\n        }\n        final Group wrapRoot = createGroup();\n        wrapRoot.setPrefix();\n        final Sequence wrapRootSeq = createSequence();\n        wrapRoot.add(wrapRootSeq);\n        wrapRootSeq.setPrefix();\n        // create non-optional matchers ([_any_][_any_]...)\n        for (int i = 0; i < prefixLength; i++) {\n            wrapRootSeq.add(createPrefixAnyMatcher());\n        }\n        if (!flags.isSticky()) {\n            Group prevOpt = null;\n            // create optional matchers ((?:|[_any_](?:|[_any_]))...)","sourceCodeStart":544,"sourceCodeEnd":580,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexAST.java#L544-L580","documentation":"Thrown by RegexAST.createPrefix when the AST reports nested look-behind assertions while building the search prefix. For find()-style searches the engine precomputes a fixed-length prefix of the regex to rewind input safely before fromIndex; that construction only supports flat look-behinds, so nested ones are rejected.","triggerScenarios":"Compiling for searching (find/fromIndex) a regex that contains a look-behind nested inside another look-around or group in a way flagged by ASTProperties.hasNestedLookBehindAssertions, e.g. (?<=(?<=a)b) or (?=(?<=x))y.","commonSituations":"Search-oriented APIs (find with fromIndex) on patterns ported from PCRE/Perl that stack look-behinds; look-behind inside quantified groups; syntax-highlighting or log-scanning grammars using nested variable-position assertions.","solutions":["Flatten the nesting: inline the inner look-behind condition into a single look-behind or move it into the main expression.","Perform the inner check in application code after locating a candidate match.","Rely on the backtracking-engine fallback (TRegexCompilationRequest catches UnsupportedRegexException and retries) — do not force the linear engine.","Use matches() on extracted substrings instead of find() with look-behind, so no prefix needs to be built."],"exampleFix":"// before\nString pattern = \"(?<=(?<=foo)bar)baz\"; Matcher m = p.matcher(input); m.find(from);\n\n// after\nString pattern = \"barbaz\"; // match candidate, then verify input.startsWith(\"foo\", m.start()-6) in code","handlingStrategy":"try-catch","validationCode":"if (pattern.indexOf(\"(?<=\") >= 0 && countOccurrences(pattern, \"(?<=\") > 1) { throw new IllegalArgumentException(\"possible nested look-behind; prefix construction unsupported\"); }","typeGuard":"null","tryCatchPattern":"try { engine.compile(pattern); } catch (UnsupportedRegexException e) { backtrackingEngine.compile(pattern); } // or verify look-behind conditions manually after find()","preventionTips":["Keep look-behinds unnested and at top level.","For find() with fromIndex, verify preceding text in code instead of look-behinds.","Watch for nesting introduced by group wrapping of look-behinds."],"tags":["regex","tregex","look-behind","search","compilation"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}