func doOneInput(input, filename string) bool { impctx := &importer.Config{Build: &build.Default} imp := importer.New(impctx) // Parsing. f, err := parser.ParseFile(imp.Fset, filename, input, 0) if err != nil { // TODO(adonovan): err is a scanner error list; // display all errors not just first? fmt.Println(err) return false } // Create single-file main package and import its dependencies. info := imp.CreatePackage("main", f) // SSA creation + building. prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err := prog.CreatePackages(imp); err != nil { fmt.Println(err) return false } prog.BuildAll() mainpkg := prog.Package(info.Pkg) 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) } 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) t, _, err = types.EvalNode(imp.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 } } 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 probes []probe var log bytes.Buffer // Run the analysis. config := &pointer.Config{ Reflection: true, BuildCallGraph: true, Mains: []*ssa.Package{ptrmain}, Log: &log, Print: func(site *ssa.CallCommon, p pointer.Pointer) { probes = append(probes, probe{site, p}) }, } // Print the log is there was an error or a panic. complete := false defer func() { if !complete || !ok { log.WriteTo(os.Stderr) } }() result := pointer.Analyze(config) // Check the expectations. for _, e := range exps { var pr *probe if e.needsProbe() { if pr = findProbe(prog, probes, e); pr == nil { ok = false e.errorf("unreachable print() statement has expectation %s", e) continue } if pr.arg0 == nil { ok = false e.errorf("expectation on non-pointerlike operand: %s", pr.instr.Args[0].Type()) continue } } switch e.kind { case "pointsto": if !checkPointsToExpectation(e, pr, lineMapping, prog) { ok = false } case "types": if !checkTypesExpectation(e, pr) { 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 }
// ptrAnalysis runs the pointer analysis and returns its result. func ptrAnalysis(o *Oracle) *pointer.Result { return pointer.Analyze(&o.ptaConfig) }
// This program demonstrates how to use the pointer analysis to // obtain a conservative call-graph of a Go program. // func Example() { const myprog = ` package main import "fmt" type I interface { f() } type C struct{} func (C) f() { fmt.Println("C.f()") } func main() { var i I = C{} i.f() // dynamic method call } ` // Construct an importer. // Imports will be loaded as if by 'go build'. imp := importer.New(&importer.Config{Build: &build.Default}) // Parse the input file. file, err := parser.ParseFile(imp.Fset, "myprog.go", myprog, parser.DeclarationErrors) if err != nil { fmt.Print(err) // parse error return } // Create a "main" package containing one file. mainInfo := imp.LoadMainPackage(file) // Create SSA-form program representation. var mode ssa.BuilderMode prog := ssa.NewProgram(imp.Fset, mode) if err := prog.CreatePackages(imp); err != nil { fmt.Print(err) // type error in some package return } mainPkg := prog.Package(mainInfo.Pkg) // Build SSA code for bodies of all functions in the whole program. prog.BuildAll() // Run the pointer analysis and build the complete callgraph. config := &pointer.Config{ Mains: []*ssa.Package{mainPkg}, BuildCallGraph: true, } result := pointer.Analyze(config) // 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 call.GraphVisitEdges(result.CallGraph, func(edge call.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) } // Output: // (main.C).f --> fmt.Println // main.init --> fmt.init // main.main --> (main.C).f }
// ptrAnalysis runs the pointer analysis and returns its result. func ptrAnalysis(o *Oracle) *pointer.Result { start := time.Now() result := pointer.Analyze(&o.config) o.timers["pointer analysis"] = time.Since(start) return result }