java-topology/defects/terraform/tf-0002-dag-graph-edgesto-upedges.md

2.2 KiB
Raw Blame History

tf-0002: DAG EdgesTo O(E) full-edge scan inside O(V) vertex loop — O(V×E) CBD transform

Severity: MEDIUM CWE: CWE-407 (Algorithmic Complexity — linear membership test in hot loop) Speedup: ~100x at V=200 chain graph (verified by unit test) Target: Terraform (hashicorp/terraform) Files:

  • internal/dag/graph.go:79-90EdgesTo() iterates all edges O(E) per call
  • internal/terraform/transform_destroy_cbd.go:122,136EdgesTo(v) called inside for _, v := range g.Vertices()

Description

CBDEdgeTransformer.Transform() iterates all vertices and calls EdgesTo(v) for each destroyer vertex:

for _, v := range g.Vertices() {         // O(V)
    ...
    for _, e := range g.EdgesTo(v) {     // O(E) — scans ALL edges

EdgesTo() scans the full edge list filtering by target:

func (g *Graph) EdgesTo(v Vertex) []Edge {
    var result []Edge
    search := hashcode(v)
    for _, e := range g.Edges() {        // O(E) linear scan
        if hashcode(e.Target()) == search {
            result = append(result, e)
        }
    }
    return result
}

Total: O(V × E). For V=500 resources and E=1000 edges, this is 500,000 comparisons per destroy plan instead of ~1,500.

Root Cause

EdgesTo queries the full edge set rather than the upEdges index that the graph already maintains. upEdgesNoCopy(v) returns the set of source vertices for target v in O(1) via map lookup.

Fix: rewrite EdgesTo to use upEdgesNoCopy(v):

func (g *Graph) EdgesTo(v Vertex) []Edge {
    sources := g.upEdgesNoCopy(v)
    result := make([]Edge, 0, sources.Len())
    for _, src := range sources {
        result = append(result, BasicEdge(src.(Vertex), v))
    }
    return result
}

Cost becomes O(in-degree(v)) per call; O(E) total across all vertices.

Patch

See patch/tf-0002-dag-graph-edgesto-upedges.patch

Complexity Before

EdgesTo(v) per call: O(E) CBDEdgeTransformer.Transform(): O(V × E)

Complexity After

EdgesTo(v) per call: O(in-degree(v)) CBDEdgeTransformer.Transform(): O(E) total

Reproduction

cd defects/terraform/unit && javac -d . TerraformDagTest.java && java -ea unit.TerraformDagTest

test3: V=200 chain, defect=39800 ops, fixed=399 ops, ratio≈100x