java-topology/defects/moby/unit/TweakCapabilitiesAlgorithm.java

173 lines
6.9 KiB
Java

package unit;
import java.util.*;
/**
* CWE-407 unit test: moby TweakCapabilities
* File: daemon/pkg/oci/caps/utils.go — TweakCapabilities
*
* Slow: for each cap in allCaps, scan capDrop slice → O(n²)
* Fast: build a HashSet from capDrop first → O(n)
*
* Run: javac -d . TweakCapabilitiesAlgorithm.java && java -ea unit.TweakCapabilitiesAlgorithm
*/
public class TweakCapabilitiesAlgorithm {
// ── slow implementation (mirrors defective Go code) ──────────────────────
static class SlowTweak {
final long ops;
final List<String> result;
SlowTweak(List<String> allCaps, List<String> capDrop) {
long count = 0;
List<String> out = new ArrayList<>();
for (String c : allCaps) {
// slices.Contains(capDrop, c) — linear scan
boolean found = false;
for (String d : capDrop) {
count++;
if (d.equals(c)) { found = true; break; }
}
if (!found) out.add(c);
}
this.ops = count;
this.result = out;
}
}
// ── fast implementation (proposed fix) ───────────────────────────────────
static class FastTweak {
final long ops;
final List<String> result;
FastTweak(List<String> allCaps, List<String> capDrop) {
long count = 0;
// Build drop set — O(|capDrop|) once
Set<String> dropSet = new HashSet<>(capDrop.size() * 2);
for (String d : capDrop) { count++; dropSet.add(d); }
List<String> out = new ArrayList<>();
for (String c : allCaps) {
count++; // O(1) map lookup
if (!dropSet.contains(c)) out.add(c);
}
this.ops = count;
this.result = out;
}
}
// ── Node/Result types for test scaffolding ────────────────────────────────
static class Node {
final String name;
Node(String name) { this.name = name; }
}
static class Result {
final long slowOps;
final long fastOps;
final List<String> slowResult;
final List<String> fastResult;
Result(long slowOps, long fastOps, List<String> slowResult, List<String> fastResult) {
this.slowOps = slowOps;
this.fastOps = fastOps;
this.slowResult = slowResult;
this.fastResult = fastResult;
}
}
// ── test helpers ──────────────────────────────────────────────────────────
static List<String> makeCaps(int n) {
List<String> caps = new ArrayList<>(n);
for (int i = 0; i < n; i++) caps.add("CAP_" + i);
return caps;
}
static Result run(int nAllCaps, int nDrop) {
List<String> allCaps = makeCaps(nAllCaps);
// Drop every other cap to maximise scan work
List<String> capDrop = new ArrayList<>();
for (int i = 0; i < nDrop; i++) capDrop.add("CAP_" + i);
SlowTweak slow = new SlowTweak(allCaps, capDrop);
FastTweak fast = new FastTweak(allCaps, capDrop);
return new Result(slow.ops, fast.ops, slow.result, fast.result);
}
// ── tests ─────────────────────────────────────────────────────────────────
static int passed = 0;
static int total = 0;
static void test(String name, boolean condition) {
total++;
if (condition) {
passed++;
System.out.println("PASS: " + name);
} else {
System.out.println("FAIL: " + name);
}
}
public static void main(String[] args) {
// T1: Realistic Linux cap count — N=41 (all caps), drop=41
{
Result r = run(41, 41);
// Slow should be O(n^2): up to 41*41 = 1681 ops
// Fast should be O(n): ~41+41 = 82 ops
test("T1-slow-is-quadratic [N=41,drop=41]",
r.slowOps > r.fastOps * 5); // slow must be substantially more than fast
test("T1-fast-is-linear [N=41,drop=41]",
r.fastOps <= 41 + 41 + 5); // build-set + lookup + small constant
double speedup = (double) r.slowOps / r.fastOps;
test("T1-speedup>=10x [N=41]", speedup >= 10.0);
System.out.printf(" slow=%d ops, fast=%d ops, speedup=%.1fx%n",
r.slowOps, r.fastOps, speedup);
// Results must match
List<String> ss = new ArrayList<>(r.slowResult);
List<String> fs = new ArrayList<>(r.fastResult);
Collections.sort(ss); Collections.sort(fs);
test("T1-results-match", ss.equals(fs));
}
// T2: Expanded future capability set — N=200
{
Result r = run(200, 200);
double speedup = (double) r.slowOps / r.fastOps;
test("T2-slow-is-quadratic [N=200]",
r.slowOps > r.fastOps * 30); // slow must be substantially worse than fast
test("T2-fast-is-linear [N=200]",
r.fastOps <= 200 + 200 + 5);
test("T2-speedup>=50x [N=200]", speedup >= 50.0);
System.out.printf(" slow=%d ops, fast=%d ops, speedup=%.1fx%n",
r.slowOps, r.fastOps, speedup);
List<String> ss = new ArrayList<>(r.slowResult);
List<String> fs = new ArrayList<>(r.fastResult);
Collections.sort(ss); Collections.sort(fs);
test("T2-results-match", ss.equals(fs));
}
// T3: Default path — basics=14, drop=5 (typical docker run --cap-drop)
{
Result r = run(14, 5);
test("T3-slow-ops>fast-ops [N=14,drop=5]", r.slowOps > r.fastOps);
double speedup = (double) r.slowOps / r.fastOps;
System.out.printf(" slow=%d ops, fast=%d ops, speedup=%.1fx%n",
r.slowOps, r.fastOps, speedup);
List<String> ss = new ArrayList<>(r.slowResult);
List<String> fs = new ArrayList<>(r.fastResult);
Collections.sort(ss); Collections.sort(fs);
test("T3-results-match", ss.equals(fs));
}
// T4: Empty drop list — no caps dropped, fast still correct
{
Result r = run(41, 0);
test("T4-empty-drop-result-size", r.fastResult.size() == 41);
test("T4-empty-drop-results-match", r.slowResult.equals(r.fastResult));
}
System.out.println();
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}