| // 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/token" | |
| "golang.org/x/tools/cmd/guru/serial" | |
| "golang.org/x/tools/go/callgraph" | |
| "golang.org/x/tools/go/callgraph/static" | |
| "golang.org/x/tools/go/loader" | |
| "golang.org/x/tools/go/ssa" | |
| "golang.org/x/tools/go/ssa/ssautil" | |
| ) | |
| // The callstack function 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(q *Query) error { | |
| fset := token.NewFileSet() | |
| lconf := loader.Config{Fset: fset, 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, false) | |
| if err != nil { | |
| return err | |
| } | |
| prog := ssautil.CreateProgram(lprog, 0) | |
| 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") | |
| } | |
| if !ssa.HasEnclosingFunction(pkg, qpos.path) { | |
| return fmt.Errorf("this position is not inside a function") | |
| } | |
| // Defer SSA construction till after errors are reported. | |
| prog.Build() | |
| target := ssa.EnclosingFunction(pkg, qpos.path) | |
| if target == nil { | |
| return fmt.Errorf("no SSA function built for this location (dead code?)") | |
| } | |
| var callpath []*callgraph.Edge | |
| isEnd := func(n *callgraph.Node) bool { return n.Func == target } | |
| // First, build a callgraph containing only static call edges, | |
| // and search for an arbitrary path from a root to the target function. | |
| // This is quick, and the user wants a static path if one exists. | |
| cg := static.CallGraph(prog) | |
| cg.DeleteSyntheticNodes() | |
| for _, ep := range entryPoints(ptaConfig.Mains) { | |
| callpath = callgraph.PathSearch(cg.CreateNode(ep), isEnd) | |
| if callpath != nil { | |
| break | |
| } | |
| } | |
| // No fully static path found. | |
| // Run the pointer analysis and build a complete call graph. | |
| if callpath == nil { | |
| ptaConfig.BuildCallGraph = true | |
| cg := ptrAnalysis(ptaConfig).CallGraph | |
| cg.DeleteSyntheticNodes() | |
| callpath = callgraph.PathSearch(cg.Root, isEnd) | |
| if callpath != nil { | |
| callpath = callpath[1:] // remove synthetic edge from <root> | |
| } | |
| } | |
| q.Output(fset, &callstackResult{ | |
| qpos: qpos, | |
| target: target, | |
| callpath: callpath, | |
| }) | |
| return nil | |
| } | |
| type callstackResult struct { | |
| qpos *queryPos | |
| target *ssa.Function | |
| callpath []*callgraph.Edge | |
| } | |
| func (r *callstackResult) PrintPlain(printf printfFunc) { | |
| if r.callpath != nil { | |
| printf(r.qpos, "Found a call path from root to %s", r.target) | |
| printf(r.target, "%s", r.target) | |
| for i := len(r.callpath) - 1; i >= 0; i-- { | |
| edge := r.callpath[i] | |
| printf(edge, "%s from %s", edge.Description(), edge.Caller.Func) | |
| } | |
| } else { | |
| printf(r.target, "%s is unreachable in this analysis scope", r.target) | |
| } | |
| } | |
| func (r *callstackResult) JSON(fset *token.FileSet) []byte { | |
| var callers []serial.Caller | |
| for i := len(r.callpath) - 1; i >= 0; i-- { // (innermost first) | |
| edge := r.callpath[i] | |
| callers = append(callers, serial.Caller{ | |
| Pos: fset.Position(edge.Pos()).String(), | |
| Caller: edge.Caller.Func.String(), | |
| Desc: edge.Description(), | |
| }) | |
| } | |
| return toJSON(&serial.CallStack{ | |
| Pos: fset.Position(r.target.Pos()).String(), | |
| Target: r.target.String(), | |
| Callers: callers, | |
| }) | |
| } |