github.com/april1989/origin-go-tools@v0.0.32/cmd/guru/peers.go (about)

     1  // Copyright 2013 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 main
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/token"
    11  	"go/types"
    12  	"sort"
    13  
    14  	"github.com/april1989/origin-go-tools/cmd/guru/serial"
    15  	"github.com/april1989/origin-go-tools/go/loader"
    16  	"github.com/april1989/origin-go-tools/go/ssa"
    17  	"github.com/april1989/origin-go-tools/go/ssa/ssautil"
    18  )
    19  
    20  // peers enumerates, for a given channel send (or receive) operation,
    21  // the set of possible receives (or sends) that correspond to it.
    22  //
    23  // TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
    24  // TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
    25  // or the implicit receive in "for v := range ch".
    26  func peers(q *Query) error {
    27  	lconf := loader.Config{Build: q.Build}
    28  
    29  	if err := setPTAScope(&lconf, q.Scope); err != nil {
    30  		return err
    31  	}
    32  
    33  	// Load/parse/type-check the program.
    34  	lprog, err := loadWithSoftErrors(&lconf)
    35  	if err != nil {
    36  		return err
    37  	}
    38  
    39  	qpos, err := parseQueryPos(lprog, q.Pos, false)
    40  	if err != nil {
    41  		return err
    42  	}
    43  
    44  	prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
    45  
    46  	ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	opPos := findOp(qpos)
    52  	if opPos == token.NoPos {
    53  		return fmt.Errorf("there is no channel operation here")
    54  	}
    55  
    56  	// Defer SSA construction till after errors are reported.
    57  	prog.Build()
    58  
    59  	var queryOp chanOp // the originating send or receive operation
    60  	var ops []chanOp   // all sends/receives of opposite direction
    61  
    62  	// Look at all channel operations in the whole ssa.Program.
    63  	// Build a list of those of same type as the query.
    64  	allFuncs := ssautil.AllFunctions(prog)
    65  	for fn := range allFuncs {
    66  		for _, b := range fn.Blocks {
    67  			for _, instr := range b.Instrs {
    68  				for _, op := range chanOps(instr) {
    69  					ops = append(ops, op)
    70  					if op.pos == opPos {
    71  						queryOp = op // we found the query op
    72  					}
    73  				}
    74  			}
    75  		}
    76  	}
    77  	if queryOp.ch == nil {
    78  		return fmt.Errorf("ssa.Instruction for send/receive not found")
    79  	}
    80  
    81  	// Discard operations of wrong channel element type.
    82  	// Build set of channel ssa.Values as query to pointer analysis.
    83  	// We compare channels by element types, not channel types, to
    84  	// ignore both directionality and type names.
    85  	queryType := queryOp.ch.Type()
    86  	queryElemType := queryType.Underlying().(*types.Chan).Elem()
    87  	ptaConfig.AddQuery(queryOp.ch)
    88  	i := 0
    89  	for _, op := range ops {
    90  		if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
    91  			ptaConfig.AddQuery(op.ch)
    92  			ops[i] = op
    93  			i++
    94  		}
    95  	}
    96  	ops = ops[:i]
    97  
    98  	// Run the pointer analysis.
    99  	ptares := ptrAnalysis(ptaConfig)
   100  
   101  	// Find the points-to set.
   102  	queryChanPtr := ptares.Queries[queryOp.ch]
   103  
   104  	// Ascertain which make(chan) labels the query's channel can alias.
   105  	var makes []token.Pos
   106  	for _, label := range queryChanPtr.PointsTo().Labels() {
   107  		makes = append(makes, label.Pos())
   108  	}
   109  	sort.Sort(byPos(makes))
   110  
   111  	// Ascertain which channel operations can alias the same make(chan) labels.
   112  	var sends, receives, closes []token.Pos
   113  	for _, op := range ops {
   114  		if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) {
   115  			switch op.dir {
   116  			case types.SendOnly:
   117  				sends = append(sends, op.pos)
   118  			case types.RecvOnly:
   119  				receives = append(receives, op.pos)
   120  			case types.SendRecv:
   121  				closes = append(closes, op.pos)
   122  			}
   123  		}
   124  	}
   125  	sort.Sort(byPos(sends))
   126  	sort.Sort(byPos(receives))
   127  	sort.Sort(byPos(closes))
   128  
   129  	q.Output(lprog.Fset, &peersResult{
   130  		queryPos:  opPos,
   131  		queryType: queryType,
   132  		makes:     makes,
   133  		sends:     sends,
   134  		receives:  receives,
   135  		closes:    closes,
   136  	})
   137  	return nil
   138  }
   139  
   140  // findOp returns the position of the enclosing send/receive/close op.
   141  // For send and receive operations, this is the position of the <- token;
   142  // for close operations, it's the Lparen of the function call.
   143  //
   144  // TODO(adonovan): handle implicit receive operations from 'for...range chan' statements.
   145  func findOp(qpos *queryPos) token.Pos {
   146  	for _, n := range qpos.path {
   147  		switch n := n.(type) {
   148  		case *ast.UnaryExpr:
   149  			if n.Op == token.ARROW {
   150  				return n.OpPos
   151  			}
   152  		case *ast.SendStmt:
   153  			return n.Arrow
   154  		case *ast.CallExpr:
   155  			// close function call can only exist as a direct identifier
   156  			if close, ok := unparen(n.Fun).(*ast.Ident); ok {
   157  				if b, ok := qpos.info.Info.Uses[close].(*types.Builtin); ok && b.Name() == "close" {
   158  					return n.Lparen
   159  				}
   160  			}
   161  		}
   162  	}
   163  	return token.NoPos
   164  }
   165  
   166  // chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
   167  type chanOp struct {
   168  	ch  ssa.Value
   169  	dir types.ChanDir // SendOnly=send, RecvOnly=recv, SendRecv=close
   170  	pos token.Pos
   171  }
   172  
   173  // chanOps returns a slice of all the channel operations in the instruction.
   174  func chanOps(instr ssa.Instruction) []chanOp {
   175  	// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too.
   176  	var ops []chanOp
   177  	switch instr := instr.(type) {
   178  	case *ssa.UnOp:
   179  		if instr.Op == token.ARROW {
   180  			ops = append(ops, chanOp{instr.X, types.RecvOnly, instr.Pos()})
   181  		}
   182  	case *ssa.Send:
   183  		ops = append(ops, chanOp{instr.Chan, types.SendOnly, instr.Pos()})
   184  	case *ssa.Select:
   185  		for _, st := range instr.States {
   186  			ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
   187  		}
   188  	case ssa.CallInstruction:
   189  		cc := instr.Common()
   190  		if b, ok := cc.Value.(*ssa.Builtin); ok && b.Name() == "close" {
   191  			ops = append(ops, chanOp{cc.Args[0], types.SendRecv, cc.Pos()})
   192  		}
   193  	}
   194  	return ops
   195  }
   196  
   197  // TODO(adonovan): show the line of text for each pos, like "referrers" does.
   198  type peersResult struct {
   199  	queryPos                       token.Pos   // of queried channel op
   200  	queryType                      types.Type  // type of queried channel
   201  	makes, sends, receives, closes []token.Pos // positions of aliased makechan/send/receive/close instrs
   202  }
   203  
   204  func (r *peersResult) PrintPlain(printf printfFunc) {
   205  	if len(r.makes) == 0 {
   206  		printf(r.queryPos, "This channel can't point to anything.")
   207  		return
   208  	}
   209  	printf(r.queryPos, "This channel of type %s may be:", r.queryType)
   210  	for _, alloc := range r.makes {
   211  		printf(alloc, "\tallocated here")
   212  	}
   213  	for _, send := range r.sends {
   214  		printf(send, "\tsent to, here")
   215  	}
   216  	for _, receive := range r.receives {
   217  		printf(receive, "\treceived from, here")
   218  	}
   219  	for _, clos := range r.closes {
   220  		printf(clos, "\tclosed, here")
   221  	}
   222  }
   223  
   224  func (r *peersResult) JSON(fset *token.FileSet) []byte {
   225  	peers := &serial.Peers{
   226  		Pos:  fset.Position(r.queryPos).String(),
   227  		Type: r.queryType.String(),
   228  	}
   229  	for _, alloc := range r.makes {
   230  		peers.Allocs = append(peers.Allocs, fset.Position(alloc).String())
   231  	}
   232  	for _, send := range r.sends {
   233  		peers.Sends = append(peers.Sends, fset.Position(send).String())
   234  	}
   235  	for _, receive := range r.receives {
   236  		peers.Receives = append(peers.Receives, fset.Position(receive).String())
   237  	}
   238  	for _, clos := range r.closes {
   239  		peers.Closes = append(peers.Closes, fset.Position(clos).String())
   240  	}
   241  	return toJSON(peers)
   242  }
   243  
   244  // -------- utils --------
   245  
   246  // NB: byPos is not deterministic across packages since it depends on load order.
   247  // Use lessPos if the tests need it.
   248  type byPos []token.Pos
   249  
   250  func (p byPos) Len() int           { return len(p) }
   251  func (p byPos) Less(i, j int) bool { return p[i] < p[j] }
   252  func (p byPos) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }