diamond-scan: spring/hibernate/weld/quarkus/micronaut O(2^D) hierarchy traversal
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).
This commit is contained in:
parent
4d4c256673
commit
9d273094e8
9 changed files with 607 additions and 6 deletions
|
|
@ -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 |
|
||||
|
|
@ -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 |
|
||||
|
|
@ -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 |
|
||||
|
|
@ -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 <T> void getImplementedInterfaces(
|
||||
Class<? super T> clazz, Set<Class<? super T>> classes) {
|
||||
for (Class<?> currentInterface : clazz.getInterfaces()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? super T> currentInterfaceCasted = (Class<? super T>) 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 <T> void getImplementedInterfaces(
|
||||
Class<? super T> clazz, Set<Class<? super T>> classes) {
|
||||
for (Class<?> currentInterface : clazz.getInterfaces()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? super T> currentInterfaceCasted = (Class<? super T>) 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.
|
||||
|
|
@ -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<C> 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<C> 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<C> hierarchy) {
|
||||
if (hierarchy.contains(element)) { return; } // guard: O(N) but prevents O(2^D)
|
||||
// Better: pass a Set<C> 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<C> hierarchy) {
|
||||
Set<C> visited = new HashSet<>();
|
||||
populateTypeHierarchy(element, hierarchy, visited);
|
||||
}
|
||||
|
||||
private void populateTypeHierarchy(C element, List<C> hierarchy, Set<C> 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.
|
||||
|
|
@ -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<String, Map<String, TypeMirror>> 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<String, Map<String, TypeMirror>> 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<String, TypeMirror> boundTypes = typeArguments.get(JavaModelUtils.getClassName(child));
|
||||
if (boundTypes == null) {
|
||||
boundTypes = Collections.emptyMap();
|
||||
}
|
||||
Map<String, TypeMirror> 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<TypeElement> 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<T> extends CrudRepository<T,ID>` shared by
|
||||
multiple intermediate interfaces) will experience exponential build slowdowns.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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 <C, R> @Nullable R processClassHierarchy(C context, int[] aggregateIndex,
|
||||
Class<?> source, AnnotationsProcessor<C, R> processor,
|
||||
boolean includeInterfaces, Predicate<Class<?>> 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<Class<?>> visited parameter to guard diamond re-traversal
|
||||
private static <C, R> @Nullable R processClassHierarchy(C context, int[] aggregateIndex,
|
||||
Class<?> source, AnnotationsProcessor<C, R> processor,
|
||||
boolean includeInterfaces, Predicate<Class<?>> searchEnclosingClass) {
|
||||
Set<Class<?>> visited = new HashSet<>();
|
||||
return processClassHierarchy(context, aggregateIndex, source, processor,
|
||||
includeInterfaces, searchEnclosingClass, visited);
|
||||
}
|
||||
|
||||
private static <C, R> @Nullable R processClassHierarchy(C context, int[] aggregateIndex,
|
||||
Class<?> source, AnnotationsProcessor<C, R> processor,
|
||||
boolean includeInterfaces, Predicate<Class<?>> searchEnclosingClass,
|
||||
Set<Class<?>> 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`.
|
||||
|
|
@ -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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue