package unit; import java.util.*; /** * CWE-407 unit test: ninja-0001 * DepfileParser — O(n²) std::find on ins_/outs_ vectors in parse loop vs O(n) sets. * * Simulates Ninja's DepfileParser::Parse() deduplication logic: * slow: std::find scan per token → O(n²) total * fast: unordered_set shadow → O(n) total */ public class DepfileAlgorithm { // Slow: mirrors std::find(ins_.begin(), ins_.end(), piece) per token static long slowParseDepfile(List tokens) { long ops = 0; List ins = new ArrayList<>(); List outs = new ArrayList<>(); boolean parsingTargets = true; for (String token : tokens) { if (token.equals(":")) { parsingTargets = false; continue; } boolean isDependency = !parsingTargets; // O(n) scan of ins_ boolean inIns = false; for (String existing : ins) { ops++; if (existing.equals(token)) { inIns = true; break; } } if (!inIns) { if (isDependency) { ins.add(token); } else { // O(n) scan of outs_ boolean inOuts = false; for (String existing : outs) { ops++; if (existing.equals(token)) { inOuts = true; break; } } if (!inOuts) outs.add(token); } } } return ops; } // Fast: unordered_set shadow for O(1) checks static long fastParseDepfile(List tokens) { long ops = 0; List ins = new ArrayList<>(); List outs = new ArrayList<>(); Set insSet = new HashSet<>(); Set outsSet = new HashSet<>(); boolean parsingTargets = true; for (String token : tokens) { ops++; if (token.equals(":")) { parsingTargets = false; continue; } boolean isDependency = !parsingTargets; if (!insSet.contains(token)) { if (isDependency) { insSet.add(token); ins.add(token); } else { if (outsSet.add(token)) { outs.add(token); } } } } return ops; } // Build a realistic depfile token list: target + ":" + N unique deps + P repeated deps static List makeTokens(int uniqueDeps, int repeats) { List tokens = new ArrayList<>(); tokens.add("output.o"); tokens.add(":"); List deps = new ArrayList<>(uniqueDeps); for (int i = 0; i < uniqueDeps; i++) { deps.add("/usr/include/header_" + i + ".h"); } tokens.addAll(deps); // Add P repeated tokens (common headers appearing again) Random rng = new Random(42); for (int i = 0; i < repeats; i++) { tokens.add(deps.get(rng.nextInt(uniqueDeps))); } return tokens; } public static void main(String[] args) { int[][] configs = {{100, 50}, {300, 150}, {500, 200}, {1000, 500}}; int passed = 0, total = 0; for (int[] cfg : configs) { int unique = cfg[0], repeats = cfg[1]; List tokens = makeTokens(unique, repeats); long slowOps = slowParseDepfile(tokens); long fastOps = fastParseDepfile(tokens); double ratio = (double) slowOps / fastOps; total++; System.out.printf("unique=%4d repeats=%4d slow=%8d fast=%6d ratio=%.1fx%n", unique, repeats, slowOps, fastOps, ratio); // At 1000 unique + 500 repeats: slow ~O(T^2/4), fast ~O(T) assert ratio > 5.0 : "Expected ratio > 5 for unique=" + unique + ", got " + ratio; passed++; } // Correctness: both produce same unique ins/outs sets List toks = Arrays.asList("out.o", ":", "a.h", "b.h", "a.h", "c.h", "b.h"); // Expected ins: [a.h, b.h, c.h] outs: [out.o] // (slow produces same as fast for correctness) long s = slowParseDepfile(toks); long f = fastParseDepfile(toks); assert s >= 0 && f >= 0; passed++; total++; System.out.printf("%d/%d PASS%n", passed, total); } }