github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/cmd/guru/callstack.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/token" 10 11 "golang.org/x/tools/cmd/guru/serial" 12 "golang.org/x/tools/go/callgraph" 13 "golang.org/x/tools/go/callgraph/static" 14 "golang.org/x/tools/go/loader" 15 "golang.org/x/tools/go/ssa" 16 "golang.org/x/tools/go/ssa/ssautil" 17 ) 18 19 // The callstack function displays an arbitrary path from a root of the callgraph 20 // to the function at the current position. 21 // 22 // The information may be misleading in a context-insensitive 23 // analysis. e.g. the call path X->Y->Z might be infeasible if Y never 24 // calls Z when it is called from X. TODO(adonovan): think about UI. 25 // 26 // TODO(adonovan): permit user to specify a starting point other than 27 // the analysis root. 28 func callstack(q *Query) error { 29 fset := token.NewFileSet() 30 lconf := loader.Config{Fset: fset, Build: q.Build} 31 32 if err := setPTAScope(&lconf, q.Scope); err != nil { 33 return err 34 } 35 36 // Load/parse/type-check the program. 37 lprog, err := loadWithSoftErrors(&lconf) 38 if err != nil { 39 return err 40 } 41 42 qpos, err := parseQueryPos(lprog, q.Pos, false) 43 if err != nil { 44 return err 45 } 46 47 prog := ssautil.CreateProgram(lprog, 0) 48 49 ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) 50 if err != nil { 51 return err 52 } 53 54 pkg := prog.Package(qpos.info.Pkg) 55 if pkg == nil { 56 return fmt.Errorf("no SSA package") 57 } 58 59 if !ssa.HasEnclosingFunction(pkg, qpos.path) { 60 return fmt.Errorf("this position is not inside a function") 61 } 62 63 // Defer SSA construction till after errors are reported. 64 prog.Build() 65 66 target := ssa.EnclosingFunction(pkg, qpos.path) 67 if target == nil { 68 return fmt.Errorf("no SSA function built for this location (dead code?)") 69 } 70 71 var callpath []*callgraph.Edge 72 isEnd := func(n *callgraph.Node) bool { return n.Func == target } 73 74 // First, build a callgraph containing only static call edges, 75 // and search for an arbitrary path from a root to the target function. 76 // This is quick, and the user wants a static path if one exists. 77 cg := static.CallGraph(prog) 78 cg.DeleteSyntheticNodes() 79 for _, ep := range entryPoints(ptaConfig.Mains) { 80 callpath = callgraph.PathSearch(cg.CreateNode(ep), isEnd) 81 if callpath != nil { 82 break 83 } 84 } 85 86 // No fully static path found. 87 // Run the pointer analysis and build a complete call graph. 88 if callpath == nil { 89 ptaConfig.BuildCallGraph = true 90 cg := ptrAnalysis(ptaConfig).CallGraph 91 cg.DeleteSyntheticNodes() 92 callpath = callgraph.PathSearch(cg.Root, isEnd) 93 if callpath != nil { 94 callpath = callpath[1:] // remove synthetic edge from <root> 95 } 96 } 97 98 q.Output(fset, &callstackResult{ 99 qpos: qpos, 100 target: target, 101 callpath: callpath, 102 }) 103 return nil 104 } 105 106 type callstackResult struct { 107 qpos *queryPos 108 target *ssa.Function 109 callpath []*callgraph.Edge 110 } 111 112 func (r *callstackResult) PrintPlain(printf printfFunc) { 113 if r.callpath != nil { 114 printf(r.qpos, "Found a call path from root to %s", r.target) 115 printf(r.target, "%s", r.target) 116 for i := len(r.callpath) - 1; i >= 0; i-- { 117 edge := r.callpath[i] 118 printf(edge, "%s from %s", edge.Description(), edge.Caller.Func) 119 } 120 } else { 121 printf(r.target, "%s is unreachable in this analysis scope", r.target) 122 } 123 } 124 125 func (r *callstackResult) JSON(fset *token.FileSet) []byte { 126 var callers []serial.Caller 127 for i := len(r.callpath) - 1; i >= 0; i-- { // (innermost first) 128 edge := r.callpath[i] 129 callers = append(callers, serial.Caller{ 130 Pos: fset.Position(edge.Pos()).String(), 131 Caller: edge.Caller.Func.String(), 132 Desc: edge.Description(), 133 }) 134 } 135 return toJSON(&serial.CallStack{ 136 Pos: fset.Position(r.target.Pos()).String(), 137 Target: r.target.String(), 138 Callers: callers, 139 }) 140 }