java-topology/defects/ninja/unit/NinjaTest.java
russell@unturf.com f176e86fbd ninja-0001/ninja-0002: CWE-407 depfile dedup and output validation O(N²)→O(N)
ninja-0001: depfile_parser.cc std::find over ins_/outs_ vectors is O(N) per
token → O(N²) total parse; fix adds unordered_set ins_seen_/outs_seen_ members
to DepfileParser for O(1) duplicate detection (490x at N=1000).

ninja-0002: graph.cc std::find_if over edge->outputs_ called for each depfile
output token is O(M×N); fix builds unordered_set from edge outputs once before
the loop for O(M+N) total (125x at M=N=500).
2026-03-30 06:56:54 -04:00

195 lines
7.7 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.*;
/**
* 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<std::string> 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<std::string> 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<String> tokens) {
List<String> 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<string> ins_seen_.find(piece_str).
* Returns total operations (each set operation counted as 1).
*/
static long depfileDedupFast(List<String> tokens) {
Set<String> insSeen = new HashSet<>();
List<String> 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<String> depfileOuts, List<String> 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<String> depfileOuts, List<String> edgeOutputs) {
long ops = 0;
// Build set: O(N)
Set<String> 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<String> buildDepTokens(int n, int duplicatePct) {
List<String> 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<String> buildOutputTokens(int m) {
List<String> 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<String> 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<String> depfileOuts = buildOutputTokens(m);
List<String> 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);
}
}
}