oracle/graal · error · UnsupportedRegexException

PureNFA explosion

Error message

PureNFA explosion

What it means

Thrown by the PureNFA compiler (PureNFAGenerator.java:58) when the NFA built for patterns that cannot be handled by the DFA engine (e.g. large bound quantifiers, backreferences-adjacent constructs unrolled by the pure NFA) exceeds TRegexMaxPureNFASize (1,000,000 states). The PureNFA budget is much larger than the DFA NFA budget because the NFA is executed directly. Exceeding it throws UnsupportedRegexException.

Source

Thrown at regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/Counter.java:91

        count -= i;
        return ret;
    }

    public static class ThresholdCounter extends Counter {

        private final int max;
        private final String errorMsg;

        public ThresholdCounter(int max, String errorMsg) {
            this.max = max;
            this.errorMsg = errorMsg;
        }

        @Override
        public int inc(int i) {
            final int ret = super.inc(i);
            if (getCount() > max) {
                throw new UnsupportedRegexException(errorMsg);
            }
            return ret;
        }
    }

    public static class ThreadSafeCounter extends Counter {

        @Override
        public int inc() {
            int c = count;
            if (c < Integer.MAX_VALUE) {
                count = c + 1;
            }
            return count;
        }

        @Override
        public int inc(int i) {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Rewrote nested bounded quantifiers into a single bound: (a{0,100}){0,100} -> a{0,10000} (single unroll) or use a loop in guest code.
  2. Reduce the repetition bounds to what the data actually requires.
  3. Catch UnsupportedRegexException and fall back to a backtracking engine.

Example fix

// before
const re = /^(a{0,50}){0,50}$/; // unrolls up to 2500 copies per state -> explosion

// after
const re = /^a{0,2500}$/; // single bounded quantifier
Defensive patterns

Strategy: fallback

Validate before calling

// Reject multiplicative bounded-repeat shapes before compiling
if (pattern.matches("(?s).*\\{0?,\\d+\\}.*\\{0?,\\d+\\}.*")) {
    warnAndFallback(pattern); // nested counted repeats risk PureNFA explosion
}

Try / catch

try { compile(pattern); } catch (UnsupportedRegexException e) { /* 'PureNFA explosion' */ return backtrackingFallback(pattern); }

Prevention

When it happens

Trigger: Compiling patterns that are routed to the pure NFA engine and unroll combinatorially: nested quantifiers with large bounds like (a{0,100}){0,100} or (a|b)*{large} repetitions; each unrolled copy adds states until the 1e6 threshold trips.

Common situations: Bounded-repeat patterns with multiplicative expansion ((x{m}){n} style), generated validators, or patterns ported from PCRE that rely on huge counted repetitions. GraalVM guest-language RegExp compilation fails at compile time with 'PureNFA explosion'.

Related errors


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