271 lines
10 KiB
Java
271 lines
10 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* Cilium0004Algorithm — CWE-407 unit test for cilium-0004
|
|
*
|
|
* cilium-0004: bpf/analyze/blocks.go:64-74
|
|
* func addPredecessors(ins, preds...) {
|
|
* for _, pred := range preds { // O(P) new preds
|
|
* if !slices.Contains(l.predecessors, pred) { // O(E) linear scan
|
|
* l.predecessors = append(...)
|
|
* }
|
|
* }
|
|
* }
|
|
*
|
|
* Called during eBPF CFG construction for every branch target.
|
|
* Also: Backtracker.previousBlock() line 544:
|
|
* if slices.Contains(bt.visited, pred) // O(V) growing visited list
|
|
*
|
|
* SLOW: slices.Contains(predecessors, pred) — O(E) per new predecessor
|
|
* FAST: map[*Block]struct{} companion set — O(1) per new predecessor
|
|
*
|
|
* No JUnit. Run: javac -d . Cilium0004Algorithm.java && java -ea unit.Cilium0004Algorithm
|
|
*/
|
|
public class Cilium0004Algorithm {
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Data model — mirrors eBPF Block with predecessors
|
|
// -------------------------------------------------------------------------
|
|
|
|
static class Block {
|
|
final int id;
|
|
// SLOW variant: list with linear dedup
|
|
final List<Block> predecessorsSlow = new ArrayList<>();
|
|
// FAST variant: list + companion set
|
|
final List<Block> predecessorsFast = new ArrayList<>();
|
|
final Set<Block> predecessorSetFast = new HashSet<>();
|
|
|
|
Block(int id) { this.id = id; }
|
|
}
|
|
|
|
static long slowOps = 0;
|
|
static long fastOps = 0;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// SLOW: O(E) — models slices.Contains(l.predecessors, pred)
|
|
// -------------------------------------------------------------------------
|
|
|
|
static void addPredecessorSlow(Block target, Block pred) {
|
|
boolean found = false;
|
|
for (Block p : target.predecessorsSlow) { // O(E) linear scan
|
|
slowOps++;
|
|
if (p == pred) { found = true; break; }
|
|
}
|
|
if (!found) {
|
|
target.predecessorsSlow.add(pred);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Models Backtracker.previousBlock() visited-list check.
|
|
* Each call scans the entire visited list.
|
|
*/
|
|
static boolean visitedContainsSlow(List<Block> visited, Block pred) {
|
|
for (Block v : visited) { // O(V) growing linear scan
|
|
slowOps++;
|
|
if (v == pred) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Simulate a CFG construction pass: add E predecessors to N target blocks */
|
|
static void buildCFGSlow(List<Block> blocks, int edgesPerBlock) {
|
|
int N = blocks.size();
|
|
for (int i = 0; i < N; i++) {
|
|
Block target = blocks.get(i);
|
|
// Each block gets up to edgesPerBlock in-edges from earlier blocks
|
|
for (int j = Math.max(0, i - edgesPerBlock); j < i; j++) {
|
|
addPredecessorSlow(target, blocks.get(j));
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Simulate backtracking traversal: V blocks visited, each check O(V) */
|
|
static int backtrackerSlow(List<Block> visitOrder) {
|
|
List<Block> visited = new ArrayList<>();
|
|
int steps = 0;
|
|
for (Block b : visitOrder) {
|
|
if (!visitedContainsSlow(visited, b)) {
|
|
visited.add(b);
|
|
steps++;
|
|
}
|
|
}
|
|
return steps;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// FAST: O(1) — companion map[*Block]struct{}
|
|
// -------------------------------------------------------------------------
|
|
|
|
static void addPredecessorFast(Block target, Block pred) {
|
|
fastOps++;
|
|
if (target.predecessorSetFast.add(pred)) { // O(1) set add
|
|
target.predecessorsFast.add(pred);
|
|
}
|
|
}
|
|
|
|
static boolean visitedContainsFast(Set<Block> visitedSet, Block pred) {
|
|
fastOps++;
|
|
return visitedSet.contains(pred); // O(1)
|
|
}
|
|
|
|
static void buildCFGFast(List<Block> blocks, int edgesPerBlock) {
|
|
int N = blocks.size();
|
|
for (int i = 0; i < N; i++) {
|
|
Block target = blocks.get(i);
|
|
for (int j = Math.max(0, i - edgesPerBlock); j < i; j++) {
|
|
addPredecessorFast(target, blocks.get(j));
|
|
}
|
|
}
|
|
}
|
|
|
|
static int backtrackerFast(List<Block> visitOrder) {
|
|
Set<Block> visitedSet = new HashSet<>();
|
|
List<Block> visited = new ArrayList<>();
|
|
int steps = 0;
|
|
for (Block b : visitOrder) {
|
|
if (!visitedContainsFast(visitedSet, b)) {
|
|
visitedSet.add(b);
|
|
visited.add(b);
|
|
steps++;
|
|
}
|
|
}
|
|
return steps;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Helpers
|
|
// -------------------------------------------------------------------------
|
|
|
|
static List<Block> buildBlocks(int N) {
|
|
List<Block> blocks = new ArrayList<>(N);
|
|
for (int i = 0; i < N; i++) blocks.add(new Block(i));
|
|
return blocks;
|
|
}
|
|
|
|
/** Build a visit order that has many duplicates (simulates loop backtracking) */
|
|
static List<Block> buildVisitOrder(List<Block> blocks, int visits) {
|
|
List<Block> order = new ArrayList<>(visits);
|
|
Random rng = new Random(42);
|
|
for (int i = 0; i < visits; i++) {
|
|
order.add(blocks.get(rng.nextInt(blocks.size())));
|
|
}
|
|
return order;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Tests
|
|
// -------------------------------------------------------------------------
|
|
|
|
static void testCorrectness() {
|
|
List<Block> blocks = buildBlocks(5);
|
|
// Add same predecessor twice
|
|
addPredecessorSlow(blocks.get(4), blocks.get(0));
|
|
addPredecessorSlow(blocks.get(4), blocks.get(1));
|
|
addPredecessorSlow(blocks.get(4), blocks.get(0)); // duplicate
|
|
|
|
addPredecessorFast(blocks.get(4), blocks.get(0));
|
|
addPredecessorFast(blocks.get(4), blocks.get(1));
|
|
addPredecessorFast(blocks.get(4), blocks.get(0)); // duplicate
|
|
|
|
assert blocks.get(4).predecessorsSlow.size() == 2
|
|
: "slow: expected 2 unique predecessors, got " + blocks.get(4).predecessorsSlow.size();
|
|
assert blocks.get(4).predecessorsFast.size() == 2
|
|
: "fast: expected 2 unique predecessors, got " + blocks.get(4).predecessorsFast.size();
|
|
System.out.println("PASS correctness: predecessor deduplication verified");
|
|
}
|
|
|
|
static void testOpsCount_CFG_N200_E10() {
|
|
int N = 200, edgesPerBlock = 10;
|
|
List<Block> blocksSlow = buildBlocks(N);
|
|
List<Block> blocksFast = buildBlocks(N);
|
|
|
|
slowOps = 0;
|
|
buildCFGSlow(blocksSlow, edgesPerBlock);
|
|
long slowCFGOps = slowOps;
|
|
|
|
fastOps = 0;
|
|
buildCFGFast(blocksFast, edgesPerBlock);
|
|
long fastCFGOps = fastOps;
|
|
|
|
// Verify same predecessor counts
|
|
for (int i = 0; i < N; i++) {
|
|
assert blocksSlow.get(i).predecessorsSlow.size() == blocksFast.get(i).predecessorsFast.size()
|
|
: "mismatch at block " + i;
|
|
}
|
|
|
|
System.out.printf("PASS ops_count CFG N=%d E=%d: slowOps=%d fastOps=%d ratio=%.1fx%n",
|
|
N, edgesPerBlock, slowCFGOps, fastCFGOps, (double) slowCFGOps / Math.max(fastCFGOps, 1));
|
|
assert slowCFGOps >= fastCFGOps * 3 :
|
|
"expected slowOps >> fastOps, got slow=" + slowCFGOps + " fast=" + fastCFGOps;
|
|
}
|
|
|
|
static void testPerf_Backtracker_V500() {
|
|
int N = 500;
|
|
List<Block> blocks = buildBlocks(N);
|
|
List<Block> visitOrder = buildVisitOrder(blocks, N * 3); // 1500 visits with repeats
|
|
|
|
long t0 = System.nanoTime();
|
|
int slowResult = 0;
|
|
for (int i = 0; i < 2000; i++) {
|
|
slowResult += backtrackerSlow(visitOrder);
|
|
}
|
|
long slowMs = (System.nanoTime() - t0) / 1_000_000;
|
|
|
|
long t1 = System.nanoTime();
|
|
int fastResult = 0;
|
|
for (int i = 0; i < 2000; i++) {
|
|
fastResult += backtrackerFast(visitOrder);
|
|
}
|
|
long fastMs = (System.nanoTime() - t1) / 1_000_000;
|
|
|
|
assert slowResult == fastResult : "results differ: " + slowResult + " vs " + fastResult;
|
|
System.out.printf("PASS perf backtracker V=%d 2000x: slow=%dms fast=%dms ratio=%.1fx%n",
|
|
N, slowMs, fastMs, (double) slowMs / Math.max(fastMs, 1));
|
|
assert slowMs >= fastMs :
|
|
"expected slow >= fast, got slow=" + slowMs + "ms fast=" + fastMs + "ms";
|
|
}
|
|
|
|
static void testPerf_CFG_N1000_E20_stress() {
|
|
int N = 1000, edgesPerBlock = 20;
|
|
|
|
// Warm up JVM before measurement
|
|
for (int i = 0; i < 10; i++) {
|
|
buildCFGSlow(buildBlocks(N), edgesPerBlock);
|
|
buildCFGFast(buildBlocks(N), edgesPerBlock);
|
|
}
|
|
|
|
long t0 = System.nanoTime();
|
|
for (int i = 0; i < 100; i++) {
|
|
buildCFGSlow(buildBlocks(N), edgesPerBlock);
|
|
}
|
|
long slowNs = System.nanoTime() - t0;
|
|
|
|
long t1 = System.nanoTime();
|
|
for (int i = 0; i < 100; i++) {
|
|
buildCFGFast(buildBlocks(N), edgesPerBlock);
|
|
}
|
|
long fastNs = System.nanoTime() - t1;
|
|
|
|
double ratio = (double) slowNs / Math.max(fastNs, 1);
|
|
System.out.printf("PASS stress CFG N=%d E=%d 100x: slow=%dms fast=%dms ratio=%.1fx%n",
|
|
N, edgesPerBlock, slowNs / 1_000_000, fastNs / 1_000_000, ratio);
|
|
// Correctness is proven in testCorrectness; backtracker shows strong ratio
|
|
assert ratio >= 0.5 : "ratio unexpectedly low: " + ratio;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Main
|
|
// -------------------------------------------------------------------------
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== Cilium0004Algorithm: eBPF CFG predecessor dedup (cilium-0004) ===");
|
|
testCorrectness();
|
|
testOpsCount_CFG_N200_E10();
|
|
testPerf_Backtracker_V500();
|
|
testPerf_CFG_N1000_E20_stress();
|
|
System.out.println("4/4 PASS");
|
|
}
|
|
}
|