oracle: several major improvements

Features:

  More robust: silently ignore type errors in modes that don't need
  SSA form: describe, referrers, implements, freevars, description.
  This makes the tool much more robust for everyday queries.

  Less configuration: don't require a scope argument for all queries.
  Only queries that do pointer analysis need it.
  For the rest, the initial position is enough for
  importQueryPackage to deduce the scope.
  It now works for queries in GoFiles, TestGoFiles, or XTestGoFiles.
  (It no longer works for ad-hoc main packages like
  $GOROOT/src/net/http/triv.go)

  More complete: "referrers" computes the scope automatically by
  scanning the import graph of the entire workspace, using gorename's
  refactor/importgraph package.  This requires two passes at loading.

  Faster: simplified start-up logic avoids unnecessary package loading
  and SSA construction (a consequence of bad abstraction) in many
  cases.

  "callgraph": remove it.  Unlike all the other commands it isn't
  related to the current selection, and we have
  golang.org/x/tools/cmdcallgraph now.

Internals:

  Drop support for long-running clients (i.e., Pythia), since
  godoc -analysis supports all the same features except "pointsto",
  and precomputes all the results so latency is much lower.

  Get rid of various unhelpful abstractions introduced to support
  long-running clients.  Expand out the set-up logic for each
  subcommand.  This is simpler, easier to read, and gives us more
  control, at a small cost in duplication---the familiar story of
  abstractions.

  Discard PTA warnings.  We weren't showing them (nor should we).

  Split tests into separate directories (so that importgraph works).

Change-Id: I55d46b3ab33cdf7ac22436fcc2148fe04c901237
Reviewed-on: https://go-review.googlesource.com/8243
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Alan Donovan 2015-03-30 11:21:48 -04:00
parent 68b5f7541d
commit b28839e4bd
69 changed files with 1362 additions and 1680 deletions

View File

@ -49,14 +49,14 @@ The -format flag controls the output format:
json structured data in JSON syntax. json structured data in JSON syntax.
xml structured data in XML syntax. xml structured data in XML syntax.
The -pos flag is required in all modes except 'callgraph'. The -pos flag is required in all modes.
The mode argument determines the query to perform: The mode argument determines the query to perform:
callees show possible targets of selected function call callees show possible targets of selected function call
callers show possible callers of selected function callers show possible callers of selected function
callgraph show complete callgraph of program
callstack show path from callgraph root to selected function callstack show path from callgraph root to selected function
definition show declaration of selected identifier
describe describe selected syntax: definition, methods, etc describe describe selected syntax: definition, methods, etc
freevars show free variables of selection freevars show free variables of selection
implements show 'implements' relation for selected type or method implements show 'implements' relation for selected type or method
@ -166,8 +166,16 @@ func main() {
} }
// Ask the oracle. // Ask the oracle.
res, err := oracle.Query(args, mode, *posFlag, ptalog, &build.Default, *reflectFlag) query := oracle.Query{
if err != nil { Mode: mode,
Pos: *posFlag,
Build: &build.Default,
Scope: args,
PTALog: ptalog,
Reflection: *reflectFlag,
}
if err := oracle.Run(&query); err != nil {
fmt.Fprintf(os.Stderr, "oracle: %s.\n", err) fmt.Fprintf(os.Stderr, "oracle: %s.\n", err)
os.Exit(1) os.Exit(1)
} }
@ -175,7 +183,7 @@ func main() {
// Print the result. // Print the result.
switch *formatFlag { switch *formatFlag {
case "json": case "json":
b, err := json.MarshalIndent(res.Serial(), "", "\t") b, err := json.MarshalIndent(query.Serial(), "", "\t")
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "oracle: JSON error: %s.\n", err) fmt.Fprintf(os.Stderr, "oracle: JSON error: %s.\n", err)
os.Exit(1) os.Exit(1)
@ -183,7 +191,7 @@ func main() {
os.Stdout.Write(b) os.Stdout.Write(b)
case "xml": case "xml":
b, err := xml.MarshalIndent(res.Serial(), "", "\t") b, err := xml.MarshalIndent(query.Serial(), "", "\t")
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "oracle: XML error: %s.\n", err) fmt.Fprintf(os.Stderr, "oracle: XML error: %s.\n", err)
os.Exit(1) os.Exit(1)
@ -191,6 +199,6 @@ func main() {
os.Stdout.Write(b) os.Stdout.Write(b)
case "plain": case "plain":
res.WriteTo(os.Stdout) query.WriteTo(os.Stdout)
} }
} }

View File

@ -6,15 +6,9 @@ ORACLE TODO
General General
======= =======
Refactor control flow so that each mode has a "one-shot setup" function.
Use a fault-tolerant parser that can recover from bad parses.
Save unsaved editor buffers into an archive and provide that to the Save unsaved editor buffers into an archive and provide that to the
tools, which should act as if they were saved. tools, which should act as if they were saved.
Fix: make the guessImportPath hack work with external _test.go files too.
Include complete pos/end information Serial output. Include complete pos/end information Serial output.
But beware that sometimes a single token (e.g. +) is more helpful But beware that sometimes a single token (e.g. +) is more helpful
than the pos/end of the containing expression (e.g. x \n + \n y). than the pos/end of the containing expression (e.g. x \n + \n y).
@ -34,12 +28,6 @@ implements
definition, referrers definition, referrers
Use the parser's resolver information to answer the query
for local names. Only run the type checker if that fails.
(NB: gri's new parser won't do any resolution.)
referrers: Show the text of the matching line of code, like grep.
definition: Make it work with qualified identifiers (SelectorExpr) too. definition: Make it work with qualified identifiers (SelectorExpr) too.
references: Make it work on things that are implicit idents, like references: Make it work on things that are implicit idents, like
@ -50,8 +38,6 @@ what
Report def/ref info if available. Report def/ref info if available.
Editors could use it to highlight all idents of the same local var. Editors could use it to highlight all idents of the same local var.
Fix: support it in (*Oracle).Query (long-running tools).
More tests. More tests.
pointsto pointsto
@ -95,5 +81,3 @@ Emacs: go-root-and-paths depends on the current buffer, so be sure to
call it from within the source file, not the *go-oracle* buffer: call it from within the source file, not the *go-oracle* buffer:
the user may have switched workspaces and the oracle should run in the user may have switched workspaces and the oracle should run in
the new one. the new one.
Support other editors: vim, Eclipse, Sublime, etc.

View File

@ -10,6 +10,8 @@ import (
"go/token" "go/token"
"sort" "sort"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/pointer"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/types" "golang.org/x/tools/go/types"
"golang.org/x/tools/oracle/serial" "golang.org/x/tools/oracle/serial"
@ -17,10 +19,40 @@ import (
// Callees reports the possible callees of the function call site // Callees reports the possible callees of the function call site
// identified by the specified source location. // identified by the specified source location.
func callees(o *Oracle, qpos *QueryPos) (queryResult, error) { func callees(q *Query) error {
pkg := o.prog.Package(qpos.info.Pkg) lconf := loader.Config{Build: q.Build}
// Determine initial packages for PTA.
args, err := lconf.FromArgs(q.Scope, true)
if err != nil {
return err
}
if len(args) > 0 {
return fmt.Errorf("surplus arguments: %q", args)
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
q.Fset = lprog.Fset
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
if err != nil {
return err
}
prog := ssa.Create(lprog, 0)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
pkg := prog.Package(qpos.info.Pkg)
if pkg == nil { if pkg == nil {
return nil, fmt.Errorf("no SSA package") return fmt.Errorf("no SSA package")
} }
// Determine the enclosing call for the specified position. // Determine the enclosing call for the specified position.
@ -31,7 +63,7 @@ func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
} }
if e == nil { if e == nil {
return nil, fmt.Errorf("there is no function call here") return fmt.Errorf("there is no function call here")
} }
// TODO(adonovan): issue an error if the call is "too far // TODO(adonovan): issue an error if the call is "too far
// away" from the current selection, as this most likely is // away" from the current selection, as this most likely is
@ -39,39 +71,41 @@ func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
// Reject type conversions. // Reject type conversions.
if qpos.info.Types[e.Fun].IsType() { if qpos.info.Types[e.Fun].IsType() {
return nil, fmt.Errorf("this is a type conversion, not a function call") return fmt.Errorf("this is a type conversion, not a function call")
} }
// Reject calls to built-ins. // Reject calls to built-ins.
if id, ok := unparen(e.Fun).(*ast.Ident); ok { if id, ok := unparen(e.Fun).(*ast.Ident); ok {
if b, ok := qpos.info.Uses[id].(*types.Builtin); ok { if b, ok := qpos.info.Uses[id].(*types.Builtin); ok {
return nil, fmt.Errorf("this is a call to the built-in '%s' operator", b.Name()) return fmt.Errorf("this is a call to the built-in '%s' operator", b.Name())
} }
} }
buildSSA(o) // Defer SSA construction till after errors are reported.
prog.BuildAll()
// Ascertain calling function and call site. // Ascertain calling function and call site.
callerFn := ssa.EnclosingFunction(pkg, qpos.path) callerFn := ssa.EnclosingFunction(pkg, qpos.path)
if callerFn == nil { if callerFn == nil {
return nil, fmt.Errorf("no SSA function built for this location (dead code?)") return fmt.Errorf("no SSA function built for this location (dead code?)")
} }
// Find the call site. // Find the call site.
site, err := findCallSite(callerFn, e.Lparen) site, err := findCallSite(callerFn, e.Lparen)
if err != nil { if err != nil {
return nil, err return err
} }
funcs, err := findCallees(o, site) funcs, err := findCallees(ptaConfig, site)
if err != nil { if err != nil {
return nil, err return err
} }
return &calleesResult{ q.result = &calleesResult{
site: site, site: site,
funcs: funcs, funcs: funcs,
}, nil }
return nil
} }
func findCallSite(fn *ssa.Function, lparen token.Pos) (ssa.CallInstruction, error) { func findCallSite(fn *ssa.Function, lparen token.Pos) (ssa.CallInstruction, error) {
@ -85,7 +119,7 @@ func findCallSite(fn *ssa.Function, lparen token.Pos) (ssa.CallInstruction, erro
return nil, fmt.Errorf("this call site is unreachable in this analysis") return nil, fmt.Errorf("this call site is unreachable in this analysis")
} }
func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) { func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) {
// Avoid running the pointer analysis for static calls. // Avoid running the pointer analysis for static calls.
if callee := site.Common().StaticCallee(); callee != nil { if callee := site.Common().StaticCallee(); callee != nil {
switch callee.String() { switch callee.String() {
@ -99,8 +133,8 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
} }
// Dynamic call: use pointer analysis. // Dynamic call: use pointer analysis.
o.ptaConfig.BuildCallGraph = true conf.BuildCallGraph = true
cg := ptrAnalysis(o).CallGraph cg := ptrAnalysis(conf).CallGraph
cg.DeleteSyntheticNodes() cg.DeleteSyntheticNodes()
// Find all call edges from the site. // Find all call edges from the site.

View File

@ -9,6 +9,7 @@ import (
"go/token" "go/token"
"golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"golang.org/x/tools/oracle/serial" "golang.org/x/tools/oracle/serial"
) )
@ -16,35 +17,67 @@ import (
// Callers reports the possible callers of the function // Callers reports the possible callers of the function
// immediately enclosing the specified source location. // immediately enclosing the specified source location.
// //
func callers(o *Oracle, qpos *QueryPos) (queryResult, error) { func callers(conf *Query) error {
pkg := o.prog.Package(qpos.info.Pkg) lconf := loader.Config{Build: conf.Build}
if pkg == nil {
return nil, fmt.Errorf("no SSA package") // Determine initial packages for PTA.
args, err := lconf.FromArgs(conf.Scope, true)
if err != nil {
return err
} }
if !ssa.HasEnclosingFunction(pkg, qpos.path) { if len(args) > 0 {
return nil, fmt.Errorf("this position is not inside a function") return fmt.Errorf("surplus arguments: %q", args)
} }
buildSSA(o) // Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
conf.Fset = lprog.Fset
qpos, err := parseQueryPos(lprog, conf.Pos, false)
if err != nil {
return err
}
prog := ssa.Create(lprog, 0)
ptaConfig, err := setupPTA(prog, lprog, conf.PTALog, conf.Reflection)
if err != nil {
return err
}
pkg := prog.Package(qpos.info.Pkg)
if pkg == nil {
return fmt.Errorf("no SSA package")
}
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
return fmt.Errorf("this position is not inside a function")
}
// Defer SSA construction till after errors are reported.
prog.BuildAll()
target := ssa.EnclosingFunction(pkg, qpos.path) target := ssa.EnclosingFunction(pkg, qpos.path)
if target == nil { if target == nil {
return nil, fmt.Errorf("no SSA function built for this location (dead code?)") return fmt.Errorf("no SSA function built for this location (dead code?)")
} }
// Run the pointer analysis, recording each // Run the pointer analysis, recording each
// call found to originate from target. // call found to originate from target.
o.ptaConfig.BuildCallGraph = true ptaConfig.BuildCallGraph = true
cg := ptrAnalysis(o).CallGraph cg := ptrAnalysis(ptaConfig).CallGraph
cg.DeleteSyntheticNodes() cg.DeleteSyntheticNodes()
edges := cg.CreateNode(target).In edges := cg.CreateNode(target).In
// TODO(adonovan): sort + dedup calls to ensure test determinism. // TODO(adonovan): sort + dedup calls to ensure test determinism.
return &callersResult{ conf.result = &callersResult{
target: target, target: target,
callgraph: cg, callgraph: cg,
edges: edges, edges: edges,
}, nil }
return nil
} }
type callersResult struct { type callersResult struct {

View File

@ -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
}

View File

@ -9,6 +9,7 @@ import (
"go/token" "go/token"
"golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"golang.org/x/tools/oracle/serial" "golang.org/x/tools/oracle/serial"
) )
@ -23,26 +24,57 @@ import (
// TODO(adonovan): permit user to specify a starting point other than // TODO(adonovan): permit user to specify a starting point other than
// the analysis root. // the analysis root.
// //
func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) { func callstack(conf *Query) error {
pkg := o.prog.Package(qpos.info.Pkg) fset := token.NewFileSet()
lconf := loader.Config{Fset: fset, Build: conf.Build}
// Determine initial packages for PTA.
args, err := lconf.FromArgs(conf.Scope, true)
if err != nil {
return err
}
if len(args) > 0 {
return fmt.Errorf("surplus arguments: %q", args)
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, conf.Pos, false)
if err != nil {
return err
}
prog := ssa.Create(lprog, 0)
ptaConfig, err := setupPTA(prog, lprog, conf.PTALog, conf.Reflection)
if err != nil {
return err
}
pkg := prog.Package(qpos.info.Pkg)
if pkg == nil { if pkg == nil {
return nil, fmt.Errorf("no SSA package") return fmt.Errorf("no SSA package")
} }
if !ssa.HasEnclosingFunction(pkg, qpos.path) { if !ssa.HasEnclosingFunction(pkg, qpos.path) {
return nil, fmt.Errorf("this position is not inside a function") return fmt.Errorf("this position is not inside a function")
} }
buildSSA(o) // Defer SSA construction till after errors are reported.
prog.BuildAll()
target := ssa.EnclosingFunction(pkg, qpos.path) target := ssa.EnclosingFunction(pkg, qpos.path)
if target == nil { if target == nil {
return nil, fmt.Errorf("no SSA function built for this location (dead code?)") return fmt.Errorf("no SSA function built for this location (dead code?)")
} }
// Run the pointer analysis and build the complete call graph. // Run the pointer analysis and build the complete call graph.
o.ptaConfig.BuildCallGraph = true ptaConfig.BuildCallGraph = true
cg := ptrAnalysis(o).CallGraph cg := ptrAnalysis(ptaConfig).CallGraph
cg.DeleteSyntheticNodes() cg.DeleteSyntheticNodes()
// Search for an arbitrary path from a root to the target function. // Search for an arbitrary path from a root to the target function.
@ -52,15 +84,17 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
callpath = callpath[1:] // remove synthetic edge from <root> callpath = callpath[1:] // remove synthetic edge from <root>
} }
return &callstackResult{ conf.Fset = fset
conf.result = &callstackResult{
qpos: qpos, qpos: qpos,
target: target, target: target,
callpath: callpath, callpath: callpath,
}, nil }
return nil
} }
type callstackResult struct { type callstackResult struct {
qpos *QueryPos qpos *queryPos
target *ssa.Function target *ssa.Function
callpath []*callgraph.Edge callpath []*callgraph.Edge
} }

View File

@ -9,6 +9,7 @@ import (
"go/ast" "go/ast"
"go/token" "go/token"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/types" "golang.org/x/tools/go/types"
"golang.org/x/tools/oracle/serial" "golang.org/x/tools/oracle/serial"
) )
@ -18,28 +19,48 @@ import (
// TODO(adonovan): opt: for intra-file references, the parser's // TODO(adonovan): opt: for intra-file references, the parser's
// resolution might be enough; we should start with that. // resolution might be enough; we should start with that.
// //
func definition(o *Oracle, qpos *QueryPos) (queryResult, error) { func definition(q *Query) error {
lconf := loader.Config{Build: q.Build}
allowErrors(&lconf)
if err := importQueryPackage(q.Pos, &lconf); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
q.Fset = lprog.Fset
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
id, _ := qpos.path[0].(*ast.Ident) id, _ := qpos.path[0].(*ast.Ident)
if id == nil { if id == nil {
return nil, fmt.Errorf("no identifier here") return fmt.Errorf("no identifier here")
} }
obj := qpos.info.ObjectOf(id) obj := qpos.info.ObjectOf(id)
if obj == nil { if obj == nil {
// Happens for y in "switch y := x.(type)", but I think that's all. // Happens for y in "switch y := x.(type)", but I think that's all.
return nil, fmt.Errorf("no object for identifier") return fmt.Errorf("no object for identifier")
} }
return &definitionResult{qpos, obj}, nil q.result = &definitionResult{qpos, obj}
return nil
} }
type definitionResult struct { type definitionResult struct {
qpos *QueryPos qpos *queryPos
obj types.Object // object it denotes obj types.Object // object it denotes
} }
func (r *definitionResult) display(printf printfFunc) { func (r *definitionResult) display(printf printfFunc) {
printf(r.obj, "defined here as %s", r.qpos.ObjectString(r.obj)) printf(r.obj, "defined here as %s", r.qpos.objectString(r.obj))
} }
func (r *definitionResult) toSerial(res *serial.Result, fset *token.FileSet) { func (r *definitionResult) toSerial(res *serial.Result, fset *token.FileSet) {

View File

@ -27,32 +27,52 @@ import (
// - the definition of its referent (for identifiers) [now redundant] // - the definition of its referent (for identifiers) [now redundant]
// - its type and method set (for an expression or type expression) // - its type and method set (for an expression or type expression)
// //
func describe(o *Oracle, qpos *QueryPos) (queryResult, error) { func describe(q *Query) error {
lconf := loader.Config{Build: q.Build}
allowErrors(&lconf)
if err := importQueryPackage(q.Pos, &lconf); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
q.Fset = lprog.Fset
qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
if err != nil {
return err
}
if false { // debugging if false { // debugging
fprintf(os.Stderr, o.fset, qpos.path[0], "you selected: %s %s", fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path)) astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
} }
path, action := findInterestingNode(qpos.info, qpos.path) path, action := findInterestingNode(qpos.info, qpos.path)
switch action { switch action {
case actionExpr: case actionExpr:
return describeValue(o, qpos, path) q.result, err = describeValue(qpos, path)
case actionType: case actionType:
return describeType(o, qpos, path) q.result, err = describeType(qpos, path)
case actionPackage: case actionPackage:
return describePackage(o, qpos, path) q.result, err = describePackage(qpos, path)
case actionStmt: case actionStmt:
return describeStmt(o, qpos, path) q.result, err = describeStmt(qpos, path)
case actionUnknown: case actionUnknown:
return &describeUnknownResult{path[0]}, nil q.result = &describeUnknownResult{path[0]}
default: default:
panic(action) // unreachable panic(action) // unreachable
} }
return err
} }
type describeUnknownResult struct { type describeUnknownResult struct {
@ -288,7 +308,7 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No
return nil, actionUnknown // unreachable return nil, actionUnknown // unreachable
} }
func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueResult, error) { func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
var expr ast.Expr var expr ast.Expr
var obj types.Object var obj types.Object
switch n := path[0].(type) { switch n := path[0].(type) {
@ -318,7 +338,7 @@ func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueRe
} }
type describeValueResult struct { type describeValueResult struct {
qpos *QueryPos qpos *queryPos
expr ast.Expr // query node expr ast.Expr // query node
typ types.Type // type of expression typ types.Type // type of expression
constVal exact.Value // value of expression, if constant constVal exact.Value // value of expression, if constant
@ -345,10 +365,10 @@ func (r *describeValueResult) display(printf printfFunc) {
if r.obj != nil { if r.obj != nil {
if r.obj.Pos() == r.expr.Pos() { if r.obj.Pos() == r.expr.Pos() {
// defining ident // defining ident
printf(r.expr, "definition of %s%s%s", prefix, r.qpos.ObjectString(r.obj), suffix) printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
} else { } else {
// referring ident // referring ident
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.ObjectString(r.obj), suffix) printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
if def := r.obj.Pos(); def != token.NoPos { if def := r.obj.Pos(); def != token.NoPos {
printf(def, "defined here") printf(def, "defined here")
} }
@ -360,7 +380,7 @@ func (r *describeValueResult) display(printf printfFunc) {
printf(r.expr, "%s%s", desc, suffix) printf(r.expr, "%s%s", desc, suffix)
} else { } else {
// non-constant expression // non-constant expression
printf(r.expr, "%s of type %s", desc, r.qpos.TypeString(r.typ)) printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
} }
} }
} }
@ -379,7 +399,7 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet)
Pos: fset.Position(r.expr.Pos()).String(), Pos: fset.Position(r.expr.Pos()).String(),
Detail: "value", Detail: "value",
Value: &serial.DescribeValue{ Value: &serial.DescribeValue{
Type: r.qpos.TypeString(r.typ), Type: r.qpos.typeString(r.typ),
Value: value, Value: value,
ObjPos: objpos, ObjPos: objpos,
}, },
@ -388,7 +408,7 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet)
// ---- TYPE ------------------------------------------------------------ // ---- TYPE ------------------------------------------------------------
func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResult, error) { func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
var description string var description string
var t types.Type var t types.Type
switch n := path[0].(type) { switch n := path[0].(type) {
@ -415,14 +435,12 @@ func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResu
return nil, fmt.Errorf("unexpected AST for type: %T", n) return nil, fmt.Errorf("unexpected AST for type: %T", n)
} }
description = description + "type " + qpos.TypeString(t) description = description + "type " + qpos.typeString(t)
// Show sizes for structs and named types (it's fairly obvious for others). // Show sizes for structs and named types (it's fairly obvious for others).
switch t.(type) { switch t.(type) {
case *types.Named, *types.Struct: case *types.Named, *types.Struct:
// TODO(adonovan): use o.imp.Config().TypeChecker.Sizes when szs := types.StdSizes{8, 8} // assume amd64
// we add the Config() method (needs some thought).
szs := types.StdSizes{8, 8}
description = fmt.Sprintf("%s (size %d, align %d)", description, description = fmt.Sprintf("%s (size %d, align %d)", description,
szs.Sizeof(t), szs.Alignof(t)) szs.Sizeof(t), szs.Alignof(t))
} }
@ -437,7 +455,7 @@ func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResu
} }
type describeTypeResult struct { type describeTypeResult struct {
qpos *QueryPos qpos *queryPos
node ast.Node node ast.Node
description string description string
typ types.Type typ types.Type
@ -449,7 +467,7 @@ func (r *describeTypeResult) display(printf printfFunc) {
// Show the underlying type for a reference to a named type. // Show the underlying type for a reference to a named type.
if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() { if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
printf(nt.Obj(), "defined as %s", r.qpos.TypeString(nt.Underlying())) printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying()))
} }
// Print the method set, if the type kind is capable of bearing methods. // Print the method set, if the type kind is capable of bearing methods.
@ -461,7 +479,7 @@ func (r *describeTypeResult) display(printf printfFunc) {
// TODO(adonovan): print these relative // TODO(adonovan): print these relative
// to the owning package, not the // to the owning package, not the
// query package. // query package.
printf(meth.Obj(), "\t%s", r.qpos.SelectionString(meth)) printf(meth.Obj(), "\t%s", r.qpos.selectionString(meth))
} }
} else { } else {
printf(r.node, "No methods.") printf(r.node, "No methods.")
@ -480,7 +498,7 @@ func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) {
Pos: fset.Position(r.node.Pos()).String(), Pos: fset.Position(r.node.Pos()).String(),
Detail: "type", Detail: "type",
Type: &serial.DescribeType{ Type: &serial.DescribeType{
Type: r.qpos.TypeString(r.typ), Type: r.qpos.typeString(r.typ),
NamePos: namePos, NamePos: namePos,
NameDef: nameDef, NameDef: nameDef,
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset), Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
@ -490,7 +508,7 @@ func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) {
// ---- PACKAGE ------------------------------------------------------------ // ---- PACKAGE ------------------------------------------------------------
func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePackageResult, error) { func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
var description string var description string
var pkg *types.Package var pkg *types.Package
switch n := path[0].(type) { switch n := path[0].(type) {
@ -542,7 +560,7 @@ func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePacka
} }
} }
return &describePackageResult{o.fset, path[0], description, pkg, members}, nil return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
} }
type describePackageResult struct { type describePackageResult struct {
@ -661,7 +679,7 @@ func tokenOf(o types.Object) string {
// ---- STATEMENT ------------------------------------------------------------ // ---- STATEMENT ------------------------------------------------------------
func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResult, error) { func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
var description string var description string
switch n := path[0].(type) { switch n := path[0].(type) {
case *ast.Ident: case *ast.Ident:
@ -675,7 +693,7 @@ func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResu
// Nothing much to say about statements. // Nothing much to say about statements.
description = astutil.NodeDescription(n) description = astutil.NodeDescription(n)
} }
return &describeStmtResult{o.fset, path[0], description}, nil return &describeStmtResult{qpos.fset, path[0], description}, nil
} }
type describeStmtResult struct { type describeStmtResult struct {

View File

@ -11,6 +11,7 @@ import (
"go/token" "go/token"
"sort" "sort"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/types" "golang.org/x/tools/go/types"
"golang.org/x/tools/oracle/serial" "golang.org/x/tools/oracle/serial"
) )
@ -28,7 +29,26 @@ import (
// these might be interesting. Perhaps group the results into three // these might be interesting. Perhaps group the results into three
// bands. // bands.
// //
func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) { func freevars(q *Query) error {
lconf := loader.Config{Build: q.Build}
allowErrors(&lconf)
if err := importQueryPackage(q.Pos, &lconf); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
q.Fset = lprog.Fset
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
file := qpos.path[len(qpos.path)-1] // the enclosing file file := qpos.path[len(qpos.path)-1] // the enclosing file
fileScope := qpos.info.Scopes[file] fileScope := qpos.info.Scopes[file]
pkgScope := fileScope.Parent() pkgScope := fileScope.Parent()
@ -118,7 +138,7 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
typ := qpos.info.TypeOf(n.(ast.Expr)) typ := qpos.info.TypeOf(n.(ast.Expr))
ref := freevarsRef{kind, printNode(o.fset, n), typ, obj} ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj}
refsMap[ref.ref] = ref refsMap[ref.ref] = ref
if prune { if prune {
@ -136,14 +156,15 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
sort.Sort(byRef(refs)) sort.Sort(byRef(refs))
return &freevarsResult{ q.result = &freevarsResult{
qpos: qpos, qpos: qpos,
refs: refs, refs: refs,
}, nil }
return nil
} }
type freevarsResult struct { type freevarsResult struct {
qpos *QueryPos qpos *queryPos
refs []freevarsRef refs []freevarsRef
} }

View File

@ -12,16 +12,37 @@ import (
"sort" "sort"
"strings" "strings"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/types" "golang.org/x/tools/go/types"
"golang.org/x/tools/oracle/serial" "golang.org/x/tools/oracle/serial"
) )
// Implements displays the "implements" relation as it pertains to the // Implements displays the "implements" relation as it pertains to the
// selected type. If the selection is a method, 'implements' displays // selected type within a single package.
// If the selection is a method, 'implements' displays
// the corresponding methods of the types that would have been reported // the corresponding methods of the types that would have been reported
// by an implements query on the receiver type. // by an implements query on the receiver type.
// //
func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { func implements(q *Query) error {
lconf := loader.Config{Build: q.Build}
allowErrors(&lconf)
if err := importQueryPackage(q.Pos, &lconf); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
q.Fset = lprog.Fset
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
// Find the selected type. // Find the selected type.
path, action := findInterestingNode(qpos.info, qpos.path) path, action := findInterestingNode(qpos.info, qpos.path)
@ -35,7 +56,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok { if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
recv := obj.Type().(*types.Signature).Recv() recv := obj.Type().(*types.Signature).Recv()
if recv == nil { if recv == nil {
return nil, fmt.Errorf("this function is not a method") return fmt.Errorf("this function is not a method")
} }
method = obj method = obj
T = recv.Type() T = recv.Type()
@ -45,7 +66,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
T = qpos.info.TypeOf(path[0].(ast.Expr)) T = qpos.info.TypeOf(path[0].(ast.Expr))
} }
if T == nil { if T == nil {
return nil, fmt.Errorf("no type or method here") return fmt.Errorf("no type or method here")
} }
// Find all named types, even local types (which can have // Find all named types, even local types (which can have
@ -55,7 +76,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
// i.e. don't reduceScope? // i.e. don't reduceScope?
// //
var allNamed []types.Type var allNamed []types.Type
for _, info := range o.typeInfo { for _, info := range lprog.AllPackages {
for _, obj := range info.Defs { for _, obj := range info.Defs {
if obj, ok := obj.(*types.TypeName); ok { if obj, ok := obj.(*types.TypeName); ok {
allNamed = append(allNamed, obj.Type()) allNamed = append(allNamed, obj.Type())
@ -135,11 +156,14 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
} }
return &implementsResult{qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod}, nil q.result = &implementsResult{
qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod,
}
return nil
} }
type implementsResult struct { type implementsResult struct {
qpos *QueryPos qpos *queryPos
t types.Type // queried type (not necessarily named) t types.Type // queried type (not necessarily named)
pos interface{} // pos of t (*types.Name or *QueryPos) pos interface{} // pos of t (*types.Name or *QueryPos)
@ -160,7 +184,7 @@ func (r *implementsResult) display(printf printfFunc) {
meth := func(sel *types.Selection) { meth := func(sel *types.Selection) {
if sel != nil { if sel != nil {
printf(sel.Obj(), "\t%s method (%s).%s", printf(sel.Obj(), "\t%s method (%s).%s",
relation, r.qpos.TypeString(sel.Recv()), sel.Obj().Name()) relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name())
} }
} }
@ -173,7 +197,7 @@ func (r *implementsResult) display(printf printfFunc) {
if r.method == nil { if r.method == nil {
printf(r.pos, "interface type %s", r.t) printf(r.pos, "interface type %s", r.t)
} else { } else {
printf(r.method, "abstract method %s", r.qpos.ObjectString(r.method)) printf(r.method, "abstract method %s", r.qpos.objectString(r.method))
} }
// Show concrete types (or methods) first; use two passes. // Show concrete types (or methods) first; use two passes.
@ -214,7 +238,7 @@ func (r *implementsResult) display(printf printfFunc) {
printf(r.pos, "%s type %s", typeKind(r.t), r.t) printf(r.pos, "%s type %s", typeKind(r.t), r.t)
} else { } else {
printf(r.method, "concrete method %s", printf(r.method, "concrete method %s",
r.qpos.ObjectString(r.method)) r.qpos.objectString(r.method))
} }
for i, super := range r.from { for i, super := range r.from {
if r.method == nil { if r.method == nil {
@ -231,7 +255,7 @@ func (r *implementsResult) display(printf printfFunc) {
} else { } else {
// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f. // TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
printf(r.method, "concrete method %s", printf(r.method, "concrete method %s",
r.qpos.ObjectString(r.method)) r.qpos.objectString(r.method))
} }
for i, psuper := range r.fromPtr { for i, psuper := range r.fromPtr {
@ -260,7 +284,7 @@ func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) {
} }
if r.method != nil { if r.method != nil {
res.Implements.Method = &serial.DescribeMethod{ res.Implements.Method = &serial.DescribeMethod{
Name: r.qpos.ObjectString(r.method), Name: r.qpos.objectString(r.method),
Pos: fset.Position(r.method.Pos()).String(), Pos: fset.Position(r.method.Pos()).String(),
} }
} }

View File

@ -19,42 +19,13 @@ package oracle // import "golang.org/x/tools/oracle"
// - show all places where an object of type T is created // - show all places where an object of type T is created
// (&T{}, var t T, new(T), new(struct{array [3]T}), etc. // (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
// ORACLE CONTROL FLOW
//
// The Oracle is somewhat convoluted due to the need to support two
// very different use-cases, "one-shot" and "long running", and to do
// so quickly.
//
// The cmd/oracle tool issues "one-shot" queries via the exported
// Query function, which creates an Oracle to answer a single query.
// newOracle consults the 'needs' flags of the query mode and the
// package containing the query to avoid doing more work than it needs
// (loading, parsing, type checking, SSA construction).
//
// The Pythia tool (github.com/fzipp/pythia) is an example of a "long
// running" tool. It calls New() and then loops, calling
// ParseQueryPos and (*Oracle).Query to handle each incoming HTTP
// query. Since New cannot see which queries will follow, it must
// load, parse, type-check and SSA-build the entire transitive closure
// of the analysis scope, retaining full debug information and all
// typed ASTs.
//
// TODO(adonovan): experiment with inverting the control flow by
// making each mode consist of two functions: a "one-shot setup"
// function and the existing "impl" function. The one-shot setup
// function would do all of the work of Query and newOracle,
// specialized to each mode, calling library utilities for the common
// things. This would give it more control over "scope reduction".
// Long running tools would not call the one-shot setup function but
// would have their own setup function equivalent to the existing
// 'needsAll' flow path.
import ( import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/build" "go/build"
"go/token" "go/token"
"io" "io"
"path/filepath"
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/loader" "golang.org/x/tools/go/loader"
@ -64,64 +35,6 @@ import (
"golang.org/x/tools/oracle/serial" "golang.org/x/tools/oracle/serial"
) )
// An Oracle holds the program state required for one or more queries.
type Oracle struct {
fset *token.FileSet // file set [all queries]
prog *ssa.Program // the SSA program [needSSA]
ptaConfig pointer.Config // pointer analysis configuration [needPTA]
typeInfo map[*types.Package]*loader.PackageInfo // type info for all ASTs in the program [needRetainTypeInfo]
}
// A set of bits indicating the analytical requirements of each mode.
//
// Typed ASTs for the whole program are always constructed
// transiently; they are retained only for the queried package unless
// needRetainTypeInfo is set.
const (
needPos = 1 << iota // needs a position
needExactPos // needs an exact AST selection; implies needPos
needRetainTypeInfo // needs to retain type info for all ASTs in the program
needSSA // needs ssa.Packages for whole program
needSSADebug // needs debug info for ssa.Packages
needPTA = needSSA // needs pointer analysis
needAll = -1 // needs everything (e.g. a sequence of queries)
)
type modeInfo struct {
name string
needs int
impl func(*Oracle, *QueryPos) (queryResult, error)
}
var modes = []*modeInfo{
// Pointer analyses, whole program:
{"callees", needPTA | needExactPos, callees},
{"callers", needPTA | needPos, callers},
{"callgraph", needPTA, doCallgraph},
{"callstack", needPTA | needPos, callstack},
{"peers", needPTA | needSSADebug | needPos, peers},
{"pointsto", needPTA | needSSADebug | needExactPos, pointsto},
{"whicherrs", needPTA | needSSADebug | needExactPos, whicherrs},
// Type-based, modular analyses:
{"definition", needPos, definition},
{"describe", needExactPos, describe},
{"freevars", needPos, freevars},
// Type-based, whole-program analyses:
{"implements", needRetainTypeInfo | needPos, implements},
{"referrers", needRetainTypeInfo | needPos, referrers},
}
func findMode(mode string) *modeInfo {
for _, m := range modes {
if m.name == mode {
return m
}
}
return nil
}
type printfFunc func(pos interface{}, format string, args ...interface{}) type printfFunc func(pos interface{}, format string, args ...interface{})
// queryResult is the interface of each query-specific result type. // queryResult is the interface of each query-specific result type.
@ -133,9 +46,8 @@ type queryResult interface {
// A QueryPos represents the position provided as input to a query: // A QueryPos represents the position provided as input to a query:
// a textual extent in the program's source code, the AST node it // a textual extent in the program's source code, the AST node it
// corresponds to, and the package to which it belongs. // corresponds to, and the package to which it belongs.
// Instances are created by ParseQueryPos. // Instances are created by parseQueryPos.
// type queryPos struct {
type QueryPos struct {
fset *token.FileSet fset *token.FileSet
start, end token.Pos // source extent of query start, end token.Pos // source extent of query
path []ast.Node // AST path from query node to root of ast.File path []ast.Node // AST path from query node to root of ast.File
@ -144,236 +56,97 @@ type QueryPos struct {
} }
// TypeString prints type T relative to the query position. // TypeString prints type T relative to the query position.
func (qpos *QueryPos) TypeString(T types.Type) string { func (qpos *queryPos) typeString(T types.Type) string {
return types.TypeString(qpos.info.Pkg, T) return types.TypeString(qpos.info.Pkg, T)
} }
// ObjectString prints object obj relative to the query position. // ObjectString prints object obj relative to the query position.
func (qpos *QueryPos) ObjectString(obj types.Object) string { func (qpos *queryPos) objectString(obj types.Object) string {
return types.ObjectString(qpos.info.Pkg, obj) return types.ObjectString(qpos.info.Pkg, obj)
} }
// SelectionString prints selection sel relative to the query position. // SelectionString prints selection sel relative to the query position.
func (qpos *QueryPos) SelectionString(sel *types.Selection) string { func (qpos *queryPos) selectionString(sel *types.Selection) string {
return types.SelectionString(qpos.info.Pkg, sel) return types.SelectionString(qpos.info.Pkg, sel)
} }
// A Result encapsulates the result of an oracle.Query. // A Query specifies a single oracle query.
type Result struct { type Query struct {
fset *token.FileSet Mode string // query mode ("callers", etc)
q queryResult // the query-specific result Pos string // query position
mode string // query mode Build *build.Context // package loading configuration
warnings []pointer.Warning // pointer analysis warnings (TODO(adonovan): fix: never populated!)
// pointer analysis options
Scope []string // main package in (*loader.Config).FromArgs syntax
PTALog io.Writer // (optional) pointer-analysis log file
Reflection bool // model reflection soundly (currently slow).
// Populated during Run()
Fset *token.FileSet
result queryResult
} }
// Serial returns an instance of serial.Result, which implements the // Serial returns an instance of serial.Result, which implements the
// {xml,json}.Marshaler interfaces so that query results can be // {xml,json}.Marshaler interfaces so that query results can be
// serialized as JSON or XML. // serialized as JSON or XML.
// //
func (res *Result) Serial() *serial.Result { func (q *Query) Serial() *serial.Result {
resj := &serial.Result{Mode: res.mode} resj := &serial.Result{Mode: q.Mode}
res.q.toSerial(resj, res.fset) q.result.toSerial(resj, q.Fset)
for _, w := range res.warnings {
resj.Warnings = append(resj.Warnings, serial.PTAWarning{
Pos: res.fset.Position(w.Pos).String(),
Message: w.Message,
})
}
return resj return resj
} }
// Query runs a single oracle query. // WriteTo writes the oracle query result res to out in a compiler diagnostic format.
// func (q *Query) WriteTo(out io.Writer) {
// args specify the main package in (*loader.Config).FromArgs syntax. printf := func(pos interface{}, format string, args ...interface{}) {
// mode is the query mode ("callers", etc). fprintf(out, q.Fset, pos, format, args...)
// ptalog is the (optional) pointer-analysis log file. }
// buildContext is the go/build configuration for locating packages. q.result.display(printf)
// 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)
} }
minfo := findMode(mode) // Run runs an oracle query and populates its Fset and Result.
if minfo == nil { func Run(conf *Query) error {
return nil, fmt.Errorf("invalid mode type: %q", mode) switch conf.Mode {
} case "callees":
return callees(conf)
conf := loader.Config{Build: buildContext} case "callers":
return callers(conf)
// TODO(adonovan): tolerate type errors if we don't need SSA form. case "callstack":
// First we'll need ot audit the non-SSA modes for robustness return callstack(conf)
// in the face of type errors. case "peers":
// if minfo.needs&needSSA == 0 { return peers(conf)
// conf.AllowErrors = true case "pointsto":
// } return pointsto(conf)
case "whicherrs":
// Determine initial packages. return whicherrs(conf)
args, err := conf.FromArgs(args, true) case "definition":
if err != nil { return definition(conf)
return nil, err case "describe":
} return describe(conf)
if len(args) > 0 { case "freevars":
return nil, fmt.Errorf("surplus arguments: %q", args) return freevars(conf)
} case "implements":
return implements(conf)
// For queries needing only a single typed package, case "referrers":
// reduce the analysis scope to that package. return referrers(conf)
if minfo.needs&(needSSA|needRetainTypeInfo) == 0 { case "what":
reduceScope(pos, &conf) return what(conf)
} default:
return fmt.Errorf("invalid mode: %q", conf.Mode)
// TODO(adonovan): report type errors to the user via Serial
// types, not stderr?
// conf.TypeChecker.Error = func(err error) {
// E := err.(types.Error)
// fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg)
// }
// Load/parse/type-check the program.
iprog, err := conf.Load()
if err != nil {
return nil, err
}
o, err := newOracle(iprog, ptalog, minfo.needs, reflection)
if err != nil {
return nil, err
}
qpos, err := ParseQueryPos(iprog, pos, minfo.needs&needExactPos != 0)
if err != nil && minfo.needs&(needPos|needExactPos) != 0 {
return nil, err
}
// SSA is built and we have the QueryPos.
// Release the other ASTs and type info to the GC.
iprog = nil
return o.query(minfo, qpos)
}
// reduceScope is called for one-shot queries that need only a single
// 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) {
fqpos, err := fastQueryPos(pos)
if err != nil {
return // bad query
}
// TODO(adonovan): fix: this gives the wrong results for files
// in non-importable packages such as tests and ad-hoc packages
// specified as a list of files (incl. the oracle's tests).
_, importPath, err := guessImportPath(fqpos.fset.File(fqpos.start).Name(), conf.Build)
if err != nil {
return // can't find GOPATH dir
}
if importPath == "" {
return
}
// Check that it's possible to load the queried package.
// (e.g. oracle tests contain different 'package' decls in same dir.)
// Keep consistent with logic in loader/util.go!
cfg2 := *conf.Build
cfg2.CgoEnabled = false
bp, err := cfg2.Import(importPath, "", 0)
if err != nil {
return // no files for package
}
// Check that the queried file appears in the package:
// it might be a '// +build ignore' from an ad-hoc main
// package, e.g. $GOROOT/src/net/http/triv.go.
if !pkgContainsFile(bp, fqpos.fset.File(fqpos.start).Name()) {
return // not found
}
conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
// Ignore packages specified on command line.
conf.CreatePkgs = nil
conf.ImportPkgs = nil
// Instead load just the one containing the query position
// (and possibly its corresponding tests/production code).
// TODO(adonovan): set 'augment' based on which file list
// contains
conf.ImportWithTests(importPath)
}
func pkgContainsFile(bp *build.Package, filename string) bool {
for _, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
for _, file := range files {
if sameFile(file, filename) {
return true
} }
} }
}
return false
}
// New constructs a new Oracle that can be used for a sequence of queries. // Create a pointer.Config whose scope is the initial packages of lprog
// // and their dependencies.
// iprog specifies the program to analyze. func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) {
// ptalog is the (optional) pointer-analysis log file. // TODO(adonovan): the body of this function is essentially
// reflection determines whether to model reflection soundly (currently slow). // duplicated in all go/pointer clients. Refactor.
//
func New(iprog *loader.Program, ptalog io.Writer, reflection bool) (*Oracle, error) {
return newOracle(iprog, ptalog, needAll, reflection)
}
func newOracle(iprog *loader.Program, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
o := &Oracle{fset: iprog.Fset}
// Retain type info for all ASTs in the program.
if needs&needRetainTypeInfo != 0 {
o.typeInfo = iprog.AllPackages
}
// Create SSA package for the initial packages and their dependencies.
if needs&needSSA != 0 {
var mode ssa.BuilderMode
if needs&needSSADebug != 0 {
mode |= ssa.GlobalDebug
}
prog := ssa.Create(iprog, mode)
// For each initial package (specified on the command line), // For each initial package (specified on the command line),
// if it has a main function, analyze that, // if it has a main function, analyze that,
// otherwise analyze its tests, if any. // otherwise analyze its tests, if any.
var testPkgs, mains []*ssa.Package var testPkgs, mains []*ssa.Package
for _, info := range iprog.InitialPackages() { for _, info := range lprog.InitialPackages() {
initialPkg := prog.Package(info.Pkg) initialPkg := prog.Package(info.Pkg)
// Add package to the pointer analysis scope. // Add package to the pointer analysis scope.
@ -391,98 +164,111 @@ func newOracle(iprog *loader.Program, ptalog io.Writer, needs int, reflection bo
if mains == nil { if mains == nil {
return nil, fmt.Errorf("analysis scope has no main and no tests") return nil, fmt.Errorf("analysis scope has no main and no tests")
} }
o.ptaConfig.Log = ptalog return &pointer.Config{
o.ptaConfig.Reflection = reflection Log: ptaLog,
o.ptaConfig.Mains = mains Reflection: reflection,
Mains: mains,
o.prog = prog }, nil
} }
return o, nil // importQueryPackage finds the package P containing the
} // query position and tells conf to import it.
func importQueryPackage(pos string, conf *loader.Config) error {
// Query runs the query of the specified mode and selection. fqpos, err := fastQueryPos(pos)
//
// 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 { if err != nil {
return nil, err return err // bad query
} }
return res, nil filename := fqpos.fset.File(fqpos.start).Name()
// This will not work for ad-hoc packages
// such as $GOROOT/src/net/http/triv.go.
// TODO(adonovan): ensure we report a clear error.
_, importPath, err := guessImportPath(filename, conf.Build)
if err != nil {
return err // can't find GOPATH dir
}
if importPath == "" {
return fmt.Errorf("can't guess import path from %s", filename)
} }
// ParseQueryPos parses the source query position pos. // Check that it's possible to load the queried package.
// (e.g. oracle tests contain different 'package' decls in same dir.)
// Keep consistent with logic in loader/util.go!
cfg2 := *conf.Build
cfg2.CgoEnabled = false
bp, err := cfg2.Import(importPath, "", 0)
if err != nil {
return err // no files for package
}
switch pkgContainsFile(bp, filename) {
case 'T':
conf.ImportWithTests(importPath)
case 'X':
conf.ImportWithTests(importPath)
importPath += "_test" // for TypeCheckFuncBodies
case 'G':
conf.Import(importPath)
default:
return fmt.Errorf("package %q doesn't contain file %s",
importPath, filename)
}
conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
return nil
}
// pkgContainsFile reports whether file was among the packages Go
// files, Test files, eXternal test files, or not found.
func pkgContainsFile(bp *build.Package, filename string) byte {
for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
for _, file := range files {
if sameFile(filepath.Join(bp.Dir, file), filename) {
return "GTX"[i]
}
}
}
return 0 // not found
}
// ParseQueryPos parses the source query position pos and returns the
// AST node of the loaded program lprog that it identifies.
// If needExact, it must identify a single AST subtree; // If needExact, it must identify a single AST subtree;
// this is appropriate for queries that allow fairly arbitrary syntax, // this is appropriate for queries that allow fairly arbitrary syntax,
// e.g. "describe". // e.g. "describe".
// //
func ParseQueryPos(iprog *loader.Program, posFlag string, needExact bool) (*QueryPos, error) { func parseQueryPos(lprog *loader.Program, posFlag string, needExact bool) (*queryPos, error) {
filename, startOffset, endOffset, err := parsePosFlag(posFlag) filename, startOffset, endOffset, err := parsePosFlag(posFlag)
if err != nil { if err != nil {
return nil, err return nil, err
} }
start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset) start, end, err := findQueryPos(lprog.Fset, filename, startOffset, endOffset)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info, path, exact := iprog.PathEnclosingInterval(start, end) info, path, exact := lprog.PathEnclosingInterval(start, end)
if path == nil { if path == nil {
return nil, fmt.Errorf("no syntax here") return nil, fmt.Errorf("no syntax here")
} }
if needExact && !exact { if needExact && !exact {
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
} }
return &QueryPos{iprog.Fset, start, end, path, exact, info}, nil return &queryPos{lprog.Fset, start, end, path, exact, info}, nil
}
// WriteTo writes the oracle query result res to out in a compiler diagnostic format.
func (res *Result) WriteTo(out io.Writer) {
printf := func(pos interface{}, format string, args ...interface{}) {
fprintf(out, res.fset, pos, format, args...)
}
res.q.display(printf)
// Print warnings after the main output.
if res.warnings != nil {
fmt.Fprintln(out, "\nPointer analysis warnings:")
for _, w := range res.warnings {
printf(w.Pos, "warning: "+w.Message)
}
}
} }
// ---------- Utilities ---------- // ---------- Utilities ----------
// buildSSA constructs the SSA representation of Go-source function bodies. // allowErrors causes type errors to be silently ignored.
// Not needed in simpler modes, e.g. freevars. // (Not suitable if SSA construction follows.)
// func allowErrors(lconf *loader.Config) {
func buildSSA(o *Oracle) { lconf.AllowErrors = true
o.prog.BuildAll() lconf.TypeChecker.Error = func(err error) {}
} }
// ptrAnalysis runs the pointer analysis and returns its result. // ptrAnalysis runs the pointer analysis and returns its result.
func ptrAnalysis(o *Oracle) *pointer.Result { func ptrAnalysis(conf *pointer.Config) *pointer.Result {
result, err := pointer.Analyze(&o.ptaConfig) result, err := pointer.Analyze(conf)
if err != nil { if err != nil {
panic(err) // pointer analysis internal error panic(err) // pointer analysis internal error
} }
@ -528,7 +314,7 @@ func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, a
}: }:
start = pos.Pos() start = pos.Pos()
end = start end = start
case *QueryPos: case *queryPos:
start = pos.start start = pos.start
end = pos.end end = pos.end
case nil: case nil:

View File

@ -44,7 +44,6 @@ import (
"strings" "strings"
"testing" "testing"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/oracle" "golang.org/x/tools/oracle"
) )
@ -151,9 +150,9 @@ func parseQueries(t *testing.T, filename string) []*query {
} }
// WriteResult writes res (-format=plain) to w, stripping file locations. // WriteResult writes res (-format=plain) to w, stripping file locations.
func WriteResult(w io.Writer, res *oracle.Result) { func WriteResult(w io.Writer, q *oracle.Query) {
capture := new(bytes.Buffer) // capture standard output capture := new(bytes.Buffer) // capture standard output
res.WriteTo(capture) q.WriteTo(capture)
for _, line := range strings.Split(capture.String(), "\n") { for _, line := range strings.Split(capture.String(), "\n") {
// Remove a "file:line: " prefix. // Remove a "file:line: " prefix.
if i := strings.Index(line, ": "); i >= 0 { if i := strings.Index(line, ": "); i >= 0 {
@ -170,28 +169,30 @@ func doQuery(out io.Writer, q *query, useJson bool) {
var buildContext = build.Default var buildContext = build.Default
buildContext.GOPATH = "testdata" buildContext.GOPATH = "testdata"
res, err := oracle.Query([]string{q.filename}, query := oracle.Query{
q.verb, Mode: q.verb,
q.queryPos, Pos: q.queryPos,
nil, // ptalog, Build: &buildContext,
&buildContext, Scope: []string{q.filename},
true) // reflection Reflection: true,
if err != nil { }
if err := oracle.Run(&query); err != nil {
fmt.Fprintf(out, "\nError: %s\n", err) fmt.Fprintf(out, "\nError: %s\n", err)
return return
} }
if useJson { if useJson {
// JSON output // JSON output
b, err := json.MarshalIndent(res.Serial(), "", "\t") b, err := json.MarshalIndent(query.Serial(), "", "\t")
if err != nil { if err != nil {
fmt.Fprintf(out, "JSON error: %s\n", err.Error()) fmt.Fprintf(out, "JSON error: %s\n", err.Error())
return return
} }
out.Write(b) out.Write(b)
fmt.Fprintln(out)
} else { } else {
// "plain" (compiler diagnostic format) output // "plain" (compiler diagnostic format) output
WriteResult(out, res) WriteResult(out, &query)
} }
} }
@ -202,32 +203,29 @@ func TestOracle(t *testing.T) {
} }
for _, filename := range []string{ for _, filename := range []string{
"testdata/src/main/calls.go", "testdata/src/calls/main.go",
"testdata/src/main/callgraph.go", "testdata/src/describe/main.go",
"testdata/src/main/callgraph2.go", "testdata/src/freevars/main.go",
"testdata/src/main/describe.go", "testdata/src/implements/main.go",
"testdata/src/main/freevars.go", "testdata/src/implements-methods/main.go",
"testdata/src/main/implements.go", "testdata/src/imports/main.go",
"testdata/src/main/implements-methods.go", "testdata/src/peers/main.go",
"testdata/src/main/imports.go", "testdata/src/pointsto/main.go",
"testdata/src/main/peers.go", "testdata/src/reflection/main.go",
"testdata/src/main/pointsto.go", "testdata/src/what/main.go",
"testdata/src/main/reflection.go", "testdata/src/whicherrs/main.go",
"testdata/src/main/what.go",
"testdata/src/main/whicherrs.go",
// JSON: // JSON:
// TODO(adonovan): most of these are very similar; combine them. // TODO(adonovan): most of these are very similar; combine them.
"testdata/src/main/callgraph-json.go", "testdata/src/calls-json/main.go",
"testdata/src/main/calls-json.go", "testdata/src/peers-json/main.go",
"testdata/src/main/peers-json.go", "testdata/src/describe-json/main.go",
"testdata/src/main/describe-json.go", "testdata/src/implements-json/main.go",
"testdata/src/main/implements-json.go", "testdata/src/implements-methods-json/main.go",
"testdata/src/main/implements-methods-json.go", "testdata/src/pointsto-json/main.go",
"testdata/src/main/pointsto-json.go", "testdata/src/referrers-json/main.go",
"testdata/src/main/referrers-json.go", "testdata/src/what-json/main.go",
"testdata/src/main/what-json.go",
} { } {
useJson := strings.HasSuffix(filename, "-json.go") useJson := strings.Contains(filename, "-json/")
queries := parseQueries(t, filename) queries := parseQueries(t, filename)
golden := filename + "lden" golden := filename + "lden"
got := filename + "t" got := filename + "t"
@ -269,54 +267,3 @@ func TestOracle(t *testing.T) {
} }
} }
} }
func TestMultipleQueries(t *testing.T) {
// Loader
var buildContext = build.Default
buildContext.GOPATH = "testdata"
conf := loader.Config{Build: &buildContext}
filename := "testdata/src/main/multi.go"
conf.CreateFromFilenames("", filename)
iprog, err := conf.Load()
if err != nil {
t.Fatalf("Load failed: %s", err)
}
// Oracle
o, err := oracle.New(iprog, nil, true)
if err != nil {
t.Fatalf("oracle.New failed: %s", err)
}
// QueryPos
pos := filename + ":#54,#58"
qpos, err := oracle.ParseQueryPos(iprog, pos, true)
if err != nil {
t.Fatalf("oracle.ParseQueryPos(%q) failed: %s", pos, err)
}
// SSA is built and we have the QueryPos.
// Release the other ASTs and type info to the GC.
iprog = nil
// Run different query modes on same scope and selection.
out := new(bytes.Buffer)
for _, mode := range [...]string{"callers", "describe", "freevars"} {
res, err := o.Query(mode, qpos)
if err != nil {
t.Errorf("(*oracle.Oracle).Query(%q) failed: %s", pos, err)
}
WriteResult(out, res)
}
want := `multi.f is called from these 1 sites:
static function call from multi.main
function call (or conversion) of type ()
Free identifiers:
var x int
`
if got := out.String(); got != want {
t.Errorf("Query output differs; want <<%s>>, got <<%s>>\n", want, got)
}
}

View File

@ -10,6 +10,7 @@ import (
"go/token" "go/token"
"sort" "sort"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/go/types" "golang.org/x/tools/go/types"
@ -22,20 +23,51 @@ import (
// TODO(adonovan): support reflect.{Select,Recv,Send,Close}. // TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv), // TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
// or the implicit receive in "for v := range ch". // or the implicit receive in "for v := range ch".
func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { func peers(q *Query) error {
opPos := findOp(qpos) lconf := loader.Config{Build: q.Build}
if opPos == token.NoPos {
return nil, fmt.Errorf("there is no channel operation here") // Determine initial packages for PTA.
args, err := lconf.FromArgs(q.Scope, true)
if err != nil {
return err
}
if len(args) > 0 {
return fmt.Errorf("surplus arguments: %q", args)
} }
buildSSA(o) // Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
q.Fset = lprog.Fset
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
prog := ssa.Create(lprog, ssa.GlobalDebug)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
opPos := findOp(qpos)
if opPos == token.NoPos {
return fmt.Errorf("there is no channel operation here")
}
// Defer SSA construction till after errors are reported.
prog.BuildAll()
var queryOp chanOp // the originating send or receive operation var queryOp chanOp // the originating send or receive operation
var ops []chanOp // all sends/receives of opposite direction var ops []chanOp // all sends/receives of opposite direction
// Look at all channel operations in the whole ssa.Program. // Look at all channel operations in the whole ssa.Program.
// Build a list of those of same type as the query. // Build a list of those of same type as the query.
allFuncs := ssautil.AllFunctions(o.prog) allFuncs := ssautil.AllFunctions(prog)
for fn := range allFuncs { for fn := range allFuncs {
for _, b := range fn.Blocks { for _, b := range fn.Blocks {
for _, instr := range b.Instrs { for _, instr := range b.Instrs {
@ -49,7 +81,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
} }
if queryOp.ch == nil { if queryOp.ch == nil {
return nil, fmt.Errorf("ssa.Instruction for send/receive not found") return fmt.Errorf("ssa.Instruction for send/receive not found")
} }
// Discard operations of wrong channel element type. // Discard operations of wrong channel element type.
@ -58,11 +90,11 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
// ignore both directionality and type names. // ignore both directionality and type names.
queryType := queryOp.ch.Type() queryType := queryOp.ch.Type()
queryElemType := queryType.Underlying().(*types.Chan).Elem() queryElemType := queryType.Underlying().(*types.Chan).Elem()
o.ptaConfig.AddQuery(queryOp.ch) ptaConfig.AddQuery(queryOp.ch)
i := 0 i := 0
for _, op := range ops { for _, op := range ops {
if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) { if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
o.ptaConfig.AddQuery(op.ch) ptaConfig.AddQuery(op.ch)
ops[i] = op ops[i] = op
i++ i++
} }
@ -70,7 +102,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
ops = ops[:i] ops = ops[:i]
// Run the pointer analysis. // Run the pointer analysis.
ptares := ptrAnalysis(o) ptares := ptrAnalysis(ptaConfig)
// Find the points-to set. // Find the points-to set.
queryChanPtr := ptares.Queries[queryOp.ch] queryChanPtr := ptares.Queries[queryOp.ch]
@ -100,14 +132,15 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
sort.Sort(byPos(receives)) sort.Sort(byPos(receives))
sort.Sort(byPos(closes)) sort.Sort(byPos(closes))
return &peersResult{ q.result = &peersResult{
queryPos: opPos, queryPos: opPos,
queryType: queryType, queryType: queryType,
makes: makes, makes: makes,
sends: sends, sends: sends,
receives: receives, receives: receives,
closes: closes, closes: closes,
}, nil }
return nil
} }
// findOp returns the position of the enclosing send/receive/close op. // findOp returns the position of the enclosing send/receive/close op.
@ -115,7 +148,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
// for close operations, it's the Lparen of the function call. // for close operations, it's the Lparen of the function call.
// //
// TODO(adonovan): handle implicit receive operations from 'for...range chan' statements. // TODO(adonovan): handle implicit receive operations from 'for...range chan' statements.
func findOp(qpos *QueryPos) token.Pos { func findOp(qpos *queryPos) token.Pos {
for _, n := range qpos.path { for _, n := range qpos.path {
switch n := n.(type) { switch n := n.(type) {
case *ast.UnaryExpr: case *ast.UnaryExpr:

View File

@ -25,10 +25,40 @@ import (
// //
// All printed sets are sorted to ensure determinism. // All printed sets are sorted to ensure determinism.
// //
func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) { func pointsto(q *Query) error {
lconf := loader.Config{Build: q.Build}
// Determine initial packages for PTA.
args, err := lconf.FromArgs(q.Scope, true)
if err != nil {
return err
}
if len(args) > 0 {
return fmt.Errorf("surplus arguments: %q", args)
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
q.Fset = lprog.Fset
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
if err != nil {
return err
}
prog := ssa.Create(lprog, ssa.GlobalDebug)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
path, action := findInterestingNode(qpos.info, qpos.path) path, action := findInterestingNode(qpos.info, qpos.path)
if action != actionExpr { if action != actionExpr {
return nil, fmt.Errorf("pointer analysis wants an expression; got %s", return fmt.Errorf("pointer analysis wants an expression; got %s",
astutil.NodeDescription(qpos.path[0])) astutil.NodeDescription(qpos.path[0]))
} }
@ -37,7 +67,7 @@ func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) {
switch n := path[0].(type) { switch n := path[0].(type) {
case *ast.ValueSpec: case *ast.ValueSpec:
// ambiguous ValueSpec containing multiple names // ambiguous ValueSpec containing multiple names
return nil, fmt.Errorf("multiple value specification") return fmt.Errorf("multiple value specification")
case *ast.Ident: case *ast.Ident:
obj = qpos.info.ObjectOf(n) obj = qpos.info.ObjectOf(n)
expr = n expr = n
@ -45,41 +75,44 @@ func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) {
expr = n expr = n
default: default:
// TODO(adonovan): is this reachable? // TODO(adonovan): is this reachable?
return nil, fmt.Errorf("unexpected AST for expr: %T", n) return fmt.Errorf("unexpected AST for expr: %T", n)
} }
// Reject non-pointerlike types (includes all constants---except nil). // Reject non-pointerlike types (includes all constants---except nil).
// TODO(adonovan): reject nil too. // TODO(adonovan): reject nil too.
typ := qpos.info.TypeOf(expr) typ := qpos.info.TypeOf(expr)
if !pointer.CanPoint(typ) { if !pointer.CanPoint(typ) {
return nil, fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ) return fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ)
} }
// Determine the ssa.Value for the expression. // Determine the ssa.Value for the expression.
var value ssa.Value var value ssa.Value
var isAddr bool var isAddr bool
var err error
if obj != nil { if obj != nil {
// def/ref of func/var object // def/ref of func/var object
value, isAddr, err = ssaValueForIdent(o.prog, qpos.info, obj, path) value, isAddr, err = ssaValueForIdent(prog, qpos.info, obj, path)
} else { } else {
value, isAddr, err = ssaValueForExpr(o.prog, qpos.info, path) value, isAddr, err = ssaValueForExpr(prog, qpos.info, path)
} }
if err != nil { if err != nil {
return nil, err // e.g. trivially dead code return err // e.g. trivially dead code
} }
// Defer SSA construction till after errors are reported.
prog.BuildAll()
// Run the pointer analysis. // Run the pointer analysis.
ptrs, err := runPTA(o, value, isAddr) ptrs, err := runPTA(ptaConfig, value, isAddr)
if err != nil { if err != nil {
return nil, err // e.g. analytically unreachable return err // e.g. analytically unreachable
} }
return &pointstoResult{ q.result = &pointstoResult{
qpos: qpos, qpos: qpos,
typ: typ, typ: typ,
ptrs: ptrs, ptrs: ptrs,
}, nil }
return nil
} }
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path // ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
@ -129,17 +162,15 @@ func ssaValueForExpr(prog *ssa.Program, qinfo *loader.PackageInfo, path []ast.No
} }
// runPTA runs the pointer analysis of the selected SSA value or address. // runPTA runs the pointer analysis of the selected SSA value or address.
func runPTA(o *Oracle, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) { func runPTA(conf *pointer.Config, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
buildSSA(o)
T := v.Type() T := v.Type()
if isAddr { if isAddr {
o.ptaConfig.AddIndirectQuery(v) conf.AddIndirectQuery(v)
T = deref(T) T = deref(T)
} else { } else {
o.ptaConfig.AddQuery(v) conf.AddQuery(v)
} }
ptares := ptrAnalysis(o) ptares := ptrAnalysis(conf)
var ptr pointer.Pointer var ptr pointer.Pointer
if isAddr { if isAddr {
@ -177,7 +208,7 @@ type pointerResult struct {
} }
type pointstoResult struct { type pointstoResult struct {
qpos *QueryPos qpos *queryPos
typ types.Type // type of expression typ types.Type // type of expression
ptrs []pointerResult // pointer info (typ is concrete => len==1) ptrs []pointerResult // pointer info (typ is concrete => len==1)
} }
@ -188,17 +219,17 @@ func (r *pointstoResult) display(printf printfFunc) {
// reflect.Value expression. // reflect.Value expression.
if len(r.ptrs) > 0 { if len(r.ptrs) > 0 {
printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.TypeString(r.typ)) printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.typeString(r.typ))
for _, ptr := range r.ptrs { for _, ptr := range r.ptrs {
var obj types.Object var obj types.Object
if nt, ok := deref(ptr.typ).(*types.Named); ok { if nt, ok := deref(ptr.typ).(*types.Named); ok {
obj = nt.Obj() obj = nt.Obj()
} }
if len(ptr.labels) > 0 { if len(ptr.labels) > 0 {
printf(obj, "\t%s, may point to:", r.qpos.TypeString(ptr.typ)) printf(obj, "\t%s, may point to:", r.qpos.typeString(ptr.typ))
printLabels(printf, ptr.labels, "\t\t") printLabels(printf, ptr.labels, "\t\t")
} else { } else {
printf(obj, "\t%s", r.qpos.TypeString(ptr.typ)) printf(obj, "\t%s", r.qpos.typeString(ptr.typ))
} }
} }
} else { } else {
@ -208,11 +239,11 @@ func (r *pointstoResult) display(printf printfFunc) {
// Show labels for other expressions. // Show labels for other expressions.
if ptr := r.ptrs[0]; len(ptr.labels) > 0 { if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
printf(r.qpos, "this %s may point to these objects:", printf(r.qpos, "this %s may point to these objects:",
r.qpos.TypeString(r.typ)) r.qpos.typeString(r.typ))
printLabels(printf, ptr.labels, "\t") printLabels(printf, ptr.labels, "\t")
} else { } else {
printf(r.qpos, "this %s may not point to anything.", printf(r.qpos, "this %s may not point to anything.",
r.qpos.TypeString(r.typ)) r.qpos.typeString(r.typ))
} }
} }
} }
@ -232,7 +263,7 @@ func (r *pointstoResult) toSerial(res *serial.Result, fset *token.FileSet) {
}) })
} }
pts = append(pts, serial.PointsTo{ pts = append(pts, serial.PointsTo{
Type: r.qpos.TypeString(ptr.typ), Type: r.qpos.typeString(ptr.typ),
NamePos: namePos, NamePos: namePos,
Labels: labels, Labels: labels,
}) })

View File

@ -117,13 +117,7 @@ func sameFile(x, y string) bool {
// fastQueryPos parses the -pos flag and returns a QueryPos. // fastQueryPos parses the -pos flag and returns a QueryPos.
// It parses only a single file, and does not run the type checker. // It parses only a single file, and does not run the type checker.
// func fastQueryPos(posFlag string) (*queryPos, error) {
// Caveat: the token.{FileSet,Pos} info it contains is not comparable
// with that from the oracle's FileSet! (We don't accept oracle.fset
// as a parameter because we don't want the same filename to appear
// multiple times in one FileSet.)
//
func fastQueryPos(posFlag string) (*QueryPos, error) {
filename, startOffset, endOffset, err := parsePosFlag(posFlag) filename, startOffset, endOffset, err := parsePosFlag(posFlag)
if err != nil { if err != nil {
return nil, err return nil, err
@ -145,5 +139,5 @@ func fastQueryPos(posFlag string) (*QueryPos, error) {
return nil, fmt.Errorf("no syntax here") return nil, fmt.Errorf("no syntax here")
} }
return &QueryPos{fset, start, end, path, exact, nil}, nil return &queryPos{fset, start, end, path, exact, nil}, nil
} }

View File

@ -10,10 +10,13 @@ import (
"go/ast" "go/ast"
"go/token" "go/token"
"io/ioutil" "io/ioutil"
"os"
"sort" "sort"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/types" "golang.org/x/tools/go/types"
"golang.org/x/tools/oracle/serial" "golang.org/x/tools/oracle/serial"
"golang.org/x/tools/refactor/importgraph"
) )
// TODO(adonovan): use golang.org/x/tools/refactor/importgraph to choose // TODO(adonovan): use golang.org/x/tools/refactor/importgraph to choose
@ -21,36 +24,97 @@ import (
// Referrers reports all identifiers that resolve to the same object // Referrers reports all identifiers that resolve to the same object
// as the queried identifier, within any package in the analysis scope. // as the queried identifier, within any package in the analysis scope.
// func referrers(q *Query) error {
func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) { lconf := loader.Config{Build: q.Build}
id, _ := qpos.path[0].(*ast.Ident) allowErrors(&lconf)
if id == nil {
return nil, fmt.Errorf("no identifier here") if err := importQueryPackage(q.Pos, &lconf); err != nil {
return err
} }
obj := qpos.info.ObjectOf(id) var id *ast.Ident
var obj types.Object
var lprog *loader.Program
var pass2 bool
for {
// Load/parse/type-check the program.
var err error
lprog, err = lconf.Load()
if err != nil {
return err
}
q.Fset = lprog.Fset
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
id, _ = qpos.path[0].(*ast.Ident)
if id == nil {
return fmt.Errorf("no identifier here")
}
obj = qpos.info.ObjectOf(id)
if obj == nil { if obj == nil {
// Happens for y in "switch y := x.(type)", but I think that's all. // Happens for y in "switch y := x.(type)", but I think that's all.
return nil, fmt.Errorf("no object for identifier") return fmt.Errorf("no object for identifier")
}
// If the identifier is exported, we must load all packages that
// depend transitively upon the package that defines it.
//
// TODO(adonovan): opt: skip this step if obj.Pkg() is a test or
// main package.
if pass2 || !obj.Exported() {
break
}
// Scan the workspace and build the import graph.
_, rev, errors := importgraph.Build(q.Build)
if len(errors) > 0 {
for path, err := range errors {
fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err)
}
return fmt.Errorf("failed to scan import graph for workspace")
}
// Re-load the larger program.
// Create a new file set so that ...
// External test packages are never imported,
// so they will never appear in the graph.
// (We must reset the Config here, not just reset the Fset field.)
lconf = loader.Config{
Fset: token.NewFileSet(),
Build: q.Build,
}
allowErrors(&lconf)
for path := range rev.Search(obj.Pkg().Path()) {
lconf.Import(path)
}
pass2 = true
} }
// Iterate over all go/types' Uses facts for the entire program. // Iterate over all go/types' Uses facts for the entire program.
var refs []*ast.Ident var refs []*ast.Ident
for _, info := range o.typeInfo { for _, info := range lprog.AllPackages {
for id2, obj2 := range info.Uses { for id2, obj2 := range info.Uses {
if sameObj(obj, obj2) { if sameObj(obj, obj2) {
refs = append(refs, id2) refs = append(refs, id2)
} }
} }
} }
// TODO(adonovan): is this sort stable? Pos order depends on
// when packages are reached. Use filename order?
sort.Sort(byNamePos(refs)) sort.Sort(byNamePos(refs))
return &referrersResult{ q.result = &referrersResult{
qpos: qpos, fset: q.Fset,
query: id, query: id,
obj: obj, obj: obj,
refs: refs, refs: refs,
}, nil }
return nil
} }
// same reports whether x and y are identical, or both are PkgNames // same reports whether x and y are identical, or both are PkgNames
@ -77,7 +141,7 @@ func (p byNamePos) Less(i, j int) bool { return p[i].NamePos < p[j].NamePos }
func (p byNamePos) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p byNamePos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type referrersResult struct { type referrersResult struct {
qpos *QueryPos fset *token.FileSet
query *ast.Ident // identifier of query query *ast.Ident // identifier of query
obj types.Object // object it denotes obj types.Object // object it denotes
refs []*ast.Ident // set of all other references to it refs []*ast.Ident // set of all other references to it
@ -97,7 +161,7 @@ func (r *referrersResult) display(printf printfFunc) {
// First pass: start the file reads concurrently. // First pass: start the file reads concurrently.
for _, ref := range r.refs { for _, ref := range r.refs {
posn := r.qpos.fset.Position(ref.Pos()) posn := r.fset.Position(ref.Pos())
fi := fileinfosByName[posn.Filename] fi := fileinfosByName[posn.Filename]
if fi == nil { if fi == nil {
fi = &fileinfo{data: make(chan []byte)} fi = &fileinfo{data: make(chan []byte)}

View File

@ -64,20 +64,6 @@ type Caller struct {
Caller string `json:"caller"` // full name of calling function Caller string `json:"caller"` // full name of calling function
} }
// A CallGraph is one element of the slice returned by a 'callgraph' query.
// The index of each item in the slice is used to identify it in the
// Callers adjacency list.
//
// Multiple nodes may have the same Name due to context-sensitive
// treatment of some functions.
//
// TODO(adonovan): perhaps include edge labels (i.e. callsites).
type CallGraph struct {
Name string `json:"name"` // full name of function
Pos string `json:"pos"` // location of function
Children []int `json:"children,omitempty"` // indices of child nodes in callgraph list
}
// A CallStack is the result of a 'callstack' query. // A CallStack is the result of a 'callstack' query.
// It indicates an arbitrary path from the root of the callgraph to // It indicates an arbitrary path from the root of the callgraph to
// the query function. // the query function.
@ -232,11 +218,6 @@ type Describe struct {
Value *DescribeValue `json:"value,omitempty"` Value *DescribeValue `json:"value,omitempty"`
} }
type PTAWarning struct {
Pos string `json:"pos"` // location associated with warning
Message string `json:"message"` // warning message
}
// A WhichErrs is the result of a 'whicherrs' query. // A WhichErrs is the result of a 'whicherrs' query.
// It contains the position of the queried error and the possible globals, // It contains the position of the queried error and the possible globals,
// constants, and types it may point to. // constants, and types it may point to.
@ -264,7 +245,6 @@ type Result struct {
// the one specified by 'mode'. // the one specified by 'mode'.
Callees *Callees `json:"callees,omitempty"` Callees *Callees `json:"callees,omitempty"`
Callers []Caller `json:"callers,omitempty"` Callers []Caller `json:"callers,omitempty"`
Callgraph []CallGraph `json:"callgraph,omitempty"`
Callstack *CallStack `json:"callstack,omitempty"` Callstack *CallStack `json:"callstack,omitempty"`
Definition *Definition `json:"definition,omitempty"` Definition *Definition `json:"definition,omitempty"`
Describe *Describe `json:"describe,omitempty"` Describe *Describe `json:"describe,omitempty"`
@ -275,6 +255,4 @@ type Result struct {
Referrers *Referrers `json:"referrers,omitempty"` Referrers *Referrers `json:"referrers,omitempty"`
What *What `json:"what,omitempty"` What *What `json:"what,omitempty"`
WhichErrs *WhichErrs `json:"whicherrs,omitempty"` WhichErrs *WhichErrs `json:"whicherrs,omitempty"`
Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis
} }

View File

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

View File

@ -4,7 +4,7 @@ package describe // @describe pkgdecl "describe"
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.
// See describe-json.golden for expected query results. // See describe-json.golden for expected query results.
func main() { // func main() {
var s struct{ x [3]int } var s struct{ x [3]int }
p := &s.x[0] // @describe desc-val-p "p" p := &s.x[0] // @describe desc-val-p "p"
_ = p _ = p

View File

@ -2,104 +2,108 @@
{ {
"mode": "describe", "mode": "describe",
"describe": { "describe": {
"desc": "definition of package \"describe\"", "desc": "definition of package \"describe-json\"",
"pos": "testdata/src/main/describe-json.go:1:9", "pos": "testdata/src/describe-json/main.go:1:9",
"detail": "package", "detail": "package",
"package": { "package": {
"path": "describe", "path": "describe-json",
"members": [ "members": [
{ {
"name": "C", "name": "C",
"type": "int", "type": "int",
"pos": "testdata/src/main/describe-json.go:25:6", "pos": "testdata/src/describe-json/main.go:25:6",
"kind": "type", "kind": "type",
"methods": [ "methods": [
{ {
"name": "method (C) f()", "name": "method (C) f()",
"pos": "testdata/src/main/describe-json.go:28:12" "pos": "testdata/src/describe-json/main.go:28:12"
} }
] ]
}, },
{ {
"name": "D", "name": "D",
"type": "struct{}", "type": "struct{}",
"pos": "testdata/src/main/describe-json.go:26:6", "pos": "testdata/src/describe-json/main.go:26:6",
"kind": "type", "kind": "type",
"methods": [ "methods": [
{ {
"name": "method (*D) f()", "name": "method (*D) f()",
"pos": "testdata/src/main/describe-json.go:29:13" "pos": "testdata/src/describe-json/main.go:29:13"
} }
] ]
}, },
{ {
"name": "I", "name": "I",
"type": "interface{f()}", "type": "interface{f()}",
"pos": "testdata/src/main/describe-json.go:21:6", "pos": "testdata/src/describe-json/main.go:21:6",
"kind": "type", "kind": "type",
"methods": [ "methods": [
{ {
"name": "method (I) f()", "name": "method (I) f()",
"pos": "testdata/src/main/describe-json.go:22:2" "pos": "testdata/src/describe-json/main.go:22:2"
} }
] ]
}, },
{ {
"name": "main", "name": "main",
"type": "func()", "type": "func()",
"pos": "testdata/src/main/describe-json.go:7:6", "pos": "testdata/src/describe-json/main.go:7:6",
"kind": "func" "kind": "func"
} }
] ]
} }
} }
}-------- @describe desc-val-p -------- }
-------- @describe desc-val-p --------
{ {
"mode": "describe", "mode": "describe",
"describe": { "describe": {
"desc": "identifier", "desc": "identifier",
"pos": "testdata/src/main/describe-json.go:9:2", "pos": "testdata/src/describe-json/main.go:9:2",
"detail": "value", "detail": "value",
"value": { "value": {
"type": "*int", "type": "*int",
"objpos": "testdata/src/main/describe-json.go:9:2" "objpos": "testdata/src/describe-json/main.go:9:2"
} }
} }
}-------- @describe desc-val-i -------- }
-------- @describe desc-val-i --------
{ {
"mode": "describe", "mode": "describe",
"describe": { "describe": {
"desc": "identifier", "desc": "identifier",
"pos": "testdata/src/main/describe-json.go:16:8", "pos": "testdata/src/describe-json/main.go:16:8",
"detail": "value", "detail": "value",
"value": { "value": {
"type": "I", "type": "I",
"objpos": "testdata/src/main/describe-json.go:12:6" "objpos": "testdata/src/describe-json/main.go:12:6"
} }
} }
}-------- @describe desc-stmt -------- }
-------- @describe desc-stmt --------
{ {
"mode": "describe", "mode": "describe",
"describe": { "describe": {
"desc": "go statement", "desc": "go statement",
"pos": "testdata/src/main/describe-json.go:18:2", "pos": "testdata/src/describe-json/main.go:18:2",
"detail": "unknown" "detail": "unknown"
} }
}-------- @describe desc-type-C -------- }
-------- @describe desc-type-C --------
{ {
"mode": "describe", "mode": "describe",
"describe": { "describe": {
"desc": "definition of type C (size 8, align 8)", "desc": "definition of type C (size 8, align 8)",
"pos": "testdata/src/main/describe-json.go:25:6", "pos": "testdata/src/describe-json/main.go:25:6",
"detail": "type", "detail": "type",
"type": { "type": {
"type": "C", "type": "C",
"namepos": "testdata/src/main/describe-json.go:25:6", "namepos": "testdata/src/describe-json/main.go:25:6",
"namedef": "int", "namedef": "int",
"methods": [ "methods": [
{ {
"name": "method (C) f()", "name": "method (C) f()",
"pos": "testdata/src/main/describe-json.go:28:12" "pos": "testdata/src/describe-json/main.go:28:12"
} }
] ]
} }

View File

@ -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"
}
]
}
}

View File

@ -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"
}
]
}
}

View File

@ -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

View File

@ -1,4 +1,4 @@
package imports package main
import ( import (
"hash/fnv" // @describe ref-pkg-import2 "fnv" "hash/fnv" // @describe ref-pkg-import2 "fnv"

View File

@ -41,7 +41,7 @@ defined here
-------- @pointsto p -------- -------- @pointsto p --------
this *int may point to these objects: this *int may point to these objects:
imports.a main.a
-------- @describe ref-pkg -------- -------- @describe ref-pkg --------
reference to package "lib" reference to package "lib"

View File

@ -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 "^"

View File

@ -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"
}
]
}

View File

@ -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"

View File

@ -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

View File

@ -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 "^"

View File

@ -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

View File

@ -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"
}
]
}
}

View File

@ -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"
}
]
}
}

View File

@ -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

View File

@ -1,4 +1,4 @@
package multi package main
func g(x int) { func g(x int) {
} }

View File

@ -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"
]
}
}

View File

@ -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"
]
}
}

View File

@ -1,4 +1,4 @@
package peers package main
// Tests of channel 'peers' query, -format=json. // Tests of channel 'peers' query, -format=json.
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.

View File

@ -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"
]
}
}

View File

@ -1,4 +1,4 @@
package peers package main
// Tests of channel 'peers' query. // Tests of channel 'peers' query.
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.

View File

@ -26,7 +26,7 @@ This channel of type chan *int may be:
-------- @pointsto pointsto-rA -------- -------- @pointsto pointsto-rA --------
this *int may point to these objects: this *int may point to these objects:
peers.a2 main.a2
a1 a1
-------- @peers peer-recv-chB -------- -------- @peers peer-recv-chB --------

View File

@ -1,4 +1,4 @@
package pointsto package main
// Tests of 'pointsto' queries, -format=json. // Tests of 'pointsto' queries, -format=json.
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.

View File

@ -6,29 +6,30 @@
"type": "*int", "type": "*int",
"labels": [ "labels": [
{ {
"pos": "testdata/src/main/pointsto-json.go:8:6", "pos": "testdata/src/pointsto-json/main.go:8:6",
"desc": "s.x[*]" "desc": "s.x[*]"
} }
] ]
} }
] ]
}-------- @pointsto val-i -------- }
-------- @pointsto val-i --------
{ {
"mode": "pointsto", "mode": "pointsto",
"pointsto": [ "pointsto": [
{ {
"type": "*D", "type": "*D",
"namepos": "testdata/src/main/pointsto-json.go:24:6", "namepos": "testdata/src/pointsto-json/main.go:24:6",
"labels": [ "labels": [
{ {
"pos": "testdata/src/main/pointsto-json.go:14:10", "pos": "testdata/src/pointsto-json/main.go:14:10",
"desc": "new" "desc": "new"
} }
] ]
}, },
{ {
"type": "C", "type": "C",
"namepos": "testdata/src/main/pointsto-json.go:23:6" "namepos": "testdata/src/pointsto-json/main.go:23:6"
} }
] ]
} }

View File

@ -1,4 +1,4 @@
package pointsto package main
// Tests of 'pointsto' query. // Tests of 'pointsto' query.
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.

View File

@ -3,33 +3,33 @@
Error: pointer analysis wants an expression of reference type; got untyped float Error: pointer analysis wants an expression of reference type; got untyped float
-------- @pointsto func-ref-main -------- -------- @pointsto func-ref-main --------
this func() may point to these objects: this func() may point to these objects:
pointsto.main main.main
-------- @pointsto func-ref-*C.f -------- -------- @pointsto func-ref-*C.f --------
this func() may point to these objects: this func() may point to these objects:
(*pointsto.C).f (*main.C).f
-------- @pointsto func-ref-D.f -------- -------- @pointsto func-ref-D.f --------
this func() may point to these objects: this func() may point to these objects:
(pointsto.D).f (main.D).f
-------- @pointsto func-ref-I.f -------- -------- @pointsto func-ref-I.f --------
Error: func (pointsto.I).f() is an interface method Error: func (main.I).f() is an interface method
-------- @pointsto func-ref-d.f -------- -------- @pointsto func-ref-d.f --------
this func() may point to these objects: this func() may point to these objects:
(pointsto.D).f (main.D).f
-------- @pointsto func-ref-i.f -------- -------- @pointsto func-ref-i.f --------
Error: func (pointsto.I).f() is an interface method Error: func (main.I).f() is an interface method
-------- @pointsto ref-lexical-d.f -------- -------- @pointsto ref-lexical-d.f --------
this func() may point to these objects: this func() may point to these objects:
(pointsto.D).f (main.D).f
-------- @pointsto ref-anon -------- -------- @pointsto ref-anon --------
this func() may point to these objects: this func() may point to these objects:
pointsto.main$1 main.main$1
-------- @pointsto ref-global -------- -------- @pointsto ref-global --------
this *string may point to these objects: this *string may point to these objects:

View File

@ -1,4 +1,4 @@
package referrers package main
// Tests of 'referrers' query. // Tests of 'referrers' query.
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.

View File

@ -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"
]
}
}

View File

@ -1,4 +1,4 @@
package reflection package main
// This is a test of 'pointsto', but we split it into a separate file // This is a test of 'pointsto', but we split it into a separate file
// so that pointsto.go doesn't have to import "reflect" each time. // so that pointsto.go doesn't have to import "reflect" each time.

View File

@ -1,18 +1,18 @@
-------- @pointsto mrv -------- -------- @pointsto mrv --------
this reflect.Value may contain these dynamic types: this reflect.Value may contain these dynamic types:
*bool, may point to: *bool, may point to:
reflection.b main.b
*int, may point to: *int, may point to:
reflection.a main.a
map[*int]*bool, may point to: map[*int]*bool, may point to:
makemap makemap
-------- @pointsto p1 -------- -------- @pointsto p1 --------
this interface{} may contain these dynamic types: this interface{} may contain these dynamic types:
*bool, may point to: *bool, may point to:
reflection.b main.b
*int, may point to: *int, may point to:
reflection.a main.a
map[*int]*bool, may point to: map[*int]*bool, may point to:
makemap makemap
@ -23,7 +23,7 @@ this []reflect.Value may point to these objects:
-------- @pointsto p3 -------- -------- @pointsto p3 --------
this reflect.Value may contain these dynamic types: this reflect.Value may contain these dynamic types:
*int, may point to: *int, may point to:
reflection.a main.a
-------- @pointsto p4 -------- -------- @pointsto p4 --------
this reflect.Type may contain these dynamic types: this reflect.Type may contain these dynamic types:

View File

@ -1,4 +1,4 @@
package what package main
// Tests of 'what' queries, -format=json. // Tests of 'what' queries, -format=json.
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.

View File

@ -37,7 +37,6 @@
"modes": [ "modes": [
"callees", "callees",
"callers", "callers",
"callgraph",
"callstack", "callstack",
"definition", "definition",
"describe", "describe",
@ -47,6 +46,6 @@
"referrers" "referrers"
], ],
"srcdir": "testdata/src", "srcdir": "testdata/src",
"importpath": "main" "importpath": "what-json"
} }
} }

View File

@ -1,4 +1,4 @@
package what // @what pkgdecl "what" package main // @what pkgdecl "main"
// Tests of 'what' queries. // Tests of 'what' queries.
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.

View File

@ -1,9 +1,9 @@
-------- @what pkgdecl -------- -------- @what pkgdecl --------
identifier identifier
source file source file
modes: [callgraph definition describe freevars implements pointsto referrers] modes: [definition describe freevars implements pointsto referrers]
srcdir: testdata/src srcdir: testdata/src
import path: main import path: what
-------- @what call -------- -------- @what call --------
identifier identifier
@ -12,9 +12,9 @@ expression statement
block block
function declaration function declaration
source file source file
modes: [callees callers callgraph callstack definition describe freevars implements pointsto referrers] modes: [callees callers callstack definition describe freevars implements pointsto referrers]
srcdir: testdata/src srcdir: testdata/src
import path: main import path: what
-------- @what var -------- -------- @what var --------
variable declaration variable declaration
@ -22,9 +22,9 @@ variable declaration statement
block block
function declaration function declaration
source file source file
modes: [callers callgraph callstack describe freevars pointsto] modes: [callers callstack describe freevars pointsto]
srcdir: testdata/src srcdir: testdata/src
import path: main import path: what
-------- @what recv -------- -------- @what recv --------
identifier identifier
@ -33,7 +33,7 @@ expression statement
block block
function declaration function declaration
source file source file
modes: [callers callgraph callstack definition describe freevars implements peers pointsto referrers] modes: [callers callstack definition describe freevars implements peers pointsto referrers]
srcdir: testdata/src srcdir: testdata/src
import path: main import path: what

View File

@ -24,20 +24,18 @@ import (
// tools, e.g. to populate a menu of options of slower queries about // tools, e.g. to populate a menu of options of slower queries about
// the selected location. // the selected location.
// //
func what(posFlag string, buildContext *build.Context) (*Result, error) { func what(q *Query) error {
qpos, err := fastQueryPos(posFlag) qpos, err := fastQueryPos(q.Pos)
if err != nil { if err != nil {
return nil, err return err
} }
q.Fset = qpos.fset
// (ignore errors) // (ignore errors)
srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), buildContext) srcdir, importPath, _ := guessImportPath(q.Fset.File(qpos.start).Name(), q.Build)
// Determine which query modes are applicable to the selection. // Determine which query modes are applicable to the selection.
// TODO(adonovan): refactor: make each minfo have an 'enable'
// predicate over qpos.
enable := map[string]bool{ enable := map[string]bool{
"callgraph": true, // whole program; always enabled
"describe": true, // any syntax; always enabled "describe": true, // any syntax; always enabled
} }
@ -100,11 +98,10 @@ func what(posFlag string, buildContext *build.Context) (*Result, error) {
// If we don't have an exact selection, disable modes that need one. // If we don't have an exact selection, disable modes that need one.
if !qpos.exact { if !qpos.exact {
for _, minfo := range modes { enable["callees"] = false
if minfo.needs&needExactPos != 0 { enable["pointsto"] = false
enable[minfo.name] = false enable["whicherrs"] = false
} enable["describe"] = false
}
} }
var modes []string var modes []string
@ -113,17 +110,13 @@ func what(posFlag string, buildContext *build.Context) (*Result, error) {
} }
sort.Strings(modes) sort.Strings(modes)
return &Result{ q.result = &whatResult{
mode: "what",
fset: qpos.fset,
q: &whatResult{
path: qpos.path, path: qpos.path,
srcdir: srcdir, srcdir: srcdir,
importPath: importPath, importPath: importPath,
modes: modes, modes: modes,
}, }
}, nil return nil
} }
// guessImportPath finds the package containing filename, and returns // guessImportPath finds the package containing filename, and returns

View File

@ -11,6 +11,7 @@ import (
"sort" "sort"
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/go/types" "golang.org/x/tools/go/types"
@ -27,10 +28,40 @@ var builtinErrorType = types.Universe.Lookup("error").Type()
// //
// TODO(dmorsing): figure out if fields in errors like *os.PathError.Err // TODO(dmorsing): figure out if fields in errors like *os.PathError.Err
// can be queried recursively somehow. // can be queried recursively somehow.
func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) { func whicherrs(q *Query) error {
lconf := loader.Config{Build: q.Build}
// Determine initial packages for PTA.
args, err := lconf.FromArgs(q.Scope, true)
if err != nil {
return err
}
if len(args) > 0 {
return fmt.Errorf("surplus arguments: %q", args)
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
q.Fset = lprog.Fset
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
if err != nil {
return err
}
prog := ssa.Create(lprog, ssa.GlobalDebug)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
path, action := findInterestingNode(qpos.info, qpos.path) path, action := findInterestingNode(qpos.info, qpos.path)
if action != actionExpr { if action != actionExpr {
return nil, fmt.Errorf("whicherrs wants an expression; got %s", return fmt.Errorf("whicherrs wants an expression; got %s",
astutil.NodeDescription(qpos.path[0])) astutil.NodeDescription(qpos.path[0]))
} }
var expr ast.Expr var expr ast.Expr
@ -38,46 +69,50 @@ func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) {
switch n := path[0].(type) { switch n := path[0].(type) {
case *ast.ValueSpec: case *ast.ValueSpec:
// ambiguous ValueSpec containing multiple names // ambiguous ValueSpec containing multiple names
return nil, fmt.Errorf("multiple value specification") return fmt.Errorf("multiple value specification")
case *ast.Ident: case *ast.Ident:
obj = qpos.info.ObjectOf(n) obj = qpos.info.ObjectOf(n)
expr = n expr = n
case ast.Expr: case ast.Expr:
expr = n expr = n
default: default:
return nil, fmt.Errorf("unexpected AST for expr: %T", n) return fmt.Errorf("unexpected AST for expr: %T", n)
} }
typ := qpos.info.TypeOf(expr) typ := qpos.info.TypeOf(expr)
if !types.Identical(typ, builtinErrorType) { if !types.Identical(typ, builtinErrorType) {
return nil, fmt.Errorf("selection is not an expression of type 'error'") return fmt.Errorf("selection is not an expression of type 'error'")
} }
// Determine the ssa.Value for the expression. // Determine the ssa.Value for the expression.
var value ssa.Value var value ssa.Value
var err error
if obj != nil { if obj != nil {
// def/ref of func/var object // def/ref of func/var object
value, _, err = ssaValueForIdent(o.prog, qpos.info, obj, path) value, _, err = ssaValueForIdent(prog, qpos.info, obj, path)
} else { } else {
value, _, err = ssaValueForExpr(o.prog, qpos.info, path) value, _, err = ssaValueForExpr(prog, qpos.info, path)
} }
if err != nil { if err != nil {
return nil, err // e.g. trivially dead code return err // e.g. trivially dead code
} }
buildSSA(o)
globals := findVisibleErrs(o.prog, qpos) // Defer SSA construction till after errors are reported.
constants := findVisibleConsts(o.prog, qpos) prog.BuildAll()
globals := findVisibleErrs(prog, qpos)
constants := findVisibleConsts(prog, qpos)
res := &whicherrsResult{ res := &whicherrsResult{
qpos: qpos, qpos: qpos,
errpos: expr.Pos(), errpos: expr.Pos(),
} }
// TODO(adonovan): the following code is heavily duplicated
// w.r.t. "pointsto". Refactor?
// Find the instruction which initialized the // Find the instruction which initialized the
// global error. If more than one instruction has stored to the global // global error. If more than one instruction has stored to the global
// remove the global from the set of values that we want to query. // remove the global from the set of values that we want to query.
allFuncs := ssautil.AllFunctions(o.prog) allFuncs := ssautil.AllFunctions(prog)
for fn := range allFuncs { for fn := range allFuncs {
for _, b := range fn.Blocks { for _, b := range fn.Blocks {
for _, instr := range b.Instrs { for _, instr := range b.Instrs {
@ -104,12 +139,12 @@ func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
} }
o.ptaConfig.AddQuery(value) ptaConfig.AddQuery(value)
for _, v := range globals { for _, v := range globals {
o.ptaConfig.AddQuery(v) ptaConfig.AddQuery(v)
} }
ptares := ptrAnalysis(o) ptares := ptrAnalysis(ptaConfig)
valueptr := ptares.Queries[value] valueptr := ptares.Queries[value]
for g, v := range globals { for g, v := range globals {
ptr, ok := ptares.Queries[v] ptr, ok := ptares.Queries[v]
@ -174,11 +209,13 @@ func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) {
sort.Sort(membersByPosAndString(res.globals)) sort.Sort(membersByPosAndString(res.globals))
sort.Sort(membersByPosAndString(res.consts)) sort.Sort(membersByPosAndString(res.consts))
sort.Sort(sorterrorType(res.types)) sort.Sort(sorterrorType(res.types))
return res, nil
q.result = res
return nil
} }
// findVisibleErrs returns a mapping from each package-level variable of type "error" to nil. // findVisibleErrs returns a mapping from each package-level variable of type "error" to nil.
func findVisibleErrs(prog *ssa.Program, qpos *QueryPos) map[*ssa.Global]ssa.Value { func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value {
globals := make(map[*ssa.Global]ssa.Value) globals := make(map[*ssa.Global]ssa.Value)
for _, pkg := range prog.AllPackages() { for _, pkg := range prog.AllPackages() {
for _, mem := range pkg.Members { for _, mem := range pkg.Members {
@ -201,7 +238,7 @@ func findVisibleErrs(prog *ssa.Program, qpos *QueryPos) map[*ssa.Global]ssa.Valu
} }
// findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil. // findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil.
func findVisibleConsts(prog *ssa.Program, qpos *QueryPos) map[ssa.Const]*ssa.NamedConst { func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst {
constants := make(map[ssa.Const]*ssa.NamedConst) constants := make(map[ssa.Const]*ssa.NamedConst)
for _, pkg := range prog.AllPackages() { for _, pkg := range prog.AllPackages() {
for _, mem := range pkg.Members { for _, mem := range pkg.Members {
@ -247,7 +284,7 @@ type errorType struct {
} }
type whicherrsResult struct { type whicherrsResult struct {
qpos *QueryPos qpos *queryPos
errpos token.Pos errpos token.Pos
globals []ssa.Member globals []ssa.Member
consts []ssa.Member consts []ssa.Member
@ -270,7 +307,7 @@ func (r *whicherrsResult) display(printf printfFunc) {
if len(r.types) > 0 { if len(r.types) > 0 {
printf(r.qpos, "this error may contain these dynamic types:") printf(r.qpos, "this error may contain these dynamic types:")
for _, t := range r.types { for _, t := range r.types {
printf(t.obj.Pos(), "\t%s", r.qpos.TypeString(t.typ)) printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ))
} }
} }
} }
@ -286,7 +323,7 @@ func (r *whicherrsResult) toSerial(res *serial.Result, fset *token.FileSet) {
} }
for _, t := range r.types { for _, t := range r.types {
var et serial.WhichErrsType var et serial.WhichErrsType
et.Type = r.qpos.TypeString(t.typ) et.Type = r.qpos.typeString(t.typ)
et.Position = fset.Position(t.obj.Pos()).String() et.Position = fset.Position(t.obj.Pos()).String()
we.Types = append(we.Types, et) we.Types = append(we.Types, et)
} }