1// Copyright 2014 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package analysis
6
7// This file computes the channel "peers" relation over all pairs of
8// channel operations in the program.  The peers are displayed in the
9// lower pane when a channel operation (make, <-, close) is clicked.
10
11// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too,
12// then enable reflection in PTA.
13
14import (
15	"fmt"
16	"go/token"
17	"go/types"
18
19	"golang.org/x/tools/go/pointer"
20	"golang.org/x/tools/go/ssa"
21)
22
23func (a *analysis) doChannelPeers(ptsets map[ssa.Value]pointer.Pointer) {
24	addSendRecv := func(j *commJSON, op chanOp) {
25		j.Ops = append(j.Ops, commOpJSON{
26			Op: anchorJSON{
27				Text: op.mode,
28				Href: a.posURL(op.pos, op.len),
29			},
30			Fn: prettyFunc(nil, op.fn),
31		})
32	}
33
34	// Build an undirected bipartite multigraph (binary relation)
35	// of MakeChan ops and send/recv/close ops.
36	//
37	// TODO(adonovan): opt: use channel element types to partition
38	// the O(n^2) problem into subproblems.
39	aliasedOps := make(map[*ssa.MakeChan][]chanOp)
40	opToMakes := make(map[chanOp][]*ssa.MakeChan)
41	for _, op := range a.ops {
42		// Combine the PT sets from all contexts.
43		var makes []*ssa.MakeChan // aliased ops
44		ptr, ok := ptsets[op.ch]
45		if !ok {
46			continue // e.g. channel op in dead code
47		}
48		for _, label := range ptr.PointsTo().Labels() {
49			makechan, ok := label.Value().(*ssa.MakeChan)
50			if !ok {
51				continue // skip intrinsically-created channels for now
52			}
53			if makechan.Pos() == token.NoPos {
54				continue // not possible?
55			}
56			makes = append(makes, makechan)
57			aliasedOps[makechan] = append(aliasedOps[makechan], op)
58		}
59		opToMakes[op] = makes
60	}
61
62	// Now that complete relation is built, build links for ops.
63	for _, op := range a.ops {
64		v := commJSON{
65			Ops: []commOpJSON{}, // (JS wants non-nil)
66		}
67		ops := make(map[chanOp]bool)
68		for _, makechan := range opToMakes[op] {
69			v.Ops = append(v.Ops, commOpJSON{
70				Op: anchorJSON{
71					Text: "made",
72					Href: a.posURL(makechan.Pos()-token.Pos(len("make")),
73						len("make")),
74				},
75				Fn: makechan.Parent().RelString(op.fn.Package().Pkg),
76			})
77			for _, op := range aliasedOps[makechan] {
78				ops[op] = true
79			}
80		}
81		for op := range ops {
82			addSendRecv(&v, op)
83		}
84
85		// Add links for each aliased op.
86		fi, offset := a.fileAndOffset(op.pos)
87		fi.addLink(aLink{
88			start:   offset,
89			end:     offset + op.len,
90			title:   "show channel ops",
91			onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)),
92		})
93	}
94	// Add links for makechan ops themselves.
95	for makechan, ops := range aliasedOps {
96		v := commJSON{
97			Ops: []commOpJSON{}, // (JS wants non-nil)
98		}
99		for _, op := range ops {
100			addSendRecv(&v, op)
101		}
102
103		fi, offset := a.fileAndOffset(makechan.Pos())
104		fi.addLink(aLink{
105			start:   offset - len("make"),
106			end:     offset,
107			title:   "show channel ops",
108			onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)),
109		})
110	}
111}
112
113// -- utilities --------------------------------------------------------
114
115// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), close(), or a SelectState.
116// Derived from cmd/guru/peers.go.
117type chanOp struct {
118	ch   ssa.Value
119	mode string // sent|received|closed
120	pos  token.Pos
121	len  int
122	fn   *ssa.Function
123}
124
125// chanOps returns a slice of all the channel operations in the instruction.
126// Derived from cmd/guru/peers.go.
127func chanOps(instr ssa.Instruction) []chanOp {
128	fn := instr.Parent()
129	var ops []chanOp
130	switch instr := instr.(type) {
131	case *ssa.UnOp:
132		if instr.Op == token.ARROW {
133			// TODO(adonovan): don't assume <-ch; could be 'range ch'.
134			ops = append(ops, chanOp{instr.X, "received", instr.Pos(), len("<-"), fn})
135		}
136	case *ssa.Send:
137		ops = append(ops, chanOp{instr.Chan, "sent", instr.Pos(), len("<-"), fn})
138	case *ssa.Select:
139		for _, st := range instr.States {
140			mode := "received"
141			if st.Dir == types.SendOnly {
142				mode = "sent"
143			}
144			ops = append(ops, chanOp{st.Chan, mode, st.Pos, len("<-"), fn})
145		}
146	case ssa.CallInstruction:
147		call := instr.Common()
148		if blt, ok := call.Value.(*ssa.Builtin); ok && blt.Name() == "close" {
149			pos := instr.Common().Pos()
150			ops = append(ops, chanOp{call.Args[0], "closed", pos - token.Pos(len("close")), len("close("), fn})
151		}
152	}
153	return ops
154}
155