oracle/graal · error · UnsupportedRegexException

nested look-behind assertions

Error message

nested look-behind assertions

What it means

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.

Source

Thrown at regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexAST.java:562

     *  -> result: /(?:[_any_][_any_](?:|[_any_](?:|[_any_])))(?<=ab)/
     *      -> the non-optional [_any_] - matchers will be used if fromIndex > 0,
     *                                    the optional matchers will always be used
     * }
     *
     * The non-optional [_any_] - matchers are also called "hard prefix states" in other parts of
     * the code. They make sure we only match look-behinds and not parts of the main expression when
     * starting a match before the fromIndex-parameter. For example, when matching a regex
     * /(?<=ab)c|ab/ on string "abc" with fromIndex 2, we would first rewind the index to 0 in order
     * to match the look-behind, but we must make sure not to match the literal "ab" of the main
     * expression's second branch until we reach index 2 again.
     */
    public void createPrefix() {
        if (root.startsWithCaret() || properties.hasNonLiteralLookBehindAssertions()) {
            wrappedRoot = root;
            return;
        }
        if (properties.hasNestedLookBehindAssertions()) {
            throw new UnsupportedRegexException("nested look-behind assertions");
        }
        final int prefixLength = root.getPrefixLengthMax();
        if (prefixLength == 0) {
            wrappedRoot = root;
            return;
        }
        final Group wrapRoot = createGroup();
        wrapRoot.setPrefix();
        final Sequence wrapRootSeq = createSequence();
        wrapRoot.add(wrapRootSeq);
        wrapRootSeq.setPrefix();
        // create non-optional matchers ([_any_][_any_]...)
        for (int i = 0; i < prefixLength; i++) {
            wrapRootSeq.add(createPrefixAnyMatcher());
        }
        if (!flags.isSticky()) {
            Group prevOpt = null;
            // create optional matchers ((?:|[_any_](?:|[_any_]))...)

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Flatten the nesting: inline the inner look-behind condition into a single look-behind or move it into the main expression.
  2. Perform the inner check in application code after locating a candidate match.
  3. Rely on the backtracking-engine fallback (TRegexCompilationRequest catches UnsupportedRegexException and retries) — do not force the linear engine.
  4. Use matches() on extracted substrings instead of find() with look-behind, so no prefix needs to be built.

Example fix

// before
String pattern = "(?<=(?<=foo)bar)baz"; Matcher m = p.matcher(input); m.find(from);

// after
String pattern = "barbaz"; // match candidate, then verify input.startsWith("foo", m.start()-6) in code
Defensive patterns

Strategy: try-catch

Validate before calling

if (pattern.indexOf("(?<=") >= 0 && countOccurrences(pattern, "(?<=") > 1) { throw new IllegalArgumentException("possible nested look-behind; prefix construction unsupported"); }

Type guard

null

Try / catch

try { engine.compile(pattern); } catch (UnsupportedRegexException e) { backtrackingEngine.compile(pattern); } // or verify look-behind conditions manually after find()

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/b0cecd1328e555b4. Report an issue: GitHub.