--- /dev/null
+// Copyright 2013 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 main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "sort"
+
+ "golang.org/x/tools/cmd/guru/serial"
+ "golang.org/x/tools/go/loader"
+ "golang.org/x/tools/go/ssa"
+ "golang.org/x/tools/go/ssa/ssautil"
+)
+
+// peers enumerates, for a given channel send (or receive) operation,
+// the set of possible receives (or sends) that correspond to it.
+//
+// TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
+// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
+// or the implicit receive in "for v := range ch".
+func peers(q *Query) error {
+ lconf := loader.Config{Build: q.Build}
+
+ if err := setPTAScope(&lconf, q.Scope); err != nil {
+ return err
+ }
+
+ // Load/parse/type-check the program.
+ lprog, err := loadWithSoftErrors(&lconf)
+ if err != nil {
+ return err
+ }
+
+ qpos, err := parseQueryPos(lprog, q.Pos, false)
+ if err != nil {
+ return err
+ }
+
+ prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
+
+ ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
+ if err != nil {
+ return err
+ }
+
+ opPos := findOp(qpos)
+ if opPos == token.NoPos {
+ return fmt.Errorf("there is no channel operation here")
+ }
+
+ // Defer SSA construction till after errors are reported.
+ prog.Build()
+
+ var queryOp chanOp // the originating send or receive operation
+ var ops []chanOp // all sends/receives of opposite direction
+
+ // Look at all channel operations in the whole ssa.Program.
+ // Build a list of those of same type as the query.
+ allFuncs := ssautil.AllFunctions(prog)
+ for fn := range allFuncs {
+ for _, b := range fn.Blocks {
+ for _, instr := range b.Instrs {
+ for _, op := range chanOps(instr) {
+ ops = append(ops, op)
+ if op.pos == opPos {
+ queryOp = op // we found the query op
+ }
+ }
+ }
+ }
+ }
+ if queryOp.ch == nil {
+ return fmt.Errorf("ssa.Instruction for send/receive not found")
+ }
+
+ // Discard operations of wrong channel element type.
+ // Build set of channel ssa.Values as query to pointer analysis.
+ // We compare channels by element types, not channel types, to
+ // ignore both directionality and type names.
+ queryType := queryOp.ch.Type()
+ queryElemType := queryType.Underlying().(*types.Chan).Elem()
+ ptaConfig.AddQuery(queryOp.ch)
+ i := 0
+ for _, op := range ops {
+ if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
+ ptaConfig.AddQuery(op.ch)
+ ops[i] = op
+ i++
+ }
+ }
+ ops = ops[:i]
+
+ // Run the pointer analysis.
+ ptares := ptrAnalysis(ptaConfig)
+
+ // Find the points-to set.
+ queryChanPtr := ptares.Queries[queryOp.ch]
+
+ // Ascertain which make(chan) labels the query's channel can alias.
+ var makes []token.Pos
+ for _, label := range queryChanPtr.PointsTo().Labels() {
+ makes = append(makes, label.Pos())
+ }
+ sort.Sort(byPos(makes))
+
+ // Ascertain which channel operations can alias the same make(chan) labels.
+ var sends, receives, closes []token.Pos
+ for _, op := range ops {
+ if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) {
+ switch op.dir {
+ case types.SendOnly:
+ sends = append(sends, op.pos)
+ case types.RecvOnly:
+ receives = append(receives, op.pos)
+ case types.SendRecv:
+ closes = append(closes, op.pos)
+ }
+ }
+ }
+ sort.Sort(byPos(sends))
+ sort.Sort(byPos(receives))
+ sort.Sort(byPos(closes))
+
+ q.Output(lprog.Fset, &peersResult{
+ queryPos: opPos,
+ queryType: queryType,
+ makes: makes,
+ sends: sends,
+ receives: receives,
+ closes: closes,
+ })
+ return nil
+}
+
+// findOp returns the position of the enclosing send/receive/close op.
+// For send and receive operations, this is the position of the <- token;
+// for close operations, it's the Lparen of the function call.
+//
+// TODO(adonovan): handle implicit receive operations from 'for...range chan' statements.
+func findOp(qpos *queryPos) token.Pos {
+ for _, n := range qpos.path {
+ switch n := n.(type) {
+ case *ast.UnaryExpr:
+ if n.Op == token.ARROW {
+ return n.OpPos
+ }
+ case *ast.SendStmt:
+ return n.Arrow
+ case *ast.CallExpr:
+ // close function call can only exist as a direct identifier
+ if close, ok := unparen(n.Fun).(*ast.Ident); ok {
+ if b, ok := qpos.info.Info.Uses[close].(*types.Builtin); ok && b.Name() == "close" {
+ return n.Lparen
+ }
+ }
+ }
+ }
+ return token.NoPos
+}
+
+// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
+type chanOp struct {
+ ch ssa.Value
+ dir types.ChanDir // SendOnly=send, RecvOnly=recv, SendRecv=close
+ pos token.Pos
+}
+
+// chanOps returns a slice of all the channel operations in the instruction.
+func chanOps(instr ssa.Instruction) []chanOp {
+ // TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too.
+ var ops []chanOp
+ switch instr := instr.(type) {
+ case *ssa.UnOp:
+ if instr.Op == token.ARROW {
+ ops = append(ops, chanOp{instr.X, types.RecvOnly, instr.Pos()})
+ }
+ case *ssa.Send:
+ ops = append(ops, chanOp{instr.Chan, types.SendOnly, instr.Pos()})
+ case *ssa.Select:
+ for _, st := range instr.States {
+ ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
+ }
+ case ssa.CallInstruction:
+ cc := instr.Common()
+ if b, ok := cc.Value.(*ssa.Builtin); ok && b.Name() == "close" {
+ ops = append(ops, chanOp{cc.Args[0], types.SendRecv, cc.Pos()})
+ }
+ }
+ return ops
+}
+
+// TODO(adonovan): show the line of text for each pos, like "referrers" does.
+type peersResult struct {
+ queryPos token.Pos // of queried channel op
+ queryType types.Type // type of queried channel
+ makes, sends, receives, closes []token.Pos // positions of aliased makechan/send/receive/close instrs
+}
+
+func (r *peersResult) PrintPlain(printf printfFunc) {
+ if len(r.makes) == 0 {
+ printf(r.queryPos, "This channel can't point to anything.")
+ return
+ }
+ printf(r.queryPos, "This channel of type %s may be:", r.queryType)
+ for _, alloc := range r.makes {
+ printf(alloc, "\tallocated here")
+ }
+ for _, send := range r.sends {
+ printf(send, "\tsent to, here")
+ }
+ for _, receive := range r.receives {
+ printf(receive, "\treceived from, here")
+ }
+ for _, clos := range r.closes {
+ printf(clos, "\tclosed, here")
+ }
+}
+
+func (r *peersResult) JSON(fset *token.FileSet) []byte {
+ peers := &serial.Peers{
+ Pos: fset.Position(r.queryPos).String(),
+ Type: r.queryType.String(),
+ }
+ for _, alloc := range r.makes {
+ peers.Allocs = append(peers.Allocs, fset.Position(alloc).String())
+ }
+ for _, send := range r.sends {
+ peers.Sends = append(peers.Sends, fset.Position(send).String())
+ }
+ for _, receive := range r.receives {
+ peers.Receives = append(peers.Receives, fset.Position(receive).String())
+ }
+ for _, clos := range r.closes {
+ peers.Closes = append(peers.Closes, fset.Position(clos).String())
+ }
+ return toJSON(peers)
+}
+
+// -------- utils --------
+
+// NB: byPos is not deterministic across packages since it depends on load order.
+// Use lessPos if the tests need it.
+type byPos []token.Pos
+
+func (p byPos) Len() int { return len(p) }
+func (p byPos) Less(i, j int) bool { return p[i] < p[j] }
+func (p byPos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }