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.
14 "golang.org/x/tools/cmd/guru/serial"
15 "golang.org/x/tools/go/loader"
16 "golang.org/x/tools/go/ssa"
17 "golang.org/x/tools/go/ssa/ssautil"
20 // peers enumerates, for a given channel send (or receive) operation,
21 // the set of possible receives (or sends) that correspond to it.
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}
29 if err := setPTAScope(&lconf, q.Scope); err != nil {
33 // Load/parse/type-check the program.
34 lprog, err := loadWithSoftErrors(&lconf)
39 qpos, err := parseQueryPos(lprog, q.Pos, false)
44 prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
46 ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
52 if opPos == token.NoPos {
53 return fmt.Errorf("there is no channel operation here")
56 // Defer SSA construction till after errors are reported.
59 var queryOp chanOp // the originating send or receive operation
60 var ops []chanOp // all sends/receives of opposite direction
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) {
71 queryOp = op // we found the query op
77 if queryOp.ch == nil {
78 return fmt.Errorf("ssa.Instruction for send/receive not found")
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)
89 for _, op := range ops {
90 if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
91 ptaConfig.AddQuery(op.ch)
98 // Run the pointer analysis.
99 ptares := ptrAnalysis(ptaConfig)
101 // Find the points-to set.
102 queryChanPtr := ptares.Queries[queryOp.ch]
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())
109 sort.Sort(byPos(makes))
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) {
117 sends = append(sends, op.pos)
119 receives = append(receives, op.pos)
121 closes = append(closes, op.pos)
125 sort.Sort(byPos(sends))
126 sort.Sort(byPos(receives))
127 sort.Sort(byPos(closes))
129 q.Output(lprog.Fset, &peersResult{
131 queryType: queryType,
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.
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) {
149 if n.Op == token.ARROW {
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" {
166 // chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
169 dir types.ChanDir // SendOnly=send, RecvOnly=recv, SendRecv=close
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.
177 switch instr := instr.(type) {
179 if instr.Op == token.ARROW {
180 ops = append(ops, chanOp{instr.X, types.RecvOnly, instr.Pos()})
183 ops = append(ops, chanOp{instr.Chan, types.SendOnly, instr.Pos()})
185 for _, st := range instr.States {
186 ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
188 case ssa.CallInstruction:
190 if b, ok := cc.Value.(*ssa.Builtin); ok && b.Name() == "close" {
191 ops = append(ops, chanOp{cc.Args[0], types.SendRecv, cc.Pos()})
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
204 func (r *peersResult) PrintPlain(printf printfFunc) {
205 if len(r.makes) == 0 {
206 printf(r.queryPos, "This channel can't point to anything.")
209 printf(r.queryPos, "This channel of type %s may be:", r.queryType)
210 for _, alloc := range r.makes {
211 printf(alloc, "\tallocated here")
213 for _, send := range r.sends {
214 printf(send, "\tsent to, here")
216 for _, receive := range r.receives {
217 printf(receive, "\treceived from, here")
219 for _, clos := range r.closes {
220 printf(clos, "\tclosed, here")
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(),
229 for _, alloc := range r.makes {
230 peers.Allocs = append(peers.Allocs, fset.Position(alloc).String())
232 for _, send := range r.sends {
233 peers.Sends = append(peers.Sends, fset.Position(send).String())
235 for _, receive := range r.receives {
236 peers.Receives = append(peers.Receives, fset.Position(receive).String())
238 for _, clos := range r.closes {
239 peers.Closes = append(peers.Closes, fset.Position(clos).String())
244 // -------- utils --------
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
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] }