{"record":{"id":"21bd7e38c6158f37","repo":"oracle/graal","slug":"possessive-quantifiers-are-not-supported-21bd7e","errorCode":null,"errorMessage":"possessive quantifiers are not supported","messagePattern":"possessive quantifiers are not supported","errorType":"exception","errorClass":"UnsupportedRegexException","httpStatus":null,"severity":"error","filePath":"regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/java/JavaRegexValidator.java","lineNumber":126,"sourceCode":"                case wordBoundary:\n                case nonWordBoundary:\n                case charClass:\n                case classSet:\n                case linebreak:\n                case backReference:\n                    curTermState = CurTermState.Other;\n                    break;\n                case quantifier:\n                    Token.Quantifier quantifier = (Token.Quantifier) token;\n                    // quantifiers of type *, + or ? cannot directly follow another quantifier\n                    if (last instanceof Token.Quantifier && quantifier.isSingleChar()) {\n                        throw syntaxErrorHere(JavaErrorMessages.danglingMetaCharacter(quantifier), ErrorCode.InvalidQuantifier);\n                    }\n                    if (curTermState == CurTermState.Null && quantifier.isSingleChar()) {\n                        throw syntaxErrorHere(JavaErrorMessages.danglingMetaCharacter(quantifier), ErrorCode.InvalidQuantifier);\n                    }\n                    if (quantifier.isPossessive()) {\n                        throw new UnsupportedRegexException(\"possessive quantifiers are not supported\");\n                    }\n                    break;\n                case alternation:\n                case inlineFlags:\n                    curTermState = CurTermState.Null;\n                    break;\n                case captureGroupBegin:\n                case nonCaptureGroupBegin:\n                    curTermState = CurTermState.Null;\n                    syntaxStack.add(RegexStackElem.Group);\n                    break;\n                case lookAheadAssertionBegin:\n                    curTermState = CurTermState.Null;\n                    syntaxStack.add(RegexStackElem.LookAheadAssertion);\n                    break;\n                case lookBehindAssertionBegin:\n                    curTermState = CurTermState.Null;\n                    syntaxStack.add(RegexStackElem.LookBehindAssertion);","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/java/JavaRegexValidator.java#L108-L144","documentation":"The same possessive-quantifier rejection as in the parser, raised from the JavaRegexValidator pass. TRegex validates the token stream before compilation; when a Quantifier token has isPossessive() set, the validator throws UnsupportedRegexException('possessive quantifiers are not supported') even in contexts where the parser's own check did not fire (e.g. dangling or validator-only paths).","triggerScenarios":"Any Java-flavor pattern containing *+, ++, ?+ or {n,m}+ that reaches the validation stage, e.g. validating \"x?+\" after parse; the validator's quantifier case calls quantifier.isPossessive() and throws.","commonSituations":"Same as the parser variant: JDK-optimized or ReDoS-hardened patterns with possessive quantifiers run on a TRegex-backed engine; users see either this validator message or the parser's depending on which pass encounters the token first.","solutions":["Remove the possessive '+' from every quantifier in the pattern ('a?+' -> 'a?', '(ab){1,3}+' -> '(ab){1,3}')","Grep patterns for the possessive suffix regex ([*+?}]|\\{[0-9]+(,[0-9]*)?\\})\\+ before compiling when patterns are externally supplied","If cut semantics are required, compute them outside the regex or switch to java.util.regex for that pattern"],"exampleFix":"// before\nString p = \"x?+y\";\n\n// after\nString p = \"x?y\";","handlingStrategy":"validation","validationCode":"private static final java.util.regex.Pattern POSSESSIVE =\n    java.util.regex.Pattern.compile(\"([*+?]|\\\\{[0-9]+(,[0-9]*)?\\\\})\\\\+\");\n\nboolean hasPossessiveQuantifier(String pattern) {\n    return POSSESSIVE.matcher(pattern).find();\n}","typeGuard":null,"tryCatchPattern":"try {\n    RegexObject re = compileJavaFlavor(pattern);\n} catch (UnsupportedRegexException e) {\n    if (e.getReason().contains(\"possessive\")) {\n        pattern = POSSESSIVE.matcher(pattern).replaceAll(\"$1\");\n        re = compileJavaFlavor(pattern);\n    } else { throw e; }\n}","preventionTips":["Validate the token stream (or string) for possessive suffixes before the validator pass rejects it","Keep a pattern sanitizer at your API boundary for user-supplied regexes"],"tags":["regex","java-flavor","quantifier","possessive","validation","unsupported-feature"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}