java-topology/defects/doris/patch/doris-0002-plannode-conjuncts-linkedhashset.md
russell@unturf.com 068ebbd29f cpp-systems: tor CLEAN.md updated to note existing patches tor-0001/0002/0003
Scanned bitcoin/dragonfly/tor/transmission/nmap/ceph/allegro5 for additional
CWE-407 defects. All repos found CLEAN beyond previously recorded patches.
Updated tor/CLEAN.md to correctly reference existing tor-0001 through tor-0003.
2026-03-29 19:54:59 -04:00

3 KiB
Raw Blame History

UNDF: UNDF-2026-000000289

UNDF: (pending)

doris-0002: PlanNode.addConjunct — ArrayList.contains() O(C²) dedup

CWE-407 — Algorithmic Complexity

Field Value
ID doris-0002
Severity MEDIUM
Ecosystem doris
Package fe-core/planner
File fe/fe-core/src/main/java/org/apache/doris/planner/PlanNode.java
Lines 308324
Complexity O(C²) — C conjuncts, each addConjuncts() call scans the list
Hot path Query planning — addConjunctsToPlanNode in PhysicalPlanTranslator

Defect

PlanNode.conjuncts is an ArrayList<Expr>. The addConjunct method deduplicates by calling ArrayList.contains(), which is O(C):

// PlanNode.java
protected List<Expr> conjuncts = Lists.newArrayList();

public void addConjuncts(List<Expr> conjuncts) {
    if (conjuncts == null) return;
    for (Expr conjunct : conjuncts) {    // O(C_new)
        addConjunct(conjunct);
    }
}

public void addConjunct(Expr conjunct) {
    if (conjuncts == null) {
        conjuncts = Lists.newArrayList();
    }
    if (!conjuncts.contains(conjunct)) {  // O(C_existing) scan
        conjuncts.add(conjunct);
    }
}

PhysicalPlanTranslator.addConjunctsToPlanNode calls this from a double loop:

// PhysicalPlanTranslator.java:3039-3047
private void addConjunctsToPlanNode(PhysicalFilter<? extends Plan> filter,
        PlanNode planNode, PlanTranslatorContext context) {
    for (Expression conjunct : filter.getConjuncts()) {                     // O(F)
        for (Expression singleConjunct : ExpressionUtils.extractConjunctionToSet(conjunct)) { // O(K)
            planNode.addConjunct(ExpressionTranslator.translate(singleConjunct, context)); // O(C)
        }
    }
}

Total: O(F × K × C) — with F=filter conjuncts, K=sub-conjuncts per predicate, C=existing conjuncts in node. At C=100 conjuncts, each addConjunct call costs 100 equality comparisons instead of O(1).

Fix

Replace conjuncts with a LinkedHashSet<Expr> (preserves insertion order, O(1) contains):

// Before:
protected List<Expr> conjuncts = Lists.newArrayList();

public void addConjunct(Expr conjunct) {
    if (conjuncts == null) {
        conjuncts = Lists.newArrayList();
    }
    if (!conjuncts.contains(conjunct)) {
        conjuncts.add(conjunct);
    }
}

// After:
protected Set<Expr> conjunctSet = new LinkedHashSet<>();
protected List<Expr> conjuncts = null; // lazy view, computed on demand

public void addConjunct(Expr conjunct) {
    conjunctSet.add(conjunct);  // O(1) via hashCode/equals
}

public List<Expr> getConjuncts() {
    return new ArrayList<>(conjunctSet);
}

If Expr does not implement hashCode/equals, a LinkedHashMap<Expr, Boolean> keyed by identity (System.identityHashCode) is the alternative.

Speedup

C (conjuncts) addConjuncts calls Before (ops) After (ops) Speedup
20 20 400 20 20×
100 100 10,000 100 100×
500 500 250,000 500 500×