|
|
// 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" )
// Callees 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] }
|