You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

522 lines
14 KiB

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