|  | // 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 main | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "go/ast" | 
|  | "go/build" | 
|  | "go/parser" | 
|  | "go/token" | 
|  | "go/types" | 
|  | "io" | 
|  | "log" | 
|  | "os" | 
|  | "sort" | 
|  | "strconv" | 
|  | "strings" | 
|  | "sync" | 
|  |  | 
|  | "golang.org/x/tools/cmd/guru/serial" | 
|  | "golang.org/x/tools/go/buildutil" | 
|  | "golang.org/x/tools/go/loader" | 
|  | "golang.org/x/tools/imports" | 
|  | "golang.org/x/tools/refactor/importgraph" | 
|  | ) | 
|  |  | 
|  | // The referrers function reports all identifiers that resolve to the same object | 
|  | // as the queried identifier, within any package in the workspace. | 
|  | func referrers(q *Query) error { | 
|  | fset := token.NewFileSet() | 
|  | lconf := loader.Config{Fset: fset, Build: q.Build} | 
|  | allowErrors(&lconf) | 
|  |  | 
|  | if _, err := importQueryPackage(q.Pos, &lconf); err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | // Load tests of the query package | 
|  | // even if the query location is not in the tests. | 
|  | for path := range lconf.ImportPkgs { | 
|  | lconf.ImportPkgs[path] = true | 
|  | } | 
|  |  | 
|  | // Load/parse/type-check the query package. | 
|  | lprog, err := lconf.Load() | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | qpos, err := parseQueryPos(lprog, q.Pos, false) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | id, _ := qpos.path[0].(*ast.Ident) | 
|  | if id == nil { | 
|  | return fmt.Errorf("no identifier here") | 
|  | } | 
|  |  | 
|  | obj := qpos.info.ObjectOf(id) | 
|  | if obj == nil { | 
|  | // Happens for y in "switch y := x.(type)", | 
|  | // the package declaration, | 
|  | // and unresolved identifiers. | 
|  | if _, ok := qpos.path[1].(*ast.File); ok { // package decl? | 
|  | return packageReferrers(q, qpos.info.Pkg.Path()) | 
|  | } | 
|  | return fmt.Errorf("no object for identifier: %T", qpos.path[1]) | 
|  | } | 
|  |  | 
|  | // Imported package name? | 
|  | if pkgname, ok := obj.(*types.PkgName); ok { | 
|  | return packageReferrers(q, pkgname.Imported().Path()) | 
|  | } | 
|  |  | 
|  | if obj.Pkg() == nil { | 
|  | return fmt.Errorf("references to predeclared %q are everywhere!", obj.Name()) | 
|  | } | 
|  |  | 
|  | q.Output(fset, &referrersInitialResult{ | 
|  | qinfo: qpos.info, | 
|  | obj:   obj, | 
|  | }) | 
|  |  | 
|  | // For a globally accessible object defined in package P, we | 
|  | // must load packages that depend on P.  Specifically, for a | 
|  | // package-level object, we need load only direct importers | 
|  | // of P, but for a field or method, we must load | 
|  | // any package that transitively imports P. | 
|  |  | 
|  | if global, pkglevel := classify(obj); global { | 
|  | if pkglevel { | 
|  | return globalReferrersPkgLevel(q, obj, fset) | 
|  | } | 
|  | // We'll use the the object's position to identify it in the larger program. | 
|  | objposn := fset.Position(obj.Pos()) | 
|  | defpkg := obj.Pkg().Path() // defining package | 
|  | return globalReferrers(q, qpos.info.Pkg.Path(), defpkg, objposn) | 
|  | } | 
|  |  | 
|  | outputUses(q, fset, usesOf(obj, qpos.info), obj.Pkg()) | 
|  |  | 
|  | return nil // success | 
|  | } | 
|  |  | 
|  | // classify classifies objects by how far | 
|  | // we have to look to find references to them. | 
|  | func classify(obj types.Object) (global, pkglevel bool) { | 
|  | if obj.Exported() { | 
|  | if obj.Parent() == nil { | 
|  | // selectable object (field or method) | 
|  | return true, false | 
|  | } | 
|  | if obj.Parent() == obj.Pkg().Scope() { | 
|  | // lexical object (package-level var/const/func/type) | 
|  | return true, true | 
|  | } | 
|  | } | 
|  | // object with unexported named or defined in local scope | 
|  | return false, false | 
|  | } | 
|  |  | 
|  | // packageReferrers reports all references to the specified package | 
|  | // throughout the workspace. | 
|  | func packageReferrers(q *Query, path string) error { | 
|  | // Scan the workspace and build the import graph. | 
|  | // Ignore broken packages. | 
|  | _, rev, _ := importgraph.Build(q.Build) | 
|  |  | 
|  | // Find the set of packages that directly import the query package. | 
|  | // Only those packages need typechecking of function bodies. | 
|  | users := rev[path] | 
|  |  | 
|  | // Load the larger program. | 
|  | fset := token.NewFileSet() | 
|  | lconf := loader.Config{ | 
|  | Fset:  fset, | 
|  | Build: q.Build, | 
|  | TypeCheckFuncBodies: func(p string) bool { | 
|  | return users[strings.TrimSuffix(p, "_test")] | 
|  | }, | 
|  | } | 
|  | allowErrors(&lconf) | 
|  |  | 
|  | // The importgraph doesn't treat external test packages | 
|  | // as separate nodes, so we must use ImportWithTests. | 
|  | for path := range users { | 
|  | lconf.ImportWithTests(path) | 
|  | } | 
|  |  | 
|  | // Subtle!  AfterTypeCheck needs no mutex for qpkg because the | 
|  | // topological import order gives us the necessary happens-before edges. | 
|  | // TODO(adonovan): what about import cycles? | 
|  | var qpkg *types.Package | 
|  |  | 
|  | // For efficiency, we scan each package for references | 
|  | // just after it has been type-checked.  The loader calls | 
|  | // AfterTypeCheck (concurrently), providing us with a stream of | 
|  | // packages. | 
|  | lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { | 
|  | // AfterTypeCheck may be called twice for the same package due to augmentation. | 
|  |  | 
|  | if info.Pkg.Path() == path && qpkg == nil { | 
|  | // Found the package of interest. | 
|  | qpkg = info.Pkg | 
|  | fakepkgname := types.NewPkgName(token.NoPos, qpkg, qpkg.Name(), qpkg) | 
|  | q.Output(fset, &referrersInitialResult{ | 
|  | qinfo: info, | 
|  | obj:   fakepkgname, // bogus | 
|  | }) | 
|  | } | 
|  |  | 
|  | // Only inspect packages that directly import the | 
|  | // declaring package (and thus were type-checked). | 
|  | if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { | 
|  | // Find PkgNames that refer to qpkg. | 
|  | // TODO(adonovan): perhaps more useful would be to show imports | 
|  | // of the package instead of qualified identifiers. | 
|  | var refs []*ast.Ident | 
|  | for id, obj := range info.Uses { | 
|  | if obj, ok := obj.(*types.PkgName); ok && obj.Imported() == qpkg { | 
|  | refs = append(refs, id) | 
|  | } | 
|  | } | 
|  | outputUses(q, fset, refs, info.Pkg) | 
|  | } | 
|  |  | 
|  | clearInfoFields(info) // save memory | 
|  | } | 
|  |  | 
|  | lconf.Load() // ignore error | 
|  |  | 
|  | if qpkg == nil { | 
|  | log.Fatalf("query package %q not found during reloading", path) | 
|  | } | 
|  |  | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func usesOf(queryObj types.Object, info *loader.PackageInfo) []*ast.Ident { | 
|  | var refs []*ast.Ident | 
|  | for id, obj := range info.Uses { | 
|  | if sameObj(queryObj, obj) { | 
|  | refs = append(refs, id) | 
|  | } | 
|  | } | 
|  | return refs | 
|  | } | 
|  |  | 
|  | // outputUses outputs a result describing refs, which appear in the package denoted by info. | 
|  | func outputUses(q *Query, fset *token.FileSet, refs []*ast.Ident, pkg *types.Package) { | 
|  | if len(refs) > 0 { | 
|  | sort.Sort(byNamePos{fset, refs}) | 
|  | q.Output(fset, &referrersPackageResult{ | 
|  | pkg:   pkg, | 
|  | build: q.Build, | 
|  | fset:  fset, | 
|  | refs:  refs, | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | // globalReferrers reports references throughout the entire workspace to the | 
|  | // object (a field or method) at the specified source position. | 
|  | // Its defining package is defpkg, and the query package is qpkg. | 
|  | func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position) error { | 
|  | // Scan the workspace and build the import graph. | 
|  | // Ignore broken packages. | 
|  | _, rev, _ := importgraph.Build(q.Build) | 
|  |  | 
|  | // Find the set of packages that depend on defpkg. | 
|  | // Only function bodies in those packages need type-checking. | 
|  | users := rev.Search(defpkg) // transitive importers | 
|  |  | 
|  | // Prepare to load the larger program. | 
|  | fset := token.NewFileSet() | 
|  | lconf := loader.Config{ | 
|  | Fset:  fset, | 
|  | Build: q.Build, | 
|  | TypeCheckFuncBodies: func(p string) bool { | 
|  | return users[strings.TrimSuffix(p, "_test")] | 
|  | }, | 
|  | } | 
|  | allowErrors(&lconf) | 
|  |  | 
|  | // The importgraph doesn't treat external test packages | 
|  | // as separate nodes, so we must use ImportWithTests. | 
|  | for path := range users { | 
|  | lconf.ImportWithTests(path) | 
|  | } | 
|  |  | 
|  | // The remainder of this function is somewhat tricky because it | 
|  | // operates on the concurrent stream of packages observed by the | 
|  | // loader's AfterTypeCheck hook.  Most of guru's helper | 
|  | // functions assume the entire program has already been loaded, | 
|  | // so we can't use them here. | 
|  | // TODO(adonovan): smooth things out once the other changes have landed. | 
|  |  | 
|  | // Results are reported concurrently from within the | 
|  | // AfterTypeCheck hook.  The program may provide a useful stream | 
|  | // of information even if the user doesn't let the program run | 
|  | // to completion. | 
|  |  | 
|  | var ( | 
|  | mu   sync.Mutex | 
|  | qobj types.Object | 
|  | ) | 
|  |  | 
|  | // For efficiency, we scan each package for references | 
|  | // just after it has been type-checked.  The loader calls | 
|  | // AfterTypeCheck (concurrently), providing us with a stream of | 
|  | // packages. | 
|  | lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { | 
|  | // AfterTypeCheck may be called twice for the same package due to augmentation. | 
|  |  | 
|  | // Only inspect packages that depend on the declaring package | 
|  | // (and thus were type-checked). | 
|  | if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { | 
|  | // Record the query object and its package when we see it. | 
|  | mu.Lock() | 
|  | if qobj == nil && info.Pkg.Path() == defpkg { | 
|  | // Find the object by its position (slightly ugly). | 
|  | qobj = findObject(fset, &info.Info, objposn) | 
|  | if qobj == nil { | 
|  | // It really ought to be there; | 
|  | // we found it once already. | 
|  | log.Fatalf("object at %s not found in package %s", | 
|  | objposn, defpkg) | 
|  | } | 
|  | } | 
|  | obj := qobj | 
|  | mu.Unlock() | 
|  |  | 
|  | // Look for references to the query object. | 
|  | if obj != nil { | 
|  | outputUses(q, fset, usesOf(obj, info), info.Pkg) | 
|  | } | 
|  | } | 
|  |  | 
|  | clearInfoFields(info) // save memory | 
|  | } | 
|  |  | 
|  | lconf.Load() // ignore error | 
|  |  | 
|  | if qobj == nil { | 
|  | log.Fatal("query object not found during reloading") | 
|  | } | 
|  |  | 
|  | return nil // success | 
|  | } | 
|  |  | 
|  | // globalReferrersPkgLevel reports references throughout the entire workspace to the package-level object obj. | 
|  | // It assumes that the query object itself has already been reported. | 
|  | func globalReferrersPkgLevel(q *Query, obj types.Object, fset *token.FileSet) error { | 
|  | // globalReferrersPkgLevel uses go/ast and friends instead of go/types. | 
|  | // This affords a considerable performance benefit. | 
|  | // It comes at the cost of some code complexity. | 
|  | // | 
|  | // Here's a high level summary. | 
|  | // | 
|  | // The goal is to find references to the query object p.Q. | 
|  | // There are several possible scenarios, each handled differently. | 
|  | // | 
|  | // 1. We are looking in a package other than p, and p is not dot-imported. | 
|  | //    This is the simplest case. Q must be referred to as n.Q, | 
|  | //    where n is the name under which p is imported. | 
|  | //    We look at all imports of p to gather all names under which it is imported. | 
|  | //    (In the typical case, it is imported only once, under its default name.) | 
|  | //    Then we look at all selector expressions and report any matches. | 
|  | // | 
|  | // 2. We are looking in a package other than p, and p is dot-imported. | 
|  | //    In this case, Q will be referred to just as Q. | 
|  | //    Furthermore, go/ast's object resolution will not be able to resolve | 
|  | //    Q to any other object, unlike any local (file- or function- or block-scoped) object. | 
|  | //    So we look at all matching identifiers and report all unresolvable ones. | 
|  | // | 
|  | // 3. We are looking in package p. | 
|  | //    (Care must be taken to separate p and p_test (an xtest package), | 
|  | //    and make sure that they are treated as separate packages.) | 
|  | //    In this case, we give go/ast the entire package for object resolution, | 
|  | //    instead of going file by file. | 
|  | //    We then iterate over all identifiers that resolve to the query object. | 
|  | //    (The query object itself has already been reported, so we don't re-report it.) | 
|  | // | 
|  | // We always skip all files that don't contain the string Q, as they cannot be | 
|  | // relevant to finding references to Q. | 
|  | // | 
|  | // We parse all files leniently. In the presence of parsing errors, results are best-effort. | 
|  |  | 
|  | // Scan the workspace and build the import graph. | 
|  | // Ignore broken packages. | 
|  | _, rev, _ := importgraph.Build(q.Build) | 
|  |  | 
|  | // Find the set of packages that directly import defpkg. | 
|  | defpkg := obj.Pkg().Path() | 
|  | defpkg = strings.TrimSuffix(defpkg, "_test") // package x_test actually has package name x | 
|  | defpkg = imports.VendorlessPath(defpkg)      // remove vendor goop | 
|  |  | 
|  | users := rev[defpkg] | 
|  | if len(users) == 0 { | 
|  | users = make(map[string]bool) | 
|  | } | 
|  | // We also need to check defpkg itself, and its xtests. | 
|  | // For the reverse graph packages, we process xtests with the main package. | 
|  | // defpkg gets special handling; we must distinguish between in-package vs out-of-package. | 
|  | // To make the control flow below simpler, add defpkg and defpkg xtest placeholders. | 
|  | // Use "!test" instead of "_test" because "!" is not a valid character in an import path. | 
|  | // (More precisely, it is not guaranteed to be a valid character in an import path, | 
|  | // so it is unlikely that it will be in use. See https://golang.org/ref/spec#Import_declarations.) | 
|  | users[defpkg] = true | 
|  | users[defpkg+"!test"] = true | 
|  |  | 
|  | cwd, err := os.Getwd() | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | defname := obj.Pkg().Name()                    // name of defining package, used for imports using import path only | 
|  | isxtest := strings.HasSuffix(defname, "_test") // indicates whether the query object is defined in an xtest package | 
|  |  | 
|  | name := obj.Name() | 
|  | namebytes := []byte(name)          // byte slice version of query object name, for early filtering | 
|  | objpos := fset.Position(obj.Pos()) // position of query object, used to prevent re-emitting original decl | 
|  |  | 
|  | sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency | 
|  | var wg sync.WaitGroup | 
|  |  | 
|  | for u := range users { | 
|  | u := u | 
|  | wg.Add(1) | 
|  | go func() { | 
|  | defer wg.Done() | 
|  |  | 
|  | uIsXTest := strings.HasSuffix(u, "!test") // indicates whether this package is the special defpkg xtest package | 
|  | u = strings.TrimSuffix(u, "!test") | 
|  |  | 
|  | // Resolve package. | 
|  | sema <- struct{}{} // acquire token | 
|  | pkg, err := q.Build.Import(u, cwd, build.IgnoreVendor) | 
|  | <-sema // release token | 
|  | if err != nil { | 
|  | return | 
|  | } | 
|  |  | 
|  | // If we're not in the query package, | 
|  | // the object is in another package regardless, | 
|  | // so we want to process all files. | 
|  | // If we are in the query package, | 
|  | // we want to only process the files that are | 
|  | // part of that query package; | 
|  | // that set depends on whether the query package itself is an xtest. | 
|  | inQueryPkg := u == defpkg && isxtest == uIsXTest | 
|  | var files []string | 
|  | if !inQueryPkg || !isxtest { | 
|  | files = append(files, pkg.GoFiles...) | 
|  | files = append(files, pkg.TestGoFiles...) | 
|  | files = append(files, pkg.CgoFiles...) // use raw cgo files, as we're only parsing | 
|  | } | 
|  | if !inQueryPkg || isxtest { | 
|  | files = append(files, pkg.XTestGoFiles...) | 
|  | } | 
|  |  | 
|  | if len(files) == 0 { | 
|  | return | 
|  | } | 
|  |  | 
|  | var deffiles map[string]*ast.File | 
|  | if inQueryPkg { | 
|  | deffiles = make(map[string]*ast.File) | 
|  | } | 
|  |  | 
|  | buf := new(bytes.Buffer) // reusable buffer for reading files | 
|  |  | 
|  | for _, file := range files { | 
|  | if !buildutil.IsAbsPath(q.Build, file) { | 
|  | file = buildutil.JoinPath(q.Build, pkg.Dir, file) | 
|  | } | 
|  | buf.Reset() | 
|  | sema <- struct{}{} // acquire token | 
|  | src, err := readFile(q.Build, file, buf) | 
|  | <-sema // release token | 
|  | if err != nil { | 
|  | continue | 
|  | } | 
|  |  | 
|  | // Fast path: If the object's name isn't present anywhere in the source, ignore the file. | 
|  | if !bytes.Contains(src, namebytes) { | 
|  | continue | 
|  | } | 
|  |  | 
|  | if inQueryPkg { | 
|  | // If we're in the query package, we defer final processing until we have | 
|  | // parsed all of the candidate files in the package. | 
|  | // Best effort; allow errors and use what we can from what remains. | 
|  | f, _ := parser.ParseFile(fset, file, src, parser.AllErrors) | 
|  | if f != nil { | 
|  | deffiles[file] = f | 
|  | } | 
|  | continue | 
|  | } | 
|  |  | 
|  | // We aren't in the query package. Go file by file. | 
|  |  | 
|  | // Parse out only the imports, to check whether the defining package | 
|  | // was imported, and if so, under what names. | 
|  | // Best effort; allow errors and use what we can from what remains. | 
|  | f, _ := parser.ParseFile(fset, file, src, parser.ImportsOnly|parser.AllErrors) | 
|  | if f == nil { | 
|  | continue | 
|  | } | 
|  |  | 
|  | // pkgnames is the set of names by which defpkg is imported in this file. | 
|  | // (Multiple imports in the same file are legal but vanishingly rare.) | 
|  | pkgnames := make([]string, 0, 1) | 
|  | var isdotimport bool | 
|  | for _, imp := range f.Imports { | 
|  | path, err := strconv.Unquote(imp.Path.Value) | 
|  | if err != nil || path != defpkg { | 
|  | continue | 
|  | } | 
|  | switch { | 
|  | case imp.Name == nil: | 
|  | pkgnames = append(pkgnames, defname) | 
|  | case imp.Name.Name == ".": | 
|  | isdotimport = true | 
|  | default: | 
|  | pkgnames = append(pkgnames, imp.Name.Name) | 
|  | } | 
|  | } | 
|  | if len(pkgnames) == 0 && !isdotimport { | 
|  | // Defining package not imported, bail. | 
|  | continue | 
|  | } | 
|  |  | 
|  | // Re-parse the entire file. | 
|  | // Parse errors are ok; we'll do the best we can with a partial AST, if we have one. | 
|  | f, _ = parser.ParseFile(fset, file, src, parser.AllErrors) | 
|  | if f == nil { | 
|  | continue | 
|  | } | 
|  |  | 
|  | // Walk the AST looking for references. | 
|  | var refs []*ast.Ident | 
|  | ast.Inspect(f, func(n ast.Node) bool { | 
|  | // Check selector expressions. | 
|  | // If the selector matches the target name, | 
|  | // and the expression is one of the names | 
|  | // that the defining package was imported under, | 
|  | // then we have a match. | 
|  | if sel, ok := n.(*ast.SelectorExpr); ok && sel.Sel.Name == name { | 
|  | if id, ok := sel.X.(*ast.Ident); ok { | 
|  | for _, n := range pkgnames { | 
|  | if n == id.Name { | 
|  | refs = append(refs, sel.Sel) | 
|  | // Don't recurse further, to avoid duplicate entries | 
|  | // from the dot import check below. | 
|  | return false | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | // Dot imports are special. | 
|  | // Objects imported from the defining package are placed in the package scope. | 
|  | // go/ast does not resolve them to an object. | 
|  | // At all other scopes (file, local), go/ast can do the resolution. | 
|  | // So we're looking for object-free idents with the right name. | 
|  | // The only other way to get something with the right name at the package scope | 
|  | // is to *be* the defining package. We handle that case separately (inQueryPkg). | 
|  | if isdotimport { | 
|  | if id, ok := n.(*ast.Ident); ok && id.Obj == nil && id.Name == name { | 
|  | refs = append(refs, id) | 
|  | return false | 
|  | } | 
|  | } | 
|  | return true | 
|  | }) | 
|  |  | 
|  | // Emit any references we found. | 
|  | if len(refs) > 0 { | 
|  | q.Output(fset, &referrersPackageResult{ | 
|  | pkg:   types.NewPackage(pkg.ImportPath, pkg.Name), | 
|  | build: q.Build, | 
|  | fset:  fset, | 
|  | refs:  refs, | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | // If we're in the query package, we've now collected all the files in the package. | 
|  | // (Or at least the ones that might contain references to the object.) | 
|  | // Find and emit refs. | 
|  | if inQueryPkg { | 
|  | // Bundle the files together into a package. | 
|  | // This does package-level object resolution. | 
|  | qpkg, _ := ast.NewPackage(fset, deffiles, nil, nil) | 
|  | // Look up the query object; we know that it is defined in the package scope. | 
|  | pkgobj := qpkg.Scope.Objects[name] | 
|  | if pkgobj == nil { | 
|  | panic("missing defpkg object for " + defpkg + "." + name) | 
|  | } | 
|  | // Find all references to the query object. | 
|  | var refs []*ast.Ident | 
|  | ast.Inspect(qpkg, func(n ast.Node) bool { | 
|  | if id, ok := n.(*ast.Ident); ok { | 
|  | // Check both that this is a reference to the query object | 
|  | // and that it is not the query object itself; | 
|  | // the query object itself was already emitted. | 
|  | if id.Obj == pkgobj && objpos != fset.Position(id.Pos()) { | 
|  | refs = append(refs, id) | 
|  | return false | 
|  | } | 
|  | } | 
|  | return true | 
|  | }) | 
|  | if len(refs) > 0 { | 
|  | q.Output(fset, &referrersPackageResult{ | 
|  | pkg:   types.NewPackage(pkg.ImportPath, pkg.Name), | 
|  | build: q.Build, | 
|  | fset:  fset, | 
|  | refs:  refs, | 
|  | }) | 
|  | } | 
|  | deffiles = nil // allow GC | 
|  | } | 
|  | }() | 
|  | } | 
|  |  | 
|  | wg.Wait() | 
|  |  | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // findObject returns the object defined at the specified position. | 
|  | func findObject(fset *token.FileSet, info *types.Info, objposn token.Position) types.Object { | 
|  | good := func(obj types.Object) bool { | 
|  | if obj == nil { | 
|  | return false | 
|  | } | 
|  | posn := fset.Position(obj.Pos()) | 
|  | return posn.Filename == objposn.Filename && posn.Offset == objposn.Offset | 
|  | } | 
|  | for _, obj := range info.Defs { | 
|  | if good(obj) { | 
|  | return obj | 
|  | } | 
|  | } | 
|  | for _, obj := range info.Implicits { | 
|  | if good(obj) { | 
|  | return obj | 
|  | } | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // same reports whether x and y are identical, or both are PkgNames | 
|  | // that import the same Package. | 
|  | // | 
|  | func sameObj(x, y types.Object) bool { | 
|  | if x == y { | 
|  | return true | 
|  | } | 
|  | if x, ok := x.(*types.PkgName); ok { | 
|  | if y, ok := y.(*types.PkgName); ok { | 
|  | return x.Imported() == y.Imported() | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | func clearInfoFields(info *loader.PackageInfo) { | 
|  | // TODO(adonovan): opt: save memory by eliminating unneeded scopes/objects. | 
|  | // (Requires go/types change for Go 1.7.) | 
|  | //   info.Pkg.Scope().ClearChildren() | 
|  |  | 
|  | // Discard the file ASTs and their accumulated type | 
|  | // information to save memory. | 
|  | info.Files = nil | 
|  | info.Defs = make(map[*ast.Ident]types.Object) | 
|  | info.Uses = make(map[*ast.Ident]types.Object) | 
|  | info.Implicits = make(map[ast.Node]types.Object) | 
|  |  | 
|  | // Also, disable future collection of wholly unneeded | 
|  | // type information for the package in case there is | 
|  | // more type-checking to do (augmentation). | 
|  | info.Types = nil | 
|  | info.Scopes = nil | 
|  | info.Selections = nil | 
|  | } | 
|  |  | 
|  | // -------- utils -------- | 
|  |  | 
|  | // An deterministic ordering for token.Pos that doesn't | 
|  | // depend on the order in which packages were loaded. | 
|  | func lessPos(fset *token.FileSet, x, y token.Pos) bool { | 
|  | fx := fset.File(x) | 
|  | fy := fset.File(y) | 
|  | if fx != fy { | 
|  | return fx.Name() < fy.Name() | 
|  | } | 
|  | return x < y | 
|  | } | 
|  |  | 
|  | type byNamePos struct { | 
|  | fset *token.FileSet | 
|  | ids  []*ast.Ident | 
|  | } | 
|  |  | 
|  | func (p byNamePos) Len() int      { return len(p.ids) } | 
|  | func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] } | 
|  | func (p byNamePos) Less(i, j int) bool { | 
|  | return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos) | 
|  | } | 
|  |  | 
|  | // referrersInitialResult is the initial result of a "referrers" query. | 
|  | type referrersInitialResult struct { | 
|  | qinfo *loader.PackageInfo | 
|  | obj   types.Object // object it denotes | 
|  | } | 
|  |  | 
|  | func (r *referrersInitialResult) PrintPlain(printf printfFunc) { | 
|  | printf(r.obj, "references to %s", | 
|  | types.ObjectString(r.obj, types.RelativeTo(r.qinfo.Pkg))) | 
|  | } | 
|  |  | 
|  | func (r *referrersInitialResult) JSON(fset *token.FileSet) []byte { | 
|  | var objpos string | 
|  | if pos := r.obj.Pos(); pos.IsValid() { | 
|  | objpos = fset.Position(pos).String() | 
|  | } | 
|  | return toJSON(&serial.ReferrersInitial{ | 
|  | Desc:   r.obj.String(), | 
|  | ObjPos: objpos, | 
|  | }) | 
|  | } | 
|  |  | 
|  | // referrersPackageResult is the streaming result for one package of a "referrers" query. | 
|  | type referrersPackageResult struct { | 
|  | pkg   *types.Package | 
|  | build *build.Context | 
|  | fset  *token.FileSet | 
|  | refs  []*ast.Ident // set of all other references to it | 
|  | } | 
|  |  | 
|  | // forEachRef calls f(id, text) for id in r.refs, in order. | 
|  | // Text is the text of the line on which id appears. | 
|  | func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string)) { | 
|  | // Show referring lines, like grep. | 
|  | type fileinfo struct { | 
|  | refs     []*ast.Ident | 
|  | linenums []int            // line number of refs[i] | 
|  | data     chan interface{} // file contents or error | 
|  | } | 
|  | var fileinfos []*fileinfo | 
|  | fileinfosByName := make(map[string]*fileinfo) | 
|  |  | 
|  | // First pass: start the file reads concurrently. | 
|  | sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency | 
|  | for _, ref := range r.refs { | 
|  | posn := r.fset.Position(ref.Pos()) | 
|  | fi := fileinfosByName[posn.Filename] | 
|  | if fi == nil { | 
|  | fi = &fileinfo{data: make(chan interface{})} | 
|  | fileinfosByName[posn.Filename] = fi | 
|  | fileinfos = append(fileinfos, fi) | 
|  |  | 
|  | // First request for this file: | 
|  | // start asynchronous read. | 
|  | go func() { | 
|  | sema <- struct{}{} // acquire token | 
|  | content, err := readFile(r.build, posn.Filename, nil) | 
|  | <-sema // release token | 
|  | if err != nil { | 
|  | fi.data <- err | 
|  | } else { | 
|  | fi.data <- content | 
|  | } | 
|  | }() | 
|  | } | 
|  | fi.refs = append(fi.refs, ref) | 
|  | fi.linenums = append(fi.linenums, posn.Line) | 
|  | } | 
|  |  | 
|  | // Second pass: print refs in original order. | 
|  | // One line may have several refs at different columns. | 
|  | for _, fi := range fileinfos { | 
|  | v := <-fi.data // wait for I/O completion | 
|  |  | 
|  | // Print one item for all refs in a file that could not | 
|  | // be loaded (perhaps due to //line directives). | 
|  | if err, ok := v.(error); ok { | 
|  | var suffix string | 
|  | if more := len(fi.refs) - 1; more > 0 { | 
|  | suffix = fmt.Sprintf(" (+ %d more refs in this file)", more) | 
|  | } | 
|  | f(fi.refs[0], err.Error()+suffix) | 
|  | continue | 
|  | } | 
|  |  | 
|  | lines := bytes.Split(v.([]byte), []byte("\n")) | 
|  | for i, ref := range fi.refs { | 
|  | f(ref, string(lines[fi.linenums[i]-1])) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // readFile is like ioutil.ReadFile, but | 
|  | // it goes through the virtualized build.Context. | 
|  | // If non-nil, buf must have been reset. | 
|  | func readFile(ctxt *build.Context, filename string, buf *bytes.Buffer) ([]byte, error) { | 
|  | rc, err := buildutil.OpenFile(ctxt, filename) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | defer rc.Close() | 
|  | if buf == nil { | 
|  | buf = new(bytes.Buffer) | 
|  | } | 
|  | if _, err := io.Copy(buf, rc); err != nil { | 
|  | return nil, err | 
|  | } | 
|  | return buf.Bytes(), nil | 
|  | } | 
|  |  | 
|  | func (r *referrersPackageResult) PrintPlain(printf printfFunc) { | 
|  | r.foreachRef(func(id *ast.Ident, text string) { | 
|  | printf(id, "%s", text) | 
|  | }) | 
|  | } | 
|  |  | 
|  | func (r *referrersPackageResult) JSON(fset *token.FileSet) []byte { | 
|  | refs := serial.ReferrersPackage{Package: r.pkg.Path()} | 
|  | r.foreachRef(func(id *ast.Ident, text string) { | 
|  | refs.Refs = append(refs.Refs, serial.Ref{ | 
|  | Pos:  fset.Position(id.NamePos).String(), | 
|  | Text: text, | 
|  | }) | 
|  | }) | 
|  | return toJSON(refs) | 
|  | } |