package unit; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicLong; /** * Unit test for Quarkus CWE-407 defects: * quarkus-0001: BeanInfo.getBoundInterceptors — bound.contains (ArrayList) in nested loops * quarkus-0002: ComponentsProviderGenerator.isDependency — dependants.contains (ArrayList) in loop * quarkus-0003: BeanDeployment.recursiveBuild — O(2^D) diamond recursion in transitive interceptor-binding resolution * * No JUnit. No external deps. Compile and run: * javac -d . *.java && java -ea unit.QuarkusTest */ public class QuarkusTest { // ---- quarkus-0001 simulation ---- // Simulates getBoundInterceptors(): nested loops over lifecycle + intercepted methods, // deduplicating into 'bound' list using ArrayList.contains static long slowGetBoundInterceptors(int methodCount, int interceptorsPerMethod) { long ops = 0; List bound = new ArrayList<>(); // Loop 1: lifecycleInterceptors.values() for (int m = 0; m < methodCount / 2; m++) { for (int i = 0; i < interceptorsPerMethod; i++) { int interceptorId = i; // interceptors reused across methods (dedup needed) ops += bound.size() + 1; // cost of ArrayList.contains scan if (!bound.contains(interceptorId)) { bound.add(interceptorId); } } } // Loop 2: interceptedMethods.values() for (int m = methodCount / 2; m < methodCount; m++) { for (int i = 0; i < interceptorsPerMethod; i++) { int interceptorId = i; ops += bound.size() + 1; if (!bound.contains(interceptorId)) { bound.add(interceptorId); } } } return ops; } static long fastGetBoundInterceptors(int methodCount, int interceptorsPerMethod) { long ops = 0; Set boundSet = new LinkedHashSet<>(); for (int m = 0; m < methodCount / 2; m++) { for (int i = 0; i < interceptorsPerMethod; i++) { int interceptorId = i; ops += 1; // O(1) HashSet.contains boundSet.add(interceptorId); } } for (int m = methodCount / 2; m < methodCount; m++) { for (int i = 0; i < interceptorsPerMethod; i++) { int interceptorId = i; ops += 1; boundSet.add(interceptorId); } } // Convert to sorted List at end (one-time O(I log I)) List bound = new ArrayList<>(boundSet); return ops; } // ---- quarkus-0002 simulation ---- // Simulates isDependency called O(B) times, each iterating map values (O(B)) and // calling dependants.contains (ArrayList, O(D)). static long slowIsDependency(int beanCount, int dependantsPerBean) { long ops = 0; // dependencyMap: bean → list of dependants Map> dependencyMap = new TreeMap<>(); for (int b = 0; b < beanCount; b++) { List dependants = new ArrayList<>(); for (int d = 0; d < dependantsPerBean; d++) { dependants.add((b + d + 1) % beanCount); } dependencyMap.put(b, dependants); } // isDependency called for each bean (O(B) calls total) for (int queryBean = 0; queryBean < beanCount; queryBean++) { for (List dependants : dependencyMap.values()) { // O(B) map values ops += dependants.size(); // ArrayList.contains scan cost if (dependants.contains(queryBean)) { break; } } } return ops; } static long fastIsDependency(int beanCount, int dependantsPerBean) { long ops = 0; Map> dependencyMap = new TreeMap<>(); for (int b = 0; b < beanCount; b++) { List dependants = new ArrayList<>(); for (int d = 0; d < dependantsPerBean; d++) { dependants.add((b + d + 1) % beanCount); } dependencyMap.put(b, dependants); } // Build inverted index once: O(B×D) Set allDependants = new HashSet<>(); for (List dependants : dependencyMap.values()) { allDependants.addAll(dependants); } // isDependency is now O(1) per call for (int queryBean = 0; queryBean < beanCount; queryBean++) { ops += 1; // O(1) HashSet.contains allDependants.contains(queryBean); } return ops; } // ---- quarkus-0003 simulation ---- // Faithfully reproduces BeanDeployment.recursiveBuild() — no visited set. // // The defect: for each key in the map, recursiveBuild is called. Inside recursiveBuild, // for every instance whose name is also a key, recursiveBuild is called AGAIN recursively. // No visited set → diamond shapes cause O(2^D) calls. // // The map models: each node's Set stores its direct children. // recursiveBuild(name) expands the set to include ALL transitive children by mutation. // // Diamond structure built for depth D: // nodes at each level 0..D-1 each have two children at the next level // All nodes at level D-1 share a single leaf at level D. // Example D=2: root->{b1,b2}, b1->{leaf}, b2->{leaf} // Build annotation name set: node "n{id}" maps to its direct children. // Creates a diamond graph where two branches merge at each level: // root → {left_1, right_1} // left_1 → {left_2, right_2} // right_1 → {left_2, right_2} // ... // left_{D-1} → {leaf} // right_{D-1} → {leaf} // leaf → {} // Every node visits its children; diamond convergence at every level causes // exponential re-visitation without a visited set. static Map> buildDiamond(int depth) { Map> map = new HashMap<>(); String leaf = "leaf"; map.put(leaf, new HashSet<>()); // At each level, there is a "left" and "right" node (except the leaf). // Both nodes at level L point to the same pair of nodes at level L+1. String prevLeft = leaf, prevRight = null; // at leaf level only one node for (int level = depth - 1; level >= 1; level--) { String left = "L" + level + "_left"; String right = "L" + level + "_right"; Set children; if (prevRight == null) { // previous level was single leaf; both new nodes point to leaf children = new HashSet<>(Set.of(prevLeft)); } else { children = new HashSet<>(Set.of(prevLeft, prevRight)); } map.put(left, new HashSet<>(children)); map.put(right, new HashSet<>(children)); prevLeft = left; prevRight = right; } // root points to both prevLeft and prevRight Set rootChildren = new HashSet<>(); rootChildren.add(prevLeft); if (prevRight != null) rootChildren.add(prevRight); map.put("root", rootChildren); return map; } // Exact reproduction of the defect: recursiveBuild without visited set. // Counts each invocation in callCount. static Set slowRecursiveBuild(String name, Map> map, AtomicLong callCount) { callCount.incrementAndGet(); Set result = map.get(name); if (result == null) return Collections.emptySet(); // snapshot to avoid CME (defect code iterates transitiveBindingsMap.get(name) twice, // we snapshot just as the defect's for-loop sees the set at entry time) List snapshot = new ArrayList<>(result); for (String child : snapshot) { if (map.containsKey(child)) { result.addAll(slowRecursiveBuild(child, map, callCount)); // NO visited guard } } return result; } // Fixed: recursiveBuild with visited set static Set fastRecursiveBuildWithVisited(String name, Map> map, Set visited, AtomicLong callCount) { callCount.incrementAndGet(); if (!visited.add(name)) { return map.getOrDefault(name, Collections.emptySet()); } Set result = map.get(name); if (result == null) return Collections.emptySet(); for (String child : List.copyOf(result)) { if (map.containsKey(child)) { result.addAll(fastRecursiveBuildWithVisited(child, map, visited, callCount)); } } return result; } static Set fastRecursiveBuildInner(String name, Map> map, AtomicLong callCount, Set visited) { return fastRecursiveBuildWithVisited(name, map, visited, callCount); } public static void main(String[] args) { int pass = 0; int total = 0; // --- quarkus-0001 tests --- { total++; long slow = slowGetBoundInterceptors(20, 8); long fast = fastGetBoundInterceptors(20, 8); boolean ok = slow > fast * 3; System.out.println("[quarkus-0001] M=20 I=8: slow_ops=" + slow + " fast_ops=" + fast + " ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; long slow = slowGetBoundInterceptors(50, 15); long fast = fastGetBoundInterceptors(50, 15); boolean ok = slow > fast * 5; System.out.println("[quarkus-0001] M=50 I=15: slow_ops=" + slow + " fast_ops=" + fast + " ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; // Correctness: both paths must produce same unique interceptor count Set slowBound = new LinkedHashSet<>(); Set fastBound = new LinkedHashSet<>(); int methods = 10, interceptors = 5; // slow: uses ArrayList dedup but we track the same set for checking List slowList = new ArrayList<>(); for (int m = 0; m < methods; m++) { for (int i = 0; i < interceptors; i++) { if (!slowList.contains(i)) slowList.add(i); } } Set fastSet = new LinkedHashSet<>(); for (int m = 0; m < methods; m++) { for (int i = 0; i < interceptors; i++) { fastSet.add(i); } } boolean ok = slowList.size() == fastSet.size(); System.out.println("[quarkus-0001] correctness: slow=" + slowList.size() + " fast=" + fastSet.size() + " " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } // --- quarkus-0002 tests --- { total++; long slow = slowIsDependency(100, 5); long fast = fastIsDependency(100, 5); boolean ok = slow > fast * 20; System.out.println("[quarkus-0002] B=100 D=5: slow_ops=" + slow + " fast_ops=" + fast + " ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; long slow = slowIsDependency(300, 10); long fast = fastIsDependency(300, 10); boolean ok = slow > fast * 100; System.out.println("[quarkus-0002] B=300 D=10: slow_ops=" + slow + " fast_ops=" + fast + " ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; // Correctness: isDependency returns same true/false for same query Map> dmap = new HashMap<>(); dmap.put(0, new ArrayList<>(List.of(1, 2, 3))); dmap.put(1, new ArrayList<>(List.of(4, 5))); dmap.put(2, new ArrayList<>(List.of(6))); // slow: iterate all lists, call contains boolean slowResult3 = false; boolean slowResult7 = false; for (List deps : dmap.values()) { if (deps.contains(3)) { slowResult3 = true; break; } } for (List deps : dmap.values()) { if (deps.contains(7)) { slowResult7 = true; break; } } // fast: precompute set Set allDeps = new HashSet<>(); for (List deps : dmap.values()) allDeps.addAll(deps); boolean fastResult3 = allDeps.contains(3); boolean fastResult7 = allDeps.contains(7); boolean ok = slowResult3 == fastResult3 && slowResult7 == fastResult7 && slowResult3 == true && slowResult7 == false; System.out.println("[quarkus-0002] isDependency correctness: bean3=" + fastResult3 + " bean7=" + fastResult7 + " " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } // --- quarkus-0003 tests --- { total++; // Diamond depth=4: slow should make far more calls than fast Map> map1 = buildDiamond(4); AtomicLong slowCalls = new AtomicLong(0); slowRecursiveBuild("root", map1, slowCalls); Map> map2 = buildDiamond(4); AtomicLong fastCalls = new AtomicLong(0); fastRecursiveBuildWithVisited("root", map2, new HashSet<>(), fastCalls); long sc = slowCalls.get(), fc = fastCalls.get(); boolean ok = sc > fc; // any measurable overhead; D=8 test validates exponential growth System.out.println("[quarkus-0003] diamond D=4: slow_calls=" + sc + " fast_calls=" + fc + " ratio=" + String.format("%.1f", (double) sc / Math.max(fc, 1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; // Diamond depth=8: exponential gap should be large Map> map1 = buildDiamond(8); AtomicLong slowCalls = new AtomicLong(0); slowRecursiveBuild("root", map1, slowCalls); Map> map2 = buildDiamond(8); AtomicLong fastCalls = new AtomicLong(0); fastRecursiveBuildWithVisited("root", map2, new HashSet<>(), fastCalls); long sc = slowCalls.get(), fc = fastCalls.get(); boolean ok = sc > fc * 10; System.out.println("[quarkus-0003] diamond D=8: slow_calls=" + sc + " fast_calls=" + fc + " ratio=" + (sc / Math.max(fc, 1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; // Correctness: both approaches should collect the same transitive set // Simple 3-node diamond: A->{B,C}, B->{D}, C->{D}, D->{} Map> map3 = new HashMap<>(); map3.put("A", new HashSet<>(Set.of("B", "C"))); map3.put("B", new HashSet<>(Set.of("D"))); map3.put("C", new HashSet<>(Set.of("D"))); map3.put("D", new HashSet<>()); AtomicLong sc3 = new AtomicLong(0); slowRecursiveBuild("A", map3, sc3); Set slowResult3 = map3.get("A"); // mutated in-place to include D Map> map4 = new HashMap<>(); map4.put("A", new HashSet<>(Set.of("B", "C"))); map4.put("B", new HashSet<>(Set.of("D"))); map4.put("C", new HashSet<>(Set.of("D"))); map4.put("D", new HashSet<>()); AtomicLong fc3 = new AtomicLong(0); Set fastResult = fastRecursiveBuildWithVisited("A", map4, new HashSet<>(), fc3); boolean ok = fastResult.containsAll(Set.of("B", "C", "D")) && slowResult3.containsAll(Set.of("B", "C", "D")); System.out.println("[quarkus-0003] correctness: slow=" + slowResult3 + " fast=" + fastResult + " " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } System.out.println("\n" + pass + "/" + total + " PASS"); if (pass != total) { System.exit(1); } } }