// GetNodes returns the D3-compatible nodes by rta analysis func GetNodes(prog *ssa.Program) ([]node.Node, error) { main, err := mainPackage(prog, false) if err != nil { return nil, err } roots := []*ssa.Function{ main.Func("init"), main.Func("main"), } added := make(map[string]struct{}) extraImports := make(map[string][]string) var nodes []node.Node callgraph := rta.Analyze(roots, true) callgraph.CallGraph.DeleteSyntheticNodes() for _, bar := range callgraph.CallGraph.Nodes { _, ok := added[bar.Func.Package().String()] if !ok { for _, edge := range bar.Out { if extraImports[edge.Callee.Func.Package().String()] == nil { extraImports[edge.Callee.Func.Package().String()] = make([]string, 0) } extraImports[edge.Callee.Func.Package().String()] = append(extraImports[edge.Callee.Func.Package().String()], strings.TrimPrefix(slug.Slug(bar.Func.Package().String()), "package-")) } added[bar.Func.Package().String()] = struct{}{} } } added = make(map[string]struct{}) for _, bar := range callgraph.CallGraph.Nodes { _, ok := added[bar.Func.Package().String()] if !ok { var imports []string for _, edge := range bar.In { imports = append(imports, strings.TrimPrefix(slug.Slug(edge.Caller.Func.Package().String()), "package-")) } imports = append(imports, extraImports[bar.Func.Package().String()]...) nodes = append(nodes, node.Node{Name: strings.TrimPrefix(slug.Slug(bar.Func.Package().String()), "package-"), Imports: imports, Size: len(imports) * 100}) added[bar.Func.Package().String()] = struct{}{} } } return nodes, nil }
// TestRTA runs RTA on each file in inputs, prints the results, and // compares it with the golden results embedded in the WANT comment at // the end of the file. // // The results string consists of two parts: the set of dynamic call // edges, "f --> g", one per line, and the set of reachable functions, // one per line. Each set is sorted. // func TestRTA(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() res := rta.Analyze([]*ssa.Function{ mainPkg.Func("main"), mainPkg.Func("init"), }, true) if got := printResult(res, mainPkg.Object); got != want { t.Errorf("%s: got:\n%s\nwant:\n%s", prog.Fset.Position(pos), got, want) } } }
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, 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 }