mirror of
https://github.com/golang/go
synced 2024-11-18 23:14:43 -07:00
148 lines
3.9 KiB
Go
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)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|