| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package pointer_test |
| |
| import ( |
| "fmt" |
| "sort" |
| |
| "golang.org/x/tools/go/callgraph" |
| "golang.org/x/tools/go/loader" |
| "golang.org/x/tools/go/pointer" |
| "golang.org/x/tools/go/ssa" |
| "golang.org/x/tools/go/ssa/ssautil" |
| ) |
| |
| // 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, ssa.InstantiateGenerics) |
| 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 |
| } |