|
|
// 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 main
import ( "fmt" "go/ast" "go/token" "go/types" "sort"
"golang.org/x/tools/cmd/guru/serial" "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" )
// 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".
func peers(q *Query) error { lconf := loader.Config{Build: q.Build}
if err := setPTAScope(&lconf, q.Scope); err != nil { return err }
// Load/parse/type-check the program.
lprog, err := loadWithSoftErrors(&lconf) if err != nil { return err }
qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { return err }
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { return err }
opPos := findOp(qpos) if opPos == token.NoPos { return fmt.Errorf("there is no channel operation here") }
// Defer SSA construction till after errors are reported.
prog.Build()
var queryOp chanOp // the originating send or receive operation
var ops []chanOp // all sends/receives of opposite direction
// Look at all channel operations in the whole ssa.Program.
// Build a list of those of same type as the query.
allFuncs := ssautil.AllFunctions(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 == opPos { queryOp = op // we found the query op
} } } } } if queryOp.ch == nil { return 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() ptaConfig.AddQuery(queryOp.ch) i := 0 for _, op := range ops { if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) { ptaConfig.AddQuery(op.ch) ops[i] = op i++ } } ops = ops[:i]
// Run the pointer analysis.
ptares := ptrAnalysis(ptaConfig)
// 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 channel operations can alias the same make(chan) labels.
var sends, receives, closes []token.Pos for _, op := range ops { if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) { switch op.dir { case types.SendOnly: sends = append(sends, op.pos) case types.RecvOnly: receives = append(receives, op.pos) case types.SendRecv: closes = append(closes, op.pos) } } } sort.Sort(byPos(sends)) sort.Sort(byPos(receives)) sort.Sort(byPos(closes))
q.Output(lprog.Fset, &peersResult{ queryPos: opPos, queryType: queryType, makes: makes, sends: sends, receives: receives, closes: closes, }) return nil }
// findOp returns the position of the enclosing send/receive/close op.
// For send and receive operations, this is the position of the <- token;
// for close operations, it's the Lparen of the function call.
//
// TODO(adonovan): handle implicit receive operations from 'for...range chan' statements.
func findOp(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 case *ast.CallExpr: // close function call can only exist as a direct identifier
if close, ok := unparen(n.Fun).(*ast.Ident); ok { if b, ok := qpos.info.Info.Uses[close].(*types.Builtin); ok && b.Name() == "close" { return n.Lparen } } } } return token.NoPos }
// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
type chanOp struct { ch ssa.Value dir types.ChanDir // SendOnly=send, RecvOnly=recv, SendRecv=close
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}) } case ssa.CallInstruction: cc := instr.Common() if b, ok := cc.Value.(*ssa.Builtin); ok && b.Name() == "close" { ops = append(ops, chanOp{cc.Args[0], types.SendRecv, cc.Pos()}) } } return ops }
// TODO(adonovan): show the line of text for each pos, like "referrers" does.
type peersResult struct { queryPos token.Pos // of queried channel op
queryType types.Type // type of queried channel
makes, sends, receives, closes []token.Pos // positions of aliased makechan/send/receive/close instrs
}
func (r *peersResult) PrintPlain(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") } for _, clos := range r.closes { printf(clos, "\tclosed, here") } }
func (r *peersResult) JSON(fset *token.FileSet) []byte { 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()) } for _, clos := range r.closes { peers.Closes = append(peers.Closes, fset.Position(clos).String()) } return toJSON(peers) }
// -------- utils --------
// NB: byPos is not deterministic across packages since it depends on load order.
// Use lessPos if the tests need it.
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] }
|