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