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:
parent
3df3227c35
commit
f119874203
|
|
@ -576,7 +576,7 @@ func NodeDescription(n ast.Node) string {
|
|||
if n.Tok == token.INC {
|
||||
return "increment statement"
|
||||
}
|
||||
return "derement statement"
|
||||
return "decrement statement"
|
||||
case *ast.IndexExpr:
|
||||
return "index expression"
|
||||
case *ast.InterfaceType:
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ func main() {
|
|||
os.Exit(2)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
if len(args) == 0 && mode != "what" {
|
||||
fmt.Fprint(os.Stderr, "Error: no package arguments.\n"+useHelp)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,18 @@
|
|||
nil
|
||||
"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
|
||||
(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 g") #'go-oracle-callgraph)
|
||||
(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 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 <") #'go-oracle-callers)
|
||||
(define-key m (kbd "C-c C-o >") #'go-oracle-callees)
|
||||
|
|
@ -128,7 +132,7 @@ result."
|
|||
;; Hide the file/line info to save space.
|
||||
;; Replace each with a little widget.
|
||||
;; 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.
|
||||
(let ((buffer-read-only nil)
|
||||
(p 1))
|
||||
|
|
@ -171,11 +175,21 @@ function containing the current point."
|
|||
(interactive)
|
||||
(go-oracle--run "callstack"))
|
||||
|
||||
(defun go-oracle-definition ()
|
||||
"Show the definition of the selected identifier."
|
||||
(interactive)
|
||||
(go-oracle--run "definition"))
|
||||
|
||||
(defun go-oracle-describe ()
|
||||
"Describe the expression at the current point."
|
||||
"Describe the selected syntax, its kind, type and methods."
|
||||
(interactive)
|
||||
(go-oracle--run "describe"))
|
||||
|
||||
(defun go-oracle-pointsto ()
|
||||
"Show what the selected expression points to."
|
||||
(interactive)
|
||||
(go-oracle--run "pointsto"))
|
||||
|
||||
(defun go-oracle-implements ()
|
||||
"Describe the 'implements' relation for types in the package
|
||||
containing the current point."
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ import (
|
|||
// An Importer's exported methods are not thread-safe.
|
||||
type Importer struct {
|
||||
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
|
||||
augment map[string]bool // packages to be augmented by TestFiles when imported
|
||||
allPackagesMu sync.Mutex // guards 'allPackages' during internal concurrency
|
||||
|
|
@ -81,9 +81,24 @@ type importInfo struct {
|
|||
// Config specifies the configuration for the importer.
|
||||
type Config struct {
|
||||
// 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.
|
||||
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 it is nil, binary object files produced by the gc
|
||||
|
|
@ -100,6 +115,13 @@ type Config struct {
|
|||
// specified by config.
|
||||
//
|
||||
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
|
||||
if importfn == nil {
|
||||
importfn = gcimporter.Import
|
||||
|
|
@ -107,18 +129,11 @@ func New(config *Config) *Importer {
|
|||
|
||||
imp := &Importer{
|
||||
Fset: token.NewFileSet(),
|
||||
config: *config, // copy (don't clobber client input)
|
||||
config: config,
|
||||
importfn: importfn,
|
||||
augment: make(map[string]bool),
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -318,7 +333,18 @@ func (imp *Importer) CreatePackage(path string, files ...*ast.File) *PackageInfo
|
|||
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)
|
||||
return info
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
package importer
|
||||
|
||||
// TODO(gri): absorb this into go/types.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
|
@ -95,3 +93,17 @@ func (info *PackageInfo) TypeCaseVar(cc *ast.CaseClause) *types.Var {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -102,7 +102,7 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
|||
}
|
||||
|
||||
// Dynamic call: use pointer analysis.
|
||||
o.config.BuildCallGraph = true
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
callgraph := ptrAnalysis(o).CallGraph
|
||||
|
||||
// Find all call edges from the site.
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
|
||||
// Run the pointer analysis, recording each
|
||||
// call found to originate from target.
|
||||
o.config.BuildCallGraph = true
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
callgraph := ptrAnalysis(o).CallGraph
|
||||
var edges []call.Edge
|
||||
call.GraphVisitEdges(callgraph, func(edge call.Edge) error {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func callgraph(o *Oracle, _ *QueryPos) (queryResult, error) {
|
|||
buildSSA(o)
|
||||
|
||||
// Run the pointer analysis and build the complete callgraph.
|
||||
o.config.BuildCallGraph = true
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
ptares := ptrAnalysis(o)
|
||||
|
||||
return &callgraphResult{
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
}
|
||||
|
||||
// Run the pointer analysis and build the complete call graph.
|
||||
o.config.BuildCallGraph = true
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
callgraph := ptrAnalysis(o).CallGraph
|
||||
|
||||
// Search for an arbitrary path from a root to the target function.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -10,8 +10,6 @@ import (
|
|||
"go/ast"
|
||||
"go/token"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/go.tools/astutil"
|
||||
|
|
@ -19,25 +17,19 @@ import (
|
|||
"code.google.com/p/go.tools/go/types"
|
||||
"code.google.com/p/go.tools/importer"
|
||||
"code.google.com/p/go.tools/oracle/serial"
|
||||
"code.google.com/p/go.tools/pointer"
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
)
|
||||
|
||||
// describe describes the syntax node denoted by the query position,
|
||||
// including:
|
||||
// - its syntactic category
|
||||
// - the location of the definition of its referent (for identifiers)
|
||||
// - the definition of its referent (for identifiers) [now redundant]
|
||||
// - its type and method set (for an expression or type expression)
|
||||
// - its points-to set (for a pointer-like expression)
|
||||
// - its dynamic types (for an interface, reflect.Value, or
|
||||
// reflect.Type expression) and their points-to sets.
|
||||
//
|
||||
// All printed sets are sorted to ensure determinism.
|
||||
//
|
||||
func describe(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
if false { // debugging
|
||||
o.fprintf(os.Stderr, qpos.path[0], "you selected: %s %s",
|
||||
astutil.NodeDescription(qpos.path[0]), pathToString2(qpos.path))
|
||||
fprintf(os.Stderr, o.fset, qpos.path[0], "you selected: %s %s",
|
||||
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
|
||||
}
|
||||
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
|
|
@ -189,7 +181,7 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
|
|||
|
||||
case *ast.SelectorExpr:
|
||||
if pkginfo.ObjectOf(n.Sel) == nil {
|
||||
// Is this reachable?
|
||||
// TODO(adonovan): is this reachable?
|
||||
return path, actionUnknown
|
||||
}
|
||||
// Descend to .Sel child.
|
||||
|
|
@ -234,6 +226,9 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
|
|||
// For reference to built-in function, return enclosing call.
|
||||
path = path[1:] // ascend to enclosing function call
|
||||
continue
|
||||
|
||||
case *types.Nil:
|
||||
return path, actionExpr
|
||||
}
|
||||
|
||||
// No object.
|
||||
|
|
@ -274,6 +269,7 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
|
|||
default:
|
||||
// e.g. blank identifier (go/types bug?)
|
||||
// or y in "switch y := x.(type)" (go/types bug?)
|
||||
// or code in a _test.go file that's not part of the package.
|
||||
fmt.Printf("unknown reference %s in %T\n", n, path[1])
|
||||
return path, actionUnknown
|
||||
}
|
||||
|
|
@ -297,54 +293,6 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
|
|||
return nil, actionUnknown // unreachable
|
||||
}
|
||||
|
||||
// ---- VALUE ------------------------------------------------------------
|
||||
|
||||
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
|
||||
// to the root of the AST is path. isAddr reports whether the
|
||||
// ssa.Value is the address denoted by the ast.Ident, not its value.
|
||||
// ssaValueForIdent may return a nil Value without an error to
|
||||
// indicate the pointer analysis is not appropriate.
|
||||
//
|
||||
func ssaValueForIdent(prog *ssa.Program, qinfo *importer.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
|
||||
if obj, ok := obj.(*types.Var); ok {
|
||||
pkg := prog.Package(qinfo.Pkg)
|
||||
pkg.Build()
|
||||
if v, addr := prog.VarValue(obj, pkg, path); v != nil {
|
||||
// Don't run pointer analysis on a ref to a const expression.
|
||||
if _, ok := v.(*ssa.Const); ok {
|
||||
return
|
||||
}
|
||||
return v, addr, nil
|
||||
}
|
||||
return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
|
||||
}
|
||||
|
||||
// Don't run pointer analysis on const/func objects.
|
||||
return
|
||||
}
|
||||
|
||||
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident
|
||||
// expression whose path to the root of the AST is path. It may
|
||||
// return a nil Value without an error to indicate the pointer
|
||||
// analysis is not appropriate.
|
||||
//
|
||||
func ssaValueForExpr(prog *ssa.Program, qinfo *importer.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
|
||||
pkg := prog.Package(qinfo.Pkg)
|
||||
pkg.SetDebugMode(true)
|
||||
pkg.Build()
|
||||
|
||||
fn := ssa.EnclosingFunction(pkg, path)
|
||||
if fn == nil {
|
||||
return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
}
|
||||
|
||||
if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
|
||||
return v, addr, nil
|
||||
}
|
||||
|
||||
return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn)
|
||||
}
|
||||
|
||||
func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueResult, error) {
|
||||
var expr ast.Expr
|
||||
var obj types.Object
|
||||
|
|
@ -358,114 +306,28 @@ func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueRe
|
|||
case ast.Expr:
|
||||
expr = n
|
||||
default:
|
||||
// Is this reachable?
|
||||
// TODO(adonovan): is this reachable?
|
||||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
typ := qpos.info.TypeOf(expr)
|
||||
constVal := qpos.info.ValueOf(expr)
|
||||
|
||||
// From this point on, we cannot fail with an error.
|
||||
// Failure to run the pointer analysis will be reported later.
|
||||
//
|
||||
// Our disposition to pointer analysis may be one of the following:
|
||||
// - ok: ssa.Value was const or func.
|
||||
// - error: no ssa.Value for expr (e.g. trivially dead code)
|
||||
// - ok: ssa.Value is non-pointerlike
|
||||
// - error: no Pointer for ssa.Value (e.g. analytically unreachable)
|
||||
// - ok: Pointer has empty points-to set
|
||||
// - ok: Pointer has non-empty points-to set
|
||||
// ptaErr is non-nil only in the "error:" cases.
|
||||
|
||||
var ptaErr error
|
||||
var ptrs []pointerResult
|
||||
|
||||
// Only run pointer analysis on pointerlike expression types.
|
||||
if pointer.CanPoint(typ) {
|
||||
// Determine the ssa.Value for the expression.
|
||||
var value ssa.Value
|
||||
var isAddr bool
|
||||
if obj != nil {
|
||||
// def/ref of func/var/const object
|
||||
value, isAddr, ptaErr = ssaValueForIdent(o.prog, qpos.info, obj, path)
|
||||
} else {
|
||||
// any other expression
|
||||
if qpos.info.ValueOf(path[0].(ast.Expr)) == nil { // non-constant?
|
||||
value, isAddr, ptaErr = ssaValueForExpr(o.prog, qpos.info, path)
|
||||
}
|
||||
}
|
||||
if value != nil {
|
||||
ptrs, ptaErr = describePointer(o, value, isAddr)
|
||||
}
|
||||
}
|
||||
|
||||
return &describeValueResult{
|
||||
qpos: qpos,
|
||||
expr: expr,
|
||||
typ: typ,
|
||||
constVal: constVal,
|
||||
obj: obj,
|
||||
ptaErr: ptaErr,
|
||||
ptrs: ptrs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// describePointer runs the pointer analysis of the selected SSA value or address.
|
||||
func describePointer(o *Oracle, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
|
||||
buildSSA(o)
|
||||
|
||||
if isAddr {
|
||||
o.config.AddIndirectQuery(v)
|
||||
} else {
|
||||
o.config.AddQuery(v)
|
||||
}
|
||||
ptares := ptrAnalysis(o)
|
||||
|
||||
// Combine the PT sets from all contexts.
|
||||
var pointers []pointer.Pointer
|
||||
if isAddr {
|
||||
pointers = ptares.IndirectQueries[v]
|
||||
} else {
|
||||
pointers = ptares.Queries[v]
|
||||
}
|
||||
if pointers == nil {
|
||||
return nil, fmt.Errorf("PTA did not encounter this expression (dead code?)")
|
||||
}
|
||||
pts := pointer.PointsToCombined(pointers)
|
||||
|
||||
if pointer.CanHaveDynamicTypes(v.Type()) {
|
||||
// Show concrete types for interface/reflect.Value expression.
|
||||
if concs := pts.DynamicTypes(); concs.Len() > 0 {
|
||||
concs.Iterate(func(conc types.Type, pta interface{}) {
|
||||
combined := pointer.PointsToCombined(pta.([]pointer.Pointer))
|
||||
labels := combined.Labels()
|
||||
sort.Sort(byPosAndString(labels)) // to ensure determinism
|
||||
ptrs = append(ptrs, pointerResult{conc, labels})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Show labels for other expressions.
|
||||
labels := pts.Labels()
|
||||
sort.Sort(byPosAndString(labels)) // to ensure determinism
|
||||
ptrs = append(ptrs, pointerResult{v.Type(), labels})
|
||||
}
|
||||
sort.Sort(byTypeString(ptrs)) // to ensure determinism
|
||||
return ptrs, nil
|
||||
}
|
||||
|
||||
type pointerResult struct {
|
||||
typ types.Type // type of the pointer (always concrete)
|
||||
labels []*pointer.Label
|
||||
}
|
||||
|
||||
type describeValueResult struct {
|
||||
qpos *QueryPos
|
||||
expr ast.Expr // query node
|
||||
typ types.Type // type of expression
|
||||
constVal exact.Value // value of expression, if constant
|
||||
obj types.Object // var/func/const object, if expr was Ident
|
||||
ptaErr error // reason why pointer analysis couldn't be run, or failed
|
||||
ptrs []pointerResult // pointer info (typ is concrete => len==1)
|
||||
expr ast.Expr // query node
|
||||
typ types.Type // type of expression
|
||||
constVal exact.Value // value of expression, if constant
|
||||
obj types.Object // var/func/const object, if expr was Ident
|
||||
}
|
||||
|
||||
func (r *describeValueResult) display(printf printfFunc) {
|
||||
|
|
@ -506,81 +368,16 @@ func (r *describeValueResult) display(printf printfFunc) {
|
|||
printf(r.expr, "%s of type %s", desc, r.qpos.TypeString(r.typ))
|
||||
}
|
||||
}
|
||||
|
||||
// pointer analysis could not be run
|
||||
if r.ptaErr != nil {
|
||||
printf(r.expr, "no points-to information: %s", r.ptaErr)
|
||||
return
|
||||
}
|
||||
|
||||
if r.ptrs == nil {
|
||||
return // PTA was not invoked (not an error)
|
||||
}
|
||||
|
||||
// Display the results of pointer analysis.
|
||||
if pointer.CanHaveDynamicTypes(r.typ) {
|
||||
// Show concrete types for interface, reflect.Type or
|
||||
// reflect.Value expression.
|
||||
|
||||
if len(r.ptrs) > 0 {
|
||||
printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.TypeString(r.typ))
|
||||
for _, ptr := range r.ptrs {
|
||||
var obj types.Object
|
||||
if nt, ok := deref(ptr.typ).(*types.Named); ok {
|
||||
obj = nt.Obj()
|
||||
}
|
||||
if len(ptr.labels) > 0 {
|
||||
printf(obj, "\t%s, may point to:", r.qpos.TypeString(ptr.typ))
|
||||
printLabels(printf, ptr.labels, "\t\t")
|
||||
} else {
|
||||
printf(obj, "\t%s", r.qpos.TypeString(ptr.typ))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf(r.qpos, "this %s cannot contain any dynamic types.", r.typ)
|
||||
}
|
||||
} else {
|
||||
// Show labels for other expressions.
|
||||
if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
|
||||
printf(r.qpos, "value may point to these labels:")
|
||||
printLabels(printf, ptr.labels, "\t")
|
||||
} else {
|
||||
printf(r.qpos, "value cannot point to anything.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var value, objpos, ptaerr string
|
||||
var value, objpos string
|
||||
if r.constVal != nil {
|
||||
value = r.constVal.String()
|
||||
}
|
||||
if r.obj != nil {
|
||||
objpos = fset.Position(r.obj.Pos()).String()
|
||||
}
|
||||
if r.ptaErr != nil {
|
||||
ptaerr = r.ptaErr.Error()
|
||||
}
|
||||
|
||||
var pts []*serial.DescribePointer
|
||||
for _, ptr := range r.ptrs {
|
||||
var namePos string
|
||||
if nt, ok := deref(ptr.typ).(*types.Named); ok {
|
||||
namePos = fset.Position(nt.Obj().Pos()).String()
|
||||
}
|
||||
var labels []serial.DescribePTALabel
|
||||
for _, l := range ptr.labels {
|
||||
labels = append(labels, serial.DescribePTALabel{
|
||||
Pos: fset.Position(l.Pos()).String(),
|
||||
Desc: l.String(),
|
||||
})
|
||||
}
|
||||
pts = append(pts, &serial.DescribePointer{
|
||||
Type: r.qpos.TypeString(ptr.typ),
|
||||
NamePos: namePos,
|
||||
Labels: labels,
|
||||
})
|
||||
}
|
||||
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: astutil.NodeDescription(r.expr),
|
||||
|
|
@ -590,35 +387,10 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet)
|
|||
Type: r.qpos.TypeString(r.typ),
|
||||
Value: value,
|
||||
ObjPos: objpos,
|
||||
PTAErr: ptaerr,
|
||||
PTS: pts,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type byTypeString []pointerResult
|
||||
|
||||
func (a byTypeString) Len() int { return len(a) }
|
||||
func (a byTypeString) Less(i, j int) bool { return a[i].typ.String() < a[j].typ.String() }
|
||||
func (a byTypeString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type byPosAndString []*pointer.Label
|
||||
|
||||
func (a byPosAndString) Len() int { return len(a) }
|
||||
func (a byPosAndString) Less(i, j int) bool {
|
||||
cmp := a[i].Pos() - a[j].Pos()
|
||||
return cmp < 0 || (cmp == 0 && a[i].String() < a[j].String())
|
||||
}
|
||||
func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
func printLabels(printf printfFunc, labels []*pointer.Label, prefix string) {
|
||||
// TODO(adonovan): due to context-sensitivity, many of these
|
||||
// labels may differ only by context, which isn't apparent.
|
||||
for _, label := range labels {
|
||||
printf(label, "%s%s", prefix, label)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- TYPE ------------------------------------------------------------
|
||||
|
||||
func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResult, error) {
|
||||
|
|
@ -725,13 +497,9 @@ func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePacka
|
|||
var pkg *types.Package
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ImportSpec:
|
||||
// Most ImportSpecs have no .Name Ident so we can't
|
||||
// use ObjectOf.
|
||||
// We could use the types.Info.Implicits mechanism,
|
||||
// but it's easier just to look it up by name.
|
||||
description = "import of package " + n.Path.Value
|
||||
importPath, _ := strconv.Unquote(n.Path.Value)
|
||||
pkg = o.prog.ImportedPackage(importPath).Object
|
||||
pkgname := qpos.info.ImportSpecPkg(n)
|
||||
description = fmt.Sprintf("import of package %q", pkgname.Name())
|
||||
pkg = pkgname.Pkg()
|
||||
|
||||
case *ast.Ident:
|
||||
if _, isDef := path[1].(*ast.File); isDef {
|
||||
|
|
@ -771,7 +539,7 @@ func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePacka
|
|||
}
|
||||
}
|
||||
|
||||
return &describePackageResult{o.prog.Fset, path[0], description, pkg, members}, nil
|
||||
return &describePackageResult{o.fset, path[0], description, pkg, members}, nil
|
||||
}
|
||||
|
||||
type describePackageResult struct {
|
||||
|
|
@ -801,7 +569,7 @@ func (r *describePackageResult) display(printf printfFunc) {
|
|||
for _, mem := range r.members {
|
||||
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
|
||||
for _, meth := range mem.methods {
|
||||
printf(meth.Obj(), "\t\t%s", meth)
|
||||
printf(meth.Obj(), "\t\t%s", types.SelectionString(r.pkg, meth))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -904,7 +672,7 @@ func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResu
|
|||
// Nothing much to say about statements.
|
||||
description = astutil.NodeDescription(n)
|
||||
}
|
||||
return &describeStmtResult{o.prog.Fset, path[0], description}, nil
|
||||
return &describeStmtResult{o.fset, path[0], description}, nil
|
||||
}
|
||||
|
||||
type describeStmtResult struct {
|
||||
|
|
@ -929,7 +697,7 @@ func (r *describeStmtResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|||
|
||||
// pathToString returns a string containing the concrete types of the
|
||||
// nodes in path.
|
||||
func pathToString2(path []ast.Node) string {
|
||||
func pathToString(path []ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, "[")
|
||||
for i, n := range path {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
package oracle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"sort"
|
||||
|
||||
|
|
@ -120,7 +122,7 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
}
|
||||
|
||||
typ := qpos.info.TypeOf(n.(ast.Expr))
|
||||
ref := freevarsRef{kind, o.printNode(n), typ, obj}
|
||||
ref := freevarsRef{kind, printNode(o.fset, n), typ, obj}
|
||||
refsMap[ref.ref] = ref
|
||||
|
||||
if prune {
|
||||
|
|
@ -140,14 +142,12 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
|
||||
return &freevarsResult{
|
||||
qpos: qpos,
|
||||
fset: o.prog.Fset,
|
||||
refs: refs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type freevarsResult struct {
|
||||
qpos *QueryPos
|
||||
fset *token.FileSet
|
||||
refs []freevarsRef
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +164,12 @@ func (r *freevarsResult) display(printf printfFunc) {
|
|||
} else {
|
||||
printf(r.qpos, "Free identifiers:")
|
||||
for _, ref := range r.refs {
|
||||
printf(ref.obj, "%s %s %s", ref.kind, ref.ref, ref.typ)
|
||||
// Avoid printing "type T T".
|
||||
var typstr string
|
||||
if ref.kind != "type" {
|
||||
typstr = " " + types.TypeString(r.qpos.info.Pkg, ref.typ)
|
||||
}
|
||||
printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -190,3 +195,10 @@ type byRef []freevarsRef
|
|||
func (p byRef) Len() int { return len(p) }
|
||||
func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref }
|
||||
func (p byRef) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
// printNode returns the pretty-printed syntax of n.
|
||||
func printNode(fset *token.FileSet, n ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
printer.Fprint(&buf, fset, n)
|
||||
return buf.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"code.google.com/p/go.tools/oracle/serial"
|
||||
)
|
||||
|
||||
// Implements displays the 'implements" relation among all
|
||||
// Implements displays the "implements" relation among all
|
||||
// package-level named types in the package containing the query
|
||||
// position.
|
||||
//
|
||||
|
|
@ -65,7 +65,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
}
|
||||
// TODO(adonovan): sort facts to ensure test nondeterminism.
|
||||
|
||||
return &implementsResult{o.prog.Fset, facts}, nil
|
||||
return &implementsResult{o.fset, facts}, nil
|
||||
}
|
||||
|
||||
type implementsFact struct {
|
||||
|
|
|
|||
354
oracle/oracle.go
354
oracle/oracle.go
|
|
@ -13,22 +13,48 @@ package oracle
|
|||
// This file defines oracle.Query, the entry point for the oracle tool.
|
||||
// The actual executable is defined in cmd/oracle.
|
||||
|
||||
// TODO(adonovan): new query: show all statements that may update the
|
||||
// selected lvalue (local, global, field, etc).
|
||||
// TODO(adonovan): new queries
|
||||
// - show all statements that may update the selected lvalue
|
||||
// (local, global, field, etc).
|
||||
// - show all places where an object of type T is created
|
||||
// (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
|
||||
|
||||
// ORACLE CONTROL FLOW
|
||||
//
|
||||
// The Oracle is somewhat convoluted due to the need to support two
|
||||
// very different use-cases, "one-shot" and "long running", and to do
|
||||
// so quickly.
|
||||
//
|
||||
// The cmd/oracle tool issues "one-shot" queries via the exported
|
||||
// Query function, which creates an Oracle to answer a single query.
|
||||
// newOracle consults the 'needs' flags of the query mode and the
|
||||
// package containing the query to avoid doing more work than it needs
|
||||
// (loading, parsing, type checking, SSA construction).
|
||||
//
|
||||
// The Pythia tool (github.com/fzipp/pythia) is an example of a "long
|
||||
// running" tool. It calls New() and then loops, calling
|
||||
// ParseQueryPos and (*Oracle).Query to handle each incoming HTTP
|
||||
// query. Since New cannot see which queries will follow, it must
|
||||
// load, parse, type-check and SSA-build the entire transitive closure
|
||||
// of the analysis scope, retaining full debug information and all
|
||||
// typed ASTs.
|
||||
//
|
||||
// TODO(adonovan): experiment with inverting the control flow by
|
||||
// making each mode consist of two functions: a "one-shot setup"
|
||||
// function and the existing "impl" function. The one-shot setup
|
||||
// function would do all of the work of Query and newOracle,
|
||||
// specialized to each mode, calling library utilities for the common
|
||||
// things. This would give it more control over "scope reduction".
|
||||
// Long running tools would not call the one-shot setup function but
|
||||
// would have their own setup function equivalent to the existing
|
||||
// 'needsAll' flow path.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.google.com/p/go.tools/astutil"
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
|
|
@ -40,29 +66,25 @@ import (
|
|||
|
||||
// An Oracle holds the program state required for one or more queries.
|
||||
type Oracle struct {
|
||||
out io.Writer // standard output
|
||||
prog *ssa.Program // the SSA program [only populated if need&SSA]
|
||||
config pointer.Config // pointer analysis configuration [TODO rename ptaConfig]
|
||||
|
||||
// need&AllTypeInfo
|
||||
typeInfo map[*types.Package]*importer.PackageInfo // type info for all ASTs in the program
|
||||
|
||||
timers map[string]time.Duration // phase timing information
|
||||
fset *token.FileSet // file set [all queries]
|
||||
prog *ssa.Program // the SSA program [needSSA]
|
||||
ptaConfig pointer.Config // pointer analysis configuration [needPTA]
|
||||
typeInfo map[*types.Package]*importer.PackageInfo // type info for all ASTs in the program [needRetainTypeInfo]
|
||||
}
|
||||
|
||||
// A set of bits indicating the analytical requirements of each mode.
|
||||
//
|
||||
// Typed ASTs for the whole program are always constructed
|
||||
// transiently; they are retained only for the queried package unless
|
||||
// needAllTypeInfo is set.
|
||||
// needRetainTypeInfo is set.
|
||||
const (
|
||||
needPos = 1 << iota // needs a position
|
||||
needExactPos // needs an exact AST selection; implies needPos
|
||||
needAllTypeInfo // needs to retain type info for all ASTs in the program
|
||||
needSSA // needs ssa.Packages for whole program
|
||||
needSSADebug // needs debug info for ssa.Packages
|
||||
needPTA = needSSA // needs pointer analysis
|
||||
needAll = -1 // needs everything (e.g. a sequence of queries)
|
||||
needPos = 1 << iota // needs a position
|
||||
needExactPos // needs an exact AST selection; implies needPos
|
||||
needRetainTypeInfo // needs to retain type info for all ASTs in the program
|
||||
needSSA // needs ssa.Packages for whole program
|
||||
needSSADebug // needs debug info for ssa.Packages
|
||||
needPTA = needSSA // needs pointer analysis
|
||||
needAll = -1 // needs everything (e.g. a sequence of queries)
|
||||
)
|
||||
|
||||
type modeInfo struct {
|
||||
|
|
@ -72,15 +94,20 @@ type modeInfo struct {
|
|||
}
|
||||
|
||||
var modes = []*modeInfo{
|
||||
// Pointer analyses: (whole program)
|
||||
{"callees", needPTA | needExactPos, callees},
|
||||
{"callers", needPTA | needPos, callers},
|
||||
{"callgraph", needPTA, callgraph},
|
||||
{"callstack", needPTA | needPos, callstack},
|
||||
{"describe", needPTA | needSSADebug | needExactPos, describe},
|
||||
{"peers", needPTA | needSSADebug | needPos, peers},
|
||||
{"pointsto", needPTA | needSSADebug | needExactPos, pointsto},
|
||||
|
||||
// Type-based analyses: (modular, mostly)
|
||||
{"definition", needPos, definition},
|
||||
{"describe", needExactPos, describe},
|
||||
{"freevars", needPos, freevars},
|
||||
{"implements", needPos, implements},
|
||||
{"peers", needPTA | needSSADebug | needPos, peers},
|
||||
{"referrers", needAllTypeInfo | needPos, referrers},
|
||||
{"referrers", needRetainTypeInfo | needPos, referrers}, // (whole-program)
|
||||
}
|
||||
|
||||
func findMode(mode string) *modeInfo {
|
||||
|
|
@ -106,9 +133,11 @@ type queryResult interface {
|
|||
// Instances are created by ParseQueryPos.
|
||||
//
|
||||
type QueryPos struct {
|
||||
fset *token.FileSet
|
||||
start, end token.Pos // source extent of query
|
||||
info *importer.PackageInfo // type info for the queried package
|
||||
path []ast.Node // AST path from query node to root of ast.File
|
||||
exact bool // 2nd result of PathEnclosingInterval
|
||||
info *importer.PackageInfo // type info for the queried package (nil for fastQueryPos)
|
||||
}
|
||||
|
||||
// TypeString prints type T relative to the query position.
|
||||
|
|
@ -128,9 +157,7 @@ func (qpos *QueryPos) SelectionString(sel *types.Selection) string {
|
|||
|
||||
// A Result encapsulates the result of an oracle.Query.
|
||||
type Result struct {
|
||||
fset *token.FileSet
|
||||
// fprintf is a closure over the oracle's fileset and start/end position.
|
||||
fprintf func(w io.Writer, pos interface{}, format string, args ...interface{})
|
||||
fset *token.FileSet
|
||||
q queryResult // the query-specific result
|
||||
mode string // query mode
|
||||
warnings []pointer.Warning // pointer analysis warnings
|
||||
|
|
@ -180,31 +207,38 @@ func (res *Result) Serial() *serial.Result {
|
|||
// depends on the query mode; how should we expose this?
|
||||
//
|
||||
func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *build.Context, reflection bool) (*Result, error) {
|
||||
if mode == "what" {
|
||||
// Bypass package loading, type checking, SSA construction.
|
||||
return what(pos, buildContext)
|
||||
}
|
||||
|
||||
minfo := findMode(mode)
|
||||
if minfo == nil {
|
||||
return nil, fmt.Errorf("invalid mode type: %q", mode)
|
||||
}
|
||||
|
||||
imp := importer.New(&importer.Config{Build: buildContext})
|
||||
o, err := New(imp, args, ptalog, reflection)
|
||||
impcfg := importer.Config{Build: buildContext}
|
||||
|
||||
// For queries needing only a single typed package,
|
||||
// reduce the analysis scope to that package.
|
||||
if minfo.needs&(needSSA|needRetainTypeInfo) == 0 {
|
||||
reduceScope(pos, &impcfg, &args)
|
||||
}
|
||||
|
||||
// TODO(adonovan): report type errors to the user via Serial
|
||||
// types, not stderr?
|
||||
// impcfg.TypeChecker.Error = func(err error) {
|
||||
// E := err.(types.Error)
|
||||
// fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg)
|
||||
// }
|
||||
imp := importer.New(&impcfg)
|
||||
o, err := newOracle(imp, args, ptalog, minfo.needs, reflection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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
|
||||
if minfo.needs&(needPos|needExactPos) != 0 {
|
||||
var err error
|
||||
qpos, err = ParseQueryPos(imp, pos, minfo.needs&needExactPos != 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -218,6 +252,58 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
|
|||
return o.query(minfo, qpos)
|
||||
}
|
||||
|
||||
// reduceScope is called for one-shot queries that need only a single
|
||||
// typed package. It attempts to guess the query package from pos and
|
||||
// reduce the analysis scope (set of loaded packages) to just that one
|
||||
// plus (the exported parts of) its dependencies. It leaves its
|
||||
// arguments unchanged on failure.
|
||||
//
|
||||
// TODO(adonovan): this is a real mess... but it's fast.
|
||||
//
|
||||
func reduceScope(pos string, impcfg *importer.Config, args *[]string) {
|
||||
// TODO(adonovan): make the 'args' argument of
|
||||
// (*Importer).LoadInitialPackages part of the
|
||||
// importer.Config, and inline LoadInitialPackages into
|
||||
// NewImporter. Then we won't need the 'args' argument.
|
||||
|
||||
fqpos, err := fastQueryPos(pos)
|
||||
if err != nil {
|
||||
return // bad query
|
||||
}
|
||||
|
||||
// TODO(adonovan): fix: this gives the wrong results for files
|
||||
// in non-importable packages such as tests and ad-hoc packages
|
||||
// specified as a list of files (incl. the oracle's tests).
|
||||
_, importPath, err := guessImportPath(fqpos.fset.File(fqpos.start).Name(), impcfg.Build)
|
||||
if err != nil {
|
||||
return // can't find GOPATH dir
|
||||
}
|
||||
if importPath == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Check that it's possible to load the queried package.
|
||||
// (e.g. oracle tests contain different 'package' decls in same dir.)
|
||||
// Keep consistent with logic in importer/util.go!
|
||||
ctxt2 := *impcfg.Build
|
||||
ctxt2.CgoEnabled = false
|
||||
bp, err := ctxt2.Import(importPath, "", 0)
|
||||
if err != nil {
|
||||
return // no files for package
|
||||
}
|
||||
_ = bp
|
||||
|
||||
// TODO(adonovan): fix: also check that the queried file appears in the package.
|
||||
// for _, f := range bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles {
|
||||
// if sameFile(f, fqpos.filename) { goto found }
|
||||
// }
|
||||
// return // not found
|
||||
// found:
|
||||
|
||||
impcfg.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
|
||||
*args = []string{importPath}
|
||||
}
|
||||
|
||||
// New constructs a new Oracle that can be used for a sequence of queries.
|
||||
//
|
||||
// imp will be used to load source code for imported packages.
|
||||
|
|
@ -233,15 +319,9 @@ func New(imp *importer.Importer, args []string, ptalog io.Writer, reflection boo
|
|||
}
|
||||
|
||||
func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
|
||||
o := &Oracle{
|
||||
prog: ssa.NewProgram(imp.Fset, 0),
|
||||
timers: make(map[string]time.Duration),
|
||||
}
|
||||
o.config.Log = ptalog
|
||||
o.config.Reflection = reflection
|
||||
o := &Oracle{fset: imp.Fset}
|
||||
|
||||
// Load/parse/type-check program from args.
|
||||
start := time.Now()
|
||||
initialPkgInfos, args, err := imp.LoadInitialPackages(args)
|
||||
if err != nil {
|
||||
return nil, err // I/O or parser error
|
||||
|
|
@ -249,10 +329,9 @@ func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs in
|
|||
if len(args) > 0 {
|
||||
return nil, fmt.Errorf("surplus arguments: %q", args)
|
||||
}
|
||||
o.timers["load/parse/type"] = time.Since(start)
|
||||
|
||||
// Retain type info for all ASTs in the program.
|
||||
if needs&needAllTypeInfo != 0 {
|
||||
if needs&needRetainTypeInfo != 0 {
|
||||
m := make(map[*types.Package]*importer.PackageInfo)
|
||||
for _, p := range imp.AllPackages() {
|
||||
m[p.Pkg] = p
|
||||
|
|
@ -262,49 +341,56 @@ func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs in
|
|||
|
||||
// Create SSA package for the initial packages and their dependencies.
|
||||
if needs&needSSA != 0 {
|
||||
start = time.Now()
|
||||
prog := ssa.NewProgram(o.fset, 0)
|
||||
|
||||
// Create SSA packages.
|
||||
if err := o.prog.CreatePackages(imp); err != nil {
|
||||
if err := prog.CreatePackages(imp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For each initial package (specified on the command line),
|
||||
// if it has a main function, analyze that,
|
||||
// otherwise analyze its tests, if any.
|
||||
var testPkgs []*ssa.Package
|
||||
var testPkgs, mains []*ssa.Package
|
||||
for _, info := range initialPkgInfos {
|
||||
initialPkg := o.prog.Package(info.Pkg)
|
||||
initialPkg := prog.Package(info.Pkg)
|
||||
|
||||
// Add package to the pointer analysis scope.
|
||||
if initialPkg.Func("main") != nil {
|
||||
o.config.Mains = append(o.config.Mains, initialPkg)
|
||||
mains = append(mains, initialPkg)
|
||||
} else {
|
||||
testPkgs = append(testPkgs, initialPkg)
|
||||
}
|
||||
}
|
||||
if testPkgs != nil {
|
||||
if p := o.prog.CreateTestMainPackage(testPkgs...); p != nil {
|
||||
o.config.Mains = append(o.config.Mains, p)
|
||||
if p := prog.CreateTestMainPackage(testPkgs...); p != nil {
|
||||
mains = append(mains, p)
|
||||
}
|
||||
}
|
||||
if o.config.Mains == nil {
|
||||
if mains == nil {
|
||||
return nil, fmt.Errorf("analysis scope has no main and no tests")
|
||||
}
|
||||
o.ptaConfig.Log = ptalog
|
||||
o.ptaConfig.Reflection = reflection
|
||||
o.ptaConfig.Mains = mains
|
||||
|
||||
if needs&needSSADebug != 0 {
|
||||
for _, pkg := range o.prog.AllPackages() {
|
||||
for _, pkg := range prog.AllPackages() {
|
||||
pkg.SetDebugMode(true)
|
||||
}
|
||||
}
|
||||
|
||||
o.timers["SSA-create"] = time.Since(start)
|
||||
o.prog = prog
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Query runs the query of the specified mode and selection.
|
||||
//
|
||||
// TODO(adonovan): fix: this function does not currently support the
|
||||
// "what" query, which needs to access the go/build.Context.
|
||||
//
|
||||
func (o *Oracle) Query(mode string, qpos *QueryPos) (*Result, error) {
|
||||
minfo := findMode(mode)
|
||||
if minfo == nil {
|
||||
|
|
@ -314,10 +400,13 @@ func (o *Oracle) Query(mode string, qpos *QueryPos) (*Result, error) {
|
|||
}
|
||||
|
||||
func (o *Oracle) query(minfo *modeInfo, qpos *QueryPos) (*Result, error) {
|
||||
// Clear out residue of previous query (for long-running clients).
|
||||
o.ptaConfig.Queries = nil
|
||||
o.ptaConfig.IndirectQueries = nil
|
||||
|
||||
res := &Result{
|
||||
mode: minfo.name,
|
||||
fset: o.prog.Fset,
|
||||
fprintf: o.fprintf, // captures o.prog, o.{start,end}Pos for later printing
|
||||
mode: minfo.name,
|
||||
fset: o.fset,
|
||||
}
|
||||
var err error
|
||||
res.q, err = minfo.impl(o, qpos)
|
||||
|
|
@ -328,10 +417,16 @@ func (o *Oracle) query(minfo *modeInfo, qpos *QueryPos) (*Result, error) {
|
|||
}
|
||||
|
||||
// ParseQueryPos parses the source query position pos.
|
||||
// If needExact, it must identify a single AST subtree.
|
||||
// If needExact, it must identify a single AST subtree;
|
||||
// this is appropriate for queries that allow fairly arbitrary syntax,
|
||||
// e.g. "describe".
|
||||
//
|
||||
func ParseQueryPos(imp *importer.Importer, pos string, needExact bool) (*QueryPos, error) {
|
||||
start, end, err := parseQueryPos(imp.Fset, pos)
|
||||
func ParseQueryPos(imp *importer.Importer, posFlag string, needExact bool) (*QueryPos, error) {
|
||||
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
start, end, err := findQueryPos(imp.Fset, filename, startOffset, endOffset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -342,13 +437,13 @@ func ParseQueryPos(imp *importer.Importer, pos string, needExact bool) (*QueryPo
|
|||
if needExact && !exact {
|
||||
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
|
||||
}
|
||||
return &QueryPos{start, end, info, path}, nil
|
||||
return &QueryPos{imp.Fset, start, end, path, exact, info}, nil
|
||||
}
|
||||
|
||||
// WriteTo writes the oracle query result res to out in a compiler diagnostic format.
|
||||
func (res *Result) WriteTo(out io.Writer) {
|
||||
printf := func(pos interface{}, format string, args ...interface{}) {
|
||||
res.fprintf(out, pos, format, args...)
|
||||
fprintf(out, res.fset, pos, format, args...)
|
||||
}
|
||||
res.q.display(printf)
|
||||
|
||||
|
|
@ -367,110 +462,12 @@ func (res *Result) WriteTo(out io.Writer) {
|
|||
// Not needed in simpler modes, e.g. freevars.
|
||||
//
|
||||
func buildSSA(o *Oracle) {
|
||||
start := time.Now()
|
||||
o.prog.BuildAll()
|
||||
o.timers["SSA-build"] = time.Since(start)
|
||||
}
|
||||
|
||||
// ptrAnalysis runs the pointer analysis and returns its result.
|
||||
func ptrAnalysis(o *Oracle) *pointer.Result {
|
||||
start := time.Now()
|
||||
result := pointer.Analyze(&o.config)
|
||||
o.timers["pointer analysis"] = time.Since(start)
|
||||
return result
|
||||
}
|
||||
|
||||
// parseOctothorpDecimal returns the numeric value if s matches "#%d",
|
||||
// otherwise -1.
|
||||
func parseOctothorpDecimal(s string) int {
|
||||
if s != "" && s[0] == '#' {
|
||||
if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
|
||||
return int(s)
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// parseQueryPos parses a string of the form "file:pos" or
|
||||
// file:start,end" where pos, start, end match #%d and represent byte
|
||||
// offsets, and returns the extent to which it refers.
|
||||
//
|
||||
// (Numbers without a '#' prefix are reserved for future use,
|
||||
// e.g. to indicate line/column positions.)
|
||||
//
|
||||
func parseQueryPos(fset *token.FileSet, queryPos string) (start, end token.Pos, err error) {
|
||||
if queryPos == "" {
|
||||
err = fmt.Errorf("no source position specified (-pos flag)")
|
||||
return
|
||||
}
|
||||
|
||||
colon := strings.LastIndex(queryPos, ":")
|
||||
if colon < 0 {
|
||||
err = fmt.Errorf("invalid source position -pos=%q", queryPos)
|
||||
return
|
||||
}
|
||||
filename, offset := queryPos[:colon], queryPos[colon+1:]
|
||||
startOffset := -1
|
||||
endOffset := -1
|
||||
if hyphen := strings.Index(offset, ","); hyphen < 0 {
|
||||
// e.g. "foo.go:#123"
|
||||
startOffset = parseOctothorpDecimal(offset)
|
||||
endOffset = startOffset
|
||||
} else {
|
||||
// e.g. "foo.go:#123,#456"
|
||||
startOffset = parseOctothorpDecimal(offset[:hyphen])
|
||||
endOffset = parseOctothorpDecimal(offset[hyphen+1:])
|
||||
}
|
||||
if startOffset < 0 || endOffset < 0 {
|
||||
err = fmt.Errorf("invalid -pos offset %q", offset)
|
||||
return
|
||||
}
|
||||
|
||||
var file *token.File
|
||||
fset.Iterate(func(f *token.File) bool {
|
||||
if sameFile(filename, f.Name()) {
|
||||
// (f.Name() is absolute)
|
||||
file = f
|
||||
return false // done
|
||||
}
|
||||
return true // continue
|
||||
})
|
||||
if file == nil {
|
||||
err = fmt.Errorf("couldn't find file containing position -pos=%q", queryPos)
|
||||
return
|
||||
}
|
||||
|
||||
// Range check [start..end], inclusive of both end-points.
|
||||
|
||||
if 0 <= startOffset && startOffset <= file.Size() {
|
||||
start = file.Pos(int(startOffset))
|
||||
} else {
|
||||
err = fmt.Errorf("start position is beyond end of file -pos=%q", queryPos)
|
||||
return
|
||||
}
|
||||
|
||||
if 0 <= endOffset && endOffset <= file.Size() {
|
||||
end = file.Pos(int(endOffset))
|
||||
} else {
|
||||
err = fmt.Errorf("end position is beyond end of file -pos=%q", queryPos)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// sameFile returns true if x and y have the same basename and denote
|
||||
// the same file.
|
||||
//
|
||||
func sameFile(x, y string) bool {
|
||||
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
|
||||
if xi, err := os.Stat(x); err == nil {
|
||||
if yi, err := os.Stat(y); err == nil {
|
||||
return os.SameFile(xi, yi)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return pointer.Analyze(&o.ptaConfig)
|
||||
}
|
||||
|
||||
// unparen returns e with any enclosing parentheses stripped.
|
||||
|
|
@ -508,7 +505,7 @@ func deref(typ types.Type) types.Type {
|
|||
// compilation-error-regexp in Emacs' compilation mode.
|
||||
// TODO(adonovan): support other editors.
|
||||
//
|
||||
func (o *Oracle) fprintf(w io.Writer, pos interface{}, format string, args ...interface{}) {
|
||||
func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) {
|
||||
var start, end token.Pos
|
||||
switch pos := pos.(type) {
|
||||
case ast.Node:
|
||||
|
|
@ -531,11 +528,11 @@ func (o *Oracle) fprintf(w io.Writer, pos interface{}, format string, args ...in
|
|||
panic(fmt.Sprintf("invalid pos: %T", pos))
|
||||
}
|
||||
|
||||
if sp := o.prog.Fset.Position(start); start == end {
|
||||
if sp := fset.Position(start); start == end {
|
||||
// (prints "-: " for token.NoPos)
|
||||
fmt.Fprintf(w, "%s: ", sp)
|
||||
} else {
|
||||
ep := o.prog.Fset.Position(end)
|
||||
ep := fset.Position(end)
|
||||
// The -1 below is a concession to Emacs's broken use of
|
||||
// inclusive (not half-open) intervals.
|
||||
// Other editors may not want it.
|
||||
|
|
@ -547,10 +544,3 @@ func (o *Oracle) fprintf(w io.Writer, pos interface{}, format string, args ...in
|
|||
fmt.Fprintf(w, format, args...)
|
||||
io.WriteString(w, "\n")
|
||||
}
|
||||
|
||||
// printNode returns the pretty-printed syntax of n.
|
||||
func (o *Oracle) printNode(n ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
printer.Fprint(&buf, o.prog.Fset, n)
|
||||
return buf.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,13 +206,17 @@ func TestOracle(t *testing.T) {
|
|||
"testdata/src/main/implements.go",
|
||||
"testdata/src/main/imports.go",
|
||||
"testdata/src/main/peers.go",
|
||||
"testdata/src/main/pointsto.go",
|
||||
"testdata/src/main/reflection.go",
|
||||
"testdata/src/main/what.go",
|
||||
// JSON:
|
||||
"testdata/src/main/callgraph-json.go",
|
||||
"testdata/src/main/calls-json.go",
|
||||
"testdata/src/main/peers-json.go",
|
||||
"testdata/src/main/describe-json.go",
|
||||
"testdata/src/main/pointsto-json.go",
|
||||
"testdata/src/main/referrers-json.go",
|
||||
"testdata/src/main/what-json.go",
|
||||
} {
|
||||
useJson := strings.HasSuffix(filename, "-json.go")
|
||||
queries := parseQueries(t, filename)
|
||||
|
|
|
|||
|
|
@ -60,11 +60,11 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
// ignore both directionality and type names.
|
||||
queryType := queryOp.ch.Type()
|
||||
queryElemType := queryType.Underlying().(*types.Chan).Elem()
|
||||
o.config.AddQuery(queryOp.ch)
|
||||
o.ptaConfig.AddQuery(queryOp.ch)
|
||||
i := 0
|
||||
for _, op := range ops {
|
||||
if types.IsIdentical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
|
||||
o.config.AddQuery(op.ch)
|
||||
o.ptaConfig.AddQuery(op.ch)
|
||||
ops[i] = op
|
||||
i++
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -30,21 +30,21 @@ func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
}
|
||||
|
||||
// Iterate over all go/types' resolver facts for the entire program.
|
||||
var refs []token.Pos
|
||||
var refs []*ast.Ident
|
||||
for _, info := range o.typeInfo {
|
||||
for id2, obj2 := range info.Objects {
|
||||
if sameObj(obj, obj2) {
|
||||
if id2.NamePos == obj.Pos() {
|
||||
continue // skip defining ident
|
||||
}
|
||||
refs = append(refs, id2.NamePos)
|
||||
refs = append(refs, id2)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(byPos(refs))
|
||||
sort.Sort(byNamePos(refs))
|
||||
|
||||
return &referrersResult{
|
||||
query: id.NamePos,
|
||||
query: id,
|
||||
obj: obj,
|
||||
refs: refs,
|
||||
}, nil
|
||||
|
|
@ -65,14 +65,22 @@ func sameObj(x, y types.Object) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// -------- utils --------
|
||||
|
||||
type byNamePos []*ast.Ident
|
||||
|
||||
func (p byNamePos) Len() int { return len(p) }
|
||||
func (p byNamePos) Less(i, j int) bool { return p[i].NamePos < p[j].NamePos }
|
||||
func (p byNamePos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
type referrersResult struct {
|
||||
query token.Pos // identifer of query
|
||||
query *ast.Ident // identifer of query
|
||||
obj types.Object // object it denotes
|
||||
refs []token.Pos // set of all other references to it
|
||||
refs []*ast.Ident // set of all other references to it
|
||||
}
|
||||
|
||||
func (r *referrersResult) display(printf printfFunc) {
|
||||
if r.query != r.obj.Pos() {
|
||||
if r.query.Pos() != r.obj.Pos() {
|
||||
printf(r.query, "reference to %s", r.obj.Name())
|
||||
}
|
||||
// TODO(adonovan): pretty-print object using same logic as
|
||||
|
|
@ -85,16 +93,18 @@ func (r *referrersResult) display(printf printfFunc) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): encode extent, not just Pos info, in Serial form.
|
||||
|
||||
func (r *referrersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
referrers := &serial.Referrers{
|
||||
Pos: fset.Position(r.query).String(),
|
||||
Pos: fset.Position(r.query.Pos()).String(),
|
||||
Desc: r.obj.String(),
|
||||
}
|
||||
if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos()
|
||||
referrers.ObjPos = fset.Position(pos).String()
|
||||
}
|
||||
for _, ref := range r.refs {
|
||||
referrers.Refs = append(referrers.Refs, fset.Position(ref).String())
|
||||
referrers.Refs = append(referrers.Refs, fset.Position(ref.NamePos).String())
|
||||
}
|
||||
res.Referrers = referrers
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,12 @@ type Referrers struct {
|
|||
Refs []string `json:"refs,omitempty"` // locations of all references
|
||||
}
|
||||
|
||||
// A Definition is the result of a 'definition' query.
|
||||
type Definition struct {
|
||||
ObjPos string `json:"objpos,omitempty"` // location of the definition
|
||||
Desc string `json:"desc"` // description of the denoted object
|
||||
}
|
||||
|
||||
type CalleesItem struct {
|
||||
Name string `json:"name"` // full name of called function
|
||||
Pos string `json:"pos"` // location of called function
|
||||
|
|
@ -104,7 +110,25 @@ type Implements struct {
|
|||
CPos string `json:"cpos"` // location of its definition
|
||||
}
|
||||
|
||||
// A DescribePTALabel describes a pointer analysis label.
|
||||
// A SyntaxNode is one element of a stack of enclosing syntax nodes in
|
||||
// a "what" query.
|
||||
type SyntaxNode struct {
|
||||
Description string `json:"desc"` // description of syntax tree
|
||||
Start int `json:"start"` // start offset (0-based)
|
||||
End int `json:"end"` // end offset
|
||||
}
|
||||
|
||||
// A What is the result of the "what" query, which quickly identifies
|
||||
// the selection, parsing only a single file. It is intended for use
|
||||
// in low-latency GUIs.
|
||||
type What struct {
|
||||
Enclosing []SyntaxNode `json:"enclosing"` // enclosing nodes of syntax tree
|
||||
Modes []string `json:"modes"` // query modes enabled for this selection.
|
||||
SrcDir string `json:"srcdir,omitempty"` // $GOROOT src directory containing queried package
|
||||
ImportPath string `json:"importpath,omitempty"` // import path of queried package
|
||||
}
|
||||
|
||||
// A PointsToLabel describes a pointer analysis label.
|
||||
//
|
||||
// A "label" is an object that may be pointed to by a pointer, map,
|
||||
// channel, 'func', slice or interface. Labels include:
|
||||
|
|
@ -116,35 +140,33 @@ type Implements struct {
|
|||
// - channels, maps and arrays created by make()
|
||||
// - and their subelements, e.g. "alloc.y[*].z"
|
||||
//
|
||||
type DescribePTALabel struct {
|
||||
type PointsToLabel struct {
|
||||
Pos string `json:"pos"` // location of syntax that allocated the object
|
||||
Desc string `json:"desc"` // description of the label
|
||||
}
|
||||
|
||||
// A DescribePointer describes a single pointer: its type and the
|
||||
// set of "labels" it points to.
|
||||
// A PointsTo is one element of the result of a 'pointsto' query on an
|
||||
// expression. It describes a single pointer: its type and the set of
|
||||
// "labels" it points to.
|
||||
//
|
||||
type DescribePointer struct {
|
||||
Type string `json:"type"` // (concrete) type of the pointer
|
||||
NamePos string `json:"namepos,omitempty"` // location of type defn, if Named
|
||||
Labels []DescribePTALabel `json:"labels,omitempty"` // pointed-to objects
|
||||
}
|
||||
|
||||
// A DescribeValue is the additional result of a 'describe' query
|
||||
// if the selection indicates a value or expression.
|
||||
//
|
||||
// If the described value is an interface, it will have one PTS entry
|
||||
// If the pointer is of interface type, it will have one PTS entry
|
||||
// describing each concrete type that it may contain. For each
|
||||
// concrete type that is a pointer, the PTS entry describes the labels
|
||||
// it may point to. The same is true for reflect.Values, except the
|
||||
// dynamic types needn't be concrete.
|
||||
//
|
||||
type PointsTo struct {
|
||||
Type string `json:"type"` // (concrete) type of the pointer
|
||||
NamePos string `json:"namepos,omitempty"` // location of type defn, if Named
|
||||
Labels []PointsToLabel `json:"labels,omitempty"` // pointed-to objects
|
||||
}
|
||||
|
||||
// A DescribeValue is the additional result of a 'describe' query
|
||||
// if the selection indicates a value or expression.
|
||||
type DescribeValue struct {
|
||||
Type string `json:"type"` // type of the expression
|
||||
Value string `json:"value,omitempty"` // value of the expression, if constant
|
||||
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
|
||||
PTAErr string `json:"ptaerr,omitempty"` // reason pointer analysis wasn't attempted
|
||||
PTS []*DescribePointer `json:"pts,omitempty"` // points-to set; an interface may have many
|
||||
Type string `json:"type"` // type of the expression
|
||||
Value string `json:"value,omitempty"` // value of the expression, if constant
|
||||
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
|
||||
}
|
||||
|
||||
type DescribeMethod struct {
|
||||
|
|
@ -200,8 +222,6 @@ type PTAWarning struct {
|
|||
// A Result is the common result of any oracle query.
|
||||
// It contains a query-specific result element.
|
||||
//
|
||||
// TODO(adonovan): perhaps include other info such as: analysis scope,
|
||||
// raw query position, stack of ast nodes, query package, etc.
|
||||
type Result struct {
|
||||
Mode string `json:"mode"` // mode of the query
|
||||
|
||||
|
|
@ -211,11 +231,14 @@ type Result struct {
|
|||
Callers []Caller `json:"callers,omitempty"`
|
||||
Callgraph []CallGraph `json:"callgraph,omitempty"`
|
||||
Callstack *CallStack `json:"callstack,omitempty"`
|
||||
Definition *Definition `json:"definition,omitempty"`
|
||||
Describe *Describe `json:"describe,omitempty"`
|
||||
Freevars []*FreeVar `json:"freevars,omitempty"`
|
||||
Implements []*Implements `json:"implements,omitempty"`
|
||||
Peers *Peers `json:"peers,omitempty"`
|
||||
PointsTo []PointsTo `json:"pointsto,omitempty"`
|
||||
Referrers *Referrers `json:"referrers,omitempty"`
|
||||
What *What `json:"what,omitempty"`
|
||||
|
||||
Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ package main
|
|||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See calls.golden for expected query results.
|
||||
|
||||
func A(x *int) { // @describe describe-A-x "x"
|
||||
func A(x *int) { // @pointsto pointsto-A-x "x"
|
||||
// @callers callers-A "^"
|
||||
// @callstack callstack-A "^"
|
||||
}
|
||||
|
||||
func B(x *int) { // @describe describe-B-x "x"
|
||||
func B(x *int) { // @pointsto pointsto-B-x "x"
|
||||
// @callers callers-B "^"
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ func store(ptr **int, value *int) {
|
|||
|
||||
func call(f func() *int) {
|
||||
// Result points to anon function.
|
||||
f() // @describe describe-result-f "f"
|
||||
f() // @pointsto pointsto-result-f "f"
|
||||
|
||||
// Target of call is anon function.
|
||||
f() // @callees callees-main.call-f "f"
|
||||
|
|
@ -42,10 +42,10 @@ func main() {
|
|||
apply(B, &b)
|
||||
|
||||
var c, d int
|
||||
var pc, pd *int // @describe describe-pc "pc"
|
||||
var pc, pd *int // @pointsto pointsto-pc "pc"
|
||||
store(&pc, &c)
|
||||
store(&pd, &d)
|
||||
_ = pd // @describe describe-pd "pd"
|
||||
_ = pd // @pointsto pointsto-pd "pd"
|
||||
|
||||
call(func() *int {
|
||||
// We are called twice from main.call
|
||||
|
|
@ -71,12 +71,12 @@ func main() {
|
|||
|
||||
var dynamic = func() {}
|
||||
|
||||
// Within dead code, dynamic calls have no callees.
|
||||
func deadcode() {
|
||||
main() // @callees callees-err-deadcode2 "main"
|
||||
// @callers callers-err-deadcode "^"
|
||||
// @callstack callstack-err-deadcode "^"
|
||||
|
||||
// Within dead code, dynamic calls have no callees.
|
||||
dynamic() // @callees callees-err-deadcode3 "dynamic"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
-------- @describe describe-A-x --------
|
||||
definition of var x *int
|
||||
value may point to these labels:
|
||||
-------- @pointsto pointsto-A-x --------
|
||||
this *int may point to these objects:
|
||||
a
|
||||
b
|
||||
|
||||
|
|
@ -10,9 +9,8 @@ main.A
|
|||
dynamic function call from main.apply
|
||||
static function call from main.main
|
||||
|
||||
-------- @describe describe-B-x --------
|
||||
definition of var x *int
|
||||
value may point to these labels:
|
||||
-------- @pointsto pointsto-B-x --------
|
||||
this *int may point to these objects:
|
||||
a
|
||||
b
|
||||
|
||||
|
|
@ -35,10 +33,8 @@ main.store is called from these 2 sites:
|
|||
static function call from main.main
|
||||
static function call from main.main
|
||||
|
||||
-------- @describe describe-result-f --------
|
||||
reference to var f func() *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
-------- @pointsto pointsto-result-f --------
|
||||
this func() *int may point to these objects:
|
||||
func@50.7
|
||||
|
||||
-------- @callees callees-main.call-f --------
|
||||
|
|
@ -54,15 +50,12 @@ main.call is called from these 2 sites:
|
|||
this static function call dispatches to:
|
||||
main.apply
|
||||
|
||||
-------- @describe describe-pc --------
|
||||
definition of var pc *int
|
||||
value may point to these labels:
|
||||
-------- @pointsto pointsto-pc --------
|
||||
this *int may point to these objects:
|
||||
c
|
||||
|
||||
-------- @describe describe-pd --------
|
||||
reference to var pd *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
-------- @pointsto pointsto-pd --------
|
||||
this *int may point to these objects:
|
||||
d
|
||||
|
||||
-------- @callees callees-err-no-call --------
|
||||
|
|
|
|||
|
|
@ -79,18 +79,7 @@
|
|||
"detail": "value",
|
||||
"value": {
|
||||
"type": "*int",
|
||||
"objpos": "testdata/src/main/describe-json.go:11:2",
|
||||
"pts": [
|
||||
{
|
||||
"type": "*int",
|
||||
"labels": [
|
||||
{
|
||||
"pos": "testdata/src/main/describe-json.go:10:6",
|
||||
"desc": "s.x[*]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"objpos": "testdata/src/main/describe-json.go:11:2"
|
||||
}
|
||||
}
|
||||
}-------- @describe desc-val-i --------
|
||||
|
|
@ -102,23 +91,7 @@
|
|||
"detail": "value",
|
||||
"value": {
|
||||
"type": "I",
|
||||
"objpos": "testdata/src/main/describe-json.go:14:6",
|
||||
"pts": [
|
||||
{
|
||||
"type": "*D",
|
||||
"namepos": "testdata/src/main/describe-json.go:28:6",
|
||||
"labels": [
|
||||
{
|
||||
"pos": "testdata/src/main/describe-json.go:16:10",
|
||||
"desc": "new"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "C",
|
||||
"namepos": "testdata/src/main/describe-json.go:27:6"
|
||||
}
|
||||
]
|
||||
"objpos": "testdata/src/main/describe-json.go:14:6"
|
||||
}
|
||||
}
|
||||
}-------- @describe desc-stmt --------
|
||||
|
|
|
|||
|
|
@ -69,13 +69,11 @@ func main() { // @describe func-def-main "main"
|
|||
go main() // @describe go-stmt "go"
|
||||
|
||||
panic(3) // @describe builtin-ref-panic "panic"
|
||||
}
|
||||
|
||||
func deadcode() {
|
||||
var a int // @describe var-decl-stmt "var a int"
|
||||
// Pointer analysis can't run on dead code.
|
||||
var b = &a // @describe b "b"
|
||||
_ = b
|
||||
var a2 int // @describe var-decl-stmt "var a2 int"
|
||||
_ = a2
|
||||
var _ int // @describe var-decl-stmt2 "var _ int"
|
||||
var _ int // @describe var-def-blank "_"
|
||||
}
|
||||
|
||||
type I interface { // @describe def-iface-I "I"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
-------- @describe pkgdecl --------
|
||||
definition of package "describe"
|
||||
type C int
|
||||
method (*describe.C) f()
|
||||
type D struct{}
|
||||
method (describe.D) f()
|
||||
type I interface{f()}
|
||||
method (describe.I) f()
|
||||
const c untyped integer = 0
|
||||
type cake float64
|
||||
func deadcode func()
|
||||
var global *string
|
||||
func main func()
|
||||
const pi untyped float = 3141/1000
|
||||
const pie cake = 1768225803696341/562949953421312
|
||||
type C int
|
||||
method (*C) f()
|
||||
type D struct{}
|
||||
method (D) f()
|
||||
type I interface{f()}
|
||||
method (I) f()
|
||||
const c untyped integer = 0
|
||||
type cake float64
|
||||
var global *string
|
||||
func main func()
|
||||
const pi untyped float = 3141/1000
|
||||
const pie cake = 1768225803696341/562949953421312
|
||||
|
||||
-------- @describe type-ref-builtin --------
|
||||
reference to built-in type float64
|
||||
|
|
@ -76,58 +75,37 @@ defined here
|
|||
-------- @describe ref-anon --------
|
||||
reference to var anon func()
|
||||
defined here
|
||||
value may point to these labels:
|
||||
func@31.10
|
||||
|
||||
-------- @describe ref-global --------
|
||||
reference to var global *string
|
||||
defined here
|
||||
value may point to these labels:
|
||||
new
|
||||
|
||||
-------- @describe var-def-x-1 --------
|
||||
definition of var x *int
|
||||
value may point to these labels:
|
||||
a
|
||||
|
||||
-------- @describe var-ref-x-1 --------
|
||||
reference to var x *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
a
|
||||
|
||||
-------- @describe var-def-x-2 --------
|
||||
reference to var x *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
b
|
||||
|
||||
-------- @describe var-ref-x-2 --------
|
||||
reference to var x *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
b
|
||||
|
||||
-------- @describe var-ref-i-C --------
|
||||
reference to var i I
|
||||
defined here
|
||||
this I may contain these dynamic types:
|
||||
*C, may point to:
|
||||
new
|
||||
|
||||
-------- @describe var-ref-i-D --------
|
||||
reference to var i I
|
||||
defined here
|
||||
this I may contain these dynamic types:
|
||||
D
|
||||
|
||||
-------- @describe var-ref-i --------
|
||||
reference to var i I
|
||||
defined here
|
||||
this I may contain these dynamic types:
|
||||
*C, may point to:
|
||||
new
|
||||
D
|
||||
|
||||
-------- @describe const-local-pi --------
|
||||
definition of const localpi untyped float
|
||||
|
|
@ -160,14 +138,10 @@ index expression of type (*int, bool)
|
|||
-------- @describe mapval --------
|
||||
reference to var mapval *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
a
|
||||
|
||||
-------- @describe m --------
|
||||
reference to var m map[string]*int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
makemap
|
||||
|
||||
-------- @describe defer-stmt --------
|
||||
defer statement
|
||||
|
|
@ -179,11 +153,13 @@ go statement
|
|||
function call (or conversion) of type ()
|
||||
|
||||
-------- @describe var-decl-stmt --------
|
||||
definition of var a int
|
||||
definition of var a2 int
|
||||
|
||||
-------- @describe b --------
|
||||
definition of var b *int
|
||||
no points-to information: PTA did not encounter this expression (dead code?)
|
||||
-------- @describe var-decl-stmt2 --------
|
||||
definition of var _ int
|
||||
|
||||
-------- @describe var-def-blank --------
|
||||
definition of var _ int
|
||||
|
||||
-------- @describe def-iface-I --------
|
||||
definition of type I (size 16, align 8)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
-------- @freevars fv1 --------
|
||||
Free identifiers:
|
||||
type C main.C
|
||||
type C
|
||||
const exp int
|
||||
var x int
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
var t lib.Type // @describe ref-type "Type"
|
||||
p := t.Method(&a) // @describe ref-method "Method"
|
||||
|
||||
print(*p + 1) // @describe p "p "
|
||||
print(*p + 1) // @pointsto p "p "
|
||||
|
||||
var _ lib.Type // @describe ref-pkg "lib"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import of package "lib"
|
|||
const Const untyped integer = 3
|
||||
func Func func()
|
||||
type Type int
|
||||
method (lib.Type) Method(x *int) *int
|
||||
method (Type) Method(x *int) *int
|
||||
var Var int
|
||||
|
||||
-------- @describe ref-const --------
|
||||
|
|
@ -28,10 +28,8 @@ Method set:
|
|||
reference to method func (lib.Type).Method(x *int) *int
|
||||
defined here
|
||||
|
||||
-------- @describe p --------
|
||||
reference to var p *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
-------- @pointsto p --------
|
||||
this *int may point to these objects:
|
||||
imports.a
|
||||
|
||||
-------- @describe ref-pkg --------
|
||||
|
|
@ -39,6 +37,6 @@ reference to package "lib"
|
|||
const Const untyped integer = 3
|
||||
func Func func()
|
||||
type Type int
|
||||
method (lib.Type) Method(x *int) *int
|
||||
method (Type) Method(x *int) *int
|
||||
var Var int
|
||||
|
||||
|
|
|
|||
|
|
@ -20,15 +20,15 @@ func main() {
|
|||
b := 3
|
||||
chB <- &b
|
||||
|
||||
<-chA // @describe describe-chA "chA"
|
||||
<-chA2 // @describe describe-chA2 "chA2"
|
||||
<-chB // @describe describe-chB "chB"
|
||||
<-chA // @pointsto pointsto-chA "chA"
|
||||
<-chA2 // @pointsto pointsto-chA2 "chA2"
|
||||
<-chB // @pointsto pointsto-chB "chB"
|
||||
|
||||
select {
|
||||
case rA := <-chA: // @peers peer-recv-chA "<-"
|
||||
_ = rA // @describe describe-rA "rA"
|
||||
_ = rA // @pointsto pointsto-rA "rA"
|
||||
case rB := <-chB: // @peers peer-recv-chB "<-"
|
||||
_ = rB // @describe describe-rB "rB"
|
||||
_ = rB // @pointsto pointsto-rB "rB"
|
||||
|
||||
case <-chA: // @peers peer-recv-chA' "<-"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,14 @@
|
|||
-------- @describe describe-chA --------
|
||||
reference to var chA chan *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
-------- @pointsto pointsto-chA --------
|
||||
this chan *int may point to these objects:
|
||||
makechan
|
||||
makechan
|
||||
|
||||
-------- @describe describe-chA2 --------
|
||||
reference to var chA2 chan *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
-------- @pointsto pointsto-chA2 --------
|
||||
this chan *int may point to these objects:
|
||||
makechan
|
||||
|
||||
-------- @describe describe-chB --------
|
||||
reference to var chB chan *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
-------- @pointsto pointsto-chB --------
|
||||
this chan *int may point to these objects:
|
||||
makechan
|
||||
|
||||
-------- @peers peer-recv-chA --------
|
||||
|
|
@ -29,10 +23,8 @@ This channel of type chan *int may be:
|
|||
received from, here
|
||||
received from, here
|
||||
|
||||
-------- @describe describe-rA --------
|
||||
reference to var rA *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
-------- @pointsto pointsto-rA --------
|
||||
this *int may point to these objects:
|
||||
peers.a2
|
||||
a1
|
||||
|
||||
|
|
@ -43,10 +35,8 @@ This channel of type chan *int may be:
|
|||
received from, here
|
||||
received from, here
|
||||
|
||||
-------- @describe describe-rB --------
|
||||
reference to var rB *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
-------- @pointsto pointsto-rB --------
|
||||
this *int may point to these objects:
|
||||
b
|
||||
|
||||
-------- @peers peer-recv-chA' --------
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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() {}
|
||||
|
|
@ -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?)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package reflection
|
||||
|
||||
// This is a test of 'describe', but we split it into a separate file
|
||||
// so that describe.go doesn't have to import "reflect" each time.
|
||||
// This is a test of 'pointsto', but we split it into a separate file
|
||||
// so that pointsto.go doesn't have to import "reflect" each time.
|
||||
|
||||
import "reflect"
|
||||
|
||||
|
|
@ -20,11 +20,11 @@ func main() {
|
|||
mrv = reflect.ValueOf(&a)
|
||||
}
|
||||
|
||||
_ = mrv // @describe mrv "mrv"
|
||||
p1 := mrv.Interface() // @describe p1 "p1"
|
||||
p2 := mrv.MapKeys() // @describe p2 "p2"
|
||||
p3 := p2[0] // @describe p3 "p3"
|
||||
p4 := reflect.TypeOf(p1) // @describe p4 "p4"
|
||||
_ = mrv // @pointsto mrv "mrv"
|
||||
p1 := mrv.Interface() // @pointsto p1 "p1"
|
||||
p2 := mrv.MapKeys() // @pointsto p2 "p2"
|
||||
p3 := p2[0] // @pointsto p3 "p3"
|
||||
p4 := reflect.TypeOf(p1) // @pointsto p4 "p4"
|
||||
|
||||
_, _, _, _ = p1, p2, p3, p4
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
-------- @describe mrv --------
|
||||
reference to var mrv reflect.Value
|
||||
defined here
|
||||
-------- @pointsto mrv --------
|
||||
this reflect.Value may contain these dynamic types:
|
||||
*bool, may point to:
|
||||
reflection.b
|
||||
|
|
@ -9,8 +7,7 @@ this reflect.Value may contain these dynamic types:
|
|||
map[*int]*bool, may point to:
|
||||
makemap
|
||||
|
||||
-------- @describe p1 --------
|
||||
definition of var p1 interface{}
|
||||
-------- @pointsto p1 --------
|
||||
this interface{} may contain these dynamic types:
|
||||
*bool, may point to:
|
||||
reflection.b
|
||||
|
|
@ -19,19 +16,16 @@ this interface{} may contain these dynamic types:
|
|||
map[*int]*bool, may point to:
|
||||
makemap
|
||||
|
||||
-------- @describe p2 --------
|
||||
definition of var p2 []reflect.Value
|
||||
value may point to these labels:
|
||||
-------- @pointsto p2 --------
|
||||
this []reflect.Value may point to these objects:
|
||||
<alloc in (reflect.Value).MapKeys>
|
||||
|
||||
-------- @describe p3 --------
|
||||
definition of var p3 reflect.Value
|
||||
-------- @pointsto p3 --------
|
||||
this reflect.Value may contain these dynamic types:
|
||||
*int, may point to:
|
||||
reflection.a
|
||||
|
||||
-------- @describe p4 --------
|
||||
definition of var p4 reflect.Type
|
||||
-------- @pointsto p4 --------
|
||||
this reflect.Type may contain these dynamic types:
|
||||
*reflect.rtype, may point to:
|
||||
*bool
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -101,6 +101,8 @@ func (l Label) Pos() token.Pos {
|
|||
// append.y[*].z (array allocated by append)
|
||||
// makeslice.y[*].z (array allocated via make)
|
||||
//
|
||||
// TODO(adonovan): expose func LabelString(*types.Package, Label).
|
||||
//
|
||||
func (l Label) String() string {
|
||||
var s string
|
||||
switch v := l.obj.data.(type) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue