package unit; import java.util.*; /** * ninja CWE-407 unit tests * * ninja-0001 — depfile_parser.cc: O(N²) duplicate detection via std::find * src/depfile_parser.cc ~line 338: std::find(ins_.begin(), ins_.end(), piece) * and std::find(outs_.begin(), outs_.end(), piece) on every parsed token. * With a depfile of N=10,000 entries each token scan is O(N) → O(N²) total. * Fix: std::unordered_set ins_seen_ / outs_seen_ for O(1) lookup. * * ninja-0002 — graph.cc: O(M×N) output validation via std::find_if * src/graph.cc ~line 712: std::find_if(edge->outputs_.begin(), ...) called for * each of M depfile output tokens against N edge outputs. * Fix: build std::unordered_set from edge outputs once → O(M+N). */ public class NinjaTest { // ----------------------------------------------------------------------- // ninja-0001: depfile duplicate detection // ----------------------------------------------------------------------- /** * Slow path: models std::find(ins_.begin(), ins_.end(), piece). * Returns total comparison operations performed across N tokens. * * For each new token we scan all previously accepted tokens to check for * duplicates → O(k) per token where k is current list size → O(N²) total. */ static long depfileDedupSlow(List tokens) { List ins = new ArrayList<>(); long ops = 0; for (String token : tokens) { // models: std::find(ins_.begin(), ins_.end(), piece) for (String existing : ins) { ops++; if (existing.equals(token)) break; } if (!ins.contains(token)) { ins.add(token); } } return ops; } /** * Fast path: models unordered_set ins_seen_.find(piece_str). * Returns total operations (each set operation counted as 1). */ static long depfileDedupFast(List tokens) { Set insSeen = new HashSet<>(); List ins = new ArrayList<>(); long ops = 0; for (String token : tokens) { ops++; // O(1) hash lookup if (!insSeen.contains(token)) { insSeen.add(token); ins.add(token); } } return ops; } // ----------------------------------------------------------------------- // ninja-0002: depfile output validation // ----------------------------------------------------------------------- /** * Slow path: models std::find_if(edge->outputs_.begin(), edge->outputs_.end(), m) * called for each of M depfile output tokens against N edge outputs. * O(M×N) total. */ static long outputValidationSlow(List depfileOuts, List edgeOutputs) { long ops = 0; for (String depOut : depfileOuts) { // models: std::find_if scanning all edge outputs for (String edgeOut : edgeOutputs) { ops++; if (edgeOut.equals(depOut)) break; } } return ops; } /** * Fast path: build unordered_set from edge outputs once, then O(1) per * depfile output token. O(M+N) total. */ static long outputValidationFast(List depfileOuts, List edgeOutputs) { long ops = 0; // Build set: O(N) Set outputPaths = new HashSet<>(edgeOutputs.size() * 2); for (String o : edgeOutputs) { outputPaths.add(o); ops++; } // Validate: O(M) for (String depOut : depfileOuts) { ops++; // O(1) hash lookup outputPaths.contains(depOut); } return ops; } // ----------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------- /** Build N unique dependency tokens like "obj/foo_0000.o.d". */ static List buildDepTokens(int n, int duplicatePct) { List tokens = new ArrayList<>(n); int uniqueCount = n * (100 - duplicatePct) / 100; if (uniqueCount < 1) uniqueCount = 1; for (int i = 0; i < n; i++) { // Every (100/duplicatePct)-th token is a duplicate of a previous one int idx = (duplicatePct > 0 && i % (100 / duplicatePct) == 0 && i > 0) ? (i % uniqueCount) : i; tokens.add("obj/source_" + String.format("%06d", idx) + ".o"); } return tokens; } /** Build M depfile output tokens all present in edge outputs. */ static List buildOutputTokens(int m) { List outs = new ArrayList<>(m); for (int i = 0; i < m; i++) outs.add("out/target_" + String.format("%04d", i) + ".o"); return outs; } // ----------------------------------------------------------------------- // main // ----------------------------------------------------------------------- public static void main(String[] args) { boolean allPass = true; // --- ninja-0001: depfile dedup --- System.out.println("ninja-0001 CWE-407: depfile_parser.cc duplicate detection O(N²) vs O(N)"); System.out.println("========================================================================="); System.out.println(" N=tokens, 10% duplicates; ops = comparison operations counted"); System.out.printf(" %-8s %-14s %-12s %s%n", "N", "slow(O(N²))", "fast(O(N))", "speedup"); int[] nValues = { 100, 500, 1000 }; for (int n : nValues) { List tokens = buildDepTokens(n, 10); long slow = depfileDedupSlow(tokens); long fast = depfileDedupFast(tokens); double ratio = (double) slow / fast; System.out.printf(" %-8d %-14d %-12d %.1fx%n", n, slow, fast, ratio); // At N=1000, 10% duplicates: slow should be roughly O(N²/2) >> O(N) // Require at least 10x speedup at N=1000 if (n == 1000 && ratio < 10.0) { System.out.println(" FAIL: expected speedup >= 10x at N=1000, got " + ratio); allPass = false; } } // --- ninja-0002: output validation --- System.out.println(); System.out.println("ninja-0002 CWE-407: graph.cc output validation O(M×N) vs O(M+N)"); System.out.println("================================================================="); System.out.println(" M=depfile outputs, N=edge outputs; ops counted"); System.out.printf(" %-6s %-6s %-16s %-12s %s%n", "M", "N", "slow(O(M×N))", "fast(O(M+N))", "speedup"); int[][] params = { {50, 50}, {200, 200}, {500, 500} }; for (int[] p : params) { int m = p[0], n = p[1]; List depfileOuts = buildOutputTokens(m); List edgeOutputs = buildOutputTokens(n); long slow = outputValidationSlow(depfileOuts, edgeOutputs); long fast = outputValidationFast(depfileOuts, edgeOutputs); double ratio = (double) slow / fast; System.out.printf(" %-6d %-6d %-16d %-12d %.1fx%n", m, n, slow, fast, ratio); // At M=N=500: slow = 500*500 = 250,000 ops; fast = 1000 ops → 250x if (m == 500 && ratio < 50.0) { System.out.println(" FAIL: expected speedup >= 50x at M=N=500, got " + ratio); allPass = false; } } System.out.println(); if (allPass) { System.out.println("PASS"); } else { System.out.println("FAIL"); System.exit(1); } } }