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