diff --git a/defects/quarkus/patch/quarkus-0001-bean-bound-interceptors-set.patch b/defects/quarkus/patch/quarkus-0001-bean-bound-interceptors-set.patch new file mode 100644 index 000000000..3af9160b1 --- /dev/null +++ b/defects/quarkus/patch/quarkus-0001-bean-bound-interceptors-set.patch @@ -0,0 +1,31 @@ +# UNDF: (leave blank) +--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java ++++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +@@ -487,17 +487,17 @@ public class BeanInfo implements InjectionTargetInfo { + public List getBoundInterceptors() { + if (lifecycleInterceptors.isEmpty() && interceptedMethods.isEmpty()) { + return Collections.emptyList(); + } +- List bound = new ArrayList<>(); ++ // Use LinkedHashSet for O(1) dedup while preserving insertion order before sort. ++ Set seen = new LinkedHashSet<>(); + for (InterceptionInfo interception : lifecycleInterceptors.values()) { + for (InterceptorInfo interceptor : interception.interceptors) { +- if (!bound.contains(interceptor)) { +- bound.add(interceptor); +- } ++ seen.add(interceptor); + } + } + for (InterceptionInfo interception : interceptedMethods.values()) { + for (InterceptorInfo interceptor : interception.interceptors) { +- if (!bound.contains(interceptor)) { +- bound.add(interceptor); +- } ++ seen.add(interceptor); + } + } ++ List bound = new ArrayList<>(seen); + Collections.sort(bound); + return bound; + } diff --git a/defects/quarkus/patch/quarkus-0002-bean-bound-decorators-set.patch b/defects/quarkus/patch/quarkus-0002-bean-bound-decorators-set.patch new file mode 100644 index 000000000..a9eea10f6 --- /dev/null +++ b/defects/quarkus/patch/quarkus-0002-bean-bound-decorators-set.patch @@ -0,0 +1,23 @@ +# UNDF: (leave blank) +--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java ++++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +@@ -515,11 +515,11 @@ public class BeanInfo implements InjectionTargetInfo { + public List getBoundDecorators() { + if (decoratedMethods.isEmpty()) { + return Collections.emptyList(); + } +- List bound = new ArrayList<>(); ++ // Use LinkedHashSet for O(1) dedup while preserving insertion order before sort. ++ Set seen = new LinkedHashSet<>(); + for (DecorationInfo decoration : decoratedMethods.values()) { + for (DecoratorMethod dm : decoration.decoratorMethods) { +- if (!bound.contains(dm.decorator)) { +- bound.add(dm.decorator); +- } ++ seen.add(dm.decorator); + } + } ++ List bound = new ArrayList<>(seen); + // Sort by priority (highest goes first) and by bean class (reversed lexicographic-order) + Collections.sort(bound, + Comparator.comparing(DecoratorInfo::getPriority) diff --git a/defects/quarkus/patch/quarkus-0003-devmode-changed-source-files-set.patch b/defects/quarkus/patch/quarkus-0003-devmode-changed-source-files-set.patch new file mode 100644 index 000000000..309f882b4 --- /dev/null +++ b/defects/quarkus/patch/quarkus-0003-devmode-changed-source-files-set.patch @@ -0,0 +1,25 @@ +# UNDF: (leave blank) +--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java ++++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java +@@ -817,7 +817,7 @@ public class RuntimeUpdatesProcessor implements HotReplacementContext, Closeable + + for (ChangeDetectionResult changeDetectionResult : changeDetectionResults) { +- final List moduleChangedSourceFilePaths = new ArrayList<>(); ++ final Set moduleChangedSourceFilePaths = new LinkedHashSet<>(); + for (RecompilableLocationsBySourcePath recompilableLocationsBySourcePath : changeDetectionResult + .changedLocations()) { + Path sourcePath = recompilableLocationsBySourcePath.sourcePath(); +@@ -936,7 +936,7 @@ public class RuntimeUpdatesProcessor implements HotReplacementContext, Closeable + private void checkForClassFilesChangesInModule(DevModeContext.ModuleInfo module, +- List moduleChangedSourceFiles, ++ Set moduleChangedSourceFiles, + boolean isInitialRun, ClassScanResult classScanResult, + Function cuf, TimestampSet timestampSet) { + +@@ -1001,7 +1001,7 @@ public class RuntimeUpdatesProcessor implements HotReplacementContext, Closeable + private Path retrieveSourceFilePathForClassFile(Path classFilePath, +- List moduleChangedSourceFiles, ++ Set moduleChangedSourceFiles, + DevModeContext.ModuleInfo module, + Function cuf, + TimestampSet timestampSet, boolean forceRefresh) { diff --git a/defects/quarkus/patch/quarkus-0004-cors-origins-set.patch b/defects/quarkus/patch/quarkus-0004-cors-origins-set.patch new file mode 100644 index 000000000..2c31ce7b8 --- /dev/null +++ b/defects/quarkus/patch/quarkus-0004-cors-origins-set.patch @@ -0,0 +1,26 @@ +# UNDF: (leave blank) +--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java ++++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java +@@ -26,6 +26,7 @@ public class CORSFilter implements Handler { + private final boolean wildcardOrigin; + private final boolean wildcardMethod; + private final List allowedOriginsRegex; ++ private final Set allowedOriginsExact; // pre-built at construction: O(1) lookup per request + private final Set configuredHttpMethods; + + private final String exposedHeaders; +@@ -40,6 +41,8 @@ public class CORSFilter implements Handler { + this.wildcardOrigin = isOriginConfiguredWithWildcard(this.corsConfig.origins()); + this.wildcardMethod = isConfiguredWithWildcard(corsConfig.methods()); + this.allowedOriginsRegex = this.wildcardOrigin ? List.of() : parseAllowedOriginsRegex(this.corsConfig.origins()); ++ this.allowedOriginsExact = (this.wildcardOrigin || corsConfig.origins().isEmpty()) ++ ? Set.of() : new HashSet<>(corsConfig.origins().get()); + this.configuredHttpMethods = createConfiguredHttpMethods(this.corsConfig.methods()); + this.exposedHeaders = createHeaderString(this.corsConfig.exposedHeaders()); + this.allowedHeaders = createHeaderString(this.corsConfig.headers()); +@@ -147,7 +150,7 @@ public class CORSFilter implements Handler { + //for both normal and preflight requests we need to check the origin + boolean allowsOrigin = wildcardOrigin; + boolean originMatches = !wildcardOrigin && corsConfig.origins().isPresent() && +- (corsConfig.origins().get().contains(origin) || isOriginAllowedByRegex(allowedOriginsRegex, origin)); ++ (allowedOriginsExact.contains(origin) || isOriginAllowedByRegex(allowedOriginsRegex, origin)); diff --git a/defects/quarkus/unit/QuarkusTest.java b/defects/quarkus/unit/QuarkusTest.java index e98b08582..62ba2a3aa 100644 --- a/defects/quarkus/unit/QuarkusTest.java +++ b/defects/quarkus/unit/QuarkusTest.java @@ -1,399 +1,270 @@ -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; +import java.util.*; /** - * 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 + * CWE-407 unit tests for Quarkus. * - * No JUnit. No external deps. Compile and run: - * javac -d . *.java && java -ea unit.QuarkusTest + * quarkus-0001: BeanInfo.getBoundInterceptors() + * independent-projects/arc/processor/.../BeanInfo.java + * Builds deduplicated interceptor list using ArrayList.contains() inside + * nested loops over lifecycle+method interceptions. + * Complexity: O(M * I^2) where M = intercepted methods, I = interceptors. + * Fix: LinkedHashSet for O(1) dedup → O(M * I). + * + * quarkus-0002: BeanInfo.getBoundDecorators() + * Same file. ArrayList.contains(dm.decorator) inside loop over decorated methods. + * Complexity: O(M * D^2) where M = decorated methods, D = decorators. + * Fix: LinkedHashSet for O(1) dedup → O(M * D). + * + * quarkus-0003: RuntimeUpdatesProcessor.checkForClassFilesChangesInModule() + * core/deployment/.../dev/RuntimeUpdatesProcessor.java + * moduleChangedSourceFiles is ArrayList; contains(sourceFilePath) called + * for every class file in the module during dev mode hot reload. + * Complexity: O(C * S) where C = class files, S = changed source files. + * Fix: change declaration to Set (LinkedHashSet) → O(C). + * + * quarkus-0004: CORSFilter.handle() + * extensions/vertx-http/.../cors/CORSFilter.java + * corsConfig.origins().get().contains(origin) on List executed per + * HTTP request. O(O) per request where O = configured origins count. + * Fix: pre-build HashSet of exact origins at construction → O(1) per request. */ public class QuarkusTest { - // ---- quarkus-0001 simulation ---- - // Simulates getBoundInterceptors(): nested loops over lifecycle + intercepted methods, - // deduplicating into 'bound' list using ArrayList.contains + // ----------------------------------------------------------------------- + // quarkus-0001 / quarkus-0002: getBoundInterceptors / getBoundDecorators + // ----------------------------------------------------------------------- - static long slowGetBoundInterceptors(int methodCount, int interceptorsPerMethod) { - long ops = 0; + /** Defect: deduplicate using ArrayList.contains — O(N^2). */ + static List deduplicateList(List inputs) { 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); - } + for (int item : inputs) { + if (!bound.contains(item)) { // O(N) per call — defect + bound.add(item); } } - - // 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; + return bound; } - 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); - } + /** Fix: deduplicate using LinkedHashSet — O(N) total, preserves order. */ + static List deduplicateSet(List inputs) { + Set seen = new LinkedHashSet<>(); + for (int item : inputs) { + seen.add(item); // O(1) per call — fix } - - 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; + return new ArrayList<>(seen); } - // ---- quarkus-0002 simulation ---- - // Simulates isDependency called O(B) times, each iterating map values (O(B)) and - // calling dependants.contains (ArrayList, O(D)). + static void testQuarkus0001_getBoundInterceptors() throws Exception { + // Simulate M=200 intercepted methods, each with I=100 interceptors (some repeated) + int M = 200; + int I = 100; + int totalInterceptors = 50; // universe of unique interceptors - 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; - } + List allInterceptors = new ArrayList<>(); + Random rng = new Random(42); + for (int m = 0; m < M; m++) { + for (int i = 0; i < I; i++) { + allInterceptors.add(rng.nextInt(totalInterceptors)); } } - return ops; + + long t0 = System.nanoTime(); + for (int trial = 0; trial < 1000; trial++) { + deduplicateList(allInterceptors); + } + long defectNs = System.nanoTime() - t0; + + long t1 = System.nanoTime(); + for (int trial = 0; trial < 1000; trial++) { + deduplicateSet(allInterceptors); + } + long fixNs = System.nanoTime() - t1; + + double ratio = (double) defectNs / fixNs; + System.out.printf("quarkus-0001 getBoundInterceptors: defect=%.1fms fix=%.1fms ratio=%.1fx%n", + defectNs / 1e6, fixNs / 1e6, ratio); + + if (ratio < 2.0) { + throw new AssertionError("Expected ratio >= 2.0, got " + ratio); + } + System.out.println("quarkus-0001 PASS"); } - 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); + static void testQuarkus0002_getBoundDecorators() throws Exception { + // Simulate M=150 decorated methods, each with D=80 decorators (some repeated) + int M = 150; + int D = 80; + int totalDecorators = 30; + + List allDecorators = new ArrayList<>(); + Random rng = new Random(123); + for (int m = 0; m < M; m++) { + for (int d = 0; d < D; d++) { + allDecorators.add(rng.nextInt(totalDecorators)); } - dependencyMap.put(b, dependants); } - // Build inverted index once: O(B×D) - Set allDependants = new HashSet<>(); - for (List dependants : dependencyMap.values()) { - allDependants.addAll(dependants); + long t0 = System.nanoTime(); + for (int trial = 0; trial < 1000; trial++) { + deduplicateList(allDecorators); } + long defectNs = System.nanoTime() - t0; - // isDependency is now O(1) per call - for (int queryBean = 0; queryBean < beanCount; queryBean++) { - ops += 1; // O(1) HashSet.contains - allDependants.contains(queryBean); + long t1 = System.nanoTime(); + for (int trial = 0; trial < 1000; trial++) { + deduplicateSet(allDecorators); } - return ops; + long fixNs = System.nanoTime() - t1; + + double ratio = (double) defectNs / fixNs; + System.out.printf("quarkus-0002 getBoundDecorators: defect=%.1fms fix=%.1fms ratio=%.1fx%n", + defectNs / 1e6, fixNs / 1e6, ratio); + + if (ratio < 2.0) { + throw new AssertionError("Expected ratio >= 2.0, got " + ratio); + } + System.out.println("quarkus-0002 PASS"); } - // ---- 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} + // ----------------------------------------------------------------------- + // quarkus-0003: checkForClassFilesChangesInModule — dev mode hot reload + // ----------------------------------------------------------------------- - // 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)); + /** Defect: List.contains per class file — O(C * S). */ + static int scanModuleDefect(List classFiles, List changedSourceFiles) { + int deletions = 0; + for (String classFile : classFiles) { + String sourceFile = classFile.replace(".class", ".java"); + if (changedSourceFiles.contains(sourceFile)) { // O(S) per call — defect + deletions++; } - 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; + return deletions; } - // 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 + /** Fix: Set.contains per class file — O(C). */ + static int scanModuleFix(List classFiles, Set changedSourceFiles) { + int deletions = 0; + for (String classFile : classFiles) { + String sourceFile = classFile.replace(".class", ".java"); + if (changedSourceFiles.contains(sourceFile)) { // O(1) per call — fix + deletions++; } } - return result; + return deletions; } - // 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()); + static void testQuarkus0003_devModeChangedSourceFiles() throws Exception { + // Simulate: C=2000 class files, S=200 changed source files + int C = 2000; + int S = 200; + + List classFiles = new ArrayList<>(C); + List changedSourceList = new ArrayList<>(S); + Set changedSourceSet = new LinkedHashSet<>(S * 2); + + for (int i = 0; i < C; i++) { + classFiles.add("com/example/Foo" + i + ".class"); } - 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)); - } + for (int i = 0; i < S; i++) { + String src = "com/example/Foo" + (i * 5) + ".java"; + changedSourceList.add(src); + changedSourceSet.add(src); } - return result; + + long t0 = System.nanoTime(); + for (int trial = 0; trial < 200; trial++) { + scanModuleDefect(classFiles, changedSourceList); + } + long defectNs = System.nanoTime() - t0; + + long t1 = System.nanoTime(); + for (int trial = 0; trial < 200; trial++) { + scanModuleFix(classFiles, changedSourceSet); + } + long fixNs = System.nanoTime() - t1; + + double ratio = (double) defectNs / fixNs; + System.out.printf("quarkus-0003 devModeChangedSourceFiles: defect=%.1fms fix=%.1fms ratio=%.1fx%n", + defectNs / 1e6, fixNs / 1e6, ratio); + + if (ratio < 5.0) { + throw new AssertionError("Expected ratio >= 5.0, got " + ratio); + } + System.out.println("quarkus-0003 PASS"); } - static Set fastRecursiveBuildInner(String name, Map> map, - AtomicLong callCount, Set visited) { - return fastRecursiveBuildWithVisited(name, map, visited, callCount); + // ----------------------------------------------------------------------- + // quarkus-0004: CORSFilter.handle — origins list lookup per request + // ----------------------------------------------------------------------- + + /** Defect: List.contains per HTTP request — O(O). */ + static int handleRequestsDefect(List origins, String[] incomingOrigins) { + int allowed = 0; + for (String origin : incomingOrigins) { + if (origins.contains(origin)) { // O(O) per request — defect + allowed++; + } + } + return allowed; } - 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); - } + /** Fix: pre-built HashSet.contains per request — O(1). */ + static int handleRequestsFix(Set originsSet, String[] incomingOrigins) { + int allowed = 0; + for (String origin : incomingOrigins) { + if (originsSet.contains(origin)) { // O(1) per request — fix + allowed++; } - 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++; + } + return allowed; + } + + static void testQuarkus0004_corsOriginsLookup() throws Exception { + // Simulate: O=100 configured origins, R=100000 requests + int O = 100; + int R = 100000; + + List originsList = new ArrayList<>(O); + Set originsSet = new HashSet<>(O * 2); + for (int i = 0; i < O; i++) { + originsList.add("https://example" + i + ".com"); + originsSet.add("https://example" + i + ".com"); } - // --- 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++; + // Requests target last origin (worst case for list scan) + String[] requests = new String[R]; + for (int i = 0; i < R; i++) { + requests[i] = "https://example" + ((i % O)) + ".com"; } - // --- 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 + long t0 = System.nanoTime(); + handleRequestsDefect(originsList, requests); + long defectNs = System.nanoTime() - t0; - 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); + long t1 = System.nanoTime(); + handleRequestsFix(originsSet, requests); + long fixNs = System.nanoTime() - t1; - 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++; - } + double ratio = (double) defectNs / fixNs; + System.out.printf("quarkus-0004 corsOriginsLookup: defect=%.1fms fix=%.1fms ratio=%.1fx%n", + defectNs / 1e6, fixNs / 1e6, ratio); - System.out.println("\n" + pass + "/" + total + " PASS"); - if (pass != total) { - System.exit(1); + if (ratio < 2.0) { + throw new AssertionError("Expected ratio >= 2.0, got " + ratio); } + System.out.println("quarkus-0004 PASS"); + } + + // ----------------------------------------------------------------------- + // Main + // ----------------------------------------------------------------------- + + public static void main(String[] args) throws Exception { + testQuarkus0001_getBoundInterceptors(); + testQuarkus0002_getBoundDecorators(); + testQuarkus0003_devModeChangedSourceFiles(); + testQuarkus0004_corsOriginsLookup(); + System.out.println("ALL PASS"); } }