1
0
mirror of https://github.com/golang/go synced 2024-10-01 09:38:36 -06:00
go/oracle/peers.go
Alan Donovan e08d89f3ed go.tools/oracle: an oracle that answers questions about Go source code.
+ Tests.
+ Emacs integration.
+ Emacs integration test.
+ very rudimentary Vim integration.  Needs some love from a Vim user.

TODO (in follow-ups):
- More tests would be good.
  We'll need to make the output order deterministic in more places.
- Documentation.

R=gri, crawshaw, dominik.honnef
CC=golang-dev
https://golang.org/cl/9502043
2013-08-27 17:58:26 -04:00

148 lines
3.9 KiB
Go

package oracle
import (
"go/ast"
"go/token"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa"
)
// 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}.
// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
// or the implicit receive in "for v := range ch".
//
func peers(o *oracle) (queryResult, error) {
// Determine the enclosing send/receive op for the specified position.
var arrowPos token.Pos
for _, n := range o.queryPath {
switch n := n.(type) {
case *ast.UnaryExpr:
if n.Op == token.ARROW {
arrowPos = n.OpPos
goto found
}
case *ast.SendStmt:
arrowPos = n.Arrow
goto found
}
}
return nil, o.errorf(o.queryPath[0], "there is no send/receive here")
found:
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 := ssa.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, o.errorf(arrowPos, "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.
queryElemType := queryOp.ch.Type().Underlying().(*types.Chan).Elem()
channels := map[ssa.Value][]pointer.Pointer{queryOp.ch: nil}
i := 0
for _, op := range ops {
if types.IsIdentical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
channels[op.ch] = nil
ops[i] = op
i++
}
}
ops = ops[:i]
// Run the pointer analysis.
o.config.QueryValues = channels
ptrAnalysis(o)
// Combine the PT sets from all contexts.
queryChanPts := pointer.PointsToCombined(channels[queryOp.ch])
return &peersResult{
queryOp: queryOp,
ops: ops,
queryChanPts: queryChanPts,
}, nil
}
// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
type chanOp struct {
ch ssa.Value
dir ast.ChanDir
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} too.
var ops []chanOp
switch instr := instr.(type) {
case *ssa.UnOp:
if instr.Op == token.ARROW {
ops = append(ops, chanOp{instr.X, ast.RECV, instr.Pos()})
}
case *ssa.Send:
ops = append(ops, chanOp{instr.Chan, ast.SEND, 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 {
queryOp chanOp
ops []chanOp
queryChanPts pointer.PointsToSet
}
func (r *peersResult) display(o *oracle) {
// Report which make(chan) labels the query's channel can alias.
labels := r.queryChanPts.Labels()
if len(labels) == 0 {
o.printf(r.queryOp.pos, "This channel can't point to anything.")
return
}
o.printf(r.queryOp.pos, "This channel of type %s may be:", r.queryOp.ch.Type())
// TODO(adonovan): sort, to ensure test determinism.
for _, label := range labels {
o.printf(label, "\tallocated here")
}
// Report which send/receive operations can alias the same make(chan) labels.
for _, op := range r.ops {
// TODO(adonovan): sort, to ensure test determinism.
for _, ptr := range o.config.QueryValues[op.ch] {
if ptr != nil && ptr.PointsTo().Intersects(r.queryChanPts) {
verb := "received from"
if op.dir == ast.SEND {
verb = "sent to"
}
o.printf(op.pos, "\t%s, here", verb)
}
}
}
}