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",
|
"micronaut-0004": "UNDF-2026-000000484",
|
||||||
"starrocks-0002": "UNDF-2026-000000495",
|
"starrocks-0002": "UNDF-2026-000000495",
|
||||||
"substrate-0003": "UNDF-2026-000000504",
|
"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