--- /dev/null
+// 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
+}