// FindNonConstCalls returns the set of callsites of the given set of methods // for which the "query" parameter is not a compile-time constant. func FindNonConstCalls(cg *callgraph.Graph, qms []*QueryMethod) []ssa.CallInstruction { cg.DeleteSyntheticNodes() // package database/sql has a couple helper functions which are thin // wrappers around other sensitive functions. Instead of handling the // general case by tracing down callsites of wrapper functions // recursively, let's just whitelist the functions we're already // tracking, since it happens to be good enough for our use case. okFuncs := make(map[*ssa.Function]struct{}, len(qms)) for _, m := range qms { okFuncs[m.SSA] = struct{}{} } bad := make([]ssa.CallInstruction, 0) for _, m := range qms { node := cg.CreateNode(m.SSA) for _, edge := range node.In { if _, ok := okFuncs[edge.Site.Parent()]; ok { continue } cc := edge.Site.Common() args := cc.Args // The first parameter is occasionally the receiver. if len(args) == m.ArgCount+1 { args = args[1:] } else if len(args) != m.ArgCount { panic("arg count mismatch") } v := args[m.Param] if _, ok := v.(*ssa.Const); !ok { bad = append(bad, edge.Site) } } } return bad }
func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error { conf := loader.Config{ Build: ctxt, SourceImports: true, } if len(args) == 0 { fmt.Fprintln(os.Stderr, Usage) return nil } // Use the initial packages from the command line. args, err := conf.FromArgs(args, tests) if err != nil { return err } // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return err } // Create and build SSA-form program representation. prog := ssa.Create(iprog, 0) prog.BuildAll() // -- call graph construction ------------------------------------------ var cg *callgraph.Graph switch algo { case "static": cg = static.CallGraph(prog) case "cha": cg = cha.CallGraph(prog) case "pta": main, err := mainPackage(prog, tests) if err != nil { return err } config := &pointer.Config{ Mains: []*ssa.Package{main}, BuildCallGraph: true, } ptares, err := pointer.Analyze(config) if err != nil { return err // internal error in pointer analysis } cg = ptares.CallGraph case "rta": main, err := mainPackage(prog, tests) if err != nil { return err } roots := []*ssa.Function{ main.Func("init"), main.Func("main"), } rtares := rta.Analyze(roots, true) cg = rtares.CallGraph // NB: RTA gives us Reachable and RuntimeTypes too. default: return fmt.Errorf("unknown algorithm: %s", algo) } cg.DeleteSyntheticNodes() // -- output------------------------------------------------------------ var before, after string // Pre-canned formats. switch format { case "digraph": format = `{{printf "%q %q" .Caller .Callee}}` case "graphviz": before = "digraph callgraph {\n" after = "}\n" format = ` {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}"` } tmpl, err := template.New("-format").Parse(format) if err != nil { return fmt.Errorf("invalid -format template: %v", err) } // Allocate these once, outside the traversal. var buf bytes.Buffer data := Edge{fset: prog.Fset} fmt.Fprint(stdout, before) if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error { data.position.Offset = -1 data.edge = edge data.Caller = edge.Caller.Func data.Callee = edge.Callee.Func buf.Reset() if err := tmpl.Execute(&buf, &data); err != nil { return err } stdout.Write(buf.Bytes()) if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' { fmt.Fprintln(stdout) } return nil }); err != nil { return err } fmt.Fprint(stdout, after) return nil }
// doCallgraph computes the CALLEES and CALLERS relations. func (a *analysis) doCallgraph(cg *callgraph.Graph) { log.Print("Deleting synthetic nodes...") // TODO(adonovan): opt: DeleteSyntheticNodes is asymptotically // inefficient and can be (unpredictably) slow. cg.DeleteSyntheticNodes() log.Print("Synthetic nodes deleted") // Populate nodes of package call graphs (PCGs). for _, n := range cg.Nodes { a.pcgAddNode(n.Func) } // Within each PCG, sort funcs by name. for _, pcg := range a.pcgs { pcg.sortNodes() } calledFuncs := make(map[ssa.CallInstruction]map[*ssa.Function]bool) callingSites := make(map[*ssa.Function]map[ssa.CallInstruction]bool) for _, n := range cg.Nodes { for _, e := range n.Out { if e.Site == nil { continue // a call from a synthetic node such as <root> } // Add (site pos, callee) to calledFuncs. // (Dynamic calls only.) callee := e.Callee.Func a.pcgAddEdge(n.Func, callee) if callee.Synthetic != "" { continue // call of a package initializer } if e.Site.Common().StaticCallee() == nil { // dynamic call // (CALLEES information for static calls // is computed using SSA information.) lparen := e.Site.Common().Pos() if lparen != token.NoPos { fns := calledFuncs[e.Site] if fns == nil { fns = make(map[*ssa.Function]bool) calledFuncs[e.Site] = fns } fns[callee] = true } } // Add (callee, site) to callingSites. fns := callingSites[callee] if fns == nil { fns = make(map[ssa.CallInstruction]bool) callingSites[callee] = fns } fns[e.Site] = true } } // CALLEES. log.Print("Callees...") for site, fns := range calledFuncs { var funcs funcsByPos for fn := range fns { funcs = append(funcs, fn) } sort.Sort(funcs) a.addCallees(site, funcs) } // CALLERS log.Print("Callers...") for callee, sites := range callingSites { pos := funcToken(callee) if pos == token.NoPos { log.Printf("CALLERS: skipping %s: no pos", callee) continue } var this *types.Package // for relativizing names if callee.Pkg != nil { this = callee.Pkg.Pkg } // Compute sites grouped by parent, with text and URLs. sitesByParent := make(map[*ssa.Function]sitesByPos) for site := range sites { fn := site.Parent() sitesByParent[fn] = append(sitesByParent[fn], site) } var funcs funcsByPos for fn := range sitesByParent { funcs = append(funcs, fn) } sort.Sort(funcs) v := callersJSON{ Callee: callee.String(), Callers: []callerJSON{}, // (JS wants non-nil) } for _, fn := range funcs { caller := callerJSON{ Func: prettyFunc(this, fn), Sites: []anchorJSON{}, // (JS wants non-nil) } sites := sitesByParent[fn] sort.Sort(sites) for _, site := range sites { pos := site.Common().Pos() if pos != token.NoPos { caller.Sites = append(caller.Sites, anchorJSON{ Text: fmt.Sprintf("%d", a.prog.Fset.Position(pos).Line), Href: a.posURL(pos, len("(")), }) } } v.Callers = append(v.Callers, caller) } fi, offset := a.fileAndOffset(pos) fi.addLink(aLink{ start: offset, end: offset + len("func"), title: fmt.Sprintf("%d callers", len(sites)), onclick: fmt.Sprintf("onClickCallers(%d)", fi.addData(v)), }) } // PACKAGE CALLGRAPH log.Print("Package call graph...") for pkg, pcg := range a.pcgs { // Maps (*ssa.Function).RelString() to index in JSON CALLGRAPH array. index := make(map[string]int) // Treat exported functions (and exported methods of // exported named types) as roots even if they aren't // actually called from outside the package. for i, n := range pcg.nodes { if i == 0 || n.fn.Object() == nil || !n.fn.Object().Exported() { continue } recv := n.fn.Signature.Recv() if recv == nil || deref(recv.Type()).(*types.Named).Obj().Exported() { roots := &pcg.nodes[0].edges roots.SetBit(roots, i, 1) } index[n.fn.RelString(pkg.Pkg)] = i } json := a.pcgJSON(pcg) // TODO(adonovan): pkg.Path() is not unique! // It is possible to declare a non-test package called x_test. a.result.pkgInfo(pkg.Pkg.Path()).setCallGraph(json, index) } }
func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error { conf := loader.Config{ Build: ctxt, SourceImports: true, } if len(args) == 0 { fmt.Fprintln(os.Stderr, Usage) return nil } // Use the initial packages from the command line. args, err := conf.FromArgs(args, tests) if err != nil { return err } // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return err } // Create and build SSA-form program representation. prog := ssa.Create(iprog, 0) prog.BuildAll() // Determine the main package. // TODO(adonovan): allow independent control over tests, mains // and libraries. // TODO(adonovan): put this logic in a library; we keep reinventing it. var main *ssa.Package pkgs := prog.AllPackages() if tests { // If -test, use all packages' tests. if len(pkgs) > 0 { main = prog.CreateTestMainPackage(pkgs...) } if main == nil { return fmt.Errorf("no tests") } } else { // Otherwise, use main.main. for _, pkg := range pkgs { if pkg.Object.Name() == "main" { main = pkg if main.Func("main") == nil { return fmt.Errorf("no func main() in main package") } break } } if main == nil { return fmt.Errorf("no main package") } } // Invariant: main package has a main() function. // -- call graph construction ------------------------------------------ var cg *callgraph.Graph switch algo { case "pta": config := &pointer.Config{ Mains: []*ssa.Package{main}, BuildCallGraph: true, } ptares, err := pointer.Analyze(config) if err != nil { return err // internal error in pointer analysis } cg = ptares.CallGraph case "rta": roots := []*ssa.Function{ main.Func("init"), main.Func("main"), } rtares := rta.Analyze(roots, true) cg = rtares.CallGraph // NB: RTA gives us Reachable and RuntimeTypes too. default: return fmt.Errorf("unknown algorithm: %s", algo) } cg.DeleteSyntheticNodes() // -- output------------------------------------------------------------ var before, after string // Pre-canned formats. switch format { case "digraph": format = `{{printf "%q %q" .Caller .Callee}}` case "graphviz": before = "digraph callgraph {\n" after = "}\n" format = ` {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}"` } tmpl, err := template.New("-format").Parse(format) if err != nil { return fmt.Errorf("invalid -format template: %v", err) } // Allocate these once, outside the traversal. var buf bytes.Buffer data := Edge{fset: prog.Fset} fmt.Fprint(stdout, before) if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error { data.position.Offset = -1 data.edge = edge data.Caller = edge.Caller.Func data.Callee = edge.Callee.Func buf.Reset() if err := tmpl.Execute(&buf, &data); err != nil { return err } stdout.Write(buf.Bytes()) if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' { fmt.Fprintln(stdout) } return nil }); err != nil { return err } fmt.Fprint(stdout, after) return nil }
func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error { conf := loader.Config{Build: ctxt} if len(args) == 0 { fmt.Fprintln(os.Stderr, Usage) return nil } if *fileFlag == "" || *lineFlag == -1 { fmt.Fprintln(os.Stderr, "Need input file and line") return nil } // Use the initial packages from the command line. args, err := conf.FromArgs(args, tests) if err != nil { return err } // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return err } // Create and build SSA-form program representation. prog := ssautil.CreateProgram(iprog, 0) prog.Build() // -- call graph construction ------------------------------------------ var cg *callgraph.Graph switch algo { case "static": cg = static.CallGraph(prog) case "cha": cg = cha.CallGraph(prog) case "pta": // Set up points-to analysis log file. var ptalog io.Writer if *ptalogFlag != "" { if f, err := os.Create(*ptalogFlag); err != nil { log.Fatalf("Failed to create PTA log file: %s", err) } else { buf := bufio.NewWriter(f) ptalog = buf defer func() { if err := buf.Flush(); err != nil { log.Printf("flush: %s", err) } if err := f.Close(); err != nil { log.Printf("close: %s", err) } }() } } main, err := mainPackage(prog, tests) if err != nil { return err } config := &pointer.Config{ Mains: []*ssa.Package{main}, BuildCallGraph: true, Log: ptalog, } ptares, err := pointer.Analyze(config) if err != nil { return err // internal error in pointer analysis } cg = ptares.CallGraph case "rta": main, err := mainPackage(prog, tests) if err != nil { return err } roots := []*ssa.Function{ main.Func("init"), main.Func("main"), } rtares := rta.Analyze(roots, true) cg = rtares.CallGraph // NB: RTA gives us Reachable and RuntimeTypes too. default: return fmt.Errorf("unknown algorithm: %s", algo) } cg.DeleteSyntheticNodes() // -- output------------------------------------------------------------ file := *fileFlag line := *lineFlag depth := *depthFlag node := findFunction(cg, file, line) if node == nil { panic("function not found") } ins, before := ChainBefore(node) after, outs := ChainAfter(node) chain := append(before, after[1:]...) sort.Sort(SortNodes(ins)) for _, n := range ins { _, ch := ChainBefore(n) fmt.Printf("(%d) %s\n", impact(ch[0]), chainToString(ch)) } fmt.Println() fmt.Println(chainToString(chain)) fmt.Println() sort.Sort(SortNodes(outs)) for _, n := range outs { if impact(n) > minImpact { continue } printFanout(depth, "", n) } return nil }