github.com/april1989/origin-go-tools@v0.0.32/cmd/guru/callees.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "fmt" 9 "go/ast" 10 "go/token" 11 "go/types" 12 "sort" 13 14 "github.com/april1989/origin-go-tools/cmd/guru/serial" 15 "github.com/april1989/origin-go-tools/go/loader" 16 "github.com/april1989/origin-go-tools/go/pointer" 17 "github.com/april1989/origin-go-tools/go/ssa" 18 "github.com/april1989/origin-go-tools/go/ssa/ssautil" 19 ) 20 21 // The callees function reports the possible callees of the function call site 22 // identified by the specified source location. 23 func callees(q *Query) error { 24 lconf := loader.Config{Build: q.Build} 25 26 if err := setPTAScope(&lconf, q.Scope); err != nil { 27 return err 28 } 29 30 // Load/parse/type-check the program. 31 lprog, err := loadWithSoftErrors(&lconf) 32 if err != nil { 33 return err 34 } 35 36 qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos 37 if err != nil { 38 return err 39 } 40 41 // Determine the enclosing call for the specified position. 42 var e *ast.CallExpr 43 for _, n := range qpos.path { 44 if e, _ = n.(*ast.CallExpr); e != nil { 45 break 46 } 47 } 48 if e == nil { 49 return fmt.Errorf("there is no function call here") 50 } 51 // TODO(adonovan): issue an error if the call is "too far 52 // away" from the current selection, as this most likely is 53 // not what the user intended. 54 55 // Reject type conversions. 56 if qpos.info.Types[e.Fun].IsType() { 57 return fmt.Errorf("this is a type conversion, not a function call") 58 } 59 60 // Deal with obviously static calls before constructing SSA form. 61 // Some static calls may yet require SSA construction, 62 // e.g. f := func(){}; f(). 63 switch funexpr := unparen(e.Fun).(type) { 64 case *ast.Ident: 65 switch obj := qpos.info.Uses[funexpr].(type) { 66 case *types.Builtin: 67 // Reject calls to built-ins. 68 return fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name()) 69 case *types.Func: 70 // This is a static function call 71 q.Output(lprog.Fset, &calleesTypesResult{ 72 site: e, 73 callee: obj, 74 }) 75 return nil 76 } 77 case *ast.SelectorExpr: 78 sel := qpos.info.Selections[funexpr] 79 if sel == nil { 80 // qualified identifier. 81 // May refer to top level function variable 82 // or to top level function. 83 callee := qpos.info.Uses[funexpr.Sel] 84 if obj, ok := callee.(*types.Func); ok { 85 q.Output(lprog.Fset, &calleesTypesResult{ 86 site: e, 87 callee: obj, 88 }) 89 return nil 90 } 91 } else if sel.Kind() == types.MethodVal { 92 // Inspect the receiver type of the selected method. 93 // If it is concrete, the call is statically dispatched. 94 // (Due to implicit field selections, it is not enough to look 95 // at sel.Recv(), the type of the actual receiver expression.) 96 method := sel.Obj().(*types.Func) 97 recvtype := method.Type().(*types.Signature).Recv().Type() 98 if !types.IsInterface(recvtype) { 99 // static method call 100 q.Output(lprog.Fset, &calleesTypesResult{ 101 site: e, 102 callee: method, 103 }) 104 return nil 105 } 106 } 107 } 108 109 prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) 110 111 ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) 112 if err != nil { 113 return err 114 } 115 116 pkg := prog.Package(qpos.info.Pkg) 117 if pkg == nil { 118 return fmt.Errorf("no SSA package") 119 } 120 121 // Defer SSA construction till after errors are reported. 122 prog.Build() 123 124 // Ascertain calling function and call site. 125 callerFn := ssa.EnclosingFunction(pkg, qpos.path) 126 if callerFn == nil { 127 return fmt.Errorf("no SSA function built for this location (dead code?)") 128 } 129 130 // Find the call site. 131 site, err := findCallSite(callerFn, e) 132 if err != nil { 133 return err 134 } 135 136 funcs, err := findCallees(ptaConfig, site) 137 if err != nil { 138 return err 139 } 140 141 q.Output(lprog.Fset, &calleesSSAResult{ 142 site: site, 143 funcs: funcs, 144 }) 145 return nil 146 } 147 148 func findCallSite(fn *ssa.Function, call *ast.CallExpr) (ssa.CallInstruction, error) { 149 instr, _ := fn.ValueForExpr(call) 150 callInstr, _ := instr.(ssa.CallInstruction) 151 if instr == nil { 152 return nil, fmt.Errorf("this call site is unreachable in this analysis") 153 } 154 return callInstr, nil 155 } 156 157 func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) { 158 // Avoid running the pointer analysis for static calls. 159 if callee := site.Common().StaticCallee(); callee != nil { 160 switch callee.String() { 161 case "runtime.SetFinalizer", "(reflect.Value).Call": 162 // The PTA treats calls to these intrinsics as dynamic. 163 // TODO(adonovan): avoid reliance on PTA internals. 164 165 default: 166 return []*ssa.Function{callee}, nil // singleton 167 } 168 } 169 170 // Dynamic call: use pointer analysis. 171 conf.BuildCallGraph = true 172 cg := ptrAnalysis(conf).CallGraph 173 cg.DeleteSyntheticNodes() 174 175 // Find all call edges from the site. 176 n := cg.Nodes[site.Parent()] 177 if n == nil { 178 return nil, fmt.Errorf("this call site is unreachable in this analysis") 179 } 180 calleesMap := make(map[*ssa.Function]bool) 181 for _, edge := range n.Out { 182 if edge.Site == site { 183 calleesMap[edge.Callee.Func] = true 184 } 185 } 186 187 // De-duplicate and sort. 188 funcs := make([]*ssa.Function, 0, len(calleesMap)) 189 for f := range calleesMap { 190 funcs = append(funcs, f) 191 } 192 sort.Sort(byFuncPos(funcs)) 193 return funcs, nil 194 } 195 196 type calleesSSAResult struct { 197 site ssa.CallInstruction 198 funcs []*ssa.Function 199 } 200 201 type calleesTypesResult struct { 202 site *ast.CallExpr 203 callee *types.Func 204 } 205 206 func (r *calleesSSAResult) PrintPlain(printf printfFunc) { 207 if len(r.funcs) == 0 { 208 // dynamic call on a provably nil func/interface 209 printf(r.site, "%s on nil value", r.site.Common().Description()) 210 } else { 211 printf(r.site, "this %s dispatches to:", r.site.Common().Description()) 212 for _, callee := range r.funcs { 213 printf(callee, "\t%s", callee) 214 } 215 } 216 } 217 218 func (r *calleesSSAResult) JSON(fset *token.FileSet) []byte { 219 j := &serial.Callees{ 220 Pos: fset.Position(r.site.Pos()).String(), 221 Desc: r.site.Common().Description(), 222 } 223 for _, callee := range r.funcs { 224 j.Callees = append(j.Callees, &serial.Callee{ 225 Name: callee.String(), 226 Pos: fset.Position(callee.Pos()).String(), 227 }) 228 } 229 return toJSON(j) 230 } 231 232 func (r *calleesTypesResult) PrintPlain(printf printfFunc) { 233 printf(r.site, "this static function call dispatches to:") 234 printf(r.callee, "\t%s", r.callee.FullName()) 235 } 236 237 func (r *calleesTypesResult) JSON(fset *token.FileSet) []byte { 238 j := &serial.Callees{ 239 Pos: fset.Position(r.site.Pos()).String(), 240 Desc: "static function call", 241 } 242 j.Callees = []*serial.Callee{ 243 { 244 Name: r.callee.FullName(), 245 Pos: fset.Position(r.callee.Pos()).String(), 246 }, 247 } 248 return toJSON(j) 249 } 250 251 // NB: byFuncPos is not deterministic across packages since it depends on load order. 252 // Use lessPos if the tests need it. 253 type byFuncPos []*ssa.Function 254 255 func (a byFuncPos) Len() int { return len(a) } 256 func (a byFuncPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() } 257 func (a byFuncPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }