package unit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; /** * IgraphCohesiveBlocksTest * * Models CWE-407 defect igraph-0001: * * python-igraph CohesiveBlocks.max_cohesion() — list membership scan * File: src/igraph/clustering.py, line 1311 * * Defective: self._clusters is a list-of-lists; `if idx in cluster` is * O(|cluster|) per block, O(B×C) per call, O(V×B×C) for all * vertices — degrades to O(V²) when B×C ~ V. * * Fixed: self._cluster_sets is a list-of-frozensets; `if idx in * cluster_set` is O(1); full-pass cost is O(V×B). * * Tests instrument comparison counts explicitly — no wall-clock timing. */ public class IgraphCohesiveBlocksTest { // ----------------------------------------------------------------------- // Model: a CohesiveBlocks object with B blocks, each of size C. // Vertex indices are integers 0..V-1. // We simulate max_cohesion(idx) for each vertex 0..V-1. // ----------------------------------------------------------------------- /** * Defective path: each cluster stored as ArrayList. * `if idx in cluster` performs a linear scan of the list. * * @param numVertices V — total vertex count * @param numBlocks B — number of cohesive blocks * @param clusterSize C — vertices per block (each block = consecutive V IDs) * @return total element comparisons across all V calls to max_cohesion */ static long defectiveMaxCohesion(int numVertices, int numBlocks, int clusterSize) { // Build B clusters, each containing clusterSize consecutive vertex IDs // (wrapping mod V to keep IDs in range) List> clusters = new ArrayList<>(); int[] cohesion = new int[numBlocks]; for (int b = 0; b < numBlocks; b++) { ArrayList cluster = new ArrayList<>(); for (int c = 0; c < clusterSize; c++) { cluster.add((b * clusterSize + c) % numVertices); } clusters.add(cluster); cohesion[b] = b + 1; // arbitrary cohesion score } long comparisons = 0; // Simulate max_cohesion(idx) for every vertex (the natural full-pass use) for (int idx = 0; idx < numVertices; idx++) { // Defective: for each block, scan the list linearly for (int b = 0; b < numBlocks; b++) { ArrayList cluster = clusters.get(b); for (Integer member : cluster) { comparisons++; // O(|cluster|) list scan if (member.equals(idx)) { break; // found — stop scanning this cluster } } } } return comparisons; } /** * Fixed path: each cluster stored as HashSet (models Python frozenset). * `if idx in cluster_set` is O(1) average. * * @param numVertices V * @param numBlocks B * @param clusterSize C * @return total hash lookups across all V calls to max_cohesion */ static long fixedMaxCohesion(int numVertices, int numBlocks, int clusterSize) { List> clusterSets = new ArrayList<>(); for (int b = 0; b < numBlocks; b++) { HashSet set = new HashSet<>(); for (int c = 0; c < clusterSize; c++) { set.add((b * clusterSize + c) % numVertices); } clusterSets.add(set); } long lookups = 0; for (int idx = 0; idx < numVertices; idx++) { for (int b = 0; b < numBlocks; b++) { lookups++; // O(1) hash probe — CWE-407 fix clusterSets.get(b).contains(idx); } } return lookups; } // ----------------------------------------------------------------------- // Test 1: defective cost > fixed cost at V=100, B=20, C=10 // ----------------------------------------------------------------------- static void test1_listScanCostsMoreThanSetLookup() { int V = 100, B = 20, C = 10; long defectOps = defectiveMaxCohesion(V, B, C); long fixedOps = fixedMaxCohesion(V, B, C); System.out.printf( "test1: V=%d B=%d C=%d defect_comparisons=%d fixed_lookups=%d%n", V, B, C, defectOps, fixedOps); assert defectOps > fixedOps : "igraph-0001: list scan must do more work than set lookup; defect=" + defectOps + " fixed=" + fixedOps; // Fixed cost is exactly V*B (one hash probe per block per vertex) assert fixedOps == (long) V * B : "igraph-0001: fixed lookups should be V*B=" + ((long) V * B) + " got " + fixedOps; } // ----------------------------------------------------------------------- // Test 2: defect grows super-linearly with clusterSize; fixed does not // Doubling C doubles defect cost (more comparisons per scan), // but fixed cost stays constant (O(1) per lookup regardless of C). // ----------------------------------------------------------------------- static void test2_defectGrowsWithClusterSize() { int V = 80, B = 10; int C1 = 8, C2 = 16; // double cluster size long d1 = defectiveMaxCohesion(V, B, C1); long d2 = defectiveMaxCohesion(V, B, C2); long f1 = fixedMaxCohesion(V, B, C1); long f2 = fixedMaxCohesion(V, B, C2); System.out.printf( "test2: V=%d B=%d C1=%d defect=%d fixed=%d | C2=%d defect=%d fixed=%d%n", V, B, C1, d1, f1, C2, d2, f2); // Defect cost grows with C; fixed cost is independent of C assert d2 > d1 : "igraph-0001: defect comparisons must grow as cluster size increases"; assert f2 == f1 : "igraph-0001: fixed lookups must not change with cluster size; f1=" + f1 + " f2=" + f2; } // ----------------------------------------------------------------------- // Test 3: at large scale (V=500, B=50, C=50) defect cost is at least // 10x the fixed cost — demonstrating quadratic vs linear behaviour. // ----------------------------------------------------------------------- static void test3_largeScaleSpeedup() { int V = 500, B = 50, C = 50; long defectOps = defectiveMaxCohesion(V, B, C); long fixedOps = fixedMaxCohesion(V, B, C); double ratio = (double) defectOps / Math.max(1, fixedOps); System.out.printf( "test3: V=%d B=%d C=%d defect=%d fixed=%d ratio=%.1fx%n", V, B, C, defectOps, fixedOps, ratio); assert ratio >= 10.0 : "igraph-0001: expected >= 10x speedup from set; got ratio=" + ratio; } // ----------------------------------------------------------------------- // Main // ----------------------------------------------------------------------- public static void main(String[] args) { System.out.println("=== IgraphCohesiveBlocksTest ==="); System.out.println("Modelling CWE-407: igraph-0001 — CohesiveBlocks.max_cohesion list scan"); System.out.println(); test1_listScanCostsMoreThanSetLookup(); System.out.println(" PASS test1_listScanCostsMoreThanSetLookup"); test2_defectGrowsWithClusterSize(); System.out.println(" PASS test2_defectGrowsWithClusterSize"); test3_largeScaleSpeedup(); System.out.println(" PASS test3_largeScaleSpeedup"); System.out.println(); System.out.println("3/3 PASS"); } }