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