Possessive Quantifier Conflict
ID |
php.regex_possessive_quantifier_conflict |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Php |
Tags |
regex, reliability |
Description
Reports a possessive repetition (X*+, X++, X{n,}+) over a single character or simple class, immediately followed by a mandatory character or class whose members the repetition has already taken.
Rationale
A possessive quantifier consumes as much as it can and never backtracks. Whatever it swallowed is gone, so a mandatory atom right after it cannot be fed from that run:
// Bad
preg_match('/^\d++\d$/', '12345'); // 0 -- \d++ took all five digits and gives none back
preg_match('/a++a/', 'aaaa'); // 0
-
The pattern can never match any input at all, so the branch it guards is dead.
-
It normally arrives from tightening a greedy quantifier for performance:
\d+\dmatches and\d++\ddoes not, and the change looks like a pure optimisation.
Remediation
Either drop the possessive marker where backtracking is what makes the pattern work, or fold the mandatory atom into the repetition’s own bounds.
// Good
preg_match('/^\d+\d$/', '12345'); // 1 -- greedy, so it gives a digit back
preg_match('/^\d{2,}+$/', '12345'); // 1 -- says "two or more" directly
What is not reported
The check is deliberately narrow, because a wrong verdict here would push someone to rewrite a working pattern:
-
only a single character or a simple class — including
\d,\w,\sand their negations — takes part in the subset test on either side; -
only the atom immediately adjacent to the quantifier is examined, so
/aa++bc/is fine: the possessive body is the seconda, andbwas never a candidate; -
a lookbehind or a group after the quantifier consumes nothing and never conflicts;
-
an atom that is itself quantified with a zero minimum —
/a++a*/— is not mandatory and is left alone.