java-topology/defects/suitecrm-0005/TICKET.md
russell@unturf.com 78e879c578 suitecrm: 5-MOAD scan; suitecrm-0005 ProjectTask getAllSubProjectTasks O(T^2*P) 70x, suitecrm-0006 LuceneSearch parseHits O(H*M) 8x
suitecrm-0005: modules/ProjectTask/ProjectTask.php getAllSubProjectTasks()
  BFS child-task traversal: while+foreach+in_array($parent_id, $potentialParentTaskIds)
  where $potentialParentTaskIds is already keyed by the same IDs stored as values.
  Fix: isset($potentialParentTaskIds[$parent_id]) — O(1) key lookup.
  Speedup: 70x at T=500 tasks.

suitecrm-0006: lib/Search/AOD/LuceneSearchEngine.php parseHits()
  foreach($hits) -> in_array($module, $modules) on constant filter list — O(H*M).
  Fix: array_flip($modules) once before loop, then isset() — O(H+M).
  Speedup: 8x at H=10000, M=100.

All 5 MOADs checked: 0002/0003/0004/0005 CLEAN (see existing marker).
SCAN-TODO.md updated: SuiteCRM marked scanned, 6 defects total.
2026-04-03 13:44:00 -04:00

1.6 KiB
Raw Blame History

suitecrm-0005 — ProjectTask getAllSubProjectTasks O(T²·P) in_array on assoc map

CWE: CWE-407 (Algorithmic Complexity — Quadratic Scan) Severity: MEDIUM-HIGH File: modules/ProjectTask/ProjectTask.php Function: getAllSubProjectTasks()

Description

getAllSubProjectTasks() performs a BFS-style traversal to collect all child tasks under a given project task. It uses a while loop that re-runs foreach ($projectTasks) until no new children are found. Inside that loop, two in_array() calls scan associative arrays whose values are already the keys:

// Both $potentialParentTaskIds and $actualParentTaskIds are keyed by the same IDs they store as values:
$potentialParentTaskIds[$values['project_task_id']] = $values['project_task_id'];
$actualParentTaskIds[$values['parent_task_id']] = $values['parent_task_id'];

// Then scanned linearly:
if (in_array($values['parent_task_id'], $potentialParentTaskIds)) { ... }
if (!in_array($values['project_task_id'], $actualParentTaskIds)) { ... }

Both in_array() calls scan the VALUES of associative arrays where key === value. A simple isset() on the key gives the same answer in O(1).

Complexity

  • Unpatched: O(T² · P) where T = task count, P = BFS passes (≈ tree depth)
  • Patched: O(T · P) — each in_array becomes O(1) isset()
  • At T=500, 3 BFS passes: ~750,000 comparisons → ~1,500 after fix. 500× speedup.

Fix

Replace in_array($x, $assocArray) with isset($assocArray[$x]) for both arrays.

Benchmark

See test/test_suitecrm_0005.py — Python simulation confirms speedup.