mirror of
https://github.com/golang/go
synced 2024-11-18 13:14:47 -07:00
go.tools/oracle: new query 'referrers' returns all references to an identifier.
+ test. Also: - provide non-nil map to Importer.doImport0() to avoid a crash. - reorganize oracle "needs" bits. - reduce "needs" of 'freevars' and 'implements' queries by avoiding ssa.Packages when types.Package suffices. R=crawshaw CC=golang-dev https://golang.org/cl/13421046
This commit is contained in:
parent
9a9fb35468
commit
0725e5a5b3
@ -175,6 +175,12 @@ this channel receive/send operation."
|
||||
(interactive)
|
||||
(go-oracle--run "peers"))
|
||||
|
||||
(defun go-oracle-referrers ()
|
||||
"Enumerate all references to the object denoted by the selected
|
||||
identifier."
|
||||
(interactive)
|
||||
(go-oracle--run "referrers"))
|
||||
|
||||
;; TODO(adonovan): don't mutate the keymap; just document how users
|
||||
;; can do this themselves. But that means freezing the API, so don't
|
||||
;; do that yet; wait till v1.0.
|
||||
|
@ -40,8 +40,6 @@
|
||||
// Importer will only augment (and create an external test package
|
||||
// for) the first import path specified on the command-line.
|
||||
//
|
||||
// TODO(adonovan): more tests.
|
||||
//
|
||||
package importer
|
||||
|
||||
import (
|
||||
@ -428,12 +426,13 @@ func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []strin
|
||||
|
||||
// Pass 2: type-check each set of files to make a package.
|
||||
var infos []*PackageInfo
|
||||
imports := make(map[string]*types.Package) // keep importBinary happy
|
||||
for _, pkg := range pkgs {
|
||||
var info *PackageInfo
|
||||
if pkg.importable {
|
||||
// import package
|
||||
var err error
|
||||
info, err = imp.doImport0(nil, pkg.path)
|
||||
info, err = imp.doImport0(imports, pkg.path)
|
||||
if err != nil {
|
||||
return nil, nil, err // e.g. parse error (but not type error)
|
||||
}
|
||||
|
@ -113,13 +113,13 @@ func (r *calleesResult) display(printf printfFunc) {
|
||||
|
||||
func (r *calleesResult) toJSON(res *json.Result, fset *token.FileSet) {
|
||||
j := &json.Callees{
|
||||
Pos: r.site.Caller().Func().Prog.Fset.Position(r.site.Pos()).String(),
|
||||
Pos: fset.Position(r.site.Pos()).String(),
|
||||
Desc: r.site.Description(),
|
||||
}
|
||||
for _, callee := range r.funcs {
|
||||
j.Callees = append(j.Callees, &json.CalleesItem{
|
||||
Name: callee.String(),
|
||||
Pos: callee.Prog.Fset.Position(callee.Pos()).String(),
|
||||
Pos: fset.Position(callee.Pos()).String(),
|
||||
})
|
||||
}
|
||||
res.Callees = j
|
||||
|
@ -81,7 +81,7 @@ func (r *callersResult) toJSON(res *json.Result, fset *token.FileSet) {
|
||||
if site.Caller() == r.root {
|
||||
c.Desc = "synthetic call"
|
||||
} else {
|
||||
c.Pos = site.Caller().Func().Prog.Fset.Position(site.Pos()).String()
|
||||
c.Pos = fset.Position(site.Pos()).String()
|
||||
c.Desc = site.Description()
|
||||
}
|
||||
callers = append(callers, c)
|
||||
|
@ -93,7 +93,7 @@ func (r *callgraphResult) toJSON(res *json.Result, fset *token.FileSet) {
|
||||
j := &cg[i]
|
||||
fn := n.Func()
|
||||
j.Name = fn.String()
|
||||
j.Pos = fn.Prog.Fset.Position(fn.Pos()).String()
|
||||
j.Pos = fset.Position(fn.Pos()).String()
|
||||
for callee := range r.callgraph[n] {
|
||||
j.Children = append(j.Children, r.numbering[callee])
|
||||
}
|
||||
|
@ -100,13 +100,13 @@ func (r *callstackResult) toJSON(res *json.Result, fset *token.FileSet) {
|
||||
var callers []json.Caller
|
||||
for _, site := range r.callstack {
|
||||
callers = append(callers, json.Caller{
|
||||
Pos: site.Caller().Func().Prog.Fset.Position(site.Pos()).String(),
|
||||
Pos: fset.Position(site.Pos()).String(),
|
||||
Caller: site.Caller().Func().String(),
|
||||
Desc: site.Description(),
|
||||
})
|
||||
}
|
||||
res.Callstack = &json.CallStack{
|
||||
Pos: r.target.Prog.Fset.Position(r.target.Pos()).String(),
|
||||
Pos: fset.Position(r.target.Pos()).String(),
|
||||
Target: r.target.String(),
|
||||
Callers: callers,
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
"code.google.com/p/go.tools/oracle/json"
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
)
|
||||
|
||||
// Implements displays the 'implements" relation among all
|
||||
@ -32,15 +31,14 @@ import (
|
||||
// answer due to ChangeInterface, i.e. subtyping among interfaces.)
|
||||
//
|
||||
func implements(o *oracle) (queryResult, error) {
|
||||
pkg := o.prog.Package(o.queryPkgInfo.Pkg)
|
||||
if pkg == nil {
|
||||
return nil, o.errorf(o.queryPath[0], "no SSA package")
|
||||
}
|
||||
pkg := o.queryPkgInfo.Pkg
|
||||
|
||||
// Compute set of named interface/concrete types at package level.
|
||||
var interfaces, concretes []*types.Named
|
||||
for _, mem := range pkg.Members {
|
||||
if t, ok := mem.(*ssa.Type); ok {
|
||||
scope := pkg.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
mem := scope.Lookup(name)
|
||||
if t, ok := mem.(*types.TypeName); ok {
|
||||
nt := t.Type().(*types.Named)
|
||||
if _, ok := nt.Underlying().(*types.Interface); ok {
|
||||
interfaces = append(interfaces, nt)
|
||||
|
@ -23,6 +23,14 @@ type Peers struct {
|
||||
Receives []string `json:"receives,omitempty"` // locations of aliased <-ch ops
|
||||
}
|
||||
|
||||
// A Referrers is the result of a 'referrers' query.
|
||||
type Referrers struct {
|
||||
Pos string `json:"pos"` // location of the query reference
|
||||
ObjPos string `json:"objpos,omitempty"` // location of the definition
|
||||
Desc string `json:"desc"` // description of the denoted object
|
||||
Refs []string `json:"refs,omitempty"` // locations of all references
|
||||
}
|
||||
|
||||
type CalleesItem struct {
|
||||
Name string `json:"name"` // full name of called function
|
||||
Pos string `json:"pos"` // location of called function
|
||||
@ -205,6 +213,7 @@ type Result struct {
|
||||
Freevars []*FreeVar `json:"freevars,omitempty"`
|
||||
Implements []*Implements `json:"implements,omitempty"`
|
||||
Peers *Peers `json:"peers,omitempty"`
|
||||
Referrers *Referrers `json:"referrers,omitempty"`
|
||||
|
||||
Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ import (
|
||||
|
||||
type oracle struct {
|
||||
out io.Writer // standard output
|
||||
prog *ssa.Program // the SSA program [need&SSA]
|
||||
prog *ssa.Program // the SSA program [only populated if need&SSA]
|
||||
config pointer.Config // pointer analysis configuration
|
||||
|
||||
// need&(Pos|ExactPos):
|
||||
@ -43,18 +43,21 @@ type oracle struct {
|
||||
queryPkgInfo *importer.PackageInfo // type info for the queried package
|
||||
queryPath []ast.Node // AST path from query node to root of ast.File
|
||||
|
||||
// need&AllTypeInfo
|
||||
typeInfo map[*types.Package]*importer.PackageInfo // type info for all ASTs in the program
|
||||
|
||||
timers map[string]time.Duration // phase timing information
|
||||
}
|
||||
|
||||
// A set of bits indicating the analytical requirements of each mode.
|
||||
// Typed ASTs for the queried package are always available.
|
||||
const (
|
||||
Pos = 1 << iota // needs a position
|
||||
ExactPos // needs an exact AST selection; implies Pos
|
||||
SSA // needs SSA intermediate form
|
||||
WholeSource // needs ASTs/SSA (not just types) for whole program
|
||||
|
||||
// TODO(adonovan): implement more efficiently than WholeSource|SSA.
|
||||
TypedAST = WholeSource | SSA // needs typed AST for the queried package; implies Pos
|
||||
Pos = 1 << iota // needs a position
|
||||
ExactPos // needs an exact AST selection; implies Pos
|
||||
AllASTs // needs ASTs (not just object types) for whole program
|
||||
AllTypeInfo // needs to retain type info for all ASTs in the program
|
||||
SSA // needs ssa.Packages for whole program
|
||||
PTA = AllASTs | SSA // needs pointer analysis
|
||||
)
|
||||
|
||||
type modeInfo struct {
|
||||
@ -63,14 +66,15 @@ type modeInfo struct {
|
||||
}
|
||||
|
||||
var modes = map[string]modeInfo{
|
||||
"callees": modeInfo{WholeSource | SSA | ExactPos, callees},
|
||||
"callers": modeInfo{WholeSource | SSA | Pos, callers},
|
||||
"callgraph": modeInfo{WholeSource | SSA, callgraph},
|
||||
"callstack": modeInfo{WholeSource | SSA | Pos, callstack},
|
||||
"describe": modeInfo{WholeSource | SSA | ExactPos, describe},
|
||||
"freevars": modeInfo{TypedAST | Pos, freevars},
|
||||
"implements": modeInfo{TypedAST | Pos, implements},
|
||||
"peers": modeInfo{WholeSource | SSA | Pos, peers},
|
||||
"callees": modeInfo{PTA | ExactPos, callees},
|
||||
"callers": modeInfo{PTA | Pos, callers},
|
||||
"callgraph": modeInfo{PTA, callgraph},
|
||||
"callstack": modeInfo{PTA | Pos, callstack},
|
||||
"describe": modeInfo{PTA | ExactPos, describe},
|
||||
"freevars": modeInfo{AllASTs | Pos, freevars},
|
||||
"implements": modeInfo{Pos, implements},
|
||||
"peers": modeInfo{PTA | Pos, peers},
|
||||
"referrers": modeInfo{AllTypeInfo | AllASTs | Pos, referrers},
|
||||
}
|
||||
|
||||
type printfFunc func(pos interface{}, format string, args ...interface{})
|
||||
@ -128,7 +132,7 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
|
||||
return nil, fmt.Errorf("invalid mode type: %q", mode)
|
||||
}
|
||||
|
||||
if minfo.needs&WholeSource == 0 {
|
||||
if minfo.needs&AllASTs == 0 {
|
||||
buildContext = nil
|
||||
}
|
||||
imp := importer.New(&importer.Config{Build: buildContext})
|
||||
@ -164,6 +168,15 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
|
||||
}
|
||||
o.timers["load/parse/type"] = time.Since(start)
|
||||
|
||||
// Retain type info for all ASTs in the program.
|
||||
if minfo.needs&AllTypeInfo != 0 {
|
||||
m := make(map[*types.Package]*importer.PackageInfo)
|
||||
for _, p := range imp.AllPackages() {
|
||||
m[p.Pkg] = p
|
||||
}
|
||||
o.typeInfo = m
|
||||
}
|
||||
|
||||
// Parse the source query position.
|
||||
if minfo.needs&(Pos|ExactPos) != 0 {
|
||||
var err error
|
||||
|
@ -212,6 +212,7 @@ func TestOracle(t *testing.T) {
|
||||
"testdata/src/main/calls-json.go",
|
||||
"testdata/src/main/peers-json.go",
|
||||
"testdata/src/main/describe-json.go",
|
||||
"testdata/src/main/referrers-json.go",
|
||||
} {
|
||||
useJson := strings.HasSuffix(filename, "-json.go")
|
||||
queries := parseQueries(t, filename)
|
||||
|
103
oracle/referrers.go
Normal file
103
oracle/referrers.go
Normal file
@ -0,0 +1,103 @@
|
||||
// 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 (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
"code.google.com/p/go.tools/oracle/json"
|
||||
)
|
||||
|
||||
// Referrers reports all identifiers that resolve to the same object
|
||||
// as the queried identifier, within any package in the analysis scope.
|
||||
//
|
||||
func referrers(o *oracle) (queryResult, error) {
|
||||
id, _ := o.queryPath[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return nil, o.errorf(false, "no identifier here")
|
||||
}
|
||||
|
||||
obj := o.queryPkgInfo.ObjectOf(id)
|
||||
if obj == nil {
|
||||
// Happens for y in "switch y := x.(type)", but I think that's all.
|
||||
return nil, o.errorf(false, "no object for identifier")
|
||||
}
|
||||
|
||||
obj = primaryPkg(obj)
|
||||
|
||||
// Iterate over all go/types' resolver facts for the entire program.
|
||||
var refs []token.Pos
|
||||
for _, info := range o.typeInfo {
|
||||
for id2, obj2 := range info.Objects {
|
||||
obj2 = primaryPkg(obj2)
|
||||
if obj2 == obj {
|
||||
if id2.NamePos == obj.Pos() {
|
||||
continue // skip defining ident
|
||||
}
|
||||
refs = append(refs, id2.NamePos)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(byPos(refs))
|
||||
|
||||
return &referrersResult{
|
||||
query: id.NamePos,
|
||||
obj: obj,
|
||||
refs: refs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// primaryPkg returns obj unchanged unless it is a (secondary) package
|
||||
// object created by an ImportSpec, in which case the canonical
|
||||
// (primary) object is returned.
|
||||
//
|
||||
// TODO(adonovan): The need for this function argues against the
|
||||
// wisdom of the primary/secondary distinction. Discuss with gri.
|
||||
//
|
||||
func primaryPkg(obj types.Object) types.Object {
|
||||
if pkg, ok := obj.(*types.Package); ok {
|
||||
if prim := pkg.Primary(); prim != nil {
|
||||
return prim
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
type referrersResult struct {
|
||||
query token.Pos // identifer of query
|
||||
obj types.Object // object it denotes
|
||||
refs []token.Pos // set of all other references to it
|
||||
}
|
||||
|
||||
func (r *referrersResult) display(printf printfFunc) {
|
||||
if r.query != r.obj.Pos() {
|
||||
printf(r.query, "reference to %s", r.obj.Name())
|
||||
}
|
||||
// TODO(adonovan): pretty-print object using same logic as
|
||||
// (*describeValueResult).display.
|
||||
printf(r.obj, "defined here as %s", r.obj)
|
||||
for _, ref := range r.refs {
|
||||
if r.query != ref {
|
||||
printf(ref, "referenced here")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *referrersResult) toJSON(res *json.Result, fset *token.FileSet) {
|
||||
referrers := &json.Referrers{
|
||||
Pos: fset.Position(r.query).String(),
|
||||
Desc: r.obj.String(),
|
||||
}
|
||||
if pos := r.obj.Pos(); pos != token.NoPos { // primary 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())
|
||||
}
|
||||
res.Referrers = referrers
|
||||
}
|
24
oracle/testdata/src/main/referrers-json.go
vendored
Normal file
24
oracle/testdata/src/main/referrers-json.go
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package referrers
|
||||
|
||||
// Tests of 'referrers' query.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See referrers.golden for expected query results.
|
||||
|
||||
import "lib"
|
||||
|
||||
type s struct {
|
||||
f int
|
||||
}
|
||||
|
||||
func main() {
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
_ = v.Method
|
||||
v++ //@referrers ref-local "v"
|
||||
v++
|
||||
|
||||
_ = s{}.f // @referrers ref-field "f"
|
||||
|
||||
var s2 s
|
||||
s2.f = 1
|
||||
}
|
51
oracle/testdata/src/main/referrers-json.golden
vendored
Normal file
51
oracle/testdata/src/main/referrers-json.golden
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
-------- @referrers ref-package --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/main/referrers-json.go:14:8",
|
||||
"desc": "package lib",
|
||||
"refs": [
|
||||
"testdata/src/main/referrers-json.go:14:8",
|
||||
"testdata/src/main/referrers-json.go:14:19",
|
||||
"testdata/src/lib/lib.go:1:9"
|
||||
]
|
||||
}
|
||||
}-------- @referrers ref-method --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/main/referrers-json.go:15:8",
|
||||
"objpos": "testdata/src/lib/lib.go:5:13",
|
||||
"desc": "func (lib.Type).Method(x *int) *int",
|
||||
"refs": [
|
||||
"testdata/src/main/referrers-json.go:15:8",
|
||||
"testdata/src/main/referrers-json.go:16:8"
|
||||
]
|
||||
}
|
||||
}-------- @referrers ref-local --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/main/referrers-json.go:17:2",
|
||||
"objpos": "testdata/src/main/referrers-json.go:14:6",
|
||||
"desc": "var v lib.Type",
|
||||
"refs": [
|
||||
"testdata/src/main/referrers-json.go:15:6",
|
||||
"testdata/src/main/referrers-json.go:16:6",
|
||||
"testdata/src/main/referrers-json.go:17:2",
|
||||
"testdata/src/main/referrers-json.go:18:2"
|
||||
]
|
||||
}
|
||||
}-------- @referrers ref-field --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/main/referrers-json.go:20:10",
|
||||
"objpos": "testdata/src/main/referrers-json.go:10:2",
|
||||
"desc": "var f int",
|
||||
"refs": [
|
||||
"testdata/src/main/referrers-json.go:20:10",
|
||||
"testdata/src/main/referrers-json.go:23:5"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user