java-topology/tools/tickets/defects/composer-0002.md
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

2.7 KiB
Raw Blame History

id repo severity status created
composer-0002 composer/composer MEDIUM PATCHED 2026-03-23

Defect

File: src/Composer/Repository/InstalledRepository.php:128,154,167,180 Pattern: in_array($link->getTarget(), $packagesInTree) / in_array($link->getSource(), $packagesInTree) inside nested foreach loops Complexity: O(|packages| × |links| × |packagesInTree|) per getDependents() call Language: PHP

Description

InstalledRepository::getDependents() traverses the installed package dependency graph to find all packages that (directly or transitively) depend on a given package. It maintains $packagesInTree — an array that accumulates visited package names — and checks membership with in_array() (O(n) linear scan) before recursing.

The traversal has the following structure:

foreach ($this->getPackages() as $package) {              // O(N) packages
    foreach ($links as $link) {                            // O(L) links per package
        foreach ($needles as $needle) {                    // O(K) needles
            if (in_array($source, $packagesInTree)) {      // O(|tree|) — O(N) worst case

In the worst case (deep dependency graph), this is O(N² × L × K) where N is installed package count, L is average links per package, K is the needle count.

$packagesInTree grows as packages are added: $packagesInTree[] = $link->getSource(). By the time N packages have been visited, each in_array call scans up to N entries.

This fires on composer show --tree, composer why, composer depends --tree, and the BSP server's dependency tree protocol response. For typical medium-size projects (100-200 packages), this produces 10,00040,000 string comparisons per invocation.

Fix

Replace: in_array($pkg, $packagesInTree) array scan With: $packagesInTreeSet[$pkg] = true associative array; check isset($packagesInTreeSet[$pkg])

All four call sites (lines 128, 154, 167, 180) use the same $packagesInTree variable and can be fixed together in a single patch. The fix requires threading a $packagesInTreeSet hash alongside the existing $packagesInTree array (or replacing the array entirely with a keyed hash, using array_values() at the return boundary if caller expects a list).

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 show --tree on project with N transitive deps (in defects/composer/integration/)
  • Benchmark — timing before/after (in defects/composer/bench/)
  • White paper section — whitepaper/vectors/tool-harness/composer.rst