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"
 | 
						|
 | 
						|
	"golang.org/x/tools/go/pointer"
 | 
						|
	"golang.org/x/tools/go/ssa"
 | 
						|
	"golang.org/x/tools/go/types"
 | 
						|
)
 | 
						|
 | 
						|
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().Object),
 | 
						|
			})
 | 
						|
			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 oracle/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 oracle/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
 | 
						|
}
 |