133 lines
4.4 KiB
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);
|
|
}
|
|
}
|