# 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 ```c /* 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. ```c /* 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.c` in GNU Make mirror (mirror/make on GitHub, ~2024) - `src/implicit.c` in GNU Make Savannah HEAD (`cgit/make.git`, 2025-03-27) Lines 796-797 (Savannah): ```c for (dp = file->deps; dp != 0; dp = dp->next) if (streq (d->name, dep_name (dp))) ```