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 selfArgs, List 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 selfArgs, List 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 selfArgs, Map otherArgMap) { long ops = 0; for (String selfName : selfArgs) { ops++; // O(1) map lookup otherArgMap.containsKey(selfName); } return ops; } static long fixedAddDef(int numOverloads, List selfArgs, List otherArgs) { // Build map once per comparison (still O(N) to build, but only done once) Map 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 selfArgs = new ArrayList<>(N); List 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 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 mixed1 = Arrays.asList("alpha", "beta", "gamma"); List mixed2 = Arrays.asList("delta", "beta", "epsilon"); Map 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"); } }