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

4.4 KiB

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

// 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:

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