Alternation With An Empty Branch
ID |
php.regex_empty_alternative |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Code Smell |
Language |
Php |
Tags |
code-smell, regex |
Description
Reports an alternation with an empty branch: (a|b|), (|a), a||b. The empty branch matches the empty string, so the whole alternation is optional and, at any position, always succeeds.
Rationale
// Bad
preg_match('/^(GET|POST|)$/', 'GET'); // 1
preg_match('/^(GET|POST|)$/', ''); // 1 -- the empty branch matched
-
The pattern was written to accept two method names and now accepts three things, one of which is nothing.
-
Almost always the residue of an edit: a branch was deleted and its bar left behind, or a trailing bar was typed while extending the list.
-
The pattern silently stops rejecting what it was written to reject, so the failure surfaces as bad data downstream rather than as a validation error.
Remediation
Drop the stray bar. When the value really is optional, say so where the reader looks for it — with ? on the group, or with an explicit check.
// Good
preg_match('/^(GET|POST)$/', $s); // the empty case was a typo
preg_match('/^(GET|POST)?$/', $s); // optional, and it says so
What is not reported
Only a lexically empty branch is reported — one whose span holds no token at all. A branch consisting of an anchor (/(^|,)/) or of a zero-width assertion (/(\b|,)/) is the deliberate "this or the boundary" idiom and is left alone. Each alternation is judged at its own level, the specific group or the top level that directly contains it, so an empty branch inside a nested group is never attributed to the level around it.