ReaddirFalsyFilenameLoss
warning native on by default quick-fixGroup: Probable bugs
Compare the directory entry with false.
Example
Finding:
<?php
while ($entry = readdir($dir)) { echo $entry; }Reported:
- line 2: Compare the directory entry with false.
Detection
- Report a resolved readdir call whose result decides an
if,elseif,while,do … whilecondition or the last condition expression of afor, when it is tested for truthiness: directly, through a plain=assignment in the condition, through any number of!negations, or through a comparison with thetrue/falseconstant that is loose on the entry itself (readdir($h) == false). A legal filename consisting of digit zero is otherwise treated as end-of-directory. Once the entry has been turned into a boolean by!or a loose comparison, further!,==,!=,===,!==tests of that boolean againsttrue/falseare followed too ((!readdir($h)) === falseis still a truthiness test). - Emit one finding per violating operation. Flow facts require a reachable path with no intervening mutation, escaped alias, unknown call involving the tracked value or analysis-budget exhaustion. Uncertain facts cannot prove a violation.
Exceptions (no report)
- Strict comparisons against false, merely assigned results and unrelated functions are excluded. A strict
===/!==comparison applied to the entry itself exempts the whole condition, however it is then negated or compared (!(readdir($h) === false),(readdir($h) !== false) == true). - The tested value must be the entry: compound assignments (
.=,??=, …), by-reference assignments, an assignment of the already negated result ($done = !readdir($h)), comparisons with anything other than thetrue/falseconstants, other operators and call arguments are not reported. First-class callable syntaxreaddir(...)is not a call. Earlier comma-separated expressions of aforcondition are not tested by the loop. - Exclude shadowed builtin symbols, unresolved required types, incomplete syntax and unsupported PHP versions.
Fix
Replace the whole condition with the entry expression, parenthesized, compared strictly: !== false when the original condition held for a truthy entry and === false otherwise. The entry expression is the call together with its own parentheses and plain assignment, copied verbatim, so the assignment and the call are evaluated exactly once and comments inside it survive. Negations and boolean comparisons around it are folded into the chosen operator (!!readdir($h) becomes (readdir($h)) !== false).
The surrounding !, parentheses and true/false comparison are dropped by the rewrite; when that dropped text contains a comment the finding is reported without a fix.
All edits use exact byte ranges, preserve comments and evaluation order, and require syntax supported by the target PHP version. A fix must never add an evaluation or silently change unrelated arguments.
PHP versions
Available throughout PHP 5.3–8.5.
Configure
In custos.json:
{
"rules": {
"ReaddirFalsyFilenameLoss": {
"enabled": false
}
}
}Suppress
Before the statement or declaration (or the first statement of the file):
// @custos-ignore ReaddirFalsyFilenameLoss