blob: 597895770ae7f5e494737a0b53d589d35d06c220 [file] [log] [blame]
// 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 main
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"sort"
"golang.org/x/tools/cmd/guru/serial"
"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"
)
// The callees function reports the possible callees of the function call site
// identified by the specified source location.
func callees(q *Query) error {
lconf := loader.Config{Build: q.Build}
if err := setPTAScope(&lconf, q.Scope); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := loadWithSoftErrors(&lconf)
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
if err != nil {
return err
}
// Determine the enclosing call for the specified position.
var e *ast.CallExpr
for _, n := range qpos.path {
if e, _ = n.(*ast.CallExpr); e != nil {
break
}
}
if e == nil {
return fmt.Errorf("there is no function call here")
}
// TODO(adonovan): issue an error if the call is "too far
// away" from the current selection, as this most likely is
// not what the user intended.
// Reject type conversions.
if qpos.info.Types[e.Fun].IsType() {
return fmt.Errorf("this is a type conversion, not a function call")
}
// Deal with obviously static calls before constructing SSA form.
// Some static calls may yet require SSA construction,
// e.g. f := func(){}; f().
switch funexpr := unparen(e.Fun).(type) {
case *ast.Ident:
switch obj := qpos.info.Uses[funexpr].(type) {
case *types.Builtin:
// Reject calls to built-ins.
return fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name())
case *types.Func:
// This is a static function call
q.Output(lprog.Fset, &calleesTypesResult{
site: e,
callee: obj,
})
return nil
}
case *ast.SelectorExpr:
sel := qpos.info.Selections[funexpr]
if sel == nil {
// qualified identifier.
// May refer to top level function variable
// or to top level function.
callee := qpos.info.Uses[funexpr.Sel]
if obj, ok := callee.(*types.Func); ok {
q.Output(lprog.Fset, &calleesTypesResult{
site: e,
callee: obj,
})
return nil
}
} else if sel.Kind() == types.MethodVal {
// Inspect the receiver type of the selected method.
// If it is concrete, the call is statically dispatched.
// (Due to implicit field selections, it is not enough to look
// at sel.Recv(), the type of the actual receiver expression.)
method := sel.Obj().(*types.Func)
recvtype := method.Type().(*types.Signature).Recv().Type()
if !types.IsInterface(recvtype) {
// static method call
q.Output(lprog.Fset, &calleesTypesResult{
site: e,
callee: method,
})
return nil
}
}
}
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
pkg := prog.Package(qpos.info.Pkg)
if pkg == nil {
return fmt.Errorf("no SSA package")
}
// Defer SSA construction till after errors are reported.
prog.Build()
// Ascertain calling function and call site.
callerFn := ssa.EnclosingFunction(pkg, qpos.path)
if callerFn == nil {
return fmt.Errorf("no SSA function built for this location (dead code?)")
}
// Find the call site.
site, err := findCallSite(callerFn, e)
if err != nil {
return err
}
funcs, err := findCallees(ptaConfig, site)
if err != nil {
return err
}
q.Output(lprog.Fset, &calleesSSAResult{
site: site,
funcs: funcs,
})
return nil
}
func findCallSite(fn *ssa.Function, call *ast.CallExpr) (ssa.CallInstruction, error) {
instr, _ := fn.ValueForExpr(call)
callInstr, _ := instr.(ssa.CallInstruction)
if instr == nil {
return nil, fmt.Errorf("this call site is unreachable in this analysis")
}
return callInstr, nil
}
func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) {
// Avoid running the pointer analysis for static calls.
if callee := site.Common().StaticCallee(); callee != nil {
switch callee.String() {
case "runtime.SetFinalizer", "(reflect.Value).Call":
// The PTA treats calls to these intrinsics as dynamic.
// TODO(adonovan): avoid reliance on PTA internals.
default:
return []*ssa.Function{callee}, nil // singleton
}
}
// Dynamic call: use pointer analysis.
conf.BuildCallGraph = true
cg := ptrAnalysis(conf).CallGraph
cg.DeleteSyntheticNodes()
// Find all call edges from the site.
n := cg.Nodes[site.Parent()]
if n == nil {
return nil, fmt.Errorf("this call site is unreachable in this analysis")
}
calleesMap := make(map[*ssa.Function]bool)
for _, edge := range n.Out {
if edge.Site == site {
calleesMap[edge.Callee.Func] = true
}
}
// De-duplicate and sort.
funcs := make([]*ssa.Function, 0, len(calleesMap))
for f := range calleesMap {
funcs = append(funcs, f)
}
sort.Sort(byFuncPos(funcs))
return funcs, nil
}
type calleesSSAResult struct {
site ssa.CallInstruction
funcs []*ssa.Function
}
type calleesTypesResult struct {
site *ast.CallExpr
callee *types.Func
}
func (r *calleesSSAResult) PrintPlain(printf printfFunc) {
if len(r.funcs) == 0 {
// dynamic call on a provably nil func/interface
printf(r.site, "%s on nil value", r.site.Common().Description())
} else {
printf(r.site, "this %s dispatches to:", r.site.Common().Description())
for _, callee := range r.funcs {
printf(callee, "\t%s", callee)
}
}
}
func (r *calleesSSAResult) JSON(fset *token.FileSet) []byte {
j := &serial.Callees{
Pos: fset.Position(r.site.Pos()).String(),
Desc: r.site.Common().Description(),
}
for _, callee := range r.funcs {
j.Callees = append(j.Callees, &serial.Callee{
Name: callee.String(),
Pos: fset.Position(callee.Pos()).String(),
})
}
return toJSON(j)
}
func (r *calleesTypesResult) PrintPlain(printf printfFunc) {
printf(r.site, "this static function call dispatches to:")
printf(r.callee, "\t%s", r.callee.FullName())
}
func (r *calleesTypesResult) JSON(fset *token.FileSet) []byte {
j := &serial.Callees{
Pos: fset.Position(r.site.Pos()).String(),
Desc: "static function call",
}
j.Callees = []*serial.Callee{
{
Name: r.callee.FullName(),
Pos: fset.Position(r.callee.Pos()).String(),
},
}
return toJSON(j)
}
// NB: byFuncPos is not deterministic across packages since it depends on load order.
// Use lessPos if the tests need it.
type byFuncPos []*ssa.Function
func (a byFuncPos) Len() int { return len(a) }
func (a byFuncPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
func (a byFuncPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }