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.

154 lines
4.2 KiB

  1. // Copyright 2014 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 analysis
  5. // This file computes the channel "peers" relation over all pairs of
  6. // channel operations in the program. The peers are displayed in the
  7. // lower pane when a channel operation (make, <-, close) is clicked.
  8. // TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too,
  9. // then enable reflection in PTA.
  10. import (
  11. "fmt"
  12. "go/token"
  13. "go/types"
  14. "golang.org/x/tools/go/pointer"
  15. "golang.org/x/tools/go/ssa"
  16. )
  17. func (a *analysis) doChannelPeers(ptsets map[ssa.Value]pointer.Pointer) {
  18. addSendRecv := func(j *commJSON, op chanOp) {
  19. j.Ops = append(j.Ops, commOpJSON{
  20. Op: anchorJSON{
  21. Text: op.mode,
  22. Href: a.posURL(op.pos, op.len),
  23. },
  24. Fn: prettyFunc(nil, op.fn),
  25. })
  26. }
  27. // Build an undirected bipartite multigraph (binary relation)
  28. // of MakeChan ops and send/recv/close ops.
  29. //
  30. // TODO(adonovan): opt: use channel element types to partition
  31. // the O(n^2) problem into subproblems.
  32. aliasedOps := make(map[*ssa.MakeChan][]chanOp)
  33. opToMakes := make(map[chanOp][]*ssa.MakeChan)
  34. for _, op := range a.ops {
  35. // Combine the PT sets from all contexts.
  36. var makes []*ssa.MakeChan // aliased ops
  37. ptr, ok := ptsets[op.ch]
  38. if !ok {
  39. continue // e.g. channel op in dead code
  40. }
  41. for _, label := range ptr.PointsTo().Labels() {
  42. makechan, ok := label.Value().(*ssa.MakeChan)
  43. if !ok {
  44. continue // skip intrinsically-created channels for now
  45. }
  46. if makechan.Pos() == token.NoPos {
  47. continue // not possible?
  48. }
  49. makes = append(makes, makechan)
  50. aliasedOps[makechan] = append(aliasedOps[makechan], op)
  51. }
  52. opToMakes[op] = makes
  53. }
  54. // Now that complete relation is built, build links for ops.
  55. for _, op := range a.ops {
  56. v := commJSON{
  57. Ops: []commOpJSON{}, // (JS wants non-nil)
  58. }
  59. ops := make(map[chanOp]bool)
  60. for _, makechan := range opToMakes[op] {
  61. v.Ops = append(v.Ops, commOpJSON{
  62. Op: anchorJSON{
  63. Text: "made",
  64. Href: a.posURL(makechan.Pos()-token.Pos(len("make")),
  65. len("make")),
  66. },
  67. Fn: makechan.Parent().RelString(op.fn.Package().Pkg),
  68. })
  69. for _, op := range aliasedOps[makechan] {
  70. ops[op] = true
  71. }
  72. }
  73. for op := range ops {
  74. addSendRecv(&v, op)
  75. }
  76. // Add links for each aliased op.
  77. fi, offset := a.fileAndOffset(op.pos)
  78. fi.addLink(aLink{
  79. start: offset,
  80. end: offset + op.len,
  81. title: "show channel ops",
  82. onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)),
  83. })
  84. }
  85. // Add links for makechan ops themselves.
  86. for makechan, ops := range aliasedOps {
  87. v := commJSON{
  88. Ops: []commOpJSON{}, // (JS wants non-nil)
  89. }
  90. for _, op := range ops {
  91. addSendRecv(&v, op)
  92. }
  93. fi, offset := a.fileAndOffset(makechan.Pos())
  94. fi.addLink(aLink{
  95. start: offset - len("make"),
  96. end: offset,
  97. title: "show channel ops",
  98. onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)),
  99. })
  100. }
  101. }
  102. // -- utilities --------------------------------------------------------
  103. // chanOp abstracts an ssa.Send, ssa.Unop(ARROW), close(), or a SelectState.
  104. // Derived from cmd/guru/peers.go.
  105. type chanOp struct {
  106. ch ssa.Value
  107. mode string // sent|received|closed
  108. pos token.Pos
  109. len int
  110. fn *ssa.Function
  111. }
  112. // chanOps returns a slice of all the channel operations in the instruction.
  113. // Derived from cmd/guru/peers.go.
  114. func chanOps(instr ssa.Instruction) []chanOp {
  115. fn := instr.Parent()
  116. var ops []chanOp
  117. switch instr := instr.(type) {
  118. case *ssa.UnOp:
  119. if instr.Op == token.ARROW {
  120. // TODO(adonovan): don't assume <-ch; could be 'range ch'.
  121. ops = append(ops, chanOp{instr.X, "received", instr.Pos(), len("<-"), fn})
  122. }
  123. case *ssa.Send:
  124. ops = append(ops, chanOp{instr.Chan, "sent", instr.Pos(), len("<-"), fn})
  125. case *ssa.Select:
  126. for _, st := range instr.States {
  127. mode := "received"
  128. if st.Dir == types.SendOnly {
  129. mode = "sent"
  130. }
  131. ops = append(ops, chanOp{st.Chan, mode, st.Pos, len("<-"), fn})
  132. }
  133. case ssa.CallInstruction:
  134. call := instr.Common()
  135. if blt, ok := call.Value.(*ssa.Builtin); ok && blt.Name() == "close" {
  136. pos := instr.Common().Pos()
  137. ops = append(ops, chanOp{call.Args[0], "closed", pos - token.Pos(len("close")), len("close("), fn})
  138. }
  139. }
  140. return ops
  141. }