3.5 KiB
3.5 KiB
UNDF: UNDF-2026-000000556
UNDF: (pending)
tomcat-0002: BeanSupportStandalone.populateFromInterfaces — O(2^D) diamond re-traversal without visited set
CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal in standalone Jakarta EL bean property collection
| Field | Value |
|---|---|
| ID | tomcat-0002 |
| Severity | MEDIUM |
| Ecosystem | tomcat |
| Package | jakarta.el |
| File | java/jakarta/el/BeanSupportStandalone.java |
| Lines | 181–196 |
| Complexity | O(2^D) on diamond interface hierarchies |
| Hot path | Called during EL bean property discovery when not using full Introspector (standalone/embedded mode) |
Defect
BeanSupportStandalone.populateFromInterfaces has the same diamond re-traversal defect as
BeanSupportFull.populateFromInterfaces (tomcat-0001). No visited set; recursion is unconditional:
// BeanSupportStandalone.java:181-196 (DEFECT)
private void populateFromInterfaces(Class<?> aClass) {
Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> ifs : interfaces) {
PropertyDescriptor[] pds = getPropertyDescriptors(type); // note: uses 'type', not 'ifs'
for (PropertyDescriptor pd : pds) {
if (!this.properties.containsKey(pd.getName())) {
this.properties.put(pd.getName(), new BeanPropertyStandalone(this.type, pd));
}
}
populateFromInterfaces(ifs); // DEFECT: unconditional recursion — no visited guard
}
Class<?> superclass = aClass.getSuperclass();
if (superclass != null) {
populateFromInterfaces(superclass); // DEFECT: unconditional recursion — no visited guard
}
}
On a diamond hierarchy, the populateFromInterfaces recursion visits shared ancestor nodes 2^D times.
The property dedup at line 186 (properties.containsKey) prevents duplicate properties but does NOT
prevent the re-traversal cost — all recursive calls happen regardless.
Same root cause as tomcat-0001; independent occurrence in the standalone implementation.
Fix
Add a Set<Class<?>> visited parameter with a public wrapper:
// AFTER — O(N+E) where N=interfaces, E=hierarchy edges
private void populateFromInterfaces(Class<?> aClass) {
populateFromInterfaces(aClass, new HashSet<>());
}
private void populateFromInterfaces(Class<?> aClass, Set<Class<?>> visited) {
if (!visited.add(aClass)) { return; } // skip already-traversed types
Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> ifs : interfaces) {
PropertyDescriptor[] pds = getPropertyDescriptors(ifs); // also fix: use ifs, not type
for (PropertyDescriptor pd : pds) {
if (!this.properties.containsKey(pd.getName())) {
this.properties.put(pd.getName(), new BeanPropertyStandalone(this.type, pd));
}
}
populateFromInterfaces(ifs, visited);
}
Class<?> superclass = aClass.getSuperclass();
if (superclass != null) {
populateFromInterfaces(superclass, visited);
}
}
Speedup
| Diamond depth (D) | Before (traversals) | After (traversals) | Speedup |
|---|---|---|---|
| 5 | 31 | 5 | 6× |
| 10 | 1,023 | 10 | 102× |
| 15 | 32,767 | 15 | 2,184× |
Growth before: O(2^D). Growth after: O(D).