| // 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 |
| } |