DirectoryIteratorDotEntries
warning native on by defaultGroup: Probable bugs
Detect a filesystem mutation on DirectoryIterator entry paths inside foreach without a dominating isDot exclusion.
Example
php
<?php
function bad($dir) { foreach (new DirectoryIterator($dir) as $entry) { unlink($entry->getPathname()); } }Reported:
- line 2: Skip dot entries before modifying directory entries.
Detection
- Report a filesystem mutation on DirectoryIterator entry paths inside foreach without a dominating isDot exclusion.
- Resolve builtin calls and named arguments before checking the contract. Project-local callees are considered only when their bodies or immutable summaries establish the required facts. The default is enabled, with positive proof required.
- For flow-dependent conditions, require a reachable source-to-use path with no intervening invalidation. Unknown calls, ambiguous aliases or recursion cannot establish definite facts. When the proof is inconclusive, nothing is reported.
Exceptions (no report)
- FilesystemIterator with SKIP_DOTS and nonmutating entry handling are excluded.
- No report when the conditions guarding the mutation prove the entry is neither
.nor.., from tests of methods called on the same loop variable earlier in the loop body. Method names match case-insensitively and redundant parentheses are ignored.- Proving tests:
isDot()orisDir()false;isFile()orisLink()true (dot entries are always directories);getFilename()or argument-lessgetBasename()compared with a string literal (===,!==, and==,!=with a literal that does not read as a number, either operand order) or searched in a literal array throughin_array. A name test rules out only the names it mentions, so both.and..must be covered, possibly by several tests; tests that contradict each other leave the mutation unreachable. - Composition:
!,&&/and,||/or, comparisons with booleans and a local holding a test combine the usual way. - Positions: the branch of an
if/elseif/elsechain, a ternary ormatch (true)arm, the right operand of&&/||, or code after anifwhose branch for a dot entry always leaves (continue, break, return, throw), with or without other arms. - Any other condition (other receiver, dynamic method, unknown list, suffix argument to
getBasename, a jump label between the test and the mutation) proves nothing and the report stays.
- Proving tests:
- Reassignment, unsetting or reuse of the element variable before its destructive use leaves its directory-entry identity incomplete. Writes inside an uninvoked separate closure do not establish outer-variable reassignment.
- Unresolved targets, incomplete required facts, suppressed findings and PHP versions lacking the referenced API or language feature produce no report. User declarations shadowing builtin names are not builtin contracts.
Fix
No automatic fix: loop control structure and entry-processing policy require deliberate handling.
PHP versions
Apply only on PHP versions supporting the referenced APIs and syntax (within PHP 5.3–8.5). Builtin behavior and argument contracts follow the configured PHP version. Extension-specific calls require resolved extension API symbols; do not treat same-named application functions as extension calls.
Options
None. Enable or disable this rule through normal rule configuration.
Configure
In custos.json:
json
{
"rules": {
"DirectoryIteratorDotEntries": {
"enabled": false
}
}
}Suppress
Before the statement or declaration (or the first statement of the file):
php
// @custos-ignore DirectoryIteratorDotEntries