wave17: openssh-0001/2, strongswan-0001, gradle-0002, groovy-0001/2, make-0001, clojure-CLEAN + whitepaper 549/240

This commit is contained in:
russell@unturf.com 2026-03-27 19:55:52 -04:00
parent 0f275c44af
commit 4221966e66
22 changed files with 1776 additions and 4 deletions

View file

@ -0,0 +1,180 @@
package unit;
import java.util.*;
/**
* Unit test for make-0001: GNU Make pattern_search file->deps O(R×D×F) linear scan.
*
* Models the pattern in src/implicit.c (pattern_search):
*
* for (intermed_ok = 0; intermed_ok < 2; ++intermed_ok)
* for (ri = 0; ri < nrules; ri++)
* [while(1) / for d in rule_deps]
* for (dp = file->deps; dp != 0; dp = dp->next) // O(F) linear scan
* if (streq(d->name, dep_name(dp))) break; // CWE-407 defect
*
* SLOW: LinkedList.contains() O(F) per (rule, dep) pair O(R×D×F) total
* FAST: HashSet.contains() O(1) per lookup O(R×D + F) total
*/
public class MakeAlgorithm {
// -----------------------------------------------------------------------
// Test parameters
// -----------------------------------------------------------------------
private static final int N_FILE_DEPS = 500; // F: explicit deps per target
private static final int N_RULES = 200; // R: pattern rules
private static final int N_RULE_DEPS = 5; // D: prereqs per rule (avg)
private static final int REQUIRED_RATIO = 5;
// -----------------------------------------------------------------------
// Simulate file->deps as a linked list (as in GNU Make)
// -----------------------------------------------------------------------
static class Dep {
final String name;
Dep next;
Dep(String name) { this.name = name; }
}
/** Build a linked list of F dependency names. */
static Dep buildDepList(int f) {
Dep head = null;
for (int i = f - 1; i >= 0; i--) {
Dep d = new Dep("dep_" + i);
d.next = head;
head = d;
}
return head;
}
/** Build R pattern rules, each with D deps (some match file->deps). */
static List<List<String>> buildRules(int r, int d, int f) {
List<List<String>> rules = new ArrayList<>(r);
Random rng = new Random(42);
for (int i = 0; i < r; i++) {
List<String> ruleDeps = new ArrayList<>(d);
for (int j = 0; j < d; j++) {
// Mix of matching and non-matching deps
if (rng.nextInt(3) == 0 && f > 0)
ruleDeps.add("dep_" + rng.nextInt(f)); // matches file->deps
else
ruleDeps.add("rule_dep_" + i + "_" + j); // doesn't match
}
rules.add(ruleDeps);
}
return rules;
}
// -----------------------------------------------------------------------
// SLOW: O(R×D×F) linear scan of file->deps for each (rule, dep)
// -----------------------------------------------------------------------
static long slowPatternSearch(List<List<String>> rules, Dep fileDepHead) {
long ops = 0;
for (int intermedOk = 0; intermedOk < 2; intermedOk++) {
for (List<String> ruleDeps : rules) {
for (String ruleDep : ruleDeps) {
// Simulate: for (dp = file->deps; dp != 0; dp = dp->next)
// if (streq(ruleDep, dep_name(dp))) break;
boolean found = false;
for (Dep dp = fileDepHead; dp != null; dp = dp.next) {
ops++;
if (dp.name.equals(ruleDep)) {
found = true;
break;
}
}
}
}
}
return ops;
}
// -----------------------------------------------------------------------
// FAST: O(R×D + F) build HashSet once, then O(1) lookup per (rule, dep)
// -----------------------------------------------------------------------
static long fastPatternSearch(List<List<String>> rules, Dep fileDepHead) {
long ops = 0;
// CWE-407 fix: build hash set from file->deps once
Set<String> depNameSet = new HashSet<>();
for (Dep dp = fileDepHead; dp != null; dp = dp.next) {
ops++; // building the set
depNameSet.add(dp.name);
}
for (int intermedOk = 0; intermedOk < 2; intermedOk++) {
for (List<String> ruleDeps : rules) {
for (String ruleDep : ruleDeps) {
ops++; // O(1) hash lookup
depNameSet.contains(ruleDep);
}
}
}
return ops;
}
// -----------------------------------------------------------------------
// Test runner
// -----------------------------------------------------------------------
static boolean runTest(String name, int fileDeps, int rules, int ruleDeps) {
Dep fileDepHead = buildDepList(fileDeps);
List<List<String>> ruleList = buildRules(rules, ruleDeps, fileDeps);
long slowOps = slowPatternSearch(ruleList, fileDepHead);
long fastOps = fastPatternSearch(ruleList, fileDepHead);
double ratio = (double) slowOps / fastOps;
boolean pass = ratio >= REQUIRED_RATIO;
System.out.printf(" %-40s slow=%,7d fast=%,7d ratio=%5.1fx %s%n",
name, slowOps, fastOps, ratio, pass ? "PASS" : "FAIL");
return pass;
}
public static void main(String[] args) {
System.out.println("make-0001: GNU Make pattern_search O(R×D×F) dep linear scan");
System.out.println("=".repeat(78));
int pass = 0, total = 0;
// ---- correctness: results should agree ----
{
Dep hd = buildDepList(50);
List<List<String>> rl = buildRules(10, 3, 50);
Set<String> depSet = new HashSet<>();
for (Dep dp = hd; dp != null; dp = dp.next) depSet.add(dp.name);
boolean slowFound = false, fastFound = false;
String target = "dep_25";
for (List<String> ruleDeps : rl) {
for (String rd : ruleDeps) {
if (rd.equals(target)) { slowFound = true; break; }
}
if (slowFound) break;
}
// Simulate fast path for correctness check
List<List<String>> rl2 = new ArrayList<>();
List<String> artificialRule = Collections.singletonList(target);
rl2.add(artificialRule);
for (List<String> ruleDeps : rl2)
for (String rd : ruleDeps)
if (depSet.contains(rd)) { fastFound = true; break; }
// target is dep_25 which IS in depSet
assert fastFound : "fast path should find dep_25 in depSet";
}
// ---- performance tests ----
System.out.println();
total++; if (runTest("F=500 R=200 D=5 (baseline)", N_FILE_DEPS, N_RULES, N_RULE_DEPS)) pass++;
total++; if (runTest("F=100 R=100 D=3 (small)", 100, 100, 3)) pass++;
total++; if (runTest("F=1000 R=300 D=5 (large)", 1000, 300, 5)) pass++;
total++; if (runTest("F=200 R=400 D=8 (many rules)",200, 400, 8)) pass++;
total++; if (runTest("F=800 R=150 D=4 (heavy deps)",800, 150, 4)) pass++;
System.out.println();
System.out.printf("%d/%d PASS%n", pass, total);
if (pass != total) System.exit(1);
}
}