2.8 KiB
UNDF: UNDF-2026-000000462
micronaut-0002: MutableAnnotationMetadata — O(P×L) annotationList.contains in loop
CWE-407 — Algorithmic Complexity: Linear Membership Test in Loop
| Field | Value |
|---|---|
| ID | micronaut-0002 |
| Severity | MEDIUM |
| Ecosystem | micronaut-core |
| Package | micronaut-inject |
| File | inject/src/main/java/io/micronaut/inject/annotation/MutableAnnotationMetadata.java |
| Lines | 332, 350, 428, 491 |
| Complexity | O(P × L) where P = |
| Fix | Change backing store from ArrayList<String> to LinkedHashSet<String> |
Description
getAnnotationsByStereotypeInternal() returns a List<String> backed by new ArrayList<>().
Both addRepeatableStereotype and addDeclaredRepeatableStereotype iterate over parents (a
List<String>) and for each call annotationList.contains(parentAnnotation) — O(L) per parent.
This pattern appears at lines 332, 350, 428, and 491, so it affects all four stereotype-addition
paths.
List<String> annotationList = getAnnotationsByStereotypeInternal(stereotype); // ArrayList
for (String parentAnnotation : parents) { // O(P)
if (!annotationList.contains(parentAnnotation)) { // O(L) ArrayList scan
annotationList.add(parentAnnotation);
}
}
Since annotationList grows as parents are added, and this runs for every stereotype registration,
the total cost per stereotype with P parents is O(P × L) where L can approach P in the worst case
→ O(P²).
Impact
Annotation metadata is built at startup for every bean, every method, every field that carries annotations. In Micronaut applications with extensive annotation-based configuration (validation, security, caching, AOP), the startup time is directly impacted.
Fix
Change the backing store in getAnnotationsByStereotypeInternal:
// Before
return getAnnotationsByStereotypeInternal().computeIfAbsent(stereotype, s -> new ArrayList<>());
// After
return getAnnotationsByStereotypeInternal().computeIfAbsent(stereotype, s -> new ArrayList<>());
// (keep the List<String> API but use a separate Set for dedup tracking)
Better: change the map value type to Set<String> (LinkedHashSet for order preservation):
// In getAnnotationsByStereotypeInternal():
private Map<String, List<String>> getAnnotationsByStereotypeInternal()
// → change to Map<String, Set<String>> or use LinkedHashSet and adapt callers
Simplest compatible fix: use LinkedHashSet<String> via the existing List interface —
change the computeIfAbsent lambda to new LinkedHashSet<>() and cast where needed.
Speedup Estimate
For P=15 parents per stereotype, L≈P: 15² = 225 ops → 15 ops. 15x speedup per stereotype registration. Multiplied across all beans at startup.