Initialize module and dependencies
This commit is contained in:
861
vendor/golang.org/x/tools/go/callgraph/vta/graph.go
generated
vendored
Normal file
861
vendor/golang.org/x/tools/go/callgraph/vta/graph.go
generated
vendored
Normal file
@@ -0,0 +1,861 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"iter"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// node interface for VTA nodes.
|
||||
type node interface {
|
||||
Type() types.Type
|
||||
String() string
|
||||
}
|
||||
|
||||
// constant node for VTA.
|
||||
type constant struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (c constant) Type() types.Type {
|
||||
return c.typ
|
||||
}
|
||||
|
||||
func (c constant) String() string {
|
||||
return fmt.Sprintf("Constant(%v)", c.Type())
|
||||
}
|
||||
|
||||
// pointer node for VTA.
|
||||
type pointer struct {
|
||||
typ *types.Pointer
|
||||
}
|
||||
|
||||
func (p pointer) Type() types.Type {
|
||||
return p.typ
|
||||
}
|
||||
|
||||
func (p pointer) String() string {
|
||||
return fmt.Sprintf("Pointer(%v)", p.Type())
|
||||
}
|
||||
|
||||
// mapKey node for VTA, modeling reachable map key types.
|
||||
type mapKey struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (mk mapKey) Type() types.Type {
|
||||
return mk.typ
|
||||
}
|
||||
|
||||
func (mk mapKey) String() string {
|
||||
return fmt.Sprintf("MapKey(%v)", mk.Type())
|
||||
}
|
||||
|
||||
// mapValue node for VTA, modeling reachable map value types.
|
||||
type mapValue struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (mv mapValue) Type() types.Type {
|
||||
return mv.typ
|
||||
}
|
||||
|
||||
func (mv mapValue) String() string {
|
||||
return fmt.Sprintf("MapValue(%v)", mv.Type())
|
||||
}
|
||||
|
||||
// sliceElem node for VTA, modeling reachable slice and array element types.
|
||||
type sliceElem struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (s sliceElem) Type() types.Type {
|
||||
return s.typ
|
||||
}
|
||||
|
||||
func (s sliceElem) String() string {
|
||||
return fmt.Sprintf("Slice([]%v)", s.Type())
|
||||
}
|
||||
|
||||
// channelElem node for VTA, modeling reachable channel element types.
|
||||
type channelElem struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (c channelElem) Type() types.Type {
|
||||
return c.typ
|
||||
}
|
||||
|
||||
func (c channelElem) String() string {
|
||||
return fmt.Sprintf("Channel(chan %v)", c.Type())
|
||||
}
|
||||
|
||||
// field node for VTA.
|
||||
type field struct {
|
||||
StructType types.Type
|
||||
index int // index of the field in the struct
|
||||
}
|
||||
|
||||
func (f field) Type() types.Type {
|
||||
s := typeparams.CoreType(f.StructType).(*types.Struct)
|
||||
return s.Field(f.index).Type()
|
||||
}
|
||||
|
||||
func (f field) String() string {
|
||||
s := typeparams.CoreType(f.StructType).(*types.Struct)
|
||||
return fmt.Sprintf("Field(%v:%s)", f.StructType, s.Field(f.index).Name())
|
||||
}
|
||||
|
||||
// global node for VTA.
|
||||
type global struct {
|
||||
val *ssa.Global
|
||||
}
|
||||
|
||||
func (g global) Type() types.Type {
|
||||
return g.val.Type()
|
||||
}
|
||||
|
||||
func (g global) String() string {
|
||||
return fmt.Sprintf("Global(%s)", g.val.Name())
|
||||
}
|
||||
|
||||
// local node for VTA modeling local variables
|
||||
// and function/method parameters.
|
||||
type local struct {
|
||||
val ssa.Value
|
||||
}
|
||||
|
||||
func (l local) Type() types.Type {
|
||||
return l.val.Type()
|
||||
}
|
||||
|
||||
func (l local) String() string {
|
||||
return fmt.Sprintf("Local(%s)", l.val.Name())
|
||||
}
|
||||
|
||||
// indexedLocal node for VTA node. Models indexed locals
|
||||
// related to the ssa extract instructions.
|
||||
type indexedLocal struct {
|
||||
val ssa.Value
|
||||
index int
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (i indexedLocal) Type() types.Type {
|
||||
return i.typ
|
||||
}
|
||||
|
||||
func (i indexedLocal) String() string {
|
||||
return fmt.Sprintf("Local(%s[%d])", i.val.Name(), i.index)
|
||||
}
|
||||
|
||||
// function node for VTA.
|
||||
type function struct {
|
||||
f *ssa.Function
|
||||
}
|
||||
|
||||
func (f function) Type() types.Type {
|
||||
return f.f.Type()
|
||||
}
|
||||
|
||||
func (f function) String() string {
|
||||
return fmt.Sprintf("Function(%s)", f.f.Name())
|
||||
}
|
||||
|
||||
// resultVar represents the result
|
||||
// variable of a function, whether
|
||||
// named or not.
|
||||
type resultVar struct {
|
||||
f *ssa.Function
|
||||
index int // valid index into result var tuple
|
||||
}
|
||||
|
||||
func (o resultVar) Type() types.Type {
|
||||
return o.f.Signature.Results().At(o.index).Type()
|
||||
}
|
||||
|
||||
func (o resultVar) String() string {
|
||||
v := o.f.Signature.Results().At(o.index)
|
||||
if n := v.Name(); n != "" {
|
||||
return fmt.Sprintf("Return(%s[%s])", o.f.Name(), n)
|
||||
}
|
||||
return fmt.Sprintf("Return(%s[%d])", o.f.Name(), o.index)
|
||||
}
|
||||
|
||||
// nestedPtrInterface node represents all references and dereferences
|
||||
// of locals and globals that have a nested pointer to interface type.
|
||||
// We merge such constructs into a single node for simplicity and without
|
||||
// much precision sacrifice as such variables are rare in practice. Both
|
||||
// a and b would be represented as the same PtrInterface(I) node in:
|
||||
//
|
||||
// type I interface
|
||||
// var a ***I
|
||||
// var b **I
|
||||
type nestedPtrInterface struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (l nestedPtrInterface) Type() types.Type {
|
||||
return l.typ
|
||||
}
|
||||
|
||||
func (l nestedPtrInterface) String() string {
|
||||
return fmt.Sprintf("PtrInterface(%v)", l.typ)
|
||||
}
|
||||
|
||||
// nestedPtrFunction node represents all references and dereferences of locals
|
||||
// and globals that have a nested pointer to function type. We merge such
|
||||
// constructs into a single node for simplicity and without much precision
|
||||
// sacrifice as such variables are rare in practice. Both a and b would be
|
||||
// represented as the same PtrFunction(func()) node in:
|
||||
//
|
||||
// var a *func()
|
||||
// var b **func()
|
||||
type nestedPtrFunction struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (p nestedPtrFunction) Type() types.Type {
|
||||
return p.typ
|
||||
}
|
||||
|
||||
func (p nestedPtrFunction) String() string {
|
||||
return fmt.Sprintf("PtrFunction(%v)", p.typ)
|
||||
}
|
||||
|
||||
// panicArg models types of all arguments passed to panic.
|
||||
type panicArg struct{}
|
||||
|
||||
func (p panicArg) Type() types.Type {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p panicArg) String() string {
|
||||
return "Panic"
|
||||
}
|
||||
|
||||
// recoverReturn models types of all return values of recover().
|
||||
type recoverReturn struct{}
|
||||
|
||||
func (r recoverReturn) Type() types.Type {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r recoverReturn) String() string {
|
||||
return "Recover"
|
||||
}
|
||||
|
||||
type empty = struct{}
|
||||
|
||||
// idx is an index representing a unique node in a vtaGraph.
|
||||
type idx int
|
||||
|
||||
// vtaGraph remembers for each VTA node the set of its successors.
|
||||
// Tailored for VTA, hence does not support singleton (sub)graphs.
|
||||
type vtaGraph struct {
|
||||
m []map[idx]empty // m[i] has the successors for the node with index i.
|
||||
idx map[node]idx // idx[n] is the index for the node n.
|
||||
node []node // node[i] is the node with index i.
|
||||
}
|
||||
|
||||
func (g *vtaGraph) numNodes() int {
|
||||
return len(g.idx)
|
||||
}
|
||||
|
||||
func (g *vtaGraph) successors(x idx) iter.Seq[idx] {
|
||||
return func(yield func(y idx) bool) {
|
||||
for y := range g.m[x] {
|
||||
if !yield(y) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addEdge adds an edge x->y to the graph.
|
||||
func (g *vtaGraph) addEdge(x, y node) {
|
||||
if g.idx == nil {
|
||||
g.idx = make(map[node]idx)
|
||||
}
|
||||
lookup := func(n node) idx {
|
||||
i, ok := g.idx[n]
|
||||
if !ok {
|
||||
i = idx(len(g.idx))
|
||||
g.m = append(g.m, nil)
|
||||
g.idx[n] = i
|
||||
g.node = append(g.node, n)
|
||||
}
|
||||
return i
|
||||
}
|
||||
a := lookup(x)
|
||||
b := lookup(y)
|
||||
succs := g.m[a]
|
||||
if succs == nil {
|
||||
succs = make(map[idx]empty)
|
||||
g.m[a] = succs
|
||||
}
|
||||
succs[b] = empty{}
|
||||
}
|
||||
|
||||
// typePropGraph builds a VTA graph for a set of `funcs` and initial
|
||||
// `callgraph` needed to establish interprocedural edges. Returns the
|
||||
// graph and a map for unique type representatives.
|
||||
func typePropGraph(funcs map[*ssa.Function]bool, callees calleesFunc) (*vtaGraph, *typeutil.Map) {
|
||||
b := builder{callees: callees}
|
||||
b.visit(funcs)
|
||||
b.callees = nil // ensure callees is not pinned by pointers to other fields of b.
|
||||
return &b.graph, &b.canon
|
||||
}
|
||||
|
||||
// Data structure responsible for linearly traversing the
|
||||
// code and building a VTA graph.
|
||||
type builder struct {
|
||||
graph vtaGraph
|
||||
callees calleesFunc // initial call graph for creating flows at unresolved call sites.
|
||||
|
||||
// Specialized type map for canonicalization of types.Type.
|
||||
// Semantically equivalent types can have different implementations,
|
||||
// i.e., they are different pointer values. The map allows us to
|
||||
// have one unique representative. The keys are fixed and from the
|
||||
// client perspective they are types. The values in our case are
|
||||
// types too, in particular type representatives. Each value is a
|
||||
// pointer so this map is not expected to take much memory.
|
||||
canon typeutil.Map
|
||||
}
|
||||
|
||||
func (b *builder) visit(funcs map[*ssa.Function]bool) {
|
||||
// Add the fixed edge Panic -> Recover
|
||||
b.graph.addEdge(panicArg{}, recoverReturn{})
|
||||
|
||||
for f, in := range funcs {
|
||||
if in {
|
||||
b.fun(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) fun(f *ssa.Function) {
|
||||
for _, bl := range f.Blocks {
|
||||
for _, instr := range bl.Instrs {
|
||||
b.instr(instr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) instr(instr ssa.Instruction) {
|
||||
switch i := instr.(type) {
|
||||
case *ssa.Store:
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(i.Addr), b.nodeFromVal(i.Val))
|
||||
case *ssa.MakeInterface:
|
||||
b.addInFlowEdge(b.nodeFromVal(i.X), b.nodeFromVal(i))
|
||||
case *ssa.MakeClosure:
|
||||
b.closure(i)
|
||||
case *ssa.UnOp:
|
||||
b.unop(i)
|
||||
case *ssa.Phi:
|
||||
b.phi(i)
|
||||
case *ssa.ChangeInterface:
|
||||
// Although in change interface a := A(b) command a and b are
|
||||
// the same object, the only interesting flow happens when A
|
||||
// is an interface. We create flow b -> a, but omit a -> b.
|
||||
// The latter flow is not needed: if a gets assigned concrete
|
||||
// type later on, that cannot be propagated back to b as b
|
||||
// is a separate variable. The a -> b flow can happen when
|
||||
// A is a pointer to interface, but then the command is of
|
||||
// type ChangeType, handled below.
|
||||
b.addInFlowEdge(b.nodeFromVal(i.X), b.nodeFromVal(i))
|
||||
case *ssa.ChangeType:
|
||||
// change type command a := A(b) results in a and b being the
|
||||
// same value. For concrete type A, there is no interesting flow.
|
||||
//
|
||||
// When A is an interface, most interface casts are handled
|
||||
// by the ChangeInterface instruction. The relevant case here is
|
||||
// when converting a pointer to an interface type. This can happen
|
||||
// when the underlying interfaces have the same method set.
|
||||
//
|
||||
// type I interface{ foo() }
|
||||
// type J interface{ foo() }
|
||||
// var b *I
|
||||
// a := (*J)(b)
|
||||
//
|
||||
// When this happens we add flows between a <--> b.
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(i), b.nodeFromVal(i.X))
|
||||
case *ssa.TypeAssert:
|
||||
b.tassert(i)
|
||||
case *ssa.Extract:
|
||||
b.extract(i)
|
||||
case *ssa.Field:
|
||||
b.field(i)
|
||||
case *ssa.FieldAddr:
|
||||
b.fieldAddr(i)
|
||||
case *ssa.Send:
|
||||
b.send(i)
|
||||
case *ssa.Select:
|
||||
b.selekt(i)
|
||||
case *ssa.Index:
|
||||
b.index(i)
|
||||
case *ssa.IndexAddr:
|
||||
b.indexAddr(i)
|
||||
case *ssa.Lookup:
|
||||
b.lookup(i)
|
||||
case *ssa.MapUpdate:
|
||||
b.mapUpdate(i)
|
||||
case *ssa.Next:
|
||||
b.next(i)
|
||||
case ssa.CallInstruction:
|
||||
b.call(i)
|
||||
case *ssa.Panic:
|
||||
b.panic(i)
|
||||
case *ssa.Return:
|
||||
b.rtrn(i)
|
||||
case *ssa.MakeChan, *ssa.MakeMap, *ssa.MakeSlice, *ssa.BinOp,
|
||||
*ssa.Alloc, *ssa.DebugRef, *ssa.Convert, *ssa.Jump, *ssa.If,
|
||||
*ssa.Slice, *ssa.SliceToArrayPointer, *ssa.Range, *ssa.RunDefers:
|
||||
// No interesting flow here.
|
||||
// Notes on individual instructions:
|
||||
// SliceToArrayPointer: t1 = slice to array pointer *[4]T <- []T (t0)
|
||||
// No interesting flow as sliceArrayElem(t1) == sliceArrayElem(t0).
|
||||
return
|
||||
case *ssa.MultiConvert:
|
||||
b.multiconvert(i)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported instruction %v\n", instr))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) unop(u *ssa.UnOp) {
|
||||
switch u.Op {
|
||||
case token.MUL:
|
||||
// Multiplication operator * is used here as a dereference operator.
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(u), b.nodeFromVal(u.X))
|
||||
case token.ARROW:
|
||||
t := typeparams.CoreType(u.X.Type()).(*types.Chan).Elem()
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(u), channelElem{typ: t})
|
||||
default:
|
||||
// There is no interesting type flow otherwise.
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) phi(p *ssa.Phi) {
|
||||
for _, edge := range p.Edges {
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(p), b.nodeFromVal(edge))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) tassert(a *ssa.TypeAssert) {
|
||||
if !a.CommaOk {
|
||||
b.addInFlowEdge(b.nodeFromVal(a.X), b.nodeFromVal(a))
|
||||
return
|
||||
}
|
||||
// The case where a is <a.AssertedType, bool> register so there
|
||||
// is a flow from a.X to a[0]. Here, a[0] is represented as an
|
||||
// indexedLocal: an entry into local tuple register a at index 0.
|
||||
tup := a.Type().(*types.Tuple)
|
||||
t := tup.At(0).Type()
|
||||
|
||||
local := indexedLocal{val: a, typ: t, index: 0}
|
||||
b.addInFlowEdge(b.nodeFromVal(a.X), local)
|
||||
}
|
||||
|
||||
// extract instruction t1 := t2[i] generates flows between t2[i]
|
||||
// and t1 where the source is indexed local representing a value
|
||||
// from tuple register t2 at index i and the target is t1.
|
||||
func (b *builder) extract(e *ssa.Extract) {
|
||||
tup := e.Tuple.Type().(*types.Tuple)
|
||||
t := tup.At(e.Index).Type()
|
||||
|
||||
local := indexedLocal{val: e.Tuple, typ: t, index: e.Index}
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(e), local)
|
||||
}
|
||||
|
||||
func (b *builder) field(f *ssa.Field) {
|
||||
fnode := field{StructType: f.X.Type(), index: f.Field}
|
||||
b.addInFlowEdge(fnode, b.nodeFromVal(f))
|
||||
}
|
||||
|
||||
func (b *builder) fieldAddr(f *ssa.FieldAddr) {
|
||||
t := typeparams.CoreType(f.X.Type()).(*types.Pointer).Elem()
|
||||
|
||||
// Since we are getting pointer to a field, make a bidirectional edge.
|
||||
fnode := field{StructType: t, index: f.Field}
|
||||
b.addInFlowEdge(fnode, b.nodeFromVal(f))
|
||||
b.addInFlowEdge(b.nodeFromVal(f), fnode)
|
||||
}
|
||||
|
||||
func (b *builder) send(s *ssa.Send) {
|
||||
t := typeparams.CoreType(s.Chan.Type()).(*types.Chan).Elem()
|
||||
b.addInFlowAliasEdges(channelElem{typ: t}, b.nodeFromVal(s.X))
|
||||
}
|
||||
|
||||
// selekt generates flows for select statement
|
||||
//
|
||||
// a = select blocking/nonblocking [c_1 <- t_1, c_2 <- t_2, ..., <- o_1, <- o_2, ...]
|
||||
//
|
||||
// between receiving channel registers c_i and corresponding input register t_i. Further,
|
||||
// flows are generated between o_i and a[2 + i]. Note that a is a tuple register of type
|
||||
// <int, bool, r_1, r_2, ...> where the type of r_i is the element type of channel o_i.
|
||||
func (b *builder) selekt(s *ssa.Select) {
|
||||
recvIndex := 0
|
||||
for _, state := range s.States {
|
||||
t := typeparams.CoreType(state.Chan.Type()).(*types.Chan).Elem()
|
||||
|
||||
if state.Dir == types.SendOnly {
|
||||
b.addInFlowAliasEdges(channelElem{typ: t}, b.nodeFromVal(state.Send))
|
||||
} else {
|
||||
// state.Dir == RecvOnly by definition of select instructions.
|
||||
tupEntry := indexedLocal{val: s, typ: t, index: 2 + recvIndex}
|
||||
b.addInFlowAliasEdges(tupEntry, channelElem{typ: t})
|
||||
recvIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// index instruction a := b[c] on slices creates flows between a and
|
||||
// SliceElem(t) flow where t is an interface type of c. Arrays and
|
||||
// slice elements are both modeled as SliceElem.
|
||||
func (b *builder) index(i *ssa.Index) {
|
||||
et := sliceArrayElem(i.X.Type())
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(i), sliceElem{typ: et})
|
||||
}
|
||||
|
||||
// indexAddr instruction a := &b[c] fetches address of a index
|
||||
// into the field so we create bidirectional flow a <-> SliceElem(t)
|
||||
// where t is an interface type of c. Arrays and slice elements are
|
||||
// both modeled as SliceElem.
|
||||
func (b *builder) indexAddr(i *ssa.IndexAddr) {
|
||||
et := sliceArrayElem(i.X.Type())
|
||||
b.addInFlowEdge(sliceElem{typ: et}, b.nodeFromVal(i))
|
||||
b.addInFlowEdge(b.nodeFromVal(i), sliceElem{typ: et})
|
||||
}
|
||||
|
||||
// lookup handles map query commands a := m[b] where m is of type
|
||||
// map[...]V and V is an interface. It creates flows between `a`
|
||||
// and MapValue(V).
|
||||
func (b *builder) lookup(l *ssa.Lookup) {
|
||||
t, ok := l.X.Type().Underlying().(*types.Map)
|
||||
if !ok {
|
||||
// No interesting flows for string lookups.
|
||||
return
|
||||
}
|
||||
|
||||
if !l.CommaOk {
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(l), mapValue{typ: t.Elem()})
|
||||
} else {
|
||||
i := indexedLocal{val: l, typ: t.Elem(), index: 0}
|
||||
b.addInFlowAliasEdges(i, mapValue{typ: t.Elem()})
|
||||
}
|
||||
}
|
||||
|
||||
// mapUpdate handles map update commands m[b] = a where m is of type
|
||||
// map[K]V and K and V are interfaces. It creates flows between `a`
|
||||
// and MapValue(V) as well as between MapKey(K) and `b`.
|
||||
func (b *builder) mapUpdate(u *ssa.MapUpdate) {
|
||||
t, ok := u.Map.Type().Underlying().(*types.Map)
|
||||
if !ok {
|
||||
// No interesting flows for string updates.
|
||||
return
|
||||
}
|
||||
|
||||
b.addInFlowAliasEdges(mapKey{typ: t.Key()}, b.nodeFromVal(u.Key))
|
||||
b.addInFlowAliasEdges(mapValue{typ: t.Elem()}, b.nodeFromVal(u.Value))
|
||||
}
|
||||
|
||||
// next instruction <ok, key, value> := next r, where r
|
||||
// is a range over map or string generates flow between
|
||||
// key and MapKey as well value and MapValue nodes.
|
||||
func (b *builder) next(n *ssa.Next) {
|
||||
if n.IsString {
|
||||
return
|
||||
}
|
||||
tup := n.Type().(*types.Tuple)
|
||||
kt := tup.At(1).Type()
|
||||
vt := tup.At(2).Type()
|
||||
|
||||
b.addInFlowAliasEdges(indexedLocal{val: n, typ: kt, index: 1}, mapKey{typ: kt})
|
||||
b.addInFlowAliasEdges(indexedLocal{val: n, typ: vt, index: 2}, mapValue{typ: vt})
|
||||
}
|
||||
|
||||
// addInFlowAliasEdges adds an edge r -> l to b.graph if l is a node that can
|
||||
// have an inflow, i.e., a node that represents an interface or an unresolved
|
||||
// function value. Similarly for the edge l -> r with an additional condition
|
||||
// of that l and r can potentially alias.
|
||||
func (b *builder) addInFlowAliasEdges(l, r node) {
|
||||
b.addInFlowEdge(r, l)
|
||||
|
||||
if canAlias(l, r) {
|
||||
b.addInFlowEdge(l, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) closure(c *ssa.MakeClosure) {
|
||||
f := c.Fn.(*ssa.Function)
|
||||
b.addInFlowEdge(function{f: f}, b.nodeFromVal(c))
|
||||
|
||||
for i, fv := range f.FreeVars {
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(fv), b.nodeFromVal(c.Bindings[i]))
|
||||
}
|
||||
}
|
||||
|
||||
// panic creates a flow from arguments to panic instructions to return
|
||||
// registers of all recover statements in the program. Introduces a
|
||||
// global panic node Panic and
|
||||
// 1. for every panic statement p: add p -> Panic
|
||||
// 2. for every recover statement r: add Panic -> r (handled in call)
|
||||
//
|
||||
// TODO(zpavlinovic): improve precision by explicitly modeling how panic
|
||||
// values flow from callees to callers and into deferred recover instructions.
|
||||
func (b *builder) panic(p *ssa.Panic) {
|
||||
// Panics often have, for instance, strings as arguments which do
|
||||
// not create interesting flows.
|
||||
if !canHaveMethods(p.X.Type()) {
|
||||
return
|
||||
}
|
||||
|
||||
b.addInFlowEdge(b.nodeFromVal(p.X), panicArg{})
|
||||
}
|
||||
|
||||
// call adds flows between arguments/parameters and return values/registers
|
||||
// for both static and dynamic calls, as well as go and defer calls.
|
||||
func (b *builder) call(c ssa.CallInstruction) {
|
||||
// When c is r := recover() call register instruction, we add Recover -> r.
|
||||
if bf, ok := c.Common().Value.(*ssa.Builtin); ok && bf.Name() == "recover" {
|
||||
if v, ok := c.(ssa.Value); ok {
|
||||
b.addInFlowEdge(recoverReturn{}, b.nodeFromVal(v))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for f := range siteCallees(c, b.callees) {
|
||||
addArgumentFlows(b, c, f)
|
||||
|
||||
site, ok := c.(ssa.Value)
|
||||
if !ok {
|
||||
continue // go or defer
|
||||
}
|
||||
|
||||
results := f.Signature.Results()
|
||||
if results.Len() == 1 {
|
||||
// When there is only one return value, the destination register does not
|
||||
// have a tuple type.
|
||||
b.addInFlowEdge(resultVar{f: f, index: 0}, b.nodeFromVal(site))
|
||||
} else {
|
||||
tup := site.Type().(*types.Tuple)
|
||||
for i := 0; i < results.Len(); i++ {
|
||||
local := indexedLocal{val: site, typ: tup.At(i).Type(), index: i}
|
||||
b.addInFlowEdge(resultVar{f: f, index: i}, local)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addArgumentFlows(b *builder, c ssa.CallInstruction, f *ssa.Function) {
|
||||
// When f has no parameters (including receiver), there is no type
|
||||
// flow here. Also, f's body and parameters might be missing, such
|
||||
// as when vta is used within the golang.org/x/tools/go/analysis
|
||||
// framework (see github.com/golang/go/issues/50670).
|
||||
if len(f.Params) == 0 {
|
||||
return
|
||||
}
|
||||
cc := c.Common()
|
||||
if cc.Method != nil {
|
||||
// In principle we don't add interprocedural flows for receiver
|
||||
// objects. At a call site, the receiver object is interface
|
||||
// while the callee object is concrete. The flow from interface
|
||||
// to concrete type in general does not make sense. The exception
|
||||
// is when the concrete type is a named function type (see #57756).
|
||||
//
|
||||
// The flow other way around would bake in information from the
|
||||
// initial call graph.
|
||||
if isFunction(f.Params[0].Type()) {
|
||||
b.addInFlowEdge(b.nodeFromVal(cc.Value), b.nodeFromVal(f.Params[0]))
|
||||
}
|
||||
}
|
||||
|
||||
offset := 0
|
||||
if cc.Method != nil {
|
||||
offset = 1
|
||||
}
|
||||
for i, v := range cc.Args {
|
||||
// Parameters of f might not be available, as in the case
|
||||
// when vta is used within the golang.org/x/tools/go/analysis
|
||||
// framework (see github.com/golang/go/issues/50670).
|
||||
//
|
||||
// TODO: investigate other cases of missing body and parameters
|
||||
if len(f.Params) <= i+offset {
|
||||
return
|
||||
}
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(f.Params[i+offset]), b.nodeFromVal(v))
|
||||
}
|
||||
}
|
||||
|
||||
// rtrn creates flow edges from the operands of the return
|
||||
// statement to the result variables of the enclosing function.
|
||||
func (b *builder) rtrn(r *ssa.Return) {
|
||||
for i, rs := range r.Results {
|
||||
b.addInFlowEdge(b.nodeFromVal(rs), resultVar{f: r.Parent(), index: i})
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) multiconvert(c *ssa.MultiConvert) {
|
||||
// TODO(zpavlinovic): decide what to do on MultiConvert long term.
|
||||
// TODO(zpavlinovic): add unit tests.
|
||||
typeSetOf := func(typ types.Type) []*types.Term {
|
||||
// This is a adaptation of x/exp/typeparams.NormalTerms which x/tools cannot depend on.
|
||||
var terms []*types.Term
|
||||
var err error
|
||||
switch typ := types.Unalias(typ).(type) {
|
||||
case *types.TypeParam:
|
||||
terms, err = typeparams.StructuralTerms(typ)
|
||||
case *types.Union:
|
||||
terms, err = typeparams.UnionTermSet(typ)
|
||||
case *types.Interface:
|
||||
terms, err = typeparams.InterfaceTermSet(typ)
|
||||
default:
|
||||
// Common case.
|
||||
// Specializing the len=1 case to avoid a slice
|
||||
// had no measurable space/time benefit.
|
||||
terms = []*types.Term{types.NewTerm(false, typ)}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return terms
|
||||
}
|
||||
// isValuePreserving returns true if a conversion from ut_src to
|
||||
// ut_dst is value-preserving, i.e. just a change of type.
|
||||
// Precondition: neither argument is a named or alias type.
|
||||
isValuePreserving := func(ut_src, ut_dst types.Type) bool {
|
||||
// Identical underlying types?
|
||||
if types.IdenticalIgnoreTags(ut_dst, ut_src) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch ut_dst.(type) {
|
||||
case *types.Chan:
|
||||
// Conversion between channel types?
|
||||
_, ok := ut_src.(*types.Chan)
|
||||
return ok
|
||||
|
||||
case *types.Pointer:
|
||||
// Conversion between pointers with identical base types?
|
||||
_, ok := ut_src.(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
dst_terms := typeSetOf(c.Type())
|
||||
src_terms := typeSetOf(c.X.Type())
|
||||
for _, s := range src_terms {
|
||||
us := s.Type().Underlying()
|
||||
for _, d := range dst_terms {
|
||||
ud := d.Type().Underlying()
|
||||
if isValuePreserving(us, ud) {
|
||||
// This is equivalent to a ChangeType.
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(c), b.nodeFromVal(c.X))
|
||||
return
|
||||
}
|
||||
// This is equivalent to either: SliceToArrayPointer,,
|
||||
// SliceToArrayPointer+Deref, Size 0 Array constant, or a Convert.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addInFlowEdge adds s -> d to g if d is node that can have an inflow, i.e., a node
|
||||
// that represents an interface or an unresolved function value. Otherwise, there
|
||||
// is no interesting type flow so the edge is omitted.
|
||||
func (b *builder) addInFlowEdge(s, d node) {
|
||||
if hasInFlow(d) {
|
||||
b.graph.addEdge(b.representative(s), b.representative(d))
|
||||
}
|
||||
}
|
||||
|
||||
// Creates const, pointer, global, func, and local nodes based on register instructions.
|
||||
func (b *builder) nodeFromVal(val ssa.Value) node {
|
||||
if p, ok := types.Unalias(val.Type()).(*types.Pointer); ok && !types.IsInterface(p.Elem()) && !isFunction(p.Elem()) {
|
||||
// Nested pointer to interfaces are modeled as a special
|
||||
// nestedPtrInterface node.
|
||||
if i := interfaceUnderPtr(p.Elem()); i != nil {
|
||||
return nestedPtrInterface{typ: i}
|
||||
}
|
||||
// The same goes for nested function types.
|
||||
if f := functionUnderPtr(p.Elem()); f != nil {
|
||||
return nestedPtrFunction{typ: f}
|
||||
}
|
||||
return pointer{typ: p}
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case *ssa.Const:
|
||||
return constant{typ: val.Type()}
|
||||
case *ssa.Global:
|
||||
return global{val: v}
|
||||
case *ssa.Function:
|
||||
return function{f: v}
|
||||
case *ssa.Parameter, *ssa.FreeVar, ssa.Instruction:
|
||||
// ssa.Param, ssa.FreeVar, and a specific set of "register" instructions,
|
||||
// satisfying the ssa.Value interface, can serve as local variables.
|
||||
return local{val: v}
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported value %v in node creation", val))
|
||||
}
|
||||
}
|
||||
|
||||
// representative returns a unique representative for node `n`. Since
|
||||
// semantically equivalent types can have different implementations,
|
||||
// this method guarantees the same implementation is always used.
|
||||
func (b *builder) representative(n node) node {
|
||||
if n.Type() == nil {
|
||||
// panicArg and recoverReturn do not have
|
||||
// types and are unique by definition.
|
||||
return n
|
||||
}
|
||||
t := canonicalize(n.Type(), &b.canon)
|
||||
|
||||
switch i := n.(type) {
|
||||
case constant:
|
||||
return constant{typ: t}
|
||||
case pointer:
|
||||
return pointer{typ: t.(*types.Pointer)}
|
||||
case sliceElem:
|
||||
return sliceElem{typ: t}
|
||||
case mapKey:
|
||||
return mapKey{typ: t}
|
||||
case mapValue:
|
||||
return mapValue{typ: t}
|
||||
case channelElem:
|
||||
return channelElem{typ: t}
|
||||
case nestedPtrInterface:
|
||||
return nestedPtrInterface{typ: t}
|
||||
case nestedPtrFunction:
|
||||
return nestedPtrFunction{typ: t}
|
||||
case field:
|
||||
return field{StructType: canonicalize(i.StructType, &b.canon), index: i.index}
|
||||
case indexedLocal:
|
||||
return indexedLocal{typ: t, val: i.val, index: i.index}
|
||||
case local, global, panicArg, recoverReturn, function, resultVar:
|
||||
return n
|
||||
default:
|
||||
panic(fmt.Errorf("canonicalizing unrecognized node %v", n))
|
||||
}
|
||||
}
|
||||
|
||||
// canonicalize returns a type representative of `t` unique subject
|
||||
// to type map `canon`.
|
||||
func canonicalize(t types.Type, canon *typeutil.Map) types.Type {
|
||||
rep := canon.At(t)
|
||||
if rep != nil {
|
||||
return rep.(types.Type)
|
||||
}
|
||||
canon.Set(t, t)
|
||||
return t
|
||||
}
|
||||
37
vendor/golang.org/x/tools/go/callgraph/vta/initial.go
generated
vendored
Normal file
37
vendor/golang.org/x/tools/go/callgraph/vta/initial.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2024 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 (
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/callgraph/internal/chautil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// calleesFunc abstracts call graph in one direction,
|
||||
// from call sites to callees.
|
||||
type calleesFunc func(ssa.CallInstruction) []*ssa.Function
|
||||
|
||||
// makeCalleesFunc returns an initial call graph for vta as a
|
||||
// calleesFunc. If c is not nil, returns callees as given by c.
|
||||
// Otherwise, it returns chautil.LazyCallees over fs.
|
||||
func makeCalleesFunc(fs map[*ssa.Function]bool, c *callgraph.Graph) calleesFunc {
|
||||
if c == nil {
|
||||
return chautil.LazyCallees(fs)
|
||||
}
|
||||
return func(call ssa.CallInstruction) []*ssa.Function {
|
||||
node := c.Nodes[call.Parent()]
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
var cs []*ssa.Function
|
||||
for _, edge := range node.Out {
|
||||
if edge.Site == call {
|
||||
cs = append(cs, edge.Callee.Func)
|
||||
}
|
||||
}
|
||||
return cs
|
||||
}
|
||||
}
|
||||
127
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/bits.go
generated
vendored
Normal file
127
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/bits.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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 trie
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// This file contains bit twiddling functions for Patricia tries.
|
||||
// Consult this paper for details.
|
||||
// C. Okasaki and A. Gill, “Fast mergeable integer maps,” in ACM SIGPLAN
|
||||
// Workshop on ML, September 1998, pp. 77–86.
|
||||
|
||||
// key is a key in a Map.
|
||||
type key uint64
|
||||
|
||||
// bitpos is the position of a bit. A position is represented by having a 1
|
||||
// bit in that position.
|
||||
// Examples:
|
||||
// - 0b0010 is the position of the `1` bit in 2.
|
||||
// It is the 3rd most specific bit position in big endian encoding
|
||||
// (0b0 and 0b1 are more specific).
|
||||
// - 0b0100 is the position of the bit that 1 and 5 disagree on.
|
||||
// - 0b0 is a special value indicating that all bit agree.
|
||||
type bitpos uint64
|
||||
|
||||
// prefixes represent a set of keys that all agree with the
|
||||
// prefix up to a bitpos m.
|
||||
//
|
||||
// The value for a prefix is determined by the mask(k, m) function.
|
||||
// (See mask for details on the values.)
|
||||
// A `p` prefix for position `m` matches a key `k` iff mask(k, m) == p.
|
||||
// A prefix always mask(p, m) == p.
|
||||
//
|
||||
// A key is its own prefix for the bit position 64,
|
||||
// e.g. seeing a `prefix(key)` is not a problem.
|
||||
//
|
||||
// Prefixes should never be turned into keys.
|
||||
type prefix uint64
|
||||
|
||||
// branchingBit returns the position of the first bit in `x` and `y`
|
||||
// that are not equal.
|
||||
func branchingBit(x, y prefix) bitpos {
|
||||
p := x ^ y
|
||||
if p == 0 {
|
||||
return 0
|
||||
}
|
||||
return bitpos(1) << uint(bits.Len64(uint64(p))-1) // uint conversion needed for go1.12
|
||||
}
|
||||
|
||||
// zeroBit returns true if k has a 0 bit at position `b`.
|
||||
func zeroBit(k prefix, b bitpos) bool {
|
||||
return (uint64(k) & uint64(b)) == 0
|
||||
}
|
||||
|
||||
// matchPrefix returns true if a prefix k matches a prefix p up to position `b`.
|
||||
func matchPrefix(k prefix, p prefix, b bitpos) bool {
|
||||
return mask(k, b) == p
|
||||
}
|
||||
|
||||
// mask returns a prefix of `k` with all bits after and including `b` zeroed out.
|
||||
//
|
||||
// In big endian encoding, this value is the [64-(m-1)] most significant bits of k
|
||||
// followed by a `0` bit at bitpos m, followed m-1 `1` bits.
|
||||
// Examples:
|
||||
//
|
||||
// prefix(0b1011) for a bitpos 0b0100 represents the keys:
|
||||
// 0b1000, 0b1001, 0b1010, 0b1011, 0b1100, 0b1101, 0b1110, 0b1111
|
||||
//
|
||||
// This mask function has the property that if matchPrefix(k, p, b), then
|
||||
// k <= p if and only if zeroBit(k, m). This induces binary search tree tries.
|
||||
// See Okasaki & Gill for more details about this choice of mask function.
|
||||
//
|
||||
// mask is idempotent for a given `b`, i.e. mask(mask(p, b), b) == mask(p,b).
|
||||
func mask(k prefix, b bitpos) prefix {
|
||||
return prefix((uint64(k) | (uint64(b) - 1)) & (^uint64(b)))
|
||||
}
|
||||
|
||||
// ord returns true if m comes before n in the bit ordering.
|
||||
func ord(m, n bitpos) bool {
|
||||
return m > n // big endian encoding
|
||||
}
|
||||
|
||||
// prefixesOverlap returns true if there is some key a prefix `p` for bitpos `m`
|
||||
// can hold that can also be held by a prefix `q` for some bitpos `n`.
|
||||
//
|
||||
// This is equivalent to:
|
||||
//
|
||||
// m ==n && p == q,
|
||||
// higher(m, n) && matchPrefix(q, p, m), or
|
||||
// higher(n, m) && matchPrefix(p, q, n)
|
||||
func prefixesOverlap(p prefix, m bitpos, q prefix, n bitpos) bool {
|
||||
fbb := n
|
||||
if ord(m, n) {
|
||||
fbb = m
|
||||
}
|
||||
return mask(p, fbb) == mask(q, fbb)
|
||||
// Lemma:
|
||||
// mask(p, fbb) == mask(q, fbb)
|
||||
// iff
|
||||
// m > n && matchPrefix(q, p, m) or (note: big endian encoding)
|
||||
// m < n && matchPrefix(p, q, n) or (note: big endian encoding)
|
||||
// m ==n && p == q
|
||||
// Quick-n-dirty proof:
|
||||
// p == mask(p0, m) for some p0 by precondition.
|
||||
// q == mask(q0, n) for some q0 by precondition.
|
||||
// So mask(p, m) == p and mask(q, n) == q as mask(*, n') is idempotent.
|
||||
//
|
||||
// [=> proof]
|
||||
// Suppose mask(p, fbb) == mask(q, fbb).
|
||||
// if m ==n, p == mask(p, m) == mask(p, fbb) == mask(q, fbb) == mask(q, n) == q
|
||||
// if m > n, fbb = firstBranchBit(m, n) = m (big endian).
|
||||
// p == mask(p, m) == mask(p, fbb) == mask(q, fbb) == mask(q, m)
|
||||
// so mask(q, m) == p or matchPrefix(q, p, m)
|
||||
// if m < n, is symmetric to the above.
|
||||
//
|
||||
// [<= proof]
|
||||
// case m ==n && p == q. Then mask(p, fbb) == mask(q, fbb)
|
||||
//
|
||||
// case m > n && matchPrefix(q, p, m).
|
||||
// fbb == firstBranchBit(m, n) == m (by m>n).
|
||||
// mask(q, fbb) == mask(q, m) == p == mask(p, m) == mask(p, fbb)
|
||||
//
|
||||
// case m < n && matchPrefix(p, q, n) is symmetric.
|
||||
}
|
||||
516
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/builder.go
generated
vendored
Normal file
516
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/builder.go
generated
vendored
Normal file
@@ -0,0 +1,516 @@
|
||||
// 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 trie
|
||||
|
||||
// Collision functions combine a left and right hand side (lhs and rhs) values
|
||||
// the two values are associated with the same key and produces the value that
|
||||
// will be stored for the key.
|
||||
//
|
||||
// Collision functions must be idempotent:
|
||||
//
|
||||
// collision(x, x) == x for all x.
|
||||
//
|
||||
// Collisions functions may be applied whenever a value is inserted
|
||||
// or two maps are merged, or intersected.
|
||||
type Collision func(lhs any, rhs any) any
|
||||
|
||||
// TakeLhs always returns the left value in a collision.
|
||||
func TakeLhs(lhs, rhs any) any { return lhs }
|
||||
|
||||
// TakeRhs always returns the right hand side in a collision.
|
||||
func TakeRhs(lhs, rhs any) any { return rhs }
|
||||
|
||||
// Builder creates new Map. Each Builder has a unique Scope.
|
||||
//
|
||||
// IMPORTANT: Nodes are hash-consed internally to reduce memory consumption. To
|
||||
// support hash-consing Builders keep an internal Map of all of the Maps that they
|
||||
// have created. To GC any of the Maps created by the Builder, all references to
|
||||
// the Builder must be dropped. This includes MutMaps.
|
||||
type Builder struct {
|
||||
scope Scope
|
||||
|
||||
// hash-consing maps for each node type.
|
||||
empty *empty
|
||||
leaves map[leaf]*leaf
|
||||
branches map[branch]*branch
|
||||
// It may be possible to support more types of patricia tries
|
||||
// (e.g. non-hash-consed) by making Builder an interface and abstracting
|
||||
// the mkLeaf and mkBranch functions.
|
||||
}
|
||||
|
||||
// NewBuilder creates a new Builder with a unique Scope.
|
||||
func NewBuilder() *Builder {
|
||||
s := newScope()
|
||||
return &Builder{
|
||||
scope: s,
|
||||
empty: &empty{s},
|
||||
leaves: make(map[leaf]*leaf),
|
||||
branches: make(map[branch]*branch),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) Scope() Scope { return b.scope }
|
||||
|
||||
// Rescope changes the builder's scope to a new unique Scope.
|
||||
//
|
||||
// Any Maps created using the previous scope need to be Cloned
|
||||
// before any operation.
|
||||
//
|
||||
// This makes the old internals of the Builder eligible to be GC'ed.
|
||||
func (b *Builder) Rescope() {
|
||||
s := newScope()
|
||||
b.scope = s
|
||||
b.empty = &empty{s}
|
||||
b.leaves = make(map[leaf]*leaf)
|
||||
b.branches = make(map[branch]*branch)
|
||||
}
|
||||
|
||||
// Empty is the empty map.
|
||||
func (b *Builder) Empty() Map { return Map{b.Scope(), b.empty} }
|
||||
|
||||
// InsertWith inserts a new association from k to v into the Map m to create a new map
|
||||
// in the current scope and handle collisions using the collision function c.
|
||||
//
|
||||
// This is roughly corresponds to updating a map[uint64]interface{} by:
|
||||
//
|
||||
// if _, ok := m[k]; ok { m[k] = c(m[k], v} else { m[k] = v}
|
||||
//
|
||||
// An insertion or update happened whenever Insert(m, ...) != m .
|
||||
func (b *Builder) InsertWith(c Collision, m Map, k uint64, v any) Map {
|
||||
m = b.Clone(m)
|
||||
return Map{b.Scope(), b.insert(c, m.n, b.mkLeaf(key(k), v), false)}
|
||||
}
|
||||
|
||||
// Inserts a new association from key to value into the Map m to create
|
||||
// a new map in the current scope.
|
||||
//
|
||||
// If there was a previous value mapped by key, keep the previously mapped value.
|
||||
// This is roughly corresponds to updating a map[uint64]interface{} by:
|
||||
//
|
||||
// if _, ok := m[k]; ok { m[k] = val }
|
||||
//
|
||||
// This is equivalent to b.Merge(m, b.Create({k: v})).
|
||||
func (b *Builder) Insert(m Map, k uint64, v any) Map {
|
||||
return b.InsertWith(TakeLhs, m, k, v)
|
||||
}
|
||||
|
||||
// Updates a (key, value) in the map. This is roughly corresponds to
|
||||
// updating a map[uint64]interface{} by:
|
||||
//
|
||||
// m[key] = val
|
||||
func (b *Builder) Update(m Map, key uint64, val any) Map {
|
||||
return b.InsertWith(TakeRhs, m, key, val)
|
||||
}
|
||||
|
||||
// Merge two maps lhs and rhs to create a new map in the current scope.
|
||||
//
|
||||
// Whenever there is a key in both maps (a collision), the resulting value mapped by
|
||||
// the key will be `c(lhs[key], rhs[key])`.
|
||||
func (b *Builder) MergeWith(c Collision, lhs, rhs Map) Map {
|
||||
lhs, rhs = b.Clone(lhs), b.Clone(rhs)
|
||||
return Map{b.Scope(), b.merge(c, lhs.n, rhs.n)}
|
||||
}
|
||||
|
||||
// Merge two maps lhs and rhs to create a new map in the current scope.
|
||||
//
|
||||
// Whenever there is a key in both maps (a collision), the resulting value mapped by
|
||||
// the key will be the value in lhs `b.Collision(lhs[key], rhs[key])`.
|
||||
func (b *Builder) Merge(lhs, rhs Map) Map {
|
||||
return b.MergeWith(TakeLhs, lhs, rhs)
|
||||
}
|
||||
|
||||
// Clone returns a Map that contains the same (key, value) elements
|
||||
// within b.Scope(), i.e. return m if m.Scope() == b.Scope() or return
|
||||
// a deep copy of m within b.Scope() otherwise.
|
||||
func (b *Builder) Clone(m Map) Map {
|
||||
if m.Scope() == b.Scope() {
|
||||
return m
|
||||
} else if m.n == nil {
|
||||
return Map{b.Scope(), b.empty}
|
||||
}
|
||||
return Map{b.Scope(), b.clone(m.n)}
|
||||
}
|
||||
func (b *Builder) clone(n node) node {
|
||||
switch n := n.(type) {
|
||||
case *empty:
|
||||
return b.empty
|
||||
case *leaf:
|
||||
return b.mkLeaf(n.k, n.v)
|
||||
case *branch:
|
||||
return b.mkBranch(n.prefix, n.branching, b.clone(n.left), b.clone(n.right))
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a key from a Map m and return the resulting Map.
|
||||
func (b *Builder) Remove(m Map, k uint64) Map {
|
||||
m = b.Clone(m)
|
||||
return Map{b.Scope(), b.remove(m.n, key(k))}
|
||||
}
|
||||
|
||||
// Intersect Maps lhs and rhs and returns a map with all of the keys in
|
||||
// both lhs and rhs and the value comes from lhs, i.e.
|
||||
//
|
||||
// {(k, lhs[k]) | k in lhs, k in rhs}.
|
||||
func (b *Builder) Intersect(lhs, rhs Map) Map {
|
||||
return b.IntersectWith(TakeLhs, lhs, rhs)
|
||||
}
|
||||
|
||||
// IntersectWith take lhs and rhs and returns the intersection
|
||||
// with the value coming from the collision function, i.e.
|
||||
//
|
||||
// {(k, c(lhs[k], rhs[k]) ) | k in lhs, k in rhs}.
|
||||
//
|
||||
// The elements of the resulting map are always { <k, c(lhs[k], rhs[k]) > }
|
||||
// for each key k that a key in both lhs and rhs.
|
||||
func (b *Builder) IntersectWith(c Collision, lhs, rhs Map) Map {
|
||||
l, r := b.Clone(lhs), b.Clone(rhs)
|
||||
return Map{b.Scope(), b.intersect(c, l.n, r.n)}
|
||||
}
|
||||
|
||||
// MutMap is a convenient wrapper for a Map and a *Builder that will be used to create
|
||||
// new Maps from it.
|
||||
type MutMap struct {
|
||||
B *Builder
|
||||
M Map
|
||||
}
|
||||
|
||||
// MutEmpty is an empty MutMap for a builder.
|
||||
func (b *Builder) MutEmpty() MutMap {
|
||||
return MutMap{b, b.Empty()}
|
||||
}
|
||||
|
||||
// Insert an element into the map using the collision function for the builder.
|
||||
// Returns true if the element was inserted.
|
||||
func (mm *MutMap) Insert(k uint64, v any) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.Insert(old, k, v)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
// Updates an element in the map. Returns true if the map was updated.
|
||||
func (mm *MutMap) Update(k uint64, v any) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.Update(old, k, v)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
// Removes a key from the map. Returns true if the element was removed.
|
||||
func (mm *MutMap) Remove(k uint64) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.Remove(old, k)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
// Merge another map into the current one using the collision function
|
||||
// for the builder. Returns true if the map changed.
|
||||
func (mm *MutMap) Merge(other Map) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.Merge(old, other)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
// Intersect another map into the current one using the collision function
|
||||
// for the builder. Returns true if the map changed.
|
||||
func (mm *MutMap) Intersect(other Map) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.Intersect(old, other)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
func (b *Builder) Create(m map[uint64]any) Map {
|
||||
var leaves []*leaf
|
||||
for k, v := range m {
|
||||
leaves = append(leaves, b.mkLeaf(key(k), v))
|
||||
}
|
||||
return Map{b.Scope(), b.create(leaves)}
|
||||
}
|
||||
|
||||
// Merge another map into the current one using the collision function
|
||||
// for the builder. Returns true if the map changed.
|
||||
func (mm *MutMap) MergeWith(c Collision, other Map) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.MergeWith(c, old, other)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
// creates a map for a collection of leaf nodes.
|
||||
func (b *Builder) create(leaves []*leaf) node {
|
||||
n := len(leaves)
|
||||
if n == 0 {
|
||||
return b.empty
|
||||
} else if n == 1 {
|
||||
return leaves[0]
|
||||
}
|
||||
// Note: we can do a more sophisticated algorithm by:
|
||||
// - sorting the leaves ahead of time,
|
||||
// - taking the prefix and branching bit of the min and max key,
|
||||
// - binary searching for the branching bit,
|
||||
// - splitting exactly where the branch will be, and
|
||||
// - making the branch node for this prefix + branching bit.
|
||||
// Skipping until this is a performance bottleneck.
|
||||
|
||||
m := n / 2 // (n >= 2) ==> 1 <= m < n
|
||||
l, r := leaves[:m], leaves[m:]
|
||||
return b.merge(nil, b.create(l), b.create(r))
|
||||
}
|
||||
|
||||
// mkLeaf returns the hash-consed representative of (k, v) in the current scope.
|
||||
func (b *Builder) mkLeaf(k key, v any) *leaf {
|
||||
rep, ok := b.leaves[leaf{k, v}]
|
||||
if !ok {
|
||||
rep = &leaf{k, v} // heap-allocated copy
|
||||
b.leaves[leaf{k, v}] = rep
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
||||
// mkBranch returns the hash-consed representative of the tuple
|
||||
//
|
||||
// (prefix, branch, left, right)
|
||||
//
|
||||
// in the current scope.
|
||||
func (b *Builder) mkBranch(p prefix, bp bitpos, left node, right node) *branch {
|
||||
br := branch{
|
||||
sz: left.size() + right.size(),
|
||||
prefix: p,
|
||||
branching: bp,
|
||||
left: left,
|
||||
right: right,
|
||||
}
|
||||
rep, ok := b.branches[br]
|
||||
if !ok {
|
||||
rep = new(branch) // heap-allocated copy
|
||||
*rep = br
|
||||
b.branches[br] = rep
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
||||
// join two maps with prefixes p0 and p1 that are *known* to disagree.
|
||||
func (b *Builder) join(p0 prefix, t0 node, p1 prefix, t1 node) *branch {
|
||||
m := branchingBit(p0, p1)
|
||||
var left, right node
|
||||
if zeroBit(p0, m) {
|
||||
left, right = t0, t1
|
||||
} else {
|
||||
left, right = t1, t0
|
||||
}
|
||||
prefix := mask(p0, m)
|
||||
return b.mkBranch(prefix, m, left, right)
|
||||
}
|
||||
|
||||
// collide two leaves with the same key to create a leaf
|
||||
// with the collided value.
|
||||
func (b *Builder) collide(c Collision, left, right *leaf) *leaf {
|
||||
if left == right {
|
||||
return left // c is idempotent: c(x, x) == x
|
||||
}
|
||||
val := left.v // keep the left value by default if c is nil
|
||||
if c != nil {
|
||||
val = c(left.v, right.v)
|
||||
}
|
||||
switch val {
|
||||
case left.v:
|
||||
return left
|
||||
case right.v:
|
||||
return right
|
||||
default:
|
||||
return b.mkLeaf(left.k, val)
|
||||
}
|
||||
}
|
||||
|
||||
// inserts a leaf l into a map m and returns the resulting map.
|
||||
// When lhs is true, l is the left hand side in a collision.
|
||||
// Both l and m are in the current scope.
|
||||
func (b *Builder) insert(c Collision, m node, l *leaf, lhs bool) node {
|
||||
switch m := m.(type) {
|
||||
case *empty:
|
||||
return l
|
||||
case *leaf:
|
||||
if m.k == l.k {
|
||||
left, right := l, m
|
||||
if !lhs {
|
||||
left, right = right, left
|
||||
}
|
||||
return b.collide(c, left, right)
|
||||
}
|
||||
return b.join(prefix(l.k), l, prefix(m.k), m)
|
||||
case *branch:
|
||||
// fallthrough
|
||||
}
|
||||
// m is a branch
|
||||
br := m.(*branch)
|
||||
if !matchPrefix(prefix(l.k), br.prefix, br.branching) {
|
||||
return b.join(prefix(l.k), l, br.prefix, br)
|
||||
}
|
||||
var left, right node
|
||||
if zeroBit(prefix(l.k), br.branching) {
|
||||
left, right = b.insert(c, br.left, l, lhs), br.right
|
||||
} else {
|
||||
left, right = br.left, b.insert(c, br.right, l, lhs)
|
||||
}
|
||||
if left == br.left && right == br.right {
|
||||
return m
|
||||
}
|
||||
return b.mkBranch(br.prefix, br.branching, left, right)
|
||||
}
|
||||
|
||||
// merge two maps in the current scope.
|
||||
func (b *Builder) merge(c Collision, lhs, rhs node) node {
|
||||
if lhs == rhs {
|
||||
return lhs
|
||||
}
|
||||
switch lhs := lhs.(type) {
|
||||
case *empty:
|
||||
return rhs
|
||||
case *leaf:
|
||||
return b.insert(c, rhs, lhs, true)
|
||||
case *branch:
|
||||
switch rhs := rhs.(type) {
|
||||
case *empty:
|
||||
return lhs
|
||||
case *leaf:
|
||||
return b.insert(c, lhs, rhs, false)
|
||||
case *branch:
|
||||
// fallthrough
|
||||
}
|
||||
}
|
||||
|
||||
// Last remaining case is branch merging.
|
||||
// For brevity, we adopt the Okasaki and Gill naming conventions
|
||||
// for branching and prefixes.
|
||||
s, t := lhs.(*branch), rhs.(*branch)
|
||||
p, m := s.prefix, s.branching
|
||||
q, n := t.prefix, t.branching
|
||||
|
||||
if m == n && p == q { // prefixes are identical.
|
||||
left, right := b.merge(c, s.left, t.left), b.merge(c, s.right, t.right)
|
||||
return b.mkBranch(p, m, left, right)
|
||||
}
|
||||
if !prefixesOverlap(p, m, q, n) {
|
||||
return b.join(p, s, q, t) // prefixes are disjoint.
|
||||
}
|
||||
// prefixesOverlap(p, m, q, n) && !(m ==n && p == q)
|
||||
// By prefixesOverlap(...), either:
|
||||
// higher(m, n) && matchPrefix(q, p, m), or
|
||||
// higher(n, m) && matchPrefix(p, q, n)
|
||||
// So either s or t may can be merged with one branch or the other.
|
||||
switch {
|
||||
case ord(m, n) && zeroBit(q, m):
|
||||
return b.mkBranch(p, m, b.merge(c, s.left, t), s.right)
|
||||
case ord(m, n) && !zeroBit(q, m):
|
||||
return b.mkBranch(p, m, s.left, b.merge(c, s.right, t))
|
||||
case ord(n, m) && zeroBit(p, n):
|
||||
return b.mkBranch(q, n, b.merge(c, s, t.left), t.right)
|
||||
default:
|
||||
return b.mkBranch(q, n, t.left, b.merge(c, s, t.right))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) remove(m node, k key) node {
|
||||
switch m := m.(type) {
|
||||
case *empty:
|
||||
return m
|
||||
case *leaf:
|
||||
if m.k == k {
|
||||
return b.empty
|
||||
}
|
||||
return m
|
||||
case *branch:
|
||||
// fallthrough
|
||||
}
|
||||
br := m.(*branch)
|
||||
kp := prefix(k)
|
||||
if !matchPrefix(kp, br.prefix, br.branching) {
|
||||
// The prefix does not match. kp is not in br.
|
||||
return br
|
||||
}
|
||||
// the prefix matches. try to remove from the left or right branch.
|
||||
left, right := br.left, br.right
|
||||
if zeroBit(kp, br.branching) {
|
||||
left = b.remove(left, k) // k may be in the left branch.
|
||||
} else {
|
||||
right = b.remove(right, k) // k may be in the right branch.
|
||||
}
|
||||
if left == br.left && right == br.right {
|
||||
return br // no update
|
||||
} else if _, ok := left.(*empty); ok {
|
||||
return right // left updated and is empty.
|
||||
} else if _, ok := right.(*empty); ok {
|
||||
return left // right updated and is empty.
|
||||
}
|
||||
// Either left or right updated. Both left and right are not empty.
|
||||
// The left and right branches still share the same prefix and disagree
|
||||
// on the same branching bit. It is safe to directly create the branch.
|
||||
return b.mkBranch(br.prefix, br.branching, left, right)
|
||||
}
|
||||
|
||||
func (b *Builder) intersect(c Collision, l, r node) node {
|
||||
if l == r {
|
||||
return l
|
||||
}
|
||||
switch l := l.(type) {
|
||||
case *empty:
|
||||
return b.empty
|
||||
case *leaf:
|
||||
if rleaf := r.find(l.k); rleaf != nil {
|
||||
return b.collide(c, l, rleaf)
|
||||
}
|
||||
return b.empty
|
||||
case *branch:
|
||||
switch r := r.(type) {
|
||||
case *empty:
|
||||
return b.empty
|
||||
case *leaf:
|
||||
if lleaf := l.find(r.k); lleaf != nil {
|
||||
return b.collide(c, lleaf, r)
|
||||
}
|
||||
return b.empty
|
||||
case *branch:
|
||||
// fallthrough
|
||||
}
|
||||
}
|
||||
// Last remaining case is branch intersection.
|
||||
s, t := l.(*branch), r.(*branch)
|
||||
p, m := s.prefix, s.branching
|
||||
q, n := t.prefix, t.branching
|
||||
|
||||
if m == n && p == q {
|
||||
// prefixes are identical.
|
||||
left, right := b.intersect(c, s.left, t.left), b.intersect(c, s.right, t.right)
|
||||
if _, ok := left.(*empty); ok {
|
||||
return right
|
||||
} else if _, ok := right.(*empty); ok {
|
||||
return left
|
||||
}
|
||||
// The left and right branches are both non-empty.
|
||||
// They still share the same prefix and disagree on the same branching bit.
|
||||
// It is safe to directly create the branch.
|
||||
return b.mkBranch(p, m, left, right)
|
||||
}
|
||||
|
||||
if !prefixesOverlap(p, m, q, n) {
|
||||
return b.empty // The prefixes share no keys.
|
||||
}
|
||||
// prefixesOverlap(p, m, q, n) && !(m ==n && p == q)
|
||||
// By prefixesOverlap(...), either:
|
||||
// ord(m, n) && matchPrefix(q, p, m), or
|
||||
// ord(n, m) && matchPrefix(p, q, n)
|
||||
// So either s or t may be a strict subtree of the other.
|
||||
var lhs, rhs node
|
||||
switch {
|
||||
case ord(m, n) && zeroBit(q, m):
|
||||
lhs, rhs = s.left, t
|
||||
case ord(m, n) && !zeroBit(q, m):
|
||||
lhs, rhs = s.right, t
|
||||
case ord(n, m) && zeroBit(p, n):
|
||||
lhs, rhs = s, t.left
|
||||
default:
|
||||
lhs, rhs = s, t.right
|
||||
}
|
||||
return b.intersect(c, lhs, rhs)
|
||||
}
|
||||
28
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/scope.go
generated
vendored
Normal file
28
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/scope.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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 trie
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Scope represents a distinct collection of maps.
|
||||
// Maps with the same Scope can be equal. Maps in different scopes are distinct.
|
||||
// Each Builder creates maps within a unique Scope.
|
||||
type Scope struct {
|
||||
id int32
|
||||
}
|
||||
|
||||
var nextScopeId int32
|
||||
|
||||
func newScope() Scope {
|
||||
id := atomic.AddInt32(&nextScopeId, 1)
|
||||
return Scope{id: id}
|
||||
}
|
||||
|
||||
func (s Scope) String() string {
|
||||
return strconv.Itoa(int(s.id))
|
||||
}
|
||||
229
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/trie.go
generated
vendored
Normal file
229
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/trie.go
generated
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
// 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.
|
||||
|
||||
// trie implements persistent Patricia trie maps.
|
||||
//
|
||||
// Each Map is effectively a map from uint64 to interface{}. Patricia tries are
|
||||
// a form of radix tree that are particularly appropriate when many maps will be
|
||||
// created, merged together and large amounts of sharing are expected (e.g.
|
||||
// environment abstract domains in program analysis).
|
||||
//
|
||||
// This implementation closely follows the paper:
|
||||
//
|
||||
// C. Okasaki and A. Gill, “Fast mergeable integer maps,” in ACM SIGPLAN
|
||||
// Workshop on ML, September 1998, pp. 77–86.
|
||||
//
|
||||
// Each Map is immutable and can be read from concurrently. The map does not
|
||||
// guarantee that the value pointed to by the interface{} value is not updated
|
||||
// concurrently.
|
||||
//
|
||||
// These Maps are optimized for situations where there will be many maps created at
|
||||
// with a high degree of sharing and combining of maps together. If you do not expect,
|
||||
// significant amount of sharing, the builtin map[T]U is much better choice!
|
||||
//
|
||||
// Each Map is created by a Builder. Each Builder has a unique Scope and each node is
|
||||
// created within this scope. Maps x and y are == if they contains the same
|
||||
// (key,value) mappings and have equal scopes.
|
||||
//
|
||||
// Internally these are big endian Patricia trie nodes, and the keys are sorted.
|
||||
package trie
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Map is effectively a finite mapping from uint64 keys to interface{} values.
|
||||
// Maps are immutable and can be read from concurrently.
|
||||
//
|
||||
// Notes on concurrency:
|
||||
// - A Map value itself is an interface and assignments to a Map value can race.
|
||||
// - Map does not guarantee that the value pointed to by the interface{} value
|
||||
// is not updated concurrently.
|
||||
type Map struct {
|
||||
s Scope
|
||||
n node
|
||||
}
|
||||
|
||||
func (m Map) Scope() Scope {
|
||||
return m.s
|
||||
}
|
||||
func (m Map) Size() int {
|
||||
if m.n == nil {
|
||||
return 0
|
||||
}
|
||||
return m.n.size()
|
||||
}
|
||||
func (m Map) Lookup(k uint64) (any, bool) {
|
||||
if m.n != nil {
|
||||
if leaf := m.n.find(key(k)); leaf != nil {
|
||||
return leaf.v, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Converts the map into a {<key>: <value>[, ...]} string. This uses the default
|
||||
// %s string conversion for <value>.
|
||||
func (m Map) String() string {
|
||||
var kvs []string
|
||||
m.Range(func(u uint64, i any) bool {
|
||||
kvs = append(kvs, fmt.Sprintf("%d: %s", u, i))
|
||||
return true
|
||||
})
|
||||
return fmt.Sprintf("{%s}", strings.Join(kvs, ", "))
|
||||
}
|
||||
|
||||
// Range over the leaf (key, value) pairs in the map in order and
|
||||
// applies cb(key, value) to each. Stops early if cb returns false.
|
||||
// Returns true if all elements were visited without stopping early.
|
||||
func (m Map) Range(cb func(uint64, any) bool) bool {
|
||||
if m.n != nil {
|
||||
return m.n.visit(cb)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DeepEqual returns true if m and other contain the same (k, v) mappings
|
||||
// [regardless of Scope].
|
||||
//
|
||||
// Equivalently m.DeepEqual(other) <=> reflect.DeepEqual(Elems(m), Elems(other))
|
||||
func (m Map) DeepEqual(other Map) bool {
|
||||
if m.Scope() == other.Scope() {
|
||||
return m.n == other.n
|
||||
}
|
||||
if (m.n == nil) || (other.n == nil) {
|
||||
return m.Size() == 0 && other.Size() == 0
|
||||
}
|
||||
return m.n.deepEqual(other.n)
|
||||
}
|
||||
|
||||
// Elems are the (k,v) elements in the Map as a map[uint64]interface{}
|
||||
func Elems(m Map) map[uint64]any {
|
||||
dest := make(map[uint64]any, m.Size())
|
||||
m.Range(func(k uint64, v any) bool {
|
||||
dest[k] = v
|
||||
return true
|
||||
})
|
||||
return dest
|
||||
}
|
||||
|
||||
// node is an internal node within a trie map.
|
||||
// A node is either empty, a leaf or a branch.
|
||||
type node interface {
|
||||
size() int
|
||||
|
||||
// visit the leaves (key, value) pairs in the map in order and
|
||||
// applies cb(key, value) to each. Stops early if cb returns false.
|
||||
// Returns true if all elements were visited without stopping early.
|
||||
visit(cb func(uint64, any) bool) bool
|
||||
|
||||
// Two nodes contain the same elements regardless of scope.
|
||||
deepEqual(node) bool
|
||||
|
||||
// find the leaf for the given key value or nil if it is not present.
|
||||
find(k key) *leaf
|
||||
|
||||
// implementations must implement this.
|
||||
nodeImpl()
|
||||
}
|
||||
|
||||
// empty represents the empty map within a scope.
|
||||
//
|
||||
// The current builder ensure
|
||||
type empty struct {
|
||||
s Scope
|
||||
}
|
||||
|
||||
// leaf represents a single <key, value> pair.
|
||||
type leaf struct {
|
||||
k key
|
||||
v any
|
||||
}
|
||||
|
||||
// branch represents a tree node within the Patricia trie.
|
||||
//
|
||||
// All keys within the branch match a `prefix` of the key
|
||||
// up to a `branching` bit, and the left and right nodes
|
||||
// contain keys that disagree on the bit at the `branching` bit.
|
||||
type branch struct {
|
||||
sz int // size. cached for O(1) lookup
|
||||
prefix prefix // == mask(p0, branching) for some p0
|
||||
branching bitpos
|
||||
|
||||
// Invariants:
|
||||
// - neither is nil.
|
||||
// - neither is *empty.
|
||||
// - all keys in left are <= p.
|
||||
// - all keys in right are > p.
|
||||
left, right node
|
||||
}
|
||||
|
||||
// all of these types are Maps.
|
||||
var _ node = &empty{}
|
||||
var _ node = &leaf{}
|
||||
var _ node = &branch{}
|
||||
|
||||
func (*empty) nodeImpl() {}
|
||||
func (*leaf) nodeImpl() {}
|
||||
func (*branch) nodeImpl() {}
|
||||
|
||||
func (*empty) find(k key) *leaf { return nil }
|
||||
func (l *leaf) find(k key) *leaf {
|
||||
if k == l.k {
|
||||
return l
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (br *branch) find(k key) *leaf {
|
||||
kp := prefix(k)
|
||||
if !matchPrefix(kp, br.prefix, br.branching) {
|
||||
return nil
|
||||
}
|
||||
if zeroBit(kp, br.branching) {
|
||||
return br.left.find(k)
|
||||
}
|
||||
return br.right.find(k)
|
||||
}
|
||||
|
||||
func (*empty) size() int { return 0 }
|
||||
func (*leaf) size() int { return 1 }
|
||||
func (br *branch) size() int { return br.sz }
|
||||
|
||||
func (*empty) deepEqual(m node) bool {
|
||||
_, ok := m.(*empty)
|
||||
return ok
|
||||
}
|
||||
func (l *leaf) deepEqual(m node) bool {
|
||||
if m, ok := m.(*leaf); ok {
|
||||
return m == l || (l.k == m.k && l.v == m.v)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (br *branch) deepEqual(m node) bool {
|
||||
if m, ok := m.(*branch); ok {
|
||||
if br == m {
|
||||
return true
|
||||
}
|
||||
return br.sz == m.sz && br.branching == m.branching && br.prefix == m.prefix &&
|
||||
br.left.deepEqual(m.left) && br.right.deepEqual(m.right)
|
||||
}
|
||||
// if m is not a branch, m contains 0 or 1 elem.
|
||||
// br contains at least 2 keys that disagree on a prefix.
|
||||
return false
|
||||
}
|
||||
|
||||
func (*empty) visit(cb func(uint64, any) bool) bool {
|
||||
return true
|
||||
}
|
||||
func (l *leaf) visit(cb func(uint64, any) bool) bool {
|
||||
return cb(uint64(l.k), l.v)
|
||||
}
|
||||
func (br *branch) visit(cb func(uint64, any) bool) bool {
|
||||
if !br.left.visit(cb) {
|
||||
return false
|
||||
}
|
||||
return br.right.visit(cb)
|
||||
}
|
||||
201
vendor/golang.org/x/tools/go/callgraph/vta/propagation.go
generated
vendored
Normal file
201
vendor/golang.org/x/tools/go/callgraph/vta/propagation.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
// 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"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"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 two slices:
|
||||
// - sccs: the SCCs, each represented as a slice of node indices
|
||||
// - idxToSccID: the inverse map, from node index to SCC number.
|
||||
//
|
||||
// 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) (sccs [][]idx, idxToSccID []int) {
|
||||
// standard data structures used by Tarjan's algorithm.
|
||||
type state struct {
|
||||
pre int // preorder of the node (0 if unvisited)
|
||||
lowLink int
|
||||
onStack bool
|
||||
}
|
||||
states := make([]state, g.numNodes())
|
||||
var stack []idx
|
||||
|
||||
idxToSccID = make([]int, g.numNodes())
|
||||
nextPre := 0
|
||||
|
||||
var doSCC func(idx)
|
||||
doSCC = func(n idx) {
|
||||
nextPre++
|
||||
ns := &states[n]
|
||||
*ns = state{pre: nextPre, lowLink: nextPre, onStack: true}
|
||||
stack = append(stack, n)
|
||||
|
||||
for s := range g.successors(n) {
|
||||
if ss := &states[s]; ss.pre == 0 {
|
||||
// Analyze successor s that has not been visited yet.
|
||||
doSCC(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.pre)
|
||||
}
|
||||
}
|
||||
|
||||
// if n is a root node, pop the stack and generate a new SCC.
|
||||
if ns.lowLink == ns.pre {
|
||||
sccStart := slicesLastIndex(stack, n)
|
||||
scc := slices.Clone(stack[sccStart:])
|
||||
stack = stack[:sccStart]
|
||||
sccID := len(sccs)
|
||||
sccs = append(sccs, scc)
|
||||
for _, w := range scc {
|
||||
states[w].onStack = false
|
||||
idxToSccID[w] = sccID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for n, nn := 0, g.numNodes(); n < nn; n++ {
|
||||
if states[n].pre == 0 {
|
||||
doSCC(idx(n))
|
||||
}
|
||||
}
|
||||
|
||||
return sccs, idxToSccID
|
||||
}
|
||||
|
||||
// slicesLastIndex returns the index of the last occurrence of v in s, or -1 if v is
|
||||
// not present in s.
|
||||
//
|
||||
// slicesLastIndex iterates backwards through the elements of s, stopping when the ==
|
||||
// operator determines an element is equal to v.
|
||||
func slicesLastIndex[S ~[]E, E comparable](s S, v E) int {
|
||||
// TODO: move to / dedup with slices.LastIndex
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] == v {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// 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 map[node]*trie.MutMap
|
||||
|
||||
// propTypes returns an iterator for the propTypes associated with
|
||||
// node `n` in map `ptm`.
|
||||
func (ptm propTypeMap) propTypes(n node) iter.Seq[propType] {
|
||||
return func(yield func(propType) bool) {
|
||||
if types := ptm[n]; types != nil {
|
||||
types.M.Range(func(_ uint64, elem any) 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 {
|
||||
sccs, idxToSccID := scc(graph)
|
||||
|
||||
// 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([]*trie.MutMap, len(sccs))
|
||||
for sccID, scc := range sccs {
|
||||
typeSet := builder.MutEmpty()
|
||||
for _, idx := range scc {
|
||||
if n := graph.node[idx]; hasInitialTypes(n) {
|
||||
// add the propType for idx to typeSet.
|
||||
pt := getPropType(n, canon)
|
||||
typeSet.Update(propTypeId(pt), pt)
|
||||
}
|
||||
}
|
||||
sccToTypes[sccID] = &typeSet
|
||||
}
|
||||
|
||||
for i := len(sccs) - 1; i >= 0; i-- {
|
||||
nextSccs := make(map[int]empty)
|
||||
for _, n := range sccs[i] {
|
||||
for succ := range graph.successors(n) {
|
||||
nextSccs[idxToSccID[succ]] = empty{}
|
||||
}
|
||||
}
|
||||
// Propagate types to all successor SCCs.
|
||||
for nextScc := range nextSccs {
|
||||
sccToTypes[nextScc].Merge(sccToTypes[i].M)
|
||||
}
|
||||
}
|
||||
nodeToTypes := make(propTypeMap, graph.numNodes())
|
||||
for sccID, scc := range sccs {
|
||||
types := sccToTypes[sccID]
|
||||
for _, idx := range scc {
|
||||
nodeToTypes[graph.node[idx]] = types
|
||||
}
|
||||
}
|
||||
return nodeToTypes
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
||||
188
vendor/golang.org/x/tools/go/callgraph/vta/utils.go
generated
vendored
Normal file
188
vendor/golang.org/x/tools/go/callgraph/vta/utils.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
// 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"
|
||||
"iter"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
func canAlias(n1, n2 node) bool {
|
||||
return isReferenceNode(n1) && isReferenceNode(n2)
|
||||
}
|
||||
|
||||
func isReferenceNode(n node) bool {
|
||||
if _, ok := n.(nestedPtrInterface); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := n.(nestedPtrFunction); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := types.Unalias(n.Type()).(*types.Pointer); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// hasInFlow checks if a concrete type can flow to node `n`.
|
||||
// Returns yes iff the type of `n` satisfies one the following:
|
||||
// 1. is an interface
|
||||
// 2. is a (nested) pointer to interface (needed for, say,
|
||||
// slice elements of nested pointers to interface type)
|
||||
// 3. is a function type (needed for higher-order type flow)
|
||||
// 4. is a (nested) pointer to function (needed for, say,
|
||||
// slice elements of nested pointers to function type)
|
||||
// 5. is a global Recover or Panic node
|
||||
func hasInFlow(n node) bool {
|
||||
if _, ok := n.(panicArg); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := n.(recoverReturn); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
t := n.Type()
|
||||
|
||||
if i := interfaceUnderPtr(t); i != nil {
|
||||
return true
|
||||
}
|
||||
if f := functionUnderPtr(t); f != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return types.IsInterface(t) || isFunction(t)
|
||||
}
|
||||
|
||||
func isFunction(t types.Type) bool {
|
||||
_, ok := t.Underlying().(*types.Signature)
|
||||
return ok
|
||||
}
|
||||
|
||||
// interfaceUnderPtr checks if type `t` is a potentially nested
|
||||
// pointer to interface and if yes, returns the interface type.
|
||||
// Otherwise, returns nil.
|
||||
func interfaceUnderPtr(t types.Type) types.Type {
|
||||
seen := make(map[types.Type]bool)
|
||||
var visit func(types.Type) types.Type
|
||||
visit = func(t types.Type) types.Type {
|
||||
if seen[t] {
|
||||
return nil
|
||||
}
|
||||
seen[t] = true
|
||||
|
||||
p, ok := t.Underlying().(*types.Pointer)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if types.IsInterface(p.Elem()) {
|
||||
return p.Elem()
|
||||
}
|
||||
|
||||
return visit(p.Elem())
|
||||
}
|
||||
return visit(t)
|
||||
}
|
||||
|
||||
// functionUnderPtr checks if type `t` is a potentially nested
|
||||
// pointer to function type and if yes, returns the function type.
|
||||
// Otherwise, returns nil.
|
||||
func functionUnderPtr(t types.Type) types.Type {
|
||||
seen := make(map[types.Type]bool)
|
||||
var visit func(types.Type) types.Type
|
||||
visit = func(t types.Type) types.Type {
|
||||
if seen[t] {
|
||||
return nil
|
||||
}
|
||||
seen[t] = true
|
||||
|
||||
p, ok := t.Underlying().(*types.Pointer)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isFunction(p.Elem()) {
|
||||
return p.Elem()
|
||||
}
|
||||
|
||||
return visit(p.Elem())
|
||||
}
|
||||
return visit(t)
|
||||
}
|
||||
|
||||
// sliceArrayElem returns the element type of type `t` that is
|
||||
// expected to be a (pointer to) array, slice or string, consistent with
|
||||
// the ssa.Index and ssa.IndexAddr instructions. Panics otherwise.
|
||||
func sliceArrayElem(t types.Type) types.Type {
|
||||
switch u := t.Underlying().(type) {
|
||||
case *types.Pointer:
|
||||
switch e := u.Elem().Underlying().(type) {
|
||||
case *types.Array:
|
||||
return e.Elem()
|
||||
case *types.Interface:
|
||||
return sliceArrayElem(e) // e is a type param with matching element types.
|
||||
default:
|
||||
panic(t)
|
||||
}
|
||||
case *types.Array:
|
||||
return u.Elem()
|
||||
case *types.Slice:
|
||||
return u.Elem()
|
||||
case *types.Basic:
|
||||
return types.Typ[types.Byte]
|
||||
case *types.Interface: // type param.
|
||||
terms, err := typeparams.InterfaceTermSet(u)
|
||||
if err != nil || len(terms) == 0 {
|
||||
panic(t)
|
||||
}
|
||||
return sliceArrayElem(terms[0].Type()) // Element types must match.
|
||||
default:
|
||||
panic(t)
|
||||
}
|
||||
}
|
||||
|
||||
// siteCallees returns an iterator for the callees for call site `c`.
|
||||
func siteCallees(c ssa.CallInstruction, callees calleesFunc) iter.Seq[*ssa.Function] {
|
||||
return func(yield func(*ssa.Function) bool) {
|
||||
for _, callee := range callees(c) {
|
||||
if !yield(callee) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func canHaveMethods(t types.Type) bool {
|
||||
t = types.Unalias(t)
|
||||
if _, ok := t.(*types.Named); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
u := t.Underlying()
|
||||
switch u.(type) {
|
||||
case *types.Interface, *types.Signature, *types.Struct:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// calls returns the set of call instructions in `f`.
|
||||
func calls(f *ssa.Function) []ssa.CallInstruction {
|
||||
var calls []ssa.CallInstruction
|
||||
for _, bl := range f.Blocks {
|
||||
for _, instr := range bl.Instrs {
|
||||
if c, ok := instr.(ssa.CallInstruction); ok {
|
||||
calls = append(calls, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return calls
|
||||
}
|
||||
190
vendor/golang.org/x/tools/go/callgraph/vta/vta.go
generated
vendored
Normal file
190
vendor/golang.org/x/tools/go/callgraph/vta/vta.go
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
// 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 computes the call graph of a Go program using the Variable
|
||||
// Type Analysis (VTA) algorithm originally described in "Practical Virtual
|
||||
// Method Call Resolution for Java," Vijay Sundaresan, Laurie Hendren,
|
||||
// Chrislain Razafimahefa, Raja Vallée-Rai, Patrick Lam, Etienne Gagnon, and
|
||||
// Charles Godin.
|
||||
//
|
||||
// Note: this package is in experimental phase and its interface is
|
||||
// subject to change.
|
||||
// TODO(zpavlinovic): reiterate on documentation.
|
||||
//
|
||||
// The VTA algorithm overapproximates the set of types (and function literals)
|
||||
// a variable can take during runtime by building a global type propagation
|
||||
// graph and propagating types (and function literals) through the graph.
|
||||
//
|
||||
// A type propagation is a directed, labeled graph. A node can represent
|
||||
// one of the following:
|
||||
// - A field of a struct type.
|
||||
// - A local (SSA) variable of a method/function.
|
||||
// - All pointers to a non-interface type.
|
||||
// - The return value of a method.
|
||||
// - All elements in an array.
|
||||
// - All elements in a slice.
|
||||
// - All elements in a map.
|
||||
// - All elements in a channel.
|
||||
// - A global variable.
|
||||
//
|
||||
// In addition, the implementation used in this package introduces
|
||||
// a few Go specific kinds of nodes:
|
||||
// - (De)references of nested pointers to interfaces are modeled
|
||||
// as a unique nestedPtrInterface node in the type propagation graph.
|
||||
// - Each function literal is represented as a function node whose
|
||||
// internal value is the (SSA) representation of the function. This
|
||||
// is done to precisely infer flow of higher-order functions.
|
||||
//
|
||||
// Edges in the graph represent flow of types (and function literals) through
|
||||
// the program. That is, the model 1) typing constraints that are induced by
|
||||
// assignment statements or function and method calls and 2) higher-order flow
|
||||
// of functions in the program.
|
||||
//
|
||||
// The labeling function maps each node to a set of types and functions that
|
||||
// can intuitively reach the program construct the node represents. Initially,
|
||||
// every node is assigned a type corresponding to the program construct it
|
||||
// represents. Function nodes are also assigned the function they represent.
|
||||
// The labeling function then propagates types and function through the graph.
|
||||
//
|
||||
// The result of VTA is a type propagation graph in which each node is labeled
|
||||
// with a conservative overapproximation of the set of types (and functions)
|
||||
// it may have. This information is then used to construct the call graph.
|
||||
// For each unresolved call site, vta uses the set of types and functions
|
||||
// reaching the node representing the call site to create a set of callees.
|
||||
package vta
|
||||
|
||||
// TODO(zpavlinovic): update VTA for how it handles generic function bodies and instantiation wrappers.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// CallGraph uses the VTA algorithm to compute call graph for all functions
|
||||
// f:true in funcs. VTA refines the results of initial call graph and uses it
|
||||
// to establish interprocedural type flow. If initial is nil, VTA uses a more
|
||||
// efficient approach to construct a CHA call graph.
|
||||
//
|
||||
// The resulting graph does not have a root node.
|
||||
//
|
||||
// CallGraph does not make any assumptions on initial types global variables
|
||||
// and function/method inputs can have. CallGraph is then sound, modulo use of
|
||||
// reflection and unsafe, if the initial call graph is sound.
|
||||
func CallGraph(funcs map[*ssa.Function]bool, initial *callgraph.Graph) *callgraph.Graph {
|
||||
callees := makeCalleesFunc(funcs, initial)
|
||||
vtaG, canon := typePropGraph(funcs, callees)
|
||||
types := propagate(vtaG, canon)
|
||||
|
||||
c := &constructor{types: types, callees: callees, cache: make(methodCache)}
|
||||
return c.construct(funcs)
|
||||
}
|
||||
|
||||
// constructor type linearly traverses the input program
|
||||
// and constructs a callgraph based on the results of the
|
||||
// VTA type propagation phase.
|
||||
type constructor struct {
|
||||
types propTypeMap
|
||||
cache methodCache
|
||||
callees calleesFunc
|
||||
}
|
||||
|
||||
func (c *constructor) construct(funcs map[*ssa.Function]bool) *callgraph.Graph {
|
||||
cg := &callgraph.Graph{Nodes: make(map[*ssa.Function]*callgraph.Node)}
|
||||
for f, in := range funcs {
|
||||
if in {
|
||||
c.constrct(cg, f)
|
||||
}
|
||||
}
|
||||
return cg
|
||||
}
|
||||
|
||||
func (c *constructor) constrct(g *callgraph.Graph, f *ssa.Function) {
|
||||
caller := g.CreateNode(f)
|
||||
for _, call := range calls(f) {
|
||||
for _, c := range c.resolves(call) {
|
||||
callgraph.AddEdge(caller, call, g.CreateNode(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resolves computes the set of functions to which VTA resolves `c`. The resolved
|
||||
// functions are intersected with functions to which `c.initial` resolves `c`.
|
||||
func (c *constructor) resolves(call ssa.CallInstruction) []*ssa.Function {
|
||||
cc := call.Common()
|
||||
if cc.StaticCallee() != nil {
|
||||
return []*ssa.Function{cc.StaticCallee()}
|
||||
}
|
||||
|
||||
// Skip builtins as they are not *ssa.Function.
|
||||
if _, ok := cc.Value.(*ssa.Builtin); ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cover the case of dynamic higher-order and interface calls.
|
||||
var res []*ssa.Function
|
||||
resolved := resolve(call, c.types, c.cache)
|
||||
for f := range siteCallees(call, c.callees) {
|
||||
if _, ok := resolved[f]; ok {
|
||||
res = append(res, f)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// resolve returns a set of functions `c` resolves to based on the
|
||||
// type propagation results in `types`.
|
||||
func resolve(c ssa.CallInstruction, types propTypeMap, cache methodCache) map[*ssa.Function]empty {
|
||||
fns := make(map[*ssa.Function]empty)
|
||||
n := local{val: c.Common().Value}
|
||||
for p := range types.propTypes(n) {
|
||||
for _, f := range propFunc(p, c, cache) {
|
||||
fns[f] = empty{}
|
||||
}
|
||||
}
|
||||
return fns
|
||||
}
|
||||
|
||||
// propFunc returns the functions modeled with the propagation type `p`
|
||||
// assigned to call site `c`. If no such function exists, nil is returned.
|
||||
func propFunc(p propType, c ssa.CallInstruction, cache methodCache) []*ssa.Function {
|
||||
if p.f != nil {
|
||||
return []*ssa.Function{p.f}
|
||||
}
|
||||
|
||||
if c.Common().Method == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cache.methods(p.typ, c.Common().Method.Name(), c.Parent().Prog)
|
||||
}
|
||||
|
||||
// methodCache serves as a type -> method name -> methods
|
||||
// cache when computing methods of a type using the
|
||||
// ssa.Program.MethodSets and ssa.Program.MethodValue
|
||||
// APIs. The cache is used to speed up querying of
|
||||
// methods of a type as the mentioned APIs are expensive.
|
||||
type methodCache map[types.Type]map[string][]*ssa.Function
|
||||
|
||||
// methods returns methods of a type `t` named `name`. First consults
|
||||
// `mc` and otherwise queries `prog` for the method. If no such method
|
||||
// exists, nil is returned.
|
||||
func (mc methodCache) methods(t types.Type, name string, prog *ssa.Program) []*ssa.Function {
|
||||
if ms, ok := mc[t]; ok {
|
||||
return ms[name]
|
||||
}
|
||||
|
||||
ms := make(map[string][]*ssa.Function)
|
||||
mset := prog.MethodSets.MethodSet(t)
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
// f can be nil when t is an interface or some
|
||||
// other type without any runtime methods.
|
||||
if f := prog.MethodValue(mset.At(i)); f != nil {
|
||||
ms[f.Name()] = append(ms[f.Name()], f)
|
||||
}
|
||||
}
|
||||
mc[t] = ms
|
||||
return ms[name]
|
||||
}
|
||||
Reference in New Issue
Block a user