2013-08-27 16:49:13 -06:00
|
|
|
// 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.
|
|
|
|
|
2013-08-27 15:58:26 -06:00
|
|
|
package oracle
|
|
|
|
|
|
|
|
import (
|
2013-09-25 12:34:39 -06:00
|
|
|
"fmt"
|
2013-08-27 15:58:26 -06:00
|
|
|
"go/ast"
|
|
|
|
"go/token"
|
2013-09-03 13:29:02 -06:00
|
|
|
"sort"
|
2013-08-27 15:58:26 -06:00
|
|
|
|
2014-01-16 07:33:58 -07:00
|
|
|
"code.google.com/p/go.tools/go/ssa"
|
|
|
|
"code.google.com/p/go.tools/go/ssa/ssautil"
|
2013-08-27 15:58:26 -06:00
|
|
|
"code.google.com/p/go.tools/go/types"
|
2013-09-24 13:08:14 -06:00
|
|
|
"code.google.com/p/go.tools/oracle/serial"
|
2013-08-27 15:58:26 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
// peers enumerates, for a given channel send (or receive) operation,
|
|
|
|
// the set of possible receives (or sends) that correspond to it.
|
|
|
|
//
|
2014-03-11 16:24:39 -06:00
|
|
|
// TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
|
2013-08-27 15:58:26 -06:00
|
|
|
// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
|
|
|
|
// or the implicit receive in "for v := range ch".
|
2014-03-11 16:24:39 -06:00
|
|
|
// TODO(adonovan): support "close" as a channel op.
|
2013-08-27 15:58:26 -06:00
|
|
|
//
|
go.tools/oracle: refactor Oracle API to allow repeated queries on same scope.
The existing standalone Query function builds an importer, ssa.Program, oracle,
and query position, executes the query and returns the result.
For clients (such as Frederik Zipp's web-based github.com/fzipp/pythia tool)
that wish to load the program once and make several queries, we now expose
these as separate operations too. Here's a client, in pseudocode:
o := oracle.New(...)
for ... {
qpos := o.ParseQueryPos(...)
res := o.Query(mode, qpos)
print result
}
NB: this is a slight deoptimisation in the one-shot case since we have to
build the entire SSA program with debug info, not just the query package,
since we now don't know the query package at that time.
The 'exact' param to ParseQueryPos needs more thought since its
ideal value is a function of the query mode. This will do for now.
Details:
- expose Oracle type, New() func and Query() method.
- expose QueryPos type and ParseQueryPos func.
- improved package doc comment.
- un-exposed the "needs" bits.
- added test.
R=crawshaw
CC=frederik.zipp, golang-dev
https://golang.org/cl/13810043
2013-09-23 13:02:18 -06:00
|
|
|
func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|
|
|
arrowPos := findArrow(qpos)
|
2013-08-29 19:36:59 -06:00
|
|
|
if arrowPos == token.NoPos {
|
2013-09-25 12:34:39 -06:00
|
|
|
return nil, fmt.Errorf("there is no send/receive here")
|
2013-08-27 15:58:26 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2013-12-05 15:16:00 -07:00
|
|
|
allFuncs := ssautil.AllFunctions(o.prog)
|
2013-08-27 15:58:26 -06:00
|
|
|
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 {
|
2013-09-25 12:34:39 -06:00
|
|
|
return nil, fmt.Errorf("ssa.Instruction for send/receive not found")
|
2013-08-27 15:58:26 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Discard operations of wrong channel element type.
|
|
|
|
// Build set of channel ssa.Values as query to pointer analysis.
|
2013-09-03 13:29:02 -06:00
|
|
|
// 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()
|
2013-12-13 08:04:55 -07:00
|
|
|
o.ptaConfig.AddQuery(queryOp.ch)
|
2013-08-27 15:58:26 -06:00
|
|
|
i := 0
|
|
|
|
for _, op := range ops {
|
2014-01-28 11:57:56 -07:00
|
|
|
if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
|
2013-12-13 08:04:55 -07:00
|
|
|
o.ptaConfig.AddQuery(op.ch)
|
2013-08-27 15:58:26 -06:00
|
|
|
ops[i] = op
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ops = ops[:i]
|
|
|
|
|
|
|
|
// Run the pointer analysis.
|
2013-09-25 15:17:42 -06:00
|
|
|
ptares := ptrAnalysis(o)
|
2013-08-27 15:58:26 -06:00
|
|
|
|
2014-02-20 09:35:09 -07:00
|
|
|
// Find the points-to set.
|
|
|
|
queryChanPtr := ptares.Queries[queryOp.ch]
|
2013-08-27 15:58:26 -06:00
|
|
|
|
2013-09-03 13:29:02 -06:00
|
|
|
// Ascertain which make(chan) labels the query's channel can alias.
|
|
|
|
var makes []token.Pos
|
2014-02-20 09:35:09 -07:00
|
|
|
for _, label := range queryChanPtr.PointsTo().Labels() {
|
2013-09-03 13:29:02 -06:00
|
|
|
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 {
|
2014-02-20 09:35:09 -07:00
|
|
|
if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) {
|
|
|
|
if op.dir == types.SendOnly {
|
|
|
|
sends = append(sends, op.pos)
|
|
|
|
} else {
|
|
|
|
receives = append(receives, op.pos)
|
2013-09-03 13:29:02 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Sort(byPos(sends))
|
|
|
|
sort.Sort(byPos(receives))
|
|
|
|
|
2013-08-27 15:58:26 -06:00
|
|
|
return &peersResult{
|
2013-09-03 13:29:02 -06:00
|
|
|
queryPos: arrowPos,
|
|
|
|
queryType: queryType,
|
|
|
|
makes: makes,
|
|
|
|
sends: sends,
|
|
|
|
receives: receives,
|
2013-08-27 15:58:26 -06:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2013-08-29 19:36:59 -06:00
|
|
|
// findArrow returns the position of the enclosing send/receive op
|
|
|
|
// (<-) for the query position, or token.NoPos if not found.
|
|
|
|
//
|
go.tools/oracle: refactor Oracle API to allow repeated queries on same scope.
The existing standalone Query function builds an importer, ssa.Program, oracle,
and query position, executes the query and returns the result.
For clients (such as Frederik Zipp's web-based github.com/fzipp/pythia tool)
that wish to load the program once and make several queries, we now expose
these as separate operations too. Here's a client, in pseudocode:
o := oracle.New(...)
for ... {
qpos := o.ParseQueryPos(...)
res := o.Query(mode, qpos)
print result
}
NB: this is a slight deoptimisation in the one-shot case since we have to
build the entire SSA program with debug info, not just the query package,
since we now don't know the query package at that time.
The 'exact' param to ParseQueryPos needs more thought since its
ideal value is a function of the query mode. This will do for now.
Details:
- expose Oracle type, New() func and Query() method.
- expose QueryPos type and ParseQueryPos func.
- improved package doc comment.
- un-exposed the "needs" bits.
- added test.
R=crawshaw
CC=frederik.zipp, golang-dev
https://golang.org/cl/13810043
2013-09-23 13:02:18 -06:00
|
|
|
func findArrow(qpos *QueryPos) token.Pos {
|
|
|
|
for _, n := range qpos.path {
|
2013-08-29 19:36:59 -06:00
|
|
|
switch n := n.(type) {
|
|
|
|
case *ast.UnaryExpr:
|
|
|
|
if n.Op == token.ARROW {
|
|
|
|
return n.OpPos
|
|
|
|
}
|
|
|
|
case *ast.SendStmt:
|
|
|
|
return n.Arrow
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return token.NoPos
|
|
|
|
}
|
|
|
|
|
2013-08-27 15:58:26 -06:00
|
|
|
// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
|
|
|
|
type chanOp struct {
|
|
|
|
ch ssa.Value
|
2013-12-17 16:45:01 -07:00
|
|
|
dir types.ChanDir // SendOnly or RecvOnly
|
2013-08-27 15:58:26 -06:00
|
|
|
pos token.Pos
|
|
|
|
}
|
|
|
|
|
|
|
|
// chanOps returns a slice of all the channel operations in the instruction.
|
|
|
|
func chanOps(instr ssa.Instruction) []chanOp {
|
2014-03-11 16:24:39 -06:00
|
|
|
// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too.
|
2013-08-27 15:58:26 -06:00
|
|
|
var ops []chanOp
|
|
|
|
switch instr := instr.(type) {
|
|
|
|
case *ssa.UnOp:
|
|
|
|
if instr.Op == token.ARROW {
|
2013-12-17 16:45:01 -07:00
|
|
|
ops = append(ops, chanOp{instr.X, types.RecvOnly, instr.Pos()})
|
2013-08-27 15:58:26 -06:00
|
|
|
}
|
|
|
|
case *ssa.Send:
|
2013-12-17 16:45:01 -07:00
|
|
|
ops = append(ops, chanOp{instr.Chan, types.SendOnly, instr.Pos()})
|
2013-08-27 15:58:26 -06:00
|
|
|
case *ssa.Select:
|
|
|
|
for _, st := range instr.States {
|
|
|
|
ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ops
|
|
|
|
}
|
|
|
|
|
|
|
|
type peersResult struct {
|
2013-09-03 13:29:02 -06:00
|
|
|
queryPos token.Pos // of queried '<-' token
|
|
|
|
queryType types.Type // type of queried channel
|
2014-05-02 15:38:08 -06:00
|
|
|
makes, sends, receives []token.Pos // positions of aliased makechan/send/receive instrs
|
2013-08-27 15:58:26 -06:00
|
|
|
}
|
|
|
|
|
2013-09-03 13:29:02 -06:00
|
|
|
func (r *peersResult) display(printf printfFunc) {
|
|
|
|
if len(r.makes) == 0 {
|
|
|
|
printf(r.queryPos, "This channel can't point to anything.")
|
2013-08-27 15:58:26 -06:00
|
|
|
return
|
|
|
|
}
|
2013-09-03 13:29:02 -06:00
|
|
|
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")
|
2013-08-27 15:58:26 -06:00
|
|
|
}
|
2013-09-03 13:29:02 -06:00
|
|
|
}
|
2013-08-27 15:58:26 -06:00
|
|
|
|
2013-09-24 13:08:14 -06:00
|
|
|
func (r *peersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
|
|
peers := &serial.Peers{
|
2013-09-03 13:29:02 -06:00
|
|
|
Pos: fset.Position(r.queryPos).String(),
|
|
|
|
Type: r.queryType.String(),
|
2013-08-27 15:58:26 -06:00
|
|
|
}
|
2013-09-03 13:29:02 -06:00
|
|
|
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
|
2013-08-27 15:58:26 -06:00
|
|
|
}
|
2013-09-03 13:29:02 -06:00
|
|
|
|
|
|
|
// -------- 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] }
|