func checkCallsExpectation(prog *ssa.Program, e *expectation, cg *callgraph.Graph) bool { found := make(map[string]int) err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error { // Name-based matching is inefficient but it allows us to // match functions whose names that would not appear in an // index ("<root>") or which are not unique ("[email protected]"). if edge.Caller.Func.String() == e.args[0] { calleeStr := edge.Callee.Func.String() if calleeStr == e.args[1] { return errOK // expectation satisified; stop the search } found[calleeStr]++ } return nil }) if err == errOK { return true } if len(found) == 0 { e.errorf("didn't find any calls from %s", e.args[0]) } e.errorf("found no call from %s to %s, but only to %s", e.args[0], e.args[1], join(found)) return false }
// This program demonstrates how to use the pointer analysis to // obtain a conservative call-graph of a Go program. // It also shows how to compute the points-to set of a variable, // in this case, (C).f's ch parameter. // func Example() { const myprog = ` package main import "fmt" type I interface { f(map[string]int) } type C struct{} func (C) f(m map[string]int) { fmt.Println("C.f()") } func main() { var i I = C{} x := map[string]int{"one":1} i.f(x) // dynamic method call } ` // Construct a loader. conf := loader.Config{SourceImports: true} // Parse the input file. file, err := conf.ParseFile("myprog.go", myprog, 0) if err != nil { fmt.Print(err) // parse error return } // Create single-file main package and import its dependencies. conf.CreateFromFiles("main", file) iprog, err := conf.Load() if err != nil { fmt.Print(err) // type error in some package return } // Create SSA-form program representation. prog := ssa.Create(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) // Build SSA code for bodies of all functions in the whole program. prog.BuildAll() // Configure the pointer analysis to build a call-graph. config := &pointer.Config{ Mains: []*ssa.Package{mainPkg}, BuildCallGraph: true, } // Query points-to set of (C).f's parameter m, a map. C := mainPkg.Type("C").Type() Cfm := prog.LookupMethod(C, mainPkg.Object, "f").Params[1] config.AddQuery(Cfm) // Run the pointer analysis. result, err := pointer.Analyze(config) if err != nil { panic(err) // internal error in pointer analysis } // Find edges originating from the main package. // By converting to strings, we de-duplicate nodes // representing the same function due to context sensitivity. var edges []string callgraph.GraphVisitEdges(result.CallGraph, func(edge *callgraph.Edge) error { caller := edge.Caller.Func if caller.Pkg == mainPkg { edges = append(edges, fmt.Sprint(caller, " --> ", edge.Callee.Func)) } return nil }) // Print the edges in sorted order. sort.Strings(edges) for _, edge := range edges { fmt.Println(edge) } fmt.Println() // Print the labels of (C).f(m)'s points-to set. fmt.Println("m may point to:") var labels []string for _, l := range result.Queries[Cfm].PointsTo().Labels() { label := fmt.Sprintf(" %s: %s", prog.Fset.Position(l.Pos()), l) labels = append(labels, label) } sort.Strings(labels) for _, label := range labels { fmt.Println(label) } // Output: // (main.C).f --> fmt.Println // main.init --> fmt.init // main.main --> (main.C).f // // m may point to: // myprog.go:18:21: makemap }