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