{"record":{"id":"4861fb38a6e9841b","repo":"oracle/graal","slug":"canonical-equivalence-is-not-supported","errorCode":null,"errorMessage":"Canonical equivalence is not supported","messagePattern":"Canonical equivalence is not supported","errorType":"exception","errorClass":"UnsupportedRegexException","httpStatus":null,"severity":"error","filePath":"regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/java/JavaRegexLexer.java","lineNumber":152,"sourceCode":"            return Token.createCharClass(curCharClass.toCodePointSet());\n        } else {\n            return Token.createCharClass(CodePointSet.create(codePoint));\n        }\n    }\n\n    private static final TBitSet WHITESPACE = TBitSet.valueOf('\\t', '\\n', '\\f', '\\r', ' ');\n    private static final TBitSet PREDEFINED_CHAR_CLASSES = TBitSet.valueOf('D', 'H', 'S', 'V', 'W', 'd', 'h', 's', 'v', 'w');\n    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);\n\n    final JavaUnicodeProperties unicode;\n    private final Deque<JavaFlags> flagsStack = new ArrayDeque<>();\n    private JavaFlags currentFlags;\n    private final CodePointSetAccumulator curCharClass = new CodePointSetAccumulator();\n\n    public JavaRegexLexer(RegexSource source, JavaFlags flags, CompilationBuffer compilationBuffer) {\n        super(source, compilationBuffer);\n        if (flags.isCanonEq()) {\n            throw new UnsupportedRegexException(\"Canonical equivalence is not supported\");\n        }\n        if (flags.isLiteral()) {\n            throw new UnsupportedRegexException(\"Literal parsing is not supported\");\n        }\n        this.unicode = JavaUnicodeProperties.create(source.getOptions());\n        this.currentFlags = flags;\n    }\n\n    @Override\n    protected boolean isPredefCharClass(char c) {\n        return PREDEFINED_CHAR_CLASSES.get(c);\n    }\n\n    JavaFlags getLocalFlags() {\n        return currentFlags;\n    }\n\n    public void pushLocalFlags() {","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/java/JavaRegexLexer.java#L134-L170","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the CANON_EQ flag if your input is already normalized (NFC/NFD) beforehand — usually sufficient.","Pre-normalize both pattern and input with java.text.Normalizer and match without CANON_EQ.","If canonical equivalence is mandatory, execute that match with a fallback regex engine that supports it."],"exampleFix":"// before\nPattern p = Pattern.compile(\"a\\u0300\", Pattern.CANON_EQ); // throws on TRegex\n\n// after\nString pat = Normalizer.normalize(\"a\\u0300\", Form.NFC); // \"\\u00E0\"\nPattern p = Pattern.compile(Pattern.quote(pat)); // match NFC-normalized input","handlingStrategy":"fallback","validationCode":"int flags = ...;\nboolean needsCanonEq = (flags & Pattern.CANON_EQ) != 0;\nif (needsCanonEq) {\n    // normalize inputs instead of relying on CANON_EQ\n    pattern = Normalizer.normalize(pattern, Normalizer.Form.NFC);\n    input = Normalizer.normalize(input, Normalizer.Form.NFC);\n    flags &= ~Pattern.CANON_EQ;\n}","typeGuard":null,"tryCatchPattern":"try {\n    return Pattern.compile(pattern, flags);\n} catch (com.oracle.truffle.regex.UnsupportedRegexException e) {\n    if ((flags & Pattern.CANON_EQ) != 0) {\n        return compileWithHostFallback(pattern, flags); // route to engine supporting CANON_EQ\n    }\n    throw e;\n}","preventionTips":["Normalize pattern and input to NFC and drop CANON_EQ — equivalent for NFC data.","Audit dependencies for unconditional Pattern.CANON_EQ use before running on GraalVM.","Wrap regex compilation in a policy layer so unsupported flags can fall back gracefully."],"tags":["regex","truffle","espresso","java-util-pattern","canonical-equivalence","unsupported-flag"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}