1
0
mirror of https://github.com/golang/go synced 2024-10-01 09:38:36 -06:00
go/oracle/peers.go
Alan Donovan 927e0f9da6 go.tools/oracle: describe: query content of lvalues, not their address.
Background: some ssa.Values represent lvalues, e.g.
      var g = new(string)
the *ssa.Global g is a **string, the address of what users
think of as the global g.

Querying pts(g) returns a singleton containing the object g, a
*string.  What users really want to see is what that in turn
points to, i.e. the label for the call to new().

This change now lets users make "indirect" pointer queries,
i.e. for pts(*v) where v is an ssa.Value.  The oracle makes an
indirect query if the type of the ssa.Value differs from the
source expression type by a pointer, i.e. it's an lvalue.

In other words, we're hiding the fact that compilers (e.g. ssa) internally represent globals by their address.

+ Tests.

This serendipitously fixed an outstanding bug mentioned in the
describe.go

R=crawshaw
CC=golang-dev
https://golang.org/cl/13532043
2013-09-09 21:06:25 -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.Indirect{queryOp.ch: false}
i := 0
for _, op := range ops {
if types.IsIdentical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
channels[op.ch] = false
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(o.config.QueryResults[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.QueryResults[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] }