149 lines
5.7 KiB
Java
149 lines
5.7 KiB
Java
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");
|
||
}
|
||
}
|