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).
195 lines
7.7 KiB
Java
195 lines
7.7 KiB
Java
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);
|
||
}
|
||
}
|
||
}
|