Initialize module and dependencies
This commit is contained in:
124
vendor/golang.org/x/tools/go/callgraph/callgraph.go
generated
vendored
Normal file
124
vendor/golang.org/x/tools/go/callgraph/callgraph.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2013 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 callgraph defines the call graph and various algorithms
|
||||
and utilities to operate on it.
|
||||
|
||||
A call graph is a labelled directed graph whose nodes represent
|
||||
functions and whose edge labels represent syntactic function call
|
||||
sites. The presence of a labelled edge (caller, site, callee)
|
||||
indicates that caller may call callee at the specified call site.
|
||||
|
||||
A call graph is a multigraph: it may contain multiple edges (caller,
|
||||
*, callee) connecting the same pair of nodes, so long as the edges
|
||||
differ by label; this occurs when one function calls another function
|
||||
from multiple call sites. Also, it may contain multiple edges
|
||||
(caller, site, *) that differ only by callee; this indicates a
|
||||
polymorphic call.
|
||||
|
||||
A SOUND call graph is one that overapproximates the dynamic calling
|
||||
behaviors of the program in all possible executions. One call graph
|
||||
is more PRECISE than another if it is a smaller overapproximation of
|
||||
the dynamic behavior.
|
||||
|
||||
All call graphs have a synthetic root node which is responsible for
|
||||
calling main() and init().
|
||||
|
||||
Calls to built-in functions (e.g. panic, println) are not represented
|
||||
in the call graph; they are treated like built-in operators of the
|
||||
language.
|
||||
*/
|
||||
package callgraph // import "golang.org/x/tools/go/callgraph"
|
||||
|
||||
// TODO(zpavlinovic): decide how callgraphs handle calls to and from generic function bodies.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// A Graph represents a call graph.
|
||||
//
|
||||
// A graph may contain nodes that are not reachable from the root.
|
||||
// If the call graph is sound, such nodes indicate unreachable
|
||||
// functions.
|
||||
type Graph struct {
|
||||
Root *Node // the distinguished root node (Root.Func may be nil)
|
||||
Nodes map[*ssa.Function]*Node // all nodes by function
|
||||
}
|
||||
|
||||
// New returns a new Graph with the specified (optional) root node.
|
||||
func New(root *ssa.Function) *Graph {
|
||||
g := &Graph{Nodes: make(map[*ssa.Function]*Node)}
|
||||
g.Root = g.CreateNode(root)
|
||||
return g
|
||||
}
|
||||
|
||||
// CreateNode returns the Node for fn, creating it if not present.
|
||||
// The root node may have fn=nil.
|
||||
func (g *Graph) CreateNode(fn *ssa.Function) *Node {
|
||||
n, ok := g.Nodes[fn]
|
||||
if !ok {
|
||||
n = &Node{Func: fn, ID: len(g.Nodes)}
|
||||
g.Nodes[fn] = n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// A Node represents a node in a call graph.
|
||||
type Node struct {
|
||||
Func *ssa.Function // the function this node represents
|
||||
ID int // 0-based sequence number
|
||||
In []*Edge // unordered set of incoming call edges (n.In[*].Callee == n)
|
||||
Out []*Edge // unordered set of outgoing call edges (n.Out[*].Caller == n)
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
return fmt.Sprintf("n%d:%s", n.ID, n.Func)
|
||||
}
|
||||
|
||||
// A Edge represents an edge in the call graph.
|
||||
//
|
||||
// Site is nil for edges originating in synthetic or intrinsic
|
||||
// functions, e.g. reflect.Value.Call or the root of the call graph.
|
||||
type Edge struct {
|
||||
Caller *Node
|
||||
Site ssa.CallInstruction
|
||||
Callee *Node
|
||||
}
|
||||
|
||||
func (e Edge) String() string {
|
||||
return fmt.Sprintf("%s --> %s", e.Caller, e.Callee)
|
||||
}
|
||||
|
||||
func (e Edge) Description() string {
|
||||
var prefix string
|
||||
switch e.Site.(type) {
|
||||
case nil:
|
||||
return "synthetic call"
|
||||
case *ssa.Go:
|
||||
prefix = "concurrent "
|
||||
case *ssa.Defer:
|
||||
prefix = "deferred "
|
||||
}
|
||||
return prefix + e.Site.Common().Description()
|
||||
}
|
||||
|
||||
func (e Edge) Pos() token.Pos {
|
||||
if e.Site == nil {
|
||||
return token.NoPos
|
||||
}
|
||||
return e.Site.Pos()
|
||||
}
|
||||
|
||||
// AddEdge adds the edge (caller, site, callee) to the call graph.
|
||||
// Elimination of duplicate edges is the caller's responsibility.
|
||||
func AddEdge(caller *Node, site ssa.CallInstruction, callee *Node) {
|
||||
e := &Edge{caller, site, callee}
|
||||
callee.In = append(callee.In, e)
|
||||
caller.Out = append(caller.Out, e)
|
||||
}
|
||||
77
vendor/golang.org/x/tools/go/callgraph/cha/cha.go
generated
vendored
Normal file
77
vendor/golang.org/x/tools/go/callgraph/cha/cha.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2014 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 cha computes the call graph of a Go program using the Class
|
||||
// Hierarchy Analysis (CHA) algorithm.
|
||||
//
|
||||
// CHA was first described in "Optimization of Object-Oriented Programs
|
||||
// Using Static Class Hierarchy Analysis", Jeffrey Dean, David Grove,
|
||||
// and Craig Chambers, ECOOP'95.
|
||||
//
|
||||
// CHA is related to RTA (see go/callgraph/rta); the difference is that
|
||||
// CHA conservatively computes the entire "implements" relation between
|
||||
// interfaces and concrete types ahead of time, whereas RTA uses dynamic
|
||||
// programming to construct it on the fly as it encounters new functions
|
||||
// reachable from main. CHA may thus include spurious call edges for
|
||||
// types that haven't been instantiated yet, or types that are never
|
||||
// instantiated.
|
||||
//
|
||||
// Since CHA conservatively assumes that all functions are address-taken
|
||||
// and all concrete types are put into interfaces, it is sound to run on
|
||||
// partial programs, such as libraries without a main or test function.
|
||||
package cha // import "golang.org/x/tools/go/callgraph/cha"
|
||||
|
||||
// TODO(zpavlinovic): update CHA for how it handles generic function bodies.
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/callgraph/internal/chautil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
)
|
||||
|
||||
// CallGraph computes the call graph of the specified program using the
|
||||
// Class Hierarchy Analysis algorithm.
|
||||
func CallGraph(prog *ssa.Program) *callgraph.Graph {
|
||||
cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph
|
||||
|
||||
allFuncs := ssautil.AllFunctions(prog)
|
||||
|
||||
calleesOf := lazyCallees(allFuncs)
|
||||
|
||||
addEdge := func(fnode *callgraph.Node, site ssa.CallInstruction, g *ssa.Function) {
|
||||
gnode := cg.CreateNode(g)
|
||||
callgraph.AddEdge(fnode, site, gnode)
|
||||
}
|
||||
|
||||
addEdges := func(fnode *callgraph.Node, site ssa.CallInstruction, callees []*ssa.Function) {
|
||||
// Because every call to a highly polymorphic and
|
||||
// frequently used abstract method such as
|
||||
// (io.Writer).Write is assumed to call every concrete
|
||||
// Write method in the program, the call graph can
|
||||
// contain a lot of duplication.
|
||||
for _, g := range callees {
|
||||
addEdge(fnode, site, g)
|
||||
}
|
||||
}
|
||||
|
||||
for f := range allFuncs {
|
||||
fnode := cg.CreateNode(f)
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if site, ok := instr.(ssa.CallInstruction); ok {
|
||||
if g := site.Common().StaticCallee(); g != nil {
|
||||
addEdge(fnode, site, g)
|
||||
} else {
|
||||
addEdges(fnode, site, calleesOf(site))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cg
|
||||
}
|
||||
|
||||
var lazyCallees = chautil.LazyCallees
|
||||
96
vendor/golang.org/x/tools/go/callgraph/internal/chautil/lazy.go
generated
vendored
Normal file
96
vendor/golang.org/x/tools/go/callgraph/internal/chautil/lazy.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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 chautil provides helper functions related to
|
||||
// class hierarchy analysis (CHA) for use in x/tools.
|
||||
package chautil
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// LazyCallees returns a function that maps a call site (in a function in fns)
|
||||
// to its callees within fns. The set of callees is computed using the CHA algorithm,
|
||||
// i.e., on the entire implements relation between interfaces and concrete types
|
||||
// in fns. Please see golang.org/x/tools/go/callgraph/cha for more information.
|
||||
//
|
||||
// The resulting function is not concurrency safe.
|
||||
func LazyCallees(fns map[*ssa.Function]bool) func(site ssa.CallInstruction) []*ssa.Function {
|
||||
// funcsBySig contains all functions, keyed by signature. It is
|
||||
// the effective set of address-taken functions used to resolve
|
||||
// a dynamic call of a particular signature.
|
||||
var funcsBySig typeutil.Map // value is []*ssa.Function
|
||||
|
||||
// methodsByID contains all methods, grouped by ID for efficient
|
||||
// lookup.
|
||||
//
|
||||
// We must key by ID, not name, for correct resolution of interface
|
||||
// calls to a type with two (unexported) methods spelled the same but
|
||||
// from different packages. The fact that the concrete type implements
|
||||
// the interface does not mean the call dispatches to both methods.
|
||||
methodsByID := make(map[string][]*ssa.Function)
|
||||
|
||||
// An imethod represents an interface method I.m.
|
||||
// (There's no go/types object for it;
|
||||
// a *types.Func may be shared by many interfaces due to interface embedding.)
|
||||
type imethod struct {
|
||||
I *types.Interface
|
||||
id string
|
||||
}
|
||||
// methodsMemo records, for every abstract method call I.m on
|
||||
// interface type I, the set of concrete methods C.m of all
|
||||
// types C that satisfy interface I.
|
||||
//
|
||||
// Abstract methods may be shared by several interfaces,
|
||||
// hence we must pass I explicitly, not guess from m.
|
||||
//
|
||||
// methodsMemo is just a cache, so it needn't be a typeutil.Map.
|
||||
methodsMemo := make(map[imethod][]*ssa.Function)
|
||||
lookupMethods := func(I *types.Interface, m *types.Func) []*ssa.Function {
|
||||
id := m.Id()
|
||||
methods, ok := methodsMemo[imethod{I, id}]
|
||||
if !ok {
|
||||
for _, f := range methodsByID[id] {
|
||||
C := f.Signature.Recv().Type() // named or *named
|
||||
if types.Implements(C, I) {
|
||||
methods = append(methods, f)
|
||||
}
|
||||
}
|
||||
methodsMemo[imethod{I, id}] = methods
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
for f := range fns {
|
||||
if f.Signature.Recv() == nil {
|
||||
// Package initializers can never be address-taken.
|
||||
if f.Name() == "init" && f.Synthetic == "package initializer" {
|
||||
continue
|
||||
}
|
||||
funcs, _ := funcsBySig.At(f.Signature).([]*ssa.Function)
|
||||
funcs = append(funcs, f)
|
||||
funcsBySig.Set(f.Signature, funcs)
|
||||
} else if obj := f.Object(); obj != nil {
|
||||
id := obj.(*types.Func).Id()
|
||||
methodsByID[id] = append(methodsByID[id], f)
|
||||
}
|
||||
}
|
||||
|
||||
return func(site ssa.CallInstruction) []*ssa.Function {
|
||||
call := site.Common()
|
||||
if call.IsInvoke() {
|
||||
tiface := call.Value.Type().Underlying().(*types.Interface)
|
||||
return lookupMethods(tiface, call.Method)
|
||||
} else if g := call.StaticCallee(); g != nil {
|
||||
return []*ssa.Function{g}
|
||||
} else if _, ok := call.Value.(*ssa.Builtin); !ok {
|
||||
fns, _ := funcsBySig.At(call.Signature()).([]*ssa.Function)
|
||||
return fns
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
563
vendor/golang.org/x/tools/go/callgraph/rta/rta.go
generated
vendored
Normal file
563
vendor/golang.org/x/tools/go/callgraph/rta/rta.go
generated
vendored
Normal file
@@ -0,0 +1,563 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// This package provides Rapid Type Analysis (RTA) for Go, a fast
|
||||
// algorithm for call graph construction and discovery of reachable code
|
||||
// (and hence dead code) and runtime types. The algorithm was first
|
||||
// described in:
|
||||
//
|
||||
// David F. Bacon and Peter F. Sweeney. 1996.
|
||||
// Fast static analysis of C++ virtual function calls. (OOPSLA '96)
|
||||
// http://doi.acm.org/10.1145/236337.236371
|
||||
//
|
||||
// The algorithm uses dynamic programming to tabulate the cross-product
|
||||
// of the set of known "address-taken" functions with the set of known
|
||||
// dynamic calls of the same type. As each new address-taken function
|
||||
// is discovered, call graph edges are added from each known callsite,
|
||||
// and as each new call site is discovered, call graph edges are added
|
||||
// from it to each known address-taken function.
|
||||
//
|
||||
// A similar approach is used for dynamic calls via interfaces: it
|
||||
// tabulates the cross-product of the set of known "runtime types",
|
||||
// i.e. types that may appear in an interface value, or may be derived from
|
||||
// one via reflection, with the set of known "invoke"-mode dynamic
|
||||
// calls. As each new runtime type is discovered, call edges are
|
||||
// added from the known call sites, and as each new call site is
|
||||
// discovered, call graph edges are added to each compatible
|
||||
// method.
|
||||
//
|
||||
// In addition, we must consider as reachable all address-taken
|
||||
// functions and all exported methods of any runtime type, since they
|
||||
// may be called via reflection.
|
||||
//
|
||||
// Each time a newly added call edge causes a new function to become
|
||||
// reachable, the code of that function is analyzed for more call sites,
|
||||
// address-taken functions, and runtime types. The process continues
|
||||
// until a fixed point is reached.
|
||||
package rta // import "golang.org/x/tools/go/callgraph/rta"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"hash/crc32"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// A Result holds the results of Rapid Type Analysis, which includes the
|
||||
// set of reachable functions/methods, runtime types, and the call graph.
|
||||
type Result struct {
|
||||
// CallGraph is the discovered callgraph.
|
||||
// It does not include edges for calls made via reflection.
|
||||
CallGraph *callgraph.Graph
|
||||
|
||||
// Reachable contains the set of reachable functions and methods.
|
||||
// This includes exported methods of runtime types, since
|
||||
// they may be accessed via reflection.
|
||||
// The value indicates whether the function is address-taken.
|
||||
//
|
||||
// (We wrap the bool in a struct to avoid inadvertent use of
|
||||
// "if Reachable[f] {" to test for set membership.)
|
||||
Reachable map[*ssa.Function]struct{ AddrTaken bool }
|
||||
|
||||
// RuntimeTypes contains the set of types that are needed at
|
||||
// runtime, for interfaces or reflection.
|
||||
//
|
||||
// The value indicates whether the type is inaccessible to reflection.
|
||||
// Consider:
|
||||
// type A struct{B}
|
||||
// fmt.Println(new(A))
|
||||
// Types *A, A and B are accessible to reflection, but the unnamed
|
||||
// type struct{B} is not.
|
||||
RuntimeTypes typeutil.Map
|
||||
}
|
||||
|
||||
// Working state of the RTA algorithm.
|
||||
type rta struct {
|
||||
result *Result
|
||||
|
||||
prog *ssa.Program
|
||||
|
||||
reflectValueCall *ssa.Function // (*reflect.Value).Call, iff part of prog
|
||||
|
||||
worklist []*ssa.Function // list of functions to visit
|
||||
|
||||
// addrTakenFuncsBySig contains all address-taken *Functions, grouped by signature.
|
||||
// Keys are *types.Signature, values are map[*ssa.Function]bool sets.
|
||||
addrTakenFuncsBySig typeutil.Map
|
||||
|
||||
// dynCallSites contains all dynamic "call"-mode call sites, grouped by signature.
|
||||
// Keys are *types.Signature, values are unordered []ssa.CallInstruction.
|
||||
dynCallSites typeutil.Map
|
||||
|
||||
// invokeSites contains all "invoke"-mode call sites, grouped by interface.
|
||||
// Keys are *types.Interface (never *types.Named),
|
||||
// Values are unordered []ssa.CallInstruction sets.
|
||||
invokeSites typeutil.Map
|
||||
|
||||
// The following two maps together define the subset of the
|
||||
// m:n "implements" relation needed by the algorithm.
|
||||
|
||||
// concreteTypes maps each concrete type to information about it.
|
||||
// Keys are types.Type, values are *concreteTypeInfo.
|
||||
// Only concrete types used as MakeInterface operands are included.
|
||||
concreteTypes typeutil.Map
|
||||
|
||||
// interfaceTypes maps each interface type to information about it.
|
||||
// Keys are *types.Interface, values are *interfaceTypeInfo.
|
||||
// Only interfaces used in "invoke"-mode CallInstructions are included.
|
||||
interfaceTypes typeutil.Map
|
||||
}
|
||||
|
||||
type concreteTypeInfo struct {
|
||||
C types.Type
|
||||
mset *types.MethodSet
|
||||
fprint uint64 // fingerprint of method set
|
||||
implements []*types.Interface // unordered set of implemented interfaces
|
||||
}
|
||||
|
||||
type interfaceTypeInfo struct {
|
||||
I *types.Interface
|
||||
mset *types.MethodSet
|
||||
fprint uint64
|
||||
implementations []types.Type // unordered set of concrete implementations
|
||||
}
|
||||
|
||||
// addReachable marks a function as potentially callable at run-time,
|
||||
// and ensures that it gets processed.
|
||||
func (r *rta) addReachable(f *ssa.Function, addrTaken bool) {
|
||||
reachable := r.result.Reachable
|
||||
n := len(reachable)
|
||||
v := reachable[f]
|
||||
if addrTaken {
|
||||
v.AddrTaken = true
|
||||
}
|
||||
reachable[f] = v
|
||||
if len(reachable) > n {
|
||||
// First time seeing f. Add it to the worklist.
|
||||
r.worklist = append(r.worklist, f)
|
||||
}
|
||||
}
|
||||
|
||||
// addEdge adds the specified call graph edge, and marks it reachable.
|
||||
// addrTaken indicates whether to mark the callee as "address-taken".
|
||||
// site is nil for calls made via reflection.
|
||||
func (r *rta) addEdge(caller *ssa.Function, site ssa.CallInstruction, callee *ssa.Function, addrTaken bool) {
|
||||
r.addReachable(callee, addrTaken)
|
||||
|
||||
if g := r.result.CallGraph; g != nil {
|
||||
if caller == nil {
|
||||
panic(site)
|
||||
}
|
||||
from := g.CreateNode(caller)
|
||||
to := g.CreateNode(callee)
|
||||
callgraph.AddEdge(from, site, to)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- addrTakenFuncs × dynCallSites ----------
|
||||
|
||||
// visitAddrTakenFunc is called each time we encounter an address-taken function f.
|
||||
func (r *rta) visitAddrTakenFunc(f *ssa.Function) {
|
||||
// Create two-level map (Signature -> Function -> bool).
|
||||
S := f.Signature
|
||||
funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ssa.Function]bool)
|
||||
if funcs == nil {
|
||||
funcs = make(map[*ssa.Function]bool)
|
||||
r.addrTakenFuncsBySig.Set(S, funcs)
|
||||
}
|
||||
if !funcs[f] {
|
||||
// First time seeing f.
|
||||
funcs[f] = true
|
||||
|
||||
// If we've seen any dyncalls of this type, mark it reachable,
|
||||
// and add call graph edges.
|
||||
sites, _ := r.dynCallSites.At(S).([]ssa.CallInstruction)
|
||||
for _, site := range sites {
|
||||
r.addEdge(site.Parent(), site, f, true)
|
||||
}
|
||||
|
||||
// If the program includes (*reflect.Value).Call,
|
||||
// add a dynamic call edge from it to any address-taken
|
||||
// function, regardless of signature.
|
||||
//
|
||||
// This isn't perfect.
|
||||
// - The actual call comes from an internal function
|
||||
// called reflect.call, but we can't rely on that here.
|
||||
// - reflect.Value.CallSlice behaves similarly,
|
||||
// but we don't bother to create callgraph edges from
|
||||
// it as well as it wouldn't fundamentally change the
|
||||
// reachability but it would add a bunch more edges.
|
||||
// - We assume that if reflect.Value.Call is among
|
||||
// the dependencies of the application, it is itself
|
||||
// reachable. (It would be more accurate to defer
|
||||
// all the addEdges below until r.V.Call itself
|
||||
// becomes reachable.)
|
||||
// - Fake call graph edges are added from r.V.Call to
|
||||
// each address-taken function, but not to every
|
||||
// method reachable through a materialized rtype,
|
||||
// which is a little inconsistent. Still, the
|
||||
// reachable set includes both kinds, which is what
|
||||
// matters for e.g. deadcode detection.)
|
||||
if r.reflectValueCall != nil {
|
||||
var site ssa.CallInstruction = nil // can't find actual call site
|
||||
r.addEdge(r.reflectValueCall, site, f, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// visitDynCall is called each time we encounter a dynamic "call"-mode call.
|
||||
func (r *rta) visitDynCall(site ssa.CallInstruction) {
|
||||
S := site.Common().Signature()
|
||||
|
||||
// Record the call site.
|
||||
sites, _ := r.dynCallSites.At(S).([]ssa.CallInstruction)
|
||||
r.dynCallSites.Set(S, append(sites, site))
|
||||
|
||||
// For each function of signature S that we know is address-taken,
|
||||
// add an edge and mark it reachable.
|
||||
funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ssa.Function]bool)
|
||||
for g := range funcs {
|
||||
r.addEdge(site.Parent(), site, g, true)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- concrete types × invoke sites ----------
|
||||
|
||||
// addInvokeEdge is called for each new pair (site, C) in the matrix.
|
||||
func (r *rta) addInvokeEdge(site ssa.CallInstruction, C types.Type) {
|
||||
// Ascertain the concrete method of C to be called.
|
||||
imethod := site.Common().Method
|
||||
cmethod := r.prog.LookupMethod(C, imethod.Pkg(), imethod.Name())
|
||||
r.addEdge(site.Parent(), site, cmethod, true)
|
||||
}
|
||||
|
||||
// visitInvoke is called each time the algorithm encounters an "invoke"-mode call.
|
||||
func (r *rta) visitInvoke(site ssa.CallInstruction) {
|
||||
I := site.Common().Value.Type().Underlying().(*types.Interface)
|
||||
|
||||
// Record the invoke site.
|
||||
sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction)
|
||||
r.invokeSites.Set(I, append(sites, site))
|
||||
|
||||
// Add callgraph edge for each existing
|
||||
// address-taken concrete type implementing I.
|
||||
for _, C := range r.implementations(I) {
|
||||
r.addInvokeEdge(site, C)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- main algorithm ----------
|
||||
|
||||
// visitFunc processes function f.
|
||||
func (r *rta) visitFunc(f *ssa.Function) {
|
||||
var space [32]*ssa.Value // preallocate space for common case
|
||||
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
rands := instr.Operands(space[:0])
|
||||
|
||||
switch instr := instr.(type) {
|
||||
case ssa.CallInstruction:
|
||||
call := instr.Common()
|
||||
if call.IsInvoke() {
|
||||
r.visitInvoke(instr)
|
||||
} else if g := call.StaticCallee(); g != nil {
|
||||
r.addEdge(f, instr, g, false)
|
||||
} else if _, ok := call.Value.(*ssa.Builtin); !ok {
|
||||
r.visitDynCall(instr)
|
||||
}
|
||||
|
||||
// Ignore the call-position operand when
|
||||
// looking for address-taken Functions.
|
||||
// Hack: assume this is rands[0].
|
||||
rands = rands[1:]
|
||||
|
||||
case *ssa.MakeInterface:
|
||||
// Converting a value of type T to an
|
||||
// interface materializes its runtime
|
||||
// type, allowing any of its exported
|
||||
// methods to be called though reflection.
|
||||
r.addRuntimeType(instr.X.Type(), false)
|
||||
}
|
||||
|
||||
// Process all address-taken functions.
|
||||
for _, op := range rands {
|
||||
if g, ok := (*op).(*ssa.Function); ok {
|
||||
r.visitAddrTakenFunc(g)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze performs Rapid Type Analysis, starting at the specified root
|
||||
// functions. It returns nil if no roots were specified.
|
||||
//
|
||||
// The root functions must be one or more entrypoints (main and init
|
||||
// functions) of a complete SSA program, with function bodies for all
|
||||
// dependencies, constructed with the [ssa.InstantiateGenerics] mode
|
||||
// flag.
|
||||
//
|
||||
// If buildCallGraph is true, Result.CallGraph will contain a call
|
||||
// graph; otherwise, only the other fields (reachable functions) are
|
||||
// populated.
|
||||
func Analyze(roots []*ssa.Function, buildCallGraph bool) *Result {
|
||||
if len(roots) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := &rta{
|
||||
result: &Result{Reachable: make(map[*ssa.Function]struct{ AddrTaken bool })},
|
||||
prog: roots[0].Prog,
|
||||
}
|
||||
|
||||
if buildCallGraph {
|
||||
// TODO(adonovan): change callgraph API to eliminate the
|
||||
// notion of a distinguished root node. Some callgraphs
|
||||
// have many roots, or none.
|
||||
r.result.CallGraph = callgraph.New(roots[0])
|
||||
}
|
||||
|
||||
// Grab ssa.Function for (*reflect.Value).Call,
|
||||
// if "reflect" is among the dependencies.
|
||||
if reflectPkg := r.prog.ImportedPackage("reflect"); reflectPkg != nil {
|
||||
reflectValue := reflectPkg.Members["Value"].(*ssa.Type)
|
||||
r.reflectValueCall = r.prog.LookupMethod(reflectValue.Object().Type(), reflectPkg.Pkg, "Call")
|
||||
}
|
||||
|
||||
hasher := typeutil.MakeHasher()
|
||||
r.result.RuntimeTypes.SetHasher(hasher)
|
||||
r.addrTakenFuncsBySig.SetHasher(hasher)
|
||||
r.dynCallSites.SetHasher(hasher)
|
||||
r.invokeSites.SetHasher(hasher)
|
||||
r.concreteTypes.SetHasher(hasher)
|
||||
r.interfaceTypes.SetHasher(hasher)
|
||||
|
||||
for _, root := range roots {
|
||||
r.addReachable(root, false)
|
||||
}
|
||||
|
||||
// Visit functions, processing their instructions, and adding
|
||||
// new functions to the worklist, until a fixed point is
|
||||
// reached.
|
||||
var shadow []*ssa.Function // for efficiency, we double-buffer the worklist
|
||||
for len(r.worklist) > 0 {
|
||||
shadow, r.worklist = r.worklist, shadow[:0]
|
||||
for _, f := range shadow {
|
||||
r.visitFunc(f)
|
||||
}
|
||||
}
|
||||
return r.result
|
||||
}
|
||||
|
||||
// interfaces(C) returns all currently known interfaces implemented by C.
|
||||
func (r *rta) interfaces(C types.Type) []*types.Interface {
|
||||
// Create an info for C the first time we see it.
|
||||
var cinfo *concreteTypeInfo
|
||||
if v := r.concreteTypes.At(C); v != nil {
|
||||
cinfo = v.(*concreteTypeInfo)
|
||||
} else {
|
||||
mset := r.prog.MethodSets.MethodSet(C)
|
||||
cinfo = &concreteTypeInfo{
|
||||
C: C,
|
||||
mset: mset,
|
||||
fprint: fingerprint(mset),
|
||||
}
|
||||
r.concreteTypes.Set(C, cinfo)
|
||||
|
||||
// Ascertain set of interfaces C implements
|
||||
// and update the 'implements' relation.
|
||||
r.interfaceTypes.Iterate(func(I types.Type, v any) {
|
||||
iinfo := v.(*interfaceTypeInfo)
|
||||
if I := types.Unalias(I).(*types.Interface); implements(cinfo, iinfo) {
|
||||
iinfo.implementations = append(iinfo.implementations, C)
|
||||
cinfo.implements = append(cinfo.implements, I)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return cinfo.implements
|
||||
}
|
||||
|
||||
// implementations(I) returns all currently known concrete types that implement I.
|
||||
func (r *rta) implementations(I *types.Interface) []types.Type {
|
||||
// Create an info for I the first time we see it.
|
||||
var iinfo *interfaceTypeInfo
|
||||
if v := r.interfaceTypes.At(I); v != nil {
|
||||
iinfo = v.(*interfaceTypeInfo)
|
||||
} else {
|
||||
mset := r.prog.MethodSets.MethodSet(I)
|
||||
iinfo = &interfaceTypeInfo{
|
||||
I: I,
|
||||
mset: mset,
|
||||
fprint: fingerprint(mset),
|
||||
}
|
||||
r.interfaceTypes.Set(I, iinfo)
|
||||
|
||||
// Ascertain set of concrete types that implement I
|
||||
// and update the 'implements' relation.
|
||||
r.concreteTypes.Iterate(func(C types.Type, v any) {
|
||||
cinfo := v.(*concreteTypeInfo)
|
||||
if implements(cinfo, iinfo) {
|
||||
cinfo.implements = append(cinfo.implements, I)
|
||||
iinfo.implementations = append(iinfo.implementations, C)
|
||||
}
|
||||
})
|
||||
}
|
||||
return iinfo.implementations
|
||||
}
|
||||
|
||||
// addRuntimeType is called for each concrete type that can be the
|
||||
// dynamic type of some interface or reflect.Value.
|
||||
// Adapted from needMethods in go/ssa/builder.go
|
||||
func (r *rta) addRuntimeType(T types.Type, skip bool) {
|
||||
// Never record aliases.
|
||||
T = types.Unalias(T)
|
||||
|
||||
if prev, ok := r.result.RuntimeTypes.At(T).(bool); ok {
|
||||
if skip && !prev {
|
||||
r.result.RuntimeTypes.Set(T, skip)
|
||||
}
|
||||
return
|
||||
}
|
||||
r.result.RuntimeTypes.Set(T, skip)
|
||||
|
||||
mset := r.prog.MethodSets.MethodSet(T)
|
||||
|
||||
if _, ok := T.Underlying().(*types.Interface); !ok {
|
||||
// T is a new concrete type.
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
sel := mset.At(i)
|
||||
m := sel.Obj()
|
||||
|
||||
if m.Exported() {
|
||||
// Exported methods are always potentially callable via reflection.
|
||||
r.addReachable(r.prog.MethodValue(sel), true)
|
||||
}
|
||||
}
|
||||
|
||||
// Add callgraph edge for each existing dynamic
|
||||
// "invoke"-mode call via that interface.
|
||||
for _, I := range r.interfaces(T) {
|
||||
sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction)
|
||||
for _, site := range sites {
|
||||
r.addInvokeEdge(site, T)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
// Recursive case: skip => don't call makeMethods(T).
|
||||
// Each package maintains its own set of types it has visited.
|
||||
|
||||
var n *types.Named
|
||||
switch T := types.Unalias(T).(type) {
|
||||
case *types.Named:
|
||||
n = T
|
||||
case *types.Pointer:
|
||||
n, _ = types.Unalias(T.Elem()).(*types.Named)
|
||||
}
|
||||
if n != nil {
|
||||
owner := n.Obj().Pkg()
|
||||
if owner == nil {
|
||||
return // built-in error type
|
||||
}
|
||||
}
|
||||
|
||||
// Recursion over signatures of each exported method.
|
||||
for method := range mset.Methods() {
|
||||
if method.Obj().Exported() {
|
||||
sig := method.Type().(*types.Signature)
|
||||
r.addRuntimeType(sig.Params(), true) // skip the Tuple itself
|
||||
r.addRuntimeType(sig.Results(), true) // skip the Tuple itself
|
||||
}
|
||||
}
|
||||
|
||||
switch t := T.(type) {
|
||||
case *types.Alias:
|
||||
panic("unreachable")
|
||||
|
||||
case *types.Basic:
|
||||
// nop
|
||||
|
||||
case *types.Interface:
|
||||
// nop---handled by recursion over method set.
|
||||
|
||||
case *types.Pointer:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Slice:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Chan:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Map:
|
||||
r.addRuntimeType(t.Key(), false)
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Signature:
|
||||
if t.Recv() != nil {
|
||||
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
|
||||
}
|
||||
r.addRuntimeType(t.Params(), true) // skip the Tuple itself
|
||||
r.addRuntimeType(t.Results(), true) // skip the Tuple itself
|
||||
|
||||
case *types.Named:
|
||||
// A pointer-to-named type can be derived from a named
|
||||
// type via reflection. It may have methods too.
|
||||
r.addRuntimeType(types.NewPointer(T), false)
|
||||
|
||||
// Consider 'type T struct{S}' where S has methods.
|
||||
// Reflection provides no way to get from T to struct{S},
|
||||
// only to S, so the method set of struct{S} is unwanted,
|
||||
// so set 'skip' flag during recursion.
|
||||
r.addRuntimeType(t.Underlying(), true)
|
||||
|
||||
case *types.Array:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Struct:
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
r.addRuntimeType(t.Field(i).Type(), false)
|
||||
}
|
||||
|
||||
case *types.Tuple:
|
||||
for i, n := 0, t.Len(); i < n; i++ {
|
||||
r.addRuntimeType(t.At(i).Type(), false)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(T)
|
||||
}
|
||||
}
|
||||
|
||||
// fingerprint returns a bitmask with one bit set per method id,
|
||||
// enabling 'implements' to quickly reject most candidates.
|
||||
func fingerprint(mset *types.MethodSet) uint64 {
|
||||
var space [64]byte
|
||||
var mask uint64
|
||||
for method := range mset.Methods() {
|
||||
method := method.Obj()
|
||||
sig := method.Type().(*types.Signature)
|
||||
sum := crc32.ChecksumIEEE(fmt.Appendf(space[:], "%s/%d/%d",
|
||||
method.Id(),
|
||||
sig.Params().Len(),
|
||||
sig.Results().Len()))
|
||||
mask |= 1 << (sum % 64)
|
||||
}
|
||||
return mask
|
||||
}
|
||||
|
||||
// implements reports whether types.Implements(cinfo.C, iinfo.I),
|
||||
// but more efficiently.
|
||||
func implements(cinfo *concreteTypeInfo, iinfo *interfaceTypeInfo) (got bool) {
|
||||
// The concrete type must have at least the methods
|
||||
// (bits) of the interface type. Use a bitwise subset
|
||||
// test to reject most candidates quickly.
|
||||
return iinfo.fprint & ^cinfo.fprint == 0 && types.Implements(cinfo.C, iinfo.I)
|
||||
}
|
||||
180
vendor/golang.org/x/tools/go/callgraph/util.go
generated
vendored
Normal file
180
vendor/golang.org/x/tools/go/callgraph/util.go
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright 2013 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 callgraph
|
||||
|
||||
import "golang.org/x/tools/go/ssa"
|
||||
|
||||
// This file provides various utilities over call graphs, such as
|
||||
// visitation and path search.
|
||||
|
||||
// CalleesOf returns a new set containing all direct callees of the
|
||||
// caller node.
|
||||
func CalleesOf(caller *Node) map[*Node]bool {
|
||||
callees := make(map[*Node]bool)
|
||||
for _, e := range caller.Out {
|
||||
callees[e.Callee] = true
|
||||
}
|
||||
return callees
|
||||
}
|
||||
|
||||
// GraphVisitEdges visits all the edges in graph g in depth-first order.
|
||||
// The edge function is called for each edge in postorder. If it
|
||||
// returns non-nil, visitation stops and GraphVisitEdges returns that
|
||||
// value.
|
||||
func GraphVisitEdges(g *Graph, edge func(*Edge) error) error {
|
||||
seen := make(map[*Node]bool)
|
||||
var visit func(n *Node) error
|
||||
visit = func(n *Node) error {
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
for _, e := range n.Out {
|
||||
if err := visit(e.Callee); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := edge(e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, n := range g.Nodes {
|
||||
if err := visit(n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PathSearch finds an arbitrary path starting at node start and
|
||||
// ending at some node for which isEnd() returns true. On success,
|
||||
// PathSearch returns the path as an ordered list of edges; on
|
||||
// failure, it returns nil.
|
||||
func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
|
||||
stack := make([]*Edge, 0, 32)
|
||||
seen := make(map[*Node]bool)
|
||||
var search func(n *Node) []*Edge
|
||||
search = func(n *Node) []*Edge {
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
if isEnd(n) {
|
||||
return stack
|
||||
}
|
||||
for _, e := range n.Out {
|
||||
stack = append(stack, e) // push
|
||||
if found := search(e.Callee); found != nil {
|
||||
return found
|
||||
}
|
||||
stack = stack[:len(stack)-1] // pop
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return search(start)
|
||||
}
|
||||
|
||||
// DeleteSyntheticNodes removes from call graph g all nodes for
|
||||
// functions that do not correspond to source syntax. For historical
|
||||
// reasons, nodes for g.Root and package initializers are always
|
||||
// kept.
|
||||
//
|
||||
// As nodes are removed, edges are created to preserve the
|
||||
// reachability relation of the remaining nodes.
|
||||
func (g *Graph) DeleteSyntheticNodes() {
|
||||
// Measurements on the standard library and go.tools show that
|
||||
// resulting graph has ~15% fewer nodes and 4-8% fewer edges
|
||||
// than the input.
|
||||
//
|
||||
// Inlining a wrapper of in-degree m, out-degree n adds m*n
|
||||
// and removes m+n edges. Since most wrappers are monomorphic
|
||||
// (n=1) this results in a slight reduction. Polymorphic
|
||||
// wrappers (n>1), e.g. from embedding an interface value
|
||||
// inside a struct to satisfy some interface, cause an
|
||||
// increase in the graph, but they seem to be uncommon.
|
||||
|
||||
// Hash all existing edges to avoid creating duplicates.
|
||||
edges := make(map[Edge]bool)
|
||||
for _, cgn := range g.Nodes {
|
||||
for _, e := range cgn.Out {
|
||||
edges[*e] = true
|
||||
}
|
||||
}
|
||||
for fn, cgn := range g.Nodes {
|
||||
if cgn == g.Root || isInit(cgn.Func) || fn.Syntax() != nil {
|
||||
continue // keep
|
||||
}
|
||||
for _, eIn := range cgn.In {
|
||||
for _, eOut := range cgn.Out {
|
||||
newEdge := Edge{eIn.Caller, eIn.Site, eOut.Callee}
|
||||
if edges[newEdge] {
|
||||
continue // don't add duplicate
|
||||
}
|
||||
AddEdge(eIn.Caller, eIn.Site, eOut.Callee)
|
||||
edges[newEdge] = true
|
||||
}
|
||||
}
|
||||
g.DeleteNode(cgn)
|
||||
}
|
||||
}
|
||||
|
||||
func isInit(fn *ssa.Function) bool {
|
||||
return fn.Pkg != nil && fn.Pkg.Func("init") == fn
|
||||
}
|
||||
|
||||
// DeleteNode removes node n and its edges from the graph g.
|
||||
// (NB: not efficient for batch deletion.)
|
||||
func (g *Graph) DeleteNode(n *Node) {
|
||||
n.deleteIns()
|
||||
n.deleteOuts()
|
||||
delete(g.Nodes, n.Func)
|
||||
}
|
||||
|
||||
// deleteIns deletes all incoming edges to n.
|
||||
func (n *Node) deleteIns() {
|
||||
for _, e := range n.In {
|
||||
removeOutEdge(e)
|
||||
}
|
||||
n.In = nil
|
||||
}
|
||||
|
||||
// deleteOuts deletes all outgoing edges from n.
|
||||
func (n *Node) deleteOuts() {
|
||||
for _, e := range n.Out {
|
||||
removeInEdge(e)
|
||||
}
|
||||
n.Out = nil
|
||||
}
|
||||
|
||||
// removeOutEdge removes edge.Caller's outgoing edge 'edge'.
|
||||
func removeOutEdge(edge *Edge) {
|
||||
caller := edge.Caller
|
||||
n := len(caller.Out)
|
||||
for i, e := range caller.Out {
|
||||
if e == edge {
|
||||
// Replace it with the final element and shrink the slice.
|
||||
caller.Out[i] = caller.Out[n-1]
|
||||
caller.Out[n-1] = nil // aid GC
|
||||
caller.Out = caller.Out[:n-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("edge not found: " + edge.String())
|
||||
}
|
||||
|
||||
// removeInEdge removes edge.Callee's incoming edge 'edge'.
|
||||
func removeInEdge(edge *Edge) {
|
||||
caller := edge.Callee
|
||||
n := len(caller.In)
|
||||
for i, e := range caller.In {
|
||||
if e == edge {
|
||||
// Replace it with the final element and shrink the slice.
|
||||
caller.In[i] = caller.In[n-1]
|
||||
caller.In[n-1] = nil // aid GC
|
||||
caller.In = caller.In[:n-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("edge not found: " + edge.String())
|
||||
}
|
||||
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