180 lines
6.1 KiB
Java
180 lines
6.1 KiB
Java
package unit;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.HashMap;
|
||
import java.util.List;
|
||
|
||
/**
|
||
* CWE-407 unit test: asterisk-0003
|
||
*
|
||
* Models cdr_object_create_public_records() variable merge.
|
||
* SLOW: nested list traversal with strcasecmp — O(B × V)
|
||
* FAST: hash set membership check — O(B + V)
|
||
*/
|
||
public class CdrVarMergeAlgorithm {
|
||
|
||
static long slowOps = 0;
|
||
static long fastOps = 0;
|
||
|
||
/** Simulated variable: name -> value pair */
|
||
static class Var {
|
||
String name;
|
||
String value;
|
||
Var(String name, String value) { this.name = name; this.value = value; }
|
||
}
|
||
|
||
/**
|
||
* SLOW path: nested linear scan to deduplicate variables.
|
||
* Models the defective AST_LIST_TRAVERSE inside AST_LIST_TRAVERSE.
|
||
*
|
||
* @param partyAVars already in varshead
|
||
* @param partyBVars party_b variables to merge in
|
||
* @return merged list
|
||
*/
|
||
static List<Var> mergeVarsSlow(List<Var> partyAVars, List<Var> partyBVars) {
|
||
List<Var> varshead = new ArrayList<>(partyAVars);
|
||
|
||
for (Var bVar : partyBVars) { // outer: B iterations
|
||
boolean found = false;
|
||
for (Var existing : varshead) { // inner: V iterations — O(B×V)
|
||
slowOps++;
|
||
if (bVar.name.equalsIgnoreCase(existing.name)) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) {
|
||
varshead.add(new Var(bVar.name, bVar.value));
|
||
}
|
||
}
|
||
return varshead;
|
||
}
|
||
|
||
/**
|
||
* FAST path: hash set membership check — O(B + V).
|
||
* Fix: build a HashMap of existing names first, then check in O(1).
|
||
*
|
||
* @param partyAVars already in varshead
|
||
* @param partyBVars party_b variables to merge in
|
||
* @return merged list
|
||
*/
|
||
static List<Var> mergeVarsFast(List<Var> partyAVars, List<Var> partyBVars) {
|
||
List<Var> varshead = new ArrayList<>(partyAVars);
|
||
HashMap<String, Boolean> existingNames = new HashMap<>();
|
||
|
||
for (Var v : partyAVars) { // O(V) build
|
||
fastOps++;
|
||
existingNames.put(v.name.toLowerCase(), Boolean.TRUE);
|
||
}
|
||
|
||
for (Var bVar : partyBVars) { // outer: B iterations
|
||
fastOps++; // O(1) lookup
|
||
if (!existingNames.containsKey(bVar.name.toLowerCase())) {
|
||
varshead.add(new Var(bVar.name, bVar.value));
|
||
existingNames.put(bVar.name.toLowerCase(), Boolean.TRUE);
|
||
}
|
||
}
|
||
return varshead;
|
||
}
|
||
|
||
static boolean runTest(int numVarsA, int numVarsB, int overlap) {
|
||
// Build party_a vars: var_a_0 ... var_a_(numVarsA-1)
|
||
List<Var> partyA = new ArrayList<>();
|
||
for (int i = 0; i < numVarsA; i++) {
|
||
partyA.add(new Var("var_a_" + i, "val_a_" + i));
|
||
}
|
||
|
||
// Build party_b vars: first 'overlap' vars share names with party_a
|
||
// rest are unique to party_b
|
||
List<Var> partyB = new ArrayList<>();
|
||
for (int i = 0; i < overlap; i++) {
|
||
partyB.add(new Var("var_a_" + i, "val_b_" + i)); // duplicate
|
||
}
|
||
for (int i = 0; i < numVarsB - overlap; i++) {
|
||
partyB.add(new Var("var_b_" + i, "val_b_" + i)); // unique
|
||
}
|
||
|
||
long slowBefore = slowOps;
|
||
long fastBefore = fastOps;
|
||
|
||
List<Var> slowResult = mergeVarsSlow(partyA, partyB);
|
||
List<Var> fastResult = mergeVarsFast(partyA, partyB);
|
||
|
||
long slowCount = slowOps - slowBefore;
|
||
long fastCount = fastOps - fastBefore;
|
||
|
||
// Both should produce same merged count: numVarsA + (numVarsB - overlap) unique vars
|
||
int expectedSize = numVarsA + (numVarsB - overlap);
|
||
if (slowResult.size() != expectedSize) {
|
||
System.out.println("FAIL: slow result size " + slowResult.size() + " expected " + expectedSize);
|
||
return false;
|
||
}
|
||
if (fastResult.size() != expectedSize) {
|
||
System.out.println("FAIL: fast result size " + fastResult.size() + " expected " + expectedSize);
|
||
return false;
|
||
}
|
||
|
||
// Slow ops should be at least numVarsB (inner loop on each), approx numVarsB * numVarsA
|
||
// Fast ops should be at most numVarsA + numVarsB
|
||
double ratio = (double) slowCount / (double) fastCount;
|
||
|
||
System.out.printf(" N=%d B=%d overlap=%d | slowOps=%d fastOps=%d ratio=%.1fx%n",
|
||
numVarsA, numVarsB, overlap, slowCount, fastCount, ratio);
|
||
|
||
if (ratio < 5.0) {
|
||
System.out.printf("FAIL: ratio %.1fx < 5x threshold%n", ratio);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
int pass = 0;
|
||
int fail = 0;
|
||
|
||
System.out.println("=== asterisk-0003: CDR Variable Merge O(B×V) ===");
|
||
System.out.println();
|
||
|
||
// Test cases: (numVarsA, numVarsB, overlap)
|
||
int[][] tests = {
|
||
{20, 20, 5},
|
||
{50, 50, 10},
|
||
{100, 100, 20},
|
||
{200, 200, 50},
|
||
{500, 500, 100},
|
||
};
|
||
|
||
for (int[] t : tests) {
|
||
slowOps = 0;
|
||
fastOps = 0;
|
||
boolean ok = runTest(t[0], t[1], t[2]);
|
||
if (ok) { pass++; } else { fail++; }
|
||
}
|
||
|
||
System.out.println();
|
||
|
||
// Verify quadratic growth in slow path
|
||
System.out.println("Quadratic growth verification (slow path):");
|
||
for (int n : new int[]{10, 50, 100, 200}) {
|
||
slowOps = 0;
|
||
fastOps = 0;
|
||
mergeVarsSlow(buildVarList("a", n), buildVarList("b", n));
|
||
System.out.printf(" N=%d slow_ops=%d (expected ~%d quadratic)%n",
|
||
n, slowOps, n * n);
|
||
}
|
||
|
||
System.out.println();
|
||
System.out.printf("%d/%d PASS%n", pass, pass + fail);
|
||
if (fail > 0) {
|
||
System.exit(1);
|
||
}
|
||
}
|
||
|
||
static List<Var> buildVarList(String prefix, int n) {
|
||
List<Var> list = new ArrayList<>();
|
||
for (int i = 0; i < n; i++) {
|
||
list.add(new Var(prefix + "_var_" + i, "val_" + i));
|
||
}
|
||
return list;
|
||
}
|
||
}
|