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