java-topology/defects/tomcat/patch/tomcat-0002-beansupportstandalone-populatefrominterfaces-diamond.md

3.5 KiB
Raw Blame History

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 181196
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).