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.
|
json structured data in JSON syntax.
|
||||||
xml structured data in XML 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:
|
The mode argument determines the query to perform:
|
||||||
|
|
||||||
callees show possible targets of selected function call
|
callees show possible targets of selected function call
|
||||||
callers show possible callers of selected function
|
callers show possible callers of selected function
|
||||||
callgraph show complete callgraph of program
|
|
||||||
callstack show path from callgraph root to selected function
|
callstack show path from callgraph root to selected function
|
||||||
|
definition show declaration of selected identifier
|
||||||
describe describe selected syntax: definition, methods, etc
|
describe describe selected syntax: definition, methods, etc
|
||||||
freevars show free variables of selection
|
freevars show free variables of selection
|
||||||
implements show 'implements' relation for selected type or method
|
implements show 'implements' relation for selected type or method
|
||||||
|
|
@ -166,8 +166,16 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask the oracle.
|
// Ask the oracle.
|
||||||
res, err := oracle.Query(args, mode, *posFlag, ptalog, &build.Default, *reflectFlag)
|
query := oracle.Query{
|
||||||
if err != nil {
|
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)
|
fmt.Fprintf(os.Stderr, "oracle: %s.\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +183,7 @@ func main() {
|
||||||
// Print the result.
|
// Print the result.
|
||||||
switch *formatFlag {
|
switch *formatFlag {
|
||||||
case "json":
|
case "json":
|
||||||
b, err := json.MarshalIndent(res.Serial(), "", "\t")
|
b, err := json.MarshalIndent(query.Serial(), "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "oracle: JSON error: %s.\n", err)
|
fmt.Fprintf(os.Stderr, "oracle: JSON error: %s.\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
@ -183,7 +191,7 @@ func main() {
|
||||||
os.Stdout.Write(b)
|
os.Stdout.Write(b)
|
||||||
|
|
||||||
case "xml":
|
case "xml":
|
||||||
b, err := xml.MarshalIndent(res.Serial(), "", "\t")
|
b, err := xml.MarshalIndent(query.Serial(), "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "oracle: XML error: %s.\n", err)
|
fmt.Fprintf(os.Stderr, "oracle: XML error: %s.\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
@ -191,6 +199,6 @@ func main() {
|
||||||
os.Stdout.Write(b)
|
os.Stdout.Write(b)
|
||||||
|
|
||||||
case "plain":
|
case "plain":
|
||||||
res.WriteTo(os.Stdout)
|
query.WriteTo(os.Stdout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
oracle/TODO
16
oracle/TODO
|
|
@ -6,15 +6,9 @@ ORACLE TODO
|
||||||
General
|
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
|
Save unsaved editor buffers into an archive and provide that to the
|
||||||
tools, which should act as if they were saved.
|
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.
|
Include complete pos/end information Serial output.
|
||||||
But beware that sometimes a single token (e.g. +) is more helpful
|
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).
|
than the pos/end of the containing expression (e.g. x \n + \n y).
|
||||||
|
|
@ -34,12 +28,6 @@ implements
|
||||||
|
|
||||||
definition, referrers
|
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.
|
definition: Make it work with qualified identifiers (SelectorExpr) too.
|
||||||
|
|
||||||
references: Make it work on things that are implicit idents, like
|
references: Make it work on things that are implicit idents, like
|
||||||
|
|
@ -50,8 +38,6 @@ what
|
||||||
Report def/ref info if available.
|
Report def/ref info if available.
|
||||||
Editors could use it to highlight all idents of the same local var.
|
Editors could use it to highlight all idents of the same local var.
|
||||||
|
|
||||||
Fix: support it in (*Oracle).Query (long-running tools).
|
|
||||||
|
|
||||||
More tests.
|
More tests.
|
||||||
|
|
||||||
pointsto
|
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:
|
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 user may have switched workspaces and the oracle should run in
|
||||||
the new one.
|
the new one.
|
||||||
|
|
||||||
Support other editors: vim, Eclipse, Sublime, etc.
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
|
"golang.org/x/tools/go/pointer"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"golang.org/x/tools/go/types"
|
"golang.org/x/tools/go/types"
|
||||||
"golang.org/x/tools/oracle/serial"
|
"golang.org/x/tools/oracle/serial"
|
||||||
|
|
@ -17,10 +19,40 @@ import (
|
||||||
|
|
||||||
// Callees reports the possible callees of the function call site
|
// Callees reports the possible callees of the function call site
|
||||||
// identified by the specified source location.
|
// identified by the specified source location.
|
||||||
func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
func callees(q *Query) error {
|
||||||
pkg := o.prog.Package(qpos.info.Pkg)
|
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 {
|
if pkg == nil {
|
||||||
return nil, fmt.Errorf("no SSA package")
|
return fmt.Errorf("no SSA package")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the enclosing call for the specified position.
|
// Determine the enclosing call for the specified position.
|
||||||
|
|
@ -31,7 +63,7 @@ func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e == nil {
|
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
|
// TODO(adonovan): issue an error if the call is "too far
|
||||||
// away" from the current selection, as this most likely is
|
// 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.
|
// Reject type conversions.
|
||||||
if qpos.info.Types[e.Fun].IsType() {
|
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.
|
// Reject calls to built-ins.
|
||||||
if id, ok := unparen(e.Fun).(*ast.Ident); ok {
|
if id, ok := unparen(e.Fun).(*ast.Ident); ok {
|
||||||
if b, ok := qpos.info.Uses[id].(*types.Builtin); 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.
|
// Ascertain calling function and call site.
|
||||||
callerFn := ssa.EnclosingFunction(pkg, qpos.path)
|
callerFn := ssa.EnclosingFunction(pkg, qpos.path)
|
||||||
if callerFn == nil {
|
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.
|
// Find the call site.
|
||||||
site, err := findCallSite(callerFn, e.Lparen)
|
site, err := findCallSite(callerFn, e.Lparen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
funcs, err := findCallees(o, site)
|
funcs, err := findCallees(ptaConfig, site)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &calleesResult{
|
q.result = &calleesResult{
|
||||||
site: site,
|
site: site,
|
||||||
funcs: funcs,
|
funcs: funcs,
|
||||||
}, nil
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findCallSite(fn *ssa.Function, lparen token.Pos) (ssa.CallInstruction, error) {
|
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")
|
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.
|
// Avoid running the pointer analysis for static calls.
|
||||||
if callee := site.Common().StaticCallee(); callee != nil {
|
if callee := site.Common().StaticCallee(); callee != nil {
|
||||||
switch callee.String() {
|
switch callee.String() {
|
||||||
|
|
@ -99,8 +133,8 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic call: use pointer analysis.
|
// Dynamic call: use pointer analysis.
|
||||||
o.ptaConfig.BuildCallGraph = true
|
conf.BuildCallGraph = true
|
||||||
cg := ptrAnalysis(o).CallGraph
|
cg := ptrAnalysis(conf).CallGraph
|
||||||
cg.DeleteSyntheticNodes()
|
cg.DeleteSyntheticNodes()
|
||||||
|
|
||||||
// Find all call edges from the site.
|
// Find all call edges from the site.
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
"golang.org/x/tools/go/callgraph"
|
"golang.org/x/tools/go/callgraph"
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"golang.org/x/tools/oracle/serial"
|
"golang.org/x/tools/oracle/serial"
|
||||||
)
|
)
|
||||||
|
|
@ -16,35 +17,67 @@ import (
|
||||||
// Callers reports the possible callers of the function
|
// Callers reports the possible callers of the function
|
||||||
// immediately enclosing the specified source location.
|
// immediately enclosing the specified source location.
|
||||||
//
|
//
|
||||||
func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
func callers(conf *Query) error {
|
||||||
pkg := o.prog.Package(qpos.info.Pkg)
|
lconf := loader.Config{Build: conf.Build}
|
||||||
if pkg == nil {
|
|
||||||
return nil, fmt.Errorf("no SSA package")
|
// Determine initial packages for PTA.
|
||||||
|
args, err := lconf.FromArgs(conf.Scope, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
|
if len(args) > 0 {
|
||||||
return nil, fmt.Errorf("this position is not inside a function")
|
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)
|
target := ssa.EnclosingFunction(pkg, qpos.path)
|
||||||
if target == nil {
|
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
|
// Run the pointer analysis, recording each
|
||||||
// call found to originate from target.
|
// call found to originate from target.
|
||||||
o.ptaConfig.BuildCallGraph = true
|
ptaConfig.BuildCallGraph = true
|
||||||
cg := ptrAnalysis(o).CallGraph
|
cg := ptrAnalysis(ptaConfig).CallGraph
|
||||||
cg.DeleteSyntheticNodes()
|
cg.DeleteSyntheticNodes()
|
||||||
edges := cg.CreateNode(target).In
|
edges := cg.CreateNode(target).In
|
||||||
// TODO(adonovan): sort + dedup calls to ensure test determinism.
|
// TODO(adonovan): sort + dedup calls to ensure test determinism.
|
||||||
|
|
||||||
return &callersResult{
|
conf.result = &callersResult{
|
||||||
target: target,
|
target: target,
|
||||||
callgraph: cg,
|
callgraph: cg,
|
||||||
edges: edges,
|
edges: edges,
|
||||||
}, nil
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type callersResult struct {
|
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"
|
"go/token"
|
||||||
|
|
||||||
"golang.org/x/tools/go/callgraph"
|
"golang.org/x/tools/go/callgraph"
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"golang.org/x/tools/oracle/serial"
|
"golang.org/x/tools/oracle/serial"
|
||||||
)
|
)
|
||||||
|
|
@ -23,26 +24,57 @@ import (
|
||||||
// TODO(adonovan): permit user to specify a starting point other than
|
// TODO(adonovan): permit user to specify a starting point other than
|
||||||
// the analysis root.
|
// the analysis root.
|
||||||
//
|
//
|
||||||
func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
func callstack(conf *Query) error {
|
||||||
pkg := o.prog.Package(qpos.info.Pkg)
|
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 {
|
if pkg == nil {
|
||||||
return nil, fmt.Errorf("no SSA package")
|
return fmt.Errorf("no SSA package")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
|
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)
|
target := ssa.EnclosingFunction(pkg, qpos.path)
|
||||||
if target == nil {
|
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.
|
// Run the pointer analysis and build the complete call graph.
|
||||||
o.ptaConfig.BuildCallGraph = true
|
ptaConfig.BuildCallGraph = true
|
||||||
cg := ptrAnalysis(o).CallGraph
|
cg := ptrAnalysis(ptaConfig).CallGraph
|
||||||
cg.DeleteSyntheticNodes()
|
cg.DeleteSyntheticNodes()
|
||||||
|
|
||||||
// Search for an arbitrary path from a root to the target function.
|
// 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>
|
callpath = callpath[1:] // remove synthetic edge from <root>
|
||||||
}
|
}
|
||||||
|
|
||||||
return &callstackResult{
|
conf.Fset = fset
|
||||||
|
conf.result = &callstackResult{
|
||||||
qpos: qpos,
|
qpos: qpos,
|
||||||
target: target,
|
target: target,
|
||||||
callpath: callpath,
|
callpath: callpath,
|
||||||
}, nil
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type callstackResult struct {
|
type callstackResult struct {
|
||||||
qpos *QueryPos
|
qpos *queryPos
|
||||||
target *ssa.Function
|
target *ssa.Function
|
||||||
callpath []*callgraph.Edge
|
callpath []*callgraph.Edge
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
"golang.org/x/tools/go/types"
|
"golang.org/x/tools/go/types"
|
||||||
"golang.org/x/tools/oracle/serial"
|
"golang.org/x/tools/oracle/serial"
|
||||||
)
|
)
|
||||||
|
|
@ -18,28 +19,48 @@ import (
|
||||||
// TODO(adonovan): opt: for intra-file references, the parser's
|
// TODO(adonovan): opt: for intra-file references, the parser's
|
||||||
// resolution might be enough; we should start with that.
|
// 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)
|
id, _ := qpos.path[0].(*ast.Ident)
|
||||||
if id == nil {
|
if id == nil {
|
||||||
return nil, fmt.Errorf("no identifier here")
|
return fmt.Errorf("no identifier here")
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := qpos.info.ObjectOf(id)
|
obj := qpos.info.ObjectOf(id)
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
// Happens for y in "switch y := x.(type)", but I think that's all.
|
// 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 {
|
type definitionResult struct {
|
||||||
qpos *QueryPos
|
qpos *queryPos
|
||||||
obj types.Object // object it denotes
|
obj types.Object // object it denotes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *definitionResult) display(printf printfFunc) {
|
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) {
|
func (r *definitionResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||||
|
|
|
||||||
|
|
@ -27,32 +27,52 @@ import (
|
||||||
// - the definition of its referent (for identifiers) [now redundant]
|
// - the definition of its referent (for identifiers) [now redundant]
|
||||||
// - its type and method set (for an expression or type expression)
|
// - 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
|
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))
|
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||||
switch action {
|
switch action {
|
||||||
case actionExpr:
|
case actionExpr:
|
||||||
return describeValue(o, qpos, path)
|
q.result, err = describeValue(qpos, path)
|
||||||
|
|
||||||
case actionType:
|
case actionType:
|
||||||
return describeType(o, qpos, path)
|
q.result, err = describeType(qpos, path)
|
||||||
|
|
||||||
case actionPackage:
|
case actionPackage:
|
||||||
return describePackage(o, qpos, path)
|
q.result, err = describePackage(qpos, path)
|
||||||
|
|
||||||
case actionStmt:
|
case actionStmt:
|
||||||
return describeStmt(o, qpos, path)
|
q.result, err = describeStmt(qpos, path)
|
||||||
|
|
||||||
case actionUnknown:
|
case actionUnknown:
|
||||||
return &describeUnknownResult{path[0]}, nil
|
q.result = &describeUnknownResult{path[0]}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic(action) // unreachable
|
panic(action) // unreachable
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type describeUnknownResult struct {
|
type describeUnknownResult struct {
|
||||||
|
|
@ -288,7 +308,7 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No
|
||||||
return nil, actionUnknown // unreachable
|
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 expr ast.Expr
|
||||||
var obj types.Object
|
var obj types.Object
|
||||||
switch n := path[0].(type) {
|
switch n := path[0].(type) {
|
||||||
|
|
@ -318,7 +338,7 @@ func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueRe
|
||||||
}
|
}
|
||||||
|
|
||||||
type describeValueResult struct {
|
type describeValueResult struct {
|
||||||
qpos *QueryPos
|
qpos *queryPos
|
||||||
expr ast.Expr // query node
|
expr ast.Expr // query node
|
||||||
typ types.Type // type of expression
|
typ types.Type // type of expression
|
||||||
constVal exact.Value // value of expression, if constant
|
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 != nil {
|
||||||
if r.obj.Pos() == r.expr.Pos() {
|
if r.obj.Pos() == r.expr.Pos() {
|
||||||
// defining ident
|
// 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 {
|
} else {
|
||||||
// referring ident
|
// 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 {
|
if def := r.obj.Pos(); def != token.NoPos {
|
||||||
printf(def, "defined here")
|
printf(def, "defined here")
|
||||||
}
|
}
|
||||||
|
|
@ -360,7 +380,7 @@ func (r *describeValueResult) display(printf printfFunc) {
|
||||||
printf(r.expr, "%s%s", desc, suffix)
|
printf(r.expr, "%s%s", desc, suffix)
|
||||||
} else {
|
} else {
|
||||||
// non-constant expression
|
// 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(),
|
Pos: fset.Position(r.expr.Pos()).String(),
|
||||||
Detail: "value",
|
Detail: "value",
|
||||||
Value: &serial.DescribeValue{
|
Value: &serial.DescribeValue{
|
||||||
Type: r.qpos.TypeString(r.typ),
|
Type: r.qpos.typeString(r.typ),
|
||||||
Value: value,
|
Value: value,
|
||||||
ObjPos: objpos,
|
ObjPos: objpos,
|
||||||
},
|
},
|
||||||
|
|
@ -388,7 +408,7 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet)
|
||||||
|
|
||||||
// ---- TYPE ------------------------------------------------------------
|
// ---- 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 description string
|
||||||
var t types.Type
|
var t types.Type
|
||||||
switch n := path[0].(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)
|
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).
|
// Show sizes for structs and named types (it's fairly obvious for others).
|
||||||
switch t.(type) {
|
switch t.(type) {
|
||||||
case *types.Named, *types.Struct:
|
case *types.Named, *types.Struct:
|
||||||
// TODO(adonovan): use o.imp.Config().TypeChecker.Sizes when
|
szs := types.StdSizes{8, 8} // assume amd64
|
||||||
// we add the Config() method (needs some thought).
|
|
||||||
szs := types.StdSizes{8, 8}
|
|
||||||
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
||||||
szs.Sizeof(t), szs.Alignof(t))
|
szs.Sizeof(t), szs.Alignof(t))
|
||||||
}
|
}
|
||||||
|
|
@ -437,7 +455,7 @@ func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResu
|
||||||
}
|
}
|
||||||
|
|
||||||
type describeTypeResult struct {
|
type describeTypeResult struct {
|
||||||
qpos *QueryPos
|
qpos *queryPos
|
||||||
node ast.Node
|
node ast.Node
|
||||||
description string
|
description string
|
||||||
typ types.Type
|
typ types.Type
|
||||||
|
|
@ -449,7 +467,7 @@ func (r *describeTypeResult) display(printf printfFunc) {
|
||||||
|
|
||||||
// Show the underlying type for a reference to a named type.
|
// Show the underlying type for a reference to a named type.
|
||||||
if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
|
if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
|
||||||
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.
|
// 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
|
// TODO(adonovan): print these relative
|
||||||
// to the owning package, not the
|
// to the owning package, not the
|
||||||
// query package.
|
// query package.
|
||||||
printf(meth.Obj(), "\t%s", r.qpos.SelectionString(meth))
|
printf(meth.Obj(), "\t%s", r.qpos.selectionString(meth))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printf(r.node, "No methods.")
|
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(),
|
Pos: fset.Position(r.node.Pos()).String(),
|
||||||
Detail: "type",
|
Detail: "type",
|
||||||
Type: &serial.DescribeType{
|
Type: &serial.DescribeType{
|
||||||
Type: r.qpos.TypeString(r.typ),
|
Type: r.qpos.typeString(r.typ),
|
||||||
NamePos: namePos,
|
NamePos: namePos,
|
||||||
NameDef: nameDef,
|
NameDef: nameDef,
|
||||||
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
|
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
|
||||||
|
|
@ -490,7 +508,7 @@ func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||||
|
|
||||||
// ---- PACKAGE ------------------------------------------------------------
|
// ---- 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 description string
|
||||||
var pkg *types.Package
|
var pkg *types.Package
|
||||||
switch n := path[0].(type) {
|
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 {
|
type describePackageResult struct {
|
||||||
|
|
@ -661,7 +679,7 @@ func tokenOf(o types.Object) string {
|
||||||
|
|
||||||
// ---- STATEMENT ------------------------------------------------------------
|
// ---- STATEMENT ------------------------------------------------------------
|
||||||
|
|
||||||
func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResult, error) {
|
func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
|
||||||
var description string
|
var description string
|
||||||
switch n := path[0].(type) {
|
switch n := path[0].(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
|
|
@ -675,7 +693,7 @@ func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResu
|
||||||
// Nothing much to say about statements.
|
// Nothing much to say about statements.
|
||||||
description = astutil.NodeDescription(n)
|
description = astutil.NodeDescription(n)
|
||||||
}
|
}
|
||||||
return &describeStmtResult{o.fset, path[0], description}, nil
|
return &describeStmtResult{qpos.fset, path[0], description}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type describeStmtResult struct {
|
type describeStmtResult struct {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
"golang.org/x/tools/go/types"
|
"golang.org/x/tools/go/types"
|
||||||
"golang.org/x/tools/oracle/serial"
|
"golang.org/x/tools/oracle/serial"
|
||||||
)
|
)
|
||||||
|
|
@ -28,7 +29,26 @@ import (
|
||||||
// these might be interesting. Perhaps group the results into three
|
// these might be interesting. Perhaps group the results into three
|
||||||
// bands.
|
// 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
|
file := qpos.path[len(qpos.path)-1] // the enclosing file
|
||||||
fileScope := qpos.info.Scopes[file]
|
fileScope := qpos.info.Scopes[file]
|
||||||
pkgScope := fileScope.Parent()
|
pkgScope := fileScope.Parent()
|
||||||
|
|
@ -118,7 +138,7 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
typ := qpos.info.TypeOf(n.(ast.Expr))
|
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
|
refsMap[ref.ref] = ref
|
||||||
|
|
||||||
if prune {
|
if prune {
|
||||||
|
|
@ -136,14 +156,15 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
}
|
}
|
||||||
sort.Sort(byRef(refs))
|
sort.Sort(byRef(refs))
|
||||||
|
|
||||||
return &freevarsResult{
|
q.result = &freevarsResult{
|
||||||
qpos: qpos,
|
qpos: qpos,
|
||||||
refs: refs,
|
refs: refs,
|
||||||
}, nil
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type freevarsResult struct {
|
type freevarsResult struct {
|
||||||
qpos *QueryPos
|
qpos *queryPos
|
||||||
refs []freevarsRef
|
refs []freevarsRef
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,37 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
"golang.org/x/tools/go/types"
|
"golang.org/x/tools/go/types"
|
||||||
"golang.org/x/tools/oracle/serial"
|
"golang.org/x/tools/oracle/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Implements displays the "implements" relation as it pertains to the
|
// 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
|
// the corresponding methods of the types that would have been reported
|
||||||
// by an implements query on the receiver type.
|
// 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.
|
// Find the selected type.
|
||||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
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 {
|
if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
|
||||||
recv := obj.Type().(*types.Signature).Recv()
|
recv := obj.Type().(*types.Signature).Recv()
|
||||||
if recv == nil {
|
if recv == nil {
|
||||||
return nil, fmt.Errorf("this function is not a method")
|
return fmt.Errorf("this function is not a method")
|
||||||
}
|
}
|
||||||
method = obj
|
method = obj
|
||||||
T = recv.Type()
|
T = recv.Type()
|
||||||
|
|
@ -45,7 +66,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
T = qpos.info.TypeOf(path[0].(ast.Expr))
|
T = qpos.info.TypeOf(path[0].(ast.Expr))
|
||||||
}
|
}
|
||||||
if T == nil {
|
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
|
// 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?
|
// i.e. don't reduceScope?
|
||||||
//
|
//
|
||||||
var allNamed []types.Type
|
var allNamed []types.Type
|
||||||
for _, info := range o.typeInfo {
|
for _, info := range lprog.AllPackages {
|
||||||
for _, obj := range info.Defs {
|
for _, obj := range info.Defs {
|
||||||
if obj, ok := obj.(*types.TypeName); ok {
|
if obj, ok := obj.(*types.TypeName); ok {
|
||||||
allNamed = append(allNamed, obj.Type())
|
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 {
|
type implementsResult struct {
|
||||||
qpos *QueryPos
|
qpos *queryPos
|
||||||
|
|
||||||
t types.Type // queried type (not necessarily named)
|
t types.Type // queried type (not necessarily named)
|
||||||
pos interface{} // pos of t (*types.Name or *QueryPos)
|
pos interface{} // pos of t (*types.Name or *QueryPos)
|
||||||
|
|
@ -160,7 +184,7 @@ func (r *implementsResult) display(printf printfFunc) {
|
||||||
meth := func(sel *types.Selection) {
|
meth := func(sel *types.Selection) {
|
||||||
if sel != nil {
|
if sel != nil {
|
||||||
printf(sel.Obj(), "\t%s method (%s).%s",
|
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 {
|
if r.method == nil {
|
||||||
printf(r.pos, "interface type %s", r.t)
|
printf(r.pos, "interface type %s", r.t)
|
||||||
} else {
|
} 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.
|
// 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)
|
printf(r.pos, "%s type %s", typeKind(r.t), r.t)
|
||||||
} else {
|
} else {
|
||||||
printf(r.method, "concrete method %s",
|
printf(r.method, "concrete method %s",
|
||||||
r.qpos.ObjectString(r.method))
|
r.qpos.objectString(r.method))
|
||||||
}
|
}
|
||||||
for i, super := range r.from {
|
for i, super := range r.from {
|
||||||
if r.method == nil {
|
if r.method == nil {
|
||||||
|
|
@ -231,7 +255,7 @@ func (r *implementsResult) display(printf printfFunc) {
|
||||||
} else {
|
} else {
|
||||||
// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
|
// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
|
||||||
printf(r.method, "concrete method %s",
|
printf(r.method, "concrete method %s",
|
||||||
r.qpos.ObjectString(r.method))
|
r.qpos.objectString(r.method))
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, psuper := range r.fromPtr {
|
for i, psuper := range r.fromPtr {
|
||||||
|
|
@ -260,7 +284,7 @@ func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||||
}
|
}
|
||||||
if r.method != nil {
|
if r.method != nil {
|
||||||
res.Implements.Method = &serial.DescribeMethod{
|
res.Implements.Method = &serial.DescribeMethod{
|
||||||
Name: r.qpos.ObjectString(r.method),
|
Name: r.qpos.objectString(r.method),
|
||||||
Pos: fset.Position(r.method.Pos()).String(),
|
Pos: fset.Position(r.method.Pos()).String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
492
oracle/oracle.go
492
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
|
// - show all places where an object of type T is created
|
||||||
// (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
|
// (&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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/token"
|
"go/token"
|
||||||
"io"
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
|
|
@ -64,64 +35,6 @@ import (
|
||||||
"golang.org/x/tools/oracle/serial"
|
"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{})
|
type printfFunc func(pos interface{}, format string, args ...interface{})
|
||||||
|
|
||||||
// queryResult is the interface of each query-specific result type.
|
// 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 QueryPos represents the position provided as input to a query:
|
||||||
// a textual extent in the program's source code, the AST node it
|
// a textual extent in the program's source code, the AST node it
|
||||||
// corresponds to, and the package to which it belongs.
|
// corresponds to, and the package to which it belongs.
|
||||||
// Instances are created by ParseQueryPos.
|
// Instances are created by parseQueryPos.
|
||||||
//
|
type queryPos struct {
|
||||||
type QueryPos struct {
|
|
||||||
fset *token.FileSet
|
fset *token.FileSet
|
||||||
start, end token.Pos // source extent of query
|
start, end token.Pos // source extent of query
|
||||||
path []ast.Node // AST path from query node to root of ast.File
|
path []ast.Node // AST path from query node to root of ast.File
|
||||||
|
|
@ -144,236 +56,97 @@ type QueryPos struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TypeString prints type T relative to the query position.
|
// 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)
|
return types.TypeString(qpos.info.Pkg, T)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectString prints object obj relative to the query position.
|
// 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)
|
return types.ObjectString(qpos.info.Pkg, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectionString prints selection sel relative to the query position.
|
// 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)
|
return types.SelectionString(qpos.info.Pkg, sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Result encapsulates the result of an oracle.Query.
|
// A Query specifies a single oracle query.
|
||||||
type Result struct {
|
type Query struct {
|
||||||
fset *token.FileSet
|
Mode string // query mode ("callers", etc)
|
||||||
q queryResult // the query-specific result
|
Pos string // query position
|
||||||
mode string // query mode
|
Build *build.Context // package loading configuration
|
||||||
warnings []pointer.Warning // pointer analysis warnings (TODO(adonovan): fix: never populated!)
|
|
||||||
|
// 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
|
// Serial returns an instance of serial.Result, which implements the
|
||||||
// {xml,json}.Marshaler interfaces so that query results can be
|
// {xml,json}.Marshaler interfaces so that query results can be
|
||||||
// serialized as JSON or XML.
|
// serialized as JSON or XML.
|
||||||
//
|
//
|
||||||
func (res *Result) Serial() *serial.Result {
|
func (q *Query) Serial() *serial.Result {
|
||||||
resj := &serial.Result{Mode: res.mode}
|
resj := &serial.Result{Mode: q.Mode}
|
||||||
res.q.toSerial(resj, res.fset)
|
q.result.toSerial(resj, q.Fset)
|
||||||
for _, w := range res.warnings {
|
|
||||||
resj.Warnings = append(resj.Warnings, serial.PTAWarning{
|
|
||||||
Pos: res.fset.Position(w.Pos).String(),
|
|
||||||
Message: w.Message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return resj
|
return resj
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query runs a single oracle query.
|
// WriteTo writes the oracle query result res to out in a compiler diagnostic format.
|
||||||
//
|
func (q *Query) WriteTo(out io.Writer) {
|
||||||
// args specify the main package in (*loader.Config).FromArgs syntax.
|
printf := func(pos interface{}, format string, args ...interface{}) {
|
||||||
// mode is the query mode ("callers", etc).
|
fprintf(out, q.Fset, pos, format, args...)
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
q.result.display(printf)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reduceScope is called for one-shot queries that need only a single
|
// Run runs an oracle query and populates its Fset and Result.
|
||||||
// typed package. It attempts to guess the query package from pos and
|
func Run(conf *Query) error {
|
||||||
// reduce the analysis scope (set of loaded packages) to just that one
|
switch conf.Mode {
|
||||||
// plus (the exported parts of) its dependencies. It leaves its
|
case "callees":
|
||||||
// arguments unchanged on failure.
|
return callees(conf)
|
||||||
//
|
case "callers":
|
||||||
// TODO(adonovan): this is a real mess... but it's fast.
|
return callers(conf)
|
||||||
//
|
case "callstack":
|
||||||
func reduceScope(pos string, conf *loader.Config) {
|
return callstack(conf)
|
||||||
fqpos, err := fastQueryPos(pos)
|
case "peers":
|
||||||
if err != nil {
|
return peers(conf)
|
||||||
return // bad query
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
if err != nil {
|
|
||||||
return // can't find GOPATH dir
|
|
||||||
}
|
|
||||||
if importPath == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that it's possible to load the queried package.
|
|
||||||
// (e.g. oracle tests contain different 'package' decls in same dir.)
|
|
||||||
// Keep consistent with logic in loader/util.go!
|
|
||||||
cfg2 := *conf.Build
|
|
||||||
cfg2.CgoEnabled = false
|
|
||||||
bp, err := cfg2.Import(importPath, "", 0)
|
|
||||||
if err != nil {
|
|
||||||
return // 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pkgContainsFile(bp *build.Package, filename string) bool {
|
// Create a pointer.Config whose scope is the initial packages of lprog
|
||||||
for _, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
|
// and their dependencies.
|
||||||
for _, file := range files {
|
func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) {
|
||||||
if sameFile(file, filename) {
|
// TODO(adonovan): the body of this function is essentially
|
||||||
return true
|
// duplicated in all go/pointer clients. Refactor.
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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),
|
// For each initial package (specified on the command line),
|
||||||
// if it has a main function, analyze that,
|
// if it has a main function, analyze that,
|
||||||
// otherwise analyze its tests, if any.
|
// otherwise analyze its tests, if any.
|
||||||
var testPkgs, mains []*ssa.Package
|
var testPkgs, mains []*ssa.Package
|
||||||
for _, info := range iprog.InitialPackages() {
|
for _, info := range lprog.InitialPackages() {
|
||||||
initialPkg := prog.Package(info.Pkg)
|
initialPkg := prog.Package(info.Pkg)
|
||||||
|
|
||||||
// Add package to the pointer analysis scope.
|
// Add package to the pointer analysis scope.
|
||||||
|
|
@ -391,98 +164,111 @@ func newOracle(iprog *loader.Program, ptalog io.Writer, needs int, reflection bo
|
||||||
if mains == nil {
|
if mains == nil {
|
||||||
return nil, fmt.Errorf("analysis scope has no main and no tests")
|
return nil, fmt.Errorf("analysis scope has no main and no tests")
|
||||||
}
|
}
|
||||||
o.ptaConfig.Log = ptalog
|
return &pointer.Config{
|
||||||
o.ptaConfig.Reflection = reflection
|
Log: ptaLog,
|
||||||
o.ptaConfig.Mains = mains
|
Reflection: reflection,
|
||||||
|
Mains: mains,
|
||||||
o.prog = prog
|
}, nil
|
||||||
}
|
|
||||||
|
|
||||||
return o, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query runs the query of the specified mode and selection.
|
// importQueryPackage finds the package P containing the
|
||||||
//
|
// query position and tells conf to import it.
|
||||||
// TODO(adonovan): fix: this function does not currently support the
|
func importQueryPackage(pos string, conf *loader.Config) error {
|
||||||
// "what" query, which needs to access the go/build.Context.
|
fqpos, err := fastQueryPos(pos)
|
||||||
//
|
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, err
|
return err // bad query
|
||||||
}
|
}
|
||||||
return res, nil
|
filename := fqpos.fset.File(fqpos.start).Name()
|
||||||
|
|
||||||
|
// 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 err // can't find GOPATH dir
|
||||||
|
}
|
||||||
|
if importPath == "" {
|
||||||
|
return fmt.Errorf("can't guess import path from %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that it's possible to load the queried package.
|
||||||
|
// (e.g. oracle tests contain different 'package' decls in same dir.)
|
||||||
|
// Keep consistent with logic in loader/util.go!
|
||||||
|
cfg2 := *conf.Build
|
||||||
|
cfg2.CgoEnabled = false
|
||||||
|
bp, err := cfg2.Import(importPath, "", 0)
|
||||||
|
if err != nil {
|
||||||
|
return err // no files for package
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseQueryPos parses the source query position pos.
|
// 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(filepath.Join(bp.Dir, file), filename) {
|
||||||
|
return "GTX"[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0 // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
// If needExact, it must identify a single AST subtree;
|
||||||
// this is appropriate for queries that allow fairly arbitrary syntax,
|
// this is appropriate for queries that allow fairly arbitrary syntax,
|
||||||
// e.g. "describe".
|
// 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)
|
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset)
|
start, end, err := findQueryPos(lprog.Fset, filename, startOffset, endOffset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
info, path, exact := iprog.PathEnclosingInterval(start, end)
|
info, path, exact := lprog.PathEnclosingInterval(start, end)
|
||||||
if path == nil {
|
if path == nil {
|
||||||
return nil, fmt.Errorf("no syntax here")
|
return nil, fmt.Errorf("no syntax here")
|
||||||
}
|
}
|
||||||
if needExact && !exact {
|
if needExact && !exact {
|
||||||
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
|
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
|
||||||
}
|
}
|
||||||
return &QueryPos{iprog.Fset, start, end, path, exact, info}, nil
|
return &queryPos{lprog.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Utilities ----------
|
// ---------- Utilities ----------
|
||||||
|
|
||||||
// buildSSA constructs the SSA representation of Go-source function bodies.
|
// allowErrors causes type errors to be silently ignored.
|
||||||
// Not needed in simpler modes, e.g. freevars.
|
// (Not suitable if SSA construction follows.)
|
||||||
//
|
func allowErrors(lconf *loader.Config) {
|
||||||
func buildSSA(o *Oracle) {
|
lconf.AllowErrors = true
|
||||||
o.prog.BuildAll()
|
lconf.TypeChecker.Error = func(err error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ptrAnalysis runs the pointer analysis and returns its result.
|
// ptrAnalysis runs the pointer analysis and returns its result.
|
||||||
func ptrAnalysis(o *Oracle) *pointer.Result {
|
func ptrAnalysis(conf *pointer.Config) *pointer.Result {
|
||||||
result, err := pointer.Analyze(&o.ptaConfig)
|
result, err := pointer.Analyze(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // pointer analysis internal error
|
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()
|
start = pos.Pos()
|
||||||
end = start
|
end = start
|
||||||
case *QueryPos:
|
case *queryPos:
|
||||||
start = pos.start
|
start = pos.start
|
||||||
end = pos.end
|
end = pos.end
|
||||||
case nil:
|
case nil:
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/oracle"
|
"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.
|
// 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
|
capture := new(bytes.Buffer) // capture standard output
|
||||||
res.WriteTo(capture)
|
q.WriteTo(capture)
|
||||||
for _, line := range strings.Split(capture.String(), "\n") {
|
for _, line := range strings.Split(capture.String(), "\n") {
|
||||||
// Remove a "file:line: " prefix.
|
// Remove a "file:line: " prefix.
|
||||||
if i := strings.Index(line, ": "); i >= 0 {
|
if i := strings.Index(line, ": "); i >= 0 {
|
||||||
|
|
@ -170,28 +169,30 @@ func doQuery(out io.Writer, q *query, useJson bool) {
|
||||||
|
|
||||||
var buildContext = build.Default
|
var buildContext = build.Default
|
||||||
buildContext.GOPATH = "testdata"
|
buildContext.GOPATH = "testdata"
|
||||||
res, err := oracle.Query([]string{q.filename},
|
query := oracle.Query{
|
||||||
q.verb,
|
Mode: q.verb,
|
||||||
q.queryPos,
|
Pos: q.queryPos,
|
||||||
nil, // ptalog,
|
Build: &buildContext,
|
||||||
&buildContext,
|
Scope: []string{q.filename},
|
||||||
true) // reflection
|
Reflection: true,
|
||||||
if err != nil {
|
}
|
||||||
|
if err := oracle.Run(&query); err != nil {
|
||||||
fmt.Fprintf(out, "\nError: %s\n", err)
|
fmt.Fprintf(out, "\nError: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if useJson {
|
if useJson {
|
||||||
// JSON output
|
// JSON output
|
||||||
b, err := json.MarshalIndent(res.Serial(), "", "\t")
|
b, err := json.MarshalIndent(query.Serial(), "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(out, "JSON error: %s\n", err.Error())
|
fmt.Fprintf(out, "JSON error: %s\n", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
out.Write(b)
|
out.Write(b)
|
||||||
|
fmt.Fprintln(out)
|
||||||
} else {
|
} else {
|
||||||
// "plain" (compiler diagnostic format) output
|
// "plain" (compiler diagnostic format) output
|
||||||
WriteResult(out, res)
|
WriteResult(out, &query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,32 +203,29 @@ func TestOracle(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, filename := range []string{
|
for _, filename := range []string{
|
||||||
"testdata/src/main/calls.go",
|
"testdata/src/calls/main.go",
|
||||||
"testdata/src/main/callgraph.go",
|
"testdata/src/describe/main.go",
|
||||||
"testdata/src/main/callgraph2.go",
|
"testdata/src/freevars/main.go",
|
||||||
"testdata/src/main/describe.go",
|
"testdata/src/implements/main.go",
|
||||||
"testdata/src/main/freevars.go",
|
"testdata/src/implements-methods/main.go",
|
||||||
"testdata/src/main/implements.go",
|
"testdata/src/imports/main.go",
|
||||||
"testdata/src/main/implements-methods.go",
|
"testdata/src/peers/main.go",
|
||||||
"testdata/src/main/imports.go",
|
"testdata/src/pointsto/main.go",
|
||||||
"testdata/src/main/peers.go",
|
"testdata/src/reflection/main.go",
|
||||||
"testdata/src/main/pointsto.go",
|
"testdata/src/what/main.go",
|
||||||
"testdata/src/main/reflection.go",
|
"testdata/src/whicherrs/main.go",
|
||||||
"testdata/src/main/what.go",
|
|
||||||
"testdata/src/main/whicherrs.go",
|
|
||||||
// JSON:
|
// JSON:
|
||||||
// TODO(adonovan): most of these are very similar; combine them.
|
// TODO(adonovan): most of these are very similar; combine them.
|
||||||
"testdata/src/main/callgraph-json.go",
|
"testdata/src/calls-json/main.go",
|
||||||
"testdata/src/main/calls-json.go",
|
"testdata/src/peers-json/main.go",
|
||||||
"testdata/src/main/peers-json.go",
|
"testdata/src/describe-json/main.go",
|
||||||
"testdata/src/main/describe-json.go",
|
"testdata/src/implements-json/main.go",
|
||||||
"testdata/src/main/implements-json.go",
|
"testdata/src/implements-methods-json/main.go",
|
||||||
"testdata/src/main/implements-methods-json.go",
|
"testdata/src/pointsto-json/main.go",
|
||||||
"testdata/src/main/pointsto-json.go",
|
"testdata/src/referrers-json/main.go",
|
||||||
"testdata/src/main/referrers-json.go",
|
"testdata/src/what-json/main.go",
|
||||||
"testdata/src/main/what-json.go",
|
|
||||||
} {
|
} {
|
||||||
useJson := strings.HasSuffix(filename, "-json.go")
|
useJson := strings.Contains(filename, "-json/")
|
||||||
queries := parseQueries(t, filename)
|
queries := parseQueries(t, filename)
|
||||||
golden := filename + "lden"
|
golden := filename + "lden"
|
||||||
got := filename + "t"
|
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"
|
"go/token"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
"golang.org/x/tools/go/ssa/ssautil"
|
||||||
"golang.org/x/tools/go/types"
|
"golang.org/x/tools/go/types"
|
||||||
|
|
@ -22,20 +23,51 @@ import (
|
||||||
// TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
|
// TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
|
||||||
// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
|
// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
|
||||||
// or the implicit receive in "for v := range ch".
|
// or the implicit receive in "for v := range ch".
|
||||||
func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
func peers(q *Query) error {
|
||||||
opPos := findOp(qpos)
|
lconf := loader.Config{Build: q.Build}
|
||||||
if opPos == token.NoPos {
|
|
||||||
return nil, fmt.Errorf("there is no channel operation here")
|
// 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 queryOp chanOp // the originating send or receive operation
|
||||||
var ops []chanOp // all sends/receives of opposite direction
|
var ops []chanOp // all sends/receives of opposite direction
|
||||||
|
|
||||||
// Look at all channel operations in the whole ssa.Program.
|
// Look at all channel operations in the whole ssa.Program.
|
||||||
// Build a list of those of same type as the query.
|
// 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 fn := range allFuncs {
|
||||||
for _, b := range fn.Blocks {
|
for _, b := range fn.Blocks {
|
||||||
for _, instr := range b.Instrs {
|
for _, instr := range b.Instrs {
|
||||||
|
|
@ -49,7 +81,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if queryOp.ch == nil {
|
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.
|
// 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.
|
// ignore both directionality and type names.
|
||||||
queryType := queryOp.ch.Type()
|
queryType := queryOp.ch.Type()
|
||||||
queryElemType := queryType.Underlying().(*types.Chan).Elem()
|
queryElemType := queryType.Underlying().(*types.Chan).Elem()
|
||||||
o.ptaConfig.AddQuery(queryOp.ch)
|
ptaConfig.AddQuery(queryOp.ch)
|
||||||
i := 0
|
i := 0
|
||||||
for _, op := range ops {
|
for _, op := range ops {
|
||||||
if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
|
if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
|
||||||
o.ptaConfig.AddQuery(op.ch)
|
ptaConfig.AddQuery(op.ch)
|
||||||
ops[i] = op
|
ops[i] = op
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +102,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
ops = ops[:i]
|
ops = ops[:i]
|
||||||
|
|
||||||
// Run the pointer analysis.
|
// Run the pointer analysis.
|
||||||
ptares := ptrAnalysis(o)
|
ptares := ptrAnalysis(ptaConfig)
|
||||||
|
|
||||||
// Find the points-to set.
|
// Find the points-to set.
|
||||||
queryChanPtr := ptares.Queries[queryOp.ch]
|
queryChanPtr := ptares.Queries[queryOp.ch]
|
||||||
|
|
@ -100,14 +132,15 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
sort.Sort(byPos(receives))
|
sort.Sort(byPos(receives))
|
||||||
sort.Sort(byPos(closes))
|
sort.Sort(byPos(closes))
|
||||||
|
|
||||||
return &peersResult{
|
q.result = &peersResult{
|
||||||
queryPos: opPos,
|
queryPos: opPos,
|
||||||
queryType: queryType,
|
queryType: queryType,
|
||||||
makes: makes,
|
makes: makes,
|
||||||
sends: sends,
|
sends: sends,
|
||||||
receives: receives,
|
receives: receives,
|
||||||
closes: closes,
|
closes: closes,
|
||||||
}, nil
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// findOp returns the position of the enclosing send/receive/close op.
|
// 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.
|
// for close operations, it's the Lparen of the function call.
|
||||||
//
|
//
|
||||||
// TODO(adonovan): handle implicit receive operations from 'for...range chan' statements.
|
// 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 {
|
for _, n := range qpos.path {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *ast.UnaryExpr:
|
case *ast.UnaryExpr:
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,40 @@ import (
|
||||||
//
|
//
|
||||||
// All printed sets are sorted to ensure determinism.
|
// 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)
|
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||||
if action != actionExpr {
|
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]))
|
astutil.NodeDescription(qpos.path[0]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +67,7 @@ func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
switch n := path[0].(type) {
|
switch n := path[0].(type) {
|
||||||
case *ast.ValueSpec:
|
case *ast.ValueSpec:
|
||||||
// ambiguous ValueSpec containing multiple names
|
// ambiguous ValueSpec containing multiple names
|
||||||
return nil, fmt.Errorf("multiple value specification")
|
return fmt.Errorf("multiple value specification")
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
obj = qpos.info.ObjectOf(n)
|
obj = qpos.info.ObjectOf(n)
|
||||||
expr = n
|
expr = n
|
||||||
|
|
@ -45,41 +75,44 @@ func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
expr = n
|
expr = n
|
||||||
default:
|
default:
|
||||||
// TODO(adonovan): is this reachable?
|
// 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).
|
// Reject non-pointerlike types (includes all constants---except nil).
|
||||||
// TODO(adonovan): reject nil too.
|
// TODO(adonovan): reject nil too.
|
||||||
typ := qpos.info.TypeOf(expr)
|
typ := qpos.info.TypeOf(expr)
|
||||||
if !pointer.CanPoint(typ) {
|
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.
|
// Determine the ssa.Value for the expression.
|
||||||
var value ssa.Value
|
var value ssa.Value
|
||||||
var isAddr bool
|
var isAddr bool
|
||||||
var err error
|
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
// def/ref of func/var object
|
// 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 {
|
} else {
|
||||||
value, isAddr, err = ssaValueForExpr(o.prog, qpos.info, path)
|
value, isAddr, err = ssaValueForExpr(prog, qpos.info, path)
|
||||||
}
|
}
|
||||||
if err != nil {
|
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.
|
// Run the pointer analysis.
|
||||||
ptrs, err := runPTA(o, value, isAddr)
|
ptrs, err := runPTA(ptaConfig, value, isAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err // e.g. analytically unreachable
|
return err // e.g. analytically unreachable
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pointstoResult{
|
q.result = &pointstoResult{
|
||||||
qpos: qpos,
|
qpos: qpos,
|
||||||
typ: typ,
|
typ: typ,
|
||||||
ptrs: ptrs,
|
ptrs: ptrs,
|
||||||
}, nil
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
|
// 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.
|
// 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) {
|
func runPTA(conf *pointer.Config, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
|
||||||
buildSSA(o)
|
|
||||||
|
|
||||||
T := v.Type()
|
T := v.Type()
|
||||||
if isAddr {
|
if isAddr {
|
||||||
o.ptaConfig.AddIndirectQuery(v)
|
conf.AddIndirectQuery(v)
|
||||||
T = deref(T)
|
T = deref(T)
|
||||||
} else {
|
} else {
|
||||||
o.ptaConfig.AddQuery(v)
|
conf.AddQuery(v)
|
||||||
}
|
}
|
||||||
ptares := ptrAnalysis(o)
|
ptares := ptrAnalysis(conf)
|
||||||
|
|
||||||
var ptr pointer.Pointer
|
var ptr pointer.Pointer
|
||||||
if isAddr {
|
if isAddr {
|
||||||
|
|
@ -177,7 +208,7 @@ type pointerResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type pointstoResult struct {
|
type pointstoResult struct {
|
||||||
qpos *QueryPos
|
qpos *queryPos
|
||||||
typ types.Type // type of expression
|
typ types.Type // type of expression
|
||||||
ptrs []pointerResult // pointer info (typ is concrete => len==1)
|
ptrs []pointerResult // pointer info (typ is concrete => len==1)
|
||||||
}
|
}
|
||||||
|
|
@ -188,17 +219,17 @@ func (r *pointstoResult) display(printf printfFunc) {
|
||||||
// reflect.Value expression.
|
// reflect.Value expression.
|
||||||
|
|
||||||
if len(r.ptrs) > 0 {
|
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 {
|
for _, ptr := range r.ptrs {
|
||||||
var obj types.Object
|
var obj types.Object
|
||||||
if nt, ok := deref(ptr.typ).(*types.Named); ok {
|
if nt, ok := deref(ptr.typ).(*types.Named); ok {
|
||||||
obj = nt.Obj()
|
obj = nt.Obj()
|
||||||
}
|
}
|
||||||
if len(ptr.labels) > 0 {
|
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")
|
printLabels(printf, ptr.labels, "\t\t")
|
||||||
} else {
|
} else {
|
||||||
printf(obj, "\t%s", r.qpos.TypeString(ptr.typ))
|
printf(obj, "\t%s", r.qpos.typeString(ptr.typ))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -208,11 +239,11 @@ func (r *pointstoResult) display(printf printfFunc) {
|
||||||
// Show labels for other expressions.
|
// Show labels for other expressions.
|
||||||
if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
|
if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
|
||||||
printf(r.qpos, "this %s may point to these objects:",
|
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")
|
printLabels(printf, ptr.labels, "\t")
|
||||||
} else {
|
} else {
|
||||||
printf(r.qpos, "this %s may not point to anything.",
|
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{
|
pts = append(pts, serial.PointsTo{
|
||||||
Type: r.qpos.TypeString(ptr.typ),
|
Type: r.qpos.typeString(ptr.typ),
|
||||||
NamePos: namePos,
|
NamePos: namePos,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -117,13 +117,7 @@ func sameFile(x, y string) bool {
|
||||||
|
|
||||||
// fastQueryPos parses the -pos flag and returns a QueryPos.
|
// fastQueryPos parses the -pos flag and returns a QueryPos.
|
||||||
// It parses only a single file, and does not run the type checker.
|
// It parses only a single file, and does not run the type checker.
|
||||||
//
|
func fastQueryPos(posFlag string) (*queryPos, error) {
|
||||||
// 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) {
|
|
||||||
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -145,5 +139,5 @@ func fastQueryPos(posFlag string) (*QueryPos, error) {
|
||||||
return nil, fmt.Errorf("no syntax here")
|
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/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
"golang.org/x/tools/go/types"
|
"golang.org/x/tools/go/types"
|
||||||
"golang.org/x/tools/oracle/serial"
|
"golang.org/x/tools/oracle/serial"
|
||||||
|
"golang.org/x/tools/refactor/importgraph"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(adonovan): use golang.org/x/tools/refactor/importgraph to choose
|
// 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
|
// Referrers reports all identifiers that resolve to the same object
|
||||||
// as the queried identifier, within any package in the analysis scope.
|
// as the queried identifier, within any package in the analysis scope.
|
||||||
//
|
func referrers(q *Query) error {
|
||||||
func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
lconf := loader.Config{Build: q.Build}
|
||||||
id, _ := qpos.path[0].(*ast.Ident)
|
allowErrors(&lconf)
|
||||||
if id == nil {
|
|
||||||
return nil, fmt.Errorf("no identifier here")
|
if err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := qpos.info.ObjectOf(id)
|
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 {
|
if obj == nil {
|
||||||
// Happens for y in "switch y := x.(type)", but I think that's all.
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
// Iterate over all go/types' Uses facts for the entire program.
|
||||||
var refs []*ast.Ident
|
var refs []*ast.Ident
|
||||||
for _, info := range o.typeInfo {
|
for _, info := range lprog.AllPackages {
|
||||||
for id2, obj2 := range info.Uses {
|
for id2, obj2 := range info.Uses {
|
||||||
if sameObj(obj, obj2) {
|
if sameObj(obj, obj2) {
|
||||||
refs = append(refs, id2)
|
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))
|
sort.Sort(byNamePos(refs))
|
||||||
|
|
||||||
return &referrersResult{
|
q.result = &referrersResult{
|
||||||
qpos: qpos,
|
fset: q.Fset,
|
||||||
query: id,
|
query: id,
|
||||||
obj: obj,
|
obj: obj,
|
||||||
refs: refs,
|
refs: refs,
|
||||||
}, nil
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// same reports whether x and y are identical, or both are PkgNames
|
// 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] }
|
func (p byNamePos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
|
||||||
type referrersResult struct {
|
type referrersResult struct {
|
||||||
qpos *QueryPos
|
fset *token.FileSet
|
||||||
query *ast.Ident // identifier of query
|
query *ast.Ident // identifier of query
|
||||||
obj types.Object // object it denotes
|
obj types.Object // object it denotes
|
||||||
refs []*ast.Ident // set of all other references to it
|
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.
|
// First pass: start the file reads concurrently.
|
||||||
for _, ref := range r.refs {
|
for _, ref := range r.refs {
|
||||||
posn := r.qpos.fset.Position(ref.Pos())
|
posn := r.fset.Position(ref.Pos())
|
||||||
fi := fileinfosByName[posn.Filename]
|
fi := fileinfosByName[posn.Filename]
|
||||||
if fi == nil {
|
if fi == nil {
|
||||||
fi = &fileinfo{data: make(chan []byte)}
|
fi = &fileinfo{data: make(chan []byte)}
|
||||||
|
|
|
||||||
|
|
@ -64,20 +64,6 @@ type Caller struct {
|
||||||
Caller string `json:"caller"` // full name of calling function
|
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.
|
// A CallStack is the result of a 'callstack' query.
|
||||||
// It indicates an arbitrary path from the root of the callgraph to
|
// It indicates an arbitrary path from the root of the callgraph to
|
||||||
// the query function.
|
// the query function.
|
||||||
|
|
@ -232,11 +218,6 @@ type Describe struct {
|
||||||
Value *DescribeValue `json:"value,omitempty"`
|
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.
|
// A WhichErrs is the result of a 'whicherrs' query.
|
||||||
// It contains the position of the queried error and the possible globals,
|
// It contains the position of the queried error and the possible globals,
|
||||||
// constants, and types it may point to.
|
// constants, and types it may point to.
|
||||||
|
|
@ -264,7 +245,6 @@ type Result struct {
|
||||||
// the one specified by 'mode'.
|
// the one specified by 'mode'.
|
||||||
Callees *Callees `json:"callees,omitempty"`
|
Callees *Callees `json:"callees,omitempty"`
|
||||||
Callers []Caller `json:"callers,omitempty"`
|
Callers []Caller `json:"callers,omitempty"`
|
||||||
Callgraph []CallGraph `json:"callgraph,omitempty"`
|
|
||||||
Callstack *CallStack `json:"callstack,omitempty"`
|
Callstack *CallStack `json:"callstack,omitempty"`
|
||||||
Definition *Definition `json:"definition,omitempty"`
|
Definition *Definition `json:"definition,omitempty"`
|
||||||
Describe *Describe `json:"describe,omitempty"`
|
Describe *Describe `json:"describe,omitempty"`
|
||||||
|
|
@ -275,6 +255,4 @@ type Result struct {
|
||||||
Referrers *Referrers `json:"referrers,omitempty"`
|
Referrers *Referrers `json:"referrers,omitempty"`
|
||||||
What *What `json:"what,omitempty"`
|
What *What `json:"what,omitempty"`
|
||||||
WhichErrs *WhichErrs `json:"whicherrs,omitempty"`
|
WhichErrs *WhichErrs `json:"whicherrs,omitempty"`
|
||||||
|
|
||||||
Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,30 @@
|
||||||
{
|
{
|
||||||
"mode": "callees",
|
"mode": "callees",
|
||||||
"callees": {
|
"callees": {
|
||||||
"pos": "testdata/src/main/calls-json.go:8:3",
|
"pos": "testdata/src/calls-json/main.go:8:3",
|
||||||
"desc": "dynamic function call",
|
"desc": "dynamic function call",
|
||||||
"callees": [
|
"callees": [
|
||||||
{
|
{
|
||||||
"name": "main.main$1",
|
"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",
|
"mode": "callstack",
|
||||||
"callstack": {
|
"callstack": {
|
||||||
"pos": "testdata/src/main/calls-json.go:12:7",
|
"pos": "testdata/src/calls-json/main.go:12:7",
|
||||||
"target": "main.main$1",
|
"target": "main.main$1",
|
||||||
"callers": [
|
"callers": [
|
||||||
{
|
{
|
||||||
"pos": "testdata/src/main/calls-json.go:8:3",
|
"pos": "testdata/src/calls-json/main.go:8:3",
|
||||||
"desc": "dynamic function call",
|
"desc": "dynamic function call",
|
||||||
"caller": "main.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",
|
"desc": "static function call",
|
||||||
"caller": "main.main"
|
"caller": "main.main"
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ package describe // @describe pkgdecl "describe"
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
// See describe-json.golden for expected query results.
|
// See describe-json.golden for expected query results.
|
||||||
|
|
||||||
func main() { //
|
func main() {
|
||||||
var s struct{ x [3]int }
|
var s struct{ x [3]int }
|
||||||
p := &s.x[0] // @describe desc-val-p "p"
|
p := &s.x[0] // @describe desc-val-p "p"
|
||||||
_ = p
|
_ = p
|
||||||
|
|
@ -2,104 +2,108 @@
|
||||||
{
|
{
|
||||||
"mode": "describe",
|
"mode": "describe",
|
||||||
"describe": {
|
"describe": {
|
||||||
"desc": "definition of package \"describe\"",
|
"desc": "definition of package \"describe-json\"",
|
||||||
"pos": "testdata/src/main/describe-json.go:1:9",
|
"pos": "testdata/src/describe-json/main.go:1:9",
|
||||||
"detail": "package",
|
"detail": "package",
|
||||||
"package": {
|
"package": {
|
||||||
"path": "describe",
|
"path": "describe-json",
|
||||||
"members": [
|
"members": [
|
||||||
{
|
{
|
||||||
"name": "C",
|
"name": "C",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"pos": "testdata/src/main/describe-json.go:25:6",
|
"pos": "testdata/src/describe-json/main.go:25:6",
|
||||||
"kind": "type",
|
"kind": "type",
|
||||||
"methods": [
|
"methods": [
|
||||||
{
|
{
|
||||||
"name": "method (C) f()",
|
"name": "method (C) f()",
|
||||||
"pos": "testdata/src/main/describe-json.go:28:12"
|
"pos": "testdata/src/describe-json/main.go:28:12"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "D",
|
"name": "D",
|
||||||
"type": "struct{}",
|
"type": "struct{}",
|
||||||
"pos": "testdata/src/main/describe-json.go:26:6",
|
"pos": "testdata/src/describe-json/main.go:26:6",
|
||||||
"kind": "type",
|
"kind": "type",
|
||||||
"methods": [
|
"methods": [
|
||||||
{
|
{
|
||||||
"name": "method (*D) f()",
|
"name": "method (*D) f()",
|
||||||
"pos": "testdata/src/main/describe-json.go:29:13"
|
"pos": "testdata/src/describe-json/main.go:29:13"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "I",
|
"name": "I",
|
||||||
"type": "interface{f()}",
|
"type": "interface{f()}",
|
||||||
"pos": "testdata/src/main/describe-json.go:21:6",
|
"pos": "testdata/src/describe-json/main.go:21:6",
|
||||||
"kind": "type",
|
"kind": "type",
|
||||||
"methods": [
|
"methods": [
|
||||||
{
|
{
|
||||||
"name": "method (I) f()",
|
"name": "method (I) f()",
|
||||||
"pos": "testdata/src/main/describe-json.go:22:2"
|
"pos": "testdata/src/describe-json/main.go:22:2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "main",
|
"name": "main",
|
||||||
"type": "func()",
|
"type": "func()",
|
||||||
"pos": "testdata/src/main/describe-json.go:7:6",
|
"pos": "testdata/src/describe-json/main.go:7:6",
|
||||||
"kind": "func"
|
"kind": "func"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}-------- @describe desc-val-p --------
|
}
|
||||||
|
-------- @describe desc-val-p --------
|
||||||
{
|
{
|
||||||
"mode": "describe",
|
"mode": "describe",
|
||||||
"describe": {
|
"describe": {
|
||||||
"desc": "identifier",
|
"desc": "identifier",
|
||||||
"pos": "testdata/src/main/describe-json.go:9:2",
|
"pos": "testdata/src/describe-json/main.go:9:2",
|
||||||
"detail": "value",
|
"detail": "value",
|
||||||
"value": {
|
"value": {
|
||||||
"type": "*int",
|
"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",
|
"mode": "describe",
|
||||||
"describe": {
|
"describe": {
|
||||||
"desc": "identifier",
|
"desc": "identifier",
|
||||||
"pos": "testdata/src/main/describe-json.go:16:8",
|
"pos": "testdata/src/describe-json/main.go:16:8",
|
||||||
"detail": "value",
|
"detail": "value",
|
||||||
"value": {
|
"value": {
|
||||||
"type": "I",
|
"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",
|
"mode": "describe",
|
||||||
"describe": {
|
"describe": {
|
||||||
"desc": "go statement",
|
"desc": "go statement",
|
||||||
"pos": "testdata/src/main/describe-json.go:18:2",
|
"pos": "testdata/src/describe-json/main.go:18:2",
|
||||||
"detail": "unknown"
|
"detail": "unknown"
|
||||||
}
|
}
|
||||||
}-------- @describe desc-type-C --------
|
}
|
||||||
|
-------- @describe desc-type-C --------
|
||||||
{
|
{
|
||||||
"mode": "describe",
|
"mode": "describe",
|
||||||
"describe": {
|
"describe": {
|
||||||
"desc": "definition of type C (size 8, align 8)",
|
"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",
|
"detail": "type",
|
||||||
"type": {
|
"type": {
|
||||||
"type": "C",
|
"type": "C",
|
||||||
"namepos": "testdata/src/main/describe-json.go:25:6",
|
"namepos": "testdata/src/describe-json/main.go:25:6",
|
||||||
"namedef": "int",
|
"namedef": "int",
|
||||||
"methods": [
|
"methods": [
|
||||||
{
|
{
|
||||||
"name": "method (C) f()",
|
"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 (
|
import (
|
||||||
"hash/fnv" // @describe ref-pkg-import2 "fnv"
|
"hash/fnv" // @describe ref-pkg-import2 "fnv"
|
||||||
|
|
@ -41,7 +41,7 @@ defined here
|
||||||
|
|
||||||
-------- @pointsto p --------
|
-------- @pointsto p --------
|
||||||
this *int may point to these objects:
|
this *int may point to these objects:
|
||||||
imports.a
|
main.a
|
||||||
|
|
||||||
-------- @describe ref-pkg --------
|
-------- @describe ref-pkg --------
|
||||||
reference to package "lib"
|
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) {
|
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.
|
// Tests of channel 'peers' query, -format=json.
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
// 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.
|
// Tests of channel 'peers' query.
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
|
|
@ -26,7 +26,7 @@ This channel of type chan *int may be:
|
||||||
|
|
||||||
-------- @pointsto pointsto-rA --------
|
-------- @pointsto pointsto-rA --------
|
||||||
this *int may point to these objects:
|
this *int may point to these objects:
|
||||||
peers.a2
|
main.a2
|
||||||
a1
|
a1
|
||||||
|
|
||||||
-------- @peers peer-recv-chB --------
|
-------- @peers peer-recv-chB --------
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package pointsto
|
package main
|
||||||
|
|
||||||
// Tests of 'pointsto' queries, -format=json.
|
// Tests of 'pointsto' queries, -format=json.
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
|
|
@ -6,29 +6,30 @@
|
||||||
"type": "*int",
|
"type": "*int",
|
||||||
"labels": [
|
"labels": [
|
||||||
{
|
{
|
||||||
"pos": "testdata/src/main/pointsto-json.go:8:6",
|
"pos": "testdata/src/pointsto-json/main.go:8:6",
|
||||||
"desc": "s.x[*]"
|
"desc": "s.x[*]"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}-------- @pointsto val-i --------
|
}
|
||||||
|
-------- @pointsto val-i --------
|
||||||
{
|
{
|
||||||
"mode": "pointsto",
|
"mode": "pointsto",
|
||||||
"pointsto": [
|
"pointsto": [
|
||||||
{
|
{
|
||||||
"type": "*D",
|
"type": "*D",
|
||||||
"namepos": "testdata/src/main/pointsto-json.go:24:6",
|
"namepos": "testdata/src/pointsto-json/main.go:24:6",
|
||||||
"labels": [
|
"labels": [
|
||||||
{
|
{
|
||||||
"pos": "testdata/src/main/pointsto-json.go:14:10",
|
"pos": "testdata/src/pointsto-json/main.go:14:10",
|
||||||
"desc": "new"
|
"desc": "new"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "C",
|
"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.
|
// Tests of 'pointsto' query.
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
// 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
|
Error: pointer analysis wants an expression of reference type; got untyped float
|
||||||
-------- @pointsto func-ref-main --------
|
-------- @pointsto func-ref-main --------
|
||||||
this func() may point to these objects:
|
this func() may point to these objects:
|
||||||
pointsto.main
|
main.main
|
||||||
|
|
||||||
-------- @pointsto func-ref-*C.f --------
|
-------- @pointsto func-ref-*C.f --------
|
||||||
this func() may point to these objects:
|
this func() may point to these objects:
|
||||||
(*pointsto.C).f
|
(*main.C).f
|
||||||
|
|
||||||
-------- @pointsto func-ref-D.f --------
|
-------- @pointsto func-ref-D.f --------
|
||||||
this func() may point to these objects:
|
this func() may point to these objects:
|
||||||
(pointsto.D).f
|
(main.D).f
|
||||||
|
|
||||||
-------- @pointsto func-ref-I.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 --------
|
-------- @pointsto func-ref-d.f --------
|
||||||
this func() may point to these objects:
|
this func() may point to these objects:
|
||||||
(pointsto.D).f
|
(main.D).f
|
||||||
|
|
||||||
-------- @pointsto func-ref-i.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 --------
|
-------- @pointsto ref-lexical-d.f --------
|
||||||
this func() may point to these objects:
|
this func() may point to these objects:
|
||||||
(pointsto.D).f
|
(main.D).f
|
||||||
|
|
||||||
-------- @pointsto ref-anon --------
|
-------- @pointsto ref-anon --------
|
||||||
this func() may point to these objects:
|
this func() may point to these objects:
|
||||||
pointsto.main$1
|
main.main$1
|
||||||
|
|
||||||
-------- @pointsto ref-global --------
|
-------- @pointsto ref-global --------
|
||||||
this *string may point to these objects:
|
this *string may point to these objects:
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package referrers
|
package main
|
||||||
|
|
||||||
// Tests of 'referrers' query.
|
// Tests of 'referrers' query.
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
// 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
|
// 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.
|
// so that pointsto.go doesn't have to import "reflect" each time.
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
-------- @pointsto mrv --------
|
-------- @pointsto mrv --------
|
||||||
this reflect.Value may contain these dynamic types:
|
this reflect.Value may contain these dynamic types:
|
||||||
*bool, may point to:
|
*bool, may point to:
|
||||||
reflection.b
|
main.b
|
||||||
*int, may point to:
|
*int, may point to:
|
||||||
reflection.a
|
main.a
|
||||||
map[*int]*bool, may point to:
|
map[*int]*bool, may point to:
|
||||||
makemap
|
makemap
|
||||||
|
|
||||||
-------- @pointsto p1 --------
|
-------- @pointsto p1 --------
|
||||||
this interface{} may contain these dynamic types:
|
this interface{} may contain these dynamic types:
|
||||||
*bool, may point to:
|
*bool, may point to:
|
||||||
reflection.b
|
main.b
|
||||||
*int, may point to:
|
*int, may point to:
|
||||||
reflection.a
|
main.a
|
||||||
map[*int]*bool, may point to:
|
map[*int]*bool, may point to:
|
||||||
makemap
|
makemap
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ this []reflect.Value may point to these objects:
|
||||||
-------- @pointsto p3 --------
|
-------- @pointsto p3 --------
|
||||||
this reflect.Value may contain these dynamic types:
|
this reflect.Value may contain these dynamic types:
|
||||||
*int, may point to:
|
*int, may point to:
|
||||||
reflection.a
|
main.a
|
||||||
|
|
||||||
-------- @pointsto p4 --------
|
-------- @pointsto p4 --------
|
||||||
this reflect.Type may contain these dynamic types:
|
this reflect.Type may contain these dynamic types:
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package what
|
package main
|
||||||
|
|
||||||
// Tests of 'what' queries, -format=json.
|
// Tests of 'what' queries, -format=json.
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
|
|
@ -37,7 +37,6 @@
|
||||||
"modes": [
|
"modes": [
|
||||||
"callees",
|
"callees",
|
||||||
"callers",
|
"callers",
|
||||||
"callgraph",
|
|
||||||
"callstack",
|
"callstack",
|
||||||
"definition",
|
"definition",
|
||||||
"describe",
|
"describe",
|
||||||
|
|
@ -47,6 +46,6 @@
|
||||||
"referrers"
|
"referrers"
|
||||||
],
|
],
|
||||||
"srcdir": "testdata/src",
|
"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.
|
// Tests of 'what' queries.
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
-------- @what pkgdecl --------
|
-------- @what pkgdecl --------
|
||||||
identifier
|
identifier
|
||||||
source file
|
source file
|
||||||
modes: [callgraph definition describe freevars implements pointsto referrers]
|
modes: [definition describe freevars implements pointsto referrers]
|
||||||
srcdir: testdata/src
|
srcdir: testdata/src
|
||||||
import path: main
|
import path: what
|
||||||
|
|
||||||
-------- @what call --------
|
-------- @what call --------
|
||||||
identifier
|
identifier
|
||||||
|
|
@ -12,9 +12,9 @@ expression statement
|
||||||
block
|
block
|
||||||
function declaration
|
function declaration
|
||||||
source file
|
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
|
srcdir: testdata/src
|
||||||
import path: main
|
import path: what
|
||||||
|
|
||||||
-------- @what var --------
|
-------- @what var --------
|
||||||
variable declaration
|
variable declaration
|
||||||
|
|
@ -22,9 +22,9 @@ variable declaration statement
|
||||||
block
|
block
|
||||||
function declaration
|
function declaration
|
||||||
source file
|
source file
|
||||||
modes: [callers callgraph callstack describe freevars pointsto]
|
modes: [callers callstack describe freevars pointsto]
|
||||||
srcdir: testdata/src
|
srcdir: testdata/src
|
||||||
import path: main
|
import path: what
|
||||||
|
|
||||||
-------- @what recv --------
|
-------- @what recv --------
|
||||||
identifier
|
identifier
|
||||||
|
|
@ -33,7 +33,7 @@ expression statement
|
||||||
block
|
block
|
||||||
function declaration
|
function declaration
|
||||||
source file
|
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
|
srcdir: testdata/src
|
||||||
import path: main
|
import path: what
|
||||||
|
|
||||||
|
|
@ -24,20 +24,18 @@ import (
|
||||||
// tools, e.g. to populate a menu of options of slower queries about
|
// tools, e.g. to populate a menu of options of slower queries about
|
||||||
// the selected location.
|
// the selected location.
|
||||||
//
|
//
|
||||||
func what(posFlag string, buildContext *build.Context) (*Result, error) {
|
func what(q *Query) error {
|
||||||
qpos, err := fastQueryPos(posFlag)
|
qpos, err := fastQueryPos(q.Pos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
q.Fset = qpos.fset
|
||||||
|
|
||||||
// (ignore errors)
|
// (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.
|
// 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{
|
enable := map[string]bool{
|
||||||
"callgraph": true, // whole program; always enabled
|
|
||||||
"describe": true, // any syntax; always enabled
|
"describe": true, // any syntax; always enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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 we don't have an exact selection, disable modes that need one.
|
||||||
if !qpos.exact {
|
if !qpos.exact {
|
||||||
for _, minfo := range modes {
|
enable["callees"] = false
|
||||||
if minfo.needs&needExactPos != 0 {
|
enable["pointsto"] = false
|
||||||
enable[minfo.name] = false
|
enable["whicherrs"] = false
|
||||||
}
|
enable["describe"] = false
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var modes []string
|
var modes []string
|
||||||
|
|
@ -113,17 +110,13 @@ func what(posFlag string, buildContext *build.Context) (*Result, error) {
|
||||||
}
|
}
|
||||||
sort.Strings(modes)
|
sort.Strings(modes)
|
||||||
|
|
||||||
return &Result{
|
q.result = &whatResult{
|
||||||
mode: "what",
|
|
||||||
fset: qpos.fset,
|
|
||||||
q: &whatResult{
|
|
||||||
path: qpos.path,
|
path: qpos.path,
|
||||||
srcdir: srcdir,
|
srcdir: srcdir,
|
||||||
importPath: importPath,
|
importPath: importPath,
|
||||||
modes: modes,
|
modes: modes,
|
||||||
},
|
}
|
||||||
}, nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// guessImportPath finds the package containing filename, and returns
|
// guessImportPath finds the package containing filename, and returns
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
"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"
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
"golang.org/x/tools/go/ssa/ssautil"
|
||||||
"golang.org/x/tools/go/types"
|
"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
|
// TODO(dmorsing): figure out if fields in errors like *os.PathError.Err
|
||||||
// can be queried recursively somehow.
|
// 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)
|
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||||
if action != actionExpr {
|
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]))
|
astutil.NodeDescription(qpos.path[0]))
|
||||||
}
|
}
|
||||||
var expr ast.Expr
|
var expr ast.Expr
|
||||||
|
|
@ -38,46 +69,50 @@ func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
switch n := path[0].(type) {
|
switch n := path[0].(type) {
|
||||||
case *ast.ValueSpec:
|
case *ast.ValueSpec:
|
||||||
// ambiguous ValueSpec containing multiple names
|
// ambiguous ValueSpec containing multiple names
|
||||||
return nil, fmt.Errorf("multiple value specification")
|
return fmt.Errorf("multiple value specification")
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
obj = qpos.info.ObjectOf(n)
|
obj = qpos.info.ObjectOf(n)
|
||||||
expr = n
|
expr = n
|
||||||
case ast.Expr:
|
case ast.Expr:
|
||||||
expr = n
|
expr = n
|
||||||
default:
|
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)
|
typ := qpos.info.TypeOf(expr)
|
||||||
if !types.Identical(typ, builtinErrorType) {
|
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.
|
// Determine the ssa.Value for the expression.
|
||||||
var value ssa.Value
|
var value ssa.Value
|
||||||
var err error
|
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
// def/ref of func/var object
|
// def/ref of func/var object
|
||||||
value, _, err = ssaValueForIdent(o.prog, qpos.info, obj, path)
|
value, _, err = ssaValueForIdent(prog, qpos.info, obj, path)
|
||||||
} else {
|
} else {
|
||||||
value, _, err = ssaValueForExpr(o.prog, qpos.info, path)
|
value, _, err = ssaValueForExpr(prog, qpos.info, path)
|
||||||
}
|
}
|
||||||
if err != nil {
|
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)
|
// Defer SSA construction till after errors are reported.
|
||||||
constants := findVisibleConsts(o.prog, qpos)
|
prog.BuildAll()
|
||||||
|
|
||||||
|
globals := findVisibleErrs(prog, qpos)
|
||||||
|
constants := findVisibleConsts(prog, qpos)
|
||||||
|
|
||||||
res := &whicherrsResult{
|
res := &whicherrsResult{
|
||||||
qpos: qpos,
|
qpos: qpos,
|
||||||
errpos: expr.Pos(),
|
errpos: expr.Pos(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(adonovan): the following code is heavily duplicated
|
||||||
|
// w.r.t. "pointsto". Refactor?
|
||||||
|
|
||||||
// Find the instruction which initialized the
|
// Find the instruction which initialized the
|
||||||
// global error. If more than one instruction has stored to the global
|
// 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.
|
// 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 fn := range allFuncs {
|
||||||
for _, b := range fn.Blocks {
|
for _, b := range fn.Blocks {
|
||||||
for _, instr := range b.Instrs {
|
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 {
|
for _, v := range globals {
|
||||||
o.ptaConfig.AddQuery(v)
|
ptaConfig.AddQuery(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
ptares := ptrAnalysis(o)
|
ptares := ptrAnalysis(ptaConfig)
|
||||||
valueptr := ptares.Queries[value]
|
valueptr := ptares.Queries[value]
|
||||||
for g, v := range globals {
|
for g, v := range globals {
|
||||||
ptr, ok := ptares.Queries[v]
|
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.globals))
|
||||||
sort.Sort(membersByPosAndString(res.consts))
|
sort.Sort(membersByPosAndString(res.consts))
|
||||||
sort.Sort(sorterrorType(res.types))
|
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.
|
// 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)
|
globals := make(map[*ssa.Global]ssa.Value)
|
||||||
for _, pkg := range prog.AllPackages() {
|
for _, pkg := range prog.AllPackages() {
|
||||||
for _, mem := range pkg.Members {
|
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.
|
// 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)
|
constants := make(map[ssa.Const]*ssa.NamedConst)
|
||||||
for _, pkg := range prog.AllPackages() {
|
for _, pkg := range prog.AllPackages() {
|
||||||
for _, mem := range pkg.Members {
|
for _, mem := range pkg.Members {
|
||||||
|
|
@ -247,7 +284,7 @@ type errorType struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type whicherrsResult struct {
|
type whicherrsResult struct {
|
||||||
qpos *QueryPos
|
qpos *queryPos
|
||||||
errpos token.Pos
|
errpos token.Pos
|
||||||
globals []ssa.Member
|
globals []ssa.Member
|
||||||
consts []ssa.Member
|
consts []ssa.Member
|
||||||
|
|
@ -270,7 +307,7 @@ func (r *whicherrsResult) display(printf printfFunc) {
|
||||||
if len(r.types) > 0 {
|
if len(r.types) > 0 {
|
||||||
printf(r.qpos, "this error may contain these dynamic types:")
|
printf(r.qpos, "this error may contain these dynamic types:")
|
||||||
for _, t := range r.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 {
|
for _, t := range r.types {
|
||||||
var et serial.WhichErrsType
|
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()
|
et.Position = fset.Position(t.obj.Pos()).String()
|
||||||
we.Types = append(we.Types, et)
|
we.Types = append(we.Types, et)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue