# UNDF: UNDF-2026-000000697
# 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

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

```java
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

```diff
--- 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.
