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