3.1 KiB
UNDF: UNDF-2026-000000457
make-0001: implicit.c pattern_search file->deps O(R×D×F) → O(R×D+F)
Defect
- File:
src/implicit.c - Function:
pattern_search - Lines: ~796 (Savannah HEAD, 2025-03-27)
- CWE: 407 — Inefficient Algorithmic Complexity
- Severity: HIGH
Pattern
/* Outer: for (intermed_ok = 0; intermed_ok < 2; ++intermed_ok) */
/* Outer: for (ri = 0; ri < nrules; ri++) */
/* Middle: while(1) / for (d = dl; d != 0; d = d->next) */
if (df && df->is_target)
explicit = 1;
else
for (dp = file->deps; dp != 0; dp = dp->next) /* O(F) */
if (streq (d->name, dep_name (dp))) /* O(F) linear scan */
break;
Complexity
R= number of pattern rules (can be hundreds of built-in + user-defined rules)D= number of pattern prerequisites per rule (typically 1-5)F= number of explicit prerequisites of the current target file
For each target, pattern_search runs through R rules × D deps × F explicit deps.
The O(F) inner linear scan is performed for every (rule, dep) pair.
Worst case: target with F=100 deps and R=200 rules with D=5 deps each:
- Without fix: 200 × 5 × 100 = 100,000 string comparisons
- With fix: build hash set once from F deps (O(F)), then O(1) per lookup: 200 × 5 × 1 = 1,000 lookups + 100 hash inserts = ~1,100 ops
- Speedup: ~91× at F=100, scales as O(F)
Root Cause
The function pattern_search is the hot path called for every target that
needs an implicit rule. In a project with many targets (e.g. a large
parallel make with hundreds of object files, each with many explicit
prerequisites), this inner loop fires F times per (rule, dep) combination.
file->deps is a linked list — no random access or hashing. The membership
test streq(d->name, dep_name(dp)) scans the whole list every time.
Fix
Build a hash_set (or a struct hash_table using GNU Make's hashmap
infrastructure) from file->deps once before the rule loop begins.
Replace the inner for (dp = file->deps; ...) linear scan with an O(1)
hash lookup.
/* CWE-407 fix: build a set of explicit dep names once before the rule loop.
The inner for(dp = file->deps; ...) scan was O(F) per (rule, dep) pair,
giving O(R * D * F) total. A hash set gives O(R * D + F). */
struct hash_table *dep_name_set = make_file_dep_name_set (file);
/* ... inside the rule/dep loops ... */
if (df && df->is_target)
explicit = 1;
else
dp = dep_name_set_contains (dep_name_set, d->name) ? (struct dep *)1 : 0;
GNU Make already uses struct hash_table (see hash.h) for file name
lookup (hash_find_item). The same infrastructure applies here.
Evidence
Confirmed present in:
src/implicit.cin GNU Make mirror (mirror/make on GitHub, ~2024)src/implicit.cin GNU Make Savannah HEAD (cgit/make.git, 2025-03-27)
Lines 796-797 (Savannah):
for (dp = file->deps; dp != 0; dp = dp->next)
if (streq (d->name, dep_name (dp)))