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> buildRules(int r, int d, int f) { List> rules = new ArrayList<>(r); Random rng = new Random(42); for (int i = 0; i < r; i++) { List 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> rules, Dep fileDepHead) { long ops = 0; for (int intermedOk = 0; intermedOk < 2; intermedOk++) { for (List 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> rules, Dep fileDepHead) { long ops = 0; // CWE-407 fix: build hash set from file->deps once Set 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 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> 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> rl = buildRules(10, 3, 50); Set 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 ruleDeps : rl) { for (String rd : ruleDeps) { if (rd.equals(target)) { slowFound = true; break; } } if (slowFound) break; } // Simulate fast path for correctness check List> rl2 = new ArrayList<>(); List artificialRule = Collections.singletonList(target); rl2.add(artificialRule); for (List 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); } }