java-topology/docs/tickets/0005-inference-context-isequiv-containsall.md
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

2.4 KiB
Raw Blame History

0005 — InferenceContext.isEquiv(): O(B²) bound-list equality via List.containsAll()

Status: open Severity: performance — low Component: jdk.compiler / com.sun.tools.javac.comp.InferenceContext$ReachabilityVisitor


Root Cause

InferenceContext.java:506isEquiv():

boolean isEquiv(UndetVar from, Type t, InferenceBound boundKind) {
    UndetVar uv = (UndetVar)asUndetVar(t);
    for (InferenceBound ib : InferenceBound.values()) {
        List<Type> b1 = from.getBounds(ib);
        ...
        List<Type> b2 = uv.getBounds(ib);
        ...
        if (!b1.containsAll(b2) || !b2.containsAll(b1)) {   // O(B²)
            return false;
        }
    }
    return true;
}

b1.containsAll(b2) on com.sun.tools.javac.util.List (javac's own linked-list) does a linear contains() check per element of b2 — O(|b1| × |b2|). Called twice.

This is a set-equality check expressed as two mutual containment checks on linear lists. Should use Set or sort-and-compare.


Call Context

isEquiv() is called from ReachabilityVisitor.visitUndetVar() which is called during inference context minimization (InferenceContext.min()). Minimization fires when the compiler attempts to reduce the inference context to its minimal reachable set before solving.

Bounds lists are typically small (110 elements), so O(B²) is not severe in practice. Impact is lower than 00010003 but follows the same pattern.


Fix

// Replace containsAll on linked lists with Set comparison:
if (!new LinkedHashSet<>(b1).equals(new LinkedHashSet<>(b2))) {
    return false;
}

Or, if getBounds() returned a Set instead of a List, the comparison would be direct.


Summary

# Location Defect Complexity Module
0001 GraphUtils$Tarjan stack.contains(n) O(V²) Tarjan jdk.compiler
0002 InferenceGraph.findNode() + closure() O(N) scan + uncached DFS O(N³) total jdk.compiler
0003 ModuleHashesBuilder$TopoSorter Deque.contains() O(V²) topo sort java.base
0004 Dependencies$Node.addDependency() List.contains() dedup O(N) per add jdk.compiler
0005 InferenceContext.isEquiv() List.containsAll() O(B²) per call jdk.compiler

All five share the root pattern: linear collection membership check where O(1) is achievable.