aboutsummaryrefslogtreecommitdiff
path: root/oracle
diff options
context:
space:
mode:
authorAlan Donovan <adonovan@google.com>2013-12-13 10:04:55 -0500
committerAlan Donovan <adonovan@google.com>2013-12-13 10:04:55 -0500
commitf1198742036d0b391747b1120333d65fd0771a7d (patch)
tree44138f93937efb4d66a602b918ab89d294ed5c07 /oracle
parent3df3227c35bdc3fa246808788e6c3870d08ef1a1 (diff)
downloadtools-f1198742036d0b391747b1120333d65fd0771a7d.tar.gz
go.tools/oracle: improvements to command set and performance.
Command set: - what: an extremely fast query that parses a single file and returns the AST stack, package name and the set of query modes that apply to the current selection. Intended for GUI tools that need to grey out UI elements. - definition: shows the definition of an identifier. - pointsto: the PTA features of 'describe' have been split out into their own command. - describe: with PTA stripped out, the cost is now bounded by type checking. Performance: - The importer.Config.TypeCheckFuncBodies predicate supports setting the 'IgnoreFuncBodies' typechecker flag on a per-package basis. This means we can load dependencies from source more quickly if we only need exported types. (We avoid gcimport data because it may be absent or stale.) This also means we can run type-based queries on packages that aren't part of the pointer analysis scope. (Yay.) - Modes that require only type analysis of the query package run a "what" query first, and restrict their analysis scope to just that package and its dependencies (sans func bodies), making them much faster. - We call newOracle not oracle.New in Query, so that the 'needs' bitset isn't ignored (oops!). This makes the non-PTA queries faster. Also: - removed vestigial timers junk. - pos.go: existing position utilties split out into own file. Added parsePosFlag utility. - numerous cosmetic tweaks. + very basic tests. To do in follow-ups: - sophisticated editor integration of "what". - better tests. - refactoring of control flow as described in comment. - changes to "implements", "describe" commands. - update design doc + user manual. R=crawshaw, dominik.honnef CC=golang-dev, gri https://golang.org/cl/40630043
Diffstat (limited to 'oracle')
-rw-r--r--oracle/TODO99
-rw-r--r--oracle/callees.go2
-rw-r--r--oracle/callers.go2
-rw-r--r--oracle/callgraph.go2
-rw-r--r--oracle/callstack.go2
-rw-r--r--oracle/definition.go53
-rw-r--r--oracle/describe.go274
-rw-r--r--oracle/freevars.go20
-rw-r--r--oracle/implements.go4
-rw-r--r--oracle/oracle.go354
-rw-r--r--oracle/oracle_test.go4
-rw-r--r--oracle/peers.go4
-rw-r--r--oracle/pointsto.go257
-rw-r--r--oracle/pos.go149
-rw-r--r--oracle/referrers.go28
-rw-r--r--oracle/serial/serial.go65
-rw-r--r--oracle/testdata/src/main/calls.go12
-rw-r--r--oracle/testdata/src/main/calls.golden27
-rw-r--r--oracle/testdata/src/main/describe-json.golden31
-rw-r--r--oracle/testdata/src/main/describe.go10
-rw-r--r--oracle/testdata/src/main/describe.golden60
-rw-r--r--oracle/testdata/src/main/freevars.golden2
-rw-r--r--oracle/testdata/src/main/imports.go2
-rw-r--r--oracle/testdata/src/main/imports.golden10
-rw-r--r--oracle/testdata/src/main/peers.go10
-rw-r--r--oracle/testdata/src/main/peers.golden30
-rw-r--r--oracle/testdata/src/main/pointsto-json.go27
-rw-r--r--oracle/testdata/src/main/pointsto-json.golden34
-rw-r--r--oracle/testdata/src/main/pointsto.go68
-rw-r--r--oracle/testdata/src/main/pointsto.golden93
-rw-r--r--oracle/testdata/src/main/reflection.go14
-rw-r--r--oracle/testdata/src/main/reflection.golden18
-rw-r--r--oracle/testdata/src/main/what-json.go9
-rw-r--r--oracle/testdata/src/main/what-json.golden52
-rw-r--r--oracle/testdata/src/main/what.go11
-rw-r--r--oracle/testdata/src/main/what.golden39
-rw-r--r--oracle/what.go197
37 files changed, 1446 insertions, 629 deletions
diff --git a/oracle/TODO b/oracle/TODO
new file mode 100644
index 0000000..0951de1
--- /dev/null
+++ b/oracle/TODO
@@ -0,0 +1,99 @@
+
+
+ORACLE TODO
+===========
+
+General
+=======
+
+Refactor control flow so that each mode has a "one-shot setup" function.
+
+Use a fault-tolerant parser that can recover from bad parses.
+
+Save unsaved editor buffers into an archive and provide that to the
+tools, which should act as if they were saved.
+
+Fix: make the guessImportPath hack work with external _test.go files too.
+
+Allow the analysis scope to include multiple test packages at once.
+
+Include complete pos/end information Serial output.
+ But beware that sometimes a single token (e.g. +) is more helpful
+ than the pos/end of the containing expression (e.g. x \n + \n y).
+
+Remove pointer analysis context information when printing results as
+it tends to be unhelpful.
+
+Specific queries
+================
+
+callers, callees
+
+ Use a type-based (e.g. RTA) callgraph when a callers/callees query is
+ outside the analysis scope.
+
+implements
+
+ Make it require that the selection is a type, and show only the
+ implements relation as it applies to that type.
+
+definition, referrers
+
+ Use the parser's resolver information to answer the query
+ for local names. Only run the type checker if that fails.
+ (NB: gri's new parser won't do any resolution.)
+
+ referrers: Show the text of the matching line of code, like grep.
+
+ definition: Make it work with qualified identifiers (SelectorExpr) too.
+
+ references: Make it work on things that are implicit idents, like
+ import specs, perhaps?
+
+what
+
+ Report def/ref info if available.
+ Editors could use it to highlight all idents of the same local var.
+
+ Fix: support it in (*Oracle).Query (long-running tools).
+
+ More tests.
+
+pointsto
+
+ When invoked on a function Ident, we get an error.
+
+ When invoked on a named return parameter, we get an error.
+
+describe
+
+ When invoked on a var, we want to see the type and its methods.
+
+ Split "show type" and "describe syntax" into separate commands?
+
+peers
+
+ Permit querying from a makechan, close(), for...range, or reflective op.
+
+ Report aliasing reflect.{Send,Recv,Close} and close() operations.
+
+New queries
+
+"updaters": show all statements that may update the selected lvalue
+ (local, global, field, etc).
+
+"creators": show all places where an object of type T is created
+ (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
+ (Useful for datatypes whose zero value is not safe)
+
+
+Editor-specific
+===============
+
+Add support for "what" to .el; clean up.
+
+Emacs: use JSON to get the raw information from the oracle. Don't
+ open an editor buffer for simpler queries, just jump to the result
+ and/or display it in the modeline.
+
+Support other editors: vim, Eclipse, Sublime, etc.
diff --git a/oracle/callees.go b/oracle/callees.go
index a80772d..d3507e8 100644
--- a/oracle/callees.go
+++ b/oracle/callees.go
@@ -102,7 +102,7 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
}
// Dynamic call: use pointer analysis.
- o.config.BuildCallGraph = true
+ o.ptaConfig.BuildCallGraph = true
callgraph := ptrAnalysis(o).CallGraph
// Find all call edges from the site.
diff --git a/oracle/callers.go b/oracle/callers.go
index e773a2d..0236fab 100644
--- a/oracle/callers.go
+++ b/oracle/callers.go
@@ -36,7 +36,7 @@ func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
// Run the pointer analysis, recording each
// call found to originate from target.
- o.config.BuildCallGraph = true
+ o.ptaConfig.BuildCallGraph = true
callgraph := ptrAnalysis(o).CallGraph
var edges []call.Edge
call.GraphVisitEdges(callgraph, func(edge call.Edge) error {
diff --git a/oracle/callgraph.go b/oracle/callgraph.go
index 745a5cf..53e559b 100644
--- a/oracle/callgraph.go
+++ b/oracle/callgraph.go
@@ -32,7 +32,7 @@ func callgraph(o *Oracle, _ *QueryPos) (queryResult, error) {
buildSSA(o)
// Run the pointer analysis and build the complete callgraph.
- o.config.BuildCallGraph = true
+ o.ptaConfig.BuildCallGraph = true
ptares := ptrAnalysis(o)
return &callgraphResult{
diff --git a/oracle/callstack.go b/oracle/callstack.go
index 57adfff..38dcdaf 100644
--- a/oracle/callstack.go
+++ b/oracle/callstack.go
@@ -41,7 +41,7 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
}
// Run the pointer analysis and build the complete call graph.
- o.config.BuildCallGraph = true
+ o.ptaConfig.BuildCallGraph = true
callgraph := ptrAnalysis(o).CallGraph
// Search for an arbitrary path from a root to the target function.
diff --git a/oracle/definition.go b/oracle/definition.go
new file mode 100644
index 0000000..16ba5eb
--- /dev/null
+++ b/oracle/definition.go
@@ -0,0 +1,53 @@
+// 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/ast"
+ "go/token"
+
+ "code.google.com/p/go.tools/go/types"
+ "code.google.com/p/go.tools/oracle/serial"
+)
+
+// definition reports the location of the definition of an identifier.
+//
+// TODO(adonovan): opt: for intra-file references, the parser's
+// resolution might be enough; we should start with that.
+//
+func definition(o *Oracle, qpos *QueryPos) (queryResult, error) {
+ id, _ := qpos.path[0].(*ast.Ident)
+ if id == nil {
+ return nil, fmt.Errorf("no identifier here")
+ }
+
+ obj := qpos.info.ObjectOf(id)
+ if obj == nil {
+ // Happens for y in "switch y := x.(type)", but I think that's all.
+ return nil, fmt.Errorf("no object for identifier")
+ }
+
+ return &definitionResult{qpos, obj}, nil
+}
+
+type definitionResult struct {
+ qpos *QueryPos
+ obj types.Object // object it denotes
+}
+
+func (r *definitionResult) display(printf printfFunc) {
+ printf(r.obj, "defined here as %s", r.qpos.ObjectString(r.obj))
+}
+
+func (r *definitionResult) toSerial(res *serial.Result, fset *token.FileSet) {
+ definition := &serial.Definition{
+ Desc: r.obj.String(),
+ }
+ if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos()
+ definition.ObjPos = fset.Position(pos).String()
+ }
+ res.Definition = definition
+}
diff --git a/oracle/describe.go b/oracle/describe.go
index 1c11f28..11d4fe7 100644
--- a/oracle/describe.go
+++ b/oracle/describe.go
@@ -10,8 +10,6 @@ import (
"go/ast"
"go/token"
"os"
- "sort"
- "strconv"
"strings"
"code.google.com/p/go.tools/astutil"
@@ -19,25 +17,19 @@ import (
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/oracle/serial"
- "code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa"
)
// describe describes the syntax node denoted by the query position,
// including:
// - its syntactic category
-// - the location of the definition of its referent (for identifiers)
+// - the definition of its referent (for identifiers) [now redundant]
// - its type and method set (for an expression or type expression)
-// - its points-to set (for a pointer-like expression)
-// - its dynamic types (for an interface, reflect.Value, or
-// reflect.Type expression) and their points-to sets.
-//
-// All printed sets are sorted to ensure determinism.
//
func describe(o *Oracle, qpos *QueryPos) (queryResult, error) {
if false { // debugging
- o.fprintf(os.Stderr, qpos.path[0], "you selected: %s %s",
- astutil.NodeDescription(qpos.path[0]), pathToString2(qpos.path))
+ fprintf(os.Stderr, o.fset, qpos.path[0], "you selected: %s %s",
+ astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
}
path, action := findInterestingNode(qpos.info, qpos.path)
@@ -189,7 +181,7 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
case *ast.SelectorExpr:
if pkginfo.ObjectOf(n.Sel) == nil {
- // Is this reachable?
+ // TODO(adonovan): is this reachable?
return path, actionUnknown
}
// Descend to .Sel child.
@@ -234,6 +226,9 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
// For reference to built-in function, return enclosing call.
path = path[1:] // ascend to enclosing function call
continue
+
+ case *types.Nil:
+ return path, actionExpr
}
// No object.
@@ -274,6 +269,7 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
default:
// e.g. blank identifier (go/types bug?)
// or y in "switch y := x.(type)" (go/types bug?)
+ // or code in a _test.go file that's not part of the package.
fmt.Printf("unknown reference %s in %T\n", n, path[1])
return path, actionUnknown
}
@@ -297,54 +293,6 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
return nil, actionUnknown // unreachable
}
-// ---- VALUE ------------------------------------------------------------
-
-// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
-// to the root of the AST is path. isAddr reports whether the
-// ssa.Value is the address denoted by the ast.Ident, not its value.
-// ssaValueForIdent may return a nil Value without an error to
-// indicate the pointer analysis is not appropriate.
-//
-func ssaValueForIdent(prog *ssa.Program, qinfo *importer.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
- if obj, ok := obj.(*types.Var); ok {
- pkg := prog.Package(qinfo.Pkg)
- pkg.Build()
- if v, addr := prog.VarValue(obj, pkg, path); v != nil {
- // Don't run pointer analysis on a ref to a const expression.
- if _, ok := v.(*ssa.Const); ok {
- return
- }
- return v, addr, nil
- }
- return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
- }
-
- // Don't run pointer analysis on const/func objects.
- return
-}
-
-// ssaValueForExpr returns the ssa.Value of the non-ast.Ident
-// expression whose path to the root of the AST is path. It may
-// return a nil Value without an error to indicate the pointer
-// analysis is not appropriate.
-//
-func ssaValueForExpr(prog *ssa.Program, qinfo *importer.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
- pkg := prog.Package(qinfo.Pkg)
- pkg.SetDebugMode(true)
- pkg.Build()
-
- fn := ssa.EnclosingFunction(pkg, path)
- if fn == nil {
- return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)")
- }
-
- if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
- return v, addr, nil
- }
-
- return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn)
-}
-
func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueResult, error) {
var expr ast.Expr
var obj types.Object
@@ -358,114 +306,28 @@ func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueRe
case ast.Expr:
expr = n
default:
- // Is this reachable?
+ // TODO(adonovan): is this reachable?
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
}
typ := qpos.info.TypeOf(expr)
constVal := qpos.info.ValueOf(expr)
- // From this point on, we cannot fail with an error.
- // Failure to run the pointer analysis will be reported later.
- //
- // Our disposition to pointer analysis may be one of the following:
- // - ok: ssa.Value was const or func.
- // - error: no ssa.Value for expr (e.g. trivially dead code)
- // - ok: ssa.Value is non-pointerlike
- // - error: no Pointer for ssa.Value (e.g. analytically unreachable)
- // - ok: Pointer has empty points-to set
- // - ok: Pointer has non-empty points-to set
- // ptaErr is non-nil only in the "error:" cases.
-
- var ptaErr error
- var ptrs []pointerResult
-
- // Only run pointer analysis on pointerlike expression types.
- if pointer.CanPoint(typ) {
- // Determine the ssa.Value for the expression.
- var value ssa.Value
- var isAddr bool
- if obj != nil {
- // def/ref of func/var/const object
- value, isAddr, ptaErr = ssaValueForIdent(o.prog, qpos.info, obj, path)
- } else {
- // any other expression
- if qpos.info.ValueOf(path[0].(ast.Expr)) == nil { // non-constant?
- value, isAddr, ptaErr = ssaValueForExpr(o.prog, qpos.info, path)
- }
- }
- if value != nil {
- ptrs, ptaErr = describePointer(o, value, isAddr)
- }
- }
-
return &describeValueResult{
qpos: qpos,
expr: expr,
typ: typ,
constVal: constVal,
obj: obj,
- ptaErr: ptaErr,
- ptrs: ptrs,
}, nil
}
-// describePointer runs the pointer analysis of the selected SSA value or address.
-func describePointer(o *Oracle, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
- buildSSA(o)
-
- if isAddr {
- o.config.AddIndirectQuery(v)
- } else {
- o.config.AddQuery(v)
- }
- ptares := ptrAnalysis(o)
-
- // Combine the PT sets from all contexts.
- var pointers []pointer.Pointer
- if isAddr {
- pointers = ptares.IndirectQueries[v]
- } else {
- pointers = ptares.Queries[v]
- }
- if pointers == nil {
- return nil, fmt.Errorf("PTA did not encounter this expression (dead code?)")
- }
- pts := pointer.PointsToCombined(pointers)
-
- if pointer.CanHaveDynamicTypes(v.Type()) {
- // Show concrete types for interface/reflect.Value expression.
- if concs := pts.DynamicTypes(); concs.Len() > 0 {
- concs.Iterate(func(conc types.Type, pta interface{}) {
- combined := pointer.PointsToCombined(pta.([]pointer.Pointer))
- labels := combined.Labels()
- sort.Sort(byPosAndString(labels)) // to ensure determinism
- ptrs = append(ptrs, pointerResult{conc, labels})
- })
- }
- } else {
- // Show labels for other expressions.
- labels := pts.Labels()
- sort.Sort(byPosAndString(labels)) // to ensure determinism
- ptrs = append(ptrs, pointerResult{v.Type(), labels})
- }
- sort.Sort(byTypeString(ptrs)) // to ensure determinism
- return ptrs, nil
-}
-
-type pointerResult struct {
- typ types.Type // type of the pointer (always concrete)
- labels []*pointer.Label
-}
-
type describeValueResult struct {
qpos *QueryPos
- expr ast.Expr // query node
- typ types.Type // type of expression
- constVal exact.Value // value of expression, if constant
- obj types.Object // var/func/const object, if expr was Ident
- ptaErr error // reason why pointer analysis couldn't be run, or failed
- ptrs []pointerResult // pointer info (typ is concrete => len==1)
+ expr ast.Expr // query node
+ typ types.Type // type of expression
+ constVal exact.Value // value of expression, if constant
+ obj types.Object // var/func/const object, if expr was Ident
}
func (r *describeValueResult) display(printf printfFunc) {
@@ -506,81 +368,16 @@ func (r *describeValueResult) display(printf printfFunc) {
printf(r.expr, "%s of type %s", desc, r.qpos.TypeString(r.typ))
}
}
-
- // pointer analysis could not be run
- if r.ptaErr != nil {
- printf(r.expr, "no points-to information: %s", r.ptaErr)
- return
- }
-
- if r.ptrs == nil {
- return // PTA was not invoked (not an error)
- }
-
- // Display the results of pointer analysis.
- if pointer.CanHaveDynamicTypes(r.typ) {
- // Show concrete types for interface, reflect.Type or
- // reflect.Value expression.
-
- if len(r.ptrs) > 0 {
- printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.TypeString(r.typ))
- for _, ptr := range r.ptrs {
- var obj types.Object
- if nt, ok := deref(ptr.typ).(*types.Named); ok {
- obj = nt.Obj()
- }
- if len(ptr.labels) > 0 {
- printf(obj, "\t%s, may point to:", r.qpos.TypeString(ptr.typ))
- printLabels(printf, ptr.labels, "\t\t")
- } else {
- printf(obj, "\t%s", r.qpos.TypeString(ptr.typ))
- }
- }
- } else {
- printf(r.qpos, "this %s cannot contain any dynamic types.", r.typ)
- }
- } else {
- // Show labels for other expressions.
- if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
- printf(r.qpos, "value may point to these labels:")
- printLabels(printf, ptr.labels, "\t")
- } else {
- printf(r.qpos, "value cannot point to anything.")
- }
- }
}
func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) {
- var value, objpos, ptaerr string
+ var value, objpos string
if r.constVal != nil {
value = r.constVal.String()
}
if r.obj != nil {
objpos = fset.Position(r.obj.Pos()).String()
}
- if r.ptaErr != nil {
- ptaerr = r.ptaErr.Error()
- }
-
- var pts []*serial.DescribePointer
- for _, ptr := range r.ptrs {
- var namePos string
- if nt, ok := deref(ptr.typ).(*types.Named); ok {
- namePos = fset.Position(nt.Obj().Pos()).String()
- }
- var labels []serial.DescribePTALabel
- for _, l := range ptr.labels {
- labels = append(labels, serial.DescribePTALabel{
- Pos: fset.Position(l.Pos()).String(),
- Desc: l.String(),
- })
- }
- pts = append(pts, &serial.DescribePointer{
- Type: r.qpos.TypeString(ptr.typ),
- NamePos: namePos,
- Labels: labels,
- })
- }
res.Describe = &serial.Describe{
Desc: astutil.NodeDescription(r.expr),
@@ -590,35 +387,10 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet)
Type: r.qpos.TypeString(r.typ),
Value: value,
ObjPos: objpos,
- PTAErr: ptaerr,
- PTS: pts,
},
}
}
-type byTypeString []pointerResult
-
-func (a byTypeString) Len() int { return len(a) }
-func (a byTypeString) Less(i, j int) bool { return a[i].typ.String() < a[j].typ.String() }
-func (a byTypeString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-
-type byPosAndString []*pointer.Label
-
-func (a byPosAndString) Len() int { return len(a) }
-func (a byPosAndString) Less(i, j int) bool {
- cmp := a[i].Pos() - a[j].Pos()
- return cmp < 0 || (cmp == 0 && a[i].String() < a[j].String())
-}
-func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-
-func printLabels(printf printfFunc, labels []*pointer.Label, prefix string) {
- // TODO(adonovan): due to context-sensitivity, many of these
- // labels may differ only by context, which isn't apparent.
- for _, label := range labels {
- printf(label, "%s%s", prefix, label)
- }
-}
-
// ---- TYPE ------------------------------------------------------------
func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResult, error) {
@@ -725,13 +497,9 @@ func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePacka
var pkg *types.Package
switch n := path[0].(type) {
case *ast.ImportSpec:
- // Most ImportSpecs have no .Name Ident so we can't
- // use ObjectOf.
- // We could use the types.Info.Implicits mechanism,
- // but it's easier just to look it up by name.
- description = "import of package " + n.Path.Value
- importPath, _ := strconv.Unquote(n.Path.Value)
- pkg = o.prog.ImportedPackage(importPath).Object
+ pkgname := qpos.info.ImportSpecPkg(n)
+ description = fmt.Sprintf("import of package %q", pkgname.Name())
+ pkg = pkgname.Pkg()
case *ast.Ident:
if _, isDef := path[1].(*ast.File); isDef {
@@ -771,7 +539,7 @@ func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePacka
}
}
- return &describePackageResult{o.prog.Fset, path[0], description, pkg, members}, nil
+ return &describePackageResult{o.fset, path[0], description, pkg, members}, nil
}
type describePackageResult struct {
@@ -801,7 +569,7 @@ func (r *describePackageResult) display(printf printfFunc) {
for _, mem := range r.members {
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
for _, meth := range mem.methods {
- printf(meth.Obj(), "\t\t%s", meth)
+ printf(meth.Obj(), "\t\t%s", types.SelectionString(r.pkg, meth))
}
}
}
@@ -904,7 +672,7 @@ func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResu
// Nothing much to say about statements.
description = astutil.NodeDescription(n)
}
- return &describeStmtResult{o.prog.Fset, path[0], description}, nil
+ return &describeStmtResult{o.fset, path[0], description}, nil
}
type describeStmtResult struct {
@@ -929,7 +697,7 @@ func (r *describeStmtResult) toSerial(res *serial.Result, fset *token.FileSet) {
// pathToString returns a string containing the concrete types of the
// nodes in path.
-func pathToString2(path []ast.Node) string {
+func pathToString(path []ast.Node) string {
var buf bytes.Buffer
fmt.Fprint(&buf, "[")
for i, n := range path {
diff --git a/oracle/freevars.go b/oracle/freevars.go
index 0241bc2..a142d15 100644
--- a/oracle/freevars.go
+++ b/oracle/freevars.go
@@ -5,7 +5,9 @@
package oracle
import (
+ "bytes"
"go/ast"
+ "go/printer"
"go/token"
"sort"
@@ -120,7 +122,7 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
}
typ := qpos.info.TypeOf(n.(ast.Expr))
- ref := freevarsRef{kind, o.printNode(n), typ, obj}
+ ref := freevarsRef{kind, printNode(o.fset, n), typ, obj}
refsMap[ref.ref] = ref
if prune {
@@ -140,14 +142,12 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
return &freevarsResult{
qpos: qpos,
- fset: o.prog.Fset,
refs: refs,
}, nil
}
type freevarsResult struct {
qpos *QueryPos
- fset *token.FileSet
refs []freevarsRef
}
@@ -164,7 +164,12 @@ func (r *freevarsResult) display(printf printfFunc) {
} else {
printf(r.qpos, "Free identifiers:")
for _, ref := range r.refs {
- printf(ref.obj, "%s %s %s", ref.kind, ref.ref, ref.typ)
+ // Avoid printing "type T T".
+ var typstr string
+ if ref.kind != "type" {
+ typstr = " " + types.TypeString(r.qpos.info.Pkg, ref.typ)
+ }
+ printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr)
}
}
}
@@ -190,3 +195,10 @@ type byRef []freevarsRef
func (p byRef) Len() int { return len(p) }
func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref }
func (p byRef) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+// printNode returns the pretty-printed syntax of n.
+func printNode(fset *token.FileSet, n ast.Node) string {
+ var buf bytes.Buffer
+ printer.Fprint(&buf, fset, n)
+ return buf.String()
+}
diff --git a/oracle/implements.go b/oracle/implements.go
index 76b2e1f..ec5924f 100644
--- a/oracle/implements.go
+++ b/oracle/implements.go
@@ -11,7 +11,7 @@ import (
"code.google.com/p/go.tools/oracle/serial"
)
-// Implements displays the 'implements" relation among all
+// Implements displays the "implements" relation among all
// package-level named types in the package containing the query
// position.
//
@@ -65,7 +65,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
}
// TODO(adonovan): sort facts to ensure test nondeterminism.
- return &implementsResult{o.prog.Fset, facts}, nil
+ return &implementsResult{o.fset, facts}, nil
}
type implementsFact struct {
diff --git a/oracle/oracle.go b/oracle/oracle.go
index 459435d..4d4887f 100644
--- a/oracle/oracle.go
+++ b/oracle/oracle.go
@@ -13,22 +13,48 @@ package oracle
// This file defines oracle.Query, the entry point for the oracle tool.
// The actual executable is defined in cmd/oracle.
-// TODO(adonovan): new query: show all statements that may update the
-// selected lvalue (local, global, field, etc).
+// TODO(adonovan): new queries
+// - show all statements that may update the selected lvalue
+// (local, global, field, etc).
+// - show all places where an object of type T is created
+// (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
+
+// ORACLE CONTROL FLOW
+//
+// The Oracle is somewhat convoluted due to the need to support two
+// very different use-cases, "one-shot" and "long running", and to do
+// so quickly.
+//
+// The cmd/oracle tool issues "one-shot" queries via the exported
+// Query function, which creates an Oracle to answer a single query.
+// newOracle consults the 'needs' flags of the query mode and the
+// package containing the query to avoid doing more work than it needs
+// (loading, parsing, type checking, SSA construction).
+//
+// The Pythia tool (github.com/fzipp/pythia‎) is an example of a "long
+// running" tool. It calls New() and then loops, calling
+// ParseQueryPos and (*Oracle).Query to handle each incoming HTTP
+// query. Since New cannot see which queries will follow, it must
+// load, parse, type-check and SSA-build the entire transitive closure
+// of the analysis scope, retaining full debug information and all
+// typed ASTs.
+//
+// TODO(adonovan): experiment with inverting the control flow by
+// making each mode consist of two functions: a "one-shot setup"
+// function and the existing "impl" function. The one-shot setup
+// function would do all of the work of Query and newOracle,
+// specialized to each mode, calling library utilities for the common
+// things. This would give it more control over "scope reduction".
+// Long running tools would not call the one-shot setup function but
+// would have their own setup function equivalent to the existing
+// 'needsAll' flow path.
import (
- "bytes"
"fmt"
"go/ast"
"go/build"
- "go/printer"
"go/token"
"io"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "time"
"code.google.com/p/go.tools/astutil"
"code.google.com/p/go.tools/go/types"
@@ -40,29 +66,25 @@ import (
// An Oracle holds the program state required for one or more queries.
type Oracle struct {
- out io.Writer // standard output
- prog *ssa.Program // the SSA program [only populated if need&SSA]
- config pointer.Config // pointer analysis configuration [TODO rename ptaConfig]
-
- // need&AllTypeInfo
- typeInfo map[*types.Package]*importer.PackageInfo // type info for all ASTs in the program
-
- timers map[string]time.Duration // phase timing information
+ 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]*importer.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
-// needAllTypeInfo is set.
+// needRetainTypeInfo is set.
const (
- needPos = 1 << iota // needs a position
- needExactPos // needs an exact AST selection; implies needPos
- needAllTypeInfo // 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)
+ 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 {
@@ -72,15 +94,20 @@ type modeInfo struct {
}
var modes = []*modeInfo{
+ // Pointer analyses: (whole program)
{"callees", needPTA | needExactPos, callees},
{"callers", needPTA | needPos, callers},
{"callgraph", needPTA, callgraph},
{"callstack", needPTA | needPos, callstack},
- {"describe", needPTA | needSSADebug | needExactPos, describe},
+ {"peers", needPTA | needSSADebug | needPos, peers},
+ {"pointsto", needPTA | needSSADebug | needExactPos, pointsto},
+
+ // Type-based analyses: (modular, mostly)
+ {"definition", needPos, definition},
+ {"describe", needExactPos, describe},
{"freevars", needPos, freevars},
{"implements", needPos, implements},
- {"peers", needPTA | needSSADebug | needPos, peers},
- {"referrers", needAllTypeInfo | needPos, referrers},
+ {"referrers", needRetainTypeInfo | needPos, referrers}, // (whole-program)
}
func findMode(mode string) *modeInfo {
@@ -106,9 +133,11 @@ type queryResult interface {
// Instances are created by ParseQueryPos.
//
type QueryPos struct {
+ fset *token.FileSet
start, end token.Pos // source extent of query
- info *importer.PackageInfo // type info for the queried package
path []ast.Node // AST path from query node to root of ast.File
+ exact bool // 2nd result of PathEnclosingInterval
+ info *importer.PackageInfo // type info for the queried package (nil for fastQueryPos)
}
// TypeString prints type T relative to the query position.
@@ -128,9 +157,7 @@ func (qpos *QueryPos) SelectionString(sel *types.Selection) string {
// A Result encapsulates the result of an oracle.Query.
type Result struct {
- fset *token.FileSet
- // fprintf is a closure over the oracle's fileset and start/end position.
- fprintf func(w io.Writer, pos interface{}, format string, args ...interface{})
+ fset *token.FileSet
q queryResult // the query-specific result
mode string // query mode
warnings []pointer.Warning // pointer analysis warnings
@@ -180,31 +207,38 @@ func (res *Result) Serial() *serial.Result {
// 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)
if minfo == nil {
return nil, fmt.Errorf("invalid mode type: %q", mode)
}
- imp := importer.New(&importer.Config{Build: buildContext})
- o, err := New(imp, args, ptalog, reflection)
- if err != nil {
- return nil, err
+ impcfg := importer.Config{Build: buildContext}
+
+ // For queries needing only a single typed package,
+ // reduce the analysis scope to that package.
+ if minfo.needs&(needSSA|needRetainTypeInfo) == 0 {
+ reduceScope(pos, &impcfg, &args)
}
- // Phase timing diagnostics.
- // TODO(adonovan): needs more work.
- // if false {
- // defer func() {
- // fmt.Println()
- // for name, duration := range o.timers {
- // fmt.Printf("# %-30s %s\n", name, duration)
- // }
- // }()
+ // TODO(adonovan): report type errors to the user via Serial
+ // types, not stderr?
+ // impcfg.TypeChecker.Error = func(err error) {
+ // E := err.(types.Error)
+ // fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg)
// }
+ imp := importer.New(&impcfg)
+ o, err := newOracle(imp, args, ptalog, minfo.needs, reflection)
+ if err != nil {
+ return nil, err
+ }
var qpos *QueryPos
if minfo.needs&(needPos|needExactPos) != 0 {
- var err error
qpos, err = ParseQueryPos(imp, pos, minfo.needs&needExactPos != 0)
if err != nil {
return nil, err
@@ -218,6 +252,58 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
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, impcfg *importer.Config, args *[]string) {
+ // TODO(adonovan): make the 'args' argument of
+ // (*Importer).LoadInitialPackages part of the
+ // importer.Config, and inline LoadInitialPackages into
+ // NewImporter. Then we won't need the 'args' argument.
+
+ 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(), impcfg.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 importer/util.go!
+ ctxt2 := *impcfg.Build
+ ctxt2.CgoEnabled = false
+ bp, err := ctxt2.Import(importPath, "", 0)
+ if err != nil {
+ return // no files for package
+ }
+ _ = bp
+
+ // TODO(adonovan): fix: also check that the queried file appears in the package.
+ // for _, f := range bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles {
+ // if sameFile(f, fqpos.filename) { goto found }
+ // }
+ // return // not found
+ // found:
+
+ impcfg.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
+ *args = []string{importPath}
+}
+
// New constructs a new Oracle that can be used for a sequence of queries.
//
// imp will be used to load source code for imported packages.
@@ -233,15 +319,9 @@ func New(imp *importer.Importer, args []string, ptalog io.Writer, reflection boo
}
func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
- o := &Oracle{
- prog: ssa.NewProgram(imp.Fset, 0),
- timers: make(map[string]time.Duration),
- }
- o.config.Log = ptalog
- o.config.Reflection = reflection
+ o := &Oracle{fset: imp.Fset}
// Load/parse/type-check program from args.
- start := time.Now()
initialPkgInfos, args, err := imp.LoadInitialPackages(args)
if err != nil {
return nil, err // I/O or parser error
@@ -249,10 +329,9 @@ func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs in
if len(args) > 0 {
return nil, fmt.Errorf("surplus arguments: %q", args)
}
- o.timers["load/parse/type"] = time.Since(start)
// Retain type info for all ASTs in the program.
- if needs&needAllTypeInfo != 0 {
+ if needs&needRetainTypeInfo != 0 {
m := make(map[*types.Package]*importer.PackageInfo)
for _, p := range imp.AllPackages() {
m[p.Pkg] = p
@@ -262,49 +341,56 @@ func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs in
// Create SSA package for the initial packages and their dependencies.
if needs&needSSA != 0 {
- start = time.Now()
+ prog := ssa.NewProgram(o.fset, 0)
// Create SSA packages.
- if err := o.prog.CreatePackages(imp); err != nil {
+ if err := prog.CreatePackages(imp); err != nil {
return nil, err
}
// For each initial package (specified on the command line),
// if it has a main function, analyze that,
// otherwise analyze its tests, if any.
- var testPkgs []*ssa.Package
+ var testPkgs, mains []*ssa.Package
for _, info := range initialPkgInfos {
- initialPkg := o.prog.Package(info.Pkg)
+ initialPkg := prog.Package(info.Pkg)
// Add package to the pointer analysis scope.
if initialPkg.Func("main") != nil {
- o.config.Mains = append(o.config.Mains, initialPkg)
+ mains = append(mains, initialPkg)
} else {
testPkgs = append(testPkgs, initialPkg)
}
}
if testPkgs != nil {
- if p := o.prog.CreateTestMainPackage(testPkgs...); p != nil {
- o.config.Mains = append(o.config.Mains, p)
+ if p := prog.CreateTestMainPackage(testPkgs...); p != nil {
+ mains = append(mains, p)
}
}
- if o.config.Mains == nil {
+ if mains == nil {
return nil, fmt.Errorf("analysis scope has no main and no tests")
}
+ o.ptaConfig.Log = ptalog
+ o.ptaConfig.Reflection = reflection
+ o.ptaConfig.Mains = mains
if needs&needSSADebug != 0 {
- for _, pkg := range o.prog.AllPackages() {
+ for _, pkg := range prog.AllPackages() {
pkg.SetDebugMode(true)
}
}
- o.timers["SSA-create"] = time.Since(start)
+ o.prog = prog
}
return o, nil
}
// Query runs the query of the specified mode and selection.
+//
+// TODO(adonovan): fix: this function does not currently support the
+// "what" query, which needs to access the go/build.Context.
+//
func (o *Oracle) Query(mode string, qpos *QueryPos) (*Result, error) {
minfo := findMode(mode)
if minfo == nil {
@@ -314,10 +400,13 @@ func (o *Oracle) Query(mode string, qpos *QueryPos) (*Result, error) {
}
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.prog.Fset,
- fprintf: o.fprintf, // captures o.prog, o.{start,end}Pos for later printing
+ mode: minfo.name,
+ fset: o.fset,
}
var err error
res.q, err = minfo.impl(o, qpos)
@@ -328,10 +417,16 @@ func (o *Oracle) query(minfo *modeInfo, qpos *QueryPos) (*Result, error) {
}
// ParseQueryPos parses the source query position pos.
-// 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,
+// e.g. "describe".
//
-func ParseQueryPos(imp *importer.Importer, pos string, needExact bool) (*QueryPos, error) {
- start, end, err := parseQueryPos(imp.Fset, pos)
+func ParseQueryPos(imp *importer.Importer, posFlag string, needExact bool) (*QueryPos, error) {
+ filename, startOffset, endOffset, err := parsePosFlag(posFlag)
+ if err != nil {
+ return nil, err
+ }
+ start, end, err := findQueryPos(imp.Fset, filename, startOffset, endOffset)
if err != nil {
return nil, err
}
@@ -342,13 +437,13 @@ func ParseQueryPos(imp *importer.Importer, pos string, needExact bool) (*QueryPo
if needExact && !exact {
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
}
- return &QueryPos{start, end, info, path}, nil
+ return &QueryPos{imp.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{}) {
- res.fprintf(out, pos, format, args...)
+ fprintf(out, res.fset, pos, format, args...)
}
res.q.display(printf)
@@ -367,110 +462,12 @@ func (res *Result) WriteTo(out io.Writer) {
// Not needed in simpler modes, e.g. freevars.
//
func buildSSA(o *Oracle) {
- start := time.Now()
o.prog.BuildAll()
- o.timers["SSA-build"] = time.Since(start)
}
// ptrAnalysis runs the pointer analysis and returns its result.
func ptrAnalysis(o *Oracle) *pointer.Result {
- start := time.Now()
- result := pointer.Analyze(&o.config)
- o.timers["pointer analysis"] = time.Since(start)
- return result
-}
-
-// parseOctothorpDecimal returns the numeric value if s matches "#%d",
-// otherwise -1.
-func parseOctothorpDecimal(s string) int {
- if s != "" && s[0] == '#' {
- if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
- return int(s)
- }
- }
- return -1
-}
-
-// parseQueryPos parses a string of the form "file:pos" or
-// file:start,end" where pos, start, end match #%d and represent byte
-// offsets, and returns the extent to which it refers.
-//
-// (Numbers without a '#' prefix are reserved for future use,
-// e.g. to indicate line/column positions.)
-//
-func parseQueryPos(fset *token.FileSet, queryPos string) (start, end token.Pos, err error) {
- if queryPos == "" {
- err = fmt.Errorf("no source position specified (-pos flag)")
- return
- }
-
- colon := strings.LastIndex(queryPos, ":")
- if colon < 0 {
- err = fmt.Errorf("invalid source position -pos=%q", queryPos)
- return
- }
- filename, offset := queryPos[:colon], queryPos[colon+1:]
- startOffset := -1
- endOffset := -1
- if hyphen := strings.Index(offset, ","); hyphen < 0 {
- // e.g. "foo.go:#123"
- startOffset = parseOctothorpDecimal(offset)
- endOffset = startOffset
- } else {
- // e.g. "foo.go:#123,#456"
- startOffset = parseOctothorpDecimal(offset[:hyphen])
- endOffset = parseOctothorpDecimal(offset[hyphen+1:])
- }
- if startOffset < 0 || endOffset < 0 {
- err = fmt.Errorf("invalid -pos offset %q", offset)
- return
- }
-
- var file *token.File
- fset.Iterate(func(f *token.File) bool {
- if sameFile(filename, f.Name()) {
- // (f.Name() is absolute)
- file = f
- return false // done
- }
- return true // continue
- })
- if file == nil {
- err = fmt.Errorf("couldn't find file containing position -pos=%q", queryPos)
- return
- }
-
- // Range check [start..end], inclusive of both end-points.
-
- if 0 <= startOffset && startOffset <= file.Size() {
- start = file.Pos(int(startOffset))
- } else {
- err = fmt.Errorf("start position is beyond end of file -pos=%q", queryPos)
- return
- }
-
- if 0 <= endOffset && endOffset <= file.Size() {
- end = file.Pos(int(endOffset))
- } else {
- err = fmt.Errorf("end position is beyond end of file -pos=%q", queryPos)
- return
- }
-
- return
-}
-
-// sameFile returns true if x and y have the same basename and denote
-// the same file.
-//
-func sameFile(x, y string) bool {
- if filepath.Base(x) == filepath.Base(y) { // (optimisation)
- if xi, err := os.Stat(x); err == nil {
- if yi, err := os.Stat(y); err == nil {
- return os.SameFile(xi, yi)
- }
- }
- }
- return false
+ return pointer.Analyze(&o.ptaConfig)
}
// unparen returns e with any enclosing parentheses stripped.
@@ -508,7 +505,7 @@ func deref(typ types.Type) types.Type {
// compilation-error-regexp in Emacs' compilation mode.
// TODO(adonovan): support other editors.
//
-func (o *Oracle) fprintf(w io.Writer, pos interface{}, format string, args ...interface{}) {
+func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) {
var start, end token.Pos
switch pos := pos.(type) {
case ast.Node:
@@ -531,11 +528,11 @@ func (o *Oracle) fprintf(w io.Writer, pos interface{}, format string, args ...in
panic(fmt.Sprintf("invalid pos: %T", pos))
}
- if sp := o.prog.Fset.Position(start); start == end {
+ if sp := fset.Position(start); start == end {
// (prints "-: " for token.NoPos)
fmt.Fprintf(w, "%s: ", sp)
} else {
- ep := o.prog.Fset.Position(end)
+ ep := fset.Position(end)
// The -1 below is a concession to Emacs's broken use of
// inclusive (not half-open) intervals.
// Other editors may not want it.
@@ -547,10 +544,3 @@ func (o *Oracle) fprintf(w io.Writer, pos interface{}, format string, args ...in
fmt.Fprintf(w, format, args...)
io.WriteString(w, "\n")
}
-
-// printNode returns the pretty-printed syntax of n.
-func (o *Oracle) printNode(n ast.Node) string {
- var buf bytes.Buffer
- printer.Fprint(&buf, o.prog.Fset, n)
- return buf.String()
-}
diff --git a/oracle/oracle_test.go b/oracle/oracle_test.go
index f78a5d0..03c3ea2 100644
--- a/oracle/oracle_test.go
+++ b/oracle/oracle_test.go
@@ -206,13 +206,17 @@ func TestOracle(t *testing.T) {
"testdata/src/main/implements.go",
"testdata/src/main/imports.go",
"testdata/src/main/peers.go",
+ "testdata/src/main/pointsto.go",
"testdata/src/main/reflection.go",
+ "testdata/src/main/what.go",
// JSON:
"testdata/src/main/callgraph-json.go",
"testdata/src/main/calls-json.go",
"testdata/src/main/peers-json.go",
"testdata/src/main/describe-json.go",
+ "testdata/src/main/pointsto-json.go",
"testdata/src/main/referrers-json.go",
+ "testdata/src/main/what-json.go",
} {
useJson := strings.HasSuffix(filename, "-json.go")
queries := parseQueries(t, filename)
diff --git a/oracle/peers.go b/oracle/peers.go
index bfd2e11..13a91d3 100644
--- a/oracle/peers.go
+++ b/oracle/peers.go
@@ -60,11 +60,11 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
// ignore both directionality and type names.
queryType := queryOp.ch.Type()
queryElemType := queryType.Underlying().(*types.Chan).Elem()
- o.config.AddQuery(queryOp.ch)
+ o.ptaConfig.AddQuery(queryOp.ch)
i := 0
for _, op := range ops {
if types.IsIdentical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
- o.config.AddQuery(op.ch)
+ o.ptaConfig.AddQuery(op.ch)
ops[i] = op
i++
}
diff --git a/oracle/pointsto.go b/oracle/pointsto.go
new file mode 100644
index 0000000..9ebaf5f
--- /dev/null
+++ b/oracle/pointsto.go
@@ -0,0 +1,257 @@
+// 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/ast"
+ "go/token"
+ "sort"
+
+ "code.google.com/p/go.tools/astutil"
+ "code.google.com/p/go.tools/go/types"
+ "code.google.com/p/go.tools/importer"
+ "code.google.com/p/go.tools/oracle/serial"
+ "code.google.com/p/go.tools/pointer"
+ "code.google.com/p/go.tools/ssa"
+)
+
+// pointsto runs the pointer analysis on the selected expression,
+// and reports its points-to set (for a pointer-like expression)
+// or its dynamic types (for an interface, reflect.Value, or
+// reflect.Type expression) and their points-to sets.
+//
+// All printed sets are sorted to ensure determinism.
+//
+func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) {
+ path, action := findInterestingNode(qpos.info, qpos.path)
+ if action != actionExpr {
+ return nil, fmt.Errorf("pointer analysis wants an expression; got %s",
+ astutil.NodeDescription(qpos.path[0]))
+ }
+
+ var expr ast.Expr
+ var obj types.Object
+ switch n := path[0].(type) {
+ case *ast.ValueSpec:
+ // ambiguous ValueSpec containing multiple names
+ return nil, fmt.Errorf("multiple value specification")
+ case *ast.Ident:
+ obj = qpos.info.ObjectOf(n)
+ expr = n
+ case ast.Expr:
+ expr = n
+ default:
+ // TODO(adonovan): is this reachable?
+ return nil, fmt.Errorf("unexpected AST for expr: %T", n)
+ }
+
+ // Reject non-pointerlike types (includes all constants).
+ typ := qpos.info.TypeOf(expr)
+ if !pointer.CanPoint(typ) {
+ return nil, fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ)
+ }
+
+ // Determine the ssa.Value for the expression.
+ var value ssa.Value
+ var isAddr bool
+ var err error
+ if obj != nil {
+ // def/ref of func/var object
+ value, isAddr, err = ssaValueForIdent(o.prog, qpos.info, obj, path)
+ } else {
+ value, isAddr, err = ssaValueForExpr(o.prog, qpos.info, path)
+ }
+ if err != nil {
+ return nil, err // e.g. trivially dead code
+ }
+
+ // Run the pointer analysis.
+ ptrs, err := runPTA(o, value, isAddr)
+ if err != nil {
+ return nil, err // e.g. analytically unreachable
+ }
+
+ return &pointstoResult{
+ qpos: qpos,
+ typ: typ,
+ ptrs: ptrs,
+ }, nil
+}
+
+// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
+// to the root of the AST is path. isAddr reports whether the
+// ssa.Value is the address denoted by the ast.Ident, not its value.
+//
+func ssaValueForIdent(prog *ssa.Program, qinfo *importer.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
+ switch obj := obj.(type) {
+ case *types.Var:
+ pkg := prog.Package(qinfo.Pkg)
+ pkg.Build()
+ if v, addr := prog.VarValue(obj, pkg, path); v != nil {
+ return v, addr, nil
+ }
+ return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
+
+ case *types.Func:
+ return prog.FuncValue(obj), false, nil
+ }
+ panic(obj)
+}
+
+// ssaValueForExpr returns the ssa.Value of the non-ast.Ident
+// expression whose path to the root of the AST is path.
+//
+func ssaValueForExpr(prog *ssa.Program, qinfo *importer.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
+ pkg := prog.Package(qinfo.Pkg)
+ pkg.SetDebugMode(true)
+ pkg.Build()
+
+ fn := ssa.EnclosingFunction(pkg, path)
+ if fn == nil {
+ return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)")
+ }
+
+ if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
+ return v, addr, nil
+ }
+
+ return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn)
+}
+
+// runPTA runs the pointer analysis of the selected SSA value or address.
+func runPTA(o *Oracle, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
+ buildSSA(o)
+
+ if isAddr {
+ o.ptaConfig.AddIndirectQuery(v)
+ } else {
+ o.ptaConfig.AddQuery(v)
+ }
+ ptares := ptrAnalysis(o)
+
+ // Combine the PT sets from all contexts.
+ var pointers []pointer.Pointer
+ if isAddr {
+ pointers = ptares.IndirectQueries[v]
+ } else {
+ pointers = ptares.Queries[v]
+ }
+ if pointers == nil {
+ return nil, fmt.Errorf("pointer analysis did not find expression (dead code?)")
+ }
+ pts := pointer.PointsToCombined(pointers)
+
+ if pointer.CanHaveDynamicTypes(v.Type()) {
+ // Show concrete types for interface/reflect.Value expression.
+ if concs := pts.DynamicTypes(); concs.Len() > 0 {
+ concs.Iterate(func(conc types.Type, pta interface{}) {
+ combined := pointer.PointsToCombined(pta.([]pointer.Pointer))
+ labels := combined.Labels()
+ sort.Sort(byPosAndString(labels)) // to ensure determinism
+ ptrs = append(ptrs, pointerResult{conc, labels})
+ })
+ }
+ } else {
+ // Show labels for other expressions.
+ labels := pts.Labels()
+ sort.Sort(byPosAndString(labels)) // to ensure determinism
+ ptrs = append(ptrs, pointerResult{v.Type(), labels})
+ }
+ sort.Sort(byTypeString(ptrs)) // to ensure determinism
+ return ptrs, nil
+}
+
+type pointerResult struct {
+ typ types.Type // type of the pointer (always concrete)
+ labels []*pointer.Label // set of labels
+}
+
+type pointstoResult struct {
+ qpos *QueryPos
+ typ types.Type // type of expression
+ ptrs []pointerResult // pointer info (typ is concrete => len==1)
+}
+
+func (r *pointstoResult) display(printf printfFunc) {
+ if pointer.CanHaveDynamicTypes(r.typ) {
+ // Show concrete types for interface, reflect.Type or
+ // reflect.Value expression.
+
+ if len(r.ptrs) > 0 {
+ printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.TypeString(r.typ))
+ for _, ptr := range r.ptrs {
+ var obj types.Object
+ if nt, ok := deref(ptr.typ).(*types.Named); ok {
+ obj = nt.Obj()
+ }
+ if len(ptr.labels) > 0 {
+ printf(obj, "\t%s, may point to:", r.qpos.TypeString(ptr.typ))
+ printLabels(printf, ptr.labels, "\t\t")
+ } else {
+ printf(obj, "\t%s", r.qpos.TypeString(ptr.typ))
+ }
+ }
+ } else {
+ printf(r.qpos, "this %s cannot contain any dynamic types.", r.typ)
+ }
+ } else {
+ // Show labels for other expressions.
+ if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
+ printf(r.qpos, "this %s may point to these objects:",
+ r.qpos.TypeString(r.typ))
+ printLabels(printf, ptr.labels, "\t")
+ } else {
+ printf(r.qpos, "this %s may not point to anything.",
+ r.qpos.TypeString(r.typ))
+ }
+ }
+}
+
+func (r *pointstoResult) toSerial(res *serial.Result, fset *token.FileSet) {
+ var pts []serial.PointsTo
+ for _, ptr := range r.ptrs {
+ var namePos string
+ if nt, ok := deref(ptr.typ).(*types.Named); ok {
+ namePos = fset.Position(nt.Obj().Pos()).String()
+ }
+ var labels []serial.PointsToLabel
+ for _, l := range ptr.labels {
+ labels = append(labels, serial.PointsToLabel{
+ Pos: fset.Position(l.Pos()).String(),
+ Desc: l.String(),
+ })
+ }
+ pts = append(pts, serial.PointsTo{
+ Type: r.qpos.TypeString(ptr.typ),
+ NamePos: namePos,
+ Labels: labels,
+ })
+ }
+ res.PointsTo = pts
+}
+
+type byTypeString []pointerResult
+
+func (a byTypeString) Len() int { return len(a) }
+func (a byTypeString) Less(i, j int) bool { return a[i].typ.String() < a[j].typ.String() }
+func (a byTypeString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+type byPosAndString []*pointer.Label
+
+func (a byPosAndString) Len() int { return len(a) }
+func (a byPosAndString) Less(i, j int) bool {
+ cmp := a[i].Pos() - a[j].Pos()
+ return cmp < 0 || (cmp == 0 && a[i].String() < a[j].String())
+}
+func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+func printLabels(printf printfFunc, labels []*pointer.Label, prefix string) {
+ // TODO(adonovan): due to context-sensitivity, many of these
+ // labels may differ only by context, which isn't apparent.
+ for _, label := range labels {
+ printf(label, "%s%s", prefix, label)
+ }
+}
diff --git a/oracle/pos.go b/oracle/pos.go
new file mode 100644
index 0000000..4ae30a6
--- /dev/null
+++ b/oracle/pos.go
@@ -0,0 +1,149 @@
+package oracle
+
+// This file defines utilities for working with file positions.
+
+import (
+ "fmt"
+ "go/parser"
+ "go/token"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "code.google.com/p/go.tools/astutil"
+)
+
+// parseOctothorpDecimal returns the numeric value if s matches "#%d",
+// otherwise -1.
+func parseOctothorpDecimal(s string) int {
+ if s != "" && s[0] == '#' {
+ if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
+ return int(s)
+ }
+ }
+ return -1
+}
+
+// parsePosFlag parses a string of the form "file:pos" or
+// file:start,end" where pos, start, end match #%d and represent byte
+// offsets, and returns its components.
+//
+// (Numbers without a '#' prefix are reserved for future use,
+// e.g. to indicate line/column positions.)
+//
+func parsePosFlag(posFlag string) (filename string, startOffset, endOffset int, err error) {
+ if posFlag == "" {
+ err = fmt.Errorf("no source position specified (-pos flag)")
+ return
+ }
+
+ colon := strings.LastIndex(posFlag, ":")
+ if colon < 0 {
+ err = fmt.Errorf("invalid source position -pos=%q", posFlag)
+ return
+ }
+ filename, offset := posFlag[:colon], posFlag[colon+1:]
+ startOffset = -1
+ endOffset = -1
+ if hyphen := strings.Index(offset, ","); hyphen < 0 {
+ // e.g. "foo.go:#123"
+ startOffset = parseOctothorpDecimal(offset)
+ endOffset = startOffset
+ } else {
+ // e.g. "foo.go:#123,#456"
+ startOffset = parseOctothorpDecimal(offset[:hyphen])
+ endOffset = parseOctothorpDecimal(offset[hyphen+1:])
+ }
+ if startOffset < 0 || endOffset < 0 {
+ err = fmt.Errorf("invalid -pos offset %q", offset)
+ return
+ }
+ return
+}
+
+// findQueryPos searches fset for filename and translates the
+// specified file-relative byte offsets into token.Pos form. It
+// returns an error if the file was not found or the offsets were out
+// of bounds.
+//
+func findQueryPos(fset *token.FileSet, filename string, startOffset, endOffset int) (start, end token.Pos, err error) {
+ var file *token.File
+ fset.Iterate(func(f *token.File) bool {
+ if sameFile(filename, f.Name()) {
+ // (f.Name() is absolute)
+ file = f
+ return false // done
+ }
+ return true // continue
+ })
+ if file == nil {
+ err = fmt.Errorf("couldn't find file containing position")
+ return
+ }
+
+ // Range check [start..end], inclusive of both end-points.
+
+ if 0 <= startOffset && startOffset <= file.Size() {
+ start = file.Pos(int(startOffset))
+ } else {
+ err = fmt.Errorf("start position is beyond end of file")
+ return
+ }
+
+ if 0 <= endOffset && endOffset <= file.Size() {
+ end = file.Pos(int(endOffset))
+ } else {
+ err = fmt.Errorf("end position is beyond end of file")
+ return
+ }
+
+ return
+}
+
+// sameFile returns true if x and y have the same basename and denote
+// the same file.
+//
+func sameFile(x, y string) bool {
+ if filepath.Base(x) == filepath.Base(y) { // (optimisation)
+ if xi, err := os.Stat(x); err == nil {
+ if yi, err := os.Stat(y); err == nil {
+ return os.SameFile(xi, yi)
+ }
+ }
+ }
+ return false
+}
+
+// fastQueryPos parses the -pos flag and returns a QueryPos.
+// It parses only a single file, and does not run the type checker.
+//
+// Caveat: the token.{FileSet,Pos} info it contains is not comparable
+// with that from the oracle's FileSet! (We don't accept oracle.fset
+// as a parameter because we don't want the same filename to appear
+// multiple times in one FileSet.)
+//
+func fastQueryPos(posFlag string) (*QueryPos, error) {
+ filename, startOffset, endOffset, err := parsePosFlag(posFlag)
+ if err != nil {
+ return nil, err
+ }
+
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, filename, nil, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ start, end, err := findQueryPos(fset, filename, startOffset, endOffset)
+ if err != nil {
+ return nil, err
+ }
+
+ path, exact := astutil.PathEnclosingInterval(f, start, end)
+ if path == nil {
+ return nil, fmt.Errorf("no syntax here")
+ }
+
+ return &QueryPos{fset, start, end, path, exact, nil}, nil
+}
diff --git a/oracle/referrers.go b/oracle/referrers.go
index 0388930..24b5414 100644
--- a/oracle/referrers.go
+++ b/oracle/referrers.go
@@ -30,21 +30,21 @@ func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) {
}
// Iterate over all go/types' resolver facts for the entire program.
- var refs []token.Pos
+ var refs []*ast.Ident
for _, info := range o.typeInfo {
for id2, obj2 := range info.Objects {
if sameObj(obj, obj2) {
if id2.NamePos == obj.Pos() {
continue // skip defining ident
}
- refs = append(refs, id2.NamePos)
+ refs = append(refs, id2)
}
}
}
- sort.Sort(byPos(refs))
+ sort.Sort(byNamePos(refs))
return &referrersResult{
- query: id.NamePos,
+ query: id,
obj: obj,
refs: refs,
}, nil
@@ -65,14 +65,22 @@ func sameObj(x, y types.Object) bool {
return false
}
+// -------- utils --------
+
+type byNamePos []*ast.Ident
+
+func (p byNamePos) Len() int { return len(p) }
+func (p byNamePos) Less(i, j int) bool { return p[i].NamePos < p[j].NamePos }
+func (p byNamePos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
type referrersResult struct {
- query token.Pos // identifer of query
+ query *ast.Ident // identifer of query
obj types.Object // object it denotes
- refs []token.Pos // set of all other references to it
+ refs []*ast.Ident // set of all other references to it
}
func (r *referrersResult) display(printf printfFunc) {
- if r.query != r.obj.Pos() {
+ if r.query.Pos() != r.obj.Pos() {
printf(r.query, "reference to %s", r.obj.Name())
}
// TODO(adonovan): pretty-print object using same logic as
@@ -85,16 +93,18 @@ func (r *referrersResult) display(printf printfFunc) {
}
}
+// TODO(adonovan): encode extent, not just Pos info, in Serial form.
+
func (r *referrersResult) toSerial(res *serial.Result, fset *token.FileSet) {
referrers := &serial.Referrers{
- Pos: fset.Position(r.query).String(),
+ Pos: fset.Position(r.query.Pos()).String(),
Desc: r.obj.String(),
}
if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos()
referrers.ObjPos = fset.Position(pos).String()
}
for _, ref := range r.refs {
- referrers.Refs = append(referrers.Refs, fset.Position(ref).String())
+ referrers.Refs = append(referrers.Refs, fset.Position(ref.NamePos).String())
}
res.Referrers = referrers
}
diff --git a/oracle/serial/serial.go b/oracle/serial/serial.go
index ac1ee6d..afb9526 100644
--- a/oracle/serial/serial.go
+++ b/oracle/serial/serial.go
@@ -32,6 +32,12 @@ type Referrers struct {
Refs []string `json:"refs,omitempty"` // locations of all references
}
+// A Definition is the result of a 'definition' query.
+type Definition struct {
+ ObjPos string `json:"objpos,omitempty"` // location of the definition
+ Desc string `json:"desc"` // description of the denoted object
+}
+
type CalleesItem struct {
Name string `json:"name"` // full name of called function
Pos string `json:"pos"` // location of called function
@@ -104,7 +110,25 @@ type Implements struct {
CPos string `json:"cpos"` // location of its definition
}
-// A DescribePTALabel describes a pointer analysis label.
+// A SyntaxNode is one element of a stack of enclosing syntax nodes in
+// a "what" query.
+type SyntaxNode struct {
+ Description string `json:"desc"` // description of syntax tree
+ Start int `json:"start"` // start offset (0-based)
+ End int `json:"end"` // end offset
+}
+
+// A What is the result of the "what" query, which quickly identifies
+// the selection, parsing only a single file. It is intended for use
+// in low-latency GUIs.
+type What struct {
+ Enclosing []SyntaxNode `json:"enclosing"` // enclosing nodes of syntax tree
+ Modes []string `json:"modes"` // query modes enabled for this selection.
+ SrcDir string `json:"srcdir,omitempty"` // $GOROOT src directory containing queried package
+ ImportPath string `json:"importpath,omitempty"` // import path of queried package
+}
+
+// A PointsToLabel describes a pointer analysis label.
//
// A "label" is an object that may be pointed to by a pointer, map,
// channel, 'func', slice or interface. Labels include:
@@ -116,35 +140,33 @@ type Implements struct {
// - channels, maps and arrays created by make()
// - and their subelements, e.g. "alloc.y[*].z"
//
-type DescribePTALabel struct {
+type PointsToLabel struct {
Pos string `json:"pos"` // location of syntax that allocated the object
Desc string `json:"desc"` // description of the label
}
-// A DescribePointer describes a single pointer: its type and the
-// set of "labels" it points to.
+// A PointsTo is one element of the result of a 'pointsto' query on an
+// expression. It describes a single pointer: its type and the set of
+// "labels" it points to.
//
-type DescribePointer struct {
- Type string `json:"type"` // (concrete) type of the pointer
- NamePos string `json:"namepos,omitempty"` // location of type defn, if Named
- Labels []DescribePTALabel `json:"labels,omitempty"` // pointed-to objects
-}
-
-// A DescribeValue is the additional result of a 'describe' query
-// if the selection indicates a value or expression.
-//
-// If the described value is an interface, it will have one PTS entry
+// If the pointer is of interface type, it will have one PTS entry
// describing each concrete type that it may contain. For each
// concrete type that is a pointer, the PTS entry describes the labels
// it may point to. The same is true for reflect.Values, except the
// dynamic types needn't be concrete.
//
+type PointsTo struct {
+ Type string `json:"type"` // (concrete) type of the pointer
+ NamePos string `json:"namepos,omitempty"` // location of type defn, if Named
+ Labels []PointsToLabel `json:"labels,omitempty"` // pointed-to objects
+}
+
+// A DescribeValue is the additional result of a 'describe' query
+// if the selection indicates a value or expression.
type DescribeValue struct {
- Type string `json:"type"` // type of the expression
- Value string `json:"value,omitempty"` // value of the expression, if constant
- ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
- PTAErr string `json:"ptaerr,omitempty"` // reason pointer analysis wasn't attempted
- PTS []*DescribePointer `json:"pts,omitempty"` // points-to set; an interface may have many
+ Type string `json:"type"` // type of the expression
+ Value string `json:"value,omitempty"` // value of the expression, if constant
+ ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
}
type DescribeMethod struct {
@@ -200,8 +222,6 @@ type PTAWarning struct {
// A Result is the common result of any oracle query.
// It contains a query-specific result element.
//
-// TODO(adonovan): perhaps include other info such as: analysis scope,
-// raw query position, stack of ast nodes, query package, etc.
type Result struct {
Mode string `json:"mode"` // mode of the query
@@ -211,11 +231,14 @@ type Result struct {
Callers []Caller `json:"callers,omitempty"`
Callgraph []CallGraph `json:"callgraph,omitempty"`
Callstack *CallStack `json:"callstack,omitempty"`
+ Definition *Definition `json:"definition,omitempty"`
Describe *Describe `json:"describe,omitempty"`
Freevars []*FreeVar `json:"freevars,omitempty"`
Implements []*Implements `json:"implements,omitempty"`
Peers *Peers `json:"peers,omitempty"`
+ PointsTo []PointsTo `json:"pointsto,omitempty"`
Referrers *Referrers `json:"referrers,omitempty"`
+ What *What `json:"what,omitempty"`
Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis
}
diff --git a/oracle/testdata/src/main/calls.go b/oracle/testdata/src/main/calls.go
index bd117d7..1135f7e 100644
--- a/oracle/testdata/src/main/calls.go
+++ b/oracle/testdata/src/main/calls.go
@@ -4,12 +4,12 @@ package main
// See go.tools/oracle/oracle_test.go for explanation.
// See calls.golden for expected query results.
-func A(x *int) { // @describe describe-A-x "x"
+func A(x *int) { // @pointsto pointsto-A-x "x"
// @callers callers-A "^"
// @callstack callstack-A "^"
}
-func B(x *int) { // @describe describe-B-x "x"
+func B(x *int) { // @pointsto pointsto-B-x "x"
// @callers callers-B "^"
}
@@ -28,7 +28,7 @@ func store(ptr **int, value *int) {
func call(f func() *int) {
// Result points to anon function.
- f() // @describe describe-result-f "f"
+ f() // @pointsto pointsto-result-f "f"
// Target of call is anon function.
f() // @callees callees-main.call-f "f"
@@ -42,10 +42,10 @@ func main() {
apply(B, &b)
var c, d int
- var pc, pd *int // @describe describe-pc "pc"
+ var pc, pd *int // @pointsto pointsto-pc "pc"
store(&pc, &c)
store(&pd, &d)
- _ = pd // @describe describe-pd "pd"
+ _ = pd // @pointsto pointsto-pd "pd"
call(func() *int {
// We are called twice from main.call
@@ -71,12 +71,12 @@ func main() {
var dynamic = func() {}
-// Within dead code, dynamic calls have no callees.
func deadcode() {
main() // @callees callees-err-deadcode2 "main"
// @callers callers-err-deadcode "^"
// @callstack callstack-err-deadcode "^"
+ // Within dead code, dynamic calls have no callees.
dynamic() // @callees callees-err-deadcode3 "dynamic"
}
diff --git a/oracle/testdata/src/main/calls.golden b/oracle/testdata/src/main/calls.golden
index 364da94..db3d730 100644
--- a/oracle/testdata/src/main/calls.golden
+++ b/oracle/testdata/src/main/calls.golden
@@ -1,6 +1,5 @@
--------- @describe describe-A-x --------
-definition of var x *int
-value may point to these labels:
+-------- @pointsto pointsto-A-x --------
+this *int may point to these objects:
a
b
@@ -10,9 +9,8 @@ main.A
dynamic function call from main.apply
static function call from main.main
--------- @describe describe-B-x --------
-definition of var x *int
-value may point to these labels:
+-------- @pointsto pointsto-B-x --------
+this *int may point to these objects:
a
b
@@ -35,10 +33,8 @@ main.store is called from these 2 sites:
static function call from main.main
static function call from main.main
--------- @describe describe-result-f --------
-reference to var f func() *int
-defined here
-value may point to these labels:
+-------- @pointsto pointsto-result-f --------
+this func() *int may point to these objects:
func@50.7
-------- @callees callees-main.call-f --------
@@ -54,15 +50,12 @@ main.call is called from these 2 sites:
this static function call dispatches to:
main.apply
--------- @describe describe-pc --------
-definition of var pc *int
-value may point to these labels:
+-------- @pointsto pointsto-pc --------
+this *int may point to these objects:
c
--------- @describe describe-pd --------
-reference to var pd *int
-defined here
-value may point to these labels:
+-------- @pointsto pointsto-pd --------
+this *int may point to these objects:
d
-------- @callees callees-err-no-call --------
diff --git a/oracle/testdata/src/main/describe-json.golden b/oracle/testdata/src/main/describe-json.golden
index db917ee..987b0c3 100644
--- a/oracle/testdata/src/main/describe-json.golden
+++ b/oracle/testdata/src/main/describe-json.golden
@@ -79,18 +79,7 @@
"detail": "value",
"value": {
"type": "*int",
- "objpos": "testdata/src/main/describe-json.go:11:2",
- "pts": [
- {
- "type": "*int",
- "labels": [
- {
- "pos": "testdata/src/main/describe-json.go:10:6",
- "desc": "s.x[*]"
- }
- ]
- }
- ]
+ "objpos": "testdata/src/main/describe-json.go:11:2"
}
}
}-------- @describe desc-val-i --------
@@ -102,23 +91,7 @@
"detail": "value",
"value": {
"type": "I",
- "objpos": "testdata/src/main/describe-json.go:14:6",
- "pts": [
- {
- "type": "*D",
- "namepos": "testdata/src/main/describe-json.go:28:6",
- "labels": [
- {
- "pos": "testdata/src/main/describe-json.go:16:10",
- "desc": "new"
- }
- ]
- },
- {
- "type": "C",
- "namepos": "testdata/src/main/describe-json.go:27:6"
- }
- ]
+ "objpos": "testdata/src/main/describe-json.go:14:6"
}
}
}-------- @describe desc-stmt --------
diff --git a/oracle/testdata/src/main/describe.go b/oracle/testdata/src/main/describe.go
index 20a04b1..69e0a75 100644
--- a/oracle/testdata/src/main/describe.go
+++ b/oracle/testdata/src/main/describe.go
@@ -69,13 +69,11 @@ func main() { // @describe func-def-main "main"
go main() // @describe go-stmt "go"
panic(3) // @describe builtin-ref-panic "panic"
-}
-func deadcode() {
- var a int // @describe var-decl-stmt "var a int"
- // Pointer analysis can't run on dead code.
- var b = &a // @describe b "b"
- _ = b
+ var a2 int // @describe var-decl-stmt "var a2 int"
+ _ = a2
+ var _ int // @describe var-decl-stmt2 "var _ int"
+ var _ int // @describe var-def-blank "_"
}
type I interface { // @describe def-iface-I "I"
diff --git a/oracle/testdata/src/main/describe.golden b/oracle/testdata/src/main/describe.golden
index b586169..810a0bd 100644
--- a/oracle/testdata/src/main/describe.golden
+++ b/oracle/testdata/src/main/describe.golden
@@ -1,18 +1,17 @@
-------- @describe pkgdecl --------
definition of package "describe"
- type C int
- method (*describe.C) f()
- type D struct{}
- method (describe.D) f()
- type I interface{f()}
- method (describe.I) f()
- const c untyped integer = 0
- type cake float64
- func deadcode func()
- var global *string
- func main func()
- const pi untyped float = 3141/1000
- const pie cake = 1768225803696341/562949953421312
+ type C int
+ method (*C) f()
+ type D struct{}
+ method (D) f()
+ type I interface{f()}
+ method (I) f()
+ const c untyped integer = 0
+ type cake float64
+ var global *string
+ func main func()
+ const pi untyped float = 3141/1000
+ const pie cake = 1768225803696341/562949953421312
-------- @describe type-ref-builtin --------
reference to built-in type float64
@@ -76,58 +75,37 @@ defined here
-------- @describe ref-anon --------
reference to var anon func()
defined here
-value may point to these labels:
- func@31.10
-------- @describe ref-global --------
reference to var global *string
defined here
-value may point to these labels:
- new
-------- @describe var-def-x-1 --------
definition of var x *int
-value may point to these labels:
- a
-------- @describe var-ref-x-1 --------
reference to var x *int
defined here
-value may point to these labels:
- a
-------- @describe var-def-x-2 --------
reference to var x *int
defined here
-value may point to these labels:
- b
-------- @describe var-ref-x-2 --------
reference to var x *int
defined here
-value may point to these labels:
- b
-------- @describe var-ref-i-C --------
reference to var i I
defined here
-this I may contain these dynamic types:
- *C, may point to:
- new
-------- @describe var-ref-i-D --------
reference to var i I
defined here
-this I may contain these dynamic types:
- D
-------- @describe var-ref-i --------
reference to var i I
defined here
-this I may contain these dynamic types:
- *C, may point to:
- new
- D
-------- @describe const-local-pi --------
definition of const localpi untyped float
@@ -160,14 +138,10 @@ index expression of type (*int, bool)
-------- @describe mapval --------
reference to var mapval *int
defined here
-value may point to these labels:
- a
-------- @describe m --------
reference to var m map[string]*int
defined here
-value may point to these labels:
- makemap
-------- @describe defer-stmt --------
defer statement
@@ -179,11 +153,13 @@ go statement
function call (or conversion) of type ()
-------- @describe var-decl-stmt --------
-definition of var a int
+definition of var a2 int
--------- @describe b --------
-definition of var b *int
-no points-to information: PTA did not encounter this expression (dead code?)
+-------- @describe var-decl-stmt2 --------
+definition of var _ int
+
+-------- @describe var-def-blank --------
+definition of var _ int
-------- @describe def-iface-I --------
definition of type I (size 16, align 8)
diff --git a/oracle/testdata/src/main/freevars.golden b/oracle/testdata/src/main/freevars.golden
index a4c2c77..fd65e99 100644
--- a/oracle/testdata/src/main/freevars.golden
+++ b/oracle/testdata/src/main/freevars.golden
@@ -1,6 +1,6 @@
-------- @freevars fv1 --------
Free identifiers:
-type C main.C
+type C
const exp int
var x int
diff --git a/oracle/testdata/src/main/imports.go b/oracle/testdata/src/main/imports.go
index 17c5e50..111c86e 100644
--- a/oracle/testdata/src/main/imports.go
+++ b/oracle/testdata/src/main/imports.go
@@ -20,7 +20,7 @@ func main() {
var t lib.Type // @describe ref-type "Type"
p := t.Method(&a) // @describe ref-method "Method"
- print(*p + 1) // @describe p "p "
+ print(*p + 1) // @pointsto p "p "
var _ lib.Type // @describe ref-pkg "lib"
}
diff --git a/oracle/testdata/src/main/imports.golden b/oracle/testdata/src/main/imports.golden
index b117396..0a66c80 100644
--- a/oracle/testdata/src/main/imports.golden
+++ b/oracle/testdata/src/main/imports.golden
@@ -3,7 +3,7 @@ import of package "lib"
const Const untyped integer = 3
func Func func()
type Type int
- method (lib.Type) Method(x *int) *int
+ method (Type) Method(x *int) *int
var Var int
-------- @describe ref-const --------
@@ -28,10 +28,8 @@ Method set:
reference to method func (lib.Type).Method(x *int) *int
defined here
--------- @describe p --------
-reference to var p *int
-defined here
-value may point to these labels:
+-------- @pointsto p --------
+this *int may point to these objects:
imports.a
-------- @describe ref-pkg --------
@@ -39,6 +37,6 @@ reference to package "lib"
const Const untyped integer = 3
func Func func()
type Type int
- method (lib.Type) Method(x *int) *int
+ method (Type) Method(x *int) *int
var Var int
diff --git a/oracle/testdata/src/main/peers.go b/oracle/testdata/src/main/peers.go
index 0fa1e4c..65ec907 100644
--- a/oracle/testdata/src/main/peers.go
+++ b/oracle/testdata/src/main/peers.go
@@ -20,15 +20,15 @@ func main() {
b := 3
chB <- &b
- <-chA // @describe describe-chA "chA"
- <-chA2 // @describe describe-chA2 "chA2"
- <-chB // @describe describe-chB "chB"
+ <-chA // @pointsto pointsto-chA "chA"
+ <-chA2 // @pointsto pointsto-chA2 "chA2"
+ <-chB // @pointsto pointsto-chB "chB"
select {
case rA := <-chA: // @peers peer-recv-chA "<-"
- _ = rA // @describe describe-rA "rA"
+ _ = rA // @pointsto pointsto-rA "rA"
case rB := <-chB: // @peers peer-recv-chB "<-"
- _ = rB // @describe describe-rB "rB"
+ _ = rB // @pointsto pointsto-rB "rB"
case <-chA: // @peers peer-recv-chA' "<-"
diff --git a/oracle/testdata/src/main/peers.golden b/oracle/testdata/src/main/peers.golden
index 3b5a398..e6b8a65 100644
--- a/oracle/testdata/src/main/peers.golden
+++ b/oracle/testdata/src/main/peers.golden
@@ -1,20 +1,14 @@
--------- @describe describe-chA --------
-reference to var chA chan *int
-defined here
-value may point to these labels:
+-------- @pointsto pointsto-chA --------
+this chan *int may point to these objects:
makechan
makechan
--------- @describe describe-chA2 --------
-reference to var chA2 chan *int
-defined here
-value may point to these labels:
+-------- @pointsto pointsto-chA2 --------
+this chan *int may point to these objects:
makechan
--------- @describe describe-chB --------
-reference to var chB chan *int
-defined here
-value may point to these labels:
+-------- @pointsto pointsto-chB --------
+this chan *int may point to these objects:
makechan
-------- @peers peer-recv-chA --------
@@ -29,10 +23,8 @@ This channel of type chan *int may be:
received from, here
received from, here
--------- @describe describe-rA --------
-reference to var rA *int
-defined here
-value may point to these labels:
+-------- @pointsto pointsto-rA --------
+this *int may point to these objects:
peers.a2
a1
@@ -43,10 +35,8 @@ This channel of type chan *int may be:
received from, here
received from, here
--------- @describe describe-rB --------
-reference to var rB *int
-defined here
-value may point to these labels:
+-------- @pointsto pointsto-rB --------
+this *int may point to these objects:
b
-------- @peers peer-recv-chA' --------
diff --git a/oracle/testdata/src/main/pointsto-json.go b/oracle/testdata/src/main/pointsto-json.go
new file mode 100644
index 0000000..79d7d3d
--- /dev/null
+++ b/oracle/testdata/src/main/pointsto-json.go
@@ -0,0 +1,27 @@
+package pointsto
+
+// Tests of 'pointsto' queries, -format=json.
+// See go.tools/oracle/oracle_test.go for explanation.
+// See pointsto-json.golden for expected query results.
+
+func main() { //
+ var s struct{ x [3]int }
+ p := &s.x[0] // @pointsto val-p "p"
+ _ = p
+
+ var i I = C(0)
+ if i == nil {
+ i = new(D)
+ }
+ print(i) // @pointsto val-i "\\bi\\b"
+}
+
+type I interface {
+ f()
+}
+
+type C int
+type D struct{}
+
+func (c C) f() {}
+func (d *D) f() {}
diff --git a/oracle/testdata/src/main/pointsto-json.golden b/oracle/testdata/src/main/pointsto-json.golden
new file mode 100644
index 0000000..b3f8511
--- /dev/null
+++ b/oracle/testdata/src/main/pointsto-json.golden
@@ -0,0 +1,34 @@
+-------- @pointsto val-p --------
+{
+ "mode": "pointsto",
+ "pointsto": [
+ {
+ "type": "*int",
+ "labels": [
+ {
+ "pos": "testdata/src/main/pointsto-json.go:8:6",
+ "desc": "s.x[*]"
+ }
+ ]
+ }
+ ]
+}-------- @pointsto val-i --------
+{
+ "mode": "pointsto",
+ "pointsto": [
+ {
+ "type": "*D",
+ "namepos": "testdata/src/main/pointsto-json.go:24:6",
+ "labels": [
+ {
+ "pos": "testdata/src/main/pointsto-json.go:14:10",
+ "desc": "new"
+ }
+ ]
+ },
+ {
+ "type": "C",
+ "namepos": "testdata/src/main/pointsto-json.go:23:6"
+ }
+ ]
+} \ No newline at end of file
diff --git a/oracle/testdata/src/main/pointsto.go b/oracle/testdata/src/main/pointsto.go
new file mode 100644
index 0000000..796ec94
--- /dev/null
+++ b/oracle/testdata/src/main/pointsto.go
@@ -0,0 +1,68 @@
+package pointsto
+
+// Tests of 'pointsto' query.
+// See go.tools/oracle/oracle_test.go for explanation.
+// See pointsto.golden for expected query results.
+
+const pi = 3.141 // @pointsto const "pi"
+
+var global = new(string) // NB: ssa.Global is indirect, i.e. **string
+
+func main() {
+ livecode()
+
+ // func objects
+ _ = main // @pointsto func-ref-main "main"
+ _ = (*C).f // @pointsto func-ref-*C.f "..C..f"
+ _ = D.f // @pointsto func-ref-D.f "D.f"
+ _ = I.f // @pointsto func-ref-I.f "I.f"
+ var d D
+ var i I
+ _ = d.f // @pointsto func-ref-d.f "d.f"
+ _ = i.f // @pointsto func-ref-i.f "i.f"
+
+ // var objects
+ anon := func() {
+ _ = d.f // @pointsto ref-lexical-d.f "d.f"
+ }
+ _ = anon // @pointsto ref-anon "anon"
+ _ = global // @pointsto ref-global "global"
+
+ // SSA affords some local flow sensitivity.
+ var a, b int
+ var x = &a // @pointsto var-def-x-1 "x"
+ _ = x // @pointsto var-ref-x-1 "x"
+ x = &b // @pointsto var-def-x-2 "x"
+ _ = x // @pointsto var-ref-x-2 "x"
+
+ i = new(C) // @pointsto var-ref-i-C "i"
+ if i != nil {
+ i = D{} // @pointsto var-ref-i-D "i"
+ }
+ print(i) // @pointsto var-ref-i "\\bi\\b"
+
+ m := map[string]*int{"a": &a}
+ mapval, _ := m["a"] // @pointsto map-lookup,ok "m..a.."
+ _ = mapval // @pointsto mapval "mapval"
+ _ = m // @pointsto m "m"
+
+ panic(3) // @pointsto builtin-panic "panic"
+}
+
+func livecode() {} // @pointsto func-live "livecode"
+
+func deadcode() { // @pointsto func-dead "deadcode"
+ // Pointer analysis can't run on dead code.
+ var b = new(int) // @pointsto b "b"
+ _ = b
+}
+
+type I interface {
+ f()
+}
+
+type C int
+type D struct{}
+
+func (c *C) f() {}
+func (d D) f() {}
diff --git a/oracle/testdata/src/main/pointsto.golden b/oracle/testdata/src/main/pointsto.golden
new file mode 100644
index 0000000..1f8035e
--- /dev/null
+++ b/oracle/testdata/src/main/pointsto.golden
@@ -0,0 +1,93 @@
+-------- @pointsto const --------
+
+Error: pointer analysis wants an expression of reference type; got untyped float
+-------- @pointsto func-ref-main --------
+this func() may point to these objects:
+ pointsto.main
+
+-------- @pointsto func-ref-*C.f --------
+this func() may point to these objects:
+ (*pointsto.C).f
+
+-------- @pointsto func-ref-D.f --------
+this func() may point to these objects:
+ (pointsto.D).f
+
+-------- @pointsto func-ref-I.f --------
+this func() may point to these objects:
+ (pointsto.I).f
+
+-------- @pointsto func-ref-d.f --------
+this func() may point to these objects:
+ (pointsto.D).f
+
+-------- @pointsto func-ref-i.f --------
+this func() may point to these objects:
+ (pointsto.I).f
+
+-------- @pointsto ref-lexical-d.f --------
+this func() may point to these objects:
+ (pointsto.D).f
+
+-------- @pointsto ref-anon --------
+this func() may point to these objects:
+ func@25.10
+
+-------- @pointsto ref-global --------
+this *string may point to these objects:
+ new
+
+-------- @pointsto var-def-x-1 --------
+this *int may point to these objects:
+ a
+
+-------- @pointsto var-ref-x-1 --------
+this *int may point to these objects:
+ a
+
+-------- @pointsto var-def-x-2 --------
+this *int may point to these objects:
+ b
+
+-------- @pointsto var-ref-x-2 --------
+this *int may point to these objects:
+ b
+
+-------- @pointsto var-ref-i-C --------
+this I may contain these dynamic types:
+ *C, may point to:
+ new
+
+-------- @pointsto var-ref-i-D --------
+this I may contain these dynamic types:
+ D
+
+-------- @pointsto var-ref-i --------
+this I may contain these dynamic types:
+ *C, may point to:
+ new
+ D
+
+-------- @pointsto map-lookup,ok --------
+
+Error: pointer analysis wants an expression of reference type; got (*int, bool)
+-------- @pointsto mapval --------
+this *int may point to these objects:
+ a
+
+-------- @pointsto m --------
+this map[string]*int may point to these objects:
+ makemap
+
+-------- @pointsto builtin-panic --------
+
+Error: pointer analysis wants an expression of reference type; got ()
+-------- @pointsto func-live --------
+
+Error: pointer analysis did not find expression (dead code?)
+-------- @pointsto func-dead --------
+
+Error: pointer analysis did not find expression (dead code?)
+-------- @pointsto b --------
+
+Error: pointer analysis did not find expression (dead code?)
diff --git a/oracle/testdata/src/main/reflection.go b/oracle/testdata/src/main/reflection.go
index fc11c2f..b10df0b 100644
--- a/oracle/testdata/src/main/reflection.go
+++ b/oracle/testdata/src/main/reflection.go
@@ -1,7 +1,7 @@
package reflection
-// This is a test of 'describe', but we split it into a separate file
-// so that describe.go doesn't have to import "reflect" each time.
+// 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.
import "reflect"
@@ -20,11 +20,11 @@ func main() {
mrv = reflect.ValueOf(&a)
}
- _ = mrv // @describe mrv "mrv"
- p1 := mrv.Interface() // @describe p1 "p1"
- p2 := mrv.MapKeys() // @describe p2 "p2"
- p3 := p2[0] // @describe p3 "p3"
- p4 := reflect.TypeOf(p1) // @describe p4 "p4"
+ _ = mrv // @pointsto mrv "mrv"
+ p1 := mrv.Interface() // @pointsto p1 "p1"
+ p2 := mrv.MapKeys() // @pointsto p2 "p2"
+ p3 := p2[0] // @pointsto p3 "p3"
+ p4 := reflect.TypeOf(p1) // @pointsto p4 "p4"
_, _, _, _ = p1, p2, p3, p4
}
diff --git a/oracle/testdata/src/main/reflection.golden b/oracle/testdata/src/main/reflection.golden
index 86dd7a6..4782132 100644
--- a/oracle/testdata/src/main/reflection.golden
+++ b/oracle/testdata/src/main/reflection.golden
@@ -1,6 +1,4 @@
--------- @describe mrv --------
-reference to var mrv reflect.Value
-defined here
+-------- @pointsto mrv --------
this reflect.Value may contain these dynamic types:
*bool, may point to:
reflection.b
@@ -9,8 +7,7 @@ this reflect.Value may contain these dynamic types:
map[*int]*bool, may point to:
makemap
--------- @describe p1 --------
-definition of var p1 interface{}
+-------- @pointsto p1 --------
this interface{} may contain these dynamic types:
*bool, may point to:
reflection.b
@@ -19,19 +16,16 @@ this interface{} may contain these dynamic types:
map[*int]*bool, may point to:
makemap
--------- @describe p2 --------
-definition of var p2 []reflect.Value
-value may point to these labels:
+-------- @pointsto p2 --------
+this []reflect.Value may point to these objects:
<alloc in (reflect.Value).MapKeys>
--------- @describe p3 --------
-definition of var p3 reflect.Value
+-------- @pointsto p3 --------
this reflect.Value may contain these dynamic types:
*int, may point to:
reflection.a
--------- @describe p4 --------
-definition of var p4 reflect.Type
+-------- @pointsto p4 --------
this reflect.Type may contain these dynamic types:
*reflect.rtype, may point to:
*bool
diff --git a/oracle/testdata/src/main/what-json.go b/oracle/testdata/src/main/what-json.go
new file mode 100644
index 0000000..d07a6c9
--- /dev/null
+++ b/oracle/testdata/src/main/what-json.go
@@ -0,0 +1,9 @@
+package what
+
+// Tests of 'what' queries, -format=json.
+// See go.tools/oracle/oracle_test.go for explanation.
+// See what-json.golden for expected query results.
+
+func main() {
+ f() // @what call "f"
+}
diff --git a/oracle/testdata/src/main/what-json.golden b/oracle/testdata/src/main/what-json.golden
new file mode 100644
index 0000000..13860dd
--- /dev/null
+++ b/oracle/testdata/src/main/what-json.golden
@@ -0,0 +1,52 @@
+-------- @what call --------
+{
+ "mode": "what",
+ "what": {
+ "enclosing": [
+ {
+ "desc": "identifier",
+ "start": 179,
+ "end": 180
+ },
+ {
+ "desc": "function call (or conversion)",
+ "start": 179,
+ "end": 182
+ },
+ {
+ "desc": "expression statement",
+ "start": 179,
+ "end": 182
+ },
+ {
+ "desc": "block",
+ "start": 176,
+ "end": 202
+ },
+ {
+ "desc": "function declaration",
+ "start": 164,
+ "end": 202
+ },
+ {
+ "desc": "source file",
+ "start": 0,
+ "end": 202
+ }
+ ],
+ "modes": [
+ "callees",
+ "callers",
+ "callgraph",
+ "callstack",
+ "definition",
+ "describe",
+ "freevars",
+ "implements",
+ "pointsto",
+ "referrers"
+ ],
+ "srcdir": "testdata/src",
+ "importpath": "main"
+ }
+} \ No newline at end of file
diff --git a/oracle/testdata/src/main/what.go b/oracle/testdata/src/main/what.go
new file mode 100644
index 0000000..041e921
--- /dev/null
+++ b/oracle/testdata/src/main/what.go
@@ -0,0 +1,11 @@
+package what // @what pkgdecl "what"
+
+// Tests of 'what' queries.
+// See go.tools/oracle/oracle_test.go for explanation.
+// See what.golden for expected query results.
+
+func main() {
+ f() // @what call "f"
+ var ch chan int // @what var "var"
+ <-ch // @what recv "ch"
+}
diff --git a/oracle/testdata/src/main/what.golden b/oracle/testdata/src/main/what.golden
new file mode 100644
index 0000000..437fdc8
--- /dev/null
+++ b/oracle/testdata/src/main/what.golden
@@ -0,0 +1,39 @@
+-------- @what pkgdecl --------
+identifier
+source file
+modes: [callgraph definition describe freevars implements pointsto referrers]
+srcdir: testdata/src
+import path: main
+
+-------- @what call --------
+identifier
+function call (or conversion)
+expression statement
+block
+function declaration
+source file
+modes: [callees callers callgraph callstack definition describe freevars implements pointsto referrers]
+srcdir: testdata/src
+import path: main
+
+-------- @what var --------
+variable declaration
+variable declaration statement
+block
+function declaration
+source file
+modes: [callers callgraph callstack describe freevars implements pointsto]
+srcdir: testdata/src
+import path: main
+
+-------- @what recv --------
+identifier
+unary <- operation
+expression statement
+block
+function declaration
+source file
+modes: [callers callgraph callstack definition describe freevars implements peers pointsto referrers]
+srcdir: testdata/src
+import path: main
+
diff --git a/oracle/what.go b/oracle/what.go
new file mode 100644
index 0000000..5ead279
--- /dev/null
+++ b/oracle/what.go
@@ -0,0 +1,197 @@
+// 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/ast"
+ "go/build"
+ "go/token"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "code.google.com/p/go.tools/astutil"
+ "code.google.com/p/go.tools/oracle/serial"
+)
+
+// what reports all the information about the query selection that can be
+// obtained from parsing only its containing source file.
+// It is intended to be a very low-latency query callable from GUI
+// tools, e.g. to populate a menu of options of slower queries about
+// the selected location.
+//
+func what(posFlag string, buildContext *build.Context) (*Result, error) {
+ qpos, err := fastQueryPos(posFlag)
+ if err != nil {
+ return nil, err
+ }
+
+ // (ignore errors)
+ srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), buildContext)
+
+ // Determine which query modes are applicable to the selection.
+ enable := map[string]bool{
+ "callgraph": true, // whole program; always enabled
+ "implements": true, // whole package; always enabled
+ "freevars": qpos.end > qpos.start, // nonempty selection?
+ "describe": true, // any syntax; always enabled
+ }
+ for _, n := range qpos.path {
+ switch n := n.(type) {
+ case *ast.Ident:
+ enable["definition"] = true
+ enable["referrers"] = true
+ case *ast.CallExpr:
+ enable["callees"] = true
+ case *ast.FuncDecl:
+ enable["callers"] = true
+ enable["callstack"] = true
+ case *ast.SendStmt:
+ enable["peers"] = true
+ case *ast.UnaryExpr:
+ if n.Op == token.ARROW {
+ enable["peers"] = true
+ }
+ }
+
+ // For pointsto, we approximate findInterestingNode.
+ if _, ok := enable["pointsto"]; !ok {
+ switch n.(type) {
+ case ast.Stmt,
+ *ast.ArrayType,
+ *ast.StructType,
+ *ast.FuncType,
+ *ast.InterfaceType,
+ *ast.MapType,
+ *ast.ChanType:
+ enable["pointsto"] = false // not an expr
+
+ case ast.Expr, ast.Decl, *ast.ValueSpec:
+ enable["pointsto"] = true // an expr, maybe
+
+ default:
+ // Comment, Field, KeyValueExpr, etc: ascend.
+ }
+ }
+ }
+
+ // If we don't have an exact selection, disable modes that need one.
+ if !qpos.exact {
+ for _, minfo := range modes {
+ if minfo.needs&needExactPos != 0 {
+ enable[minfo.name] = false
+ }
+ }
+ }
+
+ var modes []string
+ for mode := range enable {
+ modes = append(modes, mode)
+ }
+ sort.Strings(modes)
+
+ return &Result{
+ mode: "what",
+ fset: qpos.fset,
+ q: &whatResult{
+ path: qpos.path,
+ srcdir: srcdir,
+ importPath: importPath,
+ modes: modes,
+ },
+ }, nil
+
+}
+
+// guessImportPath finds the package containing filename, and returns
+// its source directory (an element of $GOPATH) and its import path
+// relative to it.
+//
+// TODO(adonovan): what about _test.go files that are not part of the
+// package?
+//
+func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
+ absFile, err := filepath.Abs(filename)
+ if err != nil {
+ err = fmt.Errorf("can't form absolute path of %s", filename)
+ return
+ }
+ absFileDir := segments(filepath.Dir(absFile))
+
+ // Find the innermost directory in $GOPATH that encloses filename.
+ minD := 1024
+ for _, gopathDir := range buildContext.SrcDirs() {
+ absDir, err := filepath.Abs(gopathDir)
+ if err != nil {
+ continue // e.g. non-existent dir on $GOPATH
+ }
+ d := prefixLen(segments(absDir), absFileDir)
+ // If there are multiple matches,
+ // prefer the innermost enclosing directory
+ // (smallest d).
+ if d >= 0 && d < minD {
+ minD = d
+ srcdir = gopathDir
+ importPath = strings.Join(absFileDir[len(absFileDir)-minD:], string(os.PathSeparator))
+ }
+ }
+ if srcdir == "" {
+ err = fmt.Errorf("can't find package for file %s", filename)
+ }
+ return
+}
+
+func segments(path string) []string {
+ return strings.Split(path, string(os.PathSeparator))
+}
+
+// prefixLen returns the length of the remainder of y if x is a prefix
+// of y, a negative number otherwise.
+func prefixLen(x, y []string) int {
+ d := len(y) - len(x)
+ if d >= 0 {
+ for i := range x {
+ if y[i] != x[i] {
+ return -1 // not a prefix
+ }
+ }
+ }
+ return d
+}
+
+type whatResult struct {
+ path []ast.Node
+ modes []string
+ srcdir string
+ importPath string
+}
+
+func (r *whatResult) display(printf printfFunc) {
+ for _, n := range r.path {
+ printf(n, "%s", astutil.NodeDescription(n))
+ }
+ printf(nil, "modes: %s", r.modes)
+ printf(nil, "srcdir: %s", r.srcdir)
+ printf(nil, "import path: %s", r.importPath)
+}
+
+func (r *whatResult) toSerial(res *serial.Result, fset *token.FileSet) {
+ var enclosing []serial.SyntaxNode
+ for _, n := range r.path {
+ enclosing = append(enclosing, serial.SyntaxNode{
+ Description: astutil.NodeDescription(n),
+ Start: fset.Position(n.Pos()).Offset,
+ End: fset.Position(n.End()).Offset,
+ })
+ }
+ res.What = &serial.What{
+ Modes: r.modes,
+ SrcDir: r.srcdir,
+ ImportPath: r.importPath,
+ Enclosing: enclosing,
+ }
+}