--- /dev/null
+// 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] }