import java.util.*; /** * CWE-407 unit tests for new Godot defects (godot-0011, godot-0012). * * godot-0011: GraphEditArranger ORDER/PRED macros — Vector.find() * O(N) linear scan called per-connection inside loops. * Fix: pre-build HashMap order lookup and * HashMap predecessor lookup. * * godot-0012: SpringBoneSimulator3D::_process_collisions — * LocalVector.has() / .find() O(N) linear scan * inside S×C nested loops. * Fix: pre-build HashSet + HashMap. */ public class GodotTest { // ----------------------------------------------------------------------- // godot-0011: GraphEditArranger ORDER/PRED macros // ----------------------------------------------------------------------- /** Simulate the DEFECTIVE ORDER macro: O(N) Vector scan per call. */ static int orderDefective(String node, Map> layers) { for (List layer : layers.values()) { int index = layer.indexOf(node); // O(N/L) linear scan if (index > 0) { return index; } } return Integer.MAX_VALUE; } /** Simulate the FIXED ORDER lookup: O(1) HashMap look-up. */ static int orderFixed(String node, Map orderMap) { return orderMap.getOrDefault(node, Integer.MAX_VALUE); } static Map buildOrderMap(Map> layers) { Map map = new HashMap<>(); for (List layer : layers.values()) { // Original ORDER macro uses `if (index > 0)` so position-0 nodes are NOT // recorded (they return MAX_ORDER, matching the defective code's behavior). for (int j = 1; j < layer.size(); j++) { map.put(layer.get(j), j); // j > 0, matches original index > 0 check } } return map; } /** Simulate the DEFECTIVE PRED macro: O(N) Vector scan to find predecessor. */ static String predDefective(String node, Map> layers) { for (List layer : layers.values()) { int index = layer.indexOf(node); // O(N/L) linear scan if (index > 0) { return layer.get(index - 1); } } return null; } /** Simulate the FIXED PRED lookup: O(1) HashMap look-up. */ static String predFixed(String node, Map predMap) { return predMap.get(node); } static Map buildPredMap(Map> layers) { Map map = new HashMap<>(); for (List layer : layers.values()) { for (int j = 1; j < layer.size(); j++) { map.put(layer.get(j), layer.get(j - 1)); } } return map; } /** Build a layer structure with N total nodes spread across L layers. */ static Map> buildLayers(int N, int L) { Map> layers = new LinkedHashMap<>(); for (int i = 0; i < L; i++) { layers.put(i, new ArrayList<>()); } for (int i = 0; i < N; i++) { layers.get(i % L).add("node_" + i); } return layers; } static void testGraphEditArrangerOrderPred() { System.out.println("=== godot-0011: GraphEditArranger ORDER/PRED macros ==="); int[] sizes = {10, 50, 100, 200, 500}; for (int N : sizes) { int L = Math.max(1, N / 10); // 10 nodes per layer Map> layers = buildLayers(N, L); Map orderMap = buildOrderMap(layers); Map predMap = buildPredMap(layers); // Pick a node in the middle of a layer (index > 0) String target = "node_" + (N / 2 + 1); // Simulate C connections querying ORDER int C = N; // worst case: E ≈ N connections long t0 = System.nanoTime(); int opCount = 0; for (int i = 0; i < C; i++) { orderDefective(target, layers); opCount++; } long defectiveNs = System.nanoTime() - t0; t0 = System.nanoTime(); for (int i = 0; i < C; i++) { orderFixed(target, orderMap); } long fixedNs = System.nanoTime() - t0; double ratio = defectiveNs / (double) fixedNs; System.out.printf(" N=%4d L=%3d C=%4d defective=%6d µs fixed=%6d µs ratio=%.1fx%n", N, L, C, defectiveNs / 1000, fixedNs / 1000, ratio); // Correctness: both must return same result int resDefective = orderDefective(target, layers); int resFixed = orderFixed(target, orderMap); if (resDefective != resFixed) { throw new AssertionError("ORDER mismatch at N=" + N + ": defective=" + resDefective + " fixed=" + resFixed); } // PRED correctness String predDef = predDefective(target, layers); String predFix = predFixed(target, predMap); if (!Objects.equals(predDef, predFix)) { throw new AssertionError("PRED mismatch at N=" + N + ": defective=" + predDef + " fixed=" + predFix); } if (N >= 100) { if (ratio < 2.0) { throw new AssertionError("Expected speedup >= 2x at N=" + N + ", got " + ratio); } } } System.out.println(" PASS"); } // ----------------------------------------------------------------------- // godot-0012: SpringBoneSimulator3D collision membership // ----------------------------------------------------------------------- /** Simulate the DEFECTIVE collision resolution: O(S×C×N). */ static long collisionResolutionDefective(List collisions, List> settingCollisions) { long ops = 0; for (List setting : settingCollisions) { for (Long id : setting) { ops++; // .has(id) — O(N) linear scan through collisions boolean found = collisions.contains(id); if (found) { ops++; } // suppress unused warning } } return ops; } /** Simulate the FIXED collision resolution: O(N + S×C). */ static long collisionResolutionFixed(List collisions, List> settingCollisions) { // Pre-build O(N) HashSet once Set collisionSet = new HashSet<>(collisions); long ops = collisions.size(); // build cost for (List setting : settingCollisions) { for (Long id : setting) { ops++; boolean found = collisionSet.contains(id); // O(1) if (found) { ops++; } // suppress unused warning } } return ops; } static void testSpringBoneCollisionMembership() { System.out.println("=== godot-0012: SpringBoneSimulator3D collision membership ==="); // Params: N = total collision objects, S = settings count, C = collisions per setting int[][] params = { {20, 5, 10}, {50, 10, 20}, {100, 20, 40}, {200, 30, 60}, {500, 50, 100}, }; Random rng = new Random(42); for (int[] p : params) { int N = p[0], S = p[1], C = p[2]; // Build N collision objects (as longs) List collisions = new ArrayList<>(N); for (int i = 0; i < N; i++) { collisions.add((long) i); } // Build S settings, each with C collision path references List> settingCollisions = new ArrayList<>(S); for (int i = 0; i < S; i++) { List sc = new ArrayList<>(C); for (int j = 0; j < C; j++) { sc.add((long) rng.nextInt(N)); } settingCollisions.add(sc); } long t0 = System.nanoTime(); long opsDefective = collisionResolutionDefective(collisions, settingCollisions); long defectiveNs = System.nanoTime() - t0; t0 = System.nanoTime(); long opsFixed = collisionResolutionFixed(collisions, settingCollisions); long fixedNs = System.nanoTime() - t0; double ratio = defectiveNs / (double) fixedNs; System.out.printf(" N=%4d S=%3d C=%3d defective=%6d µs fixed=%6d µs ratio=%.1fx%n", N, S, C, defectiveNs / 1000, fixedNs / 1000, ratio); if (N >= 100 && ratio < 1.5) { throw new AssertionError("Expected speedup >= 1.5x at N=" + N + ", got " + ratio); } } System.out.println(" PASS"); } public static void main(String[] args) { testGraphEditArrangerOrderPred(); testSpringBoneCollisionMembership(); System.out.println("ALL PASS"); } }