weld-0001/0002: CDI Beans.recursiveStereotypeSearch + addInheritedInterceptorBindings diamond O(2^D); count 643→645
This commit is contained in:
parent
bbb8ad5552
commit
8de4c6f252
3 changed files with 217 additions and 1 deletions
|
|
@ -584,5 +584,7 @@
|
|||
"micronaut-0004": "UNDF-2026-000000484",
|
||||
"starrocks-0002": "UNDF-2026-000000495",
|
||||
"substrate-0003": "UNDF-2026-000000504",
|
||||
"erlang-0004": "UNDF-2026-000000528"
|
||||
"erlang-0004": "UNDF-2026-000000528",
|
||||
"weld-0001": "UNDF-2026-000000530",
|
||||
"weld-0002": "UNDF-2026-000000550"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
# UNDF: UNDF-2026-000000530
|
||||
# UNDF: (pending)
|
||||
# weld-0001: Beans.recursiveStereotypeSearch — no visited set O(2^D) on diamond stereotype hierarchy
|
||||
|
||||
## CWE-407 — Algorithmic Complexity: Exponential Recursion on Diamond CDI Stereotype Hierarchy
|
||||
|
||||
| Field | Value |
|
||||
|--------------|-------|
|
||||
| ID | weld-0001 |
|
||||
| Severity | HIGH |
|
||||
| Ecosystem | weld |
|
||||
| Package | weld-impl |
|
||||
| File | `impl/src/main/java/org/jboss/weld/util/Beans.java` |
|
||||
| Lines | 783–804 |
|
||||
| Complexity | O(2^D) where D = diamond depth in stereotype annotation hierarchy |
|
||||
| Hot path | Called at CDI container startup for every bean type |
|
||||
|
||||
## Defect
|
||||
|
||||
`Beans.recursiveStereotypeSearch` traverses the CDI stereotype annotation hierarchy
|
||||
recursively with **no visited-set guard**. It iterates all annotations of a stereotype,
|
||||
and for each annotation that is itself a `@Stereotype`, recurses without tracking
|
||||
which stereotypes have already been processed.
|
||||
|
||||
On a diamond-shaped stereotype hierarchy (meta-stereotype @A annotated with @B and @C;
|
||||
both @B and @C are themselves stereotypes annotated with @D), the traversal is:
|
||||
|
||||
```
|
||||
recursiveStereotypeSearch(@A)
|
||||
→ recursiveStereotypeSearch(@B) → recursiveStereotypeSearch(@D)
|
||||
→ recursiveStereotypeSearch(@C) → recursiveStereotypeSearch(@D) ← D visited twice
|
||||
```
|
||||
|
||||
At depth D: `@D` is visited 2^D times. At D=24: 16,777,216 redundant calls.
|
||||
|
||||
```java
|
||||
// impl/src/main/java/org/jboss/weld/util/Beans.java:783-804 (DEFECT)
|
||||
private static void recursiveStereotypeSearch(Class<? extends Annotation> stereotype,
|
||||
AnnotationSearchResult annotationSearchResult) {
|
||||
Priority priorityAnnotation = stereotype.getAnnotation(Priority.class);
|
||||
if (priorityAnnotation != null) {
|
||||
annotationSearchResult.addPriority(priorityAnnotation.value());
|
||||
}
|
||||
// ...
|
||||
// perform a recursive search for more stereotypes
|
||||
for (Annotation annotation : stereotype.getAnnotations()) {
|
||||
Class<? extends Annotation> annotationClass = annotation.annotationType();
|
||||
if (annotationClass.getAnnotation(Stereotype.class) != null) {
|
||||
recursiveStereotypeSearch(annotationClass, annotationSearchResult);
|
||||
// NO visited set: @D reachable via @B and @C is re-traversed 2^D times
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Called from `annotationSearch` (line 770-781) for every bean annotation at startup.
|
||||
|
||||
## Fix
|
||||
|
||||
Add a `Set<Class<? extends Annotation>> visited` guard to prevent re-traversal:
|
||||
|
||||
```java
|
||||
// AFTER — O(N+E): visited set prevents exponential re-traversal
|
||||
public static AnnotationSearchResult annotationSearch(Annotated type) {
|
||||
AnnotationSearchResult annotationSearchResult = new AnnotationSearchResult();
|
||||
Set<Class<? extends Annotation>> visited = new HashSet<>();
|
||||
for (Annotation annotation : type.getAnnotations()) {
|
||||
Class<? extends Annotation> annotationClass = annotation.annotationType();
|
||||
if (annotationClass.getAnnotation(Stereotype.class) != null) {
|
||||
recursiveStereotypeSearch(annotationClass, annotationSearchResult, visited);
|
||||
}
|
||||
}
|
||||
return annotationSearchResult;
|
||||
}
|
||||
|
||||
private static void recursiveStereotypeSearch(Class<? extends Annotation> stereotype,
|
||||
AnnotationSearchResult annotationSearchResult,
|
||||
Set<Class<? extends Annotation>> visited) {
|
||||
if (!visited.add(stereotype)) { // O(1): already processed, skip
|
||||
return;
|
||||
}
|
||||
Priority priorityAnnotation = stereotype.getAnnotation(Priority.class);
|
||||
if (priorityAnnotation != null) {
|
||||
annotationSearchResult.addPriority(priorityAnnotation.value());
|
||||
}
|
||||
Reserve reserve = stereotype.getAnnotation(Reserve.class);
|
||||
if (reserve != null) {
|
||||
annotationSearchResult.setReserve(reserve.annotationType());
|
||||
}
|
||||
Alternative alternative = stereotype.getAnnotation(Alternative.class);
|
||||
if (alternative != null) {
|
||||
annotationSearchResult.setAlternative(alternative.annotationType());
|
||||
}
|
||||
for (Annotation annotation : stereotype.getAnnotations()) {
|
||||
Class<? extends Annotation> annotationClass = annotation.annotationType();
|
||||
if (annotationClass.getAnnotation(Stereotype.class) != null) {
|
||||
recursiveStereotypeSearch(annotationClass, annotationSearchResult, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Speedup
|
||||
|
||||
| Diamond depth (D) | Before (calls) | After (calls) | Speedup |
|
||||
|------------------|---------------|---------------|---------|
|
||||
| 10 | 1,023 | 10 | 102× |
|
||||
| 15 | 32,767 | 15 | 2,184× |
|
||||
| 20 | 1,048,575 | 20 | 52,428× |
|
||||
|
||||
Growth before: O(2^D). Growth after: O(D).
|
||||
|
||||
## Impact
|
||||
|
||||
Weld is the CDI reference implementation, used by WildFly, GlassFish, Payara, and
|
||||
indirectly by Quarkus (CDI layer). Diamond CDI stereotype hierarchies arise naturally
|
||||
in enterprise patterns: a shared `@ApplicationScoped @Transactional @Named` meta-stereotype
|
||||
and multiple extending stereotypes converging on it.
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# UNDF: UNDF-2026-000000550
|
||||
# UNDF: (pending)
|
||||
# weld-0002: Interceptors.addInheritedInterceptorBindings — no visited set O(2^D) diamond re-traversal
|
||||
|
||||
## CWE-407 — Algorithmic Complexity: Exponential Re-traversal on Diamond Interceptor Binding Hierarchy
|
||||
|
||||
| Field | Value |
|
||||
|--------------|-------|
|
||||
| ID | weld-0002 |
|
||||
| Severity | MEDIUM |
|
||||
| Ecosystem | weld |
|
||||
| Package | weld-impl |
|
||||
| File | `impl/src/main/java/org/jboss/weld/util/Interceptors.java` |
|
||||
| Lines | 91–100 |
|
||||
| Complexity | O(2^D) where D = diamond depth in @InterceptorBinding meta-annotation hierarchy |
|
||||
| Hot path | Called at CDI container startup during interceptor binding flattening |
|
||||
|
||||
## Defect
|
||||
|
||||
`Interceptors.addInheritedInterceptorBindings` recursively traverses the CDI
|
||||
interceptor binding meta-annotation hierarchy with **no visited-set guard**.
|
||||
It recurses into every meta-binding annotation without tracking which types
|
||||
have already been processed.
|
||||
|
||||
On a diamond interceptor hierarchy (@A meta-annotated with @B and @C; both @B
|
||||
and @C meta-annotated with @D), the traversal visits @D twice:
|
||||
|
||||
```
|
||||
addInheritedInterceptorBindings(@A)
|
||||
→ addInheritedInterceptorBindings(@B) → addInheritedInterceptorBindings(@D)
|
||||
→ addInheritedInterceptorBindings(@C) → addInheritedInterceptorBindings(@D) ← D again
|
||||
```
|
||||
|
||||
At depth D: 2^D redundant traversals.
|
||||
|
||||
```java
|
||||
// impl/src/main/java/org/jboss/weld/util/Interceptors.java:91-100 (DEFECT)
|
||||
private static void addInheritedInterceptorBindings(EnhancedAnnotatedType<?> clazz,
|
||||
Class<? extends Annotation> bindingType,
|
||||
MetaAnnotationStore metaAnnotationStore,
|
||||
Set<Annotation> flattenInterceptorBindings) {
|
||||
Set<Annotation> metaBindings = metaAnnotationStore.getInterceptorBindingModel(bindingType)
|
||||
.getInheritedInterceptionBindingTypes();
|
||||
addInterceptorBindings(clazz, metaBindings, flattenInterceptorBindings, metaAnnotationStore);
|
||||
for (Annotation metaBinding : metaBindings) {
|
||||
addInheritedInterceptorBindings(clazz, metaBinding.annotationType(), metaAnnotationStore,
|
||||
flattenInterceptorBindings);
|
||||
// NO visited set: @D reachable via @B and @C is re-traversed
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: `flattenInterceptorBindings` is a `Set<Annotation>`, so duplicate
|
||||
insertions are rejected — but the recursive traversal still happens, repeating
|
||||
the same work exponentially.
|
||||
|
||||
## Fix
|
||||
|
||||
Add a `Set<Class<? extends Annotation>> visited` parameter threaded through the
|
||||
recursion. Guard at entry with `visited.add(bindingType)`.
|
||||
|
||||
```java
|
||||
// AFTER — O(N+E): visited set prevents exponential re-traversal
|
||||
// Caller:
|
||||
for (Annotation annotation : rawBindings) {
|
||||
addInheritedInterceptorBindings(clazz, annotation.annotationType(),
|
||||
metaAnnotationStore, flattenInterceptorBindings, new HashSet<>());
|
||||
}
|
||||
|
||||
private static void addInheritedInterceptorBindings(EnhancedAnnotatedType<?> clazz,
|
||||
Class<? extends Annotation> bindingType,
|
||||
MetaAnnotationStore metaAnnotationStore,
|
||||
Set<Annotation> flattenInterceptorBindings,
|
||||
Set<Class<? extends Annotation>> visited) {
|
||||
if (!visited.add(bindingType)) { // O(1): already processed
|
||||
return;
|
||||
}
|
||||
Set<Annotation> metaBindings = metaAnnotationStore.getInterceptorBindingModel(bindingType)
|
||||
.getInheritedInterceptionBindingTypes();
|
||||
addInterceptorBindings(clazz, metaBindings, flattenInterceptorBindings, metaAnnotationStore);
|
||||
for (Annotation metaBinding : metaBindings) {
|
||||
addInheritedInterceptorBindings(clazz, metaBinding.annotationType(),
|
||||
metaAnnotationStore, flattenInterceptorBindings, visited);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Speedup
|
||||
|
||||
| Diamond depth (D) | Before (calls) | After (calls) | Speedup |
|
||||
|------------------|---------------|---------------|---------|
|
||||
| 10 | 1,023 | 10 | 102× |
|
||||
| 15 | 32,767 | 15 | 2,184× |
|
||||
| 20 | 1,048,575 | 20 | 52,428× |
|
||||
|
||||
Growth before: O(2^D). Growth after: O(D).
|
||||
Loading…
Add table
Add a link
Reference in a new issue