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
This commit is contained in:
Alan Donovan 2013-09-03 15:29:02 -04:00
parent 0126405cad
commit d2cdbefbfc
29 changed files with 1523 additions and 353 deletions

View File

@ -16,6 +16,8 @@ package main
import ( import (
"bufio" "bufio"
"bytes"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -37,6 +39,8 @@ var modeFlag = flag.String("mode", "",
var ptalogFlag = flag.String("ptalog", "", var ptalogFlag = flag.String("ptalog", "",
"Location of the points-to analysis log file, or empty to disable logging.") "Location of the points-to analysis log file, or empty to disable logging.")
var formatFlag = flag.String("format", "plain", "Output format: 'plain' or 'json'.")
const usage = `Go source code oracle. const usage = `Go source code oracle.
Usage: oracle [<flag> ...] [<file.go> ...] [<arg> ...] Usage: oracle [<flag> ...] [<file.go> ...] [<arg> ...]
Use -help flag to display options. Use -help flag to display options.
@ -63,6 +67,16 @@ func init() {
} }
runtime.GOMAXPROCS(n) runtime.GOMAXPROCS(n)
} }
// For now, caller must---before go/build.init runs---specify
// CGO_ENABLED=0, which entails the "!cgo" go/build tag,
// preferring (dummy) Go to native C implementations of
// cgoLookupHost et al.
// TODO(adonovan): make the importer do this.
if os.Getenv("CGO_ENABLED") != "0" {
fmt.Fprint(os.Stderr, "Warning: CGO_ENABLED=0 not specified; "+
"analysis of cgo code may be less precise.\n")
}
} }
func main() { func main() {
@ -99,8 +113,35 @@ func main() {
defer pprof.StopCPUProfile() defer pprof.StopCPUProfile()
} }
if err := oracle.Main(args, *modeFlag, *posFlag, ptalog, os.Stdout, nil); err != nil { // -format flag
fmt.Fprintln(os.Stderr, err.Error()) if *formatFlag != "json" && *formatFlag != "plain" {
fmt.Fprintf(os.Stderr, "illegal -format value: %q", *formatFlag)
os.Exit(1) os.Exit(1)
} }
// Ask the oracle.
res, err := oracle.Query(args, *modeFlag, *posFlag, ptalog, nil)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Print the result.
switch *formatFlag {
case "json":
b, err := json.Marshal(res)
if err != nil {
fmt.Fprintf(os.Stderr, "JSON error: %s\n", err.Error())
os.Exit(1)
}
var buf bytes.Buffer
if err := json.Indent(&buf, b, "", "\t"); err != nil {
fmt.Fprintf(os.Stderr, "json.Indent failed: %s", err)
os.Exit(1)
}
os.Stdout.Write(buf.Bytes())
case "plain":
res.WriteTo(os.Stdout)
}
} }

View File

@ -6,9 +6,13 @@ package oracle
import ( import (
"go/ast" "go/ast"
"go/token"
"sort"
"code.google.com/p/go.tools/go/types" "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/pointer"
"code.google.com/p/go.tools/ssa"
) )
// Callees reports the possible callees of the function call site // Callees reports the possible callees of the function call site
@ -51,51 +55,78 @@ func callees(o *oracle) (queryResult, error) {
// The presence of a key indicates this call site is // The presence of a key indicates this call site is
// interesting even if the value is nil. // interesting even if the value is nil.
querySites := make(map[pointer.CallSite][]pointer.CallGraphNode) querySites := make(map[pointer.CallSite][]pointer.CallGraphNode)
var arbitrarySite pointer.CallSite
o.config.CallSite = func(site pointer.CallSite) { o.config.CallSite = func(site pointer.CallSite) {
if site.Pos() == call.Lparen { if site.Pos() == call.Lparen {
// Not a no-op! Ensures key is // Not a no-op! Ensures key is
// present even if value is nil: // present even if value is nil:
querySites[site] = querySites[site] querySites[site] = querySites[site]
arbitrarySite = site
} }
} }
o.config.Call = func(site pointer.CallSite, caller, callee pointer.CallGraphNode) { o.config.Call = func(site pointer.CallSite, callee pointer.CallGraphNode) {
if targets, ok := querySites[site]; ok { if targets, ok := querySites[site]; ok {
querySites[site] = append(targets, callee) querySites[site] = append(targets, callee)
} }
} }
ptrAnalysis(o) ptrAnalysis(o)
if arbitrarySite == nil {
return nil, o.errorf(call.Lparen, "this call site is unreachable in this analysis")
}
// Compute union of callees across all contexts.
funcsMap := make(map[*ssa.Function]bool)
for _, callees := range querySites {
for _, callee := range callees {
funcsMap[callee.Func()] = true
}
}
funcs := make([]*ssa.Function, 0, len(funcsMap))
for f := range funcsMap {
funcs = append(funcs, f)
}
sort.Sort(byFuncPos(funcs))
return &calleesResult{ return &calleesResult{
call: call, site: arbitrarySite,
querySites: querySites, funcs: funcs,
}, nil }, nil
} }
type calleesResult struct { type calleesResult struct {
call *ast.CallExpr site pointer.CallSite
querySites map[pointer.CallSite][]pointer.CallGraphNode funcs []*ssa.Function
} }
func (r *calleesResult) display(o *oracle) { func (r *calleesResult) display(printf printfFunc) {
// Print the set of discovered call edges. if len(r.funcs) == 0 {
if len(r.querySites) == 0 {
// e.g. it appears within "if false {...}" or within a dead function.
o.printf(r.call.Lparen, "this call site is unreachable in this analysis")
}
// TODO(adonovan): sort, to ensure test determinism.
// TODO(adonovan): compute union of callees across all contexts.
for site, callees := range r.querySites {
if callees == nil {
// dynamic call on a provably nil func/interface // dynamic call on a provably nil func/interface
o.printf(site, "%s on nil value", site.Description()) printf(r.site, "%s on nil value", r.site.Description())
continue } else {
printf(r.site, "this %s dispatches to:", r.site.Description())
for _, callee := range r.funcs {
printf(callee, "\t%s", callee)
}
}
} }
// TODO(adonovan): sort, to ensure test determinism. func (r *calleesResult) toJSON(res *json.Result, fset *token.FileSet) {
o.printf(site, "this %s dispatches to:", site.Description()) j := &json.Callees{
for _, callee := range callees { Pos: r.site.Caller().Func().Prog.Fset.Position(r.site.Pos()).String(),
o.printf(callee.Func(), "\t%s", callee.Func()) Desc: r.site.Description(),
} }
for _, callee := range r.funcs {
j.Callees = append(j.Callees, &json.CalleesItem{
Name: callee.String(),
Pos: callee.Prog.Fset.Position(callee.Pos()).String(),
})
} }
res.Callees = j
} }
type byFuncPos []*ssa.Function
func (a byFuncPos) Len() int { return len(a) }
func (a byFuncPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
func (a byFuncPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

View File

@ -5,6 +5,9 @@
package oracle package oracle
import ( import (
"go/token"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/pointer" "code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa" "code.google.com/p/go.tools/ssa"
) )
@ -32,12 +35,14 @@ func callers(o *oracle) (queryResult, error) {
// Run the pointer analysis, recording each // Run the pointer analysis, recording each
// call found to originate from target. // call found to originate from target.
var calls []callersCall var calls []pointer.CallSite
o.config.Call = func(site pointer.CallSite, caller, callee pointer.CallGraphNode) { o.config.Call = func(site pointer.CallSite, callee pointer.CallGraphNode) {
if callee.Func() == target { if callee.Func() == target {
calls = append(calls, callersCall{site, caller}) calls = append(calls, site)
} }
} }
// TODO(adonovan): sort calls, to ensure test determinism.
root := ptrAnalysis(o) root := ptrAnalysis(o)
return &callersResult{ return &callersResult{
@ -50,28 +55,36 @@ func callers(o *oracle) (queryResult, error) {
type callersResult struct { type callersResult struct {
target *ssa.Function target *ssa.Function
root pointer.CallGraphNode root pointer.CallGraphNode
calls []callersCall calls []pointer.CallSite
} }
type callersCall struct { func (r *callersResult) display(printf printfFunc) {
site pointer.CallSite
caller pointer.CallGraphNode
}
func (r *callersResult) display(o *oracle) {
if r.calls == nil { if r.calls == nil {
o.printf(r.target, "%s is not reachable in this program.", r.target) printf(r.target, "%s is not reachable in this program.", r.target)
} else { } else {
o.printf(r.target, "%s is called from these %d sites:", r.target, len(r.calls)) printf(r.target, "%s is called from these %d sites:", r.target, len(r.calls))
// TODO(adonovan): sort, to ensure test determinism. for _, site := range r.calls {
for _, call := range r.calls { if site.Caller() == r.root {
if call.caller == r.root { printf(r.target, "the root of the call graph")
o.printf(r.target, "the root of the call graph")
} else { } else {
o.printf(call.site, "\t%s from %s", printf(site, "\t%s from %s", site.Description(), site.Caller().Func())
call.site.Description(), call.caller.Func()) }
} }
} }
} }
func (r *callersResult) toJSON(res *json.Result, fset *token.FileSet) {
var callers []json.Caller
for _, site := range r.calls {
var c json.Caller
c.Caller = site.Caller().Func().String()
if site.Caller() == r.root {
c.Desc = "synthetic call"
} else {
c.Pos = site.Caller().Func().Prog.Fset.Position(site.Pos()).String()
c.Desc = site.Description()
}
callers = append(callers, c)
}
res.Callers = callers
} }

View File

@ -5,8 +5,10 @@
package oracle package oracle
import ( import (
"go/token"
"strings" "strings"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/pointer" "code.google.com/p/go.tools/pointer"
) )
@ -16,7 +18,12 @@ import (
// context sensitivity. // context sensitivity.
// //
// TODO(adonovan): add options for restricting the display to a region // TODO(adonovan): add options for restricting the display to a region
// of interest: function, package, subgraph, dirtree, etc. // of interest: function, package, subgraph, dirtree, goroutine, etc.
//
// TODO(adonovan): add an option to project away context sensitivity.
// The callgraph API should provide this feature.
//
// TODO(adonovan): elide nodes for synthetic functions?
// //
func callgraph(o *oracle) (queryResult, error) { func callgraph(o *oracle) (queryResult, error) {
buildSSA(o) buildSSA(o)
@ -26,34 +33,35 @@ func callgraph(o *oracle) (queryResult, error) {
o.config.Call = callgraph.AddEdge o.config.Call = callgraph.AddEdge
root := ptrAnalysis(o) root := ptrAnalysis(o)
// Assign (preorder) numbers to all the callgraph nodes.
// TODO(adonovan): the callgraph API should do this for us.
numbering := make(map[pointer.CallGraphNode]int)
var number func(cgn pointer.CallGraphNode)
number = func(cgn pointer.CallGraphNode) {
if _, ok := numbering[cgn]; !ok {
numbering[cgn] = len(numbering)
for callee := range callgraph[cgn] {
number(callee)
}
}
}
number(root)
return &callgraphResult{ return &callgraphResult{
root: root, root: root,
callgraph: callgraph, callgraph: callgraph,
numbering: numbering,
}, nil }, nil
} }
type callgraphResult struct { type callgraphResult struct {
root pointer.CallGraphNode root pointer.CallGraphNode
callgraph pointer.CallGraph callgraph pointer.CallGraph
numbering map[pointer.CallGraphNode]int
numbering map[pointer.CallGraphNode]int // used by display
} }
func (r *callgraphResult) print(o *oracle, cgn pointer.CallGraphNode, indent int) { func (r *callgraphResult) display(printf printfFunc) {
if n := r.numbering[cgn]; n == 0 { printf(nil, `
n = 1 + len(r.numbering)
r.numbering[cgn] = n
o.printf(cgn.Func(), "%d\t%s%s", n, strings.Repeat(" ", indent), cgn.Func())
for callee := range r.callgraph[cgn] {
r.print(o, callee, indent+1)
}
} else {
o.printf(cgn.Func(), "\t%s%s (%d)", strings.Repeat(" ", indent), cgn.Func(), n)
}
}
func (r *callgraphResult) display(o *oracle) {
o.printf(nil, `
Below is a call graph of the entire program. Below is a call graph of the entire program.
The numbered nodes form a spanning tree. The numbered nodes form a spanning tree.
Non-numbered nodes indicate back- or cross-edges to the node whose Non-numbered nodes indicate back- or cross-edges to the node whose
@ -62,6 +70,33 @@ Some nodes may appear multiple times due to context-sensitive
treatment of some calls. treatment of some calls.
`) `)
r.numbering = make(map[pointer.CallGraphNode]int) seen := make(map[pointer.CallGraphNode]bool)
r.print(o, r.root, 0) var print func(cgn pointer.CallGraphNode, indent int)
print = func(cgn pointer.CallGraphNode, indent int) {
n := r.numbering[cgn]
if !seen[cgn] {
seen[cgn] = true
printf(cgn.Func(), "%d\t%s%s", n, strings.Repeat(" ", indent), cgn.Func())
for callee := range r.callgraph[cgn] {
print(callee, indent+1)
}
} else {
printf(cgn.Func(), "\t%s%s (%d)", strings.Repeat(" ", indent), cgn.Func(), n)
}
}
print(r.root, 0)
}
func (r *callgraphResult) toJSON(res *json.Result, fset *token.FileSet) {
cg := make([]json.CallGraph, len(r.numbering))
for n, i := range r.numbering {
j := &cg[i]
fn := n.Func()
j.Name = fn.String()
j.Pos = fn.Prog.Fset.Position(fn.Pos()).String()
for callee := range r.callgraph[n] {
j.Children = append(j.Children, r.numbering[callee])
}
}
res.Callgraph = cg
} }

View File

@ -5,6 +5,9 @@
package oracle package oracle
import ( import (
"go/token"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/pointer" "code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa" "code.google.com/p/go.tools/ssa"
) )
@ -42,32 +45,21 @@ func callstack(o *oracle) (queryResult, error) {
o.config.Call = callgraph.AddEdge o.config.Call = callgraph.AddEdge
root := ptrAnalysis(o) root := ptrAnalysis(o)
return &callstackResult{ seen := make(map[pointer.CallGraphNode]bool)
target: target, var callstack []pointer.CallSite
root: root,
callgraph: callgraph,
}, nil
}
type callstackResult struct { // Use depth-first search to find an arbitrary path from a
target *ssa.Function // root to the target function.
root pointer.CallGraphNode var search func(cgn pointer.CallGraphNode) bool
callgraph pointer.CallGraph search = func(cgn pointer.CallGraphNode) bool {
if !seen[cgn] {
seen map[pointer.CallGraphNode]bool // used by display seen[cgn] = true
} if cgn.Func() == target {
func (r *callstackResult) search(o *oracle, cgn pointer.CallGraphNode) bool {
if !r.seen[cgn] {
r.seen[cgn] = true
if cgn.Func() == r.target {
o.printf(o, "Found a call path from root to %s", r.target)
o.printf(r.target, "%s", r.target)
return true return true
} }
for callee, site := range r.callgraph[cgn] { for callee, site := range callgraph[cgn] {
if r.search(o, callee) { if search(callee) {
o.printf(site, "%s from %s", site.Description(), cgn.Func()) callstack = append(callstack, site)
return true return true
} }
} }
@ -75,16 +67,47 @@ func (r *callstackResult) search(o *oracle, cgn pointer.CallGraphNode) bool {
return false return false
} }
func (r *callstackResult) display(o *oracle) { for toplevel := range callgraph[root] {
// Show only an arbitrary path from a root to the current function. if search(toplevel) {
// We use depth-first search. break
}
}
r.seen = make(map[pointer.CallGraphNode]bool) return &callstackResult{
target: target,
callstack: callstack,
}, nil
}
for toplevel := range r.callgraph[r.root] { type callstackResult struct {
if r.search(o, toplevel) { target *ssa.Function
return callstack []pointer.CallSite
}
func (r *callstackResult) display(printf printfFunc) {
if r.callstack != nil {
printf(false, "Found a call path from root to %s", r.target)
printf(r.target, "%s", r.target)
for _, site := range r.callstack {
printf(site, "%s from %s", site.Description(), site.Caller().Func())
}
} else {
printf(r.target, "%s is unreachable in this analysis scope", r.target)
} }
} }
o.printf(r.target, "%s is unreachable in this analysis scope", r.target)
func (r *callstackResult) toJSON(res *json.Result, fset *token.FileSet) {
var callers []json.Caller
for _, site := range r.callstack {
callers = append(callers, json.Caller{
Pos: site.Caller().Func().Prog.Fset.Position(site.Pos()).String(),
Caller: site.Caller().Func().String(),
Desc: site.Description(),
})
}
res.Callstack = &json.CallStack{
Pos: r.target.Prog.Fset.Position(r.target.Pos()).String(),
Target: r.target.String(),
Callers: callers,
}
} }

View File

@ -9,18 +9,19 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"os"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/pointer" "code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa" "code.google.com/p/go.tools/ssa"
) )
// TODO(adonovan): all printed sets must be sorted to ensure test determinism.
// describe describes the syntax node denoted by the query position, // describe describes the syntax node denoted by the query position,
// including: // including:
// - its syntactic category // - its syntactic category
@ -29,9 +30,11 @@ import (
// - its points-to set (for a pointer-like expression) // - its points-to set (for a pointer-like expression)
// - its concrete types (for an interface expression) and their points-to sets. // - its concrete types (for an interface expression) and their points-to sets.
// //
// All printed sets are sorted to ensure determinism.
//
func describe(o *oracle) (queryResult, error) { func describe(o *oracle) (queryResult, error) {
if false { // debugging if false { // debugging
o.printf(o.queryPath[0], "you selected: %s %s", o.fprintf(os.Stderr, o.queryPath[0], "you selected: %s %s",
importer.NodeDescription(o.queryPath[0]), pathToString2(o.queryPath)) importer.NodeDescription(o.queryPath[0]), pathToString2(o.queryPath))
} }
@ -61,9 +64,16 @@ type describeUnknownResult struct {
node ast.Node node ast.Node
} }
func (r *describeUnknownResult) display(o *oracle) { func (r *describeUnknownResult) display(printf printfFunc) {
// Nothing much to say about misc syntax. // Nothing much to say about misc syntax.
o.printf(r.node, "%s", importer.NodeDescription(r.node)) printf(r.node, "%s", importer.NodeDescription(r.node))
}
func (r *describeUnknownResult) toJSON(res *json.Result, fset *token.FileSet) {
res.Describe = &json.Describe{
Desc: importer.NodeDescription(r.node),
Pos: fset.Position(r.node.Pos()).String(),
}
} }
type action int type action int
@ -99,7 +109,8 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
// we won't even reach here. Can we do better? // we won't even reach here. Can we do better?
// TODO(adonovan): describing a field within 'type T struct {...}' // TODO(adonovan): describing a field within 'type T struct {...}'
// describes the (anonymous) struct type and concludes "no methods". Fix. // describes the (anonymous) struct type and concludes "no methods".
// We should ascend to the enclosing type decl, if any.
for len(path) > 0 { for len(path) > 0 {
switch n := path[0].(type) { switch n := path[0].(type) {
@ -344,6 +355,15 @@ func describeValue(o *oracle, path []ast.Node) (*describeValueResult, error) {
// From this point on, we cannot fail with an error. // From this point on, we cannot fail with an error.
// Failure to run the pointer analysis will be reported later. // Failure to run the pointer analysis will be reported later.
//
// Our disposition to pointer analysis may be one of the following:
// - ok: ssa.Value was const or func.
// - error: no ssa.Value for expr (e.g. trivially dead code)
// - ok: ssa.Value is non-pointerlike
// - error: no Pointer for ssa.Value (e.g. analytically unreachable)
// - ok: Pointer has empty points-to set
// - ok: Pointer has non-empty points-to set
// ptaErr is non-nil only in the "error:" cases.
var value ssa.Value var value ssa.Value
var ptaErr error var ptaErr error
@ -367,113 +387,188 @@ func describeValue(o *oracle, path []ast.Node) (*describeValueResult, error) {
} }
// Run pointer analysis of the selected SSA value. // Run pointer analysis of the selected SSA value.
var ptrs []pointer.Pointer var ptrs []pointerResult
if value != nil { if value != nil {
buildSSA(o) buildSSA(o)
o.config.QueryValues = map[ssa.Value][]pointer.Pointer{value: nil} o.config.QueryValues = map[ssa.Value][]pointer.Pointer{value: nil}
ptrAnalysis(o) ptrAnalysis(o)
ptrs = o.config.QueryValues[value]
// Combine the PT sets from all contexts.
pointers := o.config.QueryValues[value]
if pointers == nil {
ptaErr = fmt.Errorf("PTA did not encounter this expression (dead code?)")
} }
pts := pointer.PointsToCombined(pointers)
if _, ok := value.Type().Underlying().(*types.Interface); ok {
// Show concrete types for interface expression.
if concs := pts.ConcreteTypes(); concs.Len() > 0 {
concs.Iterate(func(conc types.Type, pta interface{}) {
combined := pointer.PointsToCombined(pta.([]pointer.Pointer))
labels := combined.Labels()
sort.Sort(byPosAndString(labels)) // to ensure determinism
ptrs = append(ptrs, pointerResult{conc, labels})
})
}
} else {
// Show labels for other expressions.
labels := pts.Labels()
sort.Sort(byPosAndString(labels)) // to ensure determinism
ptrs = append(ptrs, pointerResult{value.Type(), labels})
}
}
sort.Sort(byTypeString(ptrs)) // to ensure determinism
typ := o.queryPkgInfo.TypeOf(expr)
constVal := o.queryPkgInfo.ValueOf(expr)
return &describeValueResult{ return &describeValueResult{
expr: expr, expr: expr,
typ: typ,
constVal: constVal,
obj: obj, obj: obj,
value: value,
ptaErr: ptaErr, ptaErr: ptaErr,
ptrs: ptrs, ptrs: ptrs,
}, nil }, nil
} }
type describeValueResult struct { type pointerResult struct {
expr ast.Expr // query node typ types.Type // type of the pointer (always concrete)
obj types.Object // var/func/const object, if expr was Ident labels []*pointer.Label
value ssa.Value // ssa.Value for pointer analysis query
ptaErr error // explanation of why we couldn't run pointer analysis
ptrs []pointer.Pointer // result of pointer analysis query
} }
func (r *describeValueResult) display(o *oracle) { type describeValueResult struct {
expr ast.Expr // query node
typ types.Type // type of expression
constVal exact.Value // value of expression, if constant
obj types.Object // var/func/const object, if expr was Ident
ptaErr error // reason why pointer analysis couldn't be run, or failed
ptrs []pointerResult // pointer info (typ is concrete => len==1)
}
func (r *describeValueResult) display(printf printfFunc) {
suffix := "" suffix := ""
if val := o.queryPkgInfo.ValueOf(r.expr); val != nil { if r.constVal != nil {
suffix = fmt.Sprintf(" of constant value %s", val) suffix = fmt.Sprintf(" of constant value %s", r.constVal)
} }
// Describe the expression. // Describe the expression.
if r.obj != nil { if r.obj != nil {
if r.obj.Pos() == r.expr.Pos() { if r.obj.Pos() == r.expr.Pos() {
// defining ident // defining ident
o.printf(r.expr, "definition of %s%s", r.obj, suffix) printf(r.expr, "definition of %s%s", r.obj, suffix)
} else { } else {
// referring ident // referring ident
o.printf(r.expr, "reference to %s%s", r.obj, suffix) printf(r.expr, "reference to %s%s", r.obj, suffix)
if def := r.obj.Pos(); def != token.NoPos { if def := r.obj.Pos(); def != token.NoPos {
o.printf(def, "defined here") printf(def, "defined here")
} }
} }
} else { } else {
desc := importer.NodeDescription(r.expr) desc := importer.NodeDescription(r.expr)
if suffix != "" { if suffix != "" {
// constant expression // constant expression
o.printf(r.expr, "%s%s", desc, suffix) printf(r.expr, "%s%s", desc, suffix)
} else { } else {
// non-constant expression // non-constant expression
o.printf(r.expr, "%s of type %s", desc, o.queryPkgInfo.TypeOf(r.expr)) printf(r.expr, "%s of type %s", desc, r.typ)
} }
} }
if r.value == nil { // pointer analysis could not be run
// pointer analysis was not run
if r.ptaErr != nil { if r.ptaErr != nil {
o.printf(r.expr, "no pointer analysis: %s", r.ptaErr) printf(r.expr, "no points-to information: %s", r.ptaErr)
}
return return
} }
if r.ptrs == nil { if r.ptrs == nil {
o.printf(r.expr, "pointer analysis did not analyze this expression (dead code?)") return // PTA was not invoked (not an error)
return
} }
// Display the results of pointer analysis. // Display the results of pointer analysis.
if _, ok := r.typ.Underlying().(*types.Interface); ok {
// Combine the PT sets from all contexts.
pts := pointer.PointsToCombined(r.ptrs)
// Report which make(chan) labels the query's channel can alias.
if _, ok := r.value.Type().Underlying().(*types.Interface); ok {
// Show concrete types for interface expression. // Show concrete types for interface expression.
if concs := pts.ConcreteTypes(); concs.Len() > 0 { if len(r.ptrs) > 0 {
o.printf(o, "interface may contain these concrete types:") printf(false, "interface may contain these concrete types:")
// TODO(adonovan): must sort to ensure deterministic test behaviour. for _, ptr := range r.ptrs {
concs.Iterate(func(conc types.Type, ptrs interface{}) {
var obj types.Object var obj types.Object
if nt, ok := deref(conc).(*types.Named); ok { if nt, ok := deref(ptr.typ).(*types.Named); ok {
obj = nt.Obj() obj = nt.Obj()
} }
if len(ptr.labels) > 0 {
pts := pointer.PointsToCombined(ptrs.([]pointer.Pointer)) printf(obj, "\t%s, may point to:", ptr.typ)
if labels := pts.Labels(); len(labels) > 0 { printLabels(printf, ptr.labels, "\t\t")
o.printf(obj, "\t%s, may point to:", conc)
printLabels(o, labels, "\t\t")
} else { } else {
o.printf(obj, "\t%s", conc) printf(obj, "\t%s", ptr.typ)
}
} }
})
} else { } else {
o.printf(o, "interface cannot contain any concrete values.") printf(false, "interface cannot contain any concrete values.")
} }
} else { } else {
// Show labels for other expressions. // Show labels for other expressions.
if labels := pts.Labels(); len(labels) > 0 { if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
o.printf(o, "value may point to these labels:") printf(false, "value may point to these labels:")
printLabels(o, labels, "\t") printLabels(printf, ptr.labels, "\t")
} else { } else {
o.printf(o, "value cannot point to anything.") printf(false, "value cannot point to anything.")
} }
} }
} }
func (r *describeValueResult) toJSON(res *json.Result, fset *token.FileSet) {
var value, objpos, ptaerr string
if r.constVal != nil {
value = r.constVal.String()
}
if r.obj != nil {
objpos = fset.Position(r.obj.Pos()).String()
}
if r.ptaErr != nil {
ptaerr = r.ptaErr.Error()
}
var pts []*json.DescribePointer
for _, ptr := range r.ptrs {
var namePos string
if nt, ok := deref(ptr.typ).(*types.Named); ok {
namePos = fset.Position(nt.Obj().Pos()).String()
}
var labels []json.DescribePTALabel
for _, l := range ptr.labels {
labels = append(labels, json.DescribePTALabel{
Pos: fset.Position(l.Pos()).String(),
Desc: l.String(),
})
}
pts = append(pts, &json.DescribePointer{
Type: ptr.typ.String(),
NamePos: namePos,
Labels: labels,
})
}
res.Describe = &json.Describe{
Desc: importer.NodeDescription(r.expr),
Pos: fset.Position(r.expr.Pos()).String(),
Detail: "value",
Value: &json.DescribeValue{
Type: r.typ.String(),
Value: value,
ObjPos: objpos,
PTAErr: ptaerr,
PTS: pts,
},
}
}
type byTypeString []pointerResult
func (a byTypeString) Len() int { return len(a) }
func (a byTypeString) Less(i, j int) bool { return a[i].typ.String() < a[j].typ.String() }
func (a byTypeString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type byPosAndString []*pointer.Label type byPosAndString []*pointer.Label
func (a byPosAndString) Len() int { return len(a) } func (a byPosAndString) Len() int { return len(a) }
@ -483,13 +578,11 @@ func (a byPosAndString) Less(i, j int) bool {
} }
func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func printLabels(o *oracle, labels []*pointer.Label, prefix string) { func printLabels(printf printfFunc, labels []*pointer.Label, prefix string) {
// Sort, to ensure deterministic test behaviour.
sort.Sort(byPosAndString(labels))
// TODO(adonovan): due to context-sensitivity, many of these // TODO(adonovan): due to context-sensitivity, many of these
// labels may differ only by context, which isn't apparent. // labels may differ only by context, which isn't apparent.
for _, label := range labels { for _, label := range labels {
o.printf(label, "%s%s", prefix, label) printf(label, "%s%s", prefix, label)
} }
} }
@ -523,40 +616,62 @@ func describeType(o *oracle, path []ast.Node) (*describeTypeResult, error) {
return nil, o.errorf(n, "unexpected AST for type: %T", n) return nil, o.errorf(n, "unexpected AST for type: %T", n)
} }
return &describeTypeResult{path[0], description, t}, nil return &describeTypeResult{
node: path[0],
description: description,
typ: t,
methods: accessibleMethods(t, o.queryPkgInfo.Pkg),
}, nil
} }
type describeTypeResult struct { type describeTypeResult struct {
node ast.Node node ast.Node
description string description string
typ types.Type typ types.Type
methods []*types.Selection
} }
func (r *describeTypeResult) display(o *oracle) { func (r *describeTypeResult) display(printf printfFunc) {
o.printf(r.node, "%s", r.description) printf(r.node, "%s", r.description)
// Show the underlying type for a reference to a named type. // Show the underlying type for a reference to a named type.
if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() { if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
o.printf(nt.Obj(), "defined as %s", nt.Underlying()) printf(nt.Obj(), "defined as %s", nt.Underlying())
} }
// Print the method set, if the type kind is capable of bearing methods. // Print the method set, if the type kind is capable of bearing methods.
switch r.typ.(type) { switch r.typ.(type) {
case *types.Interface, *types.Struct, *types.Named: case *types.Interface, *types.Struct, *types.Named:
// TODO(adonovan): don't show unexported methods if if len(r.methods) > 0 {
// r.typ belongs to a package other than the query printf(r.node, "Method set:")
// package. for _, meth := range r.methods {
if m := ssa.IntuitiveMethodSet(r.typ); m != nil { printf(meth.Obj(), "\t%s", meth)
o.printf(r.node, "Method set:")
for _, meth := range m {
o.printf(meth.Obj(), "\t%s", meth)
} }
} else { } else {
o.printf(r.node, "No methods.") printf(r.node, "No methods.")
} }
} }
} }
func (r *describeTypeResult) toJSON(res *json.Result, fset *token.FileSet) {
var namePos, nameDef string
if nt, ok := r.typ.(*types.Named); ok {
namePos = fset.Position(nt.Obj().Pos()).String()
nameDef = nt.Underlying().String()
}
res.Describe = &json.Describe{
Desc: r.description,
Pos: fset.Position(r.node.Pos()).String(),
Detail: "type",
Type: &json.DescribeType{
Type: r.typ.String(),
NamePos: namePos,
NameDef: nameDef,
Methods: methodsToJSON(r.methods, fset),
},
}
}
// ---- PACKAGE ------------------------------------------------------------ // ---- PACKAGE ------------------------------------------------------------
func describePackage(o *oracle, path []ast.Node) (*describePackageResult, error) { func describePackage(o *oracle, path []ast.Node) (*describePackageResult, error) {
@ -589,58 +704,70 @@ func describePackage(o *oracle, path []ast.Node) (*describePackageResult, error)
return nil, o.errorf(n, "unexpected AST for package: %T", n) return nil, o.errorf(n, "unexpected AST for package: %T", n)
} }
pkg := o.prog.PackagesByPath[importPath] var members []*describeMember
// NB: package "unsafe" has no object.
return &describePackageResult{path[0], description, pkg}, nil if pkg := o.prog.PackagesByPath[importPath]; pkg != nil {
} // Compute set of exported package members in lexicographic order.
type describePackageResult struct {
node ast.Node
description string
pkg *ssa.Package
}
func (r *describePackageResult) display(o *oracle) {
o.printf(r.node, "%s", r.description)
// TODO(adonovan): factor this into a testable utility function.
if p := r.pkg; p != nil {
samePkg := p.Object == o.queryPkgInfo.Pkg
// Describe exported package members, in lexicographic order.
// Compute max width of name "column".
var names []string var names []string
maxname := 0 for name := range pkg.Members {
for name := range p.Members { if pkg.Object == o.queryPkgInfo.Pkg || ast.IsExported(name) {
if samePkg || ast.IsExported(name) {
if l := len(name); l > maxname {
maxname = l
}
names = append(names, name) names = append(names, name)
} }
} }
sort.Strings(names) sort.Strings(names)
// Print the members. // Enumerate the package members.
for _, name := range names { for _, name := range names {
mem := p.Members[name] mem := pkg.Members[name]
o.printf(mem, "%s", formatMember(mem, maxname)) var methods []*types.Selection
// Print method set.
if mem, ok := mem.(*ssa.Type); ok { if mem, ok := mem.(*ssa.Type); ok {
for _, meth := range ssa.IntuitiveMethodSet(mem.Type()) { methods = accessibleMethods(mem.Type(), o.queryPkgInfo.Pkg)
if samePkg || ast.IsExported(meth.Obj().Name()) { }
o.printf(meth.Obj(), "\t\t%s", meth) members = append(members, &describeMember{
mem,
methods,
})
} }
} }
return &describePackageResult{o.prog.Fset, path[0], description, importPath, members}, nil
} }
type describePackageResult struct {
fset *token.FileSet
node ast.Node
description string
path string
members []*describeMember // in lexicographic name order
}
type describeMember struct {
mem ssa.Member
methods []*types.Selection // in types.MethodSet order
}
func (r *describePackageResult) display(printf printfFunc) {
printf(r.node, "%s", r.description)
// Compute max width of name "column".
maxname := 0
for _, mem := range r.members {
if l := len(mem.mem.Name()); l > maxname {
maxname = l
}
}
for _, mem := range r.members {
printf(mem.mem, "\t%s", formatMember(mem.mem, maxname))
for _, meth := range mem.methods {
printf(meth.Obj(), "\t\t%s", meth)
} }
} }
} }
func formatMember(mem ssa.Member, maxname int) string { func formatMember(mem ssa.Member, maxname int) string {
var buf bytes.Buffer var buf bytes.Buffer
fmt.Fprintf(&buf, "\t%-5s %-*s", mem.Token(), maxname, mem.Name()) fmt.Fprintf(&buf, "%-5s %-*s", mem.Token(), maxname, mem.Name())
switch mem := mem.(type) { switch mem := mem.(type) {
case *ssa.NamedConst: case *ssa.NamedConst:
fmt.Fprintf(&buf, " %s = %s", mem.Type(), mem.Value.Name()) fmt.Fprintf(&buf, " %s = %s", mem.Type(), mem.Value.Name())
@ -673,6 +800,39 @@ func formatMember(mem ssa.Member, maxname int) string {
return buf.String() return buf.String()
} }
func (r *describePackageResult) toJSON(res *json.Result, fset *token.FileSet) {
var members []*json.DescribeMember
for _, mem := range r.members {
typ := mem.mem.Type()
var val string
switch mem := mem.mem.(type) {
case *ssa.NamedConst:
val = mem.Value.Value.String()
case *ssa.Type:
typ = typ.Underlying()
case *ssa.Global:
typ = deref(typ)
}
members = append(members, &json.DescribeMember{
Name: mem.mem.Name(),
Type: typ.String(),
Value: val,
Pos: fset.Position(mem.mem.Pos()).String(),
Kind: mem.mem.Token().String(),
Methods: methodsToJSON(mem.methods, fset),
})
}
res.Describe = &json.Describe{
Desc: r.description,
Pos: fset.Position(r.node.Pos()).String(),
Detail: "package",
Package: &json.DescribePackage{
Path: r.path,
Members: members,
},
}
}
// ---- STATEMENT ------------------------------------------------------------ // ---- STATEMENT ------------------------------------------------------------
func describeStmt(o *oracle, path []ast.Node) (*describeStmtResult, error) { func describeStmt(o *oracle, path []ast.Node) (*describeStmtResult, error) {
@ -689,16 +849,25 @@ func describeStmt(o *oracle, path []ast.Node) (*describeStmtResult, error) {
// Nothing much to say about statements. // Nothing much to say about statements.
description = importer.NodeDescription(n) description = importer.NodeDescription(n)
} }
return &describeStmtResult{path[0], description}, nil return &describeStmtResult{o.prog.Fset, path[0], description}, nil
} }
type describeStmtResult struct { type describeStmtResult struct {
fset *token.FileSet
node ast.Node node ast.Node
description string description string
} }
func (r *describeStmtResult) display(o *oracle) { func (r *describeStmtResult) display(printf printfFunc) {
o.printf(r.node, "%s", r.description) printf(r.node, "%s", r.description)
}
func (r *describeStmtResult) toJSON(res *json.Result, fset *token.FileSet) {
res.Describe = &json.Describe{
Desc: r.description,
Pos: fset.Position(r.node.Pos()).String(),
Detail: "unknown",
}
} }
// ------------------- Utilities ------------------- // ------------------- Utilities -------------------
@ -717,3 +886,28 @@ func pathToString2(path []ast.Node) string {
fmt.Fprint(&buf, "]") fmt.Fprint(&buf, "]")
return buf.String() return buf.String()
} }
func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
var methods []*types.Selection
for _, meth := range ssa.IntuitiveMethodSet(t) {
if isAccessibleFrom(meth.Obj(), from) {
methods = append(methods, meth)
}
}
return methods
}
func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
}
func methodsToJSON(methods []*types.Selection, fset *token.FileSet) []json.DescribeMethod {
var jmethods []json.DescribeMethod
for _, meth := range methods {
jmethods = append(jmethods, json.DescribeMethod{
Name: meth.String(),
Pos: fset.Position(meth.Obj().Pos()).String(),
})
}
return jmethods
}

View File

@ -6,8 +6,11 @@ package oracle
import ( import (
"go/ast" "go/ast"
"go/token"
"sort"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/oracle/json"
) )
// freevars displays the lexical (not package-level) free variables of // freevars displays the lexical (not package-level) free variables of
@ -72,7 +75,8 @@ func freevars(o *oracle) (queryResult, error) {
// Maps each reference that is free in the selection // Maps each reference that is free in the selection
// to the object it refers to. // to the object it refers to.
freeRefs := make(map[string]freevarsRef) // The map de-duplicates repeated references.
refsMap := make(map[string]freevarsRef)
// Visit all the identifiers in the selected ASTs. // Visit all the identifiers in the selected ASTs.
ast.Inspect(o.queryPath[0], func(n ast.Node) bool { ast.Inspect(o.queryPath[0], func(n ast.Node) bool {
@ -84,51 +88,20 @@ func freevars(o *oracle) (queryResult, error) {
// (freevars permits inexact selections, // (freevars permits inexact selections,
// like two stmts in a block.) // like two stmts in a block.)
if o.startPos <= n.Pos() && n.End() <= o.endPos { if o.startPos <= n.Pos() && n.End() <= o.endPos {
var obj types.Object
var prune bool
switch n := n.(type) { switch n := n.(type) {
case *ast.Ident: case *ast.Ident:
if obj := id(n); obj != nil { obj = id(n)
freeRefs[o.printNode(n)] = freevarsRef{n, obj}
}
case *ast.SelectorExpr: case *ast.SelectorExpr:
if obj := sel(n); obj != nil { obj = sel(n)
freeRefs[o.printNode(n)] = freevarsRef{n, obj} prune = true
return false // don't descend
}
}
} }
return true // descend if obj != nil {
})
return &freevarsResult{
refs: freeRefs,
}, nil
}
type freevarsResult struct {
refs map[string]freevarsRef
}
type freevarsRef struct {
expr ast.Expr
obj types.Object
}
func (r *freevarsResult) display(o *oracle) {
if len(r.refs) == 0 {
o.printf(o, "No free identifers.")
return
}
o.printf(o, "Free identifers:")
for s, ref := range r.refs {
typ := ref.obj.Type()
if _, ok := ref.expr.(*ast.SelectorExpr); ok {
typ = o.queryPkgInfo.TypeOf(ref.expr)
}
var kind string var kind string
switch ref.obj.(type) { switch obj.(type) {
case *types.Var: case *types.Var:
kind = "var" kind = "var"
case *types.Func: case *types.Func:
@ -140,8 +113,75 @@ func (r *freevarsResult) display(o *oracle) {
case *types.Label: case *types.Label:
kind = "label" kind = "label"
default: default:
panic(ref.obj) panic(obj)
} }
o.printf(ref.obj, "%s %s %s", kind, s, typ)
typ := o.queryPkgInfo.TypeOf(n.(ast.Expr))
ref := freevarsRef{kind, o.printNode(n), typ, obj}
refsMap[ref.ref] = ref
if prune {
return false // don't descend
} }
} }
}
return true // descend
})
refs := make([]freevarsRef, 0, len(refsMap))
for _, ref := range refsMap {
refs = append(refs, ref)
}
sort.Sort(byRef(refs))
return &freevarsResult{
fset: o.prog.Fset,
refs: refs,
}, nil
}
type freevarsResult struct {
fset *token.FileSet
refs []freevarsRef
}
type freevarsRef struct {
kind string
ref string
typ types.Type
obj types.Object
}
func (r *freevarsResult) display(printf printfFunc) {
if len(r.refs) == 0 {
printf(false, "No free identifers.")
} else {
printf(false, "Free identifers:")
for _, ref := range r.refs {
printf(ref.obj, "%s %s %s", ref.kind, ref.ref, ref.typ)
}
}
}
func (r *freevarsResult) toJSON(res *json.Result, fset *token.FileSet) {
var refs []*json.FreeVar
for _, ref := range r.refs {
refs = append(refs,
&json.FreeVar{
Pos: fset.Position(ref.obj.Pos()).String(),
Kind: ref.kind,
Ref: ref.ref,
Type: ref.typ.String(),
})
}
res.Freevars = refs
}
// -------- utils --------
type byRef []freevarsRef
func (p byRef) Len() int { return len(p) }
func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref }
func (p byRef) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

View File

@ -5,7 +5,10 @@
package oracle package oracle
import ( import (
"go/token"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/ssa" "code.google.com/p/go.tools/ssa"
) )
@ -15,7 +18,7 @@ import (
// //
// TODO(adonovan): more features: // TODO(adonovan): more features:
// - should we include pairs of types belonging to // - should we include pairs of types belonging to
// different packages in the 'implements' relation?// // different packages in the 'implements' relation?
// - should we restrict the query to the type declaration identified // - should we restrict the query to the type declaration identified
// by the query position, if any, and use all types in the package // by the query position, if any, and use all types in the package
// otherwise? // otherwise?
@ -62,8 +65,9 @@ func implements(o *oracle) (queryResult, error) {
facts = append(facts, fact) facts = append(facts, fact)
} }
} }
// TODO(adonovan): sort facts to ensure test nondeterminism.
return &implementsResult{facts}, nil return &implementsResult{o.prog.Fset, facts}, nil
} }
type implementsFact struct { type implementsFact struct {
@ -72,17 +76,30 @@ type implementsFact struct {
} }
type implementsResult struct { type implementsResult struct {
fset *token.FileSet
facts []implementsFact // facts are grouped by interface facts []implementsFact // facts are grouped by interface
} }
func (r *implementsResult) display(o *oracle) { func (r *implementsResult) display(printf printfFunc) {
// TODO(adonovan): sort to ensure test nondeterminism.
var prevIface *types.Named var prevIface *types.Named
for _, fact := range r.facts { for _, fact := range r.facts {
if fact.iface != prevIface { if fact.iface != prevIface {
o.printf(fact.iface.Obj(), "\tInterface %s:", fact.iface) printf(fact.iface.Obj(), "\tInterface %s:", fact.iface)
prevIface = fact.iface prevIface = fact.iface
} }
o.printf(deref(fact.conc).(*types.Named).Obj(), "\t\t%s", fact.conc) printf(deref(fact.conc).(*types.Named).Obj(), "\t\t%s", fact.conc)
} }
} }
func (r *implementsResult) toJSON(res *json.Result, fset *token.FileSet) {
var facts []*json.Implements
for _, fact := range r.facts {
facts = append(facts, &json.Implements{
I: fact.iface.String(),
IPos: fset.Position(fact.iface.Obj().Pos()).String(),
C: fact.conc.String(),
CPos: fset.Position(deref(fact.conc).(*types.Named).Obj().Pos()).String(),
})
}
res.Implements = facts
}

210
oracle/json/json.go Normal file
View File

@ -0,0 +1,210 @@
// 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 json defines the oracle's JSON schema.
package json
// All 'pos' strings are of the form "file:line:col".
// TODO(adonovan): improve performance by sharing filename strings.
// TODO(adonovan): improve precision by providing the start/end
// interval when available.
//
// TODO(adonovan): consider richer encodings of types, functions,
// methods, etc.
// A Peers is the result of a 'peers' query.
// If Allocs is empty, the selected channel can't point to anything.
type Peers struct {
Pos string `json:"pos"` // location of the selected channel op (<-)
Type string `json:"type"` // type of the selected channel
Allocs []string `json:"allocs,omitempty"` // locations of aliased make(chan) ops
Sends []string `json:"sends,omitempty"` // locations of aliased ch<-x ops
Receives []string `json:"receives,omitempty"` // locations of aliased <-ch ops
}
type CalleesItem struct {
Name string `json:"name"` // full name of called function
Pos string `json:"pos"` // location of called function
}
// A Callees is the result of a 'callees' query.
//
// Callees is nonempty unless the call was a dynamic call on a
// provably nil func or interface value.
type Callees struct {
Pos string `json:"pos"` // location of selected call site
Desc string `json:"desc"` // description of call site
Callees []*CalleesItem `json:"callees,omitempty"` // set of possible call targets
}
// A Caller is one element of the slice returned by a 'callers' query.
// (Callstack also contains a similar slice.)
//
// The root of the callgraph has an unspecified "Caller" string.
type Caller struct {
Pos string `json:"pos,omitempty"` // location of the calling function
Desc string `json:"desc"` // description of call site
Caller string `json:"caller"` // full name of calling function
}
// A CallGraph is one element of the slice returned by a 'callgraph' query.
// The index of each item in the slice is used to identify it in the
// Callers adjacency list.
//
// Multiple nodes may have the same Name due to context-sensitive
// treatment of some functions.
//
// TODO(adonovan): perhaps include edge labels (i.e. callsites).
type CallGraph struct {
Name string `json:"name"` // full name of function
Pos string `json:"pos"` // location of function
Children []int `json:"children,omitempty"` // indices of child nodes in callgraph list
}
// A CallStack is the result of a 'callstack' query.
// It indicates an arbitrary path from the root of the callgraph to
// the query function.
//
// If the Callers slice is empty, the function was unreachable in this
// analysis scope.
type CallStack struct {
Pos string `json:"pos"` // location of the selected function
Target string `json:"target"` // the selected function
Callers []Caller `json:"callers"` // enclosing calls, innermost first.
}
// A FreeVar is one element of the slice returned by a 'freevars'
// query. Each one identifies an expression referencing a local
// identifier defined outside the selected region.
type FreeVar struct {
Pos string `json:"pos"` // location of the identifier's definition
Kind string `json:"kind"` // one of {var,func,type,const,label}
Ref string `json:"ref"` // referring expression (e.g. "x" or "x.y.z")
Type string `json:"type"` // type of the expression
}
// An Implements is one element of the result of an 'implements' query.
// Each one indicates a row in the "implements" relation over
// package-level named types defined by the package containing the
// selection.
type Implements struct {
I string `json:"i"` // full name of the interface type
IPos string `json:"ipos"` // location of its definition
C string `json:"c"` // full name of the concrete type
CPos string `json:"cpos"` // location of its definition
}
// A DescribePTALabel describes a pointer analysis label.
//
// A "label" is an object that may be pointed to by a pointer, map,
// channel, 'func', slice or interface. Labels include:
// - functions
// - globals
// - arrays created by literals (e.g. []byte("foo")) and conversions ([]byte(s))
// - stack- and heap-allocated variables (including composite literals)
// - arrays allocated by append()
// - channels, maps and arrays created by make()
// - and their subelements, e.g. "alloc.y[*].z"
//
type DescribePTALabel struct {
Pos string `json:"pos"` // location of syntax that allocated the object
Desc string `json:"desc"` // description of the label
}
// A DescribePointer describes a single pointer: its type and the
// set of "labels" it points to.
//
type DescribePointer struct {
Type string `json:"type"` // (concrete) type of the pointer
NamePos string `json:"namepos"` // location of type defn, if Named
Labels []DescribePTALabel `json:"labels,omitempty"` // pointed-to objects
}
// A DescribeValue is the additional result of a 'describe' query
// if the selection indicates a value or expression.
//
// If the described value is an interface, it will have one PTS entry
// describing each concrete type that it may contain. For each
// concrete type that is a pointer, the PTS entry describes the labels
// it may point to.
//
type DescribeValue struct {
Type string `json:"type"` // type of the expression
Value string `json:"value,omitempty"` // value of the expression, if constant
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
PTAErr string `json:"ptaerr,omitempty"` // reason pointer analysis wasn't attempted
PTS []*DescribePointer `json:"pts,omitempty"` // points-to set; an interface may have many
}
type DescribeMethod struct {
Name string `json:"name"` // method name, as defined by types.Selection.String()
Pos string `json:"pos"` // location of the method's definition
}
// A DescribeType is the additional result of a 'describe' query
// if the selection indicates a type.
type DescribeType struct {
Type string `json:"type"` // the string form of the type
NamePos string `json:"namepos,omitempty"` // location of definition of type, if named
NameDef string `json:"namedef,omitempty"` // underlying definition of type, if named
Methods []DescribeMethod `json:"methods,omitempty"` // methods of the type
}
type DescribeMember struct {
Name string `json:"name"` // name of member
Type string `json:"type,omitempty"` // type of member (underlying, if 'type')
Value string `json:"value,omitempty"` // value of member (if 'const')
Pos string `json:"pos"` // location of definition of member
Kind string `json:"kind"` // one of {var,const,func,type}
Methods []DescribeMethod `json:"methods,omitempty"` // methods (if member is a type)
}
// A DescribePackage is the additional result of a 'describe' if
// the selection indicates a package.
type DescribePackage struct {
Path string `json:"path"` // import path of the package
Members []*DescribeMember `json:"members,omitempty"` // accessible members of the package
}
// A Describe is the result of a 'describe' query.
// It may contain an element describing the selected semantic entity
// in detail.
type Describe struct {
Desc string `json:"desc"` // description of the selected syntax node
Pos string `json:"pos"` // location of the selected syntax node
Detail string `json:"detail,omitempty"` // one of {package, type, value}, or "".
// At most one of the following fields is populated:
// the one specified by 'detail'.
Package *DescribePackage `json:"package,omitempty"`
Type *DescribeType `json:"type,omitempty"`
Value *DescribeValue `json:"value,omitempty"`
}
type PTAWarning struct {
Pos string `json:"pos"` // location associated with warning
Message string `json:"message"` // warning message
}
// A Result is the common result of any oracle query.
// It contains a query-specific result element.
//
// TODO(adonovan): perhaps include other info such as: analysis scope,
// raw query position, stack of ast nodes, query package, etc.
type Result struct {
Mode string `json:"mode"` // mode of the query
// Exactly one of the following fields is populated:
// the one specified by 'mode'.
Callees *Callees `json:"callees,omitempty"`
Callers []Caller `json:"callers,omitempty"`
Callgraph []CallGraph `json:"callgraph,omitempty"`
Callstack *CallStack `json:"callstack,omitempty"`
Describe *Describe `json:"describe,omitempty"`
Freevars []*FreeVar `json:"freevars,omitempty"`
Implements []*Implements `json:"implements,omitempty"`
Peers *Peers `json:"peers,omitempty"`
Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis
}

View File

@ -4,7 +4,7 @@
package oracle package oracle
// This file defines oracle.Main, the entry point for the oracle tool. // This file defines oracle.Query, the entry point for the oracle tool.
// The actual executable is defined in cmd/oracle. // The actual executable is defined in cmd/oracle.
// TODO(adonovan): new query: show all statements that may update the // TODO(adonovan): new query: show all statements that may update the
@ -12,6 +12,7 @@ package oracle
import ( import (
"bytes" "bytes"
encjson "encoding/json"
"errors" "errors"
"fmt" "fmt"
"go/ast" "go/ast"
@ -27,6 +28,7 @@ import (
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/pointer" "code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa" "code.google.com/p/go.tools/ssa"
) )
@ -71,25 +73,59 @@ var modes = map[string]modeInfo{
"peers": modeInfo{WholeSource | SSA | Pos, peers}, "peers": modeInfo{WholeSource | SSA | Pos, peers},
} }
type printfFunc func(pos interface{}, format string, args ...interface{})
// queryResult is the interface of each query-specific result type.
type queryResult interface { type queryResult interface {
display(o *oracle) toJSON(res *json.Result, fset *token.FileSet)
display(printf printfFunc)
} }
// Main runs the oracle. type warning struct {
pos token.Pos
format string
args []interface{}
}
// A Result encapsulates the result of an oracle.Query.
//
// Result instances implement the json.Marshaler interface, i.e. they
// can be JSON-serialized.
type Result struct {
fset *token.FileSet
// fprintf is a closure over the oracle's fileset and start/end position.
fprintf func(w io.Writer, pos interface{}, format string, args ...interface{})
q queryResult // the query-specific result
mode string // query mode
warnings []warning // pointer analysis warnings
}
func (res *Result) MarshalJSON() ([]byte, error) {
resj := &json.Result{Mode: res.mode}
res.q.toJSON(resj, res.fset)
for _, w := range res.warnings {
resj.Warnings = append(resj.Warnings, json.PTAWarning{
Pos: res.fset.Position(w.pos).String(),
Message: fmt.Sprintf(w.format, w.args...),
})
}
return encjson.Marshal(resj)
}
// Query runs the oracle.
// args specify the main package in importer.CreatePackageFromArgs syntax. // args specify the main package in importer.CreatePackageFromArgs syntax.
// mode is the query mode ("callers", etc). // mode is the query mode ("callers", etc).
// pos is the selection in parseQueryPos() syntax. // pos is the selection in parseQueryPos() syntax.
// ptalog is the (optional) pointer-analysis log file. // ptalog is the (optional) pointer-analysis log file.
// out is the standard output stream.
// buildContext is the optional configuration for locating packages. // buildContext is the optional configuration for locating packages.
// //
func Main(args []string, mode, pos string, ptalog, out io.Writer, buildContext *build.Context) error { func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *build.Context) (*Result, error) {
minfo, ok := modes[mode] minfo, ok := modes[mode]
if !ok { if !ok {
if mode == "" { if mode == "" {
return errors.New("You must specify a -mode to perform.") return nil, errors.New("you must specify a -mode to perform")
} }
return fmt.Errorf("Invalid mode type: %q.", mode) return nil, fmt.Errorf("invalid mode type: %q", mode)
} }
var loader importer.SourceLoader var loader importer.SourceLoader
@ -98,20 +134,14 @@ func Main(args []string, mode, pos string, ptalog, out io.Writer, buildContext *
} }
imp := importer.New(&importer.Config{Loader: loader}) imp := importer.New(&importer.Config{Loader: loader})
o := &oracle{ o := &oracle{
out: out,
prog: ssa.NewProgram(imp.Fset, 0), prog: ssa.NewProgram(imp.Fset, 0),
timers: make(map[string]time.Duration), timers: make(map[string]time.Duration),
} }
o.config.Log = ptalog o.config.Log = ptalog
type warning struct { var res Result
pos token.Pos
format string
args []interface{}
}
var warnings []warning
o.config.Warn = func(pos token.Pos, format string, args ...interface{}) { o.config.Warn = func(pos token.Pos, format string, args ...interface{}) {
warnings = append(warnings, warning{pos, format, args}) res.warnings = append(res.warnings, warning{pos, format, args})
} }
// Phase timing diagnostics. // Phase timing diagnostics.
@ -128,7 +158,7 @@ func Main(args []string, mode, pos string, ptalog, out io.Writer, buildContext *
start := time.Now() start := time.Now()
initialPkgInfo, _, err := importer.CreatePackageFromArgs(imp, args) initialPkgInfo, _, err := importer.CreatePackageFromArgs(imp, args)
if err != nil { if err != nil {
return err // I/O, parser or type error return nil, err // I/O, parser or type error
} }
o.timers["load/parse/type"] = time.Since(start) o.timers["load/parse/type"] = time.Since(start)
@ -137,16 +167,16 @@ func Main(args []string, mode, pos string, ptalog, out io.Writer, buildContext *
var err error var err error
o.startPos, o.endPos, err = parseQueryPos(o.prog.Fset, pos) o.startPos, o.endPos, err = parseQueryPos(o.prog.Fset, pos)
if err != nil { if err != nil {
return err return nil, err
} }
var exact bool var exact bool
o.queryPkgInfo, o.queryPath, exact = imp.PathEnclosingInterval(o.startPos, o.endPos) o.queryPkgInfo, o.queryPath, exact = imp.PathEnclosingInterval(o.startPos, o.endPos)
if o.queryPath == nil { if o.queryPath == nil {
return o.errorf(o, "no syntax here") return nil, o.errorf(false, "no syntax here")
} }
if minfo.needs&ExactPos != 0 && !exact { if minfo.needs&ExactPos != 0 && !exact {
return o.errorf(o.queryPath[0], "ambiguous selection within %s", return nil, o.errorf(o.queryPath[0], "ambiguous selection within %s",
importer.NodeDescription(o.queryPath[0])) importer.NodeDescription(o.queryPath[0]))
} }
} }
@ -166,7 +196,7 @@ func Main(args []string, mode, pos string, ptalog, out io.Writer, buildContext *
// Add package to the pointer analysis scope. // Add package to the pointer analysis scope.
if initialPkg.Func("main") == nil { if initialPkg.Func("main") == nil {
if initialPkg.CreateTestMainFunction() == nil { if initialPkg.CreateTestMainFunction() == nil {
return o.errorf(o, "analysis scope has no main() entry points") return nil, o.errorf(false, "analysis scope has no main() entry points")
} }
} }
o.config.Mains = append(o.config.Mains, initialPkg) o.config.Mains = append(o.config.Mains, initialPkg)
@ -185,22 +215,30 @@ func Main(args []string, mode, pos string, ptalog, out io.Writer, buildContext *
// Release the other ASTs and type info to the GC. // Release the other ASTs and type info to the GC.
imp = nil imp = nil
result, err := minfo.impl(o) res.q, err = minfo.impl(o)
if err != nil { if err != nil {
return err return nil, err
} }
// TODO(adonovan): use this as a seam for testing. res.mode = mode
result.display(o) res.fset = o.prog.Fset
res.fprintf = o.fprintf // captures o.prog, o.{start,end}Pos for later printing
return &res, nil
}
// WriteTo writes the oracle query result res to out in a compiler diagnostic format.
func (res *Result) WriteTo(out io.Writer) {
printf := func(pos interface{}, format string, args ...interface{}) {
res.fprintf(out, pos, format, args...)
}
res.q.display(printf)
// Print warnings after the main output. // Print warnings after the main output.
if warnings != nil { if res.warnings != nil {
fmt.Fprintln(o.out, "\nPointer analysis warnings:") fmt.Fprintln(out, "\nPointer analysis warnings:")
for _, w := range warnings { for _, w := range res.warnings {
o.fprintf(o.out, w.pos, "warning: "+w.format, w.args...) printf(w.pos, "warning: "+w.format, w.args...)
} }
} }
return nil
} }
// ---------- Utilities ---------- // ---------- Utilities ----------
@ -338,7 +376,8 @@ func deref(typ types.Type) types.Type {
// - an ast.Node, denoting an interval // - an ast.Node, denoting an interval
// - anything with a Pos() method: // - anything with a Pos() method:
// ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc. // ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc.
// - o *oracle, meaning the extent [o.startPos, o.endPos) of the user's query. // - a bool, meaning the extent [o.startPos, o.endPos) of the user's query.
// (the value is ignored)
// - nil, meaning no position at all. // - nil, meaning no position at all.
// //
// The output format is is compatible with the 'gnu' // The output format is is compatible with the 'gnu'
@ -359,7 +398,7 @@ func (o *oracle) fprintf(w io.Writer, pos interface{}, format string, args ...in
}: }:
start = pos.Pos() start = pos.Pos()
end = start end = start
case *oracle: case bool:
start = o.startPos start = o.startPos
end = o.endPos end = o.endPos
case nil: case nil:
@ -385,11 +424,6 @@ func (o *oracle) fprintf(w io.Writer, pos interface{}, format string, args ...in
io.WriteString(w, "\n") io.WriteString(w, "\n")
} }
// printf is like fprintf, but writes to to o.out.
func (o *oracle) printf(pos interface{}, format string, args ...interface{}) {
o.fprintf(o.out, pos, format, args...)
}
// errorf is like fprintf, but returns a formatted error string. // errorf is like fprintf, but returns a formatted error string.
func (o *oracle) errorf(pos interface{}, format string, args ...interface{}) error { func (o *oracle) errorf(pos interface{}, format string, args ...interface{}) error {
var buf bytes.Buffer var buf bytes.Buffer

View File

@ -26,12 +26,9 @@ package oracle_test
// % go test code.google.com/p/go.tools/oracle -update // % go test code.google.com/p/go.tools/oracle -update
// to update the golden files. // to update the golden files.
// TODO(adonovan): improve coverage:
// - output of @callgraph is nondeterministic.
// - as are lists of labels.
import ( import (
"bytes" "bytes"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"go/build" "go/build"
@ -159,23 +156,40 @@ func stripLocation(line string) string {
// doQuery poses query q to the oracle and writes its response and // doQuery poses query q to the oracle and writes its response and
// error (if any) to out. // error (if any) to out.
func doQuery(out io.Writer, q *query) { func doQuery(out io.Writer, q *query, useJson bool) {
fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id) fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id)
capture := new(bytes.Buffer) // capture standard output
var buildContext = build.Default var buildContext = build.Default
buildContext.GOPATH = "testdata" buildContext.GOPATH = "testdata"
err := oracle.Main([]string{q.filename}, res, err := oracle.Query([]string{q.filename},
q.verb, q.verb,
fmt.Sprintf("%s:%d-%d", q.filename, q.start, q.end), fmt.Sprintf("%s:%d-%d", q.filename, q.start, q.end),
/*PTA-log=*/ nil, capture, &buildContext) /*PTA-log=*/ nil, &buildContext)
if err != nil {
fmt.Fprintf(out, "\nError: %s\n", stripLocation(err.Error()))
return
}
if useJson {
// JSON output
b, err := json.Marshal(res)
if err != nil {
fmt.Fprintf(out, "JSON error: %s\n", err.Error())
return
}
var buf bytes.Buffer
if err := json.Indent(&buf, b, "", "\t"); err != nil {
fmt.Fprintf(out, "json.Indent failed: %s", err)
return
}
out.Write(buf.Bytes())
} else {
// "plain" (compiler diagnostic format) output
capture := new(bytes.Buffer) // capture standard output
res.WriteTo(capture)
for _, line := range strings.Split(capture.String(), "\n") { for _, line := range strings.Split(capture.String(), "\n") {
fmt.Fprintf(out, "%s\n", stripLocation(line)) fmt.Fprintf(out, "%s\n", stripLocation(line))
} }
if err != nil {
fmt.Fprintf(out, "Error: %s\n", stripLocation(err.Error()))
} }
} }
@ -187,12 +201,19 @@ func TestOracle(t *testing.T) {
for _, filename := range []string{ for _, filename := range []string{
"testdata/src/main/calls.go", "testdata/src/main/calls.go",
"testdata/src/main/callgraph.go",
"testdata/src/main/describe.go", "testdata/src/main/describe.go",
"testdata/src/main/freevars.go", "testdata/src/main/freevars.go",
"testdata/src/main/implements.go", "testdata/src/main/implements.go",
"testdata/src/main/imports.go", "testdata/src/main/imports.go",
"testdata/src/main/peers.go", "testdata/src/main/peers.go",
// JSON:
"testdata/src/main/callgraph-json.go",
"testdata/src/main/calls-json.go",
"testdata/src/main/peers-json.go",
"testdata/src/main/describe-json.go",
} { } {
useJson := strings.HasSuffix(filename, "-json.go")
queries := parseQueries(t, filename) queries := parseQueries(t, filename)
golden := filename + "lden" golden := filename + "lden"
got := filename + "t" got := filename + "t"
@ -206,7 +227,7 @@ func TestOracle(t *testing.T) {
// Run the oracle on each query, redirecting its output // Run the oracle on each query, redirecting its output
// and error (if any) to the foo.got file. // and error (if any) to the foo.got file.
for _, q := range queries { for _, q := range queries {
doQuery(gotfh, q) doQuery(gotfh, q, useJson)
} }
// Compare foo.got with foo.golden. // Compare foo.got with foo.golden.

View File

@ -7,8 +7,10 @@ package oracle
import ( import (
"go/ast" "go/ast"
"go/token" "go/token"
"sort"
"code.google.com/p/go.tools/go/types" "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/pointer"
"code.google.com/p/go.tools/ssa" "code.google.com/p/go.tools/ssa"
) )
@ -52,7 +54,10 @@ func peers(o *oracle) (queryResult, error) {
// Discard operations of wrong channel element type. // Discard operations of wrong channel element type.
// Build set of channel ssa.Values as query to pointer analysis. // Build set of channel ssa.Values as query to pointer analysis.
queryElemType := queryOp.ch.Type().Underlying().(*types.Chan).Elem() // 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} channels := map[ssa.Value][]pointer.Pointer{queryOp.ch: nil}
i := 0 i := 0
for _, op := range ops { for _, op := range ops {
@ -71,10 +76,35 @@ func peers(o *oracle) (queryResult, error) {
// Combine the PT sets from all contexts. // Combine the PT sets from all contexts.
queryChanPts := pointer.PointsToCombined(channels[queryOp.ch]) 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{ return &peersResult{
queryOp: queryOp, queryPos: arrowPos,
ops: ops, queryType: queryType,
queryChanPts: queryChanPts, makes: makes,
sends: sends,
receives: receives,
}, nil }, nil
} }
@ -122,35 +152,49 @@ func chanOps(instr ssa.Instruction) []chanOp {
} }
type peersResult struct { type peersResult struct {
queryOp chanOp queryPos token.Pos // of queried '<-' token
ops []chanOp queryType types.Type // type of queried channel
queryChanPts pointer.PointsToSet makes, sends, receives []token.Pos // positions of alisaed makechan/send/receive instrs
} }
func (r *peersResult) display(o *oracle) { func (r *peersResult) display(printf printfFunc) {
// Report which make(chan) labels the query's channel can alias. if len(r.makes) == 0 {
labels := r.queryChanPts.Labels() printf(r.queryPos, "This channel can't point to anything.")
if len(labels) == 0 {
o.printf(r.queryOp.pos, "This channel can't point to anything.")
return return
} }
o.printf(r.queryOp.pos, "This channel of type %s may be:", r.queryOp.ch.Type()) printf(r.queryPos, "This channel of type %s may be:", r.queryType)
// TODO(adonovan): sort, to ensure test determinism. for _, alloc := range r.makes {
for _, label := range labels { printf(alloc, "\tallocated here")
o.printf(label, "\tallocated here") }
for _, send := range r.sends {
printf(send, "\tsent to, here")
}
for _, receive := range r.receives {
printf(receive, "\treceived from, here")
}
} }
// Report which send/receive operations can alias the same make(chan) labels. func (r *peersResult) toJSON(res *json.Result, fset *token.FileSet) {
for _, op := range r.ops { peers := &json.Peers{
// TODO(adonovan): sort, to ensure test determinism. Pos: fset.Position(r.queryPos).String(),
for _, ptr := range o.config.QueryValues[op.ch] { Type: r.queryType.String(),
if ptr != nil && ptr.PointsTo().Intersects(r.queryChanPts) {
verb := "received from"
if op.dir == ast.SEND {
verb = "sent to"
} }
o.printf(op.pos, "\t%s, here", verb) 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] }

View File

@ -0,0 +1,54 @@
package main
// Tests of call-graph queries, -format=json.
// See go.tools/oracle/oracle_test.go for explanation.
// See callgraph-json.golden for expected query results.
func A() {}
func B() {}
// call is not (yet) treated context-sensitively.
func call(f func()) {
f()
}
// nop *is* treated context-sensitively.
func nop() {}
func call2(f func()) {
f()
f()
}
func main() {
call(A)
call(B)
nop()
nop()
call2(func() {
// called twice from main.call2,
// but call2 is not context sensitive (yet).
})
print("builtin")
_ = string("type conversion")
call(nil)
if false {
main()
}
var nilFunc func()
nilFunc()
var i interface {
f()
}
i.f()
}
func deadcode() {
main()
}
// @callgraph callgraph "^"

View File

@ -0,0 +1,63 @@
-------- @callgraph callgraph --------
{
"mode": "callgraph",
"callgraph": [
{
"name": "\u003croot\u003e",
"pos": "-",
"children": [
1,
2
]
},
{
"name": "main.init",
"pos": "-"
},
{
"name": "main.main",
"pos": "testdata/src/main/callgraph-json.go:24:6",
"children": [
3,
6,
7,
8
]
},
{
"name": "main.call",
"pos": "testdata/src/main/callgraph-json.go:12:6",
"children": [
4,
5
]
},
{
"name": "main.A",
"pos": "testdata/src/main/callgraph-json.go:7:6"
},
{
"name": "main.B",
"pos": "testdata/src/main/callgraph-json.go:9:6"
},
{
"name": "main.nop",
"pos": "testdata/src/main/callgraph-json.go:17:6"
},
{
"name": "main.nop",
"pos": "testdata/src/main/callgraph-json.go:17:6"
},
{
"name": "main.call2",
"pos": "testdata/src/main/callgraph-json.go:19:6",
"children": [
9
]
},
{
"name": "func@31.8",
"pos": "testdata/src/main/callgraph-json.go:31:8"
}
]
}

54
oracle/testdata/src/main/callgraph.go vendored Normal file
View File

@ -0,0 +1,54 @@
package main
// Tests of call-graph queries.
// See go.tools/oracle/oracle_test.go for explanation.
// See callgraph.golden for expected query results.
func A() {}
func B() {}
// call is not (yet) treated context-sensitively.
func call(f func()) {
f()
}
// nop *is* treated context-sensitively.
func nop() {}
func call2(f func()) {
f()
f()
}
func main() {
call(A)
call(B)
nop()
nop()
call2(func() {
// called twice from main.call2,
// but call2 is not context sensitive (yet).
})
print("builtin")
_ = string("type conversion")
call(nil)
if false {
main()
}
var nilFunc func()
nilFunc()
var i interface {
f()
}
i.f()
}
func deadcode() {
main()
}
// @callgraph callgraph "^"

View File

@ -0,0 +1,20 @@
-------- @callgraph callgraph --------
Below is a call graph of the entire program.
The numbered nodes form a spanning tree.
Non-numbered nodes indicate back- or cross-edges to the node whose
number follows in parentheses.
Some nodes may appear multiple times due to context-sensitive
treatment of some calls.
0 <root>
1 main.init
2 main.main
3 main.call
4 main.A
5 main.B
6 main.nop
7 main.nop
8 main.call2
9 func@31.8

16
oracle/testdata/src/main/calls-json.go vendored Normal file
View File

@ -0,0 +1,16 @@
package main
// Tests of call-graph queries, -format=json.
// See go.tools/oracle/oracle_test.go for explanation.
// See calls-json.golden for expected query results.
func call(f func()) {
f() // @callees @callees-f "f"
}
func main() {
call(func() {
// @callers callers-main.anon "^"
// @callstack callstack-main.anon "^"
})
}

View File

@ -0,0 +1,33 @@
-------- @callees @callees-f --------
{
"mode": "callees",
"callees": {
"pos": "testdata/src/main/calls-json.go:8:3",
"desc": "dynamic function call",
"callees": [
{
"name": "func@12.7",
"pos": "testdata/src/main/calls-json.go:12:7"
}
]
}
}-------- @callstack callstack-main.anon --------
{
"mode": "callstack",
"callstack": {
"pos": "testdata/src/main/calls-json.go:12:7",
"target": "func@12.7",
"callers": [
{
"pos": "testdata/src/main/calls-json.go:8:3",
"desc": "dynamic function call",
"caller": "main.call"
},
{
"pos": "testdata/src/main/calls-json.go:12:6",
"desc": "static function call",
"caller": "main.main"
}
]
}
}

View File

@ -82,7 +82,8 @@ Error: this is a type conversion, not a function call
Error: ambiguous selection within function call (or conversion) Error: ambiguous selection within function call (or conversion)
-------- @callees callees-err-deadcode1 -------- -------- @callees callees-err-deadcode1 --------
this call site is unreachable in this analysis
Error: this call site is unreachable in this analysis
-------- @callees callees-err-nil-func -------- -------- @callees callees-err-nil-func --------
dynamic function call on nil value dynamic function call on nil value
@ -91,7 +92,8 @@ dynamic function call on nil value
dynamic method call on nil value dynamic method call on nil value
-------- @callees callees-err-deadcode2 -------- -------- @callees callees-err-deadcode2 --------
this call site is unreachable in this analysis
Error: this call site is unreachable in this analysis
-------- @callstack callstack-err-deadcode -------- -------- @callstack callstack-err-deadcode --------
main.deadcode is unreachable in this analysis scope main.deadcode is unreachable in this analysis scope

View File

@ -0,0 +1,30 @@
package describe // @describe pkgdecl "describe"
// @implements implements "^"
// Tests of 'describe' and 'implements' queries, -format=json.
// See go.tools/oracle/oracle_test.go for explanation.
// See describe-json.golden for expected query results.
func main() { //
var s struct{ x [3]int }
p := &s.x[0] // @describe desc-val-p "p"
var i I = C(0)
if i == nil {
i = new(D)
}
_ = i // @describe desc-val-i "i"
go main() // @describe desc-stmt "go"
}
type I interface {
f()
}
type C int // @describe desc-type-C "C"
type D struct{}
func (c C) f() {}
func (d *D) f() {}

View File

@ -0,0 +1,164 @@
-------- @describe pkgdecl --------
{
"mode": "describe",
"describe": {
"desc": "definition of package \"main\"",
"pos": "testdata/src/main/describe-json.go:1:9",
"detail": "package",
"package": {
"path": "main",
"members": [
{
"name": "C",
"type": "int",
"pos": "testdata/src/main/describe-json.go:26:6",
"kind": "type",
"methods": [
{
"name": "method (describe.C) f()",
"pos": "testdata/src/main/describe-json.go:29:12"
}
]
},
{
"name": "D",
"type": "struct{}",
"pos": "testdata/src/main/describe-json.go:27:6",
"kind": "type",
"methods": [
{
"name": "method (*describe.D) f()",
"pos": "testdata/src/main/describe-json.go:30:13"
}
]
},
{
"name": "I",
"type": "interface{f()}",
"pos": "testdata/src/main/describe-json.go:22:6",
"kind": "type",
"methods": [
{
"name": "method (describe.I) f()",
"pos": "testdata/src/main/describe-json.go:23:2"
}
]
},
{
"name": "init",
"type": "func()",
"pos": "-",
"kind": "func"
},
{
"name": "init$guard",
"type": "bool",
"pos": "-",
"kind": "var"
},
{
"name": "main",
"type": "func()",
"pos": "testdata/src/main/describe-json.go:9:6",
"kind": "func"
}
]
}
}
}-------- @implements implements --------
{
"mode": "implements",
"implements": [
{
"i": "describe.I",
"ipos": "testdata/src/main/describe-json.go:22:6",
"c": "describe.C",
"cpos": "testdata/src/main/describe-json.go:26:6"
},
{
"i": "describe.I",
"ipos": "testdata/src/main/describe-json.go:22:6",
"c": "*describe.D",
"cpos": "testdata/src/main/describe-json.go:27:6"
}
]
}-------- @describe desc-val-p --------
{
"mode": "describe",
"describe": {
"desc": "identifier",
"pos": "testdata/src/main/describe-json.go:11:2",
"detail": "value",
"value": {
"type": "*int",
"objpos": "testdata/src/main/describe-json.go:11:2",
"pts": [
{
"type": "*int",
"namepos": "",
"labels": [
{
"pos": "testdata/src/main/describe-json.go:10:6",
"desc": "s.x[*]"
}
]
}
]
}
}
}-------- @describe desc-val-i --------
{
"mode": "describe",
"describe": {
"desc": "identifier",
"pos": "testdata/src/main/describe-json.go:17:6",
"detail": "value",
"value": {
"type": "describe.I",
"objpos": "testdata/src/main/describe-json.go:13:6",
"pts": [
{
"type": "*describe.D",
"namepos": "testdata/src/main/describe-json.go:27:6",
"labels": [
{
"pos": "testdata/src/main/describe-json.go:15:10",
"desc": "new"
}
]
},
{
"type": "describe.C",
"namepos": "testdata/src/main/describe-json.go:26:6"
}
]
}
}
}-------- @describe desc-stmt --------
{
"mode": "describe",
"describe": {
"desc": "go statement",
"pos": "testdata/src/main/describe-json.go:19:2",
"detail": "unknown"
}
}-------- @describe desc-type-C --------
{
"mode": "describe",
"describe": {
"desc": "definition of type describe.C",
"pos": "testdata/src/main/describe-json.go:26:6",
"detail": "type",
"type": {
"type": "describe.C",
"namepos": "testdata/src/main/describe-json.go:26:6",
"namedef": "int",
"methods": [
{
"name": "method (describe.C) f()",
"pos": "testdata/src/main/describe-json.go:29:12"
}
]
}
}
}

View File

@ -126,7 +126,7 @@ binary - operation of constant value -2
-------- @describe map-lookup,ok -------- -------- @describe map-lookup,ok --------
index expression of type (*int, bool) index expression of type (*int, bool)
no pointer analysis: can't locate SSA Value for expression in main.main no points-to information: can't locate SSA Value for expression in main.main
-------- @describe mapval -------- -------- @describe mapval --------
reference to var mapval *int reference to var mapval *int
@ -151,7 +151,7 @@ definition of var a int
-------- @describe b -------- -------- @describe b --------
definition of var b *int definition of var b *int
pointer analysis did not analyze this expression (dead code?) no points-to information: PTA did not encounter this expression (dead code?)
-------- @describe def-iface-I -------- -------- @describe def-iface-I --------
definition of type describe.I definition of type describe.I

View File

@ -1,14 +1,14 @@
-------- @freevars fv1 -------- -------- @freevars fv1 --------
Free identifers: Free identifers:
var x int
type C main.C type C main.C
const exp untyped integer const exp int
var x int
-------- @freevars fv2 -------- -------- @freevars fv2 --------
Free identifers: Free identifers:
var s.x int
var s.t.a int var s.t.a int
var s.t.b int var s.t.b int
var s.x int
var x int var x int
var y int32 var y int32

13
oracle/testdata/src/main/peers-json.go vendored Normal file
View File

@ -0,0 +1,13 @@
package peers
// Tests of channel 'peers' query, -format=json.
// See go.tools/oracle/oracle_test.go for explanation.
// See peers-json.golden for expected query results.
func main() {
chA := make(chan *int)
<-chA
select {
case <-chA: // @peers peer-recv-chA "<-"
}
}

View File

@ -0,0 +1,15 @@
-------- @peers peer-recv-chA --------
{
"mode": "peers",
"peers": {
"pos": "testdata/src/main/peers-json.go:11:7",
"type": "chan *int",
"allocs": [
"testdata/src/main/peers-json.go:8:13"
],
"receives": [
"testdata/src/main/peers-json.go:9:2",
"testdata/src/main/peers-json.go:11:7"
]
}
}

View File

@ -22,12 +22,12 @@ This channel of type chan *int may be:
allocated here allocated here
allocated here allocated here
sent to, here sent to, here
received from, here
received from, here
received from, here
received from, here
sent to, here sent to, here
received from, here received from, here
received from, here
received from, here
received from, here
received from, here
-------- @describe describe-rA -------- -------- @describe describe-rA --------
reference to var rA *int reference to var rA *int
@ -54,20 +54,20 @@ This channel of type chan *int may be:
allocated here allocated here
allocated here allocated here
sent to, here sent to, here
received from, here
received from, here
received from, here
received from, here
sent to, here sent to, here
received from, here received from, here
received from, here
received from, here
received from, here
received from, here
-------- @peers peer-send-chA' -------- -------- @peers peer-send-chA' --------
This channel of type chan *int may be: This channel of type chan *int may be:
allocated here allocated here
received from, here
received from, here
received from, here
received from, here
sent to, here sent to, here
received from, here received from, here
received from, here
received from, here
received from, here
received from, here

View File

@ -243,7 +243,7 @@ func Analyze(config *Config) CallGraphNode {
// Notify the client of the call graph, if // Notify the client of the call graph, if
// they're interested. // they're interested.
if Call != nil { if Call != nil {
Call(site, site.caller, cgn) Call(site, cgn)
} }
// Warn about calls to non-intrinsic external functions. // Warn about calls to non-intrinsic external functions.

View File

@ -25,6 +25,8 @@ type Config struct {
// Call is invoked for each discovered call-graph edge. The // Call is invoked for each discovered call-graph edge. The
// call-graph is a multigraph over CallGraphNodes with edges // call-graph is a multigraph over CallGraphNodes with edges
// labelled by the CallSite that gives rise to the edge. // labelled by the CallSite that gives rise to the edge.
// (The caller node is available as site.Caller())
//
// Clients that wish to construct a call graph may provide // Clients that wish to construct a call graph may provide
// CallGraph.AddEdge here. // CallGraph.AddEdge here.
// //
@ -32,7 +34,7 @@ type Config struct {
// distinguish separate calls to the same function depending // distinguish separate calls to the same function depending
// on the context. // on the context.
// //
Call func(site CallSite, caller, callee CallGraphNode) Call func(site CallSite, callee CallGraphNode)
// CallSite is invoked for each call-site encountered in the // CallSite is invoked for each call-site encountered in the
// program. // program.

View File

@ -108,7 +108,8 @@ func (c *callsite) String() string {
// //
type CallGraph map[CallGraphNode]map[CallGraphNode]CallSite type CallGraph map[CallGraphNode]map[CallGraphNode]CallSite
func (cg CallGraph) AddEdge(site CallSite, caller, callee CallGraphNode) { func (cg CallGraph) AddEdge(site CallSite, callee CallGraphNode) {
caller := site.Caller()
callees := cg[caller] callees := cg[caller]
if callees == nil { if callees == nil {
callees = make(map[CallGraphNode]CallSite) callees = make(map[CallGraphNode]CallSite)