import java.util.*; /** * CWE-407 unit tests for Redmine defects. * * redmine-0001: issue.rb blocks?() BFS uses Array for 'all' visited set — O(V^2) subtraction * redmine-0002: issue.rb would_reschedule?() same BFS Array pattern — O(V^2) * redmine-0003: user.rb project_ids_by_role project_ids.include? in members.each — O(M*P) */ public class RedmineCWE407Test { // ---- redmine-0001: blocks? BFS with Array visited set ---- /** Unpatched: Array 'all' grows, Array subtraction O(C*A) per BFS layer */ static boolean blocksBfsUnpatched(int startId, int targetId, Map> graph) { List all = new ArrayList<>(); all.add(startId); List last = new ArrayList<>(); last.add(startId); while (!last.isEmpty()) { Set currentSet = new LinkedHashSet<>(); for (int node : last) { List neighbors = graph.getOrDefault(node, Collections.emptyList()); currentSet.addAll(neighbors); } List current = new ArrayList<>(currentSet); // Array subtraction: current -= last, current -= all (both O(N) per element) current.removeAll(last); Iterator it = current.iterator(); while (it.hasNext()) { if (all.contains(it.next())) { // O(A) per element it.remove(); } } if (current.contains(targetId)) return true; last = current; all.addAll(last); } return false; } /** Patched: Set 'all' for O(1) membership — O(V+E) total */ static boolean blocksBfsPatched(int startId, int targetId, Map> graph) { Set all = new HashSet<>(); all.add(startId); List last = new ArrayList<>(); last.add(startId); while (!last.isEmpty()) { Set currentSet = new LinkedHashSet<>(); for (int node : last) { List neighbors = graph.getOrDefault(node, Collections.emptyList()); currentSet.addAll(neighbors); } List current = new ArrayList<>(currentSet); // Set-based removal: O(1) per element current.removeIf(all::contains); if (current.contains(targetId)) return true; last = current; all.addAll(last); } return false; } // ---- redmine-0003: user project_ids membership ---- /** Unpatched: project_ids.include? in members.each — O(M*P) */ static Map> projectIdsByRoleUnpatched( int userId, List members, List projectIds) { Map> hash = new HashMap<>(); for (int[] member : members) { int memberUserId = member[0]; int roleId = member[1]; int projectId = member[2]; // Skip builtin group roles if user is a member of this project if (memberUserId != userId && projectIds.contains(projectId)) { // O(P) per member continue; } hash.computeIfAbsent(roleId, k -> new ArrayList<>()).add(projectId); } return hash; } /** Patched: project_ids.to_set for O(1) lookup — O(M+P) */ static Map> projectIdsByRolePatched( int userId, List members, List projectIds) { Set projectIdsSet = new HashSet<>(projectIds); // O(P) once Map> hash = new HashMap<>(); for (int[] member : members) { int memberUserId = member[0]; int roleId = member[1]; int projectId = member[2]; if (memberUserId != userId && projectIdsSet.contains(projectId)) { // O(1) per member continue; } hash.computeIfAbsent(roleId, k -> new ArrayList<>()).add(projectId); } return hash; } // ---- Test harness ---- static void testRedmine0001and0002() { System.out.println("=== redmine-0001/0002: issue BFS blocks?/would_reschedule? ==="); // Build a chain graph: 0 -> 1 -> 2 -> ... -> V-1 int V = 20000; Map> graph = new HashMap<>(); for (int i = 0; i < V - 1; i++) { graph.put(i, List.of(i + 1)); } int targetId = V - 1; // Warmup blocksBfsPatched(0, targetId, graph); long t0 = System.nanoTime(); boolean r1 = blocksBfsUnpatched(0, targetId, graph); long unpatched = System.nanoTime() - t0; t0 = System.nanoTime(); boolean r2 = blocksBfsPatched(0, targetId, graph); long patched = System.nanoTime() - t0; assert r1 == r2 : "Result mismatch"; assert r1 == true : "Expected true"; double ratio = (double) unpatched / patched; System.out.printf(" V=%d unpatched=%dms patched=%dms ratio=%.1fx%n", V, unpatched / 1_000_000, patched / 1_000_000, ratio); assert ratio > 3.0 : "Expected >3x ratio, got " + ratio; System.out.println(" PASS (covers both blocks? and would_reschedule?)"); } static void testRedmine0003() { System.out.println("=== redmine-0003: user project_ids_by_role membership ==="); int M = 50000; // membership rows int P = 5000; // user's projects int userId = 1; List projectIds = new ArrayList<>(P); for (int i = 0; i < P; i++) { projectIds.add(i); } // Generate member rows: mix of user's own and builtin group entries List members = new ArrayList<>(M); Random rng = new Random(42); for (int i = 0; i < M; i++) { int uid = rng.nextBoolean() ? userId : 999; // 50% user, 50% builtin group int roleId = rng.nextInt(10); int projectId = rng.nextInt(P * 2); // some in user's projects, some not members.add(new int[]{uid, roleId, projectId}); } // Warmup projectIdsByRolePatched(userId, members, projectIds); long t0 = System.nanoTime(); Map> r1 = projectIdsByRoleUnpatched(userId, members, projectIds); long unpatched = System.nanoTime() - t0; t0 = System.nanoTime(); Map> r2 = projectIdsByRolePatched(userId, members, projectIds); long patched = System.nanoTime() - t0; // Verify same results int totalR1 = r1.values().stream().mapToInt(List::size).sum(); int totalR2 = r2.values().stream().mapToInt(List::size).sum(); assert totalR1 == totalR2 : "Result mismatch: " + totalR1 + " vs " + totalR2; double ratio = (double) unpatched / patched; System.out.printf(" M=%d P=%d unpatched=%dms patched=%dms ratio=%.1fx%n", M, P, unpatched / 1_000_000, patched / 1_000_000, ratio); assert ratio > 3.0 : "Expected >3x ratio, got " + ratio; System.out.println(" PASS"); } public static void main(String[] args) { testRedmine0001and0002(); testRedmine0003(); System.out.println("\nAll 3 Redmine CWE-407 tests PASSED."); } }