github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/godoc/analysis/analysis14.go (about) 1 // Copyright 2014 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 // +build !go1.5 6 7 // Package analysis performs type and pointer analysis 8 // and generates mark-up for the Go source view. 9 // 10 // The Run method populates a Result object by running type and 11 // (optionally) pointer analysis. The Result object is thread-safe 12 // and at all times may be accessed by a serving thread, even as it is 13 // progressively populated as analysis facts are derived. 14 // 15 // The Result is a mapping from each godoc file URL 16 // (e.g. /src/fmt/print.go) to information about that file. The 17 // information is a list of HTML markup links and a JSON array of 18 // structured data values. Some of the links call client-side 19 // JavaScript functions that index this array. 20 // 21 // The analysis computes mark-up for the following relations: 22 // 23 // IMPORTS: for each ast.ImportSpec, the package that it denotes. 24 // 25 // RESOLUTION: for each ast.Ident, its kind and type, and the location 26 // of its definition. 27 // 28 // METHOD SETS, IMPLEMENTS: for each ast.Ident defining a named type, 29 // its method-set, the set of interfaces it implements or is 30 // implemented by, and its size/align values. 31 // 32 // CALLERS, CALLEES: for each function declaration ('func' token), its 33 // callers, and for each call-site ('(' token), its callees. 34 // 35 // CALLGRAPH: the package docs include an interactive viewer for the 36 // intra-package call graph of "fmt". 37 // 38 // CHANNEL PEERS: for each channel operation make/<-/close, the set of 39 // other channel ops that alias the same channel(s). 40 // 41 // ERRORS: for each locus of a frontend (scanner/parser/type) error, the 42 // location is highlighted in red and hover text provides the compiler 43 // error message. 44 // 45 package analysis // import "golang.org/x/tools/godoc/analysis" 46 47 import ( 48 "fmt" 49 "go/build" 50 "go/scanner" 51 "go/token" 52 "html" 53 "io" 54 "log" 55 "os" 56 "path/filepath" 57 "runtime" 58 "sort" 59 "strings" 60 "sync" 61 62 "golang.org/x/tools/go/exact" 63 "golang.org/x/tools/go/loader" 64 "golang.org/x/tools/go/pointer" 65 "golang.org/x/tools/go/ssa" 66 "golang.org/x/tools/go/ssa/ssautil" 67 "golang.org/x/tools/go/types" 68 ) 69 70 // -- links ------------------------------------------------------------ 71 72 // A Link is an HTML decoration of the bytes [Start, End) of a file. 73 // Write is called before/after those bytes to emit the mark-up. 74 type Link interface { 75 Start() int 76 End() int 77 Write(w io.Writer, _ int, start bool) // the godoc.LinkWriter signature 78 } 79 80 // An <a> element. 81 type aLink struct { 82 start, end int // =godoc.Segment 83 title string // hover text 84 onclick string // JS code (NB: trusted) 85 href string // URL (NB: trusted) 86 } 87 88 func (a aLink) Start() int { return a.start } 89 func (a aLink) End() int { return a.end } 90 func (a aLink) Write(w io.Writer, _ int, start bool) { 91 if start { 92 fmt.Fprintf(w, `<a title='%s'`, html.EscapeString(a.title)) 93 if a.onclick != "" { 94 fmt.Fprintf(w, ` onclick='%s'`, html.EscapeString(a.onclick)) 95 } 96 if a.href != "" { 97 // TODO(adonovan): I think that in principle, a.href must first be 98 // url.QueryEscape'd, but if I do that, a leading slash becomes "%2F", 99 // which causes the browser to treat the path as relative, not absolute. 100 // WTF? 101 fmt.Fprintf(w, ` href='%s'`, html.EscapeString(a.href)) 102 } 103 fmt.Fprintf(w, ">") 104 } else { 105 fmt.Fprintf(w, "</a>") 106 } 107 } 108 109 // An <a class='error'> element. 110 type errorLink struct { 111 start int 112 msg string 113 } 114 115 func (e errorLink) Start() int { return e.start } 116 func (e errorLink) End() int { return e.start + 1 } 117 118 func (e errorLink) Write(w io.Writer, _ int, start bool) { 119 // <span> causes havoc, not sure why, so use <a>. 120 if start { 121 fmt.Fprintf(w, `<a class='error' title='%s'>`, html.EscapeString(e.msg)) 122 } else { 123 fmt.Fprintf(w, "</a>") 124 } 125 } 126 127 // -- fileInfo --------------------------------------------------------- 128 129 // FileInfo holds analysis information for the source file view. 130 // Clients must not mutate it. 131 type FileInfo struct { 132 Data []interface{} // JSON serializable values 133 Links []Link // HTML link markup 134 } 135 136 // A fileInfo is the server's store of hyperlinks and JSON data for a 137 // particular file. 138 type fileInfo struct { 139 mu sync.Mutex 140 data []interface{} // JSON objects 141 links []Link 142 sorted bool 143 hasErrors bool // TODO(adonovan): surface this in the UI 144 } 145 146 // addLink adds a link to the Go source file fi. 147 func (fi *fileInfo) addLink(link Link) { 148 fi.mu.Lock() 149 fi.links = append(fi.links, link) 150 fi.sorted = false 151 if _, ok := link.(errorLink); ok { 152 fi.hasErrors = true 153 } 154 fi.mu.Unlock() 155 } 156 157 // addData adds the structured value x to the JSON data for the Go 158 // source file fi. Its index is returned. 159 func (fi *fileInfo) addData(x interface{}) int { 160 fi.mu.Lock() 161 index := len(fi.data) 162 fi.data = append(fi.data, x) 163 fi.mu.Unlock() 164 return index 165 } 166 167 // get returns the file info in external form. 168 // Callers must not mutate its fields. 169 func (fi *fileInfo) get() FileInfo { 170 var r FileInfo 171 // Copy slices, to avoid races. 172 fi.mu.Lock() 173 r.Data = append(r.Data, fi.data...) 174 if !fi.sorted { 175 sort.Sort(linksByStart(fi.links)) 176 fi.sorted = true 177 } 178 r.Links = append(r.Links, fi.links...) 179 fi.mu.Unlock() 180 return r 181 } 182 183 // PackageInfo holds analysis information for the package view. 184 // Clients must not mutate it. 185 type PackageInfo struct { 186 CallGraph []*PCGNodeJSON 187 CallGraphIndex map[string]int 188 Types []*TypeInfoJSON 189 } 190 191 type pkgInfo struct { 192 mu sync.Mutex 193 callGraph []*PCGNodeJSON 194 callGraphIndex map[string]int // keys are (*ssa.Function).RelString() 195 types []*TypeInfoJSON // type info for exported types 196 } 197 198 func (pi *pkgInfo) setCallGraph(callGraph []*PCGNodeJSON, callGraphIndex map[string]int) { 199 pi.mu.Lock() 200 pi.callGraph = callGraph 201 pi.callGraphIndex = callGraphIndex 202 pi.mu.Unlock() 203 } 204 205 func (pi *pkgInfo) addType(t *TypeInfoJSON) { 206 pi.mu.Lock() 207 pi.types = append(pi.types, t) 208 pi.mu.Unlock() 209 } 210 211 // get returns the package info in external form. 212 // Callers must not mutate its fields. 213 func (pi *pkgInfo) get() PackageInfo { 214 var r PackageInfo 215 // Copy slices, to avoid races. 216 pi.mu.Lock() 217 r.CallGraph = append(r.CallGraph, pi.callGraph...) 218 r.CallGraphIndex = pi.callGraphIndex 219 r.Types = append(r.Types, pi.types...) 220 pi.mu.Unlock() 221 return r 222 } 223 224 // -- Result ----------------------------------------------------------- 225 226 // Result contains the results of analysis. 227 // The result contains a mapping from filenames to a set of HTML links 228 // and JavaScript data referenced by the links. 229 type Result struct { 230 mu sync.Mutex // guards maps (but not their contents) 231 status string // global analysis status 232 fileInfos map[string]*fileInfo // keys are godoc file URLs 233 pkgInfos map[string]*pkgInfo // keys are import paths 234 } 235 236 // fileInfo returns the fileInfo for the specified godoc file URL, 237 // constructing it as needed. Thread-safe. 238 func (res *Result) fileInfo(url string) *fileInfo { 239 res.mu.Lock() 240 fi, ok := res.fileInfos[url] 241 if !ok { 242 if res.fileInfos == nil { 243 res.fileInfos = make(map[string]*fileInfo) 244 } 245 fi = new(fileInfo) 246 res.fileInfos[url] = fi 247 } 248 res.mu.Unlock() 249 return fi 250 } 251 252 // Status returns a human-readable description of the current analysis status. 253 func (res *Result) Status() string { 254 res.mu.Lock() 255 defer res.mu.Unlock() 256 return res.status 257 } 258 259 func (res *Result) setStatusf(format string, args ...interface{}) { 260 res.mu.Lock() 261 res.status = fmt.Sprintf(format, args...) 262 log.Printf(format, args...) 263 res.mu.Unlock() 264 } 265 266 // FileInfo returns new slices containing opaque JSON values and the 267 // HTML link markup for the specified godoc file URL. Thread-safe. 268 // Callers must not mutate the elements. 269 // It returns "zero" if no data is available. 270 // 271 func (res *Result) FileInfo(url string) (fi FileInfo) { 272 return res.fileInfo(url).get() 273 } 274 275 // pkgInfo returns the pkgInfo for the specified import path, 276 // constructing it as needed. Thread-safe. 277 func (res *Result) pkgInfo(importPath string) *pkgInfo { 278 res.mu.Lock() 279 pi, ok := res.pkgInfos[importPath] 280 if !ok { 281 if res.pkgInfos == nil { 282 res.pkgInfos = make(map[string]*pkgInfo) 283 } 284 pi = new(pkgInfo) 285 res.pkgInfos[importPath] = pi 286 } 287 res.mu.Unlock() 288 return pi 289 } 290 291 // PackageInfo returns new slices of JSON values for the callgraph and 292 // type info for the specified package. Thread-safe. 293 // Callers must not mutate its fields. 294 // PackageInfo returns "zero" if no data is available. 295 // 296 func (res *Result) PackageInfo(importPath string) PackageInfo { 297 return res.pkgInfo(importPath).get() 298 } 299 300 // -- analysis --------------------------------------------------------- 301 302 type analysis struct { 303 result *Result 304 prog *ssa.Program 305 ops []chanOp // all channel ops in program 306 allNamed []*types.Named // all named types in the program 307 ptaConfig pointer.Config 308 path2url map[string]string // maps openable path to godoc file URL (/src/fmt/print.go) 309 pcgs map[*ssa.Package]*packageCallGraph 310 } 311 312 // fileAndOffset returns the file and offset for a given pos. 313 func (a *analysis) fileAndOffset(pos token.Pos) (fi *fileInfo, offset int) { 314 return a.fileAndOffsetPosn(a.prog.Fset.Position(pos)) 315 } 316 317 // fileAndOffsetPosn returns the file and offset for a given position. 318 func (a *analysis) fileAndOffsetPosn(posn token.Position) (fi *fileInfo, offset int) { 319 url := a.path2url[posn.Filename] 320 return a.result.fileInfo(url), posn.Offset 321 } 322 323 // posURL returns the URL of the source extent [pos, pos+len). 324 func (a *analysis) posURL(pos token.Pos, len int) string { 325 if pos == token.NoPos { 326 return "" 327 } 328 posn := a.prog.Fset.Position(pos) 329 url := a.path2url[posn.Filename] 330 return fmt.Sprintf("%s?s=%d:%d#L%d", 331 url, posn.Offset, posn.Offset+len, posn.Line) 332 } 333 334 // ---------------------------------------------------------------------- 335 336 // Run runs program analysis and computes the resulting markup, 337 // populating *result in a thread-safe manner, first with type 338 // information then later with pointer analysis information if 339 // enabled by the pta flag. 340 // 341 func Run(pta bool, result *Result) { 342 conf := loader.Config{ 343 AllowErrors: true, 344 } 345 346 // Silence the default error handler. 347 // Don't print all errors; we'll report just 348 // one per errant package later. 349 conf.TypeChecker.Error = func(e error) {} 350 351 var roots, args []string // roots[i] ends with os.PathSeparator 352 353 // Enumerate packages in $GOROOT. 354 root := filepath.Join(runtime.GOROOT(), "src") + string(os.PathSeparator) 355 roots = append(roots, root) 356 args = allPackages(root) 357 log.Printf("GOROOT=%s: %s\n", root, args) 358 359 // Enumerate packages in $GOPATH. 360 for i, dir := range filepath.SplitList(build.Default.GOPATH) { 361 root := filepath.Join(dir, "src") + string(os.PathSeparator) 362 roots = append(roots, root) 363 pkgs := allPackages(root) 364 log.Printf("GOPATH[%d]=%s: %s\n", i, root, pkgs) 365 args = append(args, pkgs...) 366 } 367 368 // Uncomment to make startup quicker during debugging. 369 //args = []string{"golang.org/x/tools/cmd/godoc"} 370 //args = []string{"fmt"} 371 372 if _, err := conf.FromArgs(args, true); err != nil { 373 // TODO(adonovan): degrade gracefully, not fail totally. 374 // (The crippling case is a parse error in an external test file.) 375 result.setStatusf("Analysis failed: %s.", err) // import error 376 return 377 } 378 379 result.setStatusf("Loading and type-checking packages...") 380 iprog, err := conf.Load() 381 if iprog != nil { 382 // Report only the first error of each package. 383 for _, info := range iprog.AllPackages { 384 for _, err := range info.Errors { 385 fmt.Fprintln(os.Stderr, err) 386 break 387 } 388 } 389 log.Printf("Loaded %d packages.", len(iprog.AllPackages)) 390 } 391 if err != nil { 392 result.setStatusf("Loading failed: %s.\n", err) 393 return 394 } 395 396 // Create SSA-form program representation. 397 // Only the transitively error-free packages are used. 398 prog := ssautil.CreateProgram(iprog, ssa.GlobalDebug) 399 400 // Compute the set of main packages, including testmain. 401 allPackages := prog.AllPackages() 402 var mainPkgs []*ssa.Package 403 if testmain := prog.CreateTestMainPackage(allPackages...); testmain != nil { 404 mainPkgs = append(mainPkgs, testmain) 405 if p := testmain.Const("packages"); p != nil { 406 log.Printf("Tested packages: %v", exact.StringVal(p.Value.Value)) 407 } 408 } 409 for _, pkg := range allPackages { 410 if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil { 411 mainPkgs = append(mainPkgs, pkg) 412 } 413 } 414 log.Print("Transitively error-free main packages: ", mainPkgs) 415 416 // Build SSA code for bodies of all functions in the whole program. 417 result.setStatusf("Constructing SSA form...") 418 prog.Build() 419 log.Print("SSA construction complete") 420 421 a := analysis{ 422 result: result, 423 prog: prog, 424 pcgs: make(map[*ssa.Package]*packageCallGraph), 425 } 426 427 // Build a mapping from openable filenames to godoc file URLs, 428 // i.e. "/src/" plus path relative to GOROOT/src or GOPATH[i]/src. 429 a.path2url = make(map[string]string) 430 for _, info := range iprog.AllPackages { 431 nextfile: 432 for _, f := range info.Files { 433 if f.Pos() == 0 { 434 continue // e.g. files generated by cgo 435 } 436 abs := iprog.Fset.File(f.Pos()).Name() 437 // Find the root to which this file belongs. 438 for _, root := range roots { 439 rel := strings.TrimPrefix(abs, root) 440 if len(rel) < len(abs) { 441 a.path2url[abs] = "/src/" + filepath.ToSlash(rel) 442 continue nextfile 443 } 444 } 445 446 log.Printf("Can't locate file %s (package %q) beneath any root", 447 abs, info.Pkg.Path()) 448 } 449 } 450 451 // Add links for scanner, parser, type-checker errors. 452 // TODO(adonovan): fix: these links can overlap with 453 // identifier markup, causing the renderer to emit some 454 // characters twice. 455 errors := make(map[token.Position][]string) 456 for _, info := range iprog.AllPackages { 457 for _, err := range info.Errors { 458 switch err := err.(type) { 459 case types.Error: 460 posn := a.prog.Fset.Position(err.Pos) 461 errors[posn] = append(errors[posn], err.Msg) 462 case scanner.ErrorList: 463 for _, e := range err { 464 errors[e.Pos] = append(errors[e.Pos], e.Msg) 465 } 466 default: 467 log.Printf("Package %q has error (%T) without position: %v\n", 468 info.Pkg.Path(), err, err) 469 } 470 } 471 } 472 for posn, errs := range errors { 473 fi, offset := a.fileAndOffsetPosn(posn) 474 fi.addLink(errorLink{ 475 start: offset, 476 msg: strings.Join(errs, "\n"), 477 }) 478 } 479 480 // ---------- type-based analyses ---------- 481 482 // Compute the all-pairs IMPLEMENTS relation. 483 // Collect all named types, even local types 484 // (which can have methods via promotion) 485 // and the built-in "error". 486 errorType := types.Universe.Lookup("error").Type().(*types.Named) 487 a.allNamed = append(a.allNamed, errorType) 488 for _, info := range iprog.AllPackages { 489 for _, obj := range info.Defs { 490 if obj, ok := obj.(*types.TypeName); ok { 491 a.allNamed = append(a.allNamed, obj.Type().(*types.Named)) 492 } 493 } 494 } 495 log.Print("Computing implements relation...") 496 facts := computeImplements(&a.prog.MethodSets, a.allNamed) 497 498 // Add the type-based analysis results. 499 log.Print("Extracting type info...") 500 for _, info := range iprog.AllPackages { 501 a.doTypeInfo(info, facts) 502 } 503 504 a.visitInstrs(pta) 505 506 result.setStatusf("Type analysis complete.") 507 508 if pta { 509 a.pointer(mainPkgs) 510 } 511 } 512 513 // visitInstrs visits all SSA instructions in the program. 514 func (a *analysis) visitInstrs(pta bool) { 515 log.Print("Visit instructions...") 516 for fn := range ssautil.AllFunctions(a.prog) { 517 for _, b := range fn.Blocks { 518 for _, instr := range b.Instrs { 519 // CALLEES (static) 520 // (Dynamic calls require pointer analysis.) 521 // 522 // We use the SSA representation to find the static callee, 523 // since in many cases it does better than the 524 // types.Info.{Refs,Selection} information. For example: 525 // 526 // defer func(){}() // static call to anon function 527 // f := func(){}; f() // static call to anon function 528 // f := fmt.Println; f() // static call to named function 529 // 530 // The downside is that we get no static callee information 531 // for packages that (transitively) contain errors. 532 if site, ok := instr.(ssa.CallInstruction); ok { 533 if callee := site.Common().StaticCallee(); callee != nil { 534 // TODO(adonovan): callgraph: elide wrappers. 535 // (Do static calls ever go to wrappers?) 536 if site.Common().Pos() != token.NoPos { 537 a.addCallees(site, []*ssa.Function{callee}) 538 } 539 } 540 } 541 542 if !pta { 543 continue 544 } 545 546 // CHANNEL PEERS 547 // Collect send/receive/close instructions in the whole ssa.Program. 548 for _, op := range chanOps(instr) { 549 a.ops = append(a.ops, op) 550 a.ptaConfig.AddQuery(op.ch) // add channel ssa.Value to PTA query 551 } 552 } 553 } 554 } 555 log.Print("Visit instructions complete") 556 } 557 558 // pointer runs the pointer analysis. 559 func (a *analysis) pointer(mainPkgs []*ssa.Package) { 560 // Run the pointer analysis and build the complete callgraph. 561 a.ptaConfig.Mains = mainPkgs 562 a.ptaConfig.BuildCallGraph = true 563 a.ptaConfig.Reflection = false // (for now) 564 565 a.result.setStatusf("Pointer analysis running...") 566 567 ptares, err := pointer.Analyze(&a.ptaConfig) 568 if err != nil { 569 // If this happens, it indicates a bug. 570 a.result.setStatusf("Pointer analysis failed: %s.", err) 571 return 572 } 573 log.Print("Pointer analysis complete.") 574 575 // Add the results of pointer analysis. 576 577 a.result.setStatusf("Computing channel peers...") 578 a.doChannelPeers(ptares.Queries) 579 a.result.setStatusf("Computing dynamic call graph edges...") 580 a.doCallgraph(ptares.CallGraph) 581 582 a.result.setStatusf("Analysis complete.") 583 } 584 585 type linksByStart []Link 586 587 func (a linksByStart) Less(i, j int) bool { return a[i].Start() < a[j].Start() } 588 func (a linksByStart) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 589 func (a linksByStart) Len() int { return len(a) } 590 591 // allPackages returns a new sorted slice of all packages beneath the 592 // specified package root directory, e.g. $GOROOT/src or $GOPATH/src. 593 // Derived from from go/ssa/stdlib_test.go 594 // root must end with os.PathSeparator. 595 // 596 // TODO(adonovan): use buildutil.AllPackages when the tree thaws. 597 func allPackages(root string) []string { 598 var pkgs []string 599 filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 600 if info == nil { 601 return nil // non-existent root directory? 602 } 603 if !info.IsDir() { 604 return nil // not a directory 605 } 606 // Prune the search if we encounter any of these names: 607 base := filepath.Base(path) 608 if base == "testdata" || strings.HasPrefix(base, ".") { 609 return filepath.SkipDir 610 } 611 pkg := filepath.ToSlash(strings.TrimPrefix(path, root)) 612 switch pkg { 613 case "builtin": 614 return filepath.SkipDir 615 case "": 616 return nil // ignore root of tree 617 } 618 pkgs = append(pkgs, pkg) 619 return nil 620 }) 621 return pkgs 622 }