155 lines
4.2 KiB
Go
155 lines
4.2 KiB
Go
|
// 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
|
||
|
}
|