github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/x/tools/cmd/guru/referrers.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/build"
    12  	"go/token"
    13  	"go/types"
    14  	"io"
    15  	"log"
    16  	"sort"
    17  	"strings"
    18  	"sync"
    19  
    20  	"golang.org/x/tools/cmd/guru/serial"
    21  	"golang.org/x/tools/go/buildutil"
    22  	"golang.org/x/tools/go/loader"
    23  	"golang.org/x/tools/refactor/importgraph"
    24  )
    25  
    26  // Referrers reports all identifiers that resolve to the same object
    27  // as the queried identifier, within any package in the workspace.
    28  func referrers(q *Query) error {
    29  	fset := token.NewFileSet()
    30  	lconf := loader.Config{Fset: fset, Build: q.Build}
    31  	allowErrors(&lconf)
    32  
    33  	if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
    34  		return err
    35  	}
    36  
    37  	// Load/parse/type-check the query package.
    38  	lprog, err := lconf.Load()
    39  	if err != nil {
    40  		return err
    41  	}
    42  
    43  	qpos, err := parseQueryPos(lprog, q.Pos, false)
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	id, _ := qpos.path[0].(*ast.Ident)
    49  	if id == nil {
    50  		return fmt.Errorf("no identifier here")
    51  	}
    52  
    53  	obj := qpos.info.ObjectOf(id)
    54  	if obj == nil {
    55  		// Happens for y in "switch y := x.(type)",
    56  		// the package declaration,
    57  		// and unresolved identifiers.
    58  		if _, ok := qpos.path[1].(*ast.File); ok { // package decl?
    59  			return packageReferrers(q, qpos.info.Pkg.Path())
    60  		}
    61  		return fmt.Errorf("no object for identifier: %T", qpos.path[1])
    62  	}
    63  
    64  	// Imported package name?
    65  	if pkgname, ok := obj.(*types.PkgName); ok {
    66  		return packageReferrers(q, pkgname.Imported().Path())
    67  	}
    68  
    69  	if obj.Pkg() == nil {
    70  		return fmt.Errorf("references to predeclared %q are everywhere!", obj.Name())
    71  	}
    72  
    73  	// For a globally accessible object defined in package P, we
    74  	// must load packages that depend on P.  Specifically, for a
    75  	// package-level object, we need load only direct importers
    76  	// of P, but for a field or interface method, we must load
    77  	// any package that transitively imports P.
    78  	if global, pkglevel := classify(obj); global {
    79  		// We'll use the the object's position to identify it in the larger program.
    80  		objposn := fset.Position(obj.Pos())
    81  		defpkg := obj.Pkg().Path() // defining package
    82  		return globalReferrers(q, qpos.info.Pkg.Path(), defpkg, objposn, pkglevel)
    83  	}
    84  
    85  	q.Output(fset, &referrersInitialResult{
    86  		qinfo: qpos.info,
    87  		obj:   obj,
    88  	})
    89  
    90  	outputUses(q, fset, usesOf(obj, qpos.info), obj.Pkg())
    91  
    92  	return nil // success
    93  }
    94  
    95  // classify classifies objects by how far
    96  // we have to look to find references to them.
    97  func classify(obj types.Object) (global, pkglevel bool) {
    98  	if obj.Exported() {
    99  		if obj.Parent() == nil {
   100  			// selectable object (field or method)
   101  			return true, false
   102  		}
   103  		if obj.Parent() == obj.Pkg().Scope() {
   104  			// lexical object (package-level var/const/func/type)
   105  			return true, true
   106  		}
   107  	}
   108  	// object with unexported named or defined in local scope
   109  	return false, false
   110  }
   111  
   112  // packageReferrers reports all references to the specified package
   113  // throughout the workspace.
   114  func packageReferrers(q *Query, path string) error {
   115  	// Scan the workspace and build the import graph.
   116  	// Ignore broken packages.
   117  	_, rev, _ := importgraph.Build(q.Build)
   118  
   119  	// Find the set of packages that directly import the query package.
   120  	// Only those packages need typechecking of function bodies.
   121  	users := rev[path]
   122  
   123  	// Load the larger program.
   124  	fset := token.NewFileSet()
   125  	lconf := loader.Config{
   126  		Fset:  fset,
   127  		Build: q.Build,
   128  		TypeCheckFuncBodies: func(p string) bool {
   129  			return users[strings.TrimSuffix(p, "_test")]
   130  		},
   131  	}
   132  	allowErrors(&lconf)
   133  
   134  	// The importgraph doesn't treat external test packages
   135  	// as separate nodes, so we must use ImportWithTests.
   136  	for path := range users {
   137  		lconf.ImportWithTests(path)
   138  	}
   139  
   140  	// Subtle!  AfterTypeCheck needs no mutex for qpkg because the
   141  	// topological import order gives us the necessary happens-before edges.
   142  	// TODO(adonovan): what about import cycles?
   143  	var qpkg *types.Package
   144  
   145  	// For efficiency, we scan each package for references
   146  	// just after it has been type-checked.  The loader calls
   147  	// AfterTypeCheck (concurrently), providing us with a stream of
   148  	// packages.
   149  	lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) {
   150  		// AfterTypeCheck may be called twice for the same package due to augmentation.
   151  
   152  		if info.Pkg.Path() == path && qpkg == nil {
   153  			// Found the package of interest.
   154  			qpkg = info.Pkg
   155  			fakepkgname := types.NewPkgName(token.NoPos, qpkg, qpkg.Name(), qpkg)
   156  			q.Output(fset, &referrersInitialResult{
   157  				qinfo: info,
   158  				obj:   fakepkgname, // bogus
   159  			})
   160  		}
   161  
   162  		// Only inspect packages that directly import the
   163  		// declaring package (and thus were type-checked).
   164  		if lconf.TypeCheckFuncBodies(info.Pkg.Path()) {
   165  			// Find PkgNames that refer to qpkg.
   166  			// TODO(adonovan): perhaps more useful would be to show imports
   167  			// of the package instead of qualified identifiers.
   168  			var refs []*ast.Ident
   169  			for id, obj := range info.Uses {
   170  				if obj, ok := obj.(*types.PkgName); ok && obj.Imported() == qpkg {
   171  					refs = append(refs, id)
   172  				}
   173  			}
   174  			outputUses(q, fset, refs, info.Pkg)
   175  		}
   176  
   177  		clearInfoFields(info) // save memory
   178  	}
   179  
   180  	lconf.Load() // ignore error
   181  
   182  	if qpkg == nil {
   183  		log.Fatalf("query package %q not found during reloading", path)
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func usesOf(queryObj types.Object, info *loader.PackageInfo) []*ast.Ident {
   190  	var refs []*ast.Ident
   191  	for id, obj := range info.Uses {
   192  		if sameObj(queryObj, obj) {
   193  			refs = append(refs, id)
   194  		}
   195  	}
   196  	return refs
   197  }
   198  
   199  // outputUses outputs a result describing refs, which appear in the package denoted by info.
   200  func outputUses(q *Query, fset *token.FileSet, refs []*ast.Ident, pkg *types.Package) {
   201  	if len(refs) > 0 {
   202  		sort.Sort(byNamePos{fset, refs})
   203  		q.Output(fset, &referrersPackageResult{
   204  			pkg:   pkg,
   205  			build: q.Build,
   206  			fset:  fset,
   207  			refs:  refs,
   208  		})
   209  	}
   210  }
   211  
   212  // globalReferrers reports references throughout the entire workspace to the
   213  // object at the specified source position.  Its defining package is defpkg,
   214  // and the query package is qpkg.  isPkgLevel indicates whether the object
   215  // is defined at package-level.
   216  func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position, isPkgLevel bool) error {
   217  	// Scan the workspace and build the import graph.
   218  	// Ignore broken packages.
   219  	_, rev, _ := importgraph.Build(q.Build)
   220  
   221  	// Find the set of packages that depend on defpkg.
   222  	// Only function bodies in those packages need type-checking.
   223  	var users map[string]bool
   224  	if isPkgLevel {
   225  		users = rev[defpkg] // direct importers
   226  		if users == nil {
   227  			users = make(map[string]bool)
   228  		}
   229  		users[defpkg] = true // plus the defining package itself
   230  	} else {
   231  		users = rev.Search(defpkg) // transitive importers
   232  	}
   233  
   234  	// Prepare to load the larger program.
   235  	fset := token.NewFileSet()
   236  	lconf := loader.Config{
   237  		Fset:  fset,
   238  		Build: q.Build,
   239  		TypeCheckFuncBodies: func(p string) bool {
   240  			return users[strings.TrimSuffix(p, "_test")]
   241  		},
   242  	}
   243  	allowErrors(&lconf)
   244  
   245  	// The importgraph doesn't treat external test packages
   246  	// as separate nodes, so we must use ImportWithTests.
   247  	for path := range users {
   248  		lconf.ImportWithTests(path)
   249  	}
   250  
   251  	// The remainder of this function is somewhat tricky because it
   252  	// operates on the concurrent stream of packages observed by the
   253  	// loader's AfterTypeCheck hook.  Most of guru's helper
   254  	// functions assume the entire program has already been loaded,
   255  	// so we can't use them here.
   256  	// TODO(adonovan): smooth things out once the other changes have landed.
   257  
   258  	// Results are reported concurrently from within the
   259  	// AfterTypeCheck hook.  The program may provide a useful stream
   260  	// of information even if the user doesn't let the program run
   261  	// to completion.
   262  
   263  	var (
   264  		mu    sync.Mutex
   265  		qobj  types.Object
   266  		qinfo *loader.PackageInfo // info for qpkg
   267  	)
   268  
   269  	// For efficiency, we scan each package for references
   270  	// just after it has been type-checked.  The loader calls
   271  	// AfterTypeCheck (concurrently), providing us with a stream of
   272  	// packages.
   273  	lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) {
   274  		// AfterTypeCheck may be called twice for the same package due to augmentation.
   275  
   276  		// Only inspect packages that depend on the declaring package
   277  		// (and thus were type-checked).
   278  		if lconf.TypeCheckFuncBodies(info.Pkg.Path()) {
   279  			// Record the query object and its package when we see it.
   280  			mu.Lock()
   281  			if qobj == nil && info.Pkg.Path() == defpkg {
   282  				// Find the object by its position (slightly ugly).
   283  				qobj = findObject(fset, &info.Info, objposn)
   284  				if qobj == nil {
   285  					// It really ought to be there;
   286  					// we found it once already.
   287  					log.Fatalf("object at %s not found in package %s",
   288  						objposn, defpkg)
   289  				}
   290  
   291  				// Object found.
   292  				qinfo = info
   293  				q.Output(fset, &referrersInitialResult{
   294  					qinfo: qinfo,
   295  					obj:   qobj,
   296  				})
   297  			}
   298  			obj := qobj
   299  			mu.Unlock()
   300  
   301  			// Look for references to the query object.
   302  			if obj != nil {
   303  				outputUses(q, fset, usesOf(obj, info), info.Pkg)
   304  			}
   305  		}
   306  
   307  		clearInfoFields(info) // save memory
   308  	}
   309  
   310  	lconf.Load() // ignore error
   311  
   312  	if qobj == nil {
   313  		log.Fatal("query object not found during reloading")
   314  	}
   315  
   316  	return nil // success
   317  }
   318  
   319  // findObject returns the object defined at the specified position.
   320  func findObject(fset *token.FileSet, info *types.Info, objposn token.Position) types.Object {
   321  	good := func(obj types.Object) bool {
   322  		if obj == nil {
   323  			return false
   324  		}
   325  		posn := fset.Position(obj.Pos())
   326  		return posn.Filename == objposn.Filename && posn.Offset == objposn.Offset
   327  	}
   328  	for _, obj := range info.Defs {
   329  		if good(obj) {
   330  			return obj
   331  		}
   332  	}
   333  	for _, obj := range info.Implicits {
   334  		if good(obj) {
   335  			return obj
   336  		}
   337  	}
   338  	return nil
   339  }
   340  
   341  // same reports whether x and y are identical, or both are PkgNames
   342  // that import the same Package.
   343  //
   344  func sameObj(x, y types.Object) bool {
   345  	if x == y {
   346  		return true
   347  	}
   348  	if x, ok := x.(*types.PkgName); ok {
   349  		if y, ok := y.(*types.PkgName); ok {
   350  			return x.Imported() == y.Imported()
   351  		}
   352  	}
   353  	return false
   354  }
   355  
   356  func clearInfoFields(info *loader.PackageInfo) {
   357  	// TODO(adonovan): opt: save memory by eliminating unneeded scopes/objects.
   358  	// (Requires go/types change for Go 1.7.)
   359  	//   info.Pkg.Scope().ClearChildren()
   360  
   361  	// Discard the file ASTs and their accumulated type
   362  	// information to save memory.
   363  	info.Files = nil
   364  	info.Defs = make(map[*ast.Ident]types.Object)
   365  	info.Uses = make(map[*ast.Ident]types.Object)
   366  	info.Implicits = make(map[ast.Node]types.Object)
   367  
   368  	// Also, disable future collection of wholly unneeded
   369  	// type information for the package in case there is
   370  	// more type-checking to do (augmentation).
   371  	info.Types = nil
   372  	info.Scopes = nil
   373  	info.Selections = nil
   374  }
   375  
   376  // -------- utils --------
   377  
   378  // An deterministic ordering for token.Pos that doesn't
   379  // depend on the order in which packages were loaded.
   380  func lessPos(fset *token.FileSet, x, y token.Pos) bool {
   381  	fx := fset.File(x)
   382  	fy := fset.File(y)
   383  	if fx != fy {
   384  		return fx.Name() < fy.Name()
   385  	}
   386  	return x < y
   387  }
   388  
   389  type byNamePos struct {
   390  	fset *token.FileSet
   391  	ids  []*ast.Ident
   392  }
   393  
   394  func (p byNamePos) Len() int      { return len(p.ids) }
   395  func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] }
   396  func (p byNamePos) Less(i, j int) bool {
   397  	return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos)
   398  }
   399  
   400  // referrersInitialResult is the initial result of a "referrers" query.
   401  type referrersInitialResult struct {
   402  	qinfo *loader.PackageInfo
   403  	obj   types.Object // object it denotes
   404  }
   405  
   406  func (r *referrersInitialResult) PrintPlain(printf printfFunc) {
   407  	printf(r.obj, "references to %s",
   408  		types.ObjectString(r.obj, types.RelativeTo(r.qinfo.Pkg)))
   409  }
   410  
   411  func (r *referrersInitialResult) JSON(fset *token.FileSet) []byte {
   412  	var objpos string
   413  	if pos := r.obj.Pos(); pos.IsValid() {
   414  		objpos = fset.Position(pos).String()
   415  	}
   416  	return toJSON(&serial.ReferrersInitial{
   417  		Desc:   r.obj.String(),
   418  		ObjPos: objpos,
   419  	})
   420  }
   421  
   422  // referrersPackageResult is the streaming result for one package of a "referrers" query.
   423  type referrersPackageResult struct {
   424  	pkg   *types.Package
   425  	build *build.Context
   426  	fset  *token.FileSet
   427  	refs  []*ast.Ident // set of all other references to it
   428  }
   429  
   430  // forEachRef calls f(id, text) for id in r.refs, in order.
   431  // Text is the text of the line on which id appears.
   432  func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string)) {
   433  	// Show referring lines, like grep.
   434  	type fileinfo struct {
   435  		refs     []*ast.Ident
   436  		linenums []int            // line number of refs[i]
   437  		data     chan interface{} // file contents or error
   438  	}
   439  	var fileinfos []*fileinfo
   440  	fileinfosByName := make(map[string]*fileinfo)
   441  
   442  	// First pass: start the file reads concurrently.
   443  	sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency
   444  	for _, ref := range r.refs {
   445  		posn := r.fset.Position(ref.Pos())
   446  		fi := fileinfosByName[posn.Filename]
   447  		if fi == nil {
   448  			fi = &fileinfo{data: make(chan interface{})}
   449  			fileinfosByName[posn.Filename] = fi
   450  			fileinfos = append(fileinfos, fi)
   451  
   452  			// First request for this file:
   453  			// start asynchronous read.
   454  			go func() {
   455  				sema <- struct{}{} // acquire token
   456  				content, err := readFile(r.build, posn.Filename)
   457  				<-sema // release token
   458  				if err != nil {
   459  					fi.data <- err
   460  				} else {
   461  					fi.data <- content
   462  				}
   463  			}()
   464  		}
   465  		fi.refs = append(fi.refs, ref)
   466  		fi.linenums = append(fi.linenums, posn.Line)
   467  	}
   468  
   469  	// Second pass: print refs in original order.
   470  	// One line may have several refs at different columns.
   471  	for _, fi := range fileinfos {
   472  		v := <-fi.data // wait for I/O completion
   473  
   474  		// Print one item for all refs in a file that could not
   475  		// be loaded (perhaps due to //line directives).
   476  		if err, ok := v.(error); ok {
   477  			var suffix string
   478  			if more := len(fi.refs) - 1; more > 0 {
   479  				suffix = fmt.Sprintf(" (+ %d more refs in this file)", more)
   480  			}
   481  			f(fi.refs[0], err.Error()+suffix)
   482  			continue
   483  		}
   484  
   485  		lines := bytes.Split(v.([]byte), []byte("\n"))
   486  		for i, ref := range fi.refs {
   487  			f(ref, string(lines[fi.linenums[i]-1]))
   488  		}
   489  	}
   490  }
   491  
   492  // readFile is like ioutil.ReadFile, but
   493  // it goes through the virtualized build.Context.
   494  func readFile(ctxt *build.Context, filename string) ([]byte, error) {
   495  	rc, err := buildutil.OpenFile(ctxt, filename)
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  	defer rc.Close()
   500  	var buf bytes.Buffer
   501  	if _, err := io.Copy(&buf, rc); err != nil {
   502  		return nil, err
   503  	}
   504  	return buf.Bytes(), nil
   505  }
   506  
   507  func (r *referrersPackageResult) PrintPlain(printf printfFunc) {
   508  	r.foreachRef(func(id *ast.Ident, text string) {
   509  		printf(id, "%s", text)
   510  	})
   511  }
   512  
   513  func (r *referrersPackageResult) JSON(fset *token.FileSet) []byte {
   514  	refs := serial.ReferrersPackage{Package: r.pkg.Path()}
   515  	r.foreachRef(func(id *ast.Ident, text string) {
   516  		refs.Refs = append(refs.Refs, serial.Ref{
   517  			Pos:  fset.Position(id.NamePos).String(),
   518  			Text: text,
   519  		})
   520  	})
   521  	return toJSON(refs)
   522  }