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:
russell@unturf.com 2026-03-29 20:17:52 -04:00
parent 4d4c256673
commit 9d273094e8
9 changed files with 607 additions and 6 deletions

View file

@ -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 |

View file

@ -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 |

View file

@ -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 |

View file

@ -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 | 114121 |
| 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 6581) 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.

View file

@ -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 | 139150 |
| 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.

View file

@ -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 | 334354 |
| 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.

View file

@ -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 | 5887 |
| 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.

View file

@ -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 | 173232 |
| 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 261321) 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`.

View file

@ -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 | 106135 |
| 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.