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:
parent
0126405cad
commit
d2cdbefbfc
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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] }
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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] }
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
108
oracle/oracle.go
108
oracle/oracle.go
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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] }
|
||||||
|
|
|
@ -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 "^"
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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 "^"
|
|
@ -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
|
||||||
|
|
|
@ -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 "^"
|
||||||
|
})
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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() {}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 "<-"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue