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
This commit is contained in:
Alan Donovan 2013-12-13 10:04:55 -05:00
parent 3df3227c35
commit f119874203
43 changed files with 1518 additions and 647 deletions

View File

@ -576,7 +576,7 @@ func NodeDescription(n ast.Node) string {
if n.Tok == token.INC { if n.Tok == token.INC {
return "increment statement" return "increment statement"
} }
return "derement statement" return "decrement statement"
case *ast.IndexExpr: case *ast.IndexExpr:
return "index expression" return "index expression"
case *ast.InterfaceType: case *ast.InterfaceType:

View File

@ -122,7 +122,7 @@ func main() {
os.Exit(2) os.Exit(2)
} }
if len(args) == 0 { if len(args) == 0 && mode != "what" {
fmt.Fprint(os.Stderr, "Error: no package arguments.\n"+useHelp) fmt.Fprint(os.Stderr, "Error: no package arguments.\n"+useHelp)
os.Exit(2) os.Exit(2)
} }

View File

@ -36,14 +36,18 @@
nil nil
"History of values supplied to `go-oracle-set-scope'.") "History of values supplied to `go-oracle-set-scope'.")
;; TODO(adonovan): I'd like to get rid of this separate mode since it
;; makes it harder to use the oracle.
(defvar go-oracle-mode-map (defvar go-oracle-mode-map
(let ((m (make-sparse-keymap))) (let ((m (make-sparse-keymap)))
(define-key m (kbd "C-c C-o d") #'go-oracle-describe) (define-key m (kbd "C-c C-o t") #'go-oracle-describe) ; t for type
(define-key m (kbd "C-c C-o f") #'go-oracle-freevars) (define-key m (kbd "C-c C-o f") #'go-oracle-freevars)
(define-key m (kbd "C-c C-o g") #'go-oracle-callgraph) (define-key m (kbd "C-c C-o g") #'go-oracle-callgraph)
(define-key m (kbd "C-c C-o i") #'go-oracle-implements) (define-key m (kbd "C-c C-o i") #'go-oracle-implements)
(define-key m (kbd "C-c C-o p") #'go-oracle-peers) (define-key m (kbd "C-c C-o c") #'go-oracle-peers) ; c for channel
(define-key m (kbd "C-c C-o r") #'go-oracle-referrers) (define-key m (kbd "C-c C-o r") #'go-oracle-referrers)
(define-key m (kbd "C-c C-o d") #'go-oracle-definition)
(define-key m (kbd "C-c C-o p") #'go-oracle-pointsto)
(define-key m (kbd "C-c C-o s") #'go-oracle-callstack) (define-key m (kbd "C-c C-o s") #'go-oracle-callstack)
(define-key m (kbd "C-c C-o <") #'go-oracle-callers) (define-key m (kbd "C-c C-o <") #'go-oracle-callers)
(define-key m (kbd "C-c C-o >") #'go-oracle-callees) (define-key m (kbd "C-c C-o >") #'go-oracle-callees)
@ -128,7 +132,7 @@ result."
;; Hide the file/line info to save space. ;; Hide the file/line info to save space.
;; Replace each with a little widget. ;; Replace each with a little widget.
;; compilation-mode + this loop = slooow. ;; compilation-mode + this loop = slooow.
;; TODO(adonovan): have oracle give us an S-expression ;; TODO(adonovan): have oracle give us JSON
;; and we'll do the markup directly. ;; and we'll do the markup directly.
(let ((buffer-read-only nil) (let ((buffer-read-only nil)
(p 1)) (p 1))
@ -171,11 +175,21 @@ function containing the current point."
(interactive) (interactive)
(go-oracle--run "callstack")) (go-oracle--run "callstack"))
(defun go-oracle-definition ()
"Show the definition of the selected identifier."
(interactive)
(go-oracle--run "definition"))
(defun go-oracle-describe () (defun go-oracle-describe ()
"Describe the expression at the current point." "Describe the selected syntax, its kind, type and methods."
(interactive) (interactive)
(go-oracle--run "describe")) (go-oracle--run "describe"))
(defun go-oracle-pointsto ()
"Show what the selected expression points to."
(interactive)
(go-oracle--run "pointsto"))
(defun go-oracle-implements () (defun go-oracle-implements ()
"Describe the 'implements' relation for types in the package "Describe the 'implements' relation for types in the package
containing the current point." containing the current point."

View File

@ -61,7 +61,7 @@ import (
// An Importer's exported methods are not thread-safe. // An Importer's exported methods are not thread-safe.
type Importer struct { type Importer struct {
Fset *token.FileSet // position info for all files seen Fset *token.FileSet // position info for all files seen
config Config // the client configuration, modified by us config *Config // the client configuration, unmodified
importfn types.Importer // client's type import function importfn types.Importer // client's type import function
augment map[string]bool // packages to be augmented by TestFiles when imported augment map[string]bool // packages to be augmented by TestFiles when imported
allPackagesMu sync.Mutex // guards 'allPackages' during internal concurrency allPackagesMu sync.Mutex // guards 'allPackages' during internal concurrency
@ -81,9 +81,24 @@ type importInfo struct {
// Config specifies the configuration for the importer. // Config specifies the configuration for the importer.
type Config struct { type Config struct {
// TypeChecker contains options relating to the type checker. // TypeChecker contains options relating to the type checker.
//
// The supplied IgnoreFuncBodies is not used; the effective
// value comes from the TypeCheckFuncBodies func below.
//
// All callbacks must be thread-safe. // All callbacks must be thread-safe.
TypeChecker types.Config TypeChecker types.Config
// TypeCheckFuncBodies is a predicate over package import
// paths. A package for which the predicate is false will
// have its package-level declarations type checked, but not
// its function bodies; this can be used to quickly load
// dependencies from source. If nil, all func bodies are type
// checked.
//
// Must be thread-safe.
//
TypeCheckFuncBodies func(string) bool
// If Build is non-nil, it is used to satisfy imports. // If Build is non-nil, it is used to satisfy imports.
// //
// If it is nil, binary object files produced by the gc // If it is nil, binary object files produced by the gc
@ -100,6 +115,13 @@ type Config struct {
// specified by config. // specified by config.
// //
func New(config *Config) *Importer { func New(config *Config) *Importer {
// Initialize by mutating the caller's copy,
// so all copies agree on the identity of the map.
if config.TypeChecker.Packages == nil {
config.TypeChecker.Packages = make(map[string]*types.Package)
}
// Save the caller's effective Import funcion.
importfn := config.TypeChecker.Import importfn := config.TypeChecker.Import
if importfn == nil { if importfn == nil {
importfn = gcimporter.Import importfn = gcimporter.Import
@ -107,18 +129,11 @@ func New(config *Config) *Importer {
imp := &Importer{ imp := &Importer{
Fset: token.NewFileSet(), Fset: token.NewFileSet(),
config: *config, // copy (don't clobber client input) config: config,
importfn: importfn, importfn: importfn,
augment: make(map[string]bool), augment: make(map[string]bool),
imported: make(map[string]*importInfo), imported: make(map[string]*importInfo),
} }
// TODO(adonovan): get typechecker to supply us with a source
// position, then pass errors back to the application
// (e.g. oracle).
if imp.config.TypeChecker.Error == nil {
imp.config.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
}
imp.config.TypeChecker.Import = imp.doImport // wraps importfn, effectively
return imp return imp
} }
@ -318,7 +333,18 @@ func (imp *Importer) CreatePackage(path string, files ...*ast.File) *PackageInfo
Selections: make(map[*ast.SelectorExpr]*types.Selection), Selections: make(map[*ast.SelectorExpr]*types.Selection),
}, },
} }
info.Pkg, info.Err = imp.config.TypeChecker.Check(path, imp.Fset, files, &info.Info)
// Use a copy of the types.Config so we can vary IgnoreFuncBodies.
tc := imp.config.TypeChecker
tc.IgnoreFuncBodies = false
if f := imp.config.TypeCheckFuncBodies; f != nil {
tc.IgnoreFuncBodies = !f(path)
}
if tc.Error == nil {
tc.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
}
tc.Import = imp.doImport // doImport wraps the user's importfn, effectively
info.Pkg, info.Err = tc.Check(path, imp.Fset, files, &info.Info)
imp.addPackage(info) imp.addPackage(info)
return info return info
} }

View File

@ -4,8 +4,6 @@
package importer package importer
// TODO(gri): absorb this into go/types.
import ( import (
"fmt" "fmt"
"go/ast" "go/ast"
@ -95,3 +93,17 @@ func (info *PackageInfo) TypeCaseVar(cc *ast.CaseClause) *types.Var {
} }
return nil return nil
} }
// ImportSpecPkg returns the PkgName for a given ImportSpec, possibly
// an implicit one for a dot-import or an import-without-rename.
// It returns nil if not found.
//
func (info *PackageInfo) ImportSpecPkg(spec *ast.ImportSpec) *types.PkgName {
if spec.Name != nil {
return info.ObjectOf(spec.Name).(*types.PkgName)
}
if p := info.Implicits[spec]; p != nil {
return p.(*types.PkgName)
}
return nil
}

99
oracle/TODO Normal file
View File

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

View File

@ -102,7 +102,7 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
} }
// Dynamic call: use pointer analysis. // Dynamic call: use pointer analysis.
o.config.BuildCallGraph = true o.ptaConfig.BuildCallGraph = true
callgraph := ptrAnalysis(o).CallGraph callgraph := ptrAnalysis(o).CallGraph
// Find all call edges from the site. // Find all call edges from the site.

View File

@ -36,7 +36,7 @@ func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
// Run the pointer analysis, recording each // Run the pointer analysis, recording each
// call found to originate from target. // call found to originate from target.
o.config.BuildCallGraph = true o.ptaConfig.BuildCallGraph = true
callgraph := ptrAnalysis(o).CallGraph callgraph := ptrAnalysis(o).CallGraph
var edges []call.Edge var edges []call.Edge
call.GraphVisitEdges(callgraph, func(edge call.Edge) error { call.GraphVisitEdges(callgraph, func(edge call.Edge) error {

View File

@ -32,7 +32,7 @@ func callgraph(o *Oracle, _ *QueryPos) (queryResult, error) {
buildSSA(o) buildSSA(o)
// Run the pointer analysis and build the complete callgraph. // Run the pointer analysis and build the complete callgraph.
o.config.BuildCallGraph = true o.ptaConfig.BuildCallGraph = true
ptares := ptrAnalysis(o) ptares := ptrAnalysis(o)
return &callgraphResult{ return &callgraphResult{

View File

@ -41,7 +41,7 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
// Run the pointer analysis and build the complete call graph. // Run the pointer analysis and build the complete call graph.
o.config.BuildCallGraph = true o.ptaConfig.BuildCallGraph = true
callgraph := ptrAnalysis(o).CallGraph callgraph := ptrAnalysis(o).CallGraph
// Search for an arbitrary path from a root to the target function. // Search for an arbitrary path from a root to the target function.

53
oracle/definition.go Normal file
View File

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

View File

@ -10,8 +10,6 @@ import (
"go/ast" "go/ast"
"go/token" "go/token"
"os" "os"
"sort"
"strconv"
"strings" "strings"
"code.google.com/p/go.tools/astutil" "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/go/types"
"code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/oracle/serial" "code.google.com/p/go.tools/oracle/serial"
"code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa" "code.google.com/p/go.tools/ssa"
) )
// describe describes the syntax node denoted by the query position, // describe describes the syntax node denoted by the query position,
// including: // including:
// - its syntactic category // - 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 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) { func describe(o *Oracle, qpos *QueryPos) (queryResult, error) {
if false { // debugging if false { // debugging
o.fprintf(os.Stderr, qpos.path[0], "you selected: %s %s", fprintf(os.Stderr, o.fset, qpos.path[0], "you selected: %s %s",
astutil.NodeDescription(qpos.path[0]), pathToString2(qpos.path)) astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
} }
path, action := findInterestingNode(qpos.info, 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: case *ast.SelectorExpr:
if pkginfo.ObjectOf(n.Sel) == nil { if pkginfo.ObjectOf(n.Sel) == nil {
// Is this reachable? // TODO(adonovan): is this reachable?
return path, actionUnknown return path, actionUnknown
} }
// Descend to .Sel child. // 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. // For reference to built-in function, return enclosing call.
path = path[1:] // ascend to enclosing function call path = path[1:] // ascend to enclosing function call
continue continue
case *types.Nil:
return path, actionExpr
} }
// No object. // No object.
@ -274,6 +269,7 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
default: default:
// e.g. blank identifier (go/types bug?) // e.g. blank identifier (go/types bug?)
// or y in "switch y := x.(type)" (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]) fmt.Printf("unknown reference %s in %T\n", n, path[1])
return path, actionUnknown return path, actionUnknown
} }
@ -297,54 +293,6 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
return nil, actionUnknown // unreachable 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) { func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueResult, error) {
var expr ast.Expr var expr ast.Expr
var obj types.Object var obj types.Object
@ -358,114 +306,28 @@ func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueRe
case ast.Expr: case ast.Expr:
expr = n expr = n
default: default:
// Is this reachable? // TODO(adonovan): is this reachable?
return nil, fmt.Errorf("unexpected AST for expr: %T", n) return nil, fmt.Errorf("unexpected AST for expr: %T", n)
} }
typ := qpos.info.TypeOf(expr) typ := qpos.info.TypeOf(expr)
constVal := qpos.info.ValueOf(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{ return &describeValueResult{
qpos: qpos, qpos: qpos,
expr: expr, expr: expr,
typ: typ, typ: typ,
constVal: constVal, constVal: constVal,
obj: obj, obj: obj,
ptaErr: ptaErr,
ptrs: ptrs,
}, nil }, 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 { type describeValueResult struct {
qpos *QueryPos qpos *QueryPos
expr ast.Expr // query node expr ast.Expr // query node
typ types.Type // type of expression typ types.Type // type of expression
constVal exact.Value // value of expression, if constant constVal exact.Value // value of expression, if constant
obj types.Object // var/func/const object, if expr was Ident 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)
} }
func (r *describeValueResult) display(printf printfFunc) { 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)) 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) { func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) {
var value, objpos, ptaerr string var value, objpos string
if r.constVal != nil { if r.constVal != nil {
value = r.constVal.String() value = r.constVal.String()
} }
if r.obj != nil { if r.obj != nil {
objpos = fset.Position(r.obj.Pos()).String() 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{ res.Describe = &serial.Describe{
Desc: astutil.NodeDescription(r.expr), 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), Type: r.qpos.TypeString(r.typ),
Value: value, Value: value,
ObjPos: objpos, 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 ------------------------------------------------------------ // ---- TYPE ------------------------------------------------------------
func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResult, error) { 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 var pkg *types.Package
switch n := path[0].(type) { switch n := path[0].(type) {
case *ast.ImportSpec: case *ast.ImportSpec:
// Most ImportSpecs have no .Name Ident so we can't pkgname := qpos.info.ImportSpecPkg(n)
// use ObjectOf. description = fmt.Sprintf("import of package %q", pkgname.Name())
// We could use the types.Info.Implicits mechanism, pkg = pkgname.Pkg()
// 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
case *ast.Ident: case *ast.Ident:
if _, isDef := path[1].(*ast.File); isDef { 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 { type describePackageResult struct {
@ -801,7 +569,7 @@ func (r *describePackageResult) display(printf printfFunc) {
for _, mem := range r.members { for _, mem := range r.members {
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname)) printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
for _, meth := range mem.methods { 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. // Nothing much to say about statements.
description = astutil.NodeDescription(n) description = astutil.NodeDescription(n)
} }
return &describeStmtResult{o.prog.Fset, path[0], description}, nil return &describeStmtResult{o.fset, path[0], description}, nil
} }
type describeStmtResult struct { 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 // pathToString returns a string containing the concrete types of the
// nodes in path. // nodes in path.
func pathToString2(path []ast.Node) string { func pathToString(path []ast.Node) string {
var buf bytes.Buffer var buf bytes.Buffer
fmt.Fprint(&buf, "[") fmt.Fprint(&buf, "[")
for i, n := range path { for i, n := range path {

View File

@ -5,7 +5,9 @@
package oracle package oracle
import ( import (
"bytes"
"go/ast" "go/ast"
"go/printer"
"go/token" "go/token"
"sort" "sort"
@ -120,7 +122,7 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
typ := qpos.info.TypeOf(n.(ast.Expr)) typ := qpos.info.TypeOf(n.(ast.Expr))
ref := freevarsRef{kind, o.printNode(n), typ, obj} ref := freevarsRef{kind, printNode(o.fset, n), typ, obj}
refsMap[ref.ref] = ref refsMap[ref.ref] = ref
if prune { if prune {
@ -140,14 +142,12 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
return &freevarsResult{ return &freevarsResult{
qpos: qpos, qpos: qpos,
fset: o.prog.Fset,
refs: refs, refs: refs,
}, nil }, nil
} }
type freevarsResult struct { type freevarsResult struct {
qpos *QueryPos qpos *QueryPos
fset *token.FileSet
refs []freevarsRef refs []freevarsRef
} }
@ -164,7 +164,12 @@ func (r *freevarsResult) display(printf printfFunc) {
} else { } else {
printf(r.qpos, "Free identifiers:") printf(r.qpos, "Free identifiers:")
for _, ref := range r.refs { 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) Len() int { return len(p) }
func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref } 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] } 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()
}

View File

@ -11,7 +11,7 @@ import (
"code.google.com/p/go.tools/oracle/serial" "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 // package-level named types in the package containing the query
// position. // position.
// //
@ -65,7 +65,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
// TODO(adonovan): sort facts to ensure test nondeterminism. // TODO(adonovan): sort facts to ensure test nondeterminism.
return &implementsResult{o.prog.Fset, facts}, nil return &implementsResult{o.fset, facts}, nil
} }
type implementsFact struct { type implementsFact struct {

View File

@ -13,22 +13,48 @@ package oracle
// This file defines oracle.Query, the entry point for the oracle tool. // This file defines oracle.Query, the entry point for the oracle tool.
// The actual executable is defined in cmd/oracle. // The actual executable is defined in cmd/oracle.
// TODO(adonovan): new query: show all statements that may update the // TODO(adonovan): new queries
// selected lvalue (local, global, field, etc). // - 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 ( import (
"bytes"
"fmt" "fmt"
"go/ast" "go/ast"
"go/build" "go/build"
"go/printer"
"go/token" "go/token"
"io" "io"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"code.google.com/p/go.tools/astutil" "code.google.com/p/go.tools/astutil"
"code.google.com/p/go.tools/go/types" "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. // An Oracle holds the program state required for one or more queries.
type Oracle struct { type Oracle struct {
out io.Writer // standard output fset *token.FileSet // file set [all queries]
prog *ssa.Program // the SSA program [only populated if need&SSA] prog *ssa.Program // the SSA program [needSSA]
config pointer.Config // pointer analysis configuration [TODO rename ptaConfig] ptaConfig pointer.Config // pointer analysis configuration [needPTA]
typeInfo map[*types.Package]*importer.PackageInfo // type info for all ASTs in the program [needRetainTypeInfo]
// need&AllTypeInfo
typeInfo map[*types.Package]*importer.PackageInfo // type info for all ASTs in the program
timers map[string]time.Duration // phase timing information
} }
// A set of bits indicating the analytical requirements of each mode. // A set of bits indicating the analytical requirements of each mode.
// //
// Typed ASTs for the whole program are always constructed // Typed ASTs for the whole program are always constructed
// transiently; they are retained only for the queried package unless // transiently; they are retained only for the queried package unless
// needAllTypeInfo is set. // needRetainTypeInfo is set.
const ( const (
needPos = 1 << iota // needs a position needPos = 1 << iota // needs a position
needExactPos // needs an exact AST selection; implies needPos needExactPos // needs an exact AST selection; implies needPos
needAllTypeInfo // needs to retain type info for all ASTs in the program needRetainTypeInfo // needs to retain type info for all ASTs in the program
needSSA // needs ssa.Packages for whole program needSSA // needs ssa.Packages for whole program
needSSADebug // needs debug info for ssa.Packages needSSADebug // needs debug info for ssa.Packages
needPTA = needSSA // needs pointer analysis needPTA = needSSA // needs pointer analysis
needAll = -1 // needs everything (e.g. a sequence of queries) needAll = -1 // needs everything (e.g. a sequence of queries)
) )
type modeInfo struct { type modeInfo struct {
@ -72,15 +94,20 @@ type modeInfo struct {
} }
var modes = []*modeInfo{ var modes = []*modeInfo{
// Pointer analyses: (whole program)
{"callees", needPTA | needExactPos, callees}, {"callees", needPTA | needExactPos, callees},
{"callers", needPTA | needPos, callers}, {"callers", needPTA | needPos, callers},
{"callgraph", needPTA, callgraph}, {"callgraph", needPTA, callgraph},
{"callstack", needPTA | needPos, callstack}, {"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}, {"freevars", needPos, freevars},
{"implements", needPos, implements}, {"implements", needPos, implements},
{"peers", needPTA | needSSADebug | needPos, peers}, {"referrers", needRetainTypeInfo | needPos, referrers}, // (whole-program)
{"referrers", needAllTypeInfo | needPos, referrers},
} }
func findMode(mode string) *modeInfo { func findMode(mode string) *modeInfo {
@ -106,9 +133,11 @@ type queryResult interface {
// Instances are created by ParseQueryPos. // Instances are created by ParseQueryPos.
// //
type QueryPos struct { type QueryPos struct {
fset *token.FileSet
start, end token.Pos // source extent of query 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 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. // 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. // A Result encapsulates the result of an oracle.Query.
type Result struct { type Result struct {
fset *token.FileSet 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{})
q queryResult // the query-specific result q queryResult // the query-specific result
mode string // query mode mode string // query mode
warnings []pointer.Warning // pointer analysis warnings 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? // 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) { 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) minfo := findMode(mode)
if minfo == nil { if minfo == nil {
return nil, fmt.Errorf("invalid mode type: %q", mode) return nil, fmt.Errorf("invalid mode type: %q", mode)
} }
imp := importer.New(&importer.Config{Build: buildContext}) impcfg := importer.Config{Build: buildContext}
o, err := New(imp, args, ptalog, reflection)
// 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)
}
// 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 { if err != nil {
return nil, err return nil, err
} }
// 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)
// }
// }()
// }
var qpos *QueryPos var qpos *QueryPos
if minfo.needs&(needPos|needExactPos) != 0 { if minfo.needs&(needPos|needExactPos) != 0 {
var err error
qpos, err = ParseQueryPos(imp, pos, minfo.needs&needExactPos != 0) qpos, err = ParseQueryPos(imp, pos, minfo.needs&needExactPos != 0)
if err != nil { if err != nil {
return nil, err return nil, err
@ -218,6 +252,58 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
return o.query(minfo, qpos) 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. // 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. // 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) { func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
o := &Oracle{ o := &Oracle{fset: imp.Fset}
prog: ssa.NewProgram(imp.Fset, 0),
timers: make(map[string]time.Duration),
}
o.config.Log = ptalog
o.config.Reflection = reflection
// Load/parse/type-check program from args. // Load/parse/type-check program from args.
start := time.Now()
initialPkgInfos, args, err := imp.LoadInitialPackages(args) initialPkgInfos, args, err := imp.LoadInitialPackages(args)
if err != nil { if err != nil {
return nil, err // I/O or parser error 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 { if len(args) > 0 {
return nil, fmt.Errorf("surplus arguments: %q", args) 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. // Retain type info for all ASTs in the program.
if needs&needAllTypeInfo != 0 { if needs&needRetainTypeInfo != 0 {
m := make(map[*types.Package]*importer.PackageInfo) m := make(map[*types.Package]*importer.PackageInfo)
for _, p := range imp.AllPackages() { for _, p := range imp.AllPackages() {
m[p.Pkg] = p 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. // Create SSA package for the initial packages and their dependencies.
if needs&needSSA != 0 { if needs&needSSA != 0 {
start = time.Now() prog := ssa.NewProgram(o.fset, 0)
// Create SSA packages. // Create SSA packages.
if err := o.prog.CreatePackages(imp); err != nil { if err := prog.CreatePackages(imp); err != nil {
return nil, err return nil, err
} }
// For each initial package (specified on the command line), // For each initial package (specified on the command line),
// if it has a main function, analyze that, // if it has a main function, analyze that,
// otherwise analyze its tests, if any. // otherwise analyze its tests, if any.
var testPkgs []*ssa.Package var testPkgs, mains []*ssa.Package
for _, info := range initialPkgInfos { for _, info := range initialPkgInfos {
initialPkg := o.prog.Package(info.Pkg) initialPkg := prog.Package(info.Pkg)
// Add package to the pointer analysis scope. // Add package to the pointer analysis scope.
if initialPkg.Func("main") != nil { if initialPkg.Func("main") != nil {
o.config.Mains = append(o.config.Mains, initialPkg) mains = append(mains, initialPkg)
} else { } else {
testPkgs = append(testPkgs, initialPkg) testPkgs = append(testPkgs, initialPkg)
} }
} }
if testPkgs != nil { if testPkgs != nil {
if p := o.prog.CreateTestMainPackage(testPkgs...); p != nil { if p := prog.CreateTestMainPackage(testPkgs...); p != nil {
o.config.Mains = append(o.config.Mains, p) mains = append(mains, p)
} }
} }
if o.config.Mains == nil { if mains == nil {
return nil, fmt.Errorf("analysis scope has no main and no tests") return nil, fmt.Errorf("analysis scope has no main and no tests")
} }
o.ptaConfig.Log = ptalog
o.ptaConfig.Reflection = reflection
o.ptaConfig.Mains = mains
if needs&needSSADebug != 0 { if needs&needSSADebug != 0 {
for _, pkg := range o.prog.AllPackages() { for _, pkg := range prog.AllPackages() {
pkg.SetDebugMode(true) pkg.SetDebugMode(true)
} }
} }
o.timers["SSA-create"] = time.Since(start) o.prog = prog
} }
return o, nil return o, nil
} }
// Query runs the query of the specified mode and selection. // 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) { func (o *Oracle) Query(mode string, qpos *QueryPos) (*Result, error) {
minfo := findMode(mode) minfo := findMode(mode)
if minfo == nil { 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) { 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{ res := &Result{
mode: minfo.name, mode: minfo.name,
fset: o.prog.Fset, fset: o.fset,
fprintf: o.fprintf, // captures o.prog, o.{start,end}Pos for later printing
} }
var err error var err error
res.q, err = minfo.impl(o, qpos) 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. // 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) { func ParseQueryPos(imp *importer.Importer, posFlag string, needExact bool) (*QueryPos, error) {
start, end, err := parseQueryPos(imp.Fset, pos) filename, startOffset, endOffset, err := parsePosFlag(posFlag)
if err != nil {
return nil, err
}
start, end, err := findQueryPos(imp.Fset, filename, startOffset, endOffset)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -342,13 +437,13 @@ func ParseQueryPos(imp *importer.Importer, pos string, needExact bool) (*QueryPo
if needExact && !exact { if needExact && !exact {
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
} }
return &QueryPos{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. // WriteTo writes the oracle query result res to out in a compiler diagnostic format.
func (res *Result) WriteTo(out io.Writer) { func (res *Result) WriteTo(out io.Writer) {
printf := func(pos interface{}, format string, args ...interface{}) { 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) res.q.display(printf)
@ -367,110 +462,12 @@ func (res *Result) WriteTo(out io.Writer) {
// Not needed in simpler modes, e.g. freevars. // Not needed in simpler modes, e.g. freevars.
// //
func buildSSA(o *Oracle) { func buildSSA(o *Oracle) {
start := time.Now()
o.prog.BuildAll() o.prog.BuildAll()
o.timers["SSA-build"] = time.Since(start)
} }
// ptrAnalysis runs the pointer analysis and returns its result. // ptrAnalysis runs the pointer analysis and returns its result.
func ptrAnalysis(o *Oracle) *pointer.Result { func ptrAnalysis(o *Oracle) *pointer.Result {
start := time.Now() return pointer.Analyze(&o.ptaConfig)
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
} }
// unparen returns e with any enclosing parentheses stripped. // 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. // compilation-error-regexp in Emacs' compilation mode.
// TODO(adonovan): support other editors. // 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 var start, end token.Pos
switch pos := pos.(type) { switch pos := pos.(type) {
case ast.Node: 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)) 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) // (prints "-: " for token.NoPos)
fmt.Fprintf(w, "%s: ", sp) fmt.Fprintf(w, "%s: ", sp)
} else { } else {
ep := o.prog.Fset.Position(end) ep := fset.Position(end)
// The -1 below is a concession to Emacs's broken use of // The -1 below is a concession to Emacs's broken use of
// inclusive (not half-open) intervals. // inclusive (not half-open) intervals.
// Other editors may not want it. // 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...) fmt.Fprintf(w, format, args...)
io.WriteString(w, "\n") 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()
}

View File

@ -206,13 +206,17 @@ func TestOracle(t *testing.T) {
"testdata/src/main/implements.go", "testdata/src/main/implements.go",
"testdata/src/main/imports.go", "testdata/src/main/imports.go",
"testdata/src/main/peers.go", "testdata/src/main/peers.go",
"testdata/src/main/pointsto.go",
"testdata/src/main/reflection.go", "testdata/src/main/reflection.go",
"testdata/src/main/what.go",
// JSON: // JSON:
"testdata/src/main/callgraph-json.go", "testdata/src/main/callgraph-json.go",
"testdata/src/main/calls-json.go", "testdata/src/main/calls-json.go",
"testdata/src/main/peers-json.go", "testdata/src/main/peers-json.go",
"testdata/src/main/describe-json.go", "testdata/src/main/describe-json.go",
"testdata/src/main/pointsto-json.go",
"testdata/src/main/referrers-json.go", "testdata/src/main/referrers-json.go",
"testdata/src/main/what-json.go",
} { } {
useJson := strings.HasSuffix(filename, "-json.go") useJson := strings.HasSuffix(filename, "-json.go")
queries := parseQueries(t, filename) queries := parseQueries(t, filename)

View File

@ -60,11 +60,11 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
// ignore both directionality and type names. // ignore both directionality and type names.
queryType := queryOp.ch.Type() queryType := queryOp.ch.Type()
queryElemType := queryType.Underlying().(*types.Chan).Elem() queryElemType := queryType.Underlying().(*types.Chan).Elem()
o.config.AddQuery(queryOp.ch) o.ptaConfig.AddQuery(queryOp.ch)
i := 0 i := 0
for _, op := range ops { for _, op := range ops {
if types.IsIdentical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) { if types.IsIdentical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
o.config.AddQuery(op.ch) o.ptaConfig.AddQuery(op.ch)
ops[i] = op ops[i] = op
i++ i++
} }

257
oracle/pointsto.go Normal file
View File

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

149
oracle/pos.go Normal file
View File

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

View File

@ -30,21 +30,21 @@ func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
// Iterate over all go/types' resolver facts for the entire program. // 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 _, info := range o.typeInfo {
for id2, obj2 := range info.Objects { for id2, obj2 := range info.Objects {
if sameObj(obj, obj2) { if sameObj(obj, obj2) {
if id2.NamePos == obj.Pos() { if id2.NamePos == obj.Pos() {
continue // skip defining ident continue // skip defining ident
} }
refs = append(refs, id2.NamePos) refs = append(refs, id2)
} }
} }
} }
sort.Sort(byPos(refs)) sort.Sort(byNamePos(refs))
return &referrersResult{ return &referrersResult{
query: id.NamePos, query: id,
obj: obj, obj: obj,
refs: refs, refs: refs,
}, nil }, nil
@ -65,14 +65,22 @@ func sameObj(x, y types.Object) bool {
return false 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 { type referrersResult struct {
query token.Pos // identifer of query query *ast.Ident // identifer of query
obj types.Object // object it denotes 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) { 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()) printf(r.query, "reference to %s", r.obj.Name())
} }
// TODO(adonovan): pretty-print object using same logic as // 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) { func (r *referrersResult) toSerial(res *serial.Result, fset *token.FileSet) {
referrers := &serial.Referrers{ referrers := &serial.Referrers{
Pos: fset.Position(r.query).String(), Pos: fset.Position(r.query.Pos()).String(),
Desc: r.obj.String(), Desc: r.obj.String(),
} }
if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos() if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos()
referrers.ObjPos = fset.Position(pos).String() referrers.ObjPos = fset.Position(pos).String()
} }
for _, ref := range r.refs { 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 res.Referrers = referrers
} }

View File

@ -32,6 +32,12 @@ type Referrers struct {
Refs []string `json:"refs,omitempty"` // locations of all references 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 { type CalleesItem struct {
Name string `json:"name"` // full name of called function Name string `json:"name"` // full name of called function
Pos string `json:"pos"` // location 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 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, // A "label" is an object that may be pointed to by a pointer, map,
// channel, 'func', slice or interface. Labels include: // channel, 'func', slice or interface. Labels include:
@ -116,35 +140,33 @@ type Implements struct {
// - channels, maps and arrays created by make() // - channels, maps and arrays created by make()
// - and their subelements, e.g. "alloc.y[*].z" // - 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 Pos string `json:"pos"` // location of syntax that allocated the object
Desc string `json:"desc"` // description of the label Desc string `json:"desc"` // description of the label
} }
// A DescribePointer describes a single pointer: its type and the // A PointsTo is one element of the result of a 'pointsto' query on an
// set of "labels" it points to. // expression. It describes a single pointer: its type and the set of
// "labels" it points to.
// //
type DescribePointer struct { // If the pointer is of interface type, it will have one PTS entry
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
// describing each concrete type that it may contain. For each // describing each concrete type that it may contain. For each
// concrete type that is a pointer, the PTS entry describes the labels // 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 // it may point to. The same is true for reflect.Values, except the
// dynamic types needn't be concrete. // 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 DescribeValue struct {
Type string `json:"type"` // type of the expression Type string `json:"type"` // type of the expression
Value string `json:"value,omitempty"` // value of the expression, if constant Value string `json:"value,omitempty"` // value of the expression, if constant
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident 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 DescribeMethod struct { type DescribeMethod struct {
@ -200,8 +222,6 @@ type PTAWarning struct {
// A Result is the common result of any oracle query. // A Result is the common result of any oracle query.
// It contains a query-specific result element. // 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 { type Result struct {
Mode string `json:"mode"` // mode of the query Mode string `json:"mode"` // mode of the query
@ -211,11 +231,14 @@ type Result struct {
Callers []Caller `json:"callers,omitempty"` Callers []Caller `json:"callers,omitempty"`
Callgraph []CallGraph `json:"callgraph,omitempty"` Callgraph []CallGraph `json:"callgraph,omitempty"`
Callstack *CallStack `json:"callstack,omitempty"` Callstack *CallStack `json:"callstack,omitempty"`
Definition *Definition `json:"definition,omitempty"`
Describe *Describe `json:"describe,omitempty"` Describe *Describe `json:"describe,omitempty"`
Freevars []*FreeVar `json:"freevars,omitempty"` Freevars []*FreeVar `json:"freevars,omitempty"`
Implements []*Implements `json:"implements,omitempty"` Implements []*Implements `json:"implements,omitempty"`
Peers *Peers `json:"peers,omitempty"` Peers *Peers `json:"peers,omitempty"`
PointsTo []PointsTo `json:"pointsto,omitempty"`
Referrers *Referrers `json:"referrers,omitempty"` Referrers *Referrers `json:"referrers,omitempty"`
What *What `json:"what,omitempty"`
Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis
} }

View File

@ -4,12 +4,12 @@ package main
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.
// See calls.golden for expected query results. // 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 "^" // @callers callers-A "^"
// @callstack callstack-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 "^" // @callers callers-B "^"
} }
@ -28,7 +28,7 @@ func store(ptr **int, value *int) {
func call(f func() *int) { func call(f func() *int) {
// Result points to anon function. // Result points to anon function.
f() // @describe describe-result-f "f" f() // @pointsto pointsto-result-f "f"
// Target of call is anon function. // Target of call is anon function.
f() // @callees callees-main.call-f "f" f() // @callees callees-main.call-f "f"
@ -42,10 +42,10 @@ func main() {
apply(B, &b) apply(B, &b)
var c, d int var c, d int
var pc, pd *int // @describe describe-pc "pc" var pc, pd *int // @pointsto pointsto-pc "pc"
store(&pc, &c) store(&pc, &c)
store(&pd, &d) store(&pd, &d)
_ = pd // @describe describe-pd "pd" _ = pd // @pointsto pointsto-pd "pd"
call(func() *int { call(func() *int {
// We are called twice from main.call // We are called twice from main.call
@ -71,12 +71,12 @@ func main() {
var dynamic = func() {} var dynamic = func() {}
// Within dead code, dynamic calls have no callees.
func deadcode() { func deadcode() {
main() // @callees callees-err-deadcode2 "main" main() // @callees callees-err-deadcode2 "main"
// @callers callers-err-deadcode "^" // @callers callers-err-deadcode "^"
// @callstack callstack-err-deadcode "^" // @callstack callstack-err-deadcode "^"
// Within dead code, dynamic calls have no callees.
dynamic() // @callees callees-err-deadcode3 "dynamic" dynamic() // @callees callees-err-deadcode3 "dynamic"
} }

View File

@ -1,6 +1,5 @@
-------- @describe describe-A-x -------- -------- @pointsto pointsto-A-x --------
definition of var x *int this *int may point to these objects:
value may point to these labels:
a a
b b
@ -10,9 +9,8 @@ main.A
dynamic function call from main.apply dynamic function call from main.apply
static function call from main.main static function call from main.main
-------- @describe describe-B-x -------- -------- @pointsto pointsto-B-x --------
definition of var x *int this *int may point to these objects:
value may point to these labels:
a a
b 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
static function call from main.main static function call from main.main
-------- @describe describe-result-f -------- -------- @pointsto pointsto-result-f --------
reference to var f func() *int this func() *int may point to these objects:
defined here
value may point to these labels:
func@50.7 func@50.7
-------- @callees callees-main.call-f -------- -------- @callees callees-main.call-f --------
@ -54,15 +50,12 @@ main.call is called from these 2 sites:
this static function call dispatches to: this static function call dispatches to:
main.apply main.apply
-------- @describe describe-pc -------- -------- @pointsto pointsto-pc --------
definition of var pc *int this *int may point to these objects:
value may point to these labels:
c c
-------- @describe describe-pd -------- -------- @pointsto pointsto-pd --------
reference to var pd *int this *int may point to these objects:
defined here
value may point to these labels:
d d
-------- @callees callees-err-no-call -------- -------- @callees callees-err-no-call --------

View File

@ -79,18 +79,7 @@
"detail": "value", "detail": "value",
"value": { "value": {
"type": "*int", "type": "*int",
"objpos": "testdata/src/main/describe-json.go:11:2", "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[*]"
}
]
}
]
} }
} }
}-------- @describe desc-val-i -------- }-------- @describe desc-val-i --------
@ -102,23 +91,7 @@
"detail": "value", "detail": "value",
"value": { "value": {
"type": "I", "type": "I",
"objpos": "testdata/src/main/describe-json.go:14:6", "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"
}
]
} }
} }
}-------- @describe desc-stmt -------- }-------- @describe desc-stmt --------

View File

@ -69,13 +69,11 @@ func main() { // @describe func-def-main "main"
go main() // @describe go-stmt "go" go main() // @describe go-stmt "go"
panic(3) // @describe builtin-ref-panic "panic" panic(3) // @describe builtin-ref-panic "panic"
}
func deadcode() { var a2 int // @describe var-decl-stmt "var a2 int"
var a int // @describe var-decl-stmt "var a int" _ = a2
// Pointer analysis can't run on dead code. var _ int // @describe var-decl-stmt2 "var _ int"
var b = &a // @describe b "b" var _ int // @describe var-def-blank "_"
_ = b
} }
type I interface { // @describe def-iface-I "I" type I interface { // @describe def-iface-I "I"

View File

@ -1,18 +1,17 @@
-------- @describe pkgdecl -------- -------- @describe pkgdecl --------
definition of package "describe" definition of package "describe"
type C int type C int
method (*describe.C) f() method (*C) f()
type D struct{} type D struct{}
method (describe.D) f() method (D) f()
type I interface{f()} type I interface{f()}
method (describe.I) f() method (I) f()
const c untyped integer = 0 const c untyped integer = 0
type cake float64 type cake float64
func deadcode func() var global *string
var global *string func main func()
func main func() const pi untyped float = 3141/1000
const pi untyped float = 3141/1000 const pie cake = 1768225803696341/562949953421312
const pie cake = 1768225803696341/562949953421312
-------- @describe type-ref-builtin -------- -------- @describe type-ref-builtin --------
reference to built-in type float64 reference to built-in type float64
@ -76,58 +75,37 @@ defined here
-------- @describe ref-anon -------- -------- @describe ref-anon --------
reference to var anon func() reference to var anon func()
defined here defined here
value may point to these labels:
func@31.10
-------- @describe ref-global -------- -------- @describe ref-global --------
reference to var global *string reference to var global *string
defined here defined here
value may point to these labels:
new
-------- @describe var-def-x-1 -------- -------- @describe var-def-x-1 --------
definition of var x *int definition of var x *int
value may point to these labels:
a
-------- @describe var-ref-x-1 -------- -------- @describe var-ref-x-1 --------
reference to var x *int reference to var x *int
defined here defined here
value may point to these labels:
a
-------- @describe var-def-x-2 -------- -------- @describe var-def-x-2 --------
reference to var x *int reference to var x *int
defined here defined here
value may point to these labels:
b
-------- @describe var-ref-x-2 -------- -------- @describe var-ref-x-2 --------
reference to var x *int reference to var x *int
defined here defined here
value may point to these labels:
b
-------- @describe var-ref-i-C -------- -------- @describe var-ref-i-C --------
reference to var i I reference to var i I
defined here defined here
this I may contain these dynamic types:
*C, may point to:
new
-------- @describe var-ref-i-D -------- -------- @describe var-ref-i-D --------
reference to var i I reference to var i I
defined here defined here
this I may contain these dynamic types:
D
-------- @describe var-ref-i -------- -------- @describe var-ref-i --------
reference to var i I reference to var i I
defined here defined here
this I may contain these dynamic types:
*C, may point to:
new
D
-------- @describe const-local-pi -------- -------- @describe const-local-pi --------
definition of const localpi untyped float definition of const localpi untyped float
@ -160,14 +138,10 @@ index expression of type (*int, bool)
-------- @describe mapval -------- -------- @describe mapval --------
reference to var mapval *int reference to var mapval *int
defined here defined here
value may point to these labels:
a
-------- @describe m -------- -------- @describe m --------
reference to var m map[string]*int reference to var m map[string]*int
defined here defined here
value may point to these labels:
makemap
-------- @describe defer-stmt -------- -------- @describe defer-stmt --------
defer statement defer statement
@ -179,11 +153,13 @@ go statement
function call (or conversion) of type () function call (or conversion) of type ()
-------- @describe var-decl-stmt -------- -------- @describe var-decl-stmt --------
definition of var a int definition of var a2 int
-------- @describe b -------- -------- @describe var-decl-stmt2 --------
definition of var b *int definition of var _ int
no points-to information: PTA did not encounter this expression (dead code?)
-------- @describe var-def-blank --------
definition of var _ int
-------- @describe def-iface-I -------- -------- @describe def-iface-I --------
definition of type I (size 16, align 8) definition of type I (size 16, align 8)

View File

@ -1,6 +1,6 @@
-------- @freevars fv1 -------- -------- @freevars fv1 --------
Free identifiers: Free identifiers:
type C main.C type C
const exp int const exp int
var x int var x int

View File

@ -20,7 +20,7 @@ func main() {
var t lib.Type // @describe ref-type "Type" var t lib.Type // @describe ref-type "Type"
p := t.Method(&a) // @describe ref-method "Method" 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" var _ lib.Type // @describe ref-pkg "lib"
} }

View File

@ -3,7 +3,7 @@ import of package "lib"
const Const untyped integer = 3 const Const untyped integer = 3
func Func func() func Func func()
type Type int type Type int
method (lib.Type) Method(x *int) *int method (Type) Method(x *int) *int
var Var int var Var int
-------- @describe ref-const -------- -------- @describe ref-const --------
@ -28,10 +28,8 @@ Method set:
reference to method func (lib.Type).Method(x *int) *int reference to method func (lib.Type).Method(x *int) *int
defined here defined here
-------- @describe p -------- -------- @pointsto p --------
reference to var p *int this *int may point to these objects:
defined here
value may point to these labels:
imports.a imports.a
-------- @describe ref-pkg -------- -------- @describe ref-pkg --------
@ -39,6 +37,6 @@ reference to package "lib"
const Const untyped integer = 3 const Const untyped integer = 3
func Func func() func Func func()
type Type int type Type int
method (lib.Type) Method(x *int) *int method (Type) Method(x *int) *int
var Var int var Var int

View File

@ -20,15 +20,15 @@ func main() {
b := 3 b := 3
chB <- &b chB <- &b
<-chA // @describe describe-chA "chA" <-chA // @pointsto pointsto-chA "chA"
<-chA2 // @describe describe-chA2 "chA2" <-chA2 // @pointsto pointsto-chA2 "chA2"
<-chB // @describe describe-chB "chB" <-chB // @pointsto pointsto-chB "chB"
select { select {
case rA := <-chA: // @peers peer-recv-chA "<-" case rA := <-chA: // @peers peer-recv-chA "<-"
_ = rA // @describe describe-rA "rA" _ = rA // @pointsto pointsto-rA "rA"
case rB := <-chB: // @peers peer-recv-chB "<-" case rB := <-chB: // @peers peer-recv-chB "<-"
_ = rB // @describe describe-rB "rB" _ = rB // @pointsto pointsto-rB "rB"
case <-chA: // @peers peer-recv-chA' "<-" case <-chA: // @peers peer-recv-chA' "<-"

View File

@ -1,20 +1,14 @@
-------- @describe describe-chA -------- -------- @pointsto pointsto-chA --------
reference to var chA chan *int this chan *int may point to these objects:
defined here
value may point to these labels:
makechan makechan
makechan makechan
-------- @describe describe-chA2 -------- -------- @pointsto pointsto-chA2 --------
reference to var chA2 chan *int this chan *int may point to these objects:
defined here
value may point to these labels:
makechan makechan
-------- @describe describe-chB -------- -------- @pointsto pointsto-chB --------
reference to var chB chan *int this chan *int may point to these objects:
defined here
value may point to these labels:
makechan makechan
-------- @peers peer-recv-chA -------- -------- @peers peer-recv-chA --------
@ -29,10 +23,8 @@ This channel of type chan *int may be:
received from, here received from, here
received from, here received from, here
-------- @describe describe-rA -------- -------- @pointsto pointsto-rA --------
reference to var rA *int this *int may point to these objects:
defined here
value may point to these labels:
peers.a2 peers.a2
a1 a1
@ -43,10 +35,8 @@ This channel of type chan *int may be:
received from, here received from, here
received from, here received from, here
-------- @describe describe-rB -------- -------- @pointsto pointsto-rB --------
reference to var rB *int this *int may point to these objects:
defined here
value may point to these labels:
b b
-------- @peers peer-recv-chA' -------- -------- @peers peer-recv-chA' --------

View File

@ -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() {}

View File

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

68
oracle/testdata/src/main/pointsto.go vendored Normal file
View File

@ -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() {}

View File

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

View File

@ -1,7 +1,7 @@
package reflection package reflection
// This is a test of 'describe', but we split it into a separate file // This is a test of 'pointsto', but we split it into a separate file
// so that describe.go doesn't have to import "reflect" each time. // so that pointsto.go doesn't have to import "reflect" each time.
import "reflect" import "reflect"
@ -20,11 +20,11 @@ func main() {
mrv = reflect.ValueOf(&a) mrv = reflect.ValueOf(&a)
} }
_ = mrv // @describe mrv "mrv" _ = mrv // @pointsto mrv "mrv"
p1 := mrv.Interface() // @describe p1 "p1" p1 := mrv.Interface() // @pointsto p1 "p1"
p2 := mrv.MapKeys() // @describe p2 "p2" p2 := mrv.MapKeys() // @pointsto p2 "p2"
p3 := p2[0] // @describe p3 "p3" p3 := p2[0] // @pointsto p3 "p3"
p4 := reflect.TypeOf(p1) // @describe p4 "p4" p4 := reflect.TypeOf(p1) // @pointsto p4 "p4"
_, _, _, _ = p1, p2, p3, p4 _, _, _, _ = p1, p2, p3, p4
} }

View File

@ -1,6 +1,4 @@
-------- @describe mrv -------- -------- @pointsto mrv --------
reference to var mrv reflect.Value
defined here
this reflect.Value may contain these dynamic types: this reflect.Value may contain these dynamic types:
*bool, may point to: *bool, may point to:
reflection.b reflection.b
@ -9,8 +7,7 @@ this reflect.Value may contain these dynamic types:
map[*int]*bool, may point to: map[*int]*bool, may point to:
makemap makemap
-------- @describe p1 -------- -------- @pointsto p1 --------
definition of var p1 interface{}
this interface{} may contain these dynamic types: this interface{} may contain these dynamic types:
*bool, may point to: *bool, may point to:
reflection.b reflection.b
@ -19,19 +16,16 @@ this interface{} may contain these dynamic types:
map[*int]*bool, may point to: map[*int]*bool, may point to:
makemap makemap
-------- @describe p2 -------- -------- @pointsto p2 --------
definition of var p2 []reflect.Value this []reflect.Value may point to these objects:
value may point to these labels:
<alloc in (reflect.Value).MapKeys> <alloc in (reflect.Value).MapKeys>
-------- @describe p3 -------- -------- @pointsto p3 --------
definition of var p3 reflect.Value
this reflect.Value may contain these dynamic types: this reflect.Value may contain these dynamic types:
*int, may point to: *int, may point to:
reflection.a reflection.a
-------- @describe p4 -------- -------- @pointsto p4 --------
definition of var p4 reflect.Type
this reflect.Type may contain these dynamic types: this reflect.Type may contain these dynamic types:
*reflect.rtype, may point to: *reflect.rtype, may point to:
*bool *bool

9
oracle/testdata/src/main/what-json.go vendored Normal file
View File

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

View File

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

11
oracle/testdata/src/main/what.go vendored Normal file
View File

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

39
oracle/testdata/src/main/what.golden vendored Normal file
View File

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

197
oracle/what.go Normal file
View File

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

View File

@ -101,6 +101,8 @@ func (l Label) Pos() token.Pos {
// append.y[*].z (array allocated by append) // append.y[*].z (array allocated by append)
// makeslice.y[*].z (array allocated via make) // makeslice.y[*].z (array allocated via make)
// //
// TODO(adonovan): expose func LabelString(*types.Package, Label).
//
func (l Label) String() string { func (l Label) String() string {
var s string var s string
switch v := l.obj.data.(type) { switch v := l.obj.data.(type) {