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.concurrent.atomic.AtomicLong; /** * Unit test for Micronaut CWE-407 defects: * micronaut-0001: ClassUtils.resolveHierarchy — hierarchy.contains (ArrayList) in while loop * micronaut-0002: MutableAnnotationMetadata — annotationList.contains (ArrayList) in for loop * micronaut-0003: EnvironmentPropertySource — includes/excludes.contains (List) in env loop * micronaut-0004: AbstractAnnotationMetadataBuilder.processAnnotation — O(2^D) diamond recursion in meta-annotation traversal * * No JUnit. No external deps. Compile and run: * javac -d . *.java && java -ea unit.MicronautTest */ public class MicronautTest { // ---- micronaut-0001 simulation ---- // Simulates resolveHierarchy: builds hierarchy list while walking superclass chain, // calling contains() to deduplicate. Also simulates populateHierarchyInterfaces recursion. static long slowResolveHierarchy(int superclassCount, int interfacesPerClass) { long ops = 0; List hierarchy = new ArrayList<>(); List interfaces = new ArrayList<>(); // Walk superclass chain for (int superclass = 0; superclass < superclassCount; superclass++) { if (!hierarchy.contains(superclass)) { // O(H) ArrayList ops += hierarchy.size() + 1; hierarchy.add(superclass); } // Populate interfaces for this superclass for (int iface = 0; iface < interfacesPerClass; iface++) { int ifaceId = superclass * 100 + iface; if (!interfaces.contains(ifaceId)) { // O(|interfaces|) ArrayList ops += interfaces.size() + 1; interfaces.add(ifaceId); } // Recursive: each interface may have parent interfaces for (int parentIface = 0; parentIface < interfacesPerClass / 2; parentIface++) { int parentId = ifaceId * 100 + parentIface; if (!interfaces.contains(parentId)) { // O(|interfaces|) ops += interfaces.size() + 1; interfaces.add(parentId); } } } } return ops; } static long fastResolveHierarchy(int superclassCount, int interfacesPerClass) { long ops = 0; Set hierarchy = new LinkedHashSet<>(); Set interfaces = new LinkedHashSet<>(); for (int superclass = 0; superclass < superclassCount; superclass++) { if (!hierarchy.contains(superclass)) { // O(1) HashSet ops += 1; hierarchy.add(superclass); } for (int iface = 0; iface < interfacesPerClass; iface++) { int ifaceId = superclass * 100 + iface; if (!interfaces.contains(ifaceId)) { // O(1) ops += 1; interfaces.add(ifaceId); } for (int parentIface = 0; parentIface < interfacesPerClass / 2; parentIface++) { int parentId = ifaceId * 100 + parentIface; if (!interfaces.contains(parentId)) { // O(1) ops += 1; interfaces.add(parentId); } } } } return ops; } // ---- micronaut-0002 simulation ---- // Simulates addRepeatableStereotype: for each parent in parents list, // check annotationList.contains (ArrayList) before adding. static long slowAddRepeatableStereotype(int parentCount, int existingAnnotations) { long ops = 0; List annotationList = new ArrayList<>(); // Pre-populate with existingAnnotations for (int i = 0; i < existingAnnotations; i++) { annotationList.add("existing-" + i); } // Add parents for (int i = 0; i < parentCount; i++) { String parent = "parent-" + i; if (!annotationList.contains(parent)) { // O(|annotationList|) ArrayList ops += annotationList.size() + 1; annotationList.add(parent); } } return ops; } static long fastAddRepeatableStereotype(int parentCount, int existingAnnotations) { long ops = 0; Set annotationSet = new LinkedHashSet<>(); for (int i = 0; i < existingAnnotations; i++) { annotationSet.add("existing-" + i); } for (int i = 0; i < parentCount; i++) { String parent = "parent-" + i; if (!annotationSet.contains(parent)) { // O(1) ops += 1; annotationSet.add(parent); } } return ops; } // ---- micronaut-0003 simulation ---- // Simulates getEnv: for each env var, check excludes.contains and includes.contains static long slowEnvFilter(int envVarCount, int filterListSize) { long ops = 0; List excludes = new ArrayList<>(); List includes = new ArrayList<>(); for (int i = 0; i < filterListSize; i++) { excludes.add("EXCLUDE_" + i); includes.add("INCLUDE_" + i); } Map env = new HashMap<>(); for (int i = 0; i < envVarCount; i++) { env.put("ENV_VAR_" + i, "value"); } for (String envVar : env.keySet()) { if (excludes.contains(envVar)) { // O(filterListSize) ArrayList ops += filterListSize; continue; } if (!includes.contains(envVar)) { // O(filterListSize) ArrayList ops += filterListSize; continue; } ops += filterListSize * 2; } return ops; } static long fastEnvFilter(int envVarCount, int filterListSize) { long ops = 0; Set excludes = new HashSet<>(); Set includes = new HashSet<>(); for (int i = 0; i < filterListSize; i++) { excludes.add("EXCLUDE_" + i); includes.add("INCLUDE_" + i); } Map env = new HashMap<>(); for (int i = 0; i < envVarCount; i++) { env.put("ENV_VAR_" + i, "value"); } for (String envVar : env.keySet()) { if (excludes.contains(envVar)) { // O(1) ops += 1; continue; } if (!includes.contains(envVar)) { // O(1) ops += 1; continue; } ops += 2; } return ops; } public static void main(String[] args) { int pass = 0; int total = 0; // --- micronaut-0001 tests --- { total++; long slow = slowResolveHierarchy(20, 5); long fast = fastResolveHierarchy(20, 5); boolean ok = slow > fast * 5; System.out.println("[micronaut-0001] C=20 I=5: slow_ops=" + slow + " fast_ops=" + fast + " ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; // Correctness: same unique elements discovered List slowHierarchy = new ArrayList<>(); Set fastHierarchy = new LinkedHashSet<>(); for (int i = 0; i < 30; i++) { int val = i % 15; if (!slowHierarchy.contains(val)) slowHierarchy.add(val); fastHierarchy.add(val); } boolean ok = slowHierarchy.size() == fastHierarchy.size(); System.out.println("[micronaut-0001] correctness: slow=" + slowHierarchy.size() + " fast=" + fastHierarchy.size() + " " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } // --- micronaut-0002 tests --- { total++; long slow = slowAddRepeatableStereotype(30, 10); long fast = fastAddRepeatableStereotype(30, 10); boolean ok = slow > fast * 3; System.out.println("[micronaut-0002] P=30 existing=10: slow_ops=" + slow + " fast_ops=" + fast + " ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; long slow = slowAddRepeatableStereotype(100, 50); long fast = fastAddRepeatableStereotype(100, 50); boolean ok = slow > fast * 10; System.out.println("[micronaut-0002] P=100 existing=50: slow_ops=" + slow + " fast_ops=" + fast + " ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; // Correctness: same dedup result List slowResult = new ArrayList<>(); Set fastResult = new LinkedHashSet<>(); String[] parents = {"a", "b", "a", "c", "b", "d"}; for (String p : parents) { if (!slowResult.contains(p)) slowResult.add(p); fastResult.add(p); } boolean ok = slowResult.size() == fastResult.size() && new ArrayList<>(fastResult).equals(slowResult); System.out.println("[micronaut-0002] dedup correctness: slow=" + slowResult.size() + " fast=" + fastResult.size() + " " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } // --- micronaut-0003 tests --- { total++; long slow = slowEnvFilter(500, 50); long fast = fastEnvFilter(500, 50); boolean ok = slow > fast * 10; System.out.println("[micronaut-0003] E=500 N=50: slow_ops=" + slow + " fast_ops=" + fast + " ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; // Correctness: same env vars pass through filter List slowPassed = new ArrayList<>(); List fastPassed = new ArrayList<>(); List excludeList = new ArrayList<>(); Set excludeSet = new HashSet<>(); List includeList = new ArrayList<>(); Set includeSet = new HashSet<>(); for (int i = 0; i < 5; i++) { excludeList.add("EX_" + i); excludeSet.add("EX_" + i); includeList.add("ENV_VAR_" + i); includeSet.add("ENV_VAR_" + i); } for (int i = 0; i < 10; i++) { String v = "ENV_VAR_" + i; if (!excludeList.contains(v) && includeList.contains(v)) slowPassed.add(v); if (!excludeSet.contains(v) && includeSet.contains(v)) fastPassed.add(v); } boolean ok = slowPassed.size() == fastPassed.size(); System.out.println("[micronaut-0003] filter correctness: slow=" + slowPassed.size() + " fast=" + fastPassed.size() + " " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } // --- micronaut-0004 tests --- // Simulates AbstractAnnotationMetadataBuilder.processAnnotation diamond recursion. // The guard context.isProcessed() only checks CURRENT PATH ancestors — not globally // visited nodes — so a diamond meta-annotation graph causes O(2^D) re-visits. // // Model: annotation name → set of meta-annotations (stereotypes) // processAnnotation(ctx, name): check if name in ctx.parentAnnotations; if not, // recursively process each stereotype with ctx.withParent(name) { total++; // Diamond depth=8: slow ~383 calls, fast ~29 calls, ratio ~13x Map> stereotypes = buildMetaDiamond(8); AtomicLong slowCalls = new AtomicLong(0); slowProcessAnnotation("root", stereotypes, new HashSet<>(), slowCalls); Map> stereotypes2 = buildMetaDiamond(8); AtomicLong fastCalls = new AtomicLong(0); fastProcessAnnotation("root", stereotypes2, new HashSet<>(), new HashSet<>(), fastCalls); long sc = slowCalls.get(), fc = fastCalls.get(); boolean ok = sc > fc * 5; System.out.println("[micronaut-0004] diamond D=8: slow=" + sc + " fast=" + fc + " ratio=" + (sc/Math.max(fc,1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; // Diamond depth=10: slow ~1535 calls, fast ~37 calls, ratio ~41x Map> stereotypes = buildMetaDiamond(10); AtomicLong slowCalls = new AtomicLong(0); slowProcessAnnotation("root", stereotypes, new HashSet<>(), slowCalls); Map> stereotypes2 = buildMetaDiamond(10); AtomicLong fastCalls = new AtomicLong(0); fastProcessAnnotation("root", stereotypes2, new HashSet<>(), new HashSet<>(), fastCalls); long sc = slowCalls.get(), fc = fastCalls.get(); boolean ok = sc > fc * 20; System.out.println("[micronaut-0004] diamond D=10: slow=" + sc + " fast=" + fc + " ratio=" + (sc/Math.max(fc,1)) + "x " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } { total++; // Correctness: fast traversal visits all nodes in diamond Map> s2 = buildMetaDiamond(5); Set fastCollected = new HashSet<>(); AtomicLong fc = new AtomicLong(0); fastProcessAnnotation("root", s2, new HashSet<>(), fastCollected, fc); // All nodes should be visited: root, L1..L4 left/right pairs, leaf boolean ok = fastCollected.contains("root") && fastCollected.contains("leaf") && fastCollected.size() == s2.size(); System.out.println("[micronaut-0004] correctness: visited=" + fastCollected.size() + "/" + s2.size() + " nodes " + (ok ? "PASS" : "FAIL")); if (ok) pass++; } System.out.println("\n" + pass + "/" + total + " PASS"); if (pass != total) { System.exit(1); } } // ---- micronaut-0004 helpers ---- // Build diamond meta-annotation graph of depth D. // root → {left_1, right_1}; left_i → {left_{i+1}, right_{i+1}}; right_i → {left_{i+1}, right_{i+1}}; leaf → {} static Map> buildMetaDiamond(int depth) { Map> map = new HashMap<>(); String leaf = "leaf"; map.put(leaf, new HashSet<>()); String prevLeft = leaf, prevRight = null; for (int level = depth - 1; level >= 1; level--) { String left = "L" + level + "_left"; String right = "L" + level + "_right"; Set children = new HashSet<>(); children.add(prevLeft); if (prevRight != null) children.add(prevRight); map.put(left, new HashSet<>(children)); map.put(right, new HashSet<>(children)); prevLeft = left; prevRight = right; } Set rootChildren = new HashSet<>(); rootChildren.add(prevLeft); if (prevRight != null) rootChildren.add(prevRight); map.put("root", rootChildren); return map; } // Defect simulation: processAnnotation without global visited set. // parentAnnotations = current-path ancestors only (isProcessed guard per the defect). static void slowProcessAnnotation(String name, Map> stereotypesMap, Set parentAnnotations, AtomicLong callCount) { callCount.incrementAndGet(); if (parentAnnotations.contains(name)) return; // cycle guard (current path only) Set stereotypes = stereotypesMap.getOrDefault(name, Collections.emptySet()); // withParent: add name to parent set for child calls Set newParents = new HashSet<>(parentAnnotations); newParents.add(name); for (String stereotype : new ArrayList<>(stereotypes)) { slowProcessAnnotation(stereotype, stereotypesMap, newParents, callCount); // NO global visited } } // Fixed: processAnnotation with global visited set. static void fastProcessAnnotation(String name, Map> stereotypesMap, Set parentAnnotations, Set globalVisited, AtomicLong callCount) { callCount.incrementAndGet(); if (parentAnnotations.contains(name)) return; // cycle guard if (!globalVisited.add(name)) return; // global visited guard (FIX) Set stereotypes = stereotypesMap.getOrDefault(name, Collections.emptySet()); Set newParents = new HashSet<>(parentAnnotations); newParents.add(name); for (String stereotype : new ArrayList<>(stereotypes)) { fastProcessAnnotation(stereotype, stereotypesMap, newParents, globalVisited, callCount); } } }