java-topology/defects/make/unit/MakeAlgorithm.java

180 lines
7.1 KiB
Java
Raw 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.

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);
}
}