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 5package oracle 6 7import ( 8 "fmt" 9 "go/token" 10 11 "github.com/visualfc/gotools/oracle/oracle/serial" 12 "golang.org/x/tools/go/callgraph" 13 "golang.org/x/tools/go/loader" 14 "golang.org/x/tools/go/ssa" 15 "golang.org/x/tools/go/ssa/ssautil" 16) 17 18// Callstack displays an arbitrary path from a root of the callgraph 19// to the function at the current position. 20// 21// The information may be misleading in a context-insensitive 22// analysis. e.g. the call path X->Y->Z might be infeasible if Y never 23// calls Z when it is called from X. TODO(adonovan): think about UI. 24// 25// TODO(adonovan): permit user to specify a starting point other than 26// the analysis root. 27// 28func 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 := lconf.Load() 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 // Run the pointer analysis and build the complete call graph. 72 ptaConfig.BuildCallGraph = true 73 cg := ptrAnalysis(ptaConfig).CallGraph 74 cg.DeleteSyntheticNodes() 75 76 // Search for an arbitrary path from a root to the target function. 77 isEnd := func(n *callgraph.Node) bool { return n.Func == target } 78 callpath := callgraph.PathSearch(cg.Root, isEnd) 79 if callpath != nil { 80 callpath = callpath[1:] // remove synthetic edge from <root> 81 } 82 83 q.Fset = fset 84 q.result = &callstackResult{ 85 qpos: qpos, 86 target: target, 87 callpath: callpath, 88 } 89 return nil 90} 91 92type callstackResult struct { 93 qpos *queryPos 94 target *ssa.Function 95 callpath []*callgraph.Edge 96} 97 98func (r *callstackResult) display(printf printfFunc) { 99 if r.callpath != nil { 100 printf(r.qpos, "Found a call path from root to %s", r.target) 101 printf(r.target, "%s", r.target) 102 for i := len(r.callpath) - 1; i >= 0; i-- { 103 edge := r.callpath[i] 104 printf(edge, "%s from %s", edge.Description(), edge.Caller.Func) 105 } 106 } else { 107 printf(r.target, "%s is unreachable in this analysis scope", r.target) 108 } 109} 110 111func (r *callstackResult) toSerial(res *serial.Result, fset *token.FileSet) { 112 var callers []serial.Caller 113 for i := len(r.callpath) - 1; i >= 0; i-- { // (innermost first) 114 edge := r.callpath[i] 115 callers = append(callers, serial.Caller{ 116 Pos: fset.Position(edge.Pos()).String(), 117 Caller: edge.Caller.Func.String(), 118 Desc: edge.Description(), 119 }) 120 } 121 res.Callstack = &serial.CallStack{ 122 Pos: fset.Position(r.target.Pos()).String(), 123 Target: r.target.String(), 124 Callers: callers, 125 } 126} 127