1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
// 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
}
|