// generate generates offline constraints for the entire program. func (a *analysis) generate() { start("Constraint generation") if a.log != nil { fmt.Fprintln(a.log, "==== Generating constraints") } // Create a dummy node since we use the nodeid 0 for // non-pointerlike variables. a.addNodes(tInvalid, "(zero)") // Create the global node for panic values. a.panicNode = a.addNodes(tEface, "panic") // Create nodes and constraints for all methods of reflect.rtype. // (Shared contours are used by dynamic calls to reflect.Type // methods---typically just String().) if rtype := a.reflectRtypePtr; rtype != nil { a.genMethodsOf(rtype) } root := a.genRootCalls() if a.config.BuildCallGraph { a.result.CallGraph = callgraph.New(root.fn) } // Create nodes and constraints for all methods of all types // that are dynamically accessible via reflection or interfaces. for _, T := range a.prog.RuntimeTypes() { a.genMethodsOf(T) } // Generate constraints for functions as they become reachable // from the roots. (No constraints are generated for functions // that are dead in this analysis scope.) for len(a.genq) > 0 { cgn := a.genq[0] a.genq = a.genq[1:] a.genFunc(cgn) } // The runtime magically allocates os.Args; so should we. if os := a.prog.ImportedPackage("os"); os != nil { // In effect: os.Args = new([1]string)[:] T := types.NewSlice(types.Typ[types.String]) obj := a.addNodes(sliceToArray(T), "<command-line args>") a.endObject(obj, nil, "<command-line args>") a.addressOf(T, a.objectNode(nil, os.Var("Args")), obj) } // Discard generation state, to avoid confusion after node renumbering. a.panicNode = 0 a.globalval = nil a.localval = nil a.localobj = nil stop("Constraint generation") }
// CallGraph computes the call graph of the specified program // considering only static calls. // func CallGraph(prog *ssa.Program) *callgraph.Graph { cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph // TODO(adonovan): opt: use only a single pass over the ssa.Program. for f := range ssautil.AllFunctions(prog) { 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 { gnode := cg.CreateNode(g) callgraph.AddEdge(fnode, site, gnode) } } } } } return cg }
// Analyze performs Rapid Type Analysis, starting at the specified root // functions. It returns nil if no roots were specified. // // 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]) } 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) // 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 r.worklist = append(r.worklist, roots...) for len(r.worklist) > 0 { shadow, r.worklist = r.worklist, shadow[:0] for _, f := range shadow { r.visitFunc(f) } } return r.result }
// 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) // 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. methodsByName := make(map[string][]*ssa.Function) // methodsMemo records, for every abstract method call call I.f on // interface type I, the set of concrete methods C.f of all // types C that satisfy interface I. methodsMemo := make(map[*types.Func][]*ssa.Function) lookupMethods := func(m *types.Func) []*ssa.Function { methods, ok := methodsMemo[m] if !ok { I := m.Type().(*types.Signature).Recv().Type().Underlying().(*types.Interface) for _, f := range methodsByName[m.Name()] { C := f.Signature.Recv().Type() // named or *named if types.Implements(C, I) { methods = append(methods, f) } } methodsMemo[m] = methods } return methods } for f := range allFuncs { 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) } } 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(adonovan): opt: consider factoring the callgraph // API so that the Callers component of each edge is a // slice of nodes, not a singleton. 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 { call := site.Common() if call.IsInvoke() { addEdges(fnode, site, lookupMethods(call.Method)) } else if g := call.StaticCallee(); g != nil { addEdge(fnode, site, g) } else if _, ok := call.Value.(*ssa.Builtin); !ok { callees, _ := funcsBySig.At(call.Signature()).([]*ssa.Function) addEdges(fnode, site, callees) } } } } } return cg }
// directCallsTo inspects the whole program and returns a callgraph // containing edges for all direct calls to the target function. // directCallsTo returns nil if the function is ever address-taken. func directCallsTo(target *ssa.Function, entrypoints []*ssa.Function) *callgraph.Graph { cg := callgraph.New(nil) // use nil as root *Function targetNode := cg.CreateNode(target) // Is the function a program entry point? // If so, add edge from callgraph root. for _, f := range entrypoints { if f == target { callgraph.AddEdge(cg.Root, nil, targetNode) } } // Find receiver type (for methods). var recvType types.Type if recv := target.Signature.Recv(); recv != nil { recvType = recv.Type() } // Find all direct calls to function, // or a place where its address is taken. var space [32]*ssa.Value // preallocate for fn := range ssautil.AllFunctions(target.Prog) { for _, b := range fn.Blocks { for _, instr := range b.Instrs { // Is this a method (T).f of a concrete type T // whose runtime type descriptor is address-taken? // (To be fully sound, we would have to check that // the type doesn't make it to reflection as a // subelement of some other address-taken type.) if recvType != nil { if mi, ok := instr.(*ssa.MakeInterface); ok { if types.Identical(mi.X.Type(), recvType) { return nil // T is address-taken } if ptr, ok := mi.X.Type().(*types.Pointer); ok && types.Identical(ptr.Elem(), recvType) { return nil // *T is address-taken } } } // Direct call to target? rands := instr.Operands(space[:0]) if site, ok := instr.(ssa.CallInstruction); ok && site.Common().Value == target { callgraph.AddEdge(cg.CreateNode(fn), site, targetNode) rands = rands[1:] // skip .Value (rands[0]) } // Address-taken? for _, rand := range rands { if rand != nil && *rand == target { return nil } } } } } return cg }