| // 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 ( |
| "go/types" |
| |
| "golang.org/x/tools/go/callgraph" |
| "golang.org/x/tools/go/ssa" |
| "golang.org/x/tools/go/ssa/ssautil" |
| "golang.org/x/tools/go/types/typeutil" |
| ) |
| |
| // 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. |
| // |
| // TODO(taking): opt: consider making lazyCallees public. |
| // Using the same benchmarks as callgraph_test.go, removing just |
| // the explicit callgraph.Graph construction is 4x less memory |
| // and is 37% faster. |
| // CHA 86 ms/op 16 MB/op |
| // lazyCallees 63 ms/op 4 MB/op |
| 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 |
| } |
| |
| // lazyCallees returns a function that maps a call site (in a function in fns) |
| // to its callees within fns. |
| // |
| // 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 |
| |
| // methodsByName contains all methods, |
| // grouped by name for efficient lookup. |
| // (methodsById would be better but not every SSA method has a go/types ID.) |
| methodsByName := 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 methodsByName[m.Name()] { |
| 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 { |
| methodsByName[f.Name()] = append(methodsByName[f.Name()], 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 |
| } |
| } |