java-topology/defects/doris/patch/doris-0001-equivalence-class-list-contains-hashset.md
russell@unturf.com 5b5355cb07 infra-cluster: consul/helm/doris CWE-407 scan; buildkit/containerd/traefik/grafana/victoria-metrics/nifi/samza CLEAN
consul-0001: makeMeshGatewayPeerFilterChain peerNames O(S×B×P) slice scan → map[string]struct{}
helm-0001: ResourceList.Difference/Intersect O(N²) via Contains → pre-built map index
doris-0001: EquivalenceClass.getEquivalenceSetList ArrayList visited O(V²) → IdentityHashMap
doris-0002: PlanNode.addConjunct ArrayList.contains O(C²) → LinkedHashSet

6 CLEAN: buildkit, containerd, traefik, grafana, victoria-metrics, nifi (except pre-existing 0001), samza
2026-03-29 19:54:13 -04:00

3 KiB
Raw Blame History

UNDF: UNDF-2026-000000381

UNDF: (pending)

doris-0001: EquivalenceClass.getEquivalenceSetList — O(N²) List.contains dedup

CWE-407 — Algorithmic Complexity

Field Value
ID doris-0001
Severity MEDIUM
Ecosystem doris
Package fe-core
File fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/EquivalenceClass.java
Lines 130146
Complexity O(N²)
Hot path Called during materialized view rewrite in query planning (every query with MV candidates)

Defect

getEquivalenceSetList() deduplicates equivalence class lists using a List<List<SlotReference>> as the visited set. visited.contains(slotSet) calls ArrayList.contains() which iterates the entire visited list comparing lists element-by-element — O(N) per call. The outer loop also iterates N times, making the total complexity O(N²) where N is the number of distinct equivalence slot lists in equivalenceSlotMap.values().

This runs on every query that touches materialized view rewrite logic in Doris Nereids.

public List<List<SlotReference>> getEquivalenceSetList() {
    if (equivalenceSlotList != null) {
        return equivalenceSlotList;
    }
    List<List<SlotReference>> equivalenceSets = new ArrayList<>();
    List<List<SlotReference>> visited = new ArrayList<>();         // O(N) per contains
    equivalenceSlotMap.values().forEach(slotSet -> {
        if (!visited.contains(slotSet)) {                          // O(N) scan
            equivalenceSets.add(slotSet);
        }
        visited.add(slotSet);
    });
    this.equivalenceSlotList = equivalenceSets;
    return this.equivalenceSlotList;
}

Fix

equivalenceSlotMap maps each SlotReference to its equivalence-class list (which is a shared List<SlotReference> object). Deduplication by object identity is sufficient and correct because the union-find structure ensures all members of an equivalence class share the same list reference. Use IdentityHashMap or Set based on object identity:

public List<List<SlotReference>> getEquivalenceSetList() {
    if (equivalenceSlotList != null) {
        return equivalenceSlotList;
    }
    // Use identity comparison: all slots in the same equivalence class share the same List object
    Set<List<SlotReference>> seen = Collections.newSetFromMap(new IdentityHashMap<>());
    List<List<SlotReference>> equivalenceSets = new ArrayList<>();
    for (List<SlotReference> slotSet : equivalenceSlotMap.values()) {
        if (seen.add(slotSet)) {                                    // O(1) identity hash
            equivalenceSets.add(slotSet);
        }
    }
    this.equivalenceSlotList = equivalenceSets;
    return this.equivalenceSlotList;
}

Speedup

N (equivalence classes) Before (ops) After (ops) Speedup
10 100 10 10×
50 2,500 50 50×
100 10,000 100 100×
500 250,000 500 500×