java-topology/defects/hazelcast/patch/hazelcast-0002-classloaderutil-addowninterfaces-diamond.md

71 lines
3.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UNDF: UNDF-2026-000000416
# UNDF: (pending)
# hazelcast-0002: ClassLoaderUtil.addOwnInterfaces — O(2^D) diamond re-traversal; Collections.addAll() return value ignored
## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal in class interface hierarchy collection
| Field | Value |
|--------------|-------|
| ID | hazelcast-0002 |
| Severity | HIGH |
| Ecosystem | hazelcast |
| Package | hazelcast |
| File | `hazelcast/src/main/java/com/hazelcast/internal/nio/ClassLoaderUtil.java` |
| Lines | 387393 |
| Complexity | O(2^D) on diamond interface hierarchies |
| Hot path | Called from `getAllInterfaces()``implementsInterfaceWithSameName()` during class loading |
## Defect
`ClassLoaderUtil.addOwnInterfaces` collects interfaces into a `Collection<Class<?>> allInterfaces`
(backed by a `HashSet` at the call site in `getAllInterfaces`). Although items are added to a Set,
the recursion guard is MISSING — `Collections.addAll` fills the set but the `for` loop recurses
unconditionally, ignoring whether each interface was already present:
```java
// ClassLoaderUtil.java:387-393 (DEFECT)
private static void addOwnInterfaces(Class<?> clazz, Collection<Class<?>> allInterfaces) {
Class<?>[] interfaces = clazz.getInterfaces();
Collections.addAll(allInterfaces, interfaces); // adds items — return values IGNORED
for (Class cl : interfaces) {
addOwnInterfaces(cl, allInterfaces); // DEFECT: recurses unconditionally
}
}
```
On a diamond (I1 and I2 both extend Base; clazz implements I1 and I2):
1. `Collections.addAll(allInterfaces, [I1, I2])` → both added
2. Loop: `addOwnInterfaces(I1)` → adds Base; recurses into Base
3. Loop: `addOwnInterfaces(I2)``Collections.addAll(allInterfaces, [Base])` — add returns false
(Base already present) — IGNORED; loop still recurses into Base again
At diamond depth D, Base is visited 2^D times.
This is a second distinct occurrence of the same pattern found in `SerializationUtil.getInterfaces`
(hazelcast-0001) — both share the `Collections.addAll + unconditional loop` anti-pattern.
## Fix
Replace `Collections.addAll + unconditional loop` with a per-element `allInterfaces.add()` guard:
```java
// AFTER — O(N+E) where N=interfaces, E=inheritance edges
private static void addOwnInterfaces(Class<?> clazz, Collection<Class<?>> allInterfaces) {
for (Class<?> cl : clazz.getInterfaces()) {
if (allInterfaces.add(cl)) { // add returns false if already present → skip
addOwnInterfaces(cl, allInterfaces); // only recurse if newly added
}
}
}
```
## Speedup
| Diamond depth (D) | Before (visits) | After (visits) | Speedup |
|------------------|----------------|----------------|---------|
| 5 | 31 | 5 | 6× |
| 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).