1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
|
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package oracle
import (
"fmt"
"go/ast"
"go/token"
"sort"
"code.google.com/p/go.tools/go/ssa"
"code.google.com/p/go.tools/go/ssa/ssautil"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/oracle/serial"
)
// peers enumerates, for a given channel send (or receive) operation,
// the set of possible receives (or sends) that correspond to it.
//
// TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
// or the implicit receive in "for v := range ch".
// TODO(adonovan): support "close" as a channel op.
//
func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
arrowPos := findArrow(qpos)
if arrowPos == token.NoPos {
return nil, fmt.Errorf("there is no send/receive here")
}
buildSSA(o)
var queryOp chanOp // the originating send or receive operation
var ops []chanOp // all sends/receives of opposite direction
// Look at all send/receive instructions in the whole ssa.Program.
// Build a list of those of same type to query.
allFuncs := ssautil.AllFunctions(o.prog)
for fn := range allFuncs {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
for _, op := range chanOps(instr) {
ops = append(ops, op)
if op.pos == arrowPos {
queryOp = op // we found the query op
}
}
}
}
}
if queryOp.ch == nil {
return nil, fmt.Errorf("ssa.Instruction for send/receive not found")
}
// Discard operations of wrong channel element type.
// Build set of channel ssa.Values as query to pointer analysis.
// We compare channels by element types, not channel types, to
// ignore both directionality and type names.
queryType := queryOp.ch.Type()
queryElemType := queryType.Underlying().(*types.Chan).Elem()
o.ptaConfig.AddQuery(queryOp.ch)
i := 0
for _, op := range ops {
if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
o.ptaConfig.AddQuery(op.ch)
ops[i] = op
i++
}
}
ops = ops[:i]
// Run the pointer analysis.
ptares := ptrAnalysis(o)
// Find the points-to set.
queryChanPtr := ptares.Queries[queryOp.ch]
// Ascertain which make(chan) labels the query's channel can alias.
var makes []token.Pos
for _, label := range queryChanPtr.PointsTo().Labels() {
makes = append(makes, label.Pos())
}
sort.Sort(byPos(makes))
// Ascertain which send/receive operations can alias the same make(chan) labels.
var sends, receives []token.Pos
for _, op := range ops {
if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) {
if op.dir == types.SendOnly {
sends = append(sends, op.pos)
} else {
receives = append(receives, op.pos)
}
}
}
sort.Sort(byPos(sends))
sort.Sort(byPos(receives))
return &peersResult{
queryPos: arrowPos,
queryType: queryType,
makes: makes,
sends: sends,
receives: receives,
}, nil
}
// findArrow returns the position of the enclosing send/receive op
// (<-) for the query position, or token.NoPos if not found.
//
func findArrow(qpos *QueryPos) token.Pos {
for _, n := range qpos.path {
switch n := n.(type) {
case *ast.UnaryExpr:
if n.Op == token.ARROW {
return n.OpPos
}
case *ast.SendStmt:
return n.Arrow
}
}
return token.NoPos
}
// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
type chanOp struct {
ch ssa.Value
dir types.ChanDir // SendOnly or RecvOnly
pos token.Pos
}
// chanOps returns a slice of all the channel operations in the instruction.
func chanOps(instr ssa.Instruction) []chanOp {
// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too.
var ops []chanOp
switch instr := instr.(type) {
case *ssa.UnOp:
if instr.Op == token.ARROW {
ops = append(ops, chanOp{instr.X, types.RecvOnly, instr.Pos()})
}
case *ssa.Send:
ops = append(ops, chanOp{instr.Chan, types.SendOnly, instr.Pos()})
case *ssa.Select:
for _, st := range instr.States {
ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
}
}
return ops
}
type peersResult struct {
queryPos token.Pos // of queried '<-' token
queryType types.Type // type of queried channel
makes, sends, receives []token.Pos // positions of aliased makechan/send/receive instrs
}
func (r *peersResult) display(printf printfFunc) {
if len(r.makes) == 0 {
printf(r.queryPos, "This channel can't point to anything.")
return
}
printf(r.queryPos, "This channel of type %s may be:", r.queryType)
for _, alloc := range r.makes {
printf(alloc, "\tallocated here")
}
for _, send := range r.sends {
printf(send, "\tsent to, here")
}
for _, receive := range r.receives {
printf(receive, "\treceived from, here")
}
}
func (r *peersResult) toSerial(res *serial.Result, fset *token.FileSet) {
peers := &serial.Peers{
Pos: fset.Position(r.queryPos).String(),
Type: r.queryType.String(),
}
for _, alloc := range r.makes {
peers.Allocs = append(peers.Allocs, fset.Position(alloc).String())
}
for _, send := range r.sends {
peers.Sends = append(peers.Sends, fset.Position(send).String())
}
for _, receive := range r.receives {
peers.Receives = append(peers.Receives, fset.Position(receive).String())
}
res.Peers = peers
}
// -------- utils --------
type byPos []token.Pos
func (p byPos) Len() int { return len(p) }
func (p byPos) Less(i, j int) bool { return p[i] < p[j] }
func (p byPos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|