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

140 lines
5.1 KiB
Markdown

# 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.