propagation.go (5865B)
1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package vta 6 7 import ( 8 "go/types" 9 "iter" 10 "slices" 11 12 "golang.org/x/tools/go/callgraph/vta/internal/trie" 13 "golang.org/x/tools/go/ssa" 14 15 "golang.org/x/tools/go/types/typeutil" 16 ) 17 18 // scc computes strongly connected components (SCCs) of `g` using the 19 // classical Tarjan's algorithm for SCCs. The result is two slices: 20 // - sccs: the SCCs, each represented as a slice of node indices 21 // - idxToSccID: the inverse map, from node index to SCC number. 22 // 23 // The SCCs are sorted in reverse topological order: for SCCs 24 // with ids X and Y s.t. X < Y, Y comes before X in the topological order. 25 func scc(g *vtaGraph) (sccs [][]idx, idxToSccID []int) { 26 // standard data structures used by Tarjan's algorithm. 27 type state struct { 28 pre int // preorder of the node (0 if unvisited) 29 lowLink int 30 onStack bool 31 } 32 states := make([]state, g.numNodes()) 33 var stack []idx 34 35 idxToSccID = make([]int, g.numNodes()) 36 nextPre := 0 37 38 var doSCC func(idx) 39 doSCC = func(n idx) { 40 nextPre++ 41 ns := &states[n] 42 *ns = state{pre: nextPre, lowLink: nextPre, onStack: true} 43 stack = append(stack, n) 44 45 for s := range g.successors(n) { 46 if ss := &states[s]; ss.pre == 0 { 47 // Analyze successor s that has not been visited yet. 48 doSCC(s) 49 ns.lowLink = min(ns.lowLink, ss.lowLink) 50 } else if ss.onStack { 51 // The successor is on the stack, meaning it has to be 52 // in the current SCC. 53 ns.lowLink = min(ns.lowLink, ss.pre) 54 } 55 } 56 57 // if n is a root node, pop the stack and generate a new SCC. 58 if ns.lowLink == ns.pre { 59 sccStart := slicesLastIndex(stack, n) 60 scc := slices.Clone(stack[sccStart:]) 61 stack = stack[:sccStart] 62 sccID := len(sccs) 63 sccs = append(sccs, scc) 64 for _, w := range scc { 65 states[w].onStack = false 66 idxToSccID[w] = sccID 67 } 68 } 69 } 70 71 for n, nn := 0, g.numNodes(); n < nn; n++ { 72 if states[n].pre == 0 { 73 doSCC(idx(n)) 74 } 75 } 76 77 return sccs, idxToSccID 78 } 79 80 // slicesLastIndex returns the index of the last occurrence of v in s, or -1 if v is 81 // not present in s. 82 // 83 // slicesLastIndex iterates backwards through the elements of s, stopping when the == 84 // operator determines an element is equal to v. 85 func slicesLastIndex[S ~[]E, E comparable](s S, v E) int { 86 // TODO: move to / dedup with slices.LastIndex 87 for i := len(s) - 1; i >= 0; i-- { 88 if s[i] == v { 89 return i 90 } 91 } 92 return -1 93 } 94 95 // propType represents type information being propagated 96 // over the vta graph. f != nil only for function nodes 97 // and nodes reachable from function nodes. There, we also 98 // remember the actual *ssa.Function in order to more 99 // precisely model higher-order flow. 100 type propType struct { 101 typ types.Type 102 f *ssa.Function 103 } 104 105 // propTypeMap is an auxiliary structure that serves 106 // the role of a map from nodes to a set of propTypes. 107 type propTypeMap map[node]*trie.MutMap 108 109 // propTypes returns an iterator for the propTypes associated with 110 // node `n` in map `ptm`. 111 func (ptm propTypeMap) propTypes(n node) iter.Seq[propType] { 112 return func(yield func(propType) bool) { 113 if types := ptm[n]; types != nil { 114 types.M.Range(func(_ uint64, elem any) bool { 115 return yield(elem.(propType)) 116 }) 117 } 118 } 119 } 120 121 // propagate reduces the `graph` based on its SCCs and 122 // then propagates type information through the reduced 123 // graph. The result is a map from nodes to a set of types 124 // and functions, stemming from higher-order data flow, 125 // reaching the node. `canon` is used for type uniqueness. 126 func propagate(graph *vtaGraph, canon *typeutil.Map) propTypeMap { 127 sccs, idxToSccID := scc(graph) 128 129 // propTypeIds are used to create unique ids for 130 // propType, to be used for trie-based type sets. 131 propTypeIds := make(map[propType]uint64) 132 // Id creation is based on == equality, which works 133 // as types are canonicalized (see getPropType). 134 propTypeId := func(p propType) uint64 { 135 if id, ok := propTypeIds[p]; ok { 136 return id 137 } 138 id := uint64(len(propTypeIds)) 139 propTypeIds[p] = id 140 return id 141 } 142 builder := trie.NewBuilder() 143 // Initialize sccToTypes to avoid repeated check 144 // for initialization later. 145 sccToTypes := make([]*trie.MutMap, len(sccs)) 146 for sccID, scc := range sccs { 147 typeSet := builder.MutEmpty() 148 for _, idx := range scc { 149 if n := graph.node[idx]; hasInitialTypes(n) { 150 // add the propType for idx to typeSet. 151 pt := getPropType(n, canon) 152 typeSet.Update(propTypeId(pt), pt) 153 } 154 } 155 sccToTypes[sccID] = &typeSet 156 } 157 158 for i, scc := range slices.Backward(sccs) { 159 nextSccs := make(map[int]empty) 160 for _, n := range scc { 161 for succ := range graph.successors(n) { 162 nextSccs[idxToSccID[succ]] = empty{} 163 } 164 } 165 // Propagate types to all successor SCCs. 166 for nextScc := range nextSccs { 167 sccToTypes[nextScc].Merge(sccToTypes[i].M) 168 } 169 } 170 nodeToTypes := make(propTypeMap, graph.numNodes()) 171 for sccID, scc := range sccs { 172 types := sccToTypes[sccID] 173 for _, idx := range scc { 174 nodeToTypes[graph.node[idx]] = types 175 } 176 } 177 return nodeToTypes 178 } 179 180 // hasInitialTypes check if a node can have initial types. 181 // Returns true iff `n` is not a panic, recover, nestedPtr* 182 // node, nor a node whose type is an interface. 183 func hasInitialTypes(n node) bool { 184 switch n.(type) { 185 case panicArg, recoverReturn, nestedPtrFunction, nestedPtrInterface: 186 return false 187 default: 188 return !types.IsInterface(n.Type()) 189 } 190 } 191 192 // getPropType creates a propType for `node` based on its type. 193 // propType.typ is always node.Type(). If node is function, then 194 // propType.val is the underlying function; nil otherwise. 195 func getPropType(node node, canon *typeutil.Map) propType { 196 t := canonicalize(node.Type(), canon) 197 if fn, ok := node.(function); ok { 198 return propType{f: fn.f, typ: t} 199 } 200 return propType{f: nil, typ: t} 201 }