New UNDF assignments (693→720): elixir-0002 → UNDF-2026-000000698 (typespec used_type_pairs O(T²)) r-source-0002 → UNDF-2026-000000711 (.walkClassGraph match dedup O(S²)) ruby-0003 → UNDF-2026-000000712 (RubyGems dependent_gems O(N²×D)) victoria-metrics-0002 → UNDF-2026-000000717 (MetricName tag-filter O(T×I)) Total: 720 UNDF assigned
5.1 KiB
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
// 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.