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 }