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