java-topology/defects/hazelcast/patch/hazelcast-0001-serializationutil-getinterfaces-diamond.md

3.3 KiB
Raw Blame History

UNDF: UNDF-2026-000000415

UNDF: (pending)

hazelcast-0001: SerializationUtil.getInterfaces — O(2^D) diamond re-traversal; Collections.addAll() return value ignored

CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal in interface hierarchy collection for serializer lookup

Field Value
ID hazelcast-0001
Severity HIGH
Ecosystem hazelcast
Package hazelcast
File hazelcast/src/main/java/com/hazelcast/internal/serialization/impl/SerializationUtil.java
Lines 150158
Complexity O(2^D) on diamond interface hierarchies
Hot path Called per serialization of a type not yet seen by the serialization service (first-time serializer lookup)

Defect

SerializationUtil.getInterfaces recursively collects all interfaces of a class into a Set<Class<?>>. Although it uses a Set, the recursion guard is MISSING — Collections.addAll(interfaces, classes) fills the set but the for loop then recurses into every interface unconditionally, regardless of whether each was already present in the set:

// SerializationUtil.java:150-158 (DEFECT)
static void getInterfaces(Class<?> clazz, Set<Class<?>> interfaces) {
    final var classes = clazz.getInterfaces();
    if (classes.length > 0) {
        Collections.addAll(interfaces, classes);    // adds to set (ignored return vals)
        for (Class<?> cl : classes) {
            getInterfaces(cl, interfaces);           // DEFECT: recurses unconditionally
        }                                            // ignores whether cl was already present
    }
}

On a diamond hierarchy (interface I1 extends Base; interface I2 extends Base; class C implements I1, I2):

  • getInterfaces(C):
    • Collections.addAll(interfaces, [I1, I2]) → both added
    • recurse into I1: getInterfaces(I1):
      • adds Base to set, recurses into Base
    • recurse into I2: getInterfaces(I2):
      • Collections.addAll(interfaces, [Base]) → Base already present, add() returns false (IGNORED)
      • STILL recurses into Base → unnecessary re-traversal

At diamond depth D, Base is visited 2^D times.

Called from AbstractSerializationService.lookupCustomSerializer() (line 663, and again once per superclass in the superclass chain at line 669), which is invoked on every first serialization of a previously-unseen class type.

Fix

Replace Collections.addAll + unconditional loop with a per-element interfaces.add() guard:

// AFTER — O(N+E) where N=interfaces, E=inheritance edges
static void getInterfaces(Class<?> clazz, Set<Class<?>> interfaces) {
    for (Class<?> cl : clazz.getInterfaces()) {
        if (interfaces.add(cl)) {           // add returns false if already present → skip
            getInterfaces(cl, interfaces);  // 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).