You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
3.7 KiB

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