java-topology/defects/dubbo/patch/dubbo-0001-getAllMetaAnnotations-diamond-recursion.md
russell@unturf.com 52a8d535a2 dubbo-0001 + cxf-0001: annotation diamond recursion O(2^D); WSDL import O(N²); count 621→623
dubbo-0001: AnnotationUtils.getAllMetaAnnotations recurses into meta-annotation graph
without a visited set — diamond shapes cause O(2^D) calls (UNDF-2026-000000238)

cxf-0001: SchemaUtil.parseImports + WSDLServiceBuilder.parseImports use ArrayList.contains
as visited guard on WSDL import graph — O(N²) membership checks (UNDF-2026-000000237)

Struts, RabbitMQ, Camel: CLEAN
2026-03-29 18:23:19 -04:00

116 lines
4.4 KiB
Markdown

# UNDF: UNDF-2026-000000238
# dubbo-0001: AnnotationUtils.getAllMetaAnnotations diamond recursion O(2^D)
## Classification
- **CWE**: CWE-407 (Inefficient Algorithmic Complexity)
- **Severity**: MEDIUM
- **Component**: Apache Dubbo — `dubbo-common`
- **File**: `dubbo-common/src/main/java/org/apache/dubbo/common/utils/AnnotationUtils.java`
- **Method**: `getAllMetaAnnotations(Class<? extends Annotation>, Predicate<Annotation>...)`
## Description
`getAllMetaAnnotations` recursively traverses the meta-annotation graph of a Java annotation
type without a visited set. When the annotation DAG has a diamond shape — two annotations B and
C both carry meta-annotation D, and a top-level annotation A carries both B and C — the method
visits D twice. At depth D the call count is O(2^D).
## Defect Code
```java
// dubbo-common/.../utils/AnnotationUtils.java lines 281-296
static List<Annotation> getAllMetaAnnotations(
Class<? extends Annotation> annotationType, Predicate<Annotation>... annotationsToFilter) {
List<Annotation> allMetaAnnotations = new LinkedList<>();
List<Annotation> metaAnnotations = getMetaAnnotations(annotationType); // direct meta-annotations
allMetaAnnotations.addAll(metaAnnotations);
for (Annotation metaAnnotation : metaAnnotations) {
// Get the nested meta annotations recursively
allMetaAnnotations.addAll(getAllMetaAnnotations(metaAnnotation.annotationType()));
// NO visited set: diamond causes 2^D recursive calls
}
return unmodifiableList(filterAll(allMetaAnnotations, annotationsToFilter));
}
```
## Diamond Example
```
@A
|-meta-> @B
| |-meta-> @D <-- visited ONCE per path
|-meta-> @C
|-meta-> @D <-- visited AGAIN (second path)
Depth 1 diamond: getAllMetaAnnotations(@D) called 2 times
Depth 2 diamond: 4 times
Depth D: 2^D times
```
In a Dubbo deployment with composite Dubbo service annotations (e.g., `@DubboService` carrying
`@Service`, `@Component`, and custom composed annotations), this pattern can occur at annotation
processor startup time. The method is called from `findMetaAnnotations`, which is called during
service registration and reference injection on every annotated class.
## Callers
- `findMetaAnnotations(Class<? extends Annotation>, Class<A>)` — line 334
- `findMetaAnnotations(AnnotatedElement, Class<A>)` — line 351 (calls `findMetaAnnotations` on
each declared annotation's type, which internally calls `getAllMetaAnnotations`)
## Complexity
| D (diamond depth) | Call count | Speedup with fix |
|---|---|---|
| 1 | 2 | 2x |
| 5 | 32 | 32x |
| 10 | 1024 | 1024x |
| 15 | 32768 | ~33000x |
In practice D is small (3-5 for typical Java meta-annotation hierarchies), but Dubbo's rich
composed-annotation ecosystem (e.g., `@DubboService` + `@EnableDubbo` + framework annotations)
can create D=5+ chains, giving 32x+ redundant work at startup.
## Fix
Pass a `Set<Class<? extends Annotation>> visited` accumulator through the recursion:
```java
static List<Annotation> getAllMetaAnnotations(
Class<? extends Annotation> annotationType, Predicate<Annotation>... annotationsToFilter) {
Set<Class<? extends Annotation>> visited = new LinkedHashSet<>();
return getAllMetaAnnotations(annotationType, visited, annotationsToFilter);
}
private static List<Annotation> getAllMetaAnnotations(
Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visited,
Predicate<Annotation>... annotationsToFilter) {
List<Annotation> allMetaAnnotations = new LinkedList<>();
List<Annotation> metaAnnotations = getMetaAnnotations(annotationType);
allMetaAnnotations.addAll(metaAnnotations);
for (Annotation metaAnnotation : metaAnnotations) {
Class<? extends Annotation> metaType = metaAnnotation.annotationType();
if (visited.add(metaType)) { // skip if already visited
allMetaAnnotations.addAll(getAllMetaAnnotations(metaType, visited));
}
}
return unmodifiableList(filterAll(allMetaAnnotations, annotationsToFilter));
}
```
## Evidence
- File: `dubbo-common/src/main/java/org/apache/dubbo/common/utils/AnnotationUtils.java`
- Lines 281-296: recursive call with no visited guard
- No `Set`, `Map`, or `cache` parameter in method signature
- `getMetaAnnotations` excludes `@Target`, `@Retention`, `@Documented` to avoid Java built-in
annotation self-references — but does not prevent application-defined diamond shapes