From 0725e5a5b3140bfacb19554b9d1ddcfe975b2790 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Tue, 10 Sep 2013 14:11:42 -0400 Subject: [PATCH] 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 --- cmd/oracle/oracle.el | 6 + importer/importer.go | 5 +- oracle/callees.go | 4 +- oracle/callers.go | 2 +- oracle/callgraph.go | 2 +- oracle/callstack.go | 4 +- oracle/implements.go | 12 +- oracle/json/json.go | 9 ++ oracle/oracle.go | 47 +++++--- oracle/oracle_test.go | 1 + oracle/referrers.go | 103 ++++++++++++++++++ oracle/testdata/src/main/referrers-json.go | 24 ++++ .../testdata/src/main/referrers-json.golden | 51 +++++++++ 13 files changed, 237 insertions(+), 33 deletions(-) create mode 100644 oracle/referrers.go create mode 100644 oracle/testdata/src/main/referrers-json.go create mode 100644 oracle/testdata/src/main/referrers-json.golden diff --git a/cmd/oracle/oracle.el b/cmd/oracle/oracle.el index 4b348c50..92d625ba 100644 --- a/cmd/oracle/oracle.el +++ b/cmd/oracle/oracle.el @@ -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. diff --git a/importer/importer.go b/importer/importer.go index 8e60b759..80f8250b 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -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) } diff --git a/oracle/callees.go b/oracle/callees.go index 1528cb81..afd8d83f 100644 --- a/oracle/callees.go +++ b/oracle/callees.go @@ -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 diff --git a/oracle/callers.go b/oracle/callers.go index bfe14145..37549978 100644 --- a/oracle/callers.go +++ b/oracle/callers.go @@ -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) diff --git a/oracle/callgraph.go b/oracle/callgraph.go index 43f49c8e..6ebe1912 100644 --- a/oracle/callgraph.go +++ b/oracle/callgraph.go @@ -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]) } diff --git a/oracle/callstack.go b/oracle/callstack.go index 53c27b60..16516b6d 100644 --- a/oracle/callstack.go +++ b/oracle/callstack.go @@ -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, } diff --git a/oracle/implements.go b/oracle/implements.go index dc3a356c..b31617a2 100644 --- a/oracle/implements.go +++ b/oracle/implements.go @@ -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) diff --git a/oracle/json/json.go b/oracle/json/json.go index bf0efbbe..da6888d7 100644 --- a/oracle/json/json.go +++ b/oracle/json/json.go @@ -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 } diff --git a/oracle/oracle.go b/oracle/oracle.go index 2496548f..5b70ee84 100644 --- a/oracle/oracle.go +++ b/oracle/oracle.go @@ -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 diff --git a/oracle/oracle_test.go b/oracle/oracle_test.go index 5ca38a40..07c5d9cc 100644 --- a/oracle/oracle_test.go +++ b/oracle/oracle_test.go @@ -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) diff --git a/oracle/referrers.go b/oracle/referrers.go new file mode 100644 index 00000000..98e23c8b --- /dev/null +++ b/oracle/referrers.go @@ -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 +} diff --git a/oracle/testdata/src/main/referrers-json.go b/oracle/testdata/src/main/referrers-json.go new file mode 100644 index 00000000..4799e53c --- /dev/null +++ b/oracle/testdata/src/main/referrers-json.go @@ -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 +} diff --git a/oracle/testdata/src/main/referrers-json.golden b/oracle/testdata/src/main/referrers-json.golden new file mode 100644 index 00000000..c85882b6 --- /dev/null +++ b/oracle/testdata/src/main/referrers-json.golden @@ -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" + ] + } +} \ No newline at end of file