// pointer runs the pointer analysis. func (a *analysis) pointer(mainPkgs []*ssa.Package) { // Run the pointer analysis and build the complete callgraph. a.ptaConfig.Mains = mainPkgs a.ptaConfig.BuildCallGraph = true a.ptaConfig.Reflection = false // (for now) a.result.setStatusf("Pointer analysis running...") ptares, err := pointer.Analyze(&a.ptaConfig) if err != nil { // If this happens, it indicates a bug. a.result.setStatusf("Pointer analysis failed: %s.", err) return } log.Print("Pointer analysis complete.") // Add the results of pointer analysis. a.result.setStatusf("Computing channel peers...") a.doChannelPeers(ptares.Queries) a.result.setStatusf("Computing dynamic call graph edges...") a.doCallgraph(ptares.CallGraph) a.result.setStatusf("Analysis complete.") }
// ptrAnalysis runs the pointer analysis and returns its result. func ptrAnalysis(conf *pointer.Config) *pointer.Result { result, err := pointer.Analyze(conf) if err != nil { panic(err) // pointer analysis internal error } return result }
// ptrAnalysis runs the pointer analysis and returns its result. func ptrAnalysis(o *Oracle) *pointer.Result { result, err := pointer.Analyze(&o.ptaConfig) if err != nil { panic(err) // pointer analysis internal error } return result }
// NewPta performs a custom pointer analysis on given values. func (info *SSAInfo) NewPta(vals ...ssa.Value) *pointer.Result { for _, val := range vals { info.PtaConf.AddQuery(val) } result, err := pointer.Analyze(info.PtaConf) if err != nil { info.Logger.Print("NewPta:", ErrPtaInternal) } return result }
// boilerplate callgraph code stolen off golang.org/x/tools/cmd/callgraph func doCallgraph(prog *ssa.Program, tests bool) (*callgraph.Graph, error) { main, err := mainPackage(prog, tests) if err != nil { return nil, err } config := &pointer.Config{ Mains: []*ssa.Package{main}, BuildCallGraph: true, } ptares, err := pointer.Analyze(config) if err != nil { return nil, err // internal error in pointer analysis } cg := ptares.CallGraph cg.DeleteSyntheticNodes() return cg, nil }
// FindChan performs a ptr analysis on a given chan ssa.Value, returns a list of // related ChanOp on the chan. func (info *SSAInfo) FindChan(ch ssa.Value) []ChanOp { chanOps := purgeChanOps(progChanOps(info.Prog), ch) for _, op := range chanOps { info.PtaConf.AddQuery(op.Value) } result, err := pointer.Analyze(info.PtaConf) if err != nil { info.Logger.Print("FindChan failed:", ErrPtaInternal) } queryCh := result.Queries[ch] var ops []ChanOp for _, label := range queryCh.PointsTo().Labels() { // Add MakeChan to result ops = append(ops, ChanOp{label.Value(), ChanMake, label.Pos()}) } for _, op := range chanOps { if ptr, ok := result.Queries[op.Value]; ok && ptr.MayAlias(queryCh) { ops = append(ops, op) } } return ops }
func doMain() error { args, err := setup() if err != nil { return err } prog, pkgs, mainPkg, err := buildSSA(false, args) if err != nil { return err } if *prefix == "" { *prefix = args[0] } config := &pointer.Config{ Mains: mainPkg, BuildCallGraph: true, Reflection: true, } ptrResult, err := pointer.Analyze(config) if err != nil { return err } //fmt.Println("number of pointer analysis warnings:", len(ptrResult.Warnings)) // but pointer analysis only fully works when the "reflect" and "unsafe" packages are not used for _, pkg := range pkgs { switch pkg.Pkg.Name() { case "reflect", "unsafe", "C": fmt.Println("*** be cautious, pointer analysis is suspect because program uses package: " + pkg.Pkg.Name()) } } showResults(prog, pkgs, ptrResult) return nil }
// This program demonstrates how to use the pointer analysis to // obtain a conservative call-graph of a Go program. // It also shows how to compute the points-to set of a variable, // in this case, (C).f's ch parameter. // func Example() { const myprog = ` package main import "fmt" type I interface { f(map[string]int) } type C struct{} func (C) f(m map[string]int) { fmt.Println("C.f()") } func main() { var i I = C{} x := map[string]int{"one":1} i.f(x) // dynamic method call } ` var conf loader.Config // Parse the input file, a string. // (Command-line tools should use conf.FromArgs.) file, err := conf.ParseFile("myprog.go", myprog) if err != nil { fmt.Print(err) // parse error return } // Create single-file main package and import its dependencies. conf.CreateFromFiles("main", file) iprog, err := conf.Load() if err != nil { fmt.Print(err) // type error in some package return } // Create SSA-form program representation. prog := ssautil.CreateProgram(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) // Build SSA code for bodies of all functions in the whole program. prog.Build() // Configure the pointer analysis to build a call-graph. config := &pointer.Config{ Mains: []*ssa.Package{mainPkg}, BuildCallGraph: true, } // Query points-to set of (C).f's parameter m, a map. C := mainPkg.Type("C").Type() Cfm := prog.LookupMethod(C, mainPkg.Pkg, "f").Params[1] config.AddQuery(Cfm) // Run the pointer analysis. result, err := pointer.Analyze(config) if err != nil { panic(err) // internal error in pointer analysis } // 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 callgraph.GraphVisitEdges(result.CallGraph, func(edge *callgraph.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) } fmt.Println() // Print the labels of (C).f(m)'s points-to set. fmt.Println("m may point to:") var labels []string for _, l := range result.Queries[Cfm].PointsTo().Labels() { label := fmt.Sprintf(" %s: %s", prog.Fset.Position(l.Pos()), l) labels = append(labels, label) } sort.Strings(labels) for _, label := range labels { fmt.Println(label) } // Output: // (main.C).f --> fmt.Println // main.init --> fmt.init // main.main --> (main.C).f // // m may point to: // myprog.go:18:21: makemap }
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 doOneInput(input, filename string) bool { var conf loader.Config // Parsing. f, err := conf.ParseFile(filename, input) if err != nil { fmt.Println(err) return false } // Create single-file main package and import its dependencies. conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { fmt.Println(err) return false } mainPkgInfo := iprog.Created[0].Pkg // SSA creation + building. prog := ssa.Create(iprog, ssa.SanityCheckFunctions) prog.BuildAll() mainpkg := prog.Package(mainPkgInfo) ptrmain := mainpkg // main package for the pointer analysis if mainpkg.Func("main") == nil { // No main function; assume it's a test. ptrmain = prog.CreateTestMainPackage(mainpkg) } // Find all calls to the built-in print(x). Analytically, // print is a no-op, but it's a convenient hook for testing // the PTS of an expression, so our tests use it. probes := make(map[*ssa.CallCommon]bool) for fn := range ssautil.AllFunctions(prog) { if fn.Pkg == mainpkg { for _, b := range fn.Blocks { for _, instr := range b.Instrs { if instr, ok := instr.(ssa.CallInstruction); ok { call := instr.Common() if b, ok := call.Value.(*ssa.Builtin); ok && b.Name() == "print" && len(call.Args) == 1 { probes[instr.Common()] = true } } } } } } ok := true lineMapping := make(map[string]string) // maps "file:line" to @line tag // Parse expectations in this input. var exps []*expectation re := regexp.MustCompile("// *@([a-z]*) *(.*)$") lines := strings.Split(input, "\n") for linenum, line := range lines { linenum++ // make it 1-based if matches := re.FindAllStringSubmatch(line, -1); matches != nil { match := matches[0] kind, rest := match[1], match[2] e := &expectation{kind: kind, filename: filename, linenum: linenum} if kind == "line" { if rest == "" { ok = false e.errorf("@%s expectation requires identifier", kind) } else { lineMapping[fmt.Sprintf("%s:%d", filename, linenum)] = rest } continue } if e.needsProbe() && !strings.Contains(line, "print(") { ok = false e.errorf("@%s expectation must follow call to print(x)", kind) continue } switch kind { case "pointsto": e.args = split(rest, "|") case "types": for _, typstr := range split(rest, "|") { var t types.Type = types.Typ[types.Invalid] // means "..." if typstr != "..." { texpr, err := parser.ParseExpr(typstr) if err != nil { ok = false // Don't print err since its location is bad. e.errorf("'%s' is not a valid type", typstr) continue } mainFileScope := mainpkg.Object.Scope().Child(0) tv, err := types.EvalNode(prog.Fset, texpr, mainpkg.Object, mainFileScope) if err != nil { ok = false // Don't print err since its location is bad. e.errorf("'%s' is not a valid type: %s", typstr, err) continue } t = tv.Type } e.types = append(e.types, t) } case "calls": e.args = split(rest, "->") // TODO(adonovan): eagerly reject the // expectation if fn doesn't denote // existing function, rather than fail // the expectation after analysis. if len(e.args) != 2 { ok = false e.errorf("@calls expectation wants 'caller -> callee' arguments") continue } case "warning": lit, err := strconv.Unquote(strings.TrimSpace(rest)) if err != nil { ok = false e.errorf("couldn't parse @warning operand: %s", err.Error()) continue } e.args = append(e.args, lit) default: ok = false e.errorf("unknown expectation kind: %s", e) continue } exps = append(exps, e) } } var log bytes.Buffer fmt.Fprintf(&log, "Input: %s\n", filename) // Run the analysis. config := &pointer.Config{ Reflection: true, BuildCallGraph: true, Mains: []*ssa.Package{ptrmain}, Log: &log, } for probe := range probes { v := probe.Args[0] if pointer.CanPoint(v.Type()) { config.AddQuery(v) } } // Print the log is there was an error or a panic. complete := false defer func() { if !complete || !ok { log.WriteTo(os.Stderr) } }() result, err := pointer.Analyze(config) if err != nil { panic(err) // internal error in pointer analysis } // Check the expectations. for _, e := range exps { var call *ssa.CallCommon var pts pointer.PointsToSet var tProbe types.Type if e.needsProbe() { if call, pts = findProbe(prog, probes, result.Queries, e); call == nil { ok = false e.errorf("unreachable print() statement has expectation %s", e) continue } tProbe = call.Args[0].Type() if !pointer.CanPoint(tProbe) { ok = false e.errorf("expectation on non-pointerlike operand: %s", tProbe) continue } } switch e.kind { case "pointsto": if !checkPointsToExpectation(e, pts, lineMapping, prog) { ok = false } case "types": if !checkTypesExpectation(e, pts, tProbe) { ok = false } case "calls": if !checkCallsExpectation(prog, e, result.CallGraph) { ok = false } case "warning": if !checkWarningExpectation(prog, e, result.Warnings) { ok = false } } } complete = true // ok = false // debugging: uncomment to always see log return ok }
func main() { var verbose, quiet bool flag.BoolVar(&verbose, "v", false, "Verbose mode") flag.BoolVar(&quiet, "q", false, "Only print on failure") flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [-q] [-v] package1 [package2 ...]\n", os.Args[0]) flag.PrintDefaults() } flag.Parse() pkgs := flag.Args() if len(pkgs) == 0 { flag.Usage() os.Exit(2) } c := loader.Config{} c.Import("database/sql") for _, pkg := range pkgs { c.Import(pkg) } p, err := c.Load() if err != nil { fmt.Printf("error loading packages %v: %v\n", pkgs, err) os.Exit(2) } s := ssautil.CreateProgram(p, 0) s.Build() qms := FindQueryMethods(p.Package("database/sql").Pkg, s) if verbose { fmt.Println("database/sql functions that accept queries:") for _, m := range qms { fmt.Printf("- %s (param %d)\n", m.Func, m.Param) } fmt.Println() } mains := FindMains(p, s) if len(mains) == 0 { fmt.Println("Did not find any commands (i.e., main functions).") os.Exit(2) } res, err := pointer.Analyze(&pointer.Config{ Mains: mains, BuildCallGraph: true, }) if err != nil { fmt.Printf("error performing pointer analysis: %v\n", err) os.Exit(2) } bad := FindNonConstCalls(res.CallGraph, qms) if len(bad) == 0 { if !quiet { fmt.Println(`You're safe from SQL injection! Yay \o/`) } return } fmt.Printf("Found %d potentially unsafe SQL statements:\n", len(bad)) for _, ci := range bad { pos := p.Fset.Position(ci.Pos()) fmt.Printf("- %s\n", pos) } fmt.Println("Please ensure that all SQL queries you use are compile-time constants.") fmt.Println("You should always use parameterized queries or prepared statements") fmt.Println("instead of building queries from strings.") os.Exit(1) }
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 }