func checkCallsExpectation(prog *ssa.Program, e *expectation, callgraph call.Graph) bool { found := make(map[string]int) err := call.GraphVisitEdges(callgraph, func(edge call.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 }
// Callers reports the possible callers of the function // immediately enclosing the specified source location. // // TODO(adonovan): if a caller is a wrapper, show the caller's caller. // func callers(o *Oracle, qpos *QueryPos) (queryResult, error) { pkg := o.prog.Package(qpos.info.Pkg) if pkg == nil { return nil, fmt.Errorf("no SSA package") } if !ssa.HasEnclosingFunction(pkg, qpos.path) { return nil, fmt.Errorf("this position is not inside a function") } buildSSA(o) target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { return nil, fmt.Errorf("no SSA function built for this location (dead code?)") } // Run the pointer analysis, recording each // call found to originate from target. o.config.BuildCallGraph = true callgraph := ptrAnalysis(o).CallGraph var edges []call.Edge call.GraphVisitEdges(callgraph, func(edge call.Edge) error { if edge.Callee.Func() == target { edges = append(edges, edge) } return nil }) // TODO(adonovan): sort + dedup calls to ensure test determinism. return &callersResult{ target: target, callgraph: callgraph, edges: edges, }, nil }
// This program demonstrates how to use the pointer analysis to // obtain a conservative call-graph of a Go program. // func Example() { const myprog = ` package main import "fmt" type I interface { f() } type C struct{} func (C) f() { fmt.Println("C.f()") } func main() { var i I = C{} i.f() // dynamic method call } ` // Construct an importer. // Imports will be loaded as if by 'go build'. imp := importer.New(&importer.Config{Build: &build.Default}) // Parse the input file. file, err := parser.ParseFile(imp.Fset, "myprog.go", myprog, parser.DeclarationErrors) if err != nil { fmt.Print(err) // parse error return } // Create a "main" package containing one file. mainInfo := imp.LoadMainPackage(file) // Create SSA-form program representation. var mode ssa.BuilderMode prog := ssa.NewProgram(imp.Fset, mode) if err := prog.CreatePackages(imp); err != nil { fmt.Print(err) // type error in some package return } mainPkg := prog.Package(mainInfo.Pkg) // Build SSA code for bodies of all functions in the whole program. prog.BuildAll() // Run the pointer analysis and build the complete callgraph. config := &pointer.Config{ Mains: []*ssa.Package{mainPkg}, BuildCallGraph: true, } result := pointer.Analyze(config) // 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 call.GraphVisitEdges(result.CallGraph, func(edge call.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) } // Output: // (main.C).f --> fmt.Println // main.init --> fmt.init // main.main --> (main.C).f }