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.

252 lines
7.0 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/ast"
  8. "go/token"
  9. "go/types"
  10. "sort"
  11. "golang.org/x/tools/cmd/guru/serial"
  12. "golang.org/x/tools/go/loader"
  13. "golang.org/x/tools/go/ssa"
  14. "golang.org/x/tools/go/ssa/ssautil"
  15. )
  16. // peers enumerates, for a given channel send (or receive) operation,
  17. // the set of possible receives (or sends) that correspond to it.
  18. //
  19. // TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
  20. // TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
  21. // or the implicit receive in "for v := range ch".
  22. func peers(q *Query) error {
  23. lconf := loader.Config{Build: q.Build}
  24. if err := setPTAScope(&lconf, q.Scope); err != nil {
  25. return err
  26. }
  27. // Load/parse/type-check the program.
  28. lprog, err := loadWithSoftErrors(&lconf)
  29. if err != nil {
  30. return err
  31. }
  32. qpos, err := parseQueryPos(lprog, q.Pos, false)
  33. if err != nil {
  34. return err
  35. }
  36. prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
  37. ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
  38. if err != nil {
  39. return err
  40. }
  41. opPos := findOp(qpos)
  42. if opPos == token.NoPos {
  43. return fmt.Errorf("there is no channel operation here")
  44. }
  45. // Defer SSA construction till after errors are reported.
  46. prog.Build()
  47. var queryOp chanOp // the originating send or receive operation
  48. var ops []chanOp // all sends/receives of opposite direction
  49. // Look at all channel operations in the whole ssa.Program.
  50. // Build a list of those of same type as the query.
  51. allFuncs := ssautil.AllFunctions(prog)
  52. for fn := range allFuncs {
  53. for _, b := range fn.Blocks {
  54. for _, instr := range b.Instrs {
  55. for _, op := range chanOps(instr) {
  56. ops = append(ops, op)
  57. if op.pos == opPos {
  58. queryOp = op // we found the query op
  59. }
  60. }
  61. }
  62. }
  63. }
  64. if queryOp.ch == nil {
  65. return fmt.Errorf("ssa.Instruction for send/receive not found")
  66. }
  67. // Discard operations of wrong channel element type.
  68. // Build set of channel ssa.Values as query to pointer analysis.
  69. // We compare channels by element types, not channel types, to
  70. // ignore both directionality and type names.
  71. queryType := queryOp.ch.Type()
  72. queryElemType := queryType.Underlying().(*types.Chan).Elem()
  73. ptaConfig.AddQuery(queryOp.ch)
  74. i := 0
  75. for _, op := range ops {
  76. if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
  77. ptaConfig.AddQuery(op.ch)
  78. ops[i] = op
  79. i++
  80. }
  81. }
  82. ops = ops[:i]
  83. // Run the pointer analysis.
  84. ptares := ptrAnalysis(ptaConfig)
  85. // Find the points-to set.
  86. queryChanPtr := ptares.Queries[queryOp.ch]
  87. // Ascertain which make(chan) labels the query's channel can alias.
  88. var makes []token.Pos
  89. for _, label := range queryChanPtr.PointsTo().Labels() {
  90. makes = append(makes, label.Pos())
  91. }
  92. sort.Sort(byPos(makes))
  93. // Ascertain which channel operations can alias the same make(chan) labels.
  94. var sends, receives, closes []token.Pos
  95. for _, op := range ops {
  96. if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) {
  97. switch op.dir {
  98. case types.SendOnly:
  99. sends = append(sends, op.pos)
  100. case types.RecvOnly:
  101. receives = append(receives, op.pos)
  102. case types.SendRecv:
  103. closes = append(closes, op.pos)
  104. }
  105. }
  106. }
  107. sort.Sort(byPos(sends))
  108. sort.Sort(byPos(receives))
  109. sort.Sort(byPos(closes))
  110. q.Output(lprog.Fset, &peersResult{
  111. queryPos: opPos,
  112. queryType: queryType,
  113. makes: makes,
  114. sends: sends,
  115. receives: receives,
  116. closes: closes,
  117. })
  118. return nil
  119. }
  120. // findOp returns the position of the enclosing send/receive/close op.
  121. // For send and receive operations, this is the position of the <- token;
  122. // for close operations, it's the Lparen of the function call.
  123. //
  124. // TODO(adonovan): handle implicit receive operations from 'for...range chan' statements.
  125. func findOp(qpos *queryPos) token.Pos {
  126. for _, n := range qpos.path {
  127. switch n := n.(type) {
  128. case *ast.UnaryExpr:
  129. if n.Op == token.ARROW {
  130. return n.OpPos
  131. }
  132. case *ast.SendStmt:
  133. return n.Arrow
  134. case *ast.CallExpr:
  135. // close function call can only exist as a direct identifier
  136. if close, ok := unparen(n.Fun).(*ast.Ident); ok {
  137. if b, ok := qpos.info.Info.Uses[close].(*types.Builtin); ok && b.Name() == "close" {
  138. return n.Lparen
  139. }
  140. }
  141. }
  142. }
  143. return token.NoPos
  144. }
  145. // chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
  146. type chanOp struct {
  147. ch ssa.Value
  148. dir types.ChanDir // SendOnly=send, RecvOnly=recv, SendRecv=close
  149. pos token.Pos
  150. }
  151. // chanOps returns a slice of all the channel operations in the instruction.
  152. func chanOps(instr ssa.Instruction) []chanOp {
  153. // TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too.
  154. var ops []chanOp
  155. switch instr := instr.(type) {
  156. case *ssa.UnOp:
  157. if instr.Op == token.ARROW {
  158. ops = append(ops, chanOp{instr.X, types.RecvOnly, instr.Pos()})
  159. }
  160. case *ssa.Send:
  161. ops = append(ops, chanOp{instr.Chan, types.SendOnly, instr.Pos()})
  162. case *ssa.Select:
  163. for _, st := range instr.States {
  164. ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
  165. }
  166. case ssa.CallInstruction:
  167. cc := instr.Common()
  168. if b, ok := cc.Value.(*ssa.Builtin); ok && b.Name() == "close" {
  169. ops = append(ops, chanOp{cc.Args[0], types.SendRecv, cc.Pos()})
  170. }
  171. }
  172. return ops
  173. }
  174. // TODO(adonovan): show the line of text for each pos, like "referrers" does.
  175. type peersResult struct {
  176. queryPos token.Pos // of queried channel op
  177. queryType types.Type // type of queried channel
  178. makes, sends, receives, closes []token.Pos // positions of aliased makechan/send/receive/close instrs
  179. }
  180. func (r *peersResult) PrintPlain(printf printfFunc) {
  181. if len(r.makes) == 0 {
  182. printf(r.queryPos, "This channel can't point to anything.")
  183. return
  184. }
  185. printf(r.queryPos, "This channel of type %s may be:", r.queryType)
  186. for _, alloc := range r.makes {
  187. printf(alloc, "\tallocated here")
  188. }
  189. for _, send := range r.sends {
  190. printf(send, "\tsent to, here")
  191. }
  192. for _, receive := range r.receives {
  193. printf(receive, "\treceived from, here")
  194. }
  195. for _, clos := range r.closes {
  196. printf(clos, "\tclosed, here")
  197. }
  198. }
  199. func (r *peersResult) JSON(fset *token.FileSet) []byte {
  200. peers := &serial.Peers{
  201. Pos: fset.Position(r.queryPos).String(),
  202. Type: r.queryType.String(),
  203. }
  204. for _, alloc := range r.makes {
  205. peers.Allocs = append(peers.Allocs, fset.Position(alloc).String())
  206. }
  207. for _, send := range r.sends {
  208. peers.Sends = append(peers.Sends, fset.Position(send).String())
  209. }
  210. for _, receive := range r.receives {
  211. peers.Receives = append(peers.Receives, fset.Position(receive).String())
  212. }
  213. for _, clos := range r.closes {
  214. peers.Closes = append(peers.Closes, fset.Position(clos).String())
  215. }
  216. return toJSON(peers)
  217. }
  218. // -------- utils --------
  219. // NB: byPos is not deterministic across packages since it depends on load order.
  220. // Use lessPos if the tests need it.
  221. type byPos []token.Pos
  222. func (p byPos) Len() int { return len(p) }
  223. func (p byPos) Less(i, j int) bool { return p[i] < p[j] }
  224. func (p byPos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }