oracle/graal · error · UnsupportedRegexException

Canonical equivalence is not supported

Error message

Canonical equivalence is not supported

What it means

The java.util.regex-flavored lexer (JavaRegexLexer) rejects Pattern.CANON_EQ at construction time by throwing UnsupportedRegexException: TRegex does not implement canonical equivalence decomposition, so patterns compiled with that flag cannot be translated.

Source

Thrown at regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/java/JavaRegexLexer.java:152

            return Token.createCharClass(curCharClass.toCodePointSet());
        } else {
            return Token.createCharClass(CodePointSet.create(codePoint));
        }
    }

    private static final TBitSet WHITESPACE = TBitSet.valueOf('\t', '\n', '\f', '\r', ' ');
    private static final TBitSet PREDEFINED_CHAR_CLASSES = TBitSet.valueOf('D', 'H', 'S', 'V', 'W', 'd', 'h', 's', 'v', 'w');
    private static final TBitSet LATIN1_CHARS_THAT_CASE_FOLD_TO_NON_LATIN1_CHARS = TBitSet.valueOf(0x49, 0x4b, 0x53, 0x69, 0x6b, 0x73, 0xb5, 0xc5, 0xe5, 0xff);

    final JavaUnicodeProperties unicode;
    private final Deque<JavaFlags> flagsStack = new ArrayDeque<>();
    private JavaFlags currentFlags;
    private final CodePointSetAccumulator curCharClass = new CodePointSetAccumulator();

    public JavaRegexLexer(RegexSource source, JavaFlags flags, CompilationBuffer compilationBuffer) {
        super(source, compilationBuffer);
        if (flags.isCanonEq()) {
            throw new UnsupportedRegexException("Canonical equivalence is not supported");
        }
        if (flags.isLiteral()) {
            throw new UnsupportedRegexException("Literal parsing is not supported");
        }
        this.unicode = JavaUnicodeProperties.create(source.getOptions());
        this.currentFlags = flags;
    }

    @Override
    protected boolean isPredefCharClass(char c) {
        return PREDEFINED_CHAR_CLASSES.get(c);
    }

    JavaFlags getLocalFlags() {
        return currentFlags;
    }

    public void pushLocalFlags() {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Remove the CANON_EQ flag if your input is already normalized (NFC/NFD) beforehand — usually sufficient.
  2. Pre-normalize both pattern and input with java.text.Normalizer and match without CANON_EQ.
  3. If canonical equivalence is mandatory, execute that match with a fallback regex engine that supports it.

Example fix

// before
Pattern p = Pattern.compile("a\u0300", Pattern.CANON_EQ); // throws on TRegex

// after
String pat = Normalizer.normalize("a\u0300", Form.NFC); // "\u00E0"
Pattern p = Pattern.compile(Pattern.quote(pat)); // match NFC-normalized input
Defensive patterns

Strategy: fallback

Validate before calling

int flags = ...;
boolean needsCanonEq = (flags & Pattern.CANON_EQ) != 0;
if (needsCanonEq) {
    // normalize inputs instead of relying on CANON_EQ
    pattern = Normalizer.normalize(pattern, Normalizer.Form.NFC);
    input = Normalizer.normalize(input, Normalizer.Form.NFC);
    flags &= ~Pattern.CANON_EQ;
}

Try / catch

try {
    return Pattern.compile(pattern, flags);
} catch (com.oracle.truffle.regex.UnsupportedRegexException e) {
    if ((flags & Pattern.CANON_EQ) != 0) {
        return compileWithHostFallback(pattern, flags); // route to engine supporting CANON_EQ
    }
    throw e;
}

Prevention

When it happens

Trigger: Compiling a regex through the JavaUtilPattern flavor with Pattern.CANON_EQ set, e.g. Pattern.compile(pattern, Pattern.CANON_EQ) executed on Espresso, or a library (e.g. text normalization utilities) that always passes CANON_EQ.

Common situations: Libraries written for HotSpot that use CANON_EQ for Unicode text matching run on GraalVM/Espresso; security or text-processing code assuming full java.util.regex feature parity.

Related errors


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