// TestCHA runs CHA on each file in inputs, prints the dynamic edges of // the call graph, and compares it with the golden results embedded in // the WANT comment at the end of the file. // func TestCHA(t *testing.T) { for _, filename := range inputs { content, err := ioutil.ReadFile(filename) if err != nil { t.Errorf("couldn't read file '%s': %s", filename, err) continue } conf := loader.Config{ SourceImports: true, ParserMode: parser.ParseComments, } f, err := conf.ParseFile(filename, content) if err != nil { t.Error(err) continue } want, pos := expectation(f) if pos == token.NoPos { t.Errorf("No WANT: comment in %s", filename) continue } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) continue } prog := ssa.Create(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) prog.BuildAll() cg := cha.CallGraph(prog) if got := printGraph(cg, mainPkg.Object); got != want { t.Errorf("%s: got:\n%s\nwant:\n%s", prog.Fset.Position(pos), got, want) } } }
// GetNodes returns the D3-compatible nodes by cha analysis - // notably, works without a main() func GetNodes(prog *ssa.Program) ([]node.Node, error) { added := make(map[string]struct{}) extraImports := make(map[string][]string) var nodes []node.Node callgraph := cha.CallGraph(prog) callgraph.DeleteSyntheticNodes() for f, bar := range callgraph.Nodes { fName := fmt.Sprintf("%+v", f) _, ok := added[fName] if !ok { for _, edge := range bar.Out { if extraImports[edge.Callee.Func.String()] == nil { extraImports[edge.Callee.Func.String()] = make([]string, 0) } extraImports[edge.Callee.Func.String()] = append(extraImports[edge.Callee.Func.String()], strings.TrimPrefix(slug.Slug(fName), "package-")) } added[fName] = struct{}{} } } added = make(map[string]struct{}) for f, bar := range callgraph.Nodes { fName := fmt.Sprintf("%+v", f) _, ok := added[fName] if !ok { var imports []string for _, edge := range bar.In { imports = append(imports, strings.TrimPrefix(slug.Slug(edge.Caller.Func.String()), "package-")) } imports = append(imports, extraImports[fName]...) nodes = append(nodes, node.Node{Name: strings.TrimPrefix(slug.Slug(fName), "package-"), Imports: imports, Size: len(imports) * 100}) added[fName] = struct{}{} } } return nodes, nil }
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 }
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 }