From 9d273094e8a879b25e76ae528816bce04e48521e Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 29 Mar 2026 20:17:52 -0400 Subject: [PATCH] diamond-scan: spring/hibernate/weld/quarkus/micronaut O(2^D) hierarchy traversal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5 new defects across 5 repos — all diamond recursion (map/set .put/.add return value ignored before unconditional recurse): - spring-0001: AnnotationsScanner.processClassHierarchy TYPE_HIERARCHY mode, no visited guard, used by every @Transactional/@Autowired/@RequestMapping scan - hibernate-0001: ClassHierarchyHelper.getImplementedInterfaces, Set.add() return ignored before unconditional recurse — hibernate-validator - weld-0004: HierarchyDiscovery.discoverTypes, HashMap.put() return ignored, CDI bean type closure discovery at startup - quarkus-0004: HierarchyDiscovery.discoverTypes (ArC, copied from Weld), same defect — Quarkus startup + native image - micronaut-0005: NativeElementsHelper.populateTypeHierarchy, no visited guard - micronaut-0006: GenericUtils.populateTypeArgumentsForInterfaces, recursion outside containsKey guard Also rename cpython-0001→0002, 0002→0003, 0003→0004 (pre-existing renumber). --- ...on-0002-pydoc-allmethods-diamond-bases.md} | 4 +- ...n-0003-turtle-methoddict-diamond-bases.md} | 4 +- ...4-idlelib-rpc-getmethods-diamond-bases.md} | 4 +- ...ss-hierarchy-helper-diamond-retraversal.md | 79 +++++++++++ ...late-type-hierarchy-diamond-retraversal.md | 101 ++++++++++++++ ...ype-args-interfaces-diamond-retraversal.md | 97 ++++++++++++++ ...hierarchy-discovery-diamond-retraversal.md | 96 ++++++++++++++ ...nner-type-hierarchy-diamond-retraversal.md | 124 ++++++++++++++++++ ...hierarchy-discovery-diamond-retraversal.md | 104 +++++++++++++++ 9 files changed, 607 insertions(+), 6 deletions(-) rename defects/cpython/patch/{cpython-0001-pydoc-allmethods-diamond-bases.md => cpython-0002-pydoc-allmethods-diamond-bases.md} (96%) rename defects/cpython/patch/{cpython-0002-turtle-methoddict-diamond-bases.md => cpython-0003-turtle-methoddict-diamond-bases.md} (96%) rename defects/cpython/patch/{cpython-0003-idlelib-rpc-getmethods-diamond-bases.md => cpython-0004-idlelib-rpc-getmethods-diamond-bases.md} (96%) create mode 100644 defects/hibernate/patch/hibernate-0001-class-hierarchy-helper-diamond-retraversal.md create mode 100644 defects/micronaut/patch/micronaut-0005-populate-type-hierarchy-diamond-retraversal.md create mode 100644 defects/micronaut/patch/micronaut-0006-populate-type-args-interfaces-diamond-retraversal.md create mode 100644 defects/quarkus/patch/quarkus-0004-hierarchy-discovery-diamond-retraversal.md create mode 100644 defects/spring/patch/spring-0001-annotations-scanner-type-hierarchy-diamond-retraversal.md create mode 100644 defects/weld/patch/weld-0004-hierarchy-discovery-diamond-retraversal.md diff --git a/defects/cpython/patch/cpython-0001-pydoc-allmethods-diamond-bases.md b/defects/cpython/patch/cpython-0002-pydoc-allmethods-diamond-bases.md similarity index 96% rename from defects/cpython/patch/cpython-0001-pydoc-allmethods-diamond-bases.md rename to defects/cpython/patch/cpython-0002-pydoc-allmethods-diamond-bases.md index 74b5470cc..bf67d8762 100644 --- a/defects/cpython/patch/cpython-0001-pydoc-allmethods-diamond-bases.md +++ b/defects/cpython/patch/cpython-0002-pydoc-allmethods-diamond-bases.md @@ -1,11 +1,11 @@ # UNDF: (pending) -# cpython-0001: pydoc.allmethods — O(2^D) diamond base traversal +# cpython-0002: pydoc.allmethods — O(2^D) diamond base traversal ## CWE-407 — Algorithmic Complexity: O(2^D) diamond mixin/base traversal | Field | Value | |-------|-------| -| ID | cpython-0001 | +| ID | cpython-0002 | | Severity | MEDIUM | | Ecosystem | cpython | | Package | pydoc | diff --git a/defects/cpython/patch/cpython-0002-turtle-methoddict-diamond-bases.md b/defects/cpython/patch/cpython-0003-turtle-methoddict-diamond-bases.md similarity index 96% rename from defects/cpython/patch/cpython-0002-turtle-methoddict-diamond-bases.md rename to defects/cpython/patch/cpython-0003-turtle-methoddict-diamond-bases.md index c4dd5c76f..dac17e1a3 100644 --- a/defects/cpython/patch/cpython-0002-turtle-methoddict-diamond-bases.md +++ b/defects/cpython/patch/cpython-0003-turtle-methoddict-diamond-bases.md @@ -1,11 +1,11 @@ # UNDF: (pending) -# cpython-0002: turtle.__methodDict — O(2^D) diamond base traversal +# cpython-0003: turtle.__methodDict — O(2^D) diamond base traversal ## CWE-407 — Algorithmic Complexity: O(2^D) diamond mixin/base traversal | Field | Value | |-------|-------| -| ID | cpython-0002 | +| ID | cpython-0003 | | Severity | MEDIUM | | Ecosystem | cpython | | Package | turtle | diff --git a/defects/cpython/patch/cpython-0003-idlelib-rpc-getmethods-diamond-bases.md b/defects/cpython/patch/cpython-0004-idlelib-rpc-getmethods-diamond-bases.md similarity index 96% rename from defects/cpython/patch/cpython-0003-idlelib-rpc-getmethods-diamond-bases.md rename to defects/cpython/patch/cpython-0004-idlelib-rpc-getmethods-diamond-bases.md index 3b62e8d41..154ea98b5 100644 --- a/defects/cpython/patch/cpython-0003-idlelib-rpc-getmethods-diamond-bases.md +++ b/defects/cpython/patch/cpython-0004-idlelib-rpc-getmethods-diamond-bases.md @@ -1,11 +1,11 @@ # UNDF: (pending) -# cpython-0003: idlelib.rpc._getmethods — O(2^D) diamond base traversal +# cpython-0004: idlelib.rpc._getmethods — O(2^D) diamond base traversal ## CWE-407 — Algorithmic Complexity: O(2^D) diamond mixin/base traversal | Field | Value | |-------|-------| -| ID | cpython-0003 | +| ID | cpython-0004 | | Severity | MEDIUM | | Ecosystem | cpython | | Package | idlelib.rpc | diff --git a/defects/hibernate/patch/hibernate-0001-class-hierarchy-helper-diamond-retraversal.md b/defects/hibernate/patch/hibernate-0001-class-hierarchy-helper-diamond-retraversal.md new file mode 100644 index 000000000..1c869cdf3 --- /dev/null +++ b/defects/hibernate/patch/hibernate-0001-class-hierarchy-helper-diamond-retraversal.md @@ -0,0 +1,79 @@ +# UNDF: (pending) +# hibernate-0001: ClassHierarchyHelper.getImplementedInterfaces — O(2^D) diamond re-traversal + +## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal + +| Field | Value | +|-------|-------| +| ID | hibernate-0001 | +| Severity | HIGH | +| Ecosystem | hibernate-validator | +| Package | org.hibernate.validator.internal.util.classhierarchy | +| File | `engine/src/main/java/org/hibernate/validator/internal/util/classhierarchy/ClassHierarchyHelper.java` | +| Lines | 114–121 | +| Complexity | O(2^D) on diamond interface hierarchies | +| Hot path | Bean validation constraint discovery — every constrained bean validation call | + +## Defect + +```java +// DEFECT: Set.add() return value IGNORED, then unconditional recursion +private static void getImplementedInterfaces( + Class clazz, Set> classes) { + for (Class currentInterface : clazz.getInterfaces()) { + @SuppressWarnings("unchecked") + Class currentInterfaceCasted = (Class) currentInterface; + classes.add(currentInterfaceCasted); // return value IGNORED + getImplementedInterfaces(currentInterfaceCasted, classes); // UNCONDITIONAL → O(2^D) + } +} +``` + +`Set.add()` returns `true` if the element was newly inserted, `false` if it +was already present. The return value is discarded, and `getImplementedInterfaces` +is called unconditionally on every interface including already-visited ones. + +For a diamond interface hierarchy `A implements B,C; B extends D; C extends D`: +- Processing `B`: adds `D`, recurses into `D` (adds D's interfaces) +- Processing `C`: adds `D` (already there, add returns false — IGNORED), then + recurses into `D` again → full re-traversal of D's subtree + +At diamond depth D, 2^D recursive calls occur. + +## Fix + +```java +// CORRECT: check Set.add() return value — skip if already visited +private static void getImplementedInterfaces( + Class clazz, Set> classes) { + for (Class currentInterface : clazz.getInterfaces()) { + @SuppressWarnings("unchecked") + Class currentInterfaceCasted = (Class) currentInterface; + if (classes.add(currentInterfaceCasted)) { // guard: only recurse if newly added + getImplementedInterfaces(currentInterfaceCasted, classes); + } + } +} +``` + +## Speedup + +| Diamond depth (D) | Before (traversals) | After (traversals) | Speedup | +|------------------|--------------------|--------------------|---------| +| 5 | 31 | 5 | 6× | +| 10 | 1,023 | 10 | 102× | +| 15 | 32,767 | 15 | 2,184× | +| 20 | 1,048,575 | 20 | 52,428× | + +## Impact + +`getImplementedInterfaces` is called from `getDirectlyImplementedInterfaces`, +which is used during Hibernate Validator constraint metadata discovery. Beans +with deep interface diamond hierarchies (common in layered service architectures +or generic repository patterns) will experience exponential constraint discovery +time. In extreme cases this causes application startup to hang. + +Note: `getHierarchy` (lines 65–81) also lacks a proper guard — it uses +`classes.contains(current)` (O(N) List search) and only checks `current` in +the superclass walk, not before recurring into interface subtrees — but this +is a separate O(N²) issue, not a diamond O(2^D) defect. diff --git a/defects/micronaut/patch/micronaut-0005-populate-type-hierarchy-diamond-retraversal.md b/defects/micronaut/patch/micronaut-0005-populate-type-hierarchy-diamond-retraversal.md new file mode 100644 index 000000000..44109d75b --- /dev/null +++ b/defects/micronaut/patch/micronaut-0005-populate-type-hierarchy-diamond-retraversal.md @@ -0,0 +1,101 @@ +# UNDF: (pending) +# micronaut-0005: NativeElementsHelper.populateTypeHierarchy — O(2^D) diamond hierarchy re-traversal + +## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal + +| Field | Value | +|-------|-------| +| ID | micronaut-0005 | +| Severity | HIGH | +| Ecosystem | micronaut-core | +| Package | io.micronaut.inject.utils | +| File | `core-processor/src/main/java/io/micronaut/inject/utils/NativeElementsHelper.java` | +| Lines | 139–150 | +| Complexity | O(2^D) on diamond interface hierarchies | +| Hot path | Annotation processing — every bean type hierarchy traversal at compile time | + +## Defect + +```java +// DEFECT: no visited guard; hierarchy.add() called AFTER unconditional recursion +public final void populateTypeHierarchy(C element, List hierarchy) { + for (C anInterface : getInterfaces(element)) { + populateTypeHierarchy(anInterface, hierarchy); // UNCONDITIONAL → O(2^D) + } + C superClass = getSuperClass(element); + if (superClass != null) { + populateTypeHierarchy(superClass, hierarchy); // UNCONDITIONAL → O(2^D) + } + if (!excludeClass(element)) { + hierarchy.add(element); // added AFTER recursion, so duplicate check must be pre-recursion + } +} +``` + +For a diamond interface hierarchy `A implements B,C; B extends D; C extends D`, +calling `populateTypeHierarchy(A, list)` will call: +- `populateTypeHierarchy(B, list)` → `populateTypeHierarchy(D, list)` [adds D] +- `populateTypeHierarchy(C, list)` → `populateTypeHierarchy(D, list)` [adds D again] + +`D` is traversed and added twice. At diamond depth D, 2^D traversals occur. +The `List hierarchy` can also contain duplicates since there is no +deduplication before recursing. + +## Fix + +```java +// CORRECT: check if element already in hierarchy before recursing +public final void populateTypeHierarchy(C element, List hierarchy) { + if (hierarchy.contains(element)) { return; } // guard: O(N) but prevents O(2^D) + // Better: pass a Set visited parameter for O(1) guard + for (C anInterface : getInterfaces(element)) { + populateTypeHierarchy(anInterface, hierarchy); + } + C superClass = getSuperClass(element); + if (superClass != null) { + populateTypeHierarchy(superClass, hierarchy); + } + if (!excludeClass(element)) { + hierarchy.add(element); + } +} +``` + +Alternatively (preferred — O(1) visited check): + +```java +public final void populateTypeHierarchy(C element, List hierarchy) { + Set visited = new HashSet<>(); + populateTypeHierarchy(element, hierarchy, visited); +} + +private void populateTypeHierarchy(C element, List hierarchy, Set visited) { + if (!visited.add(element)) { return; } // guard: skip if already visited + for (C anInterface : getInterfaces(element)) { + populateTypeHierarchy(anInterface, hierarchy, visited); + } + C superClass = getSuperClass(element); + if (superClass != null) { + populateTypeHierarchy(superClass, hierarchy, visited); + } + if (!excludeClass(element)) { + hierarchy.add(element); + } +} +``` + +## Speedup + +| Diamond depth (D) | Before (traversals) | After (traversals) | Speedup | +|------------------|--------------------|--------------------|---------| +| 5 | 31 | 5 | 6× | +| 10 | 1,023 | 10 | 102× | +| 15 | 32,767 | 15 | 2,184× | +| 20 | 1,048,575 | 20 | 52,428× | + +## Impact + +`populateTypeHierarchy` is called during annotation processing (compile time) +for every class element traversal in Micronaut's bean model construction. +Deep diamond hierarchies in service/repository classes will cause exponential +annotation-processor execution time, slowing builds significantly. diff --git a/defects/micronaut/patch/micronaut-0006-populate-type-args-interfaces-diamond-retraversal.md b/defects/micronaut/patch/micronaut-0006-populate-type-args-interfaces-diamond-retraversal.md new file mode 100644 index 000000000..53b78974e --- /dev/null +++ b/defects/micronaut/patch/micronaut-0006-populate-type-args-interfaces-diamond-retraversal.md @@ -0,0 +1,97 @@ +# UNDF: (pending) +# micronaut-0006: GenericUtils.populateTypeArgumentsForInterfaces — O(2^D) diamond re-traversal + +## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal + +| Field | Value | +|-------|-------| +| ID | micronaut-0006 | +| Severity | MEDIUM | +| Ecosystem | micronaut-core | +| Package | io.micronaut.annotation.processing | +| File | `inject-java/src/main/java/io/micronaut/annotation/processing/GenericUtils.java` | +| Lines | 334–354 | +| Complexity | O(2^D) on diamond interface hierarchies | +| Hot path | Generic type argument resolution — annotation processing at compile time | + +## Defect + +```java +private void populateTypeArgumentsForInterfaces( + Map> typeArguments, TypeElement child) { + for (TypeMirror anInterface : child.getInterfaces()) { + if (anInterface instanceof DeclaredType declaredType) { + Element element = declaredType.asElement(); + if (element instanceof TypeElement te) { + String name = JavaModelUtils.getClassName(te); + if (!typeArguments.containsKey(name)) { // containsKey guard... + // ... resolves and puts type arguments + if (!types.isEmpty()) { + typeArguments.put(name, types); + } + } + populateTypeArgumentsForInterfaces(typeArguments, te); // UNCONDITIONAL → O(2^D) + // ^ recursion is OUTSIDE the if-block — happens even when + // typeArguments.containsKey(name) is true (already processed) + } + } + } +} +``` + +The `if (!typeArguments.containsKey(name))` guard at line 340 skips the type +argument resolution for already-seen interfaces, but the recursive call at +line 350 is OUTSIDE this guard — it executes unconditionally for every +interface, including those already fully processed. + +For a diamond hierarchy `A implements B,C; B extends D; C extends D`, the +call from processing `C` will recurse into `D` even though `D` was already +fully processed when `B` was handled. At depth D, 2^D recursive calls occur. + +## Fix + +```java +// CORRECT: move recursion inside the guard, or add a separate visited set +private void populateTypeArgumentsForInterfaces( + Map> typeArguments, TypeElement child) { + for (TypeMirror anInterface : child.getInterfaces()) { + if (anInterface instanceof DeclaredType declaredType) { + Element element = declaredType.asElement(); + if (element instanceof TypeElement te) { + String name = JavaModelUtils.getClassName(te); + if (!typeArguments.containsKey(name)) { + Map boundTypes = typeArguments.get(JavaModelUtils.getClassName(child)); + if (boundTypes == null) { + boundTypes = Collections.emptyMap(); + } + Map types = resolveGenericTypes(declaredType, te, boundTypes); + if (!types.isEmpty()) { + typeArguments.put(name, types); + } + populateTypeArgumentsForInterfaces(typeArguments, te); // recurse INSIDE guard + } + // If containsKey(name) is true, te was already fully processed — skip recursion + } + } + } +} +``` + +Alternative: use a separate `Set visited` passed through the +recursion for O(1) guard instead of relying on map membership. + +## Speedup + +| Diamond depth (D) | Before (traversals) | After (traversals) | Speedup | +|------------------|--------------------|--------------------|---------| +| 5 | 31 | 5 | 6× | +| 10 | 1,023 | 10 | 102× | +| 15 | 32,767 | 15 | 2,184× | + +## Impact + +`populateTypeArgumentsForInterfaces` is called during compile-time annotation +processing to resolve generic type parameters across the class hierarchy. +Applications with deep generic interface diamonds (common in repository +patterns, e.g. `Repository extends CrudRepository` shared by +multiple intermediate interfaces) will experience exponential build slowdowns. diff --git a/defects/quarkus/patch/quarkus-0004-hierarchy-discovery-diamond-retraversal.md b/defects/quarkus/patch/quarkus-0004-hierarchy-discovery-diamond-retraversal.md new file mode 100644 index 000000000..8665013af --- /dev/null +++ b/defects/quarkus/patch/quarkus-0004-hierarchy-discovery-diamond-retraversal.md @@ -0,0 +1,96 @@ +# UNDF: (pending) +# quarkus-0004: HierarchyDiscovery.discoverTypes — O(2^D) diamond type closure re-traversal + +## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal + +| Field | Value | +|-------|-------| +| ID | quarkus-0004 | +| Severity | HIGH | +| Ecosystem | quarkus | +| Package | io.quarkus.arc.impl | +| File | `independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/HierarchyDiscovery.java` | +| Lines | 58–87 | +| Complexity | O(2^D) on diamond interface/type hierarchies | +| Hot path | ArC CDI bean type closure discovery — startup, injection point resolution | + +## Defect + +```java +// DEFECT: types.put() return value IGNORED, then unconditional recurse +protected void discoverTypes(Type type, boolean rawGeneric) { + if (type instanceof Class) { + Class clazz = (Class) type; + this.types.put(clazz, clazz); // return value IGNORED + discoverFromClass(clazz, rawGeneric); // UNCONDITIONAL → O(2^D) + } else if (rawGeneric) { + discoverTypes(Types.getRawType(type), rawGeneric); + } else if (type instanceof GenericArrayType) { + // ... + this.types.put(arrayClass, type); // return value IGNORED + discoverFromClass(arrayClass, rawGeneric); // UNCONDITIONAL + } else if (isParameterizedType(type)) { + // ... + this.types.put(clazz, type); // return value IGNORED + discoverFromClass(clazz, rawGeneric); // UNCONDITIONAL + } +} +``` + +This code is a near-identical copy of Weld's `HierarchyDiscovery` (even retains +the Weld author attribution comment) and inherits the same diamond defect. +`HashMap.put()` returns the prior value when a key already exists; if the class +was already discovered, `discoverFromClass` is redundant but is still called. + +For a diamond interface hierarchy at depth D, type closure discovery performs +2^D redundant traversals. + +## Fix + +```java +// CORRECT: guard on put() return value +protected void discoverTypes(Type type, boolean rawGeneric) { + if (type instanceof Class) { + Class clazz = (Class) type; + if (this.types.put(clazz, clazz) != null) { return; } + discoverFromClass(clazz, rawGeneric); + } else if (rawGeneric) { + discoverTypes(Types.getRawType(type), rawGeneric); + } else if (type instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) type; + Type genericComponentType = arrayType.getGenericComponentType(); + Class rawComponentType = Types.getRawType(genericComponentType); + if (rawComponentType != null) { + Class arrayClass = Array.newInstance(rawComponentType, 0).getClass(); + if (this.types.put(arrayClass, type) != null) { return; } + discoverFromClass(arrayClass, rawGeneric); + } + } else if (isParameterizedType(type)) { + final ParameterizedType parameterizedType = asParameterizedType(type); + final Type rawType = parameterizedType.getRawType(); + if (rawType instanceof Class) { + Class clazz = (Class) rawType; + processTypeVariables(clazz.getTypeParameters(), parameterizedType.getActualTypeArguments()); + if (this.types.put(clazz, type) != null) { return; } + discoverFromClass(clazz, rawGeneric); + } + } +} +``` + +## Speedup + +| Diamond depth (D) | Before (traversals) | After (traversals) | Speedup | +|------------------|--------------------|--------------------|---------| +| 5 | 31 | 5 | 6× | +| 10 | 1,023 | 10 | 102× | +| 15 | 32,767 | 15 | 2,184× | +| 20 | 1,048,575 | 20 | 52,428× | + +## Impact + +ArC is Quarkus's CDI container. `HierarchyDiscovery` computes the bean type +closure at startup for every discovered bean. Quarkus applications with +diamond interface hierarchies in their CDI beans will experience quadratic-to- +exponential startup time growth. Native builds are especially affected since +startup is expected to be near-instant. diff --git a/defects/spring/patch/spring-0001-annotations-scanner-type-hierarchy-diamond-retraversal.md b/defects/spring/patch/spring-0001-annotations-scanner-type-hierarchy-diamond-retraversal.md new file mode 100644 index 000000000..5f973f107 --- /dev/null +++ b/defects/spring/patch/spring-0001-annotations-scanner-type-hierarchy-diamond-retraversal.md @@ -0,0 +1,124 @@ +# UNDF: (pending) +# spring-0001: AnnotationsScanner.processClassHierarchy — O(2^D) diamond annotation re-traversal + +## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal + +| Field | Value | +|-------|-------| +| ID | spring-0001 | +| Severity | HIGH | +| Ecosystem | spring-framework | +| Package | org.springframework.core.annotation | +| File | `spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java` | +| Lines | 173–232 | +| Complexity | O(2^D) on diamond interface/annotation hierarchies | +| Hot path | `MergedAnnotations.from(element, TYPE_HIERARCHY)` — used by every `@Autowired`, `@Component`, AOP advisor scan | + +## Defect + +```java +// DEFECT: no visited set; interfaces recursed unconditionally with no diamond guard +private static @Nullable R processClassHierarchy(C context, int[] aggregateIndex, + Class source, AnnotationsProcessor processor, + boolean includeInterfaces, Predicate> searchEnclosingClass) { + try { + R result = processor.doWithAggregate(context, aggregateIndex[0]); + if (result != null) { return result; } + if (hasPlainJavaAnnotationsOnly(source)) { return null; } + @Nullable Annotation[] annotations = getDeclaredAnnotations(source, false); + result = processor.doWithAnnotations(context, aggregateIndex[0], source, annotations); + if (result != null) { return result; } + aggregateIndex[0]++; + if (includeInterfaces) { + for (Class interfaceType : source.getInterfaces()) { + R interfacesResult = processClassHierarchy(context, aggregateIndex, + interfaceType, processor, true, searchEnclosingClass); // UNCONDITIONAL → O(2^D) + if (interfacesResult != null) { return interfacesResult; } + } + } + Class superclass = source.getSuperclass(); + if (superclass != Object.class && superclass != null) { + R superclassResult = processClassHierarchy(context, aggregateIndex, + superclass, processor, includeInterfaces, searchEnclosingClass); // recurses + ... + } + ... + } +} +``` + +There is no `visited` set tracking which types have been processed. When +`TYPE_HIERARCHY` search strategy is used with `includeInterfaces=true`, any +class that appears as a shared ancestor (diamond) is processed exponentially +many times. + +`hasPlainJavaAnnotationsOnly` only short-circuits for `java.*` types and +`Ordered.class` — custom interface diamonds at any depth are fully re-traversed. + +The `processMethodHierarchy` method (lines 261–321) has the same structure and +the same diamond defect when `includeInterfaces=true`. + +## Fix + +```java +// CORRECT: add a Set> visited parameter to guard diamond re-traversal +private static @Nullable R processClassHierarchy(C context, int[] aggregateIndex, + Class source, AnnotationsProcessor processor, + boolean includeInterfaces, Predicate> searchEnclosingClass) { + Set> visited = new HashSet<>(); + return processClassHierarchy(context, aggregateIndex, source, processor, + includeInterfaces, searchEnclosingClass, visited); +} + +private static @Nullable R processClassHierarchy(C context, int[] aggregateIndex, + Class source, AnnotationsProcessor processor, + boolean includeInterfaces, Predicate> searchEnclosingClass, + Set> visited) { + if (!visited.add(source)) { return null; } // guard: skip already-visited types + try { + R result = processor.doWithAggregate(context, aggregateIndex[0]); + if (result != null) { return result; } + if (hasPlainJavaAnnotationsOnly(source)) { return null; } + @Nullable Annotation[] annotations = getDeclaredAnnotations(source, false); + result = processor.doWithAnnotations(context, aggregateIndex[0], source, annotations); + if (result != null) { return result; } + aggregateIndex[0]++; + if (includeInterfaces) { + for (Class interfaceType : source.getInterfaces()) { + R interfacesResult = processClassHierarchy(context, aggregateIndex, + interfaceType, processor, true, searchEnclosingClass, visited); + if (interfacesResult != null) { return interfacesResult; } + } + } + // ... superclass and enclosing class handling unchanged, passing 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× | +| 20 | 1,048,575 | 20 | 52,428× | + +## Impact + +`AnnotationsScanner.processClassHierarchy` is the innermost engine behind +Spring's entire annotation scanning infrastructure: + +- `MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY)` +- `AnnotatedElementUtils.findMergedAnnotation()` — used by every `@RequestMapping`, `@Transactional`, `@Cacheable`, `@Async` lookup +- `AnnotationUtils.findAnnotation()` when interface hierarchy scanning is enabled +- AOP advisor scanning on application startup + +Any Spring application using annotation-driven AOP, Spring MVC, Spring Data, +or Spring Security where annotated interfaces form diamond hierarchies +(e.g. a `@Transactional` service interface extended by multiple intermediate +interfaces) will experience exponential annotation scanning time. + +This defect is present in both `processClassHierarchy` and +`processMethodHierarchy`. diff --git a/defects/weld/patch/weld-0004-hierarchy-discovery-diamond-retraversal.md b/defects/weld/patch/weld-0004-hierarchy-discovery-diamond-retraversal.md new file mode 100644 index 000000000..83be875a5 --- /dev/null +++ b/defects/weld/patch/weld-0004-hierarchy-discovery-diamond-retraversal.md @@ -0,0 +1,104 @@ +# UNDF: (pending) +# weld-0004: HierarchyDiscovery.discoverTypes — O(2^D) diamond type closure re-traversal + +## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal + +| Field | Value | +|-------|-------| +| ID | weld-0004 | +| Severity | HIGH | +| Ecosystem | weld-core | +| Package | org.jboss.weld.util.reflection | +| File | `impl/src/main/java/org/jboss/weld/util/reflection/HierarchyDiscovery.java` | +| Lines | 106–135 | +| Complexity | O(2^D) on diamond interface/type hierarchies | +| Hot path | CDI bean type closure discovery — every bean startup, every injection point resolution | + +## Defect + +```java +// DEFECT: types.put() return value IGNORED, then unconditional recurse +protected void discoverTypes(Type type, boolean rawGeneric) { + if (type instanceof Class) { + Class clazz = (Class) type; + this.types.put(clazz, clazz); // return value IGNORED + discoverFromClass(clazz, rawGeneric); // UNCONDITIONAL → O(2^D) + } else if (type instanceof GenericArrayType) { + // ... + this.types.put(arrayClass, type); // return value IGNORED + discoverFromClass(arrayClass, rawGeneric); // UNCONDITIONAL + } else if (type instanceof ParameterizedType) { + // ... + this.types.put(clazz, type); // return value IGNORED + discoverFromClass(clazz, rawGeneric); // UNCONDITIONAL + } +} + +protected void discoverFromClass(Class clazz, boolean rawGeneric) { + if (clazz.getSuperclass() != null) { + discoverTypes(..., clazz.getSuperclass(), ...); // recurses + } + discoverInterfaces(clazz, rawGeneric); // recurses into all interfaces +} +``` + +For a diamond hierarchy `A implements B,C; B extends D; C extends D`, `D` is +re-traversed from both `B` and `C` paths. At depth D, 2^D traversals occur. + +`HashMap.put(k,v)` returns the previous value — if non-null, the class was +already discovered and `discoverFromClass` need not be called. The guard is +available but not checked. + +## Fix + +```java +// CORRECT: check put() return value — skip recursion if already discovered +protected void discoverTypes(Type type, boolean rawGeneric) { + if (type instanceof Class) { + Class clazz = (Class) type; + if (this.types.put(clazz, clazz) != null) { return; } // already visited + discoverFromClass(clazz, rawGeneric); + } else if (type instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) type; + Type genericComponentType = arrayType.getGenericComponentType(); + Class rawComponentType = Reflections.getRawType(genericComponentType); + if (rawComponentType != null) { + Class arrayClass = Array.newInstance(rawComponentType, 0).getClass(); + if (this.types.put(arrayClass, type) != null) { return; } // already visited + discoverFromClass(arrayClass, rawGeneric); + } + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type rawType = parameterizedType.getRawType(); + if (rawType instanceof Class) { + Class clazz = (Class) rawType; + processTypeVariables(clazz.getTypeParameters(), parameterizedType.getActualTypeArguments()); + if (this.types.put(clazz, type) != null) { return; } // already visited + discoverFromClass(clazz, rawGeneric); + } + } +} +``` + +Note: `HashMap.put()` returns the previous mapping for the key, or `null` if +there was no previous mapping. If non-null → already discovered → skip. + +## Speedup + +| Diamond depth (D) | Before (traversals) | After (traversals) | Speedup | +|------------------|--------------------|--------------------|---------| +| 5 | 31 | 5 | 6× | +| 10 | 1,023 | 10 | 102× | +| 15 | 32,767 | 15 | 2,184× | +| 20 | 1,048,575 | 20 | 52,428× | + +## Impact + +`HierarchyDiscovery` is called for every CDI bean's type closure: +- `EnhancedAnnotatedTypeImpl` — enhanced type construction +- `BackedAnnotatedType` — slim annotated types +- Injection point resolution across the entire CDI container + +Applications with deep interface diamond hierarchies (e.g. service layers +implementing multiple generic interfaces sharing common supertypes) will +experience exponential CDI startup times.