From 1b0b3223b3df36f1e1357caf8cc8e20fbe03351d Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 29 Mar 2026 19:25:47 -0400 Subject: [PATCH] =?UTF-8?q?hazelcast-0002/0003:=20ClassLoaderUtil=20+=20Pr?= =?UTF-8?q?obeUtils=20diamond=20O(2^D);=20count=20664=E2=86=92666?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...lassloaderutil-addowninterfaces-diamond.md | 70 +++++++++++++++++ ...zelcast-0003-probeutils-flatten-diamond.md | 77 +++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 defects/hazelcast/patch/hazelcast-0002-classloaderutil-addowninterfaces-diamond.md create mode 100644 defects/hazelcast/patch/hazelcast-0003-probeutils-flatten-diamond.md diff --git a/defects/hazelcast/patch/hazelcast-0002-classloaderutil-addowninterfaces-diamond.md b/defects/hazelcast/patch/hazelcast-0002-classloaderutil-addowninterfaces-diamond.md new file mode 100644 index 000000000..e44ac1373 --- /dev/null +++ b/defects/hazelcast/patch/hazelcast-0002-classloaderutil-addowninterfaces-diamond.md @@ -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 | 387–393 | +| 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> 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> 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> 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). diff --git a/defects/hazelcast/patch/hazelcast-0003-probeutils-flatten-diamond.md b/defects/hazelcast/patch/hazelcast-0003-probeutils-flatten-diamond.md new file mode 100644 index 000000000..7d32d5d6a --- /dev/null +++ b/defects/hazelcast/patch/hazelcast-0003-probeutils-flatten-diamond.md @@ -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 | 28–39 | +| 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> 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> 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> 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).