java-topology/defects/crystal/unit/CrystalCompareStrictnessAlgorithm.java

149 lines
5.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.*;
/**
* CWE-407 unit test: Crystal compare_strictness named arg O(N²) lookup
*
* Models Crystal's DefWithMetadata#compare_strictness where:
* - Each def has a list of named parameters (args with external names)
* - For each named arg in self, we search for it in other's named args
* - This is O(N²) where N = number of named parameters
*
* The defect is in restrictions.cr:94,104,141,148 — called from
* add_def in types.cr:919 inside a loop over existing overloads.
*
* Test measures ops count at N=800 (named args per def).
*/
public class CrystalCompareStrictnessAlgorithm {
// ---- Defective version: Array#any? / Array#find (linear scan) -------------
/**
* For each name in selfArgs, scan otherArgs linearly.
* Returns total comparisons performed.
*/
static long defectiveCompareNamedArgs(List<String> selfArgs, List<String> otherArgs) {
long ops = 0;
for (String selfName : selfArgs) {
// models: other_named_args.try &.any?(&.external_name.== self_arg.external_name)
for (String otherName : otherArgs) {
ops++;
if (selfName.equals(otherName)) break;
}
}
return ops;
}
/**
* Model add_def loop calling compare_strictness for D overloads.
* Total cost: O(D × N²)
*/
static long defectiveAddDef(int numOverloads, List<String> selfArgs, List<String> otherArgs) {
long totalOps = 0;
for (int i = 0; i < numOverloads; i++) {
totalOps += defectiveCompareNamedArgs(selfArgs, otherArgs);
}
return totalOps;
}
// ---- Fixed version: HashMap O(1) lookup -----------------------------------
/**
* Build a map from name -> arg once, then all lookups are O(1).
*/
static long fixedCompareNamedArgs(List<String> selfArgs, Map<String, String> otherArgMap) {
long ops = 0;
for (String selfName : selfArgs) {
ops++; // O(1) map lookup
otherArgMap.containsKey(selfName);
}
return ops;
}
static long fixedAddDef(int numOverloads, List<String> selfArgs, List<String> otherArgs) {
// Build map once per comparison (still O(N) to build, but only done once)
Map<String, String> otherArgMap = new HashMap<>();
for (String name : otherArgs) otherArgMap.put(name, name);
long totalOps = 0;
for (int i = 0; i < numOverloads; i++) {
totalOps += fixedCompareNamedArgs(selfArgs, otherArgMap);
}
return totalOps;
}
// ---- Test harness ----------------------------------------------------------
static final int N = 800; // named args per def
static final int D = 10; // number of overloads
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Build two arg lists of size N with no overlap (worst case: scan all)
List<String> selfArgs = new ArrayList<>(N);
List<String> otherArgs = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
selfArgs.add("self_" + i);
otherArgs.add("other_" + i); // different names — always scans full list
}
// Test 1: defective is O(N²) per comparison call
total++;
long slowOps = defectiveCompareNamedArgs(selfArgs, otherArgs);
// Worst case: every self arg scans all N other args (no match found)
assert slowOps == (long) N * N
: "Expected " + ((long) N * N) + " ops, got " + slowOps;
System.out.println("PASS test1: defective single compare ops=" + slowOps
+ " (N^2=" + ((long) N * N) + ", N=" + N + ")");
passed++;
// Test 2: fixed is O(N) per comparison call
total++;
Map<String, String> otherArgMap = new HashMap<>();
for (String name : otherArgs) otherArgMap.put(name, name);
long fastOps = fixedCompareNamedArgs(selfArgs, otherArgMap);
assert fastOps == N
: "Expected exactly N=" + N + " ops, got " + fastOps;
System.out.println("PASS test2: fixed single compare ops=" + fastOps + " (O(N), N=" + N + ")");
passed++;
// Test 3: speedup per call is >= 10x
total++;
double speedup = (double) slowOps / fastOps;
assert speedup >= 10.0
: "Expected speedup >= 10x, got " + speedup;
System.out.printf("PASS test3: single-compare speedup=%.1fx%n", speedup);
passed++;
// Test 4: with D overloads, defective is O(D*N²), fixed is O(D*N)
total++;
long slowTotal = defectiveAddDef(D, selfArgs, otherArgs);
long fastTotal = fixedAddDef(D, selfArgs, otherArgs);
double addDefSpeedup = (double) slowTotal / fastTotal;
assert addDefSpeedup >= 10.0
: "add_def speedup expected >= 10x, got " + addDefSpeedup;
System.out.printf("PASS test4: add_def D=%d speedup=%.1fx (slow=%d fast=%d)%n",
D, addDefSpeedup, slowTotal, fastTotal);
passed++;
// Test 5: correctness — matching names found correctly
total++;
List<String> mixed1 = Arrays.asList("alpha", "beta", "gamma");
List<String> mixed2 = Arrays.asList("delta", "beta", "epsilon");
Map<String, String> mixed2Map = new HashMap<>();
for (String name : mixed2) mixed2Map.put(name, name);
// "beta" should be found in both, "alpha"/"gamma" should not match
assert mixed2.contains("beta");
assert mixed2Map.containsKey("beta");
assert !mixed2.contains("alpha");
assert !mixed2Map.containsKey("alpha");
System.out.println("PASS test5: correctness — defective and fixed agree on membership");
passed++;
System.out.println(passed + "/" + total + " PASS");
}
}