hazelcast-0002/0003: ClassLoaderUtil + ProbeUtils diamond O(2^D); count 664→666

This commit is contained in:
russell@unturf.com 2026-03-29 19:25:47 -04:00
parent db463c1b4e
commit 1b0b3223b3
2 changed files with 147 additions and 0 deletions

View file

@ -0,0 +1,70 @@
# 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).

View file

@ -0,0 +1,77 @@
# UNDF: (pending)
# hazelcast-0003: ProbeUtils.flatten — O(2^D) diamond re-traversal; result.add() return value ignored before recursion
## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal in metrics type flattening
| Field | Value |
|--------------|-------|
| ID | hazelcast-0003 |
| Severity | MEDIUM |
| Ecosystem | hazelcast |
| Package | hazelcast |
| File | `hazelcast/src/main/java/com/hazelcast/internal/metrics/impl/ProbeUtils.java` |
| Lines | 2839 |
| Complexity | O(2^D) on diamond interface hierarchies |
| Hot path | Called during metrics probe registration (`SourceMetadata`, `ProbeType`) — initialization/startup path |
## Defect
`ProbeUtils.flatten` recursively collects all classes and interfaces in a type's hierarchy into
a `Collection<Class<?>> result` (backed by `LinkedHashSet` at both call sites). Although a Set
is used, the recursion guard is MISSING — `result.add(clazz)` return value at the top of the
method is IGNORED, so the method recurses into superclass AND all interfaces unconditionally even
when `clazz` was already fully traversed:
```java
// ProbeUtils.java:28-39 (DEFECT)
static void flatten(final Class<?> clazz, final Collection<Class<?>> result) {
result.add(clazz); // return value IGNORED
if (clazz.getSuperclass() != null) {
flatten(clazz.getSuperclass(), result); // recurses unconditionally
}
for (final Class<?> interfaze : clazz.getInterfaces()) {
result.add(interfaze); // return value IGNORED
flatten(interfaze, result); // DEFECT: recurses unconditionally
}
}
```
On a diamond (I1 and I2 both extend Base; clazz implements I1 and I2):
- `flatten(I1)` adds Base to result; recurses into Base (traverses Base's hierarchy)
- `flatten(I2)` attempts to add Base: `result.add(Base)` returns false (already present) — IGNORED;
then `flatten(Base, result)` is called AGAIN → re-traverses Base's full hierarchy
At diamond depth D, Base is visited 2^D times.
Callers: `SourceMetadata` (line 50) and `ProbeType` (line 92) both use `LinkedHashSet`.
## Fix
Check `result.add()` return value at the top of the method to short-circuit already-visited nodes:
```java
// AFTER — O(N+E) where N=types, E=hierarchy edges
static void flatten(final Class<?> clazz, final Collection<Class<?>> result) {
if (!result.add(clazz)) { return; } // already visited — skip entire subtree
if (clazz.getSuperclass() != null) {
flatten(clazz.getSuperclass(), result);
}
for (final Class<?> interfaze : clazz.getInterfaces()) {
flatten(interfaze, result); // result.add happens at top of recursion
}
}
```
## Speedup
| Diamond depth (D) | Before (visits) | After (visits) | Speedup |
|------------------|----------------|----------------|---------|
| 5 | 31 | 5 | 6× |
| 10 | 1,023 | 10 | 102× |
| 15 | 32,767 | 15 | 2,184× |
Growth before: O(2^D). Growth after: O(D).