quarkus: 4 CWE-407 defects — Arc CDI interceptor/decorator dedup O(N^2), devmode class scan O(C*S), CORS origins O(O)/req

This commit is contained in:
russell@unturf.com 2026-03-30 08:53:29 -04:00
parent 2b12adc044
commit 4483a2ee12
5 changed files with 321 additions and 345 deletions

View file

@ -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<InterceptorInfo> getBoundInterceptors() {
if (lifecycleInterceptors.isEmpty() && interceptedMethods.isEmpty()) {
return Collections.emptyList();
}
- List<InterceptorInfo> bound = new ArrayList<>();
+ // Use LinkedHashSet for O(1) dedup while preserving insertion order before sort.
+ Set<InterceptorInfo> 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<InterceptorInfo> bound = new ArrayList<>(seen);
Collections.sort(bound);
return bound;
}

View file

@ -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<DecoratorInfo> getBoundDecorators() {
if (decoratedMethods.isEmpty()) {
return Collections.emptyList();
}
- List<DecoratorInfo> bound = new ArrayList<>();
+ // Use LinkedHashSet for O(1) dedup while preserving insertion order before sort.
+ Set<DecoratorInfo> 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<DecoratorInfo> bound = new ArrayList<>(seen);
// Sort by priority (highest goes first) and by bean class (reversed lexicographic-order)
Collections.sort(bound,
Comparator.comparing(DecoratorInfo::getPriority)

View file

@ -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<Path> moduleChangedSourceFilePaths = new ArrayList<>();
+ final Set<Path> 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<Path> moduleChangedSourceFiles,
+ Set<Path> moduleChangedSourceFiles,
boolean isInitialRun, ClassScanResult classScanResult,
Function<DevModeContext.ModuleInfo, DevModeContext.CompilationUnit> cuf, TimestampSet timestampSet) {
@@ -1001,7 +1001,7 @@ public class RuntimeUpdatesProcessor implements HotReplacementContext, Closeable
private Path retrieveSourceFilePathForClassFile(Path classFilePath,
- List<Path> moduleChangedSourceFiles,
+ Set<Path> moduleChangedSourceFiles,
DevModeContext.ModuleInfo module,
Function<DevModeContext.ModuleInfo, DevModeContext.CompilationUnit> cuf,
TimestampSet timestampSet, boolean forceRefresh) {

View file

@ -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<RoutingContext> {
private final boolean wildcardOrigin;
private final boolean wildcardMethod;
private final List<Pattern> allowedOriginsRegex;
+ private final Set<String> allowedOriginsExact; // pre-built at construction: O(1) lookup per request
private final Set<HttpMethod> configuredHttpMethods;
private final String exposedHeaders;
@@ -40,6 +41,8 @@ public class CORSFilter implements Handler<RoutingContext> {
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<RoutingContext> {
//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));

View file

@ -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<Path>; 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<Path> (LinkedHashSet) O(C).
*
* quarkus-0004: CORSFilter.handle()
* extensions/vertx-http/.../cors/CORSFilter.java
* corsConfig.origins().get().contains(origin) on List<String> executed per
* HTTP request. O(O) per request where O = configured origins count.
* Fix: pre-build HashSet<String> 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<Integer> deduplicateList(List<Integer> inputs) {
List<Integer> 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<Integer> 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<Integer> deduplicateSet(List<Integer> inputs) {
Set<Integer> 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<Integer> 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<Integer, List<Integer>> dependencyMap = new TreeMap<>();
for (int b = 0; b < beanCount; b++) {
List<Integer> 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<Integer> dependants : dependencyMap.values()) { // O(B) map values
ops += dependants.size(); // ArrayList.contains scan cost
if (dependants.contains(queryBean)) {
break;
}
List<Integer> 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<Integer, List<Integer>> dependencyMap = new TreeMap<>();
for (int b = 0; b < beanCount; b++) {
List<Integer> 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<Integer> 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<Integer> allDependants = new HashSet<>();
for (List<Integer> 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<String> 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<String, Set<String>> buildDiamond(int depth) {
Map<String, Set<String>> 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<String> 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<String>.contains per class file — O(C * S). */
static int scanModuleDefect(List<String> classFiles, List<String> 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<String> 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<String> slowRecursiveBuild(String name,
Map<String, Set<String>> map,
AtomicLong callCount) {
callCount.incrementAndGet();
Set<String> 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<String> snapshot = new ArrayList<>(result);
for (String child : snapshot) {
if (map.containsKey(child)) {
result.addAll(slowRecursiveBuild(child, map, callCount)); // NO visited guard
/** Fix: Set<String>.contains per class file — O(C). */
static int scanModuleFix(List<String> classFiles, Set<String> 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<String> fastRecursiveBuildWithVisited(String name,
Map<String, Set<String>> map,
Set<String> 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<String> classFiles = new ArrayList<>(C);
List<String> changedSourceList = new ArrayList<>(S);
Set<String> changedSourceSet = new LinkedHashSet<>(S * 2);
for (int i = 0; i < C; i++) {
classFiles.add("com/example/Foo" + i + ".class");
}
Set<String> 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<String> fastRecursiveBuildInner(String name, Map<String, Set<String>> map,
AtomicLong callCount, Set<String> visited) {
return fastRecursiveBuildWithVisited(name, map, visited, callCount);
// -----------------------------------------------------------------------
// quarkus-0004: CORSFilter.handle origins list lookup per request
// -----------------------------------------------------------------------
/** Defect: List<String>.contains per HTTP request — O(O). */
static int handleRequestsDefect(List<String> 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<Integer> slowBound = new LinkedHashSet<>();
Set<Integer> fastBound = new LinkedHashSet<>();
int methods = 10, interceptors = 5;
// slow: uses ArrayList dedup but we track the same set for checking
List<Integer> 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<String>.contains per request — O(1). */
static int handleRequestsFix(Set<String> originsSet, String[] incomingOrigins) {
int allowed = 0;
for (String origin : incomingOrigins) {
if (originsSet.contains(origin)) { // O(1) per request fix
allowed++;
}
Set<Integer> 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<String> originsList = new ArrayList<>(O);
Set<String> 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<Integer, List<Integer>> 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<Integer> deps : dmap.values()) {
if (deps.contains(3)) { slowResult3 = true; break; }
}
for (List<Integer> deps : dmap.values()) {
if (deps.contains(7)) { slowResult7 = true; break; }
}
// fast: precompute set
Set<Integer> allDeps = new HashSet<>();
for (List<Integer> 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<String, Set<String>> map1 = buildDiamond(4);
AtomicLong slowCalls = new AtomicLong(0);
slowRecursiveBuild("root", map1, slowCalls);
Map<String, Set<String>> 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<String, Set<String>> map1 = buildDiamond(8);
AtomicLong slowCalls = new AtomicLong(0);
slowRecursiveBuild("root", map1, slowCalls);
Map<String, Set<String>> 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<String, Set<String>> 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<String> slowResult3 = map3.get("A"); // mutated in-place to include D
long t0 = System.nanoTime();
handleRequestsDefect(originsList, requests);
long defectNs = System.nanoTime() - t0;
Map<String, Set<String>> 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<String> 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");
}
}