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