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