1
0
mirror of https://github.com/golang/go synced 2024-10-01 11:28:34 -06:00
go/oracle/peers.go
Alan Donovan d2cdbefbfc go.tools/oracle: add option to output results in JSON syntax.
See json.go for interface specification.

Example usage:
% oracle -format=json -mode=callgraph code.google.com/p/go.tools/cmd/oracle

+ Tests, based on (small) golden files.

Overview:
  Each <query>Result structure has been "lowered" so that all
  but the most trivial logic in each display() function has
  been moved to the main query.

  Each one now has a toJSON method that populates a json.Result
  struct.  Though the <query>Result structs are similar to the
  correponding JSON protocol, they're not close enough to be
  used directly; for example, the former contain richer
  semantic entities (token.Pos, ast.Expr, ssa.Value,
  pointer.Pointer, etc) whereas JSON contains only their
  printed forms using Go basic types.

  The choices of what levels of abstractions the two sets of
  structs should have is somewhat arbitrary.  We may want
  richer information in the JSON output in future.

Details:
- oracle.Main has been split into oracle.Query() and the
  printing of the oracle.Result.
- the display() method no longer needs an *oracle param, only
  a print function.
- callees: sort the result for determinism.
- callees: compute the union across all contexts.
- callers: sort the results for determinism.
- describe(package): fixed a bug in the predicate for method
  accessibility: an unexported method defined in pkg A may
  belong to a type defined in package B (via
  embedding/promotion) and may thus be accessible to A.  New
  accessibleMethods() utility fixes this.
- describe(type): filter methods by accessibility.
- added tests of 'callgraph'.
- pointer: eliminated the 'caller CallGraphNode' parameter from
  pointer.Context.Call callback since it was redundant w.r.t
  site.Caller().
- added warning if CGO_ENABLED is unset.

R=crawshaw
CC=golang-dev
https://golang.org/cl/13270045
2013-09-03 15:29:02 -04:00

201 lines
5.5 KiB
Go

// 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 oracle
import (
"go/ast"
"go/token"
"sort"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa"
)
// 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}.
// 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(o *oracle) (queryResult, error) {
arrowPos := findArrow(o)
if arrowPos == token.NoPos {
return nil, o.errorf(o.queryPath[0], "there is no send/receive here")
}
buildSSA(o)
var queryOp chanOp // the originating send or receive operation
var ops []chanOp // all sends/receives of opposite direction
// Look at all send/receive instructions in the whole ssa.Program.
// Build a list of those of same type to query.
allFuncs := ssa.AllFunctions(o.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 == arrowPos {
queryOp = op // we found the query op
}
}
}
}
}
if queryOp.ch == nil {
return nil, o.errorf(arrowPos, "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()
channels := map[ssa.Value][]pointer.Pointer{queryOp.ch: nil}
i := 0
for _, op := range ops {
if types.IsIdentical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
channels[op.ch] = nil
ops[i] = op
i++
}
}
ops = ops[:i]
// Run the pointer analysis.
o.config.QueryValues = channels
ptrAnalysis(o)
// Combine the PT sets from all contexts.
queryChanPts := pointer.PointsToCombined(channels[queryOp.ch])
// Ascertain which make(chan) labels the query's channel can alias.
var makes []token.Pos
for _, label := range queryChanPts.Labels() {
makes = append(makes, label.Pos())
}
sort.Sort(byPos(makes))
// Ascertain which send/receive operations can alias the same make(chan) labels.
var sends, receives []token.Pos
for _, op := range ops {
for _, ptr := range o.config.QueryValues[op.ch] {
if ptr != nil && ptr.PointsTo().Intersects(queryChanPts) {
if op.dir == ast.SEND {
sends = append(sends, op.pos)
} else {
receives = append(receives, op.pos)
}
}
}
}
sort.Sort(byPos(sends))
sort.Sort(byPos(receives))
return &peersResult{
queryPos: arrowPos,
queryType: queryType,
makes: makes,
sends: sends,
receives: receives,
}, nil
}
// findArrow returns the position of the enclosing send/receive op
// (<-) for the query position, or token.NoPos if not found.
//
func findArrow(o *oracle) token.Pos {
for _, n := range o.queryPath {
switch n := n.(type) {
case *ast.UnaryExpr:
if n.Op == token.ARROW {
return n.OpPos
}
case *ast.SendStmt:
return n.Arrow
}
}
return token.NoPos
}
// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
type chanOp struct {
ch ssa.Value
dir ast.ChanDir
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} too.
var ops []chanOp
switch instr := instr.(type) {
case *ssa.UnOp:
if instr.Op == token.ARROW {
ops = append(ops, chanOp{instr.X, ast.RECV, instr.Pos()})
}
case *ssa.Send:
ops = append(ops, chanOp{instr.Chan, ast.SEND, instr.Pos()})
case *ssa.Select:
for _, st := range instr.States {
ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
}
}
return ops
}
type peersResult struct {
queryPos token.Pos // of queried '<-' token
queryType types.Type // type of queried channel
makes, sends, receives []token.Pos // positions of alisaed makechan/send/receive instrs
}
func (r *peersResult) display(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")
}
}
func (r *peersResult) toJSON(res *json.Result, fset *token.FileSet) {
peers := &json.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())
}
res.Peers = peers
}
// -------- utils --------
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] }