oracle: several major improvements
Features: More robust: silently ignore type errors in modes that don't need SSA form: describe, referrers, implements, freevars, description. This makes the tool much more robust for everyday queries. Less configuration: don't require a scope argument for all queries. Only queries that do pointer analysis need it. For the rest, the initial position is enough for importQueryPackage to deduce the scope. It now works for queries in GoFiles, TestGoFiles, or XTestGoFiles. (It no longer works for ad-hoc main packages like $GOROOT/src/net/http/triv.go) More complete: "referrers" computes the scope automatically by scanning the import graph of the entire workspace, using gorename's refactor/importgraph package. This requires two passes at loading. Faster: simplified start-up logic avoids unnecessary package loading and SSA construction (a consequence of bad abstraction) in many cases. "callgraph": remove it. Unlike all the other commands it isn't related to the current selection, and we have golang.org/x/tools/cmdcallgraph now. Internals: Drop support for long-running clients (i.e., Pythia), since godoc -analysis supports all the same features except "pointsto", and precomputes all the results so latency is much lower. Get rid of various unhelpful abstractions introduced to support long-running clients. Expand out the set-up logic for each subcommand. This is simpler, easier to read, and gives us more control, at a small cost in duplication---the familiar story of abstractions. Discard PTA warnings. We weren't showing them (nor should we). Split tests into separate directories (so that importgraph works). Change-Id: I55d46b3ab33cdf7ac22436fcc2148fe04c901237 Reviewed-on: https://go-review.googlesource.com/8243 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
68b5f7541d
commit
b28839e4bd
|
|
@ -49,14 +49,14 @@ The -format flag controls the output format:
|
|||
json structured data in JSON syntax.
|
||||
xml structured data in XML syntax.
|
||||
|
||||
The -pos flag is required in all modes except 'callgraph'.
|
||||
The -pos flag is required in all modes.
|
||||
|
||||
The mode argument determines the query to perform:
|
||||
|
||||
callees show possible targets of selected function call
|
||||
callers show possible callers of selected function
|
||||
callgraph show complete callgraph of program
|
||||
callstack show path from callgraph root to selected function
|
||||
definition show declaration of selected identifier
|
||||
describe describe selected syntax: definition, methods, etc
|
||||
freevars show free variables of selection
|
||||
implements show 'implements' relation for selected type or method
|
||||
|
|
@ -166,8 +166,16 @@ func main() {
|
|||
}
|
||||
|
||||
// Ask the oracle.
|
||||
res, err := oracle.Query(args, mode, *posFlag, ptalog, &build.Default, *reflectFlag)
|
||||
if err != nil {
|
||||
query := oracle.Query{
|
||||
Mode: mode,
|
||||
Pos: *posFlag,
|
||||
Build: &build.Default,
|
||||
Scope: args,
|
||||
PTALog: ptalog,
|
||||
Reflection: *reflectFlag,
|
||||
}
|
||||
|
||||
if err := oracle.Run(&query); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "oracle: %s.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
@ -175,7 +183,7 @@ func main() {
|
|||
// Print the result.
|
||||
switch *formatFlag {
|
||||
case "json":
|
||||
b, err := json.MarshalIndent(res.Serial(), "", "\t")
|
||||
b, err := json.MarshalIndent(query.Serial(), "", "\t")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "oracle: JSON error: %s.\n", err)
|
||||
os.Exit(1)
|
||||
|
|
@ -183,7 +191,7 @@ func main() {
|
|||
os.Stdout.Write(b)
|
||||
|
||||
case "xml":
|
||||
b, err := xml.MarshalIndent(res.Serial(), "", "\t")
|
||||
b, err := xml.MarshalIndent(query.Serial(), "", "\t")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "oracle: XML error: %s.\n", err)
|
||||
os.Exit(1)
|
||||
|
|
@ -191,6 +199,6 @@ func main() {
|
|||
os.Stdout.Write(b)
|
||||
|
||||
case "plain":
|
||||
res.WriteTo(os.Stdout)
|
||||
query.WriteTo(os.Stdout)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
oracle/TODO
16
oracle/TODO
|
|
@ -6,15 +6,9 @@ ORACLE TODO
|
|||
General
|
||||
=======
|
||||
|
||||
Refactor control flow so that each mode has a "one-shot setup" function.
|
||||
|
||||
Use a fault-tolerant parser that can recover from bad parses.
|
||||
|
||||
Save unsaved editor buffers into an archive and provide that to the
|
||||
tools, which should act as if they were saved.
|
||||
|
||||
Fix: make the guessImportPath hack work with external _test.go files too.
|
||||
|
||||
Include complete pos/end information Serial output.
|
||||
But beware that sometimes a single token (e.g. +) is more helpful
|
||||
than the pos/end of the containing expression (e.g. x \n + \n y).
|
||||
|
|
@ -34,12 +28,6 @@ implements
|
|||
|
||||
definition, referrers
|
||||
|
||||
Use the parser's resolver information to answer the query
|
||||
for local names. Only run the type checker if that fails.
|
||||
(NB: gri's new parser won't do any resolution.)
|
||||
|
||||
referrers: Show the text of the matching line of code, like grep.
|
||||
|
||||
definition: Make it work with qualified identifiers (SelectorExpr) too.
|
||||
|
||||
references: Make it work on things that are implicit idents, like
|
||||
|
|
@ -50,8 +38,6 @@ what
|
|||
Report def/ref info if available.
|
||||
Editors could use it to highlight all idents of the same local var.
|
||||
|
||||
Fix: support it in (*Oracle).Query (long-running tools).
|
||||
|
||||
More tests.
|
||||
|
||||
pointsto
|
||||
|
|
@ -95,5 +81,3 @@ Emacs: go-root-and-paths depends on the current buffer, so be sure to
|
|||
call it from within the source file, not the *go-oracle* buffer:
|
||||
the user may have switched workspaces and the oracle should run in
|
||||
the new one.
|
||||
|
||||
Support other editors: vim, Eclipse, Sublime, etc.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import (
|
|||
"go/token"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/pointer"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
|
|
@ -17,10 +19,40 @@ import (
|
|||
|
||||
// Callees reports the possible callees of the function call site
|
||||
// identified by the specified source location.
|
||||
func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
pkg := o.prog.Package(qpos.info.Pkg)
|
||||
func callees(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
||||
// Determine initial packages for PTA.
|
||||
args, err := lconf.FromArgs(q.Scope, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("surplus arguments: %q", args)
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssa.Create(lprog, 0)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkg := prog.Package(qpos.info.Pkg)
|
||||
if pkg == nil {
|
||||
return nil, fmt.Errorf("no SSA package")
|
||||
return fmt.Errorf("no SSA package")
|
||||
}
|
||||
|
||||
// Determine the enclosing call for the specified position.
|
||||
|
|
@ -31,7 +63,7 @@ func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
}
|
||||
}
|
||||
if e == nil {
|
||||
return nil, fmt.Errorf("there is no function call here")
|
||||
return fmt.Errorf("there is no function call here")
|
||||
}
|
||||
// TODO(adonovan): issue an error if the call is "too far
|
||||
// away" from the current selection, as this most likely is
|
||||
|
|
@ -39,39 +71,41 @@ func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
|
||||
// Reject type conversions.
|
||||
if qpos.info.Types[e.Fun].IsType() {
|
||||
return nil, fmt.Errorf("this is a type conversion, not a function call")
|
||||
return fmt.Errorf("this is a type conversion, not a function call")
|
||||
}
|
||||
|
||||
// Reject calls to built-ins.
|
||||
if id, ok := unparen(e.Fun).(*ast.Ident); ok {
|
||||
if b, ok := qpos.info.Uses[id].(*types.Builtin); ok {
|
||||
return nil, fmt.Errorf("this is a call to the built-in '%s' operator", b.Name())
|
||||
return fmt.Errorf("this is a call to the built-in '%s' operator", b.Name())
|
||||
}
|
||||
}
|
||||
|
||||
buildSSA(o)
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.BuildAll()
|
||||
|
||||
// Ascertain calling function and call site.
|
||||
callerFn := ssa.EnclosingFunction(pkg, qpos.path)
|
||||
if callerFn == nil {
|
||||
return nil, fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
return fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
}
|
||||
|
||||
// Find the call site.
|
||||
site, err := findCallSite(callerFn, e.Lparen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
funcs, err := findCallees(o, site)
|
||||
funcs, err := findCallees(ptaConfig, site)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
return &calleesResult{
|
||||
q.result = &calleesResult{
|
||||
site: site,
|
||||
funcs: funcs,
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findCallSite(fn *ssa.Function, lparen token.Pos) (ssa.CallInstruction, error) {
|
||||
|
|
@ -85,7 +119,7 @@ func findCallSite(fn *ssa.Function, lparen token.Pos) (ssa.CallInstruction, erro
|
|||
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
||||
}
|
||||
|
||||
func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
||||
func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
||||
// Avoid running the pointer analysis for static calls.
|
||||
if callee := site.Common().StaticCallee(); callee != nil {
|
||||
switch callee.String() {
|
||||
|
|
@ -99,8 +133,8 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
|||
}
|
||||
|
||||
// Dynamic call: use pointer analysis.
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(o).CallGraph
|
||||
conf.BuildCallGraph = true
|
||||
cg := ptrAnalysis(conf).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
|
||||
// Find all call edges from the site.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
|
@ -16,35 +17,67 @@ import (
|
|||
// Callers reports the possible callers of the function
|
||||
// immediately enclosing the specified source location.
|
||||
//
|
||||
func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
pkg := o.prog.Package(qpos.info.Pkg)
|
||||
if pkg == nil {
|
||||
return nil, fmt.Errorf("no SSA package")
|
||||
func callers(conf *Query) error {
|
||||
lconf := loader.Config{Build: conf.Build}
|
||||
|
||||
// Determine initial packages for PTA.
|
||||
args, err := lconf.FromArgs(conf.Scope, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
|
||||
return nil, fmt.Errorf("this position is not inside a function")
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("surplus arguments: %q", args)
|
||||
}
|
||||
|
||||
buildSSA(o)
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, conf.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssa.Create(lprog, 0)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, conf.PTALog, conf.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkg := prog.Package(qpos.info.Pkg)
|
||||
if pkg == nil {
|
||||
return fmt.Errorf("no SSA package")
|
||||
}
|
||||
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
|
||||
return fmt.Errorf("this position is not inside a function")
|
||||
}
|
||||
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.BuildAll()
|
||||
|
||||
target := ssa.EnclosingFunction(pkg, qpos.path)
|
||||
if target == nil {
|
||||
return nil, fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
return fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
}
|
||||
|
||||
// Run the pointer analysis, recording each
|
||||
// call found to originate from target.
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(o).CallGraph
|
||||
ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(ptaConfig).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
edges := cg.CreateNode(target).In
|
||||
// TODO(adonovan): sort + dedup calls to ensure test determinism.
|
||||
|
||||
return &callersResult{
|
||||
conf.result = &callersResult{
|
||||
target: target,
|
||||
callgraph: cg,
|
||||
edges: edges,
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type callersResult struct {
|
||||
|
|
|
|||
|
|
@ -1,152 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// doCallgraph displays the entire callgraph of the current program,
|
||||
// or if a query -pos was provided, the query package.
|
||||
func doCallgraph(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
buildSSA(o)
|
||||
|
||||
// Run the pointer analysis and build the callgraph.
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(o).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
|
||||
var qpkg *types.Package
|
||||
var isQueryPkg func(fn *ssa.Function) bool
|
||||
var keep, remove, roots []*callgraph.Node
|
||||
if qpos == nil {
|
||||
// No -pos provided: show complete callgraph.
|
||||
roots = append(roots, cg.Root)
|
||||
isQueryPkg = func(fn *ssa.Function) bool { return true }
|
||||
} else {
|
||||
// A query -pos was provided: restrict result to
|
||||
// functions belonging to the query package.
|
||||
qpkg = qpos.info.Pkg
|
||||
isQueryPkg = func(fn *ssa.Function) bool {
|
||||
return fn.Pkg != nil && fn.Pkg.Object == qpkg
|
||||
}
|
||||
}
|
||||
|
||||
// First compute the nodes to keep and remove.
|
||||
for fn, cgn := range cg.Nodes {
|
||||
if isQueryPkg(fn) {
|
||||
keep = append(keep, cgn)
|
||||
} else {
|
||||
remove = append(remove, cgn)
|
||||
}
|
||||
}
|
||||
|
||||
// Compact the Node.ID sequence of the kept nodes,
|
||||
// preserving the original order.
|
||||
sort.Sort(nodesByID(keep))
|
||||
for i, cgn := range keep {
|
||||
cgn.ID = i
|
||||
}
|
||||
|
||||
// Compute the set of roots:
|
||||
// in-package nodes with out-of-package callers.
|
||||
// For determinism, roots are ordered by original Node.ID.
|
||||
for _, cgn := range keep {
|
||||
for _, e := range cgn.In {
|
||||
if !isQueryPkg(e.Caller.Func) {
|
||||
roots = append(roots, cgn)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, discard all out-of-package nodes.
|
||||
for _, cgn := range remove {
|
||||
cg.DeleteNode(cgn)
|
||||
}
|
||||
|
||||
return &callgraphResult{qpkg, cg.Nodes, roots}, nil
|
||||
}
|
||||
|
||||
type callgraphResult struct {
|
||||
qpkg *types.Package
|
||||
nodes map[*ssa.Function]*callgraph.Node
|
||||
roots []*callgraph.Node
|
||||
}
|
||||
|
||||
func (r *callgraphResult) display(printf printfFunc) {
|
||||
descr := "the entire program"
|
||||
if r.qpkg != nil {
|
||||
descr = fmt.Sprintf("package %s", r.qpkg.Path())
|
||||
}
|
||||
|
||||
printf(nil, `
|
||||
Below is a call graph of %s.
|
||||
The numbered nodes form a spanning tree.
|
||||
Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||
number follows in parentheses.
|
||||
`, descr)
|
||||
|
||||
printed := make(map[*callgraph.Node]int)
|
||||
var print func(caller *callgraph.Node, indent int)
|
||||
print = func(caller *callgraph.Node, indent int) {
|
||||
if num, ok := printed[caller]; !ok {
|
||||
num = len(printed)
|
||||
printed[caller] = num
|
||||
|
||||
// Sort the children into name order for deterministic* output.
|
||||
// (*mostly: anon funcs' names are not globally unique.)
|
||||
var funcs funcsByName
|
||||
for callee := range callgraph.CalleesOf(caller) {
|
||||
funcs = append(funcs, callee.Func)
|
||||
}
|
||||
sort.Sort(funcs)
|
||||
|
||||
printf(caller.Func, "%d\t%*s%s", num, 4*indent, "", caller.Func.RelString(r.qpkg))
|
||||
for _, callee := range funcs {
|
||||
print(r.nodes[callee], indent+1)
|
||||
}
|
||||
} else {
|
||||
printf(caller.Func, "\t%*s%s (%d)", 4*indent, "", caller.Func.RelString(r.qpkg), num)
|
||||
}
|
||||
}
|
||||
for _, root := range r.roots {
|
||||
print(root, 0)
|
||||
}
|
||||
}
|
||||
|
||||
type nodesByID []*callgraph.Node
|
||||
|
||||
func (s nodesByID) Len() int { return len(s) }
|
||||
func (s nodesByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s nodesByID) Less(i, j int) bool { return s[i].ID < s[j].ID }
|
||||
|
||||
type funcsByName []*ssa.Function
|
||||
|
||||
func (s funcsByName) Len() int { return len(s) }
|
||||
func (s funcsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s funcsByName) Less(i, j int) bool { return s[i].String() < s[j].String() }
|
||||
|
||||
func (r *callgraphResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
cg := make([]serial.CallGraph, len(r.nodes))
|
||||
for _, n := range r.nodes {
|
||||
j := &cg[n.ID]
|
||||
fn := n.Func
|
||||
j.Name = fn.String()
|
||||
j.Pos = fset.Position(fn.Pos()).String()
|
||||
for callee := range callgraph.CalleesOf(n) {
|
||||
j.Children = append(j.Children, callee.ID)
|
||||
}
|
||||
sort.Ints(j.Children)
|
||||
}
|
||||
res.Callgraph = cg
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
|
@ -23,26 +24,57 @@ import (
|
|||
// TODO(adonovan): permit user to specify a starting point other than
|
||||
// the analysis root.
|
||||
//
|
||||
func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
pkg := o.prog.Package(qpos.info.Pkg)
|
||||
func callstack(conf *Query) error {
|
||||
fset := token.NewFileSet()
|
||||
lconf := loader.Config{Fset: fset, Build: conf.Build}
|
||||
|
||||
// Determine initial packages for PTA.
|
||||
args, err := lconf.FromArgs(conf.Scope, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("surplus arguments: %q", args)
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qpos, err := parseQueryPos(lprog, conf.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssa.Create(lprog, 0)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, conf.PTALog, conf.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkg := prog.Package(qpos.info.Pkg)
|
||||
if pkg == nil {
|
||||
return nil, fmt.Errorf("no SSA package")
|
||||
return fmt.Errorf("no SSA package")
|
||||
}
|
||||
|
||||
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
|
||||
return nil, fmt.Errorf("this position is not inside a function")
|
||||
return fmt.Errorf("this position is not inside a function")
|
||||
}
|
||||
|
||||
buildSSA(o)
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.BuildAll()
|
||||
|
||||
target := ssa.EnclosingFunction(pkg, qpos.path)
|
||||
if target == nil {
|
||||
return nil, fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
return fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
}
|
||||
|
||||
// Run the pointer analysis and build the complete call graph.
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(o).CallGraph
|
||||
ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(ptaConfig).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
|
||||
// Search for an arbitrary path from a root to the target function.
|
||||
|
|
@ -52,15 +84,17 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
callpath = callpath[1:] // remove synthetic edge from <root>
|
||||
}
|
||||
|
||||
return &callstackResult{
|
||||
conf.Fset = fset
|
||||
conf.result = &callstackResult{
|
||||
qpos: qpos,
|
||||
target: target,
|
||||
callpath: callpath,
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type callstackResult struct {
|
||||
qpos *QueryPos
|
||||
qpos *queryPos
|
||||
target *ssa.Function
|
||||
callpath []*callgraph.Edge
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
|
@ -18,28 +19,48 @@ import (
|
|||
// TODO(adonovan): opt: for intra-file references, the parser's
|
||||
// resolution might be enough; we should start with that.
|
||||
//
|
||||
func definition(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
func definition(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ := qpos.path[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return nil, fmt.Errorf("no identifier here")
|
||||
return fmt.Errorf("no identifier here")
|
||||
}
|
||||
|
||||
obj := qpos.info.ObjectOf(id)
|
||||
if obj == nil {
|
||||
// Happens for y in "switch y := x.(type)", but I think that's all.
|
||||
return nil, fmt.Errorf("no object for identifier")
|
||||
return fmt.Errorf("no object for identifier")
|
||||
}
|
||||
|
||||
return &definitionResult{qpos, obj}, nil
|
||||
q.result = &definitionResult{qpos, obj}
|
||||
return nil
|
||||
}
|
||||
|
||||
type definitionResult struct {
|
||||
qpos *QueryPos
|
||||
qpos *queryPos
|
||||
obj types.Object // object it denotes
|
||||
}
|
||||
|
||||
func (r *definitionResult) display(printf printfFunc) {
|
||||
printf(r.obj, "defined here as %s", r.qpos.ObjectString(r.obj))
|
||||
printf(r.obj, "defined here as %s", r.qpos.objectString(r.obj))
|
||||
}
|
||||
|
||||
func (r *definitionResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
|
|
|
|||
|
|
@ -27,32 +27,52 @@ import (
|
|||
// - the definition of its referent (for identifiers) [now redundant]
|
||||
// - its type and method set (for an expression or type expression)
|
||||
//
|
||||
func describe(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
func describe(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if false { // debugging
|
||||
fprintf(os.Stderr, o.fset, qpos.path[0], "you selected: %s %s",
|
||||
fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
|
||||
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
|
||||
}
|
||||
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
switch action {
|
||||
case actionExpr:
|
||||
return describeValue(o, qpos, path)
|
||||
q.result, err = describeValue(qpos, path)
|
||||
|
||||
case actionType:
|
||||
return describeType(o, qpos, path)
|
||||
q.result, err = describeType(qpos, path)
|
||||
|
||||
case actionPackage:
|
||||
return describePackage(o, qpos, path)
|
||||
q.result, err = describePackage(qpos, path)
|
||||
|
||||
case actionStmt:
|
||||
return describeStmt(o, qpos, path)
|
||||
q.result, err = describeStmt(qpos, path)
|
||||
|
||||
case actionUnknown:
|
||||
return &describeUnknownResult{path[0]}, nil
|
||||
q.result = &describeUnknownResult{path[0]}
|
||||
|
||||
default:
|
||||
panic(action) // unreachable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type describeUnknownResult struct {
|
||||
|
|
@ -288,7 +308,7 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No
|
|||
return nil, actionUnknown // unreachable
|
||||
}
|
||||
|
||||
func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueResult, error) {
|
||||
func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
|
||||
var expr ast.Expr
|
||||
var obj types.Object
|
||||
switch n := path[0].(type) {
|
||||
|
|
@ -318,7 +338,7 @@ func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueRe
|
|||
}
|
||||
|
||||
type describeValueResult struct {
|
||||
qpos *QueryPos
|
||||
qpos *queryPos
|
||||
expr ast.Expr // query node
|
||||
typ types.Type // type of expression
|
||||
constVal exact.Value // value of expression, if constant
|
||||
|
|
@ -345,10 +365,10 @@ func (r *describeValueResult) display(printf printfFunc) {
|
|||
if r.obj != nil {
|
||||
if r.obj.Pos() == r.expr.Pos() {
|
||||
// defining ident
|
||||
printf(r.expr, "definition of %s%s%s", prefix, r.qpos.ObjectString(r.obj), suffix)
|
||||
printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
||||
} else {
|
||||
// referring ident
|
||||
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.ObjectString(r.obj), suffix)
|
||||
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
||||
if def := r.obj.Pos(); def != token.NoPos {
|
||||
printf(def, "defined here")
|
||||
}
|
||||
|
|
@ -360,7 +380,7 @@ func (r *describeValueResult) display(printf printfFunc) {
|
|||
printf(r.expr, "%s%s", desc, suffix)
|
||||
} else {
|
||||
// non-constant expression
|
||||
printf(r.expr, "%s of type %s", desc, r.qpos.TypeString(r.typ))
|
||||
printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -379,7 +399,7 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet)
|
|||
Pos: fset.Position(r.expr.Pos()).String(),
|
||||
Detail: "value",
|
||||
Value: &serial.DescribeValue{
|
||||
Type: r.qpos.TypeString(r.typ),
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
Value: value,
|
||||
ObjPos: objpos,
|
||||
},
|
||||
|
|
@ -388,7 +408,7 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet)
|
|||
|
||||
// ---- TYPE ------------------------------------------------------------
|
||||
|
||||
func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResult, error) {
|
||||
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
|
||||
var description string
|
||||
var t types.Type
|
||||
switch n := path[0].(type) {
|
||||
|
|
@ -415,14 +435,12 @@ func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResu
|
|||
return nil, fmt.Errorf("unexpected AST for type: %T", n)
|
||||
}
|
||||
|
||||
description = description + "type " + qpos.TypeString(t)
|
||||
description = description + "type " + qpos.typeString(t)
|
||||
|
||||
// Show sizes for structs and named types (it's fairly obvious for others).
|
||||
switch t.(type) {
|
||||
case *types.Named, *types.Struct:
|
||||
// TODO(adonovan): use o.imp.Config().TypeChecker.Sizes when
|
||||
// we add the Config() method (needs some thought).
|
||||
szs := types.StdSizes{8, 8}
|
||||
szs := types.StdSizes{8, 8} // assume amd64
|
||||
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
||||
szs.Sizeof(t), szs.Alignof(t))
|
||||
}
|
||||
|
|
@ -437,7 +455,7 @@ func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResu
|
|||
}
|
||||
|
||||
type describeTypeResult struct {
|
||||
qpos *QueryPos
|
||||
qpos *queryPos
|
||||
node ast.Node
|
||||
description string
|
||||
typ types.Type
|
||||
|
|
@ -449,7 +467,7 @@ func (r *describeTypeResult) display(printf printfFunc) {
|
|||
|
||||
// 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() {
|
||||
printf(nt.Obj(), "defined as %s", r.qpos.TypeString(nt.Underlying()))
|
||||
printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying()))
|
||||
}
|
||||
|
||||
// Print the method set, if the type kind is capable of bearing methods.
|
||||
|
|
@ -461,7 +479,7 @@ func (r *describeTypeResult) display(printf printfFunc) {
|
|||
// TODO(adonovan): print these relative
|
||||
// to the owning package, not the
|
||||
// query package.
|
||||
printf(meth.Obj(), "\t%s", r.qpos.SelectionString(meth))
|
||||
printf(meth.Obj(), "\t%s", r.qpos.selectionString(meth))
|
||||
}
|
||||
} else {
|
||||
printf(r.node, "No methods.")
|
||||
|
|
@ -480,7 +498,7 @@ func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "type",
|
||||
Type: &serial.DescribeType{
|
||||
Type: r.qpos.TypeString(r.typ),
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
NamePos: namePos,
|
||||
NameDef: nameDef,
|
||||
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
|
||||
|
|
@ -490,7 +508,7 @@ func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|||
|
||||
// ---- PACKAGE ------------------------------------------------------------
|
||||
|
||||
func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePackageResult, error) {
|
||||
func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
|
||||
var description string
|
||||
var pkg *types.Package
|
||||
switch n := path[0].(type) {
|
||||
|
|
@ -542,7 +560,7 @@ func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePacka
|
|||
}
|
||||
}
|
||||
|
||||
return &describePackageResult{o.fset, path[0], description, pkg, members}, nil
|
||||
return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
|
||||
}
|
||||
|
||||
type describePackageResult struct {
|
||||
|
|
@ -661,7 +679,7 @@ func tokenOf(o types.Object) string {
|
|||
|
||||
// ---- STATEMENT ------------------------------------------------------------
|
||||
|
||||
func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResult, error) {
|
||||
func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
|
||||
var description string
|
||||
switch n := path[0].(type) {
|
||||
case *ast.Ident:
|
||||
|
|
@ -675,7 +693,7 @@ func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResu
|
|||
// Nothing much to say about statements.
|
||||
description = astutil.NodeDescription(n)
|
||||
}
|
||||
return &describeStmtResult{o.fset, path[0], description}, nil
|
||||
return &describeStmtResult{qpos.fset, path[0], description}, nil
|
||||
}
|
||||
|
||||
type describeStmtResult struct {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"go/token"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
|
@ -28,7 +29,26 @@ import (
|
|||
// these might be interesting. Perhaps group the results into three
|
||||
// bands.
|
||||
//
|
||||
func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
func freevars(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file := qpos.path[len(qpos.path)-1] // the enclosing file
|
||||
fileScope := qpos.info.Scopes[file]
|
||||
pkgScope := fileScope.Parent()
|
||||
|
|
@ -118,7 +138,7 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
}
|
||||
|
||||
typ := qpos.info.TypeOf(n.(ast.Expr))
|
||||
ref := freevarsRef{kind, printNode(o.fset, n), typ, obj}
|
||||
ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj}
|
||||
refsMap[ref.ref] = ref
|
||||
|
||||
if prune {
|
||||
|
|
@ -136,14 +156,15 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
}
|
||||
sort.Sort(byRef(refs))
|
||||
|
||||
return &freevarsResult{
|
||||
q.result = &freevarsResult{
|
||||
qpos: qpos,
|
||||
refs: refs,
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type freevarsResult struct {
|
||||
qpos *QueryPos
|
||||
qpos *queryPos
|
||||
refs []freevarsRef
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,16 +12,37 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// Implements displays the "implements" relation as it pertains to the
|
||||
// selected type. If the selection is a method, 'implements' displays
|
||||
// selected type within a single package.
|
||||
// If the selection is a method, 'implements' displays
|
||||
// the corresponding methods of the types that would have been reported
|
||||
// by an implements query on the receiver type.
|
||||
//
|
||||
func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
func implements(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the selected type.
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
|
||||
|
|
@ -35,7 +56,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
|
||||
recv := obj.Type().(*types.Signature).Recv()
|
||||
if recv == nil {
|
||||
return nil, fmt.Errorf("this function is not a method")
|
||||
return fmt.Errorf("this function is not a method")
|
||||
}
|
||||
method = obj
|
||||
T = recv.Type()
|
||||
|
|
@ -45,7 +66,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
T = qpos.info.TypeOf(path[0].(ast.Expr))
|
||||
}
|
||||
if T == nil {
|
||||
return nil, fmt.Errorf("no type or method here")
|
||||
return fmt.Errorf("no type or method here")
|
||||
}
|
||||
|
||||
// Find all named types, even local types (which can have
|
||||
|
|
@ -55,7 +76,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
// i.e. don't reduceScope?
|
||||
//
|
||||
var allNamed []types.Type
|
||||
for _, info := range o.typeInfo {
|
||||
for _, info := range lprog.AllPackages {
|
||||
for _, obj := range info.Defs {
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
allNamed = append(allNamed, obj.Type())
|
||||
|
|
@ -135,11 +156,14 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return &implementsResult{qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod}, nil
|
||||
q.result = &implementsResult{
|
||||
qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type implementsResult struct {
|
||||
qpos *QueryPos
|
||||
qpos *queryPos
|
||||
|
||||
t types.Type // queried type (not necessarily named)
|
||||
pos interface{} // pos of t (*types.Name or *QueryPos)
|
||||
|
|
@ -160,7 +184,7 @@ func (r *implementsResult) display(printf printfFunc) {
|
|||
meth := func(sel *types.Selection) {
|
||||
if sel != nil {
|
||||
printf(sel.Obj(), "\t%s method (%s).%s",
|
||||
relation, r.qpos.TypeString(sel.Recv()), sel.Obj().Name())
|
||||
relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +197,7 @@ func (r *implementsResult) display(printf printfFunc) {
|
|||
if r.method == nil {
|
||||
printf(r.pos, "interface type %s", r.t)
|
||||
} else {
|
||||
printf(r.method, "abstract method %s", r.qpos.ObjectString(r.method))
|
||||
printf(r.method, "abstract method %s", r.qpos.objectString(r.method))
|
||||
}
|
||||
|
||||
// Show concrete types (or methods) first; use two passes.
|
||||
|
|
@ -214,7 +238,7 @@ func (r *implementsResult) display(printf printfFunc) {
|
|||
printf(r.pos, "%s type %s", typeKind(r.t), r.t)
|
||||
} else {
|
||||
printf(r.method, "concrete method %s",
|
||||
r.qpos.ObjectString(r.method))
|
||||
r.qpos.objectString(r.method))
|
||||
}
|
||||
for i, super := range r.from {
|
||||
if r.method == nil {
|
||||
|
|
@ -231,7 +255,7 @@ func (r *implementsResult) display(printf printfFunc) {
|
|||
} else {
|
||||
// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
|
||||
printf(r.method, "concrete method %s",
|
||||
r.qpos.ObjectString(r.method))
|
||||
r.qpos.objectString(r.method))
|
||||
}
|
||||
|
||||
for i, psuper := range r.fromPtr {
|
||||
|
|
@ -260,7 +284,7 @@ func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|||
}
|
||||
if r.method != nil {
|
||||
res.Implements.Method = &serial.DescribeMethod{
|
||||
Name: r.qpos.ObjectString(r.method),
|
||||
Name: r.qpos.objectString(r.method),
|
||||
Pos: fset.Position(r.method.Pos()).String(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
494
oracle/oracle.go
494
oracle/oracle.go
|
|
@ -19,42 +19,13 @@ package oracle // import "golang.org/x/tools/oracle"
|
|||
// - show all places where an object of type T is created
|
||||
// (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
|
||||
|
||||
// ORACLE CONTROL FLOW
|
||||
//
|
||||
// The Oracle is somewhat convoluted due to the need to support two
|
||||
// very different use-cases, "one-shot" and "long running", and to do
|
||||
// so quickly.
|
||||
//
|
||||
// The cmd/oracle tool issues "one-shot" queries via the exported
|
||||
// Query function, which creates an Oracle to answer a single query.
|
||||
// newOracle consults the 'needs' flags of the query mode and the
|
||||
// package containing the query to avoid doing more work than it needs
|
||||
// (loading, parsing, type checking, SSA construction).
|
||||
//
|
||||
// The Pythia tool (github.com/fzipp/pythia) is an example of a "long
|
||||
// running" tool. It calls New() and then loops, calling
|
||||
// ParseQueryPos and (*Oracle).Query to handle each incoming HTTP
|
||||
// query. Since New cannot see which queries will follow, it must
|
||||
// load, parse, type-check and SSA-build the entire transitive closure
|
||||
// of the analysis scope, retaining full debug information and all
|
||||
// typed ASTs.
|
||||
//
|
||||
// TODO(adonovan): experiment with inverting the control flow by
|
||||
// making each mode consist of two functions: a "one-shot setup"
|
||||
// function and the existing "impl" function. The one-shot setup
|
||||
// function would do all of the work of Query and newOracle,
|
||||
// specialized to each mode, calling library utilities for the common
|
||||
// things. This would give it more control over "scope reduction".
|
||||
// Long running tools would not call the one-shot setup function but
|
||||
// would have their own setup function equivalent to the existing
|
||||
// 'needsAll' flow path.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
|
|
@ -64,64 +35,6 @@ import (
|
|||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// An Oracle holds the program state required for one or more queries.
|
||||
type Oracle struct {
|
||||
fset *token.FileSet // file set [all queries]
|
||||
prog *ssa.Program // the SSA program [needSSA]
|
||||
ptaConfig pointer.Config // pointer analysis configuration [needPTA]
|
||||
typeInfo map[*types.Package]*loader.PackageInfo // type info for all ASTs in the program [needRetainTypeInfo]
|
||||
}
|
||||
|
||||
// A set of bits indicating the analytical requirements of each mode.
|
||||
//
|
||||
// Typed ASTs for the whole program are always constructed
|
||||
// transiently; they are retained only for the queried package unless
|
||||
// needRetainTypeInfo is set.
|
||||
const (
|
||||
needPos = 1 << iota // needs a position
|
||||
needExactPos // needs an exact AST selection; implies needPos
|
||||
needRetainTypeInfo // needs to retain type info for all ASTs in the program
|
||||
needSSA // needs ssa.Packages for whole program
|
||||
needSSADebug // needs debug info for ssa.Packages
|
||||
needPTA = needSSA // needs pointer analysis
|
||||
needAll = -1 // needs everything (e.g. a sequence of queries)
|
||||
)
|
||||
|
||||
type modeInfo struct {
|
||||
name string
|
||||
needs int
|
||||
impl func(*Oracle, *QueryPos) (queryResult, error)
|
||||
}
|
||||
|
||||
var modes = []*modeInfo{
|
||||
// Pointer analyses, whole program:
|
||||
{"callees", needPTA | needExactPos, callees},
|
||||
{"callers", needPTA | needPos, callers},
|
||||
{"callgraph", needPTA, doCallgraph},
|
||||
{"callstack", needPTA | needPos, callstack},
|
||||
{"peers", needPTA | needSSADebug | needPos, peers},
|
||||
{"pointsto", needPTA | needSSADebug | needExactPos, pointsto},
|
||||
{"whicherrs", needPTA | needSSADebug | needExactPos, whicherrs},
|
||||
|
||||
// Type-based, modular analyses:
|
||||
{"definition", needPos, definition},
|
||||
{"describe", needExactPos, describe},
|
||||
{"freevars", needPos, freevars},
|
||||
|
||||
// Type-based, whole-program analyses:
|
||||
{"implements", needRetainTypeInfo | needPos, implements},
|
||||
{"referrers", needRetainTypeInfo | needPos, referrers},
|
||||
}
|
||||
|
||||
func findMode(mode string) *modeInfo {
|
||||
for _, m := range modes {
|
||||
if m.name == mode {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type printfFunc func(pos interface{}, format string, args ...interface{})
|
||||
|
||||
// queryResult is the interface of each query-specific result type.
|
||||
|
|
@ -133,9 +46,8 @@ type queryResult interface {
|
|||
// A QueryPos represents the position provided as input to a query:
|
||||
// a textual extent in the program's source code, the AST node it
|
||||
// corresponds to, and the package to which it belongs.
|
||||
// Instances are created by ParseQueryPos.
|
||||
//
|
||||
type QueryPos struct {
|
||||
// Instances are created by parseQueryPos.
|
||||
type queryPos struct {
|
||||
fset *token.FileSet
|
||||
start, end token.Pos // source extent of query
|
||||
path []ast.Node // AST path from query node to root of ast.File
|
||||
|
|
@ -144,162 +56,139 @@ type QueryPos struct {
|
|||
}
|
||||
|
||||
// TypeString prints type T relative to the query position.
|
||||
func (qpos *QueryPos) TypeString(T types.Type) string {
|
||||
func (qpos *queryPos) typeString(T types.Type) string {
|
||||
return types.TypeString(qpos.info.Pkg, T)
|
||||
}
|
||||
|
||||
// ObjectString prints object obj relative to the query position.
|
||||
func (qpos *QueryPos) ObjectString(obj types.Object) string {
|
||||
func (qpos *queryPos) objectString(obj types.Object) string {
|
||||
return types.ObjectString(qpos.info.Pkg, obj)
|
||||
}
|
||||
|
||||
// SelectionString prints selection sel relative to the query position.
|
||||
func (qpos *QueryPos) SelectionString(sel *types.Selection) string {
|
||||
func (qpos *queryPos) selectionString(sel *types.Selection) string {
|
||||
return types.SelectionString(qpos.info.Pkg, sel)
|
||||
}
|
||||
|
||||
// A Result encapsulates the result of an oracle.Query.
|
||||
type Result struct {
|
||||
fset *token.FileSet
|
||||
q queryResult // the query-specific result
|
||||
mode string // query mode
|
||||
warnings []pointer.Warning // pointer analysis warnings (TODO(adonovan): fix: never populated!)
|
||||
// A Query specifies a single oracle query.
|
||||
type Query struct {
|
||||
Mode string // query mode ("callers", etc)
|
||||
Pos string // query position
|
||||
Build *build.Context // package loading configuration
|
||||
|
||||
// pointer analysis options
|
||||
Scope []string // main package in (*loader.Config).FromArgs syntax
|
||||
PTALog io.Writer // (optional) pointer-analysis log file
|
||||
Reflection bool // model reflection soundly (currently slow).
|
||||
|
||||
// Populated during Run()
|
||||
Fset *token.FileSet
|
||||
result queryResult
|
||||
}
|
||||
|
||||
// Serial returns an instance of serial.Result, which implements the
|
||||
// {xml,json}.Marshaler interfaces so that query results can be
|
||||
// serialized as JSON or XML.
|
||||
//
|
||||
func (res *Result) Serial() *serial.Result {
|
||||
resj := &serial.Result{Mode: res.mode}
|
||||
res.q.toSerial(resj, res.fset)
|
||||
for _, w := range res.warnings {
|
||||
resj.Warnings = append(resj.Warnings, serial.PTAWarning{
|
||||
Pos: res.fset.Position(w.Pos).String(),
|
||||
Message: w.Message,
|
||||
})
|
||||
}
|
||||
func (q *Query) Serial() *serial.Result {
|
||||
resj := &serial.Result{Mode: q.Mode}
|
||||
q.result.toSerial(resj, q.Fset)
|
||||
return resj
|
||||
}
|
||||
|
||||
// Query runs a single oracle query.
|
||||
//
|
||||
// args specify the main package in (*loader.Config).FromArgs syntax.
|
||||
// mode is the query mode ("callers", etc).
|
||||
// ptalog is the (optional) pointer-analysis log file.
|
||||
// buildContext is the go/build configuration for locating packages.
|
||||
// reflection determines whether to model reflection soundly (currently slow).
|
||||
//
|
||||
// Clients that intend to perform multiple queries against the same
|
||||
// analysis scope should use this pattern instead:
|
||||
//
|
||||
// conf := loader.Config{Build: buildContext}
|
||||
// ... populate config, e.g. conf.FromArgs(args) ...
|
||||
// iprog, err := conf.Load()
|
||||
// if err != nil { ... }
|
||||
// o, err := oracle.New(iprog, nil, false)
|
||||
// if err != nil { ... }
|
||||
// for ... {
|
||||
// qpos, err := oracle.ParseQueryPos(imp, pos, needExact)
|
||||
// if err != nil { ... }
|
||||
//
|
||||
// res, err := o.Query(mode, qpos)
|
||||
// if err != nil { ... }
|
||||
//
|
||||
// // use res
|
||||
// }
|
||||
//
|
||||
// TODO(adonovan): the ideal 'needsExact' parameter for ParseQueryPos
|
||||
// depends on the query mode; how should we expose this?
|
||||
//
|
||||
func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *build.Context, reflection bool) (*Result, error) {
|
||||
if mode == "what" {
|
||||
// Bypass package loading, type checking, SSA construction.
|
||||
return what(pos, buildContext)
|
||||
// WriteTo writes the oracle query result res to out in a compiler diagnostic format.
|
||||
func (q *Query) WriteTo(out io.Writer) {
|
||||
printf := func(pos interface{}, format string, args ...interface{}) {
|
||||
fprintf(out, q.Fset, pos, format, args...)
|
||||
}
|
||||
|
||||
minfo := findMode(mode)
|
||||
if minfo == nil {
|
||||
return nil, fmt.Errorf("invalid mode type: %q", mode)
|
||||
}
|
||||
|
||||
conf := loader.Config{Build: buildContext}
|
||||
|
||||
// TODO(adonovan): tolerate type errors if we don't need SSA form.
|
||||
// First we'll need ot audit the non-SSA modes for robustness
|
||||
// in the face of type errors.
|
||||
// if minfo.needs&needSSA == 0 {
|
||||
// conf.AllowErrors = true
|
||||
// }
|
||||
|
||||
// Determine initial packages.
|
||||
args, err := conf.FromArgs(args, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return nil, fmt.Errorf("surplus arguments: %q", args)
|
||||
}
|
||||
|
||||
// For queries needing only a single typed package,
|
||||
// reduce the analysis scope to that package.
|
||||
if minfo.needs&(needSSA|needRetainTypeInfo) == 0 {
|
||||
reduceScope(pos, &conf)
|
||||
}
|
||||
|
||||
// TODO(adonovan): report type errors to the user via Serial
|
||||
// types, not stderr?
|
||||
// conf.TypeChecker.Error = func(err error) {
|
||||
// E := err.(types.Error)
|
||||
// fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg)
|
||||
// }
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o, err := newOracle(iprog, ptalog, minfo.needs, reflection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
qpos, err := ParseQueryPos(iprog, pos, minfo.needs&needExactPos != 0)
|
||||
if err != nil && minfo.needs&(needPos|needExactPos) != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// SSA is built and we have the QueryPos.
|
||||
// Release the other ASTs and type info to the GC.
|
||||
iprog = nil
|
||||
|
||||
return o.query(minfo, qpos)
|
||||
q.result.display(printf)
|
||||
}
|
||||
|
||||
// reduceScope is called for one-shot queries that need only a single
|
||||
// typed package. It attempts to guess the query package from pos and
|
||||
// reduce the analysis scope (set of loaded packages) to just that one
|
||||
// plus (the exported parts of) its dependencies. It leaves its
|
||||
// arguments unchanged on failure.
|
||||
//
|
||||
// TODO(adonovan): this is a real mess... but it's fast.
|
||||
//
|
||||
func reduceScope(pos string, conf *loader.Config) {
|
||||
// Run runs an oracle query and populates its Fset and Result.
|
||||
func Run(conf *Query) error {
|
||||
switch conf.Mode {
|
||||
case "callees":
|
||||
return callees(conf)
|
||||
case "callers":
|
||||
return callers(conf)
|
||||
case "callstack":
|
||||
return callstack(conf)
|
||||
case "peers":
|
||||
return peers(conf)
|
||||
case "pointsto":
|
||||
return pointsto(conf)
|
||||
case "whicherrs":
|
||||
return whicherrs(conf)
|
||||
case "definition":
|
||||
return definition(conf)
|
||||
case "describe":
|
||||
return describe(conf)
|
||||
case "freevars":
|
||||
return freevars(conf)
|
||||
case "implements":
|
||||
return implements(conf)
|
||||
case "referrers":
|
||||
return referrers(conf)
|
||||
case "what":
|
||||
return what(conf)
|
||||
default:
|
||||
return fmt.Errorf("invalid mode: %q", conf.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a pointer.Config whose scope is the initial packages of lprog
|
||||
// and their dependencies.
|
||||
func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) {
|
||||
// TODO(adonovan): the body of this function is essentially
|
||||
// duplicated in all go/pointer clients. Refactor.
|
||||
|
||||
// For each initial package (specified on the command line),
|
||||
// if it has a main function, analyze that,
|
||||
// otherwise analyze its tests, if any.
|
||||
var testPkgs, mains []*ssa.Package
|
||||
for _, info := range lprog.InitialPackages() {
|
||||
initialPkg := prog.Package(info.Pkg)
|
||||
|
||||
// Add package to the pointer analysis scope.
|
||||
if initialPkg.Func("main") != nil {
|
||||
mains = append(mains, initialPkg)
|
||||
} else {
|
||||
testPkgs = append(testPkgs, initialPkg)
|
||||
}
|
||||
}
|
||||
if testPkgs != nil {
|
||||
if p := prog.CreateTestMainPackage(testPkgs...); p != nil {
|
||||
mains = append(mains, p)
|
||||
}
|
||||
}
|
||||
if mains == nil {
|
||||
return nil, fmt.Errorf("analysis scope has no main and no tests")
|
||||
}
|
||||
return &pointer.Config{
|
||||
Log: ptaLog,
|
||||
Reflection: reflection,
|
||||
Mains: mains,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// importQueryPackage finds the package P containing the
|
||||
// query position and tells conf to import it.
|
||||
func importQueryPackage(pos string, conf *loader.Config) error {
|
||||
fqpos, err := fastQueryPos(pos)
|
||||
if err != nil {
|
||||
return // bad query
|
||||
return err // bad query
|
||||
}
|
||||
filename := fqpos.fset.File(fqpos.start).Name()
|
||||
|
||||
// TODO(adonovan): fix: this gives the wrong results for files
|
||||
// in non-importable packages such as tests and ad-hoc packages
|
||||
// specified as a list of files (incl. the oracle's tests).
|
||||
_, importPath, err := guessImportPath(fqpos.fset.File(fqpos.start).Name(), conf.Build)
|
||||
// This will not work for ad-hoc packages
|
||||
// such as $GOROOT/src/net/http/triv.go.
|
||||
// TODO(adonovan): ensure we report a clear error.
|
||||
_, importPath, err := guessImportPath(filename, conf.Build)
|
||||
if err != nil {
|
||||
return // can't find GOPATH dir
|
||||
return err // can't find GOPATH dir
|
||||
}
|
||||
if importPath == "" {
|
||||
return
|
||||
return fmt.Errorf("can't guess import path from %s", filename)
|
||||
}
|
||||
|
||||
// Check that it's possible to load the queried package.
|
||||
|
|
@ -309,180 +198,77 @@ func reduceScope(pos string, conf *loader.Config) {
|
|||
cfg2.CgoEnabled = false
|
||||
bp, err := cfg2.Import(importPath, "", 0)
|
||||
if err != nil {
|
||||
return // no files for package
|
||||
return err // no files for package
|
||||
}
|
||||
|
||||
// Check that the queried file appears in the package:
|
||||
// it might be a '// +build ignore' from an ad-hoc main
|
||||
// package, e.g. $GOROOT/src/net/http/triv.go.
|
||||
if !pkgContainsFile(bp, fqpos.fset.File(fqpos.start).Name()) {
|
||||
return // not found
|
||||
switch pkgContainsFile(bp, filename) {
|
||||
case 'T':
|
||||
conf.ImportWithTests(importPath)
|
||||
case 'X':
|
||||
conf.ImportWithTests(importPath)
|
||||
importPath += "_test" // for TypeCheckFuncBodies
|
||||
case 'G':
|
||||
conf.Import(importPath)
|
||||
default:
|
||||
return fmt.Errorf("package %q doesn't contain file %s",
|
||||
importPath, filename)
|
||||
}
|
||||
|
||||
conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
|
||||
|
||||
// Ignore packages specified on command line.
|
||||
conf.CreatePkgs = nil
|
||||
conf.ImportPkgs = nil
|
||||
|
||||
// Instead load just the one containing the query position
|
||||
// (and possibly its corresponding tests/production code).
|
||||
// TODO(adonovan): set 'augment' based on which file list
|
||||
// contains
|
||||
conf.ImportWithTests(importPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func pkgContainsFile(bp *build.Package, filename string) bool {
|
||||
for _, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
|
||||
// pkgContainsFile reports whether file was among the packages Go
|
||||
// files, Test files, eXternal test files, or not found.
|
||||
func pkgContainsFile(bp *build.Package, filename string) byte {
|
||||
for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
|
||||
for _, file := range files {
|
||||
if sameFile(file, filename) {
|
||||
return true
|
||||
if sameFile(filepath.Join(bp.Dir, file), filename) {
|
||||
return "GTX"[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return 0 // not found
|
||||
}
|
||||
|
||||
// New constructs a new Oracle that can be used for a sequence of queries.
|
||||
//
|
||||
// iprog specifies the program to analyze.
|
||||
// ptalog is the (optional) pointer-analysis log file.
|
||||
// reflection determines whether to model reflection soundly (currently slow).
|
||||
//
|
||||
func New(iprog *loader.Program, ptalog io.Writer, reflection bool) (*Oracle, error) {
|
||||
return newOracle(iprog, ptalog, needAll, reflection)
|
||||
}
|
||||
|
||||
func newOracle(iprog *loader.Program, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
|
||||
o := &Oracle{fset: iprog.Fset}
|
||||
|
||||
// Retain type info for all ASTs in the program.
|
||||
if needs&needRetainTypeInfo != 0 {
|
||||
o.typeInfo = iprog.AllPackages
|
||||
}
|
||||
|
||||
// Create SSA package for the initial packages and their dependencies.
|
||||
if needs&needSSA != 0 {
|
||||
var mode ssa.BuilderMode
|
||||
if needs&needSSADebug != 0 {
|
||||
mode |= ssa.GlobalDebug
|
||||
}
|
||||
prog := ssa.Create(iprog, mode)
|
||||
|
||||
// For each initial package (specified on the command line),
|
||||
// if it has a main function, analyze that,
|
||||
// otherwise analyze its tests, if any.
|
||||
var testPkgs, mains []*ssa.Package
|
||||
for _, info := range iprog.InitialPackages() {
|
||||
initialPkg := prog.Package(info.Pkg)
|
||||
|
||||
// Add package to the pointer analysis scope.
|
||||
if initialPkg.Func("main") != nil {
|
||||
mains = append(mains, initialPkg)
|
||||
} else {
|
||||
testPkgs = append(testPkgs, initialPkg)
|
||||
}
|
||||
}
|
||||
if testPkgs != nil {
|
||||
if p := prog.CreateTestMainPackage(testPkgs...); p != nil {
|
||||
mains = append(mains, p)
|
||||
}
|
||||
}
|
||||
if mains == nil {
|
||||
return nil, fmt.Errorf("analysis scope has no main and no tests")
|
||||
}
|
||||
o.ptaConfig.Log = ptalog
|
||||
o.ptaConfig.Reflection = reflection
|
||||
o.ptaConfig.Mains = mains
|
||||
|
||||
o.prog = prog
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Query runs the query of the specified mode and selection.
|
||||
//
|
||||
// TODO(adonovan): fix: this function does not currently support the
|
||||
// "what" query, which needs to access the go/build.Context.
|
||||
//
|
||||
func (o *Oracle) Query(mode string, qpos *QueryPos) (*Result, error) {
|
||||
minfo := findMode(mode)
|
||||
if minfo == nil {
|
||||
return nil, fmt.Errorf("invalid mode type: %q", mode)
|
||||
}
|
||||
return o.query(minfo, qpos)
|
||||
}
|
||||
|
||||
func (o *Oracle) query(minfo *modeInfo, qpos *QueryPos) (*Result, error) {
|
||||
// Clear out residue of previous query (for long-running clients).
|
||||
o.ptaConfig.Queries = nil
|
||||
o.ptaConfig.IndirectQueries = nil
|
||||
|
||||
res := &Result{
|
||||
mode: minfo.name,
|
||||
fset: o.fset,
|
||||
}
|
||||
var err error
|
||||
res.q, err = minfo.impl(o, qpos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ParseQueryPos parses the source query position pos.
|
||||
// ParseQueryPos parses the source query position pos and returns the
|
||||
// AST node of the loaded program lprog that it identifies.
|
||||
// If needExact, it must identify a single AST subtree;
|
||||
// this is appropriate for queries that allow fairly arbitrary syntax,
|
||||
// e.g. "describe".
|
||||
//
|
||||
func ParseQueryPos(iprog *loader.Program, posFlag string, needExact bool) (*QueryPos, error) {
|
||||
func parseQueryPos(lprog *loader.Program, posFlag string, needExact bool) (*queryPos, error) {
|
||||
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset)
|
||||
start, end, err := findQueryPos(lprog.Fset, filename, startOffset, endOffset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, path, exact := iprog.PathEnclosingInterval(start, end)
|
||||
info, path, exact := lprog.PathEnclosingInterval(start, end)
|
||||
if path == nil {
|
||||
return nil, fmt.Errorf("no syntax here")
|
||||
}
|
||||
if needExact && !exact {
|
||||
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
|
||||
}
|
||||
return &QueryPos{iprog.Fset, start, end, path, exact, info}, 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{}) {
|
||||
fprintf(out, res.fset, pos, format, args...)
|
||||
}
|
||||
res.q.display(printf)
|
||||
|
||||
// Print warnings after the main output.
|
||||
if res.warnings != nil {
|
||||
fmt.Fprintln(out, "\nPointer analysis warnings:")
|
||||
for _, w := range res.warnings {
|
||||
printf(w.Pos, "warning: "+w.Message)
|
||||
}
|
||||
}
|
||||
return &queryPos{lprog.Fset, start, end, path, exact, info}, nil
|
||||
}
|
||||
|
||||
// ---------- Utilities ----------
|
||||
|
||||
// buildSSA constructs the SSA representation of Go-source function bodies.
|
||||
// Not needed in simpler modes, e.g. freevars.
|
||||
//
|
||||
func buildSSA(o *Oracle) {
|
||||
o.prog.BuildAll()
|
||||
// allowErrors causes type errors to be silently ignored.
|
||||
// (Not suitable if SSA construction follows.)
|
||||
func allowErrors(lconf *loader.Config) {
|
||||
lconf.AllowErrors = true
|
||||
lconf.TypeChecker.Error = func(err error) {}
|
||||
}
|
||||
|
||||
// ptrAnalysis runs the pointer analysis and returns its result.
|
||||
func ptrAnalysis(o *Oracle) *pointer.Result {
|
||||
result, err := pointer.Analyze(&o.ptaConfig)
|
||||
func ptrAnalysis(conf *pointer.Config) *pointer.Result {
|
||||
result, err := pointer.Analyze(conf)
|
||||
if err != nil {
|
||||
panic(err) // pointer analysis internal error
|
||||
}
|
||||
|
|
@ -528,7 +314,7 @@ func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, a
|
|||
}:
|
||||
start = pos.Pos()
|
||||
end = start
|
||||
case *QueryPos:
|
||||
case *queryPos:
|
||||
start = pos.start
|
||||
end = pos.end
|
||||
case nil:
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/oracle"
|
||||
)
|
||||
|
||||
|
|
@ -151,9 +150,9 @@ func parseQueries(t *testing.T, filename string) []*query {
|
|||
}
|
||||
|
||||
// WriteResult writes res (-format=plain) to w, stripping file locations.
|
||||
func WriteResult(w io.Writer, res *oracle.Result) {
|
||||
func WriteResult(w io.Writer, q *oracle.Query) {
|
||||
capture := new(bytes.Buffer) // capture standard output
|
||||
res.WriteTo(capture)
|
||||
q.WriteTo(capture)
|
||||
for _, line := range strings.Split(capture.String(), "\n") {
|
||||
// Remove a "file:line: " prefix.
|
||||
if i := strings.Index(line, ": "); i >= 0 {
|
||||
|
|
@ -170,28 +169,30 @@ func doQuery(out io.Writer, q *query, useJson bool) {
|
|||
|
||||
var buildContext = build.Default
|
||||
buildContext.GOPATH = "testdata"
|
||||
res, err := oracle.Query([]string{q.filename},
|
||||
q.verb,
|
||||
q.queryPos,
|
||||
nil, // ptalog,
|
||||
&buildContext,
|
||||
true) // reflection
|
||||
if err != nil {
|
||||
query := oracle.Query{
|
||||
Mode: q.verb,
|
||||
Pos: q.queryPos,
|
||||
Build: &buildContext,
|
||||
Scope: []string{q.filename},
|
||||
Reflection: true,
|
||||
}
|
||||
if err := oracle.Run(&query); err != nil {
|
||||
fmt.Fprintf(out, "\nError: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if useJson {
|
||||
// JSON output
|
||||
b, err := json.MarshalIndent(res.Serial(), "", "\t")
|
||||
b, err := json.MarshalIndent(query.Serial(), "", "\t")
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "JSON error: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
out.Write(b)
|
||||
fmt.Fprintln(out)
|
||||
} else {
|
||||
// "plain" (compiler diagnostic format) output
|
||||
WriteResult(out, res)
|
||||
WriteResult(out, &query)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,32 +203,29 @@ func TestOracle(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, filename := range []string{
|
||||
"testdata/src/main/calls.go",
|
||||
"testdata/src/main/callgraph.go",
|
||||
"testdata/src/main/callgraph2.go",
|
||||
"testdata/src/main/describe.go",
|
||||
"testdata/src/main/freevars.go",
|
||||
"testdata/src/main/implements.go",
|
||||
"testdata/src/main/implements-methods.go",
|
||||
"testdata/src/main/imports.go",
|
||||
"testdata/src/main/peers.go",
|
||||
"testdata/src/main/pointsto.go",
|
||||
"testdata/src/main/reflection.go",
|
||||
"testdata/src/main/what.go",
|
||||
"testdata/src/main/whicherrs.go",
|
||||
"testdata/src/calls/main.go",
|
||||
"testdata/src/describe/main.go",
|
||||
"testdata/src/freevars/main.go",
|
||||
"testdata/src/implements/main.go",
|
||||
"testdata/src/implements-methods/main.go",
|
||||
"testdata/src/imports/main.go",
|
||||
"testdata/src/peers/main.go",
|
||||
"testdata/src/pointsto/main.go",
|
||||
"testdata/src/reflection/main.go",
|
||||
"testdata/src/what/main.go",
|
||||
"testdata/src/whicherrs/main.go",
|
||||
// JSON:
|
||||
// TODO(adonovan): most of these are very similar; combine them.
|
||||
"testdata/src/main/callgraph-json.go",
|
||||
"testdata/src/main/calls-json.go",
|
||||
"testdata/src/main/peers-json.go",
|
||||
"testdata/src/main/describe-json.go",
|
||||
"testdata/src/main/implements-json.go",
|
||||
"testdata/src/main/implements-methods-json.go",
|
||||
"testdata/src/main/pointsto-json.go",
|
||||
"testdata/src/main/referrers-json.go",
|
||||
"testdata/src/main/what-json.go",
|
||||
"testdata/src/calls-json/main.go",
|
||||
"testdata/src/peers-json/main.go",
|
||||
"testdata/src/describe-json/main.go",
|
||||
"testdata/src/implements-json/main.go",
|
||||
"testdata/src/implements-methods-json/main.go",
|
||||
"testdata/src/pointsto-json/main.go",
|
||||
"testdata/src/referrers-json/main.go",
|
||||
"testdata/src/what-json/main.go",
|
||||
} {
|
||||
useJson := strings.HasSuffix(filename, "-json.go")
|
||||
useJson := strings.Contains(filename, "-json/")
|
||||
queries := parseQueries(t, filename)
|
||||
golden := filename + "lden"
|
||||
got := filename + "t"
|
||||
|
|
@ -269,54 +267,3 @@ func TestOracle(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleQueries(t *testing.T) {
|
||||
// Loader
|
||||
var buildContext = build.Default
|
||||
buildContext.GOPATH = "testdata"
|
||||
conf := loader.Config{Build: &buildContext}
|
||||
filename := "testdata/src/main/multi.go"
|
||||
conf.CreateFromFilenames("", filename)
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load failed: %s", err)
|
||||
}
|
||||
|
||||
// Oracle
|
||||
o, err := oracle.New(iprog, nil, true)
|
||||
if err != nil {
|
||||
t.Fatalf("oracle.New failed: %s", err)
|
||||
}
|
||||
|
||||
// QueryPos
|
||||
pos := filename + ":#54,#58"
|
||||
qpos, err := oracle.ParseQueryPos(iprog, pos, true)
|
||||
if err != nil {
|
||||
t.Fatalf("oracle.ParseQueryPos(%q) failed: %s", pos, err)
|
||||
}
|
||||
// SSA is built and we have the QueryPos.
|
||||
// Release the other ASTs and type info to the GC.
|
||||
iprog = nil
|
||||
|
||||
// Run different query modes on same scope and selection.
|
||||
out := new(bytes.Buffer)
|
||||
for _, mode := range [...]string{"callers", "describe", "freevars"} {
|
||||
res, err := o.Query(mode, qpos)
|
||||
if err != nil {
|
||||
t.Errorf("(*oracle.Oracle).Query(%q) failed: %s", pos, err)
|
||||
}
|
||||
WriteResult(out, res)
|
||||
}
|
||||
want := `multi.f is called from these 1 sites:
|
||||
static function call from multi.main
|
||||
|
||||
function call (or conversion) of type ()
|
||||
|
||||
Free identifiers:
|
||||
var x int
|
||||
|
||||
`
|
||||
if got := out.String(); got != want {
|
||||
t.Errorf("Query output differs; want <<%s>>, got <<%s>>\n", want, got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"go/token"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
|
|
@ -22,20 +23,51 @@ import (
|
|||
// TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
|
||||
// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
|
||||
// or the implicit receive in "for v := range ch".
|
||||
func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
opPos := findOp(qpos)
|
||||
if opPos == token.NoPos {
|
||||
return nil, fmt.Errorf("there is no channel operation here")
|
||||
func peers(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
||||
// Determine initial packages for PTA.
|
||||
args, err := lconf.FromArgs(q.Scope, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("surplus arguments: %q", args)
|
||||
}
|
||||
|
||||
buildSSA(o)
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssa.Create(lprog, ssa.GlobalDebug)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opPos := findOp(qpos)
|
||||
if opPos == token.NoPos {
|
||||
return fmt.Errorf("there is no channel operation here")
|
||||
}
|
||||
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.BuildAll()
|
||||
|
||||
var queryOp chanOp // the originating send or receive operation
|
||||
var ops []chanOp // all sends/receives of opposite direction
|
||||
|
||||
// Look at all channel operations in the whole ssa.Program.
|
||||
// Build a list of those of same type as the query.
|
||||
allFuncs := ssautil.AllFunctions(o.prog)
|
||||
allFuncs := ssautil.AllFunctions(prog)
|
||||
for fn := range allFuncs {
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
|
|
@ -49,7 +81,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
}
|
||||
}
|
||||
if queryOp.ch == nil {
|
||||
return nil, fmt.Errorf("ssa.Instruction for send/receive not found")
|
||||
return fmt.Errorf("ssa.Instruction for send/receive not found")
|
||||
}
|
||||
|
||||
// Discard operations of wrong channel element type.
|
||||
|
|
@ -58,11 +90,11 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
// ignore both directionality and type names.
|
||||
queryType := queryOp.ch.Type()
|
||||
queryElemType := queryType.Underlying().(*types.Chan).Elem()
|
||||
o.ptaConfig.AddQuery(queryOp.ch)
|
||||
ptaConfig.AddQuery(queryOp.ch)
|
||||
i := 0
|
||||
for _, op := range ops {
|
||||
if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
|
||||
o.ptaConfig.AddQuery(op.ch)
|
||||
ptaConfig.AddQuery(op.ch)
|
||||
ops[i] = op
|
||||
i++
|
||||
}
|
||||
|
|
@ -70,7 +102,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
ops = ops[:i]
|
||||
|
||||
// Run the pointer analysis.
|
||||
ptares := ptrAnalysis(o)
|
||||
ptares := ptrAnalysis(ptaConfig)
|
||||
|
||||
// Find the points-to set.
|
||||
queryChanPtr := ptares.Queries[queryOp.ch]
|
||||
|
|
@ -100,14 +132,15 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
sort.Sort(byPos(receives))
|
||||
sort.Sort(byPos(closes))
|
||||
|
||||
return &peersResult{
|
||||
q.result = &peersResult{
|
||||
queryPos: opPos,
|
||||
queryType: queryType,
|
||||
makes: makes,
|
||||
sends: sends,
|
||||
receives: receives,
|
||||
closes: closes,
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findOp returns the position of the enclosing send/receive/close op.
|
||||
|
|
@ -115,7 +148,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
// for close operations, it's the Lparen of the function call.
|
||||
//
|
||||
// TODO(adonovan): handle implicit receive operations from 'for...range chan' statements.
|
||||
func findOp(qpos *QueryPos) token.Pos {
|
||||
func findOp(qpos *queryPos) token.Pos {
|
||||
for _, n := range qpos.path {
|
||||
switch n := n.(type) {
|
||||
case *ast.UnaryExpr:
|
||||
|
|
|
|||
|
|
@ -25,10 +25,40 @@ import (
|
|||
//
|
||||
// All printed sets are sorted to ensure determinism.
|
||||
//
|
||||
func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
func pointsto(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
||||
// Determine initial packages for PTA.
|
||||
args, err := lconf.FromArgs(q.Scope, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("surplus arguments: %q", args)
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssa.Create(lprog, ssa.GlobalDebug)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
if action != actionExpr {
|
||||
return nil, fmt.Errorf("pointer analysis wants an expression; got %s",
|
||||
return fmt.Errorf("pointer analysis wants an expression; got %s",
|
||||
astutil.NodeDescription(qpos.path[0]))
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +67,7 @@ func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
switch n := path[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
// ambiguous ValueSpec containing multiple names
|
||||
return nil, fmt.Errorf("multiple value specification")
|
||||
return fmt.Errorf("multiple value specification")
|
||||
case *ast.Ident:
|
||||
obj = qpos.info.ObjectOf(n)
|
||||
expr = n
|
||||
|
|
@ -45,41 +75,44 @@ func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
expr = n
|
||||
default:
|
||||
// TODO(adonovan): is this reachable?
|
||||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
return fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
// Reject non-pointerlike types (includes all constants---except nil).
|
||||
// TODO(adonovan): reject nil too.
|
||||
typ := qpos.info.TypeOf(expr)
|
||||
if !pointer.CanPoint(typ) {
|
||||
return nil, fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ)
|
||||
return fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ)
|
||||
}
|
||||
|
||||
// Determine the ssa.Value for the expression.
|
||||
var value ssa.Value
|
||||
var isAddr bool
|
||||
var err error
|
||||
if obj != nil {
|
||||
// def/ref of func/var object
|
||||
value, isAddr, err = ssaValueForIdent(o.prog, qpos.info, obj, path)
|
||||
value, isAddr, err = ssaValueForIdent(prog, qpos.info, obj, path)
|
||||
} else {
|
||||
value, isAddr, err = ssaValueForExpr(o.prog, qpos.info, path)
|
||||
value, isAddr, err = ssaValueForExpr(prog, qpos.info, path)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err // e.g. trivially dead code
|
||||
return err // e.g. trivially dead code
|
||||
}
|
||||
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.BuildAll()
|
||||
|
||||
// Run the pointer analysis.
|
||||
ptrs, err := runPTA(o, value, isAddr)
|
||||
ptrs, err := runPTA(ptaConfig, value, isAddr)
|
||||
if err != nil {
|
||||
return nil, err // e.g. analytically unreachable
|
||||
return err // e.g. analytically unreachable
|
||||
}
|
||||
|
||||
return &pointstoResult{
|
||||
q.result = &pointstoResult{
|
||||
qpos: qpos,
|
||||
typ: typ,
|
||||
ptrs: ptrs,
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
|
||||
|
|
@ -129,17 +162,15 @@ func ssaValueForExpr(prog *ssa.Program, qinfo *loader.PackageInfo, path []ast.No
|
|||
}
|
||||
|
||||
// runPTA runs the pointer analysis of the selected SSA value or address.
|
||||
func runPTA(o *Oracle, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
|
||||
buildSSA(o)
|
||||
|
||||
func runPTA(conf *pointer.Config, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
|
||||
T := v.Type()
|
||||
if isAddr {
|
||||
o.ptaConfig.AddIndirectQuery(v)
|
||||
conf.AddIndirectQuery(v)
|
||||
T = deref(T)
|
||||
} else {
|
||||
o.ptaConfig.AddQuery(v)
|
||||
conf.AddQuery(v)
|
||||
}
|
||||
ptares := ptrAnalysis(o)
|
||||
ptares := ptrAnalysis(conf)
|
||||
|
||||
var ptr pointer.Pointer
|
||||
if isAddr {
|
||||
|
|
@ -177,7 +208,7 @@ type pointerResult struct {
|
|||
}
|
||||
|
||||
type pointstoResult struct {
|
||||
qpos *QueryPos
|
||||
qpos *queryPos
|
||||
typ types.Type // type of expression
|
||||
ptrs []pointerResult // pointer info (typ is concrete => len==1)
|
||||
}
|
||||
|
|
@ -188,17 +219,17 @@ func (r *pointstoResult) display(printf printfFunc) {
|
|||
// reflect.Value expression.
|
||||
|
||||
if len(r.ptrs) > 0 {
|
||||
printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.TypeString(r.typ))
|
||||
printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.typeString(r.typ))
|
||||
for _, ptr := range r.ptrs {
|
||||
var obj types.Object
|
||||
if nt, ok := deref(ptr.typ).(*types.Named); ok {
|
||||
obj = nt.Obj()
|
||||
}
|
||||
if len(ptr.labels) > 0 {
|
||||
printf(obj, "\t%s, may point to:", r.qpos.TypeString(ptr.typ))
|
||||
printf(obj, "\t%s, may point to:", r.qpos.typeString(ptr.typ))
|
||||
printLabels(printf, ptr.labels, "\t\t")
|
||||
} else {
|
||||
printf(obj, "\t%s", r.qpos.TypeString(ptr.typ))
|
||||
printf(obj, "\t%s", r.qpos.typeString(ptr.typ))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -208,11 +239,11 @@ func (r *pointstoResult) display(printf printfFunc) {
|
|||
// Show labels for other expressions.
|
||||
if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
|
||||
printf(r.qpos, "this %s may point to these objects:",
|
||||
r.qpos.TypeString(r.typ))
|
||||
r.qpos.typeString(r.typ))
|
||||
printLabels(printf, ptr.labels, "\t")
|
||||
} else {
|
||||
printf(r.qpos, "this %s may not point to anything.",
|
||||
r.qpos.TypeString(r.typ))
|
||||
r.qpos.typeString(r.typ))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -232,7 +263,7 @@ func (r *pointstoResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|||
})
|
||||
}
|
||||
pts = append(pts, serial.PointsTo{
|
||||
Type: r.qpos.TypeString(ptr.typ),
|
||||
Type: r.qpos.typeString(ptr.typ),
|
||||
NamePos: namePos,
|
||||
Labels: labels,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -117,13 +117,7 @@ func sameFile(x, y string) bool {
|
|||
|
||||
// fastQueryPos parses the -pos flag and returns a QueryPos.
|
||||
// It parses only a single file, and does not run the type checker.
|
||||
//
|
||||
// Caveat: the token.{FileSet,Pos} info it contains is not comparable
|
||||
// with that from the oracle's FileSet! (We don't accept oracle.fset
|
||||
// as a parameter because we don't want the same filename to appear
|
||||
// multiple times in one FileSet.)
|
||||
//
|
||||
func fastQueryPos(posFlag string) (*QueryPos, error) {
|
||||
func fastQueryPos(posFlag string) (*queryPos, error) {
|
||||
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -145,5 +139,5 @@ func fastQueryPos(posFlag string) (*QueryPos, error) {
|
|||
return nil, fmt.Errorf("no syntax here")
|
||||
}
|
||||
|
||||
return &QueryPos{fset, start, end, path, exact, nil}, nil
|
||||
return &queryPos{fset, start, end, path, exact, nil}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@ import (
|
|||
"go/ast"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
"golang.org/x/tools/refactor/importgraph"
|
||||
)
|
||||
|
||||
// TODO(adonovan): use golang.org/x/tools/refactor/importgraph to choose
|
||||
|
|
@ -21,36 +24,97 @@ import (
|
|||
|
||||
// Referrers reports all identifiers that resolve to the same object
|
||||
// as the queried identifier, within any package in the analysis scope.
|
||||
//
|
||||
func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
id, _ := qpos.path[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return nil, fmt.Errorf("no identifier here")
|
||||
func referrers(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj := qpos.info.ObjectOf(id)
|
||||
if obj == nil {
|
||||
// Happens for y in "switch y := x.(type)", but I think that's all.
|
||||
return nil, fmt.Errorf("no object for identifier")
|
||||
var id *ast.Ident
|
||||
var obj types.Object
|
||||
var lprog *loader.Program
|
||||
var pass2 bool
|
||||
for {
|
||||
// Load/parse/type-check the program.
|
||||
var err error
|
||||
lprog, err = lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ = qpos.path[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return fmt.Errorf("no identifier here")
|
||||
}
|
||||
|
||||
obj = qpos.info.ObjectOf(id)
|
||||
if obj == nil {
|
||||
// Happens for y in "switch y := x.(type)", but I think that's all.
|
||||
return fmt.Errorf("no object for identifier")
|
||||
}
|
||||
|
||||
// If the identifier is exported, we must load all packages that
|
||||
// depend transitively upon the package that defines it.
|
||||
//
|
||||
// TODO(adonovan): opt: skip this step if obj.Pkg() is a test or
|
||||
// main package.
|
||||
if pass2 || !obj.Exported() {
|
||||
break
|
||||
}
|
||||
|
||||
// Scan the workspace and build the import graph.
|
||||
_, rev, errors := importgraph.Build(q.Build)
|
||||
if len(errors) > 0 {
|
||||
for path, err := range errors {
|
||||
fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err)
|
||||
}
|
||||
return fmt.Errorf("failed to scan import graph for workspace")
|
||||
}
|
||||
|
||||
// Re-load the larger program.
|
||||
// Create a new file set so that ...
|
||||
// External test packages are never imported,
|
||||
// so they will never appear in the graph.
|
||||
// (We must reset the Config here, not just reset the Fset field.)
|
||||
lconf = loader.Config{
|
||||
Fset: token.NewFileSet(),
|
||||
Build: q.Build,
|
||||
}
|
||||
allowErrors(&lconf)
|
||||
for path := range rev.Search(obj.Pkg().Path()) {
|
||||
lconf.Import(path)
|
||||
}
|
||||
pass2 = true
|
||||
}
|
||||
|
||||
// Iterate over all go/types' Uses facts for the entire program.
|
||||
var refs []*ast.Ident
|
||||
for _, info := range o.typeInfo {
|
||||
for _, info := range lprog.AllPackages {
|
||||
for id2, obj2 := range info.Uses {
|
||||
if sameObj(obj, obj2) {
|
||||
refs = append(refs, id2)
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(adonovan): is this sort stable? Pos order depends on
|
||||
// when packages are reached. Use filename order?
|
||||
sort.Sort(byNamePos(refs))
|
||||
|
||||
return &referrersResult{
|
||||
qpos: qpos,
|
||||
q.result = &referrersResult{
|
||||
fset: q.Fset,
|
||||
query: id,
|
||||
obj: obj,
|
||||
refs: refs,
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// same reports whether x and y are identical, or both are PkgNames
|
||||
|
|
@ -77,7 +141,7 @@ func (p byNamePos) Less(i, j int) bool { return p[i].NamePos < p[j].NamePos }
|
|||
func (p byNamePos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
type referrersResult struct {
|
||||
qpos *QueryPos
|
||||
fset *token.FileSet
|
||||
query *ast.Ident // identifier of query
|
||||
obj types.Object // object it denotes
|
||||
refs []*ast.Ident // set of all other references to it
|
||||
|
|
@ -97,7 +161,7 @@ func (r *referrersResult) display(printf printfFunc) {
|
|||
|
||||
// First pass: start the file reads concurrently.
|
||||
for _, ref := range r.refs {
|
||||
posn := r.qpos.fset.Position(ref.Pos())
|
||||
posn := r.fset.Position(ref.Pos())
|
||||
fi := fileinfosByName[posn.Filename]
|
||||
if fi == nil {
|
||||
fi = &fileinfo{data: make(chan []byte)}
|
||||
|
|
|
|||
|
|
@ -64,20 +64,6 @@ type Caller struct {
|
|||
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.
|
||||
|
|
@ -232,11 +218,6 @@ type Describe struct {
|
|||
Value *DescribeValue `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type PTAWarning struct {
|
||||
Pos string `json:"pos"` // location associated with warning
|
||||
Message string `json:"message"` // warning message
|
||||
}
|
||||
|
||||
// A WhichErrs is the result of a 'whicherrs' query.
|
||||
// It contains the position of the queried error and the possible globals,
|
||||
// constants, and types it may point to.
|
||||
|
|
@ -264,7 +245,6 @@ type Result struct {
|
|||
// 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"`
|
||||
Definition *Definition `json:"definition,omitempty"`
|
||||
Describe *Describe `json:"describe,omitempty"`
|
||||
|
|
@ -275,6 +255,4 @@ type Result struct {
|
|||
Referrers *Referrers `json:"referrers,omitempty"`
|
||||
What *What `json:"what,omitempty"`
|
||||
WhichErrs *WhichErrs `json:"whicherrs,omitempty"`
|
||||
|
||||
Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,32 +2,33 @@
|
|||
{
|
||||
"mode": "callees",
|
||||
"callees": {
|
||||
"pos": "testdata/src/main/calls-json.go:8:3",
|
||||
"pos": "testdata/src/calls-json/main.go:8:3",
|
||||
"desc": "dynamic function call",
|
||||
"callees": [
|
||||
{
|
||||
"name": "main.main$1",
|
||||
"pos": "testdata/src/main/calls-json.go:12:7"
|
||||
"pos": "testdata/src/calls-json/main.go:12:7"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @callstack callstack-main.anon --------
|
||||
}
|
||||
-------- @callstack callstack-main.anon --------
|
||||
{
|
||||
"mode": "callstack",
|
||||
"callstack": {
|
||||
"pos": "testdata/src/main/calls-json.go:12:7",
|
||||
"pos": "testdata/src/calls-json/main.go:12:7",
|
||||
"target": "main.main$1",
|
||||
"callers": [
|
||||
{
|
||||
"pos": "testdata/src/main/calls-json.go:8:3",
|
||||
"pos": "testdata/src/calls-json/main.go:8:3",
|
||||
"desc": "dynamic function call",
|
||||
"caller": "main.call"
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/main/calls-json.go:12:6",
|
||||
"pos": "testdata/src/calls-json/main.go:12:6",
|
||||
"desc": "static function call",
|
||||
"caller": "main.main"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ package describe // @describe pkgdecl "describe"
|
|||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See describe-json.golden for expected query results.
|
||||
|
||||
func main() { //
|
||||
func main() {
|
||||
var s struct{ x [3]int }
|
||||
p := &s.x[0] // @describe desc-val-p "p"
|
||||
_ = p
|
||||
|
|
@ -2,106 +2,110 @@
|
|||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "definition of package \"describe\"",
|
||||
"pos": "testdata/src/main/describe-json.go:1:9",
|
||||
"desc": "definition of package \"describe-json\"",
|
||||
"pos": "testdata/src/describe-json/main.go:1:9",
|
||||
"detail": "package",
|
||||
"package": {
|
||||
"path": "describe",
|
||||
"path": "describe-json",
|
||||
"members": [
|
||||
{
|
||||
"name": "C",
|
||||
"type": "int",
|
||||
"pos": "testdata/src/main/describe-json.go:25:6",
|
||||
"pos": "testdata/src/describe-json/main.go:25:6",
|
||||
"kind": "type",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (C) f()",
|
||||
"pos": "testdata/src/main/describe-json.go:28:12"
|
||||
"pos": "testdata/src/describe-json/main.go:28:12"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "D",
|
||||
"type": "struct{}",
|
||||
"pos": "testdata/src/main/describe-json.go:26:6",
|
||||
"pos": "testdata/src/describe-json/main.go:26:6",
|
||||
"kind": "type",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (*D) f()",
|
||||
"pos": "testdata/src/main/describe-json.go:29:13"
|
||||
"pos": "testdata/src/describe-json/main.go:29:13"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "I",
|
||||
"type": "interface{f()}",
|
||||
"pos": "testdata/src/main/describe-json.go:21:6",
|
||||
"pos": "testdata/src/describe-json/main.go:21:6",
|
||||
"kind": "type",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (I) f()",
|
||||
"pos": "testdata/src/main/describe-json.go:22:2"
|
||||
"pos": "testdata/src/describe-json/main.go:22:2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main",
|
||||
"type": "func()",
|
||||
"pos": "testdata/src/main/describe-json.go:7:6",
|
||||
"pos": "testdata/src/describe-json/main.go:7:6",
|
||||
"kind": "func"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}-------- @describe desc-val-p --------
|
||||
}
|
||||
-------- @describe desc-val-p --------
|
||||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "identifier",
|
||||
"pos": "testdata/src/main/describe-json.go:9:2",
|
||||
"pos": "testdata/src/describe-json/main.go:9:2",
|
||||
"detail": "value",
|
||||
"value": {
|
||||
"type": "*int",
|
||||
"objpos": "testdata/src/main/describe-json.go:9:2"
|
||||
"objpos": "testdata/src/describe-json/main.go:9:2"
|
||||
}
|
||||
}
|
||||
}-------- @describe desc-val-i --------
|
||||
}
|
||||
-------- @describe desc-val-i --------
|
||||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "identifier",
|
||||
"pos": "testdata/src/main/describe-json.go:16:8",
|
||||
"pos": "testdata/src/describe-json/main.go:16:8",
|
||||
"detail": "value",
|
||||
"value": {
|
||||
"type": "I",
|
||||
"objpos": "testdata/src/main/describe-json.go:12:6"
|
||||
"objpos": "testdata/src/describe-json/main.go:12:6"
|
||||
}
|
||||
}
|
||||
}-------- @describe desc-stmt --------
|
||||
}
|
||||
-------- @describe desc-stmt --------
|
||||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "go statement",
|
||||
"pos": "testdata/src/main/describe-json.go:18:2",
|
||||
"pos": "testdata/src/describe-json/main.go:18:2",
|
||||
"detail": "unknown"
|
||||
}
|
||||
}-------- @describe desc-type-C --------
|
||||
}
|
||||
-------- @describe desc-type-C --------
|
||||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "definition of type C (size 8, align 8)",
|
||||
"pos": "testdata/src/main/describe-json.go:25:6",
|
||||
"pos": "testdata/src/describe-json/main.go:25:6",
|
||||
"detail": "type",
|
||||
"type": {
|
||||
"type": "C",
|
||||
"namepos": "testdata/src/main/describe-json.go:25:6",
|
||||
"namepos": "testdata/src/describe-json/main.go:25:6",
|
||||
"namedef": "int",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (C) f()",
|
||||
"pos": "testdata/src/main/describe-json.go:28:12"
|
||||
"pos": "testdata/src/describe-json/main.go:28:12"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
-------- @implements E --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-json.E",
|
||||
"pos": "testdata/src/implements-json/main.go:10:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
}
|
||||
}
|
||||
-------- @implements F --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-json.C",
|
||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
{
|
||||
"name": "implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
{
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements FG --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements slice --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "[]int",
|
||||
"pos": "-",
|
||||
"kind": "slice"
|
||||
}
|
||||
}
|
||||
}
|
||||
-------- @implements C --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-json.C",
|
||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
||||
"kind": "basic"
|
||||
},
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements starC --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*implements-json.C",
|
||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements D --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements starD --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
{
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
-------- @implements F.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-methods-json.C",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
{
|
||||
"name": "implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
{
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (F).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*C) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:24:13"
|
||||
},
|
||||
{
|
||||
"name": "method (D) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
||||
},
|
||||
{
|
||||
"name": "method (FG) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements FG.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (FG).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*D) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
||||
}
|
||||
],
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements FG.g --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (FG).g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:18:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*D) g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:27:13"
|
||||
}
|
||||
],
|
||||
"from_method": [
|
||||
{
|
||||
"name": "",
|
||||
"pos": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements *C.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*implements-methods-json.C",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (*C).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:24:13"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements D.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (D).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
}
|
||||
],
|
||||
"fromptr_method": [
|
||||
{
|
||||
"name": "method (FG) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements *D.g --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
{
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (*D).g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:27:13"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "",
|
||||
"pos": ""
|
||||
},
|
||||
{
|
||||
"name": "method (FG) g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:18:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements Len --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.sorter",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:29:6",
|
||||
"kind": "slice"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "lib.Sorter",
|
||||
"pos": "testdata/src/lib/lib.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (sorter).Len() int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:31:15"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (lib.Sorter) Len() int",
|
||||
"pos": "testdata/src/lib/lib.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements I.Method --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.I",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:35:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "lib.Type",
|
||||
"pos": "testdata/src/lib/lib.go:3:6",
|
||||
"kind": "basic"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (I).Method(*int) *int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:36:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (lib.Type) Method(x *int) *int",
|
||||
"pos": "testdata/src/lib/lib.go:5:13"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
-------- @implements E --------
|
||||
empty interface type implements.E
|
||||
|
||||
-------- @implements F --------
|
||||
interface type implements.F
|
||||
is implemented by pointer type *implements.C
|
||||
is implemented by struct type implements.D
|
||||
is implemented by interface type implements.FG
|
||||
|
||||
-------- @implements FG --------
|
||||
interface type implements.FG
|
||||
is implemented by pointer type *implements.D
|
||||
implements implements.F
|
||||
|
||||
-------- @implements slice --------
|
||||
slice type []int implements only interface{}
|
||||
|
||||
-------- @implements C --------
|
||||
pointer type *implements.C
|
||||
implements implements.F
|
||||
|
||||
-------- @implements starC --------
|
||||
pointer type *implements.C
|
||||
implements implements.F
|
||||
|
||||
-------- @implements D --------
|
||||
struct type implements.D
|
||||
implements implements.F
|
||||
pointer type *implements.D
|
||||
implements implements.FG
|
||||
|
||||
-------- @implements starD --------
|
||||
pointer type *implements.D
|
||||
implements implements.F
|
||||
implements implements.FG
|
||||
|
||||
-------- @implements sorter --------
|
||||
slice type implements.sorter
|
||||
implements lib.Sorter
|
||||
|
||||
-------- @implements I --------
|
||||
interface type implements.I
|
||||
is implemented by basic type lib.Type
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package imports
|
||||
package main
|
||||
|
||||
import (
|
||||
"hash/fnv" // @describe ref-pkg-import2 "fnv"
|
||||
|
|
@ -41,7 +41,7 @@ defined here
|
|||
|
||||
-------- @pointsto p --------
|
||||
this *int may point to these objects:
|
||||
imports.a
|
||||
main.a
|
||||
|
||||
-------- @describe ref-pkg --------
|
||||
reference to package "lib"
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
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 "^"
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
-------- @callgraph callgraph --------
|
||||
{
|
||||
"mode": "callgraph",
|
||||
"callgraph": [
|
||||
{
|
||||
"name": "main.main",
|
||||
"pos": "testdata/src/main/callgraph-json.go:24:6",
|
||||
"children": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main.call",
|
||||
"pos": "testdata/src/main/callgraph-json.go:12:6",
|
||||
"children": [
|
||||
5,
|
||||
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": [
|
||||
7
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main.init",
|
||||
"pos": "-"
|
||||
},
|
||||
{
|
||||
"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.main$1",
|
||||
"pos": "testdata/src/main/callgraph-json.go:31:8"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of call-graph queries.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See callgraph.golden for expected query results.
|
||||
|
||||
import "lib"
|
||||
|
||||
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()
|
||||
|
||||
lib.Func()
|
||||
}
|
||||
|
||||
func deadcode() {
|
||||
main()
|
||||
}
|
||||
|
||||
// @callgraph callgraph-main "^"
|
||||
|
||||
// @callgraph callgraph-complete "nopos"
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
-------- @callgraph callgraph-main --------
|
||||
|
||||
Below is a call graph of package main.
|
||||
The numbered nodes form a spanning tree.
|
||||
Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||
number follows in parentheses.
|
||||
|
||||
0 init
|
||||
1 main
|
||||
2 call
|
||||
3 A
|
||||
4 B
|
||||
5 call2
|
||||
6 main$1
|
||||
main (1)
|
||||
7 nop
|
||||
|
||||
-------- @callgraph callgraph-complete --------
|
||||
|
||||
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.
|
||||
|
||||
0 <root>
|
||||
1 main.init
|
||||
2 lib.init
|
||||
3 main.main
|
||||
4 lib.Func
|
||||
5 main.call
|
||||
6 main.A
|
||||
7 main.B
|
||||
8 main.call2
|
||||
9 main.main$1
|
||||
main.main (3)
|
||||
10 main.nop
|
||||
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of call-graph queries.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See callgraph2.golden for expected query results.
|
||||
|
||||
// (Regression test for pointer analysis: programs that use reflection
|
||||
// create some cgnodes before the root of the callgraph.)
|
||||
import _ "reflect"
|
||||
|
||||
func f() {}
|
||||
func main() {
|
||||
f()
|
||||
}
|
||||
|
||||
// @callgraph callgraph "^"
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
-------- @callgraph callgraph --------
|
||||
|
||||
Below is a call graph of package main.
|
||||
The numbered nodes form a spanning tree.
|
||||
Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||
number follows in parentheses.
|
||||
|
||||
0 init
|
||||
1 main
|
||||
2 f
|
||||
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
-------- @implements E --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.E",
|
||||
"pos": "testdata/src/main/implements-json.go:10:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
}
|
||||
}-------- @implements F --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-json.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*main.C",
|
||||
"pos": "testdata/src/main/implements-json.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
{
|
||||
"name": "main.D",
|
||||
"pos": "testdata/src/main/implements-json.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
{
|
||||
"name": "main.FG",
|
||||
"pos": "testdata/src/main/implements-json.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements FG --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.FG",
|
||||
"pos": "testdata/src/main/implements-json.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*main.D",
|
||||
"pos": "testdata/src/main/implements-json.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-json.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements slice --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "[]int",
|
||||
"pos": "-",
|
||||
"kind": "slice"
|
||||
}
|
||||
}
|
||||
}-------- @implements C --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.C",
|
||||
"pos": "testdata/src/main/implements-json.go:21:6",
|
||||
"kind": "basic"
|
||||
},
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-json.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements starC --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*main.C",
|
||||
"pos": "testdata/src/main/implements-json.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-json.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements D --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.D",
|
||||
"pos": "testdata/src/main/implements-json.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-json.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "main.FG",
|
||||
"pos": "testdata/src/main/implements-json.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements starD --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*main.D",
|
||||
"pos": "testdata/src/main/implements-json.go:22:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-json.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
{
|
||||
"name": "main.FG",
|
||||
"pos": "testdata/src/main/implements-json.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,283 +0,0 @@
|
|||
-------- @implements F.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*main.C",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
{
|
||||
"name": "main.D",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
{
|
||||
"name": "main.FG",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (F).f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:13:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*C) f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:24:13"
|
||||
},
|
||||
{
|
||||
"name": "method (D) f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:25:12"
|
||||
},
|
||||
{
|
||||
"name": "method (FG) f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements FG.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.FG",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*main.D",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (FG).f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:17:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*D) f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:25:12"
|
||||
}
|
||||
],
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:13:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements FG.g --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.FG",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*main.D",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (FG).g() []int",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:18:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*D) g() []int",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:27:13"
|
||||
}
|
||||
],
|
||||
"from_method": [
|
||||
{
|
||||
"name": "",
|
||||
"pos": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements *C.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*main.C",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (*C).f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:24:13"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:13:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements D.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.D",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "main.FG",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (D).f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:25:12"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:13:2"
|
||||
}
|
||||
],
|
||||
"fromptr_method": [
|
||||
{
|
||||
"name": "method (FG) f()",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements *D.g --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*main.D",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:22:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "main.F",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
{
|
||||
"name": "main.FG",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (*D).g() []int",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:27:13"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "",
|
||||
"pos": ""
|
||||
},
|
||||
{
|
||||
"name": "method (FG) g() []int",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:18:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements Len --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.sorter",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:29:6",
|
||||
"kind": "slice"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "lib.Sorter",
|
||||
"pos": "testdata/src/lib/lib.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (sorter).Len() int",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:31:15"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (lib.Sorter) Len() int",
|
||||
"pos": "testdata/src/lib/lib.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}-------- @implements I.Method --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "main.I",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:35:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "lib.Type",
|
||||
"pos": "testdata/src/lib/lib.go:3:6",
|
||||
"kind": "basic"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (I).Method(*int) *int",
|
||||
"pos": "testdata/src/main/implements-methods-json.go:36:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (lib.Type) Method(x *int) *int",
|
||||
"pos": "testdata/src/lib/lib.go:5:13"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
-------- @implements E --------
|
||||
empty interface type main.E
|
||||
|
||||
-------- @implements F --------
|
||||
interface type main.F
|
||||
is implemented by pointer type *main.C
|
||||
is implemented by struct type main.D
|
||||
is implemented by interface type main.FG
|
||||
|
||||
-------- @implements FG --------
|
||||
interface type main.FG
|
||||
is implemented by pointer type *main.D
|
||||
implements main.F
|
||||
|
||||
-------- @implements slice --------
|
||||
slice type []int implements only interface{}
|
||||
|
||||
-------- @implements C --------
|
||||
pointer type *main.C
|
||||
implements main.F
|
||||
|
||||
-------- @implements starC --------
|
||||
pointer type *main.C
|
||||
implements main.F
|
||||
|
||||
-------- @implements D --------
|
||||
struct type main.D
|
||||
implements main.F
|
||||
pointer type *main.D
|
||||
implements main.FG
|
||||
|
||||
-------- @implements starD --------
|
||||
pointer type *main.D
|
||||
implements main.F
|
||||
implements main.FG
|
||||
|
||||
-------- @implements sorter --------
|
||||
slice type main.sorter
|
||||
implements lib.Sorter
|
||||
|
||||
-------- @implements I --------
|
||||
interface type main.I
|
||||
is implemented by basic type lib.Type
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package multi
|
||||
package main
|
||||
|
||||
func g(x int) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
-------- @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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
-------- @referrers ref-package --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/main/referrers-json.go:14:8",
|
||||
"objpos": "testdata/src/main/referrers-json.go:7:8",
|
||||
"desc": "package lib",
|
||||
"refs": [
|
||||
"testdata/src/main/referrers-json.go:14:8",
|
||||
"testdata/src/main/referrers-json.go:14:19"
|
||||
]
|
||||
}
|
||||
}-------- @referrers ref-method --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/main/referrers-json.go:15:8",
|
||||
"objpos": "testdata/src/lib/lib.go:5:13",
|
||||
"desc": "func (lib.Type).Method(x *int) *int",
|
||||
"refs": [
|
||||
"testdata/src/main/referrers-json.go:15:8",
|
||||
"testdata/src/main/referrers-json.go:16:8"
|
||||
]
|
||||
}
|
||||
}-------- @referrers ref-local --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/main/referrers-json.go:17:2",
|
||||
"objpos": "testdata/src/main/referrers-json.go:14:6",
|
||||
"desc": "var v lib.Type",
|
||||
"refs": [
|
||||
"testdata/src/main/referrers-json.go:15:6",
|
||||
"testdata/src/main/referrers-json.go:16:6",
|
||||
"testdata/src/main/referrers-json.go:17:2",
|
||||
"testdata/src/main/referrers-json.go:18:2"
|
||||
]
|
||||
}
|
||||
}-------- @referrers ref-field --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/main/referrers-json.go:20:10",
|
||||
"objpos": "testdata/src/main/referrers-json.go:10:2",
|
||||
"desc": "field f int",
|
||||
"refs": [
|
||||
"testdata/src/main/referrers-json.go:20:10",
|
||||
"testdata/src/main/referrers-json.go:23:5"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package peers
|
||||
package main
|
||||
|
||||
// Tests of channel 'peers' query, -format=json.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
-------- @peers peer-recv-chA --------
|
||||
{
|
||||
"mode": "peers",
|
||||
"peers": {
|
||||
"pos": "testdata/src/peers-json/main.go:11:7",
|
||||
"type": "chan *int",
|
||||
"allocs": [
|
||||
"testdata/src/peers-json/main.go:8:13"
|
||||
],
|
||||
"receives": [
|
||||
"testdata/src/peers-json/main.go:9:2",
|
||||
"testdata/src/peers-json/main.go:11:7"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package peers
|
||||
package main
|
||||
|
||||
// Tests of channel 'peers' query.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
|
|
@ -26,7 +26,7 @@ This channel of type chan *int may be:
|
|||
|
||||
-------- @pointsto pointsto-rA --------
|
||||
this *int may point to these objects:
|
||||
peers.a2
|
||||
main.a2
|
||||
a1
|
||||
|
||||
-------- @peers peer-recv-chB --------
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package pointsto
|
||||
package main
|
||||
|
||||
// Tests of 'pointsto' queries, -format=json.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
|
|
@ -6,29 +6,30 @@
|
|||
"type": "*int",
|
||||
"labels": [
|
||||
{
|
||||
"pos": "testdata/src/main/pointsto-json.go:8:6",
|
||||
"pos": "testdata/src/pointsto-json/main.go:8:6",
|
||||
"desc": "s.x[*]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}-------- @pointsto val-i --------
|
||||
}
|
||||
-------- @pointsto val-i --------
|
||||
{
|
||||
"mode": "pointsto",
|
||||
"pointsto": [
|
||||
{
|
||||
"type": "*D",
|
||||
"namepos": "testdata/src/main/pointsto-json.go:24:6",
|
||||
"namepos": "testdata/src/pointsto-json/main.go:24:6",
|
||||
"labels": [
|
||||
{
|
||||
"pos": "testdata/src/main/pointsto-json.go:14:10",
|
||||
"pos": "testdata/src/pointsto-json/main.go:14:10",
|
||||
"desc": "new"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "C",
|
||||
"namepos": "testdata/src/main/pointsto-json.go:23:6"
|
||||
"namepos": "testdata/src/pointsto-json/main.go:23:6"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package pointsto
|
||||
package main
|
||||
|
||||
// Tests of 'pointsto' query.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
|
|
@ -3,33 +3,33 @@
|
|||
Error: pointer analysis wants an expression of reference type; got untyped float
|
||||
-------- @pointsto func-ref-main --------
|
||||
this func() may point to these objects:
|
||||
pointsto.main
|
||||
main.main
|
||||
|
||||
-------- @pointsto func-ref-*C.f --------
|
||||
this func() may point to these objects:
|
||||
(*pointsto.C).f
|
||||
(*main.C).f
|
||||
|
||||
-------- @pointsto func-ref-D.f --------
|
||||
this func() may point to these objects:
|
||||
(pointsto.D).f
|
||||
(main.D).f
|
||||
|
||||
-------- @pointsto func-ref-I.f --------
|
||||
|
||||
Error: func (pointsto.I).f() is an interface method
|
||||
Error: func (main.I).f() is an interface method
|
||||
-------- @pointsto func-ref-d.f --------
|
||||
this func() may point to these objects:
|
||||
(pointsto.D).f
|
||||
(main.D).f
|
||||
|
||||
-------- @pointsto func-ref-i.f --------
|
||||
|
||||
Error: func (pointsto.I).f() is an interface method
|
||||
Error: func (main.I).f() is an interface method
|
||||
-------- @pointsto ref-lexical-d.f --------
|
||||
this func() may point to these objects:
|
||||
(pointsto.D).f
|
||||
(main.D).f
|
||||
|
||||
-------- @pointsto ref-anon --------
|
||||
this func() may point to these objects:
|
||||
pointsto.main$1
|
||||
main.main$1
|
||||
|
||||
-------- @pointsto ref-global --------
|
||||
this *string may point to these objects:
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package referrers
|
||||
package main
|
||||
|
||||
// Tests of 'referrers' query.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
-------- @referrers ref-package --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/referrers-json/main.go:14:8",
|
||||
"objpos": "testdata/src/referrers-json/main.go:7:8",
|
||||
"desc": "package lib",
|
||||
"refs": [
|
||||
"testdata/src/referrers-json/main.go:14:8",
|
||||
"testdata/src/referrers-json/main.go:14:19"
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @referrers ref-method --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/referrers-json/main.go:15:8",
|
||||
"objpos": "testdata/src/lib/lib.go:5:13",
|
||||
"desc": "func (lib.Type).Method(x *int) *int",
|
||||
"refs": [
|
||||
"testdata/src/referrers-json/main.go:15:8",
|
||||
"testdata/src/referrers-json/main.go:16:8",
|
||||
"testdata/src/imports/main.go:22:9"
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @referrers ref-local --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/referrers-json/main.go:17:2",
|
||||
"objpos": "testdata/src/referrers-json/main.go:14:6",
|
||||
"desc": "var v lib.Type",
|
||||
"refs": [
|
||||
"testdata/src/referrers-json/main.go:15:6",
|
||||
"testdata/src/referrers-json/main.go:16:6",
|
||||
"testdata/src/referrers-json/main.go:17:2",
|
||||
"testdata/src/referrers-json/main.go:18:2"
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @referrers ref-field --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/referrers-json/main.go:20:10",
|
||||
"objpos": "testdata/src/referrers-json/main.go:10:2",
|
||||
"desc": "field f int",
|
||||
"refs": [
|
||||
"testdata/src/referrers-json/main.go:20:10",
|
||||
"testdata/src/referrers-json/main.go:23:5"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package reflection
|
||||
package main
|
||||
|
||||
// This is a test of 'pointsto', but we split it into a separate file
|
||||
// so that pointsto.go doesn't have to import "reflect" each time.
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
-------- @pointsto mrv --------
|
||||
this reflect.Value may contain these dynamic types:
|
||||
*bool, may point to:
|
||||
reflection.b
|
||||
main.b
|
||||
*int, may point to:
|
||||
reflection.a
|
||||
main.a
|
||||
map[*int]*bool, may point to:
|
||||
makemap
|
||||
|
||||
-------- @pointsto p1 --------
|
||||
this interface{} may contain these dynamic types:
|
||||
*bool, may point to:
|
||||
reflection.b
|
||||
main.b
|
||||
*int, may point to:
|
||||
reflection.a
|
||||
main.a
|
||||
map[*int]*bool, may point to:
|
||||
makemap
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ this []reflect.Value may point to these objects:
|
|||
-------- @pointsto p3 --------
|
||||
this reflect.Value may contain these dynamic types:
|
||||
*int, may point to:
|
||||
reflection.a
|
||||
main.a
|
||||
|
||||
-------- @pointsto p4 --------
|
||||
this reflect.Type may contain these dynamic types:
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package what
|
||||
package main
|
||||
|
||||
// Tests of 'what' queries, -format=json.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
|
|
@ -37,7 +37,6 @@
|
|||
"modes": [
|
||||
"callees",
|
||||
"callers",
|
||||
"callgraph",
|
||||
"callstack",
|
||||
"definition",
|
||||
"describe",
|
||||
|
|
@ -47,6 +46,6 @@
|
|||
"referrers"
|
||||
],
|
||||
"srcdir": "testdata/src",
|
||||
"importpath": "main"
|
||||
"importpath": "what-json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package what // @what pkgdecl "what"
|
||||
package main // @what pkgdecl "main"
|
||||
|
||||
// Tests of 'what' queries.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
-------- @what pkgdecl --------
|
||||
identifier
|
||||
source file
|
||||
modes: [callgraph definition describe freevars implements pointsto referrers]
|
||||
modes: [definition describe freevars implements pointsto referrers]
|
||||
srcdir: testdata/src
|
||||
import path: main
|
||||
import path: what
|
||||
|
||||
-------- @what call --------
|
||||
identifier
|
||||
|
|
@ -12,9 +12,9 @@ expression statement
|
|||
block
|
||||
function declaration
|
||||
source file
|
||||
modes: [callees callers callgraph callstack definition describe freevars implements pointsto referrers]
|
||||
modes: [callees callers callstack definition describe freevars implements pointsto referrers]
|
||||
srcdir: testdata/src
|
||||
import path: main
|
||||
import path: what
|
||||
|
||||
-------- @what var --------
|
||||
variable declaration
|
||||
|
|
@ -22,9 +22,9 @@ variable declaration statement
|
|||
block
|
||||
function declaration
|
||||
source file
|
||||
modes: [callers callgraph callstack describe freevars pointsto]
|
||||
modes: [callers callstack describe freevars pointsto]
|
||||
srcdir: testdata/src
|
||||
import path: main
|
||||
import path: what
|
||||
|
||||
-------- @what recv --------
|
||||
identifier
|
||||
|
|
@ -33,7 +33,7 @@ expression statement
|
|||
block
|
||||
function declaration
|
||||
source file
|
||||
modes: [callers callgraph callstack definition describe freevars implements peers pointsto referrers]
|
||||
modes: [callers callstack definition describe freevars implements peers pointsto referrers]
|
||||
srcdir: testdata/src
|
||||
import path: main
|
||||
import path: what
|
||||
|
||||
|
|
@ -24,21 +24,19 @@ import (
|
|||
// tools, e.g. to populate a menu of options of slower queries about
|
||||
// the selected location.
|
||||
//
|
||||
func what(posFlag string, buildContext *build.Context) (*Result, error) {
|
||||
qpos, err := fastQueryPos(posFlag)
|
||||
func what(q *Query) error {
|
||||
qpos, err := fastQueryPos(q.Pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
q.Fset = qpos.fset
|
||||
|
||||
// (ignore errors)
|
||||
srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), buildContext)
|
||||
srcdir, importPath, _ := guessImportPath(q.Fset.File(qpos.start).Name(), q.Build)
|
||||
|
||||
// Determine which query modes are applicable to the selection.
|
||||
// TODO(adonovan): refactor: make each minfo have an 'enable'
|
||||
// predicate over qpos.
|
||||
enable := map[string]bool{
|
||||
"callgraph": true, // whole program; always enabled
|
||||
"describe": true, // any syntax; always enabled
|
||||
"describe": true, // any syntax; always enabled
|
||||
}
|
||||
|
||||
if qpos.end > qpos.start {
|
||||
|
|
@ -100,11 +98,10 @@ func what(posFlag string, buildContext *build.Context) (*Result, error) {
|
|||
|
||||
// If we don't have an exact selection, disable modes that need one.
|
||||
if !qpos.exact {
|
||||
for _, minfo := range modes {
|
||||
if minfo.needs&needExactPos != 0 {
|
||||
enable[minfo.name] = false
|
||||
}
|
||||
}
|
||||
enable["callees"] = false
|
||||
enable["pointsto"] = false
|
||||
enable["whicherrs"] = false
|
||||
enable["describe"] = false
|
||||
}
|
||||
|
||||
var modes []string
|
||||
|
|
@ -113,17 +110,13 @@ func what(posFlag string, buildContext *build.Context) (*Result, error) {
|
|||
}
|
||||
sort.Strings(modes)
|
||||
|
||||
return &Result{
|
||||
mode: "what",
|
||||
fset: qpos.fset,
|
||||
q: &whatResult{
|
||||
path: qpos.path,
|
||||
srcdir: srcdir,
|
||||
importPath: importPath,
|
||||
modes: modes,
|
||||
},
|
||||
}, nil
|
||||
|
||||
q.result = &whatResult{
|
||||
path: qpos.path,
|
||||
srcdir: srcdir,
|
||||
importPath: importPath,
|
||||
modes: modes,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// guessImportPath finds the package containing filename, and returns
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
|
|
@ -27,10 +28,40 @@ var builtinErrorType = types.Universe.Lookup("error").Type()
|
|||
//
|
||||
// TODO(dmorsing): figure out if fields in errors like *os.PathError.Err
|
||||
// can be queried recursively somehow.
|
||||
func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
func whicherrs(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
||||
// Determine initial packages for PTA.
|
||||
args, err := lconf.FromArgs(q.Scope, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("surplus arguments: %q", args)
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssa.Create(lprog, ssa.GlobalDebug)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
if action != actionExpr {
|
||||
return nil, fmt.Errorf("whicherrs wants an expression; got %s",
|
||||
return fmt.Errorf("whicherrs wants an expression; got %s",
|
||||
astutil.NodeDescription(qpos.path[0]))
|
||||
}
|
||||
var expr ast.Expr
|
||||
|
|
@ -38,46 +69,50 @@ func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
switch n := path[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
// ambiguous ValueSpec containing multiple names
|
||||
return nil, fmt.Errorf("multiple value specification")
|
||||
return fmt.Errorf("multiple value specification")
|
||||
case *ast.Ident:
|
||||
obj = qpos.info.ObjectOf(n)
|
||||
expr = n
|
||||
case ast.Expr:
|
||||
expr = n
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
return fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
typ := qpos.info.TypeOf(expr)
|
||||
if !types.Identical(typ, builtinErrorType) {
|
||||
return nil, fmt.Errorf("selection is not an expression of type 'error'")
|
||||
return fmt.Errorf("selection is not an expression of type 'error'")
|
||||
}
|
||||
// Determine the ssa.Value for the expression.
|
||||
var value ssa.Value
|
||||
var err error
|
||||
if obj != nil {
|
||||
// def/ref of func/var object
|
||||
value, _, err = ssaValueForIdent(o.prog, qpos.info, obj, path)
|
||||
value, _, err = ssaValueForIdent(prog, qpos.info, obj, path)
|
||||
} else {
|
||||
value, _, err = ssaValueForExpr(o.prog, qpos.info, path)
|
||||
value, _, err = ssaValueForExpr(prog, qpos.info, path)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err // e.g. trivially dead code
|
||||
return err // e.g. trivially dead code
|
||||
}
|
||||
buildSSA(o)
|
||||
|
||||
globals := findVisibleErrs(o.prog, qpos)
|
||||
constants := findVisibleConsts(o.prog, qpos)
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.BuildAll()
|
||||
|
||||
globals := findVisibleErrs(prog, qpos)
|
||||
constants := findVisibleConsts(prog, qpos)
|
||||
|
||||
res := &whicherrsResult{
|
||||
qpos: qpos,
|
||||
errpos: expr.Pos(),
|
||||
}
|
||||
|
||||
// TODO(adonovan): the following code is heavily duplicated
|
||||
// w.r.t. "pointsto". Refactor?
|
||||
|
||||
// Find the instruction which initialized the
|
||||
// global error. If more than one instruction has stored to the global
|
||||
// remove the global from the set of values that we want to query.
|
||||
allFuncs := ssautil.AllFunctions(o.prog)
|
||||
allFuncs := ssautil.AllFunctions(prog)
|
||||
for fn := range allFuncs {
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
|
|
@ -104,12 +139,12 @@ func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
}
|
||||
}
|
||||
|
||||
o.ptaConfig.AddQuery(value)
|
||||
ptaConfig.AddQuery(value)
|
||||
for _, v := range globals {
|
||||
o.ptaConfig.AddQuery(v)
|
||||
ptaConfig.AddQuery(v)
|
||||
}
|
||||
|
||||
ptares := ptrAnalysis(o)
|
||||
ptares := ptrAnalysis(ptaConfig)
|
||||
valueptr := ptares.Queries[value]
|
||||
for g, v := range globals {
|
||||
ptr, ok := ptares.Queries[v]
|
||||
|
|
@ -174,11 +209,13 @@ func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
sort.Sort(membersByPosAndString(res.globals))
|
||||
sort.Sort(membersByPosAndString(res.consts))
|
||||
sort.Sort(sorterrorType(res.types))
|
||||
return res, nil
|
||||
|
||||
q.result = res
|
||||
return nil
|
||||
}
|
||||
|
||||
// findVisibleErrs returns a mapping from each package-level variable of type "error" to nil.
|
||||
func findVisibleErrs(prog *ssa.Program, qpos *QueryPos) map[*ssa.Global]ssa.Value {
|
||||
func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value {
|
||||
globals := make(map[*ssa.Global]ssa.Value)
|
||||
for _, pkg := range prog.AllPackages() {
|
||||
for _, mem := range pkg.Members {
|
||||
|
|
@ -201,7 +238,7 @@ func findVisibleErrs(prog *ssa.Program, qpos *QueryPos) map[*ssa.Global]ssa.Valu
|
|||
}
|
||||
|
||||
// findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil.
|
||||
func findVisibleConsts(prog *ssa.Program, qpos *QueryPos) map[ssa.Const]*ssa.NamedConst {
|
||||
func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst {
|
||||
constants := make(map[ssa.Const]*ssa.NamedConst)
|
||||
for _, pkg := range prog.AllPackages() {
|
||||
for _, mem := range pkg.Members {
|
||||
|
|
@ -247,7 +284,7 @@ type errorType struct {
|
|||
}
|
||||
|
||||
type whicherrsResult struct {
|
||||
qpos *QueryPos
|
||||
qpos *queryPos
|
||||
errpos token.Pos
|
||||
globals []ssa.Member
|
||||
consts []ssa.Member
|
||||
|
|
@ -270,7 +307,7 @@ func (r *whicherrsResult) display(printf printfFunc) {
|
|||
if len(r.types) > 0 {
|
||||
printf(r.qpos, "this error may contain these dynamic types:")
|
||||
for _, t := range r.types {
|
||||
printf(t.obj.Pos(), "\t%s", r.qpos.TypeString(t.typ))
|
||||
printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -286,7 +323,7 @@ func (r *whicherrsResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|||
}
|
||||
for _, t := range r.types {
|
||||
var et serial.WhichErrsType
|
||||
et.Type = r.qpos.TypeString(t.typ)
|
||||
et.Type = r.qpos.typeString(t.typ)
|
||||
et.Position = fset.Position(t.obj.Pos()).String()
|
||||
we.Types = append(we.Types, et)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue