func TestStatic(t *testing.T) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("P.go", input) if err != nil { t.Fatal(err) } conf.CreateFromFiles("P", f) iprog, err := conf.Load() if err != nil { t.Fatal(err) } P := iprog.Created[0].Pkg prog := ssautil.CreateProgram(iprog, 0) prog.BuildAll() cg := static.CallGraph(prog) var edges []string callgraph.GraphVisitEdges(cg, func(e *callgraph.Edge) error { edges = append(edges, fmt.Sprintf("%s -> %s", e.Caller.Func.RelString(P), e.Callee.Func.RelString(P))) return nil }) sort.Strings(edges) want := []string{ "(*C).f -> (C).f", "f -> (C).f", "f -> f$1", "f -> g", } if !reflect.DeepEqual(edges, want) { t.Errorf("Got edges %v, want %v", edges, 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 }
// Callstack displays an arbitrary path from a root of the callgraph // to the function at the current position. // // The information may be misleading in a context-insensitive // analysis. e.g. the call path X->Y->Z might be infeasible if Y never // calls Z when it is called from X. TODO(adonovan): think about UI. // // TODO(adonovan): permit user to specify a starting point other than // the analysis root. // func callstack(q *Query) error { fset := token.NewFileSet() lconf := loader.Config{Fset: fset, Build: q.Build} if err := setPTAScope(&lconf, q.Scope); err != nil { return err } // Load/parse/type-check the program. lprog, err := loadWithSoftErrors(&lconf) if err != nil { return err } qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { return err } prog := ssautil.CreateProgram(lprog, 0) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { return err } pkg := prog.Package(qpos.info.Pkg) if pkg == nil { return fmt.Errorf("no SSA package") } if !ssa.HasEnclosingFunction(pkg, qpos.path) { return fmt.Errorf("this position is not inside a function") } // Defer SSA construction till after errors are reported. prog.Build() target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { return fmt.Errorf("no SSA function built for this location (dead code?)") } var callpath []*callgraph.Edge isEnd := func(n *callgraph.Node) bool { return n.Func == target } // First, build a callgraph containing only static call edges, // and search for an arbitrary path from a root to the target function. // This is quick, and the user wants a static path if one exists. cg := static.CallGraph(prog) cg.DeleteSyntheticNodes() for _, ep := range entryPoints(ptaConfig.Mains) { callpath = callgraph.PathSearch(cg.CreateNode(ep), isEnd) if callpath != nil { break } } // No fully static path found. // Run the pointer analysis and build a complete call graph. if callpath == nil { ptaConfig.BuildCallGraph = true cg := ptrAnalysis(ptaConfig).CallGraph cg.DeleteSyntheticNodes() callpath = callgraph.PathSearch(cg.Root, isEnd) if callpath != nil { callpath = callpath[1:] // remove synthetic edge from <root> } } q.Output(fset, &callstackResult{ qpos: qpos, target: target, callpath: callpath, }) 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 }