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