3.3 KiB
UNDF: UNDF-2026-000000517
quarkus-0001: BeanInfo.getBoundInterceptors — O(I²) bound.contains in nested loops
CWE-407 — Algorithmic Complexity: Linear Membership Test in Loop
| Field | Value |
|---|---|
| ID | quarkus-0001 |
| Severity | HIGH |
| Ecosystem | quarkus |
| Package | arc/processor |
| File | independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java |
| Lines | 493, 500, 522 |
| Complexity | O(I²) where I = total interceptors across all interception registrations |
| Fix | Use LinkedHashSet<InterceptorInfo> for bound collection |
Description
getBoundInterceptors() builds a deduplicated List<InterceptorInfo> bound = new ArrayList<>().
For each InterceptionInfo in lifecycleInterceptors.values() (outer loop), it iterates
interception.interceptors (inner loop) and calls bound.contains(interceptor) — O(|bound|) on
ArrayList. The same pattern repeats for interceptedMethods.values().
Similarly, getBoundDecorators() at line 522 has the same pattern with List<DecoratorInfo>.
List<InterceptorInfo> bound = new ArrayList<>();
for (InterceptionInfo interception : lifecycleInterceptors.values()) { // O(M)
for (InterceptorInfo interceptor : interception.interceptors) { // O(I_per_method)
if (!bound.contains(interceptor)) { // O(|bound|) ArrayList
bound.add(interceptor);
}
}
}
for (InterceptionInfo interception : interceptedMethods.values()) { // O(M)
for (InterceptorInfo interceptor : interception.interceptors) { // O(I_per_method)
if (!bound.contains(interceptor)) { // O(|bound|) ArrayList
bound.add(interceptor);
}
}
}
Compounding Effect
getBoundInterceptors() is not cached — it recomputes on every call. It is called from:
ComponentsProviderGenerator.initBeanDependencyMap()— in a loop over all beansBeanDeployment— in loops checking removable interceptors (lines 429, 434)BeanGenerator,SubclassGenerator,InterceptionProxyGenerator— multiple hot paths
This means the O(I²) computation is repeated multiple times per bean per build phase.
Fix
// Before
List<InterceptorInfo> bound = new ArrayList<>();
// dedup via bound.contains()
// After — use LinkedHashSet for O(1) contains, convert to sorted List at end
Set<InterceptorInfo> boundSet = new LinkedHashSet<>();
for (InterceptionInfo interception : lifecycleInterceptors.values()) {
for (InterceptorInfo interceptor : interception.interceptors) {
boundSet.add(interceptor); // Set.add handles dedup, O(1)
}
}
for (InterceptionInfo interception : interceptedMethods.values()) {
for (InterceptorInfo interceptor : interception.interceptors) {
boundSet.add(interceptor);
}
}
List<InterceptorInfo> bound = new ArrayList<>(boundSet);
Collections.sort(bound);
Same pattern for getBoundDecorators().
Additionally, consider caching the result (memoize after initialize() is called).
Speedup Estimate
For a bean with 10 intercepted methods × 5 interceptors each: 50 × 25 average bound size = 1,250 operations → 50. 25x speedup per call, multiplied by the number of call sites per build.