+++ /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] }