java-topology/defects/make/patch/make-0001-implicit-dep-hashset.md

86 lines
3.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)))
```