{"record":{"id":"ac4684cc07e74ee9","repo":"oracle/graal","slug":"utf-16-range-with-surrogate-values-as-upper-or-low","errorCode":null,"errorMessage":"UTF-16 range with surrogate values as upper or lower bound","messagePattern":"UTF-16 range with surrogate values as upper or lower bound","errorType":"exception","errorClass":"UnsupportedRegexException","httpStatus":null,"severity":"error","filePath":"regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/oracledb/OracleDBRegexParser.java","lineNumber":304,"sourceCode":"     *\n     * This function transforms a given range to match LXR's behavior in case-sensitive mode.\n     */\n    private CodePointSet utf16RangeQuirkTransform(ClassSetContents contents) {\n        int lo = contents.getRangeLo();\n        int hi = contents.getRangeHi();\n        if (contents.isBrokenRange()) {\n            assert lo > Character.MAX_VALUE && hi > Character.MAX_SURROGATE && hi <= Character.MAX_VALUE && Character.highSurrogate(lo) <= hi;\n            if (hi + 1 == lo) {\n                return CodePointSet.createNoDedup(Character.MAX_SURROGATE + 1, Character.MAX_CODE_POINT);\n            } else {\n                return CodePointSet.createNoDedup(Character.MAX_SURROGATE + 1, hi, lo, Character.MAX_CODE_POINT);\n            }\n        }\n        if (hi < Character.MIN_SURROGATE) {\n            return contents.getCodePointSet();\n        }\n        if (Character.MIN_SURROGATE <= lo && lo <= Character.MAX_SURROGATE || hi <= Character.MAX_SURROGATE) {\n            throw new UnsupportedRegexException(\"UTF-16 range with surrogate values as upper or lower bound\");\n        }\n        if (lo < Character.MIN_SURROGATE) {\n            if (hi == Character.MAX_VALUE) {\n                // range contains the surrogate range => surrogate pairs will match as well.\n                return CodePointSet.create(lo, Character.MAX_CODE_POINT);\n            } else if (hi < Character.MAX_VALUE) {\n                // range contains the surrogate range => surrogate pairs will match as well.\n                return CodePointSet.create(lo, hi, Character.MAX_VALUE + 1, Character.MAX_CODE_POINT);\n            } else {\n                // lower bound is less than surrogate range and upper bound is a surrogate\n                // pair => exclude the range from surrogate range to 0xffff.\n                return CodePointSet.create(lo, Character.MIN_SURROGATE - 1, Character.MAX_VALUE + 1, hi);\n            }\n        } else {\n            if (hi <= Character.MAX_VALUE || lo > Character.MAX_VALUE) {\n                // either both values are encoded as a surrogate pair or both are single\n                // char values\n                return contents.getCodePointSet();","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/oracledb/OracleDBRegexParser.java#L286-L322","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Exclude the surrogate block from class ranges: use bounds below U+D800 or at/above U+E000, e.g. [\\u1000-\\uD7FF] or [\\uE000-\\uFFFF]","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","Validate generated ranges programmatically: reject any bound where 0xD800 <= cp <= 0xDFFF"],"exampleFix":"// before (OracleDB flavor)\n\"[\\\\uD800-\\\\uDFFF]\" // surrogate bounds\n\n// after\n\"[\\\\uE000-\\\\uFFFF]\" // avoid the surrogate block","handlingStrategy":"validation","validationCode":"boolean rangeHasSurrogateBound(String pattern) {\n    java.util.regex.Matcher m = java.util.regex.Pattern.compile(\"\\\\\\\\u(D800|DB7F|DB80|DBFF|DC00|DF7F|DF80|DFFF)\").matcher(pattern);\n    return m.find();\n}\n// stronger: parse class ranges and reject any bound cp where 0xD800 <= cp <= 0xDFFF","typeGuard":null,"tryCatchPattern":"try {\n    RegexObject re = compileOracleDB(pattern);\n} catch (UnsupportedRegexException e) {\n    // rewrite ranges to exclude U+D800..U+DFFF bounds, then recompile\n}","preventionTips":["Never use surrogate code points as range endpoints in OracleDB-flavor classes","Generate ranges from code point data with an assert outside 0xD800..0xDFFF"],"tags":["regex","oracledb-flavor","unicode","surrogates","character-class"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}