B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
2.4 KiB
| id | repo | severity | status | created |
|---|---|---|---|---|
| composer-0001 | composer/composer | MEDIUM | PATCHED | 2026-03-23 |
Defect
File: src/Composer/Repository/RepositoryUtils.php:46
Pattern: !in_array($candidate, $bucket, true) inside foreach ($packages as $candidate)
Complexity: O(N²) per call + O(N³) with recursive transitive closure
Language: PHP
Description
filterRequiredPackages() computes the set of packages from $packages that are
transitively required by $requirer. It does this by iterating over all candidates
in an outer foreach ($packages as $candidate) loop and checking !in_array($candidate, $bucket, true)
to avoid re-adding packages already in the result set.
$bucket is a PHP array (not a Set or hash map). in_array() on a PHP array is O(n)
linear scan. As packages are found they are appended to $bucket with $bucket[],
so $bucket grows from 0 to N. The total cost of all membership checks in a single
call is O(N²) in the number of installed packages.
The function is also called recursively for each transitive dependency:
$bucket = self::filterRequiredPackages($packages, $candidate, false, $bucket).
In the worst case (long dependency chain), the total complexity is O(N³) where N
is the package count.
Triggered by: composer show, composer why, composer depends, and any
Composer command that computes transitive package sets for display. Not in the
SAT/CDCL installation solver.
For a project with 200 packages (typical Symfony or Laravel application):
O(200²) = 40,000 membership comparisons per composer why invocation.
Fix
Replace: $bucket as PHP array with membership via in_array()
With: $bucket as PHP associative array (hash map) keyed on package name/identity;
or use SplObjectStorage for object-identity lookup in O(1).
// Before
if (!in_array($candidate, $bucket, true)) {
$bucket[] = $candidate;
// After
if (!isset($bucketMap[spl_object_id($candidate)])) {
$bucketMap[spl_object_id($candidate)] = true;
$bucket[] = $candidate;
Work required
- Patch in
defects/composer/patch/ - Unit test — asserts operation counts before/after at N=50,100,200 packages (in
defects/composer/unit/) - Integration test —
composer whyon project with N transitive deps (indefects/composer/integration/) - Benchmark — timing before/after (in
defects/composer/bench/) - White paper section —
whitepaper/vectors/tool-harness/composer.rst