1
0
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:
Alan Donovan 2013-09-10 14:11:42 -04:00
parent 9a9fb35468
commit 0725e5a5b3
13 changed files with 237 additions and 33 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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