wave17: openssh-0001/2, strongswan-0001, gradle-0002, groovy-0001/2, make-0001, clojure-CLEAN + whitepaper 549/240
This commit is contained in:
parent
0f275c44af
commit
4221966e66
22 changed files with 1776 additions and 4 deletions
85
defects/make/patch/make-0001-implicit-dep-hashset.md
Normal file
85
defects/make/patch/make-0001-implicit-dep-hashset.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# 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)))
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue