java-topology/defects/ninja/unit/DepfileAlgorithm.java

133 lines
4.4 KiB
Java

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<String> tokens) {
long ops = 0;
List<String> ins = new ArrayList<>();
List<String> 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<String> tokens) {
long ops = 0;
List<String> ins = new ArrayList<>();
List<String> outs = new ArrayList<>();
Set<String> insSet = new HashSet<>();
Set<String> 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<String> makeTokens(int uniqueDeps, int repeats) {
List<String> tokens = new ArrayList<>();
tokens.add("output.o");
tokens.add(":");
List<String> 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<String> 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<String> 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);
}
}