wave4: psalm-0001 flagship patch + linter/CI/config scan survey

psalm-0001: FileFilter.allowsClass runs in_array() on every class the
analyzer visits. For C classes and F filter entries, per-run cost is
O(C*F). Fix: lazy array_fill_keys hash set; O(1) probe per class.
Bench: 336x at C=F=5000. Patch + ticket + bench + intel brief ship.

wave4-linter-ci-survey.md: consolidated report on 40 projects scanned
across linters (eslint, biome, prettier, pylint, ruff, black, rubocop,
shellcheck, stylelint, sqlfluff, phpstan, PHP_CodeSniffer, psalm,
rustfmt, golangci-lint, scalafmt, hadolint, yamllint, markdownlint,
ktlint, detekt), CI runners (act, buildkite-agent, tektoncd/pipeline,
concourse, woodpecker), config management (aws-cdk, cdk8s, kustomize),
and build tools (rollup, parcel, vite, turborepo, nx, lerna, swc,
babel, gulp).

Clean scans (zero HIGH+ findings): hadolint, shellcheck, gulp.

Document includes per-target finding counts and 7 triage follow-ups for
future waves (PHP_CodeSniffer ReDoS, ktlint spacing rule, pylint
MSG_ORDER.index, black pgen2 dfa, golangci-lint migrate, tektoncd
forbidden-env scan, aws-cdk region-info).
This commit is contained in:
russell@unturf.com 2026-04-24 17:21:51 -04:00
parent 788514bcf7
commit bb81a1a3a0
No known key found for this signature in database
9 changed files with 384 additions and 1 deletions

View file

@ -0,0 +1,53 @@
# UNDF: UNDF-2026-000001296
# UNDF: UNDF-2026-XXXXXXXXX
# CWE-407: Algorithmic Complexity -- O(C*F) -> O(C+F) in FileFilter::allowsClass
#
# Defect: allowsClass runs in_array(strtolower($cls), $this->fq_classlike_names, true)
# on every class the analyzer visits. For C classes and F filter entries,
# per-analysis cost is O(C*F). Psalm is already CPU-bound; this compounds
# the scan time on large monorepos with large filter lists.
#
# Fix: Lazy-init a lowercase hash set ($fq_classlike_names_set) and probe via
# isset() for O(1) per class. Built once per FileFilter instance and reused.
#
# Complexity gate (tests/test-psalm-cwe407.py):
# C=F=1000: fixed must complete in <5ms
# k-scaling 5x: time ratio must be <17.5x
--- a/src/Psalm/Config/FileFilter.php
+++ b/src/Psalm/Config/FileFilter.php
@@ -74,6 +74,12 @@ class FileFilter
*/
protected array $fq_classlike_names = [];
+ /**
+ * Lazy O(1) lookup set keyed by lowercased class name; rebuilt when
+ * fq_classlike_names is set / mutated.
+ */
+ private ?array $fq_classlike_names_set = null;
+
/**
* @var array<string>
*/
@@ -570,6 +576,8 @@ class FileFilter
return true;
}
public function allowsClass(string $fq_classlike_name): bool
{
if ($this->fq_classlike_patterns) {
@@ -580,7 +588,14 @@ class FileFilter
}
}
- return in_array(strtolower($fq_classlike_name), $this->fq_classlike_names, true);
+ if ($this->fq_classlike_names_set === null) {
+ $this->fq_classlike_names_set = array_fill_keys(
+ array_map('strtolower', $this->fq_classlike_names),
+ true,
+ );
+ }
+
+ return isset($this->fq_classlike_names_set[strtolower($fq_classlike_name)]);
}
public function allowsMethod(string $method_id): bool