github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/cmd/guru/callees.go (about)

     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  
     5  package main
     6  
     7  import (
     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.
    23  func callees(q *Query) error {
    24  	lconf := loader.Config{Build: q.Build}
    25  
    26  	if err := setPTAScope(&lconf, q.Scope); err != nil {
    27  		return err
    28  	}
    29  
    30  	// Load/parse/type-check the program.
    31  	lprog, err := loadWithSoftErrors(&lconf)
    32  	if err != nil {
    33  		return err
    34  	}
    35  
    36  	qpos, err := parseQueryPos(lprog, q.Pos, true) // 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  				callee: obj,
    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 obj, ok := callee.(*types.Func); ok {
    85  				q.Output(lprog.Fset, &calleesTypesResult{
    86  					site:   e,
    87  					callee: obj,
    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  					callee: method,
   103  				})
   104  				return nil
   105  			}
   106  		}
   107  	}
   108  
   109  	prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
   110  
   111  	ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.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(pkg, qpos.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  	site, err := findCallSite(callerFn, e)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	funcs, err := findCallees(ptaConfig, site)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	q.Output(lprog.Fset, &calleesSSAResult{
   142  		site:  site,
   143  		funcs: funcs,
   144  	})
   145  	return nil
   146  }
   147  
   148  func findCallSite(fn *ssa.Function, call *ast.CallExpr) (ssa.CallInstruction, error) {
   149  	instr, _ := fn.ValueForExpr(call)
   150  	callInstr, _ := instr.(ssa.CallInstruction)
   151  	if instr == nil {
   152  		return nil, fmt.Errorf("this call site is unreachable in this analysis")
   153  	}
   154  	return callInstr, nil
   155  }
   156  
   157  func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) {
   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 nil, fmt.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.Function, 0, len(calleesMap))
   189  	for f := range calleesMap {
   190  		funcs = append(funcs, f)
   191  	}
   192  	sort.Sort(byFuncPos(funcs))
   193  	return funcs, nil
   194  }
   195  
   196  type calleesSSAResult struct {
   197  	site  ssa.CallInstruction
   198  	funcs []*ssa.Function
   199  }
   200  
   201  type calleesTypesResult struct {
   202  	site   *ast.CallExpr
   203  	callee *types.Func
   204  }
   205  
   206  func (r *calleesSSAResult) PrintPlain(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  
   218  func (r *calleesSSAResult) JSON(fset *token.FileSet) []byte {
   219  	j := &serial.Callees{
   220  		Pos:  fset.Position(r.site.Pos()).String(),
   221  		Desc: r.site.Common().Description(),
   222  	}
   223  	for _, callee := range r.funcs {
   224  		j.Callees = append(j.Callees, &serial.Callee{
   225  			Name: callee.String(),
   226  			Pos:  fset.Position(callee.Pos()).String(),
   227  		})
   228  	}
   229  	return toJSON(j)
   230  }
   231  
   232  func (r *calleesTypesResult) PrintPlain(printf printfFunc) {
   233  	printf(r.site, "this static function call dispatches to:")
   234  	printf(r.callee, "\t%s", r.callee.FullName())
   235  }
   236  
   237  func (r *calleesTypesResult) JSON(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  			Name: r.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.
   253  type byFuncPos []*ssa.Function
   254  
   255  func (a byFuncPos) Len() int           { return len(a) }
   256  func (a byFuncPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
   257  func (a byFuncPos) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }