src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }