|
|
// Copyright 2014 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 analysis
// This file computes the channel "peers" relation over all pairs of
// channel operations in the program. The peers are displayed in the
// lower pane when a channel operation (make, <-, close) is clicked.
// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too,
// then enable reflection in PTA.
import ( "fmt" "go/token" "go/types"
"golang.org/x/tools/go/pointer" "golang.org/x/tools/go/ssa" )
func (a *analysis) doChannelPeers(ptsets map[ssa.Value]pointer.Pointer) { addSendRecv := func(j *commJSON, op chanOp) { j.Ops = append(j.Ops, commOpJSON{ Op: anchorJSON{ Text: op.mode, Href: a.posURL(op.pos, op.len), }, Fn: prettyFunc(nil, op.fn), }) }
// Build an undirected bipartite multigraph (binary relation)
// of MakeChan ops and send/recv/close ops.
//
// TODO(adonovan): opt: use channel element types to partition
// the O(n^2) problem into subproblems.
aliasedOps := make(map[*ssa.MakeChan][]chanOp) opToMakes := make(map[chanOp][]*ssa.MakeChan) for _, op := range a.ops { // Combine the PT sets from all contexts.
var makes []*ssa.MakeChan // aliased ops
ptr, ok := ptsets[op.ch] if !ok { continue // e.g. channel op in dead code
} for _, label := range ptr.PointsTo().Labels() { makechan, ok := label.Value().(*ssa.MakeChan) if !ok { continue // skip intrinsically-created channels for now
} if makechan.Pos() == token.NoPos { continue // not possible?
} makes = append(makes, makechan) aliasedOps[makechan] = append(aliasedOps[makechan], op) } opToMakes[op] = makes }
// Now that complete relation is built, build links for ops.
for _, op := range a.ops { v := commJSON{ Ops: []commOpJSON{}, // (JS wants non-nil)
} ops := make(map[chanOp]bool) for _, makechan := range opToMakes[op] { v.Ops = append(v.Ops, commOpJSON{ Op: anchorJSON{ Text: "made", Href: a.posURL(makechan.Pos()-token.Pos(len("make")), len("make")), }, Fn: makechan.Parent().RelString(op.fn.Package().Pkg), }) for _, op := range aliasedOps[makechan] { ops[op] = true } } for op := range ops { addSendRecv(&v, op) }
// Add links for each aliased op.
fi, offset := a.fileAndOffset(op.pos) fi.addLink(aLink{ start: offset, end: offset + op.len, title: "show channel ops", onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)), }) } // Add links for makechan ops themselves.
for makechan, ops := range aliasedOps { v := commJSON{ Ops: []commOpJSON{}, // (JS wants non-nil)
} for _, op := range ops { addSendRecv(&v, op) }
fi, offset := a.fileAndOffset(makechan.Pos()) fi.addLink(aLink{ start: offset - len("make"), end: offset, title: "show channel ops", onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)), }) } }
// -- utilities --------------------------------------------------------
// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), close(), or a SelectState.
// Derived from cmd/guru/peers.go.
type chanOp struct { ch ssa.Value mode string // sent|received|closed
pos token.Pos len int fn *ssa.Function }
// chanOps returns a slice of all the channel operations in the instruction.
// Derived from cmd/guru/peers.go.
func chanOps(instr ssa.Instruction) []chanOp { fn := instr.Parent() var ops []chanOp switch instr := instr.(type) { case *ssa.UnOp: if instr.Op == token.ARROW { // TODO(adonovan): don't assume <-ch; could be 'range ch'.
ops = append(ops, chanOp{instr.X, "received", instr.Pos(), len("<-"), fn}) } case *ssa.Send: ops = append(ops, chanOp{instr.Chan, "sent", instr.Pos(), len("<-"), fn}) case *ssa.Select: for _, st := range instr.States { mode := "received" if st.Dir == types.SendOnly { mode = "sent" } ops = append(ops, chanOp{st.Chan, mode, st.Pos, len("<-"), fn}) } case ssa.CallInstruction: call := instr.Common() if blt, ok := call.Value.(*ssa.Builtin); ok && blt.Name() == "close" { pos := instr.Common().Pos() ops = append(ops, chanOp{call.Args[0], "closed", pos - token.Pos(len("close")), len("close("), fn}) } } return ops }
|