oracle/graal · error · UnsupportedRegexException

UTF-16 range with surrogate values as upper or lower bound

Error message

UTF-16 range with surrogate values as upper or lower bound

What it means

Thrown by the OracleDB-flavor parser when a character-class range has one bound inside the UTF-16 surrogate block (U+D800..U+DFFF). OracleDB regexes match over UTF-16 code units, but TRegex internally works on code points and treats unpaired surrogates as invalid bounds; if lo or hi falls inside the surrogate range (and the range is not the already-handled broken/pair cases above), it throws UnsupportedRegexException.

Source

Thrown at regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/oracledb/OracleDBRegexParser.java:304

     *
     * This function transforms a given range to match LXR's behavior in case-sensitive mode.
     */
    private CodePointSet utf16RangeQuirkTransform(ClassSetContents contents) {
        int lo = contents.getRangeLo();
        int hi = contents.getRangeHi();
        if (contents.isBrokenRange()) {
            assert lo > Character.MAX_VALUE && hi > Character.MAX_SURROGATE && hi <= Character.MAX_VALUE && Character.highSurrogate(lo) <= hi;
            if (hi + 1 == lo) {
                return CodePointSet.createNoDedup(Character.MAX_SURROGATE + 1, Character.MAX_CODE_POINT);
            } else {
                return CodePointSet.createNoDedup(Character.MAX_SURROGATE + 1, hi, lo, Character.MAX_CODE_POINT);
            }
        }
        if (hi < Character.MIN_SURROGATE) {
            return contents.getCodePointSet();
        }
        if (Character.MIN_SURROGATE <= lo && lo <= Character.MAX_SURROGATE || hi <= Character.MAX_SURROGATE) {
            throw new UnsupportedRegexException("UTF-16 range with surrogate values as upper or lower bound");
        }
        if (lo < Character.MIN_SURROGATE) {
            if (hi == Character.MAX_VALUE) {
                // range contains the surrogate range => surrogate pairs will match as well.
                return CodePointSet.create(lo, Character.MAX_CODE_POINT);
            } else if (hi < Character.MAX_VALUE) {
                // range contains the surrogate range => surrogate pairs will match as well.
                return CodePointSet.create(lo, hi, Character.MAX_VALUE + 1, Character.MAX_CODE_POINT);
            } else {
                // lower bound is less than surrogate range and upper bound is a surrogate
                // pair => exclude the range from surrogate range to 0xffff.
                return CodePointSet.create(lo, Character.MIN_SURROGATE - 1, Character.MAX_VALUE + 1, hi);
            }
        } else {
            if (hi <= Character.MAX_VALUE || lo > Character.MAX_VALUE) {
                // either both values are encoded as a surrogate pair or both are single
                // char values
                return contents.getCodePointSet();

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Exclude the surrogate block from class ranges: use bounds below U+D800 or at/above U+E000, e.g. [\u1000-\uD7FF] or [\uE000-\uFFFF]
  2. To match supplementary (astral) characters, allow surrogate pairs by extending the upper bound to U+10FFFF via the pair-matching forms the parser supports (range ending at Character.MAX_VALUE), rather than naming surrogate units directly
  3. Validate generated ranges programmatically: reject any bound where 0xD800 <= cp <= 0xDFFF

Example fix

// before (OracleDB flavor)
"[\\uD800-\\uDFFF]" // surrogate bounds

// after
"[\\uE000-\\uFFFF]" // avoid the surrogate block
Defensive patterns

Strategy: validation

Validate before calling

boolean rangeHasSurrogateBound(String pattern) {
    java.util.regex.Matcher m = java.util.regex.Pattern.compile("\\\\u(D800|DB7F|DB80|DBFF|DC00|DF7F|DF80|DFFF)").matcher(pattern);
    return m.find();
}
// stronger: parse class ranges and reject any bound cp where 0xD800 <= cp <= 0xDFFF

Try / catch

try {
    RegexObject re = compileOracleDB(pattern);
} catch (UnsupportedRegexException e) {
    // rewrite ranges to exclude U+D800..U+DFFF bounds, then recompile
}

Prevention

When it happens

Trigger: Compiling an OracleDB-flavor class like [\uD800-\uDBFF], [\uDC00-\uDFFF], or [\u1000-\uD800] where a range endpoint is a surrogate code unit. The checks Character.MIN_SURROGATE <= lo <= MAX_SURROGATE or hi <= MAX_SURROGATE fire and throw.

Common situations: Patterns generated from code point arithmetic or \uXXXX literals intended to match astral characters by their surrogate halves; porting patterns that attempt to match raw surrogate units; ranges copied from Unicode charts that accidentally include the surrogate block.

Related errors


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