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 | "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" |
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] } |
253 |
Members