GoPLS Viewer

Home|gopls/cmd/guru/callees.go
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
5package main
6
7import (
8    "fmt"
9    "go/ast"
10    "go/token"
11    "go/types"
12    "sort"
13
14    "golang.org/x/tools/cmd/guru/serial"
15    "golang.org/x/tools/go/loader"
16    "golang.org/x/tools/go/pointer"
17    "golang.org/x/tools/go/ssa"
18    "golang.org/x/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.
23func callees(q *Queryerror {
24    lconf := loader.Config{Buildq.Build}
25
26    if err := setPTAScope(&lconfq.Scope); err != nil {
27        return err
28    }
29
30    // Load/parse/type-check the program.
31    lprogerr := loadWithSoftErrors(&lconf)
32    if err != nil {
33        return err
34    }
35
36    qposerr := parseQueryPos(lprogq.Postrue// 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                calleeobj,
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 objok := callee.(*types.Func); ok {
85                q.Output(lprog.Fset, &calleesTypesResult{
86                    site:   e,
87                    calleeobj,
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                    calleemethod,
103                })
104                return nil
105            }
106        }
107    }
108
109    prog := ssautil.CreateProgram(lprogssa.GlobalDebug)
110
111    ptaConfigerr := setupPTA(proglprogq.PTALogq.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(pkgqpos.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    siteerr := findCallSite(callerFne)
132    if err != nil {
133        return err
134    }
135
136    funcserr := findCallees(ptaConfigsite)
137    if err != nil {
138        return err
139    }
140
141    q.Output(lprog.Fset, &calleesSSAResult{
142        site:  site,
143        funcsfuncs,
144    })
145    return nil
146}
147
148func findCallSite(fn *ssa.Functioncall *ast.CallExpr) (ssa.CallInstructionerror) {
149    instr_ := fn.ValueForExpr(call)
150    callInstr_ := instr.(ssa.CallInstruction)
151    if instr == nil {
152        return nilfmt.Errorf("this call site is unreachable in this analysis")
153    }
154    return callInstrnil
155}
156
157func findCallees(conf *pointer.Configsite ssa.CallInstruction) ([]*ssa.Functionerror) {
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 nilfmt.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.Function0len(calleesMap))
189    for f := range calleesMap {
190        funcs = append(funcsf)
191    }
192    sort.Sort(byFuncPos(funcs))
193    return funcsnil
194}
195
196type calleesSSAResult struct {
197    site  ssa.CallInstruction
198    funcs []*ssa.Function
199}
200
201type calleesTypesResult struct {
202    site   *ast.CallExpr
203    callee *types.Func
204}
205
206func (r *calleesSSAResultPrintPlain(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
218func (r *calleesSSAResultJSON(fset *token.FileSet) []byte {
219    j := &serial.Callees{
220        Pos:  fset.Position(r.site.Pos()).String(),
221        Descr.site.Common().Description(),
222    }
223    for _callee := range r.funcs {
224        j.Callees = append(j.Callees, &serial.Callee{
225            Namecallee.String(),
226            Pos:  fset.Position(callee.Pos()).String(),
227        })
228    }
229    return toJSON(j)
230}
231
232func (r *calleesTypesResultPrintPlain(printf printfFunc) {
233    printf(r.site"this static function call dispatches to:")
234    printf(r.callee"\t%s"r.callee.FullName())
235}
236
237func (r *calleesTypesResultJSON(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            Namer.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.
253type byFuncPos []*ssa.Function
254
255func (a byFuncPosLen() int           { return len(a) }
256func (a byFuncPosLess(ij intbool { return a[i].Pos() < a[j].Pos() }
257func (a byFuncPosSwap(ij int)      { a[i], a[j] = a[j], a[i] }
258
MembersX
calleesTypesResult.JSON.fset
byFuncPos.Less.j
fmt
callees.lprog
callees.prog
findCallSite.instr
findCallees.site
calleesSSAResult.site
callees.q
findCallSite._
findCallees.calleesMap
calleesSSAResult.funcs
calleesTypesResult.JSON
byFuncPos.Len
callees.RangeStmt_975.n
callees.ptaConfig
findCallees.callee
findCallees.RangeStmt_4835.edge
calleesSSAResult.PrintPlain.printf
calleesTypesResult.PrintPlain
callees.e
findCallSite.call
calleesTypesResult.callee
calleesSSAResult.JSON.r
token
ssa
callees.BlockStmt.BlockStmt.recvtype
calleesTypesResult.PrintPlain.printf
byFuncPos.Less.i
callees
calleesTypesResult.site
calleesSSAResult.JSON.RangeStmt_5825.callee
byFuncPos
byFuncPos.Len.a
calleesTypesResult.PrintPlain.r
calleesTypesResult.JSON.r
loader
ssautil
findCallees.RangeStmt_5016.f
byFuncPos.Swap.i
byFuncPos.Swap.j
callees.callerFn
byFuncPos.Less
sort
callees.funcs
findCallSite
byFuncPos.Swap
types
pointer
findCallees.cg
calleesSSAResult.PrintPlain.BlockStmt.RangeStmt_5574.callee
byFuncPos.Less.a
ast
callees.lconf
serial
findCallSite.fn
calleesSSAResult
callees.qpos
calleesTypesResult.JSON.j
callees.err
callees.pkg
findCallees
calleesSSAResult.PrintPlain
byFuncPos.Swap.a
calleesSSAResult.JSON.fset
calleesSSAResult.JSON.j
callees.site
findCallees.conf
findCallees.funcs
calleesTypesResult
calleesSSAResult.PrintPlain.r
calleesSSAResult.JSON
Members
X