// Callstack displays an arbitrary path from a root of the callgraph // to the function at the current position. // // The information may be misleading in a context-insensitive // analysis. e.g. the call path X->Y->Z might be infeasible if Y never // calls Z when it is called from X. TODO(adonovan): think about UI. // // TODO(adonovan): permit user to specify a starting point other than // the analysis root. // func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) { pkg := o.prog.Package(qpos.info.Pkg) if pkg == nil { return nil, fmt.Errorf("no SSA package") } if !ssa.HasEnclosingFunction(pkg, qpos.path) { return nil, fmt.Errorf("this position is not inside a function") } buildSSA(o) target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { return nil, fmt.Errorf("no SSA function built for this location (dead code?)") } // Run the pointer analysis and build the complete call graph. o.config.BuildCallGraph = true callgraph := ptrAnalysis(o).CallGraph // Search for an arbitrary path from a root to the target function. isEnd := func(n call.GraphNode) bool { return n.Func() == target } callpath := call.PathSearch(callgraph.Root(), isEnd) if callpath != nil { callpath = callpath[1:] // remove synthetic edge from <root> } return &callstackResult{ qpos: qpos, target: target, callpath: callpath, }, nil }
// Callers reports the possible callers of the function // immediately enclosing the specified source location. // // TODO(adonovan): if a caller is a wrapper, show the caller's caller. // func callers(o *Oracle, qpos *QueryPos) (queryResult, error) { pkg := o.prog.Package(qpos.info.Pkg) if pkg == nil { return nil, fmt.Errorf("no SSA package") } if !ssa.HasEnclosingFunction(pkg, qpos.path) { return nil, fmt.Errorf("this position is not inside a function") } buildSSA(o) target := ssa.EnclosingFunction(pkg, qpos.path) if target == nil { return nil, fmt.Errorf("no SSA function built for this location (dead code?)") } // Run the pointer analysis, recording each // call found to originate from target. o.config.BuildCallGraph = true callgraph := ptrAnalysis(o).CallGraph var edges []call.Edge call.GraphVisitEdges(callgraph, func(edge call.Edge) error { if edge.Callee.Func() == target { edges = append(edges, edge) } return nil }) // TODO(adonovan): sort + dedup calls to ensure test determinism. return &callersResult{ target: target, callgraph: callgraph, edges: edges, }, nil }
// Callees reports the possible callees of the function call site // identified by the specified source location. // // TODO(adonovan): if a callee is a wrapper, show the callee's callee. // func callees(o *Oracle, qpos *QueryPos) (queryResult, error) { pkg := o.prog.Package(qpos.info.Pkg) if pkg == nil { return nil, fmt.Errorf("no SSA package") } // 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 nil, 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.IsType(e.Fun) { return nil, fmt.Errorf("this is a type conversion, not a function call") } // Reject calls to built-ins. if id, ok := unparen(e.Fun).(*ast.Ident); ok { if b, ok := qpos.info.ObjectOf(id).(*types.Builtin); ok { return nil, fmt.Errorf("this is a call to the built-in '%s' operator", b.Name()) } } buildSSA(o) // Ascertain calling function and call site. callerFn := ssa.EnclosingFunction(pkg, qpos.path) if callerFn == nil { return nil, fmt.Errorf("no SSA function built for this location (dead code?)") } // Find the call site. site, err := findCallSite(callerFn, e.Lparen) if err != nil { return nil, err } funcs, err := findCallees(o, site) if err != nil { return nil, err } return &calleesResult{ site: site, funcs: funcs, }, nil }
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident // expression whose path to the root of the AST is path. It may // return a nil Value without an error to indicate the pointer // analysis is not appropriate. // func ssaValueForExpr(prog *ssa.Program, qinfo *importer.PackageInfo, path []ast.Node) (ssa.Value, error) { pkg := prog.Package(qinfo.Pkg) pkg.SetDebugMode(true) pkg.Build() fn := ssa.EnclosingFunction(pkg, path) if fn == nil { return nil, fmt.Errorf("no SSA function built for this location (dead code?)") } if v := fn.ValueForExpr(path[0].(ast.Expr)); v != nil { return v, nil } return nil, fmt.Errorf("can't locate SSA Value for expression in %s", fn) }
// TODO(adonovan): move this test into ssa. func TestEnclosingFunction(t *testing.T) { tests := []struct { input string // the input file substr string // first occurrence of this string denotes interval fn string // name of expected containing function }{ // We use distinctive numbers as syntactic landmarks. // Ordinary function: {`package main func f() { println(1003) }`, "100", "main.f"}, // Methods: {`package main type T int func (t T) f() { println(200) }`, "200", "(main.T).f"}, // Function literal: {`package main func f() { println(func() { print(300) }) }`, "300", "[email protected]"}, // Doubly nested {`package main func f() { println(func() { print(func() { print(350) })})}`, "350", "[email protected]"}, // Implicit init for package-level var initializer. {"package main; var a = 400", "400", "main.init"}, // No code for constants: {"package main; const a = 500", "500", "(none)"}, // Explicit init() {"package main; func init() { println(600) }", "600", "main.init$1"}, // Multiple explicit init functions: {`package main func init() { println("foo") } func init() { println(800) }`, "800", "main.init$2"}, // init() containing FuncLit. {`package main func init() { println(func(){print(900)}) }`, "900", "[email protected]"}, } for _, test := range tests { imp := importer.New(new(importer.Config)) // (NB: no go/build.Config) f, start, end := findInterval(t, imp.Fset, test.input, test.substr) if f == nil { continue } path, exact := importer.PathEnclosingInterval(f, start, end) if !exact { t.Errorf("EnclosingFunction(%q) not exact", test.substr) continue } mainInfo := imp.CreatePackage("main", f) prog := ssa.NewProgram(imp.Fset, 0) if err := prog.CreatePackages(imp); err != nil { t.Error(err) continue } pkg := prog.Package(mainInfo.Pkg) pkg.Build() name := "(none)" fn := ssa.EnclosingFunction(pkg, path) if fn != nil { name = fn.String() } if name != test.fn { t.Errorf("EnclosingFunction(%q in %q) got %s, want %s", test.substr, test.input, name, test.fn) continue } // While we're here: test HasEnclosingFunction. if has := ssa.HasEnclosingFunction(pkg, path); has != (fn != nil) { t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v", test.substr, test.input, has, fn != nil) continue } } }
// Ensure that, in debug mode, we can determine the ssa.Value // corresponding to every ast.Expr. func TestValueForExpr(t *testing.T) { imp := importer.New(new(importer.Config)) // (uses GCImporter) f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil, parser.ParseComments) if err != nil { t.Error(err) return } mainInfo := imp.CreatePackage("main", f) prog := ssa.NewProgram(imp.Fset, 0) if err := prog.CreatePackages(imp); err != nil { t.Error(err) return } mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() if false { // debugging for _, mem := range mainPkg.Members { if fn, ok := mem.(*ssa.Function); ok { fn.DumpTo(os.Stderr) } } } // Find the actual AST node for each canonical position. parenExprByPos := make(map[token.Pos]*ast.ParenExpr) ast.Inspect(f, func(n ast.Node) bool { if n != nil { if e, ok := n.(*ast.ParenExpr); ok { parenExprByPos[e.Pos()] = e } } return true }) // Find all annotations of form /*@kind*/. for _, c := range f.Comments { text := strings.TrimSpace(c.Text()) if text == "" || text[0] != '@' { continue } text = text[1:] pos := c.End() + 1 position := imp.Fset.Position(pos) var e ast.Expr if target := parenExprByPos[pos]; target == nil { t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text) continue } else { e = target.X } path, _ := importer.PathEnclosingInterval(f, pos, pos) if path == nil { t.Errorf("%s: can't find AST path from root to comment: %s", position, text) continue } fn := ssa.EnclosingFunction(mainPkg, path) if fn == nil { t.Errorf("%s: can't find enclosing function", position) continue } v, gotAddr := fn.ValueForExpr(e) // (may be nil) got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa.") if want := text; got != want { t.Errorf("%s: got value %q, want %q", position, got, want) } if v != nil { T := v.Type() if gotAddr { T = T.Underlying().(*types.Pointer).Elem() // deref } if !types.IsIdentical(T, mainInfo.TypeOf(e)) { t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T) } } } }
// Callees reports the possible callees of the function call site // identified by the specified source location. // // TODO(adonovan): if a callee is a wrapper, show the callee's callee. // func callees(o *Oracle, qpos *QueryPos) (queryResult, error) { pkg := o.prog.Package(qpos.info.Pkg) if pkg == nil { return nil, fmt.Errorf("no SSA package") } // 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 nil, 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.IsType(e.Fun) { return nil, fmt.Errorf("this is a type conversion, not a function call") } // Reject calls to built-ins. if id, ok := unparen(e.Fun).(*ast.Ident); ok { if b, ok := qpos.info.ObjectOf(id).(*types.Builtin); ok { return nil, fmt.Errorf("this is a call to the built-in '%s' operator", b.Name()) } } buildSSA(o) // Ascertain calling function and call site. callerFn := ssa.EnclosingFunction(pkg, qpos.path) if callerFn == nil { return nil, fmt.Errorf("no SSA function built for this location (dead code?)") } o.config.BuildCallGraph = true callgraph := ptrAnalysis(o).CallGraph // Find the call site and all edges from it. var site ssa.CallInstruction calleesMap := make(map[*ssa.Function]bool) for _, n := range callgraph.Nodes() { if n.Func() == callerFn { if site == nil { // First node for callerFn: identify the site. for _, s := range n.Sites() { if s.Pos() == e.Lparen { site = s break } } if site == nil { return nil, fmt.Errorf("this call site is unreachable in this analysis") } } for _, edge := range n.Edges() { if edge.Site == site { calleesMap[edge.Callee.Func()] = true } } } } if site == nil { return nil, fmt.Errorf("this function is unreachable in this analysis") } // Discard context, de-duplicate and sort. funcs := make([]*ssa.Function, 0, len(calleesMap)) for f := range calleesMap { funcs = append(funcs, f) } sort.Sort(byFuncPos(funcs)) return &calleesResult{ site: site, funcs: funcs, }, nil }