java-topology/defects/dubbo/patch/dubbo-0002-method-walker-diamond-recursion.md
russell@unturf.com 72c98d9af6 netty-0001 + dubbo-0002 + doris-0004: DNS dedup O(R²); MethodWalker O(2^D); NormalizeRepeat O(S×G); count 621→624
- netty-0001: DnsResolveContext.finalResult ArrayList.contains O(R²) dedup on DNS records
  File: resolver-dns/.../dns/DnsResolveContext.java line ~914
  Fix: LinkedHashSet gives O(1) dedup with preserved insertion order
  Ratio: 24.5x at R=50 records

- dubbo-0002: MethodWalker.walkHierarchy no visited guard — O(2^D) diamond recursion
  File: dubbo-rpc-triple/.../rest/util/MethodWalker.java walkHierarchy()
  Fix: add visited HashSet, return early if already visited
  Ratio: 8x at D=3 (common Spring proxy depth)

- doris-0004: NormalizeRepeat.buildContextWithAlias ImmutableList.contains O(S×G) for GROUPING SETS
  File: fe-core/.../nereids/rules/analysis/NormalizeRepeat.java buildContextWithAlias()
  Fix: convert groupingSetExpressions to HashSet before loop — O(1) lookup
  Ratio: 49x for CUBE(c1..c8), 1000x+ for CUBE(c1..c10)
2026-03-29 22:11:57 -04:00

5.1 KiB

dubbo-0002: MethodWalker.walkHierarchy diamond recursion O(2^D)

Classification

  • CWE: CWE-407 (Inefficient Algorithmic Complexity)
  • Severity: MEDIUM
  • Component: Apache Dubbo — dubbo-rpc-triple
  • File: dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java
  • Method: walkHierarchy(Class<?>)
  • Complexity: O(2^D) where D = diamond depth in interface inheritance hierarchy

Description

MethodWalker.walkHierarchy recursively traverses the class and interface hierarchy to collect method mappings for Triple REST service registration. It recurses into clazz.getSuperclass() and all elements of clazz.getInterfaces(), but has no visited set guard. The classes field is a LinkedHashSet, but it is not used to guard recursion — it is updated only conditionally (if classes.isEmpty() || clazz has annotations), not as a visited guard.

When the hierarchy contains a diamond pattern — two interfaces A and B both extend a common interface C, and a service class implements both A and B — walkHierarchy(C) is called twice. At depth D, the call count is O(2^D).

This is called from DefaultRequestMappingRegistry.register() during service startup/registration for Triple REST endpoints. For a service class that implements multiple interfaces with shared superinterfaces (common with Spring proxy classes), this causes redundant traversal and duplicate method entries in methodsMap.

Defect Code

// dubbo-rpc/dubbo-rpc-triple/.../rest/util/MethodWalker.java
private void walkHierarchy(Class<?> clazz) {
    if (classes.isEmpty() || clazz.getDeclaredAnnotations().length > 0) {
        classes.add(clazz);   // NOT a visited guard - conditional add only
    }
    for (Method method : clazz.getDeclaredMethods()) {
        // adds methods to methodsMap...
    }
    Class<?> superClass = clazz.getSuperclass();
    if (superClass != null && superClass != Object.class) {
        walkHierarchy(superClass);           // no visited check
    }
    for (Class<?> itf : clazz.getInterfaces()) {
        walkHierarchy(itf);                  // no visited check - diamond paths revisited!
    }
}

Example Diamond

interface Base { void baseMethod(); }
interface A extends Base {}
interface B extends Base {}
class Service implements A, B { ... }

walkHierarchy(Service) calls:

  • walkHierarchy(A)walkHierarchy(Base) (1st visit, baseMethod added once)
  • walkHierarchy(B)walkHierarchy(Base) (2nd visit, baseMethod added again)

Result: methodsMap contains [baseMethod: [Method, Method]] — duplicate entries. For the consuming resolver, duplicate method processing causes redundant work.

Fix

Add a visited set and check before recursing:

private final Set<Class<?>> visited = new HashSet<>();

private void walkHierarchy(Class<?> clazz) {
    if (!visited.add(clazz)) {
        return;   // already processed this class/interface — skip
    }
    if (classes.isEmpty() || clazz.getDeclaredAnnotations().length > 0) {
        classes.add(clazz);
    }
    for (Method method : clazz.getDeclaredMethods()) {
        int modifiers = method.getModifiers();
        if ((modifiers & (Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) {
            methodsMap
                    .computeIfAbsent(Key.of(method), k -> new ArrayList<>())
                    .add(method);
        }
    }
    Class<?> superClass = clazz.getSuperclass();
    if (superClass != null && superClass != Object.class) {
        walkHierarchy(superClass);
    }
    for (Class<?> itf : clazz.getInterfaces()) {
        walkHierarchy(itf);
    }
}

Patch

--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java
@@ -29,8 +29,11 @@ import java.util.function.BiConsumer;
 import java.util.function.Consumer;

 public final class MethodWalker {

     private final Set<Class<?>> classes = new LinkedHashSet<>();
+    private final Set<Class<?>> visited = new HashSet<>();
     private final Map<Key, List<Method>> methodsMap = new HashMap<>();

@@ -48,6 +51,9 @@ public final class MethodWalker {
     }

     private void walkHierarchy(Class<?> clazz) {
+        if (!visited.add(clazz)) {
+            return;
+        }
         if (classes.isEmpty() || clazz.getDeclaredAnnotations().length > 0) {
             classes.add(clazz);
         }

Complexity Comparison

Depth D (diamond depth) Old (no guard) visits New (HashSet guard) visits
1 2 1
3 8 1
5 32 1
10 1,024 1

At D=3 (common in Spring proxy hierarchies): 8x speedup.

Hot Path

Called during Triple REST service registration in DefaultRequestMappingRegistry.register(). Executed once per service at startup, but in large applications with hundreds of service beans and deep interface hierarchies (especially Spring CGLIB proxies), the cumulative effect is significant.