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