github.com/v2fly/tools@v0.100.0/godoc/analysis/peers.go (about)

     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  
     5  package 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  
    14  import (
    15  	"fmt"
    16  	"go/token"
    17  	"go/types"
    18  
    19  	"github.com/v2fly/tools/go/pointer"
    20  	"github.com/v2fly/tools/go/ssa"
    21  )
    22  
    23  func (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.
   117  type 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.
   127  func 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  }