github.com/JorgeGCoelho/gospal@v0.0.1/ssa/callgraph.go (about) 1 package ssa 2 3 import ( 4 "bufio" 5 "fmt" 6 "go/token" 7 "io" 8 9 "github.com/pkg/errors" 10 11 "golang.org/x/tools/go/callgraph" 12 "golang.org/x/tools/go/callgraph/cha" 13 "golang.org/x/tools/go/callgraph/rta" 14 "golang.org/x/tools/go/callgraph/static" 15 "golang.org/x/tools/go/ssa" 16 ) 17 18 // CallGraph is a representation of CallGraph, wrapped with metadata. 19 type CallGraph struct { 20 cg *callgraph.Graph // Internal cached copy of the callgraph. 21 edges []*cgEdge // Result of callgraph analysis. 22 prog *ssa.Program // SSA Program for which the callgraph is built from. 23 usedFns []*ssa.Function // Functions actually used by current Program. 24 allFns []*ssa.Function // Functions in the current Program (including unused). 25 } 26 27 // AllFunctions return all ssa.Functions defined in the current Program. 28 func (g *CallGraph) AllFunctions() ([]*ssa.Function, error) { 29 // If cached. 30 if g.allFns != nil { 31 return g.allFns, nil 32 } 33 34 visited := make(map[*ssa.Function]bool) 35 if err := callgraph.GraphVisitEdges(g.cg, func(edge *callgraph.Edge) error { 36 if _, ok := visited[edge.Caller.Func]; !ok { 37 visited[edge.Caller.Func] = true 38 } 39 if _, ok := visited[edge.Callee.Func]; !ok { 40 visited[edge.Callee.Func] = true 41 } 42 return nil 43 }); err != nil { 44 return nil, errors.Wrap(err, "callgraph: failed to visit edges") 45 } 46 47 for fn := range visited { 48 g.allFns = append(g.allFns, fn) 49 } 50 return g.allFns, nil 51 } 52 53 // UsedFunctions return a slice of ssa.Function actually used by the current 54 // Program, rooted at main.init() and main.main(). 55 func (g *CallGraph) UsedFunctions() ([]*ssa.Function, error) { 56 // Cached. 57 if g.usedFns != nil { 58 return g.usedFns, nil 59 } 60 61 callTree := make(map[*ssa.Function][]*ssa.Function) 62 if err := callgraph.GraphVisitEdges(g.cg, func(edge *callgraph.Edge) error { 63 callTree[edge.Caller.Func] = append(callTree[edge.Caller.Func], edge.Callee.Func) 64 return nil 65 }); err != nil { 66 return nil, errors.Wrap(err, "callgraph: failed to visit edges") 67 } 68 69 mains, err := MainPkgs(g.prog, false) 70 if err != nil { 71 return nil, errors.Wrap(err, "callgraph: failed to find main packages (Check if this this a command?)") 72 } 73 74 var fnQueue []*ssa.Function 75 for _, main := range mains { 76 if main.Func("main") != nil { 77 fnQueue = append(fnQueue, main.Func("init"), main.Func("main")) 78 } 79 } 80 81 visited := make(map[*ssa.Function]bool) 82 for len(fnQueue) > 0 { 83 headFn := fnQueue[0] 84 fnQueue = fnQueue[1:] 85 visited[headFn] = true 86 for _, fn := range callTree[headFn] { 87 if _, ok := visited[fn]; !ok { // If not visited 88 fnQueue = append(fnQueue, fn) 89 } 90 visited[fn] = true 91 } 92 } 93 94 for fn := range visited { 95 g.usedFns = append(g.usedFns, fn) 96 } 97 return g.usedFns, nil 98 } 99 100 // populateEdges populates a slice of edges in the CallGraph. 101 func (g *CallGraph) populateEdges(edge *callgraph.Edge) error { 102 e := &cgEdge{ 103 Caller: edge.Caller.Func, 104 Callee: edge.Callee.Func, 105 position: token.Position{Offset: -1}, 106 edge: edge, 107 fset: g.prog.Fset, 108 } 109 g.edges = append(g.edges, e) 110 return nil 111 } 112 113 // WriteGraphviz writes callgraph to w in graphviz dot format. 114 func (g *CallGraph) WriteGraphviz(w io.Writer) error { 115 if g.edges == nil { 116 if err := callgraph.GraphVisitEdges(g.cg, g.populateEdges); err != nil { 117 return err 118 } 119 } 120 121 bufw := bufio.NewWriter(w) 122 bufw.WriteString("digraph callgraph {\n") 123 // Instead of using template.. 124 for _, edge := range g.edges { 125 bufw.WriteString(fmt.Sprintf(" %q -> %q\n", edge.Caller, edge.Callee)) 126 } 127 bufw.WriteString("}\n") 128 bufw.Flush() 129 return nil 130 } 131 132 // cgEdge is a single edge in the callgraph. 133 // 134 // Code based on golang.org/x/tools/cmd/callgraph 135 // 136 type cgEdge struct { 137 Caller *ssa.Function 138 Callee *ssa.Function 139 140 edge *callgraph.Edge 141 fset *token.FileSet 142 position token.Position // initialized lazily 143 } 144 145 func (e *cgEdge) pos() *token.Position { 146 if e.position.Offset == -1 { 147 e.position = e.fset.Position(e.edge.Pos()) // called lazily 148 } 149 return &e.position 150 } 151 152 func (e *cgEdge) Filename() string { return e.pos().Filename } 153 func (e *cgEdge) Column() int { return e.pos().Column } 154 func (e *cgEdge) Line() int { return e.pos().Line } 155 func (e *cgEdge) Offset() int { return e.pos().Offset } 156 157 func (e *cgEdge) Dynamic() string { 158 if e.edge.Site != nil && e.edge.Site.Common().StaticCallee() == nil { 159 return "dynamic" 160 } 161 return "static" 162 } 163 164 func (e *cgEdge) Description() string { return e.edge.Description() } 165 166 // BuildCallGraph constructs a callgraph from ssa.Info. 167 // algo is algorithm available in golang.org/x/tools/go/callgraph, which 168 // includes: 169 // - static static calls only (unsound) 170 // - cha Class Hierarchy Analysis 171 // - rta Rapid Type Analysis 172 // - pta inclusion-based Points-To Analysis 173 // 174 func (info *Info) BuildCallGraph(algo string, tests bool) (*CallGraph, error) { 175 var cg *callgraph.Graph 176 switch algo { 177 case "static": 178 cg = static.CallGraph(info.Prog) 179 180 case "cha": 181 cg = cha.CallGraph(info.Prog) 182 183 case "pta": 184 ptrCfg, err := info.PtrAnlysCfg(tests) 185 if err != nil { 186 return nil, err 187 } 188 ptrCfg.BuildCallGraph = true 189 ptares, err := info.RunPtrAnlys(ptrCfg) 190 if err != nil { 191 return nil, err // internal error in pointer analysis 192 } 193 cg = ptares.CallGraph 194 195 case "rta": 196 mains, err := MainPkgs(info.Prog, tests) 197 if err != nil { 198 return nil, err 199 } 200 var roots []*ssa.Function 201 for _, main := range mains { 202 roots = append(roots, main.Func("init"), main.Func("main")) 203 } 204 rtares := rta.Analyze(roots, true) 205 cg = rtares.CallGraph 206 } 207 208 cg.DeleteSyntheticNodes() 209 210 return &CallGraph{cg: cg, prog: info.Prog}, nil 211 }