honnef.co/go/tools@v0.5.0-0.dev.0.20240520180541-dcae280a5e87/lintcmd/runner/runner.go (about) 1 // Package runner implements a go/analysis runner. It makes heavy use 2 // of on-disk caching to reduce overall memory usage and to speed up 3 // repeat runs. 4 // 5 // # Public API 6 // 7 // A Runner maps a list of analyzers and package patterns to a list of 8 // results. Results provide access to diagnostics, directives, errors 9 // encountered, and information about packages. Results explicitly do 10 // not contain ASTs or type information. All position information is 11 // returned in the form of token.Position, not token.Pos. All work 12 // that requires access to the loaded representation of a package has 13 // to occur inside analyzers. 14 // 15 // # Planning and execution 16 // 17 // Analyzing packages is split into two phases: planning and 18 // execution. 19 // 20 // During planning, a directed acyclic graph of package dependencies 21 // is computed. We materialize the full graph so that we can execute 22 // the graph from the bottom up, without keeping unnecessary data in 23 // memory during a DFS and with simplified parallel execution. 24 // 25 // During execution, leaf nodes (nodes with no outstanding 26 // dependencies) get executed in parallel, bounded by a semaphore 27 // sized according to the number of CPUs. Conceptually, this happens 28 // in a loop, processing new leaf nodes as they appear, until no more 29 // nodes are left. In the actual implementation, nodes know their 30 // dependents, and the last dependency of a node to be processed is 31 // responsible for scheduling its dependent. 32 // 33 // The graph is rooted at a synthetic root node. Upon execution of the 34 // root node, the algorithm terminates. 35 // 36 // Analyzing a package repeats the same planning + execution steps, 37 // but this time on a graph of analyzers for the package. Parallel 38 // execution of individual analyzers is bounded by the same semaphore 39 // as executing packages. 40 // 41 // # Parallelism 42 // 43 // Actions are executed in parallel where the dependency graph allows. 44 // Overall parallelism is bounded by a semaphore, sized according to 45 // GOMAXPROCS. Each concurrently processed package takes up a 46 // token, as does each analyzer – but a package can always execute at 47 // least one analyzer, using the package's token. 48 // 49 // Depending on the overall shape of the graph, there may be GOMAXPROCS 50 // packages running a single analyzer each, a single package running 51 // GOMAXPROCS analyzers, or anything in between. 52 // 53 // Total memory consumption grows roughly linearly with the number of 54 // CPUs, while total execution time is inversely proportional to the 55 // number of CPUs. Overall, parallelism is affected by the shape of 56 // the dependency graph. A lot of inter-connected packages will see 57 // less parallelism than a lot of independent packages. 58 // 59 // # Caching 60 // 61 // The runner caches facts, directives and diagnostics in a 62 // content-addressable cache that is designed after Go's own cache. 63 // Additionally, it makes use of Go's export data. 64 // 65 // This cache not only speeds up repeat runs, it also reduces peak 66 // memory usage. When we've analyzed a package, we cache the results 67 // and drop them from memory. When a dependent needs any of this 68 // information, or when analysis is complete and we wish to render the 69 // results, the data gets loaded from disk again. 70 // 71 // Data only exists in memory when it is immediately needed, not 72 // retained for possible future uses. This trades increased CPU usage 73 // for reduced memory usage. A single dependency may be loaded many 74 // times over, but it greatly reduces peak memory usage, as an 75 // arbitrary amount of time may pass between analyzing a dependency 76 // and its dependent, during which other packages will be processed. 77 package runner 78 79 // OPT(dh): we could reduce disk storage usage of cached data by 80 // compressing it, either directly at the cache layer, or by feeding 81 // compressed data to the cache. Of course doing so may negatively 82 // affect CPU usage, and there are lower hanging fruit, such as 83 // needing to cache less data in the first place. 84 85 // OPT(dh): right now, each package is analyzed completely 86 // independently. Each package loads all of its dependencies from 87 // export data and cached facts. If we have two packages A and B, 88 // which both depend on C, and which both get analyzed in parallel, 89 // then C will be loaded twice. This wastes CPU time and memory. It 90 // would be nice if we could reuse a single C for the analysis of both 91 // A and B. 92 // 93 // We can't reuse the actual types.Package or facts, because each 94 // package gets its own token.FileSet. Sharing a global FileSet has 95 // several drawbacks, including increased memory usage and running the 96 // risk of running out of FileSet address space. 97 // 98 // We could however avoid loading the same raw export data from disk 99 // twice, as well as deserializing gob data twice. One possible 100 // solution would be a duplicate-suppressing in-memory cache that 101 // caches data for a limited amount of time. When the same package 102 // needs to be loaded twice in close succession, we can reuse work, 103 // without holding unnecessary data in memory for an extended period 104 // of time. 105 // 106 // We would likely need to do extensive benchmarking to figure out how 107 // long to keep data around to find a sweet spot where we reduce CPU 108 // load without increasing memory usage. 109 // 110 // We can probably populate the cache after we've analyzed a package, 111 // on the assumption that it will have to be loaded again in the near 112 // future. 113 114 import ( 115 "bytes" 116 "encoding/gob" 117 "fmt" 118 "go/token" 119 "go/types" 120 "io" 121 "os" 122 "reflect" 123 "runtime" 124 "sort" 125 "strings" 126 "sync/atomic" 127 "time" 128 129 "honnef.co/go/tools/analysis/lint" 130 "honnef.co/go/tools/analysis/report" 131 "honnef.co/go/tools/config" 132 "honnef.co/go/tools/go/loader" 133 tsync "honnef.co/go/tools/internal/sync" 134 "honnef.co/go/tools/lintcmd/cache" 135 "honnef.co/go/tools/unused" 136 137 "golang.org/x/tools/go/analysis" 138 "golang.org/x/tools/go/packages" 139 "golang.org/x/tools/go/types/objectpath" 140 ) 141 142 const sanityCheck = false 143 144 // Diagnostic is like go/analysis.Diagnostic, but with all token.Pos resolved to token.Position. 145 type Diagnostic struct { 146 Position token.Position 147 End token.Position 148 Category string 149 Message string 150 151 SuggestedFixes []SuggestedFix 152 Related []RelatedInformation 153 } 154 155 // RelatedInformation provides additional context for a diagnostic. 156 type RelatedInformation struct { 157 Position token.Position 158 End token.Position 159 Message string 160 } 161 162 type SuggestedFix struct { 163 Message string 164 TextEdits []TextEdit 165 } 166 167 type TextEdit struct { 168 Position token.Position 169 End token.Position 170 NewText []byte 171 } 172 173 // A Result describes the result of analyzing a single package. 174 // 175 // It holds references to cached diagnostics and directives. They can 176 // be loaded on demand with the Load method. 177 type Result struct { 178 Package *loader.PackageSpec 179 Config config.Config 180 Initial bool 181 Skipped bool 182 183 Failed bool 184 Errors []error 185 // Action results, path to file 186 results string 187 // Results relevant to testing, only set when test mode is enabled, path to file 188 testData string 189 } 190 191 type SerializedDirective struct { 192 Command string 193 Arguments []string 194 // The position of the comment 195 DirectivePosition token.Position 196 // The position of the node that the comment is attached to 197 NodePosition token.Position 198 } 199 200 func serializeDirective(dir lint.Directive, fset *token.FileSet) SerializedDirective { 201 return SerializedDirective{ 202 Command: dir.Command, 203 Arguments: dir.Arguments, 204 DirectivePosition: report.DisplayPosition(fset, dir.Directive.Pos()), 205 NodePosition: report.DisplayPosition(fset, dir.Node.Pos()), 206 } 207 } 208 209 type ResultData struct { 210 Directives []SerializedDirective 211 Diagnostics []Diagnostic 212 Unused unused.Result 213 } 214 215 func (r Result) Load() (ResultData, error) { 216 if r.Failed { 217 panic("Load called on failed Result") 218 } 219 if r.results == "" { 220 // this package was only a dependency 221 return ResultData{}, nil 222 } 223 f, err := os.Open(r.results) 224 if err != nil { 225 return ResultData{}, fmt.Errorf("failed loading result: %w", err) 226 } 227 defer f.Close() 228 var out ResultData 229 err = gob.NewDecoder(f).Decode(&out) 230 return out, err 231 } 232 233 // TestData contains extra information about analysis runs that is only available in test mode. 234 type TestData struct { 235 // Facts contains facts produced by analyzers for a package. 236 // Unlike vetx, this list only contains facts specific to this package, 237 // not all facts for the transitive closure of dependencies. 238 Facts []TestFact 239 // List of files that were part of the package. 240 Files []string 241 } 242 243 // LoadTest returns data relevant to testing. 244 // It should only be called if Runner.TestMode was set to true. 245 func (r Result) LoadTest() (TestData, error) { 246 if r.Failed { 247 panic("Load called on failed Result") 248 } 249 if r.results == "" { 250 // this package was only a dependency 251 return TestData{}, nil 252 } 253 f, err := os.Open(r.testData) 254 if err != nil { 255 return TestData{}, fmt.Errorf("failed loading test data: %w", err) 256 } 257 defer f.Close() 258 var out TestData 259 err = gob.NewDecoder(f).Decode(&out) 260 return out, err 261 } 262 263 type action interface { 264 Deps() []action 265 Triggers() []action 266 DecrementPending() bool 267 MarkFailed() 268 IsFailed() bool 269 AddError(error) 270 } 271 272 type baseAction struct { 273 // Action description 274 275 deps []action 276 triggers []action 277 pending uint32 278 279 // Action results 280 281 // failed is set to true if the action couldn't be processed. This 282 // may either be due to an error specific to this action, in 283 // which case the errors field will be populated, or due to a 284 // dependency being marked as failed, in which case errors will be 285 // empty. 286 failed bool 287 errors []error 288 } 289 290 func (act *baseAction) Deps() []action { return act.deps } 291 func (act *baseAction) Triggers() []action { return act.triggers } 292 func (act *baseAction) DecrementPending() bool { 293 return atomic.AddUint32(&act.pending, ^uint32(0)) == 0 294 } 295 func (act *baseAction) MarkFailed() { act.failed = true } 296 func (act *baseAction) IsFailed() bool { return act.failed } 297 func (act *baseAction) AddError(err error) { act.errors = append(act.errors, err) } 298 299 // packageAction describes the act of loading a package, fully 300 // analyzing it, and storing the results. 301 type packageAction struct { 302 baseAction 303 304 // Action description 305 Package *loader.PackageSpec 306 factsOnly bool 307 hash cache.ActionID 308 309 // Action results 310 cfg config.Config 311 vetx string 312 results string 313 testData string 314 skipped bool 315 } 316 317 func (act *packageAction) String() string { 318 return fmt.Sprintf("packageAction(%s)", act.Package) 319 } 320 321 type objectFact struct { 322 fact analysis.Fact 323 // TODO(dh): why do we store the objectpath when producing the 324 // fact? Is it just for the sanity checking, which compares the 325 // stored path with a path recomputed from objectFactKey.Obj? 326 path objectpath.Path 327 } 328 329 type objectFactKey struct { 330 Obj types.Object 331 Type reflect.Type 332 } 333 334 type packageFactKey struct { 335 Pkg *types.Package 336 Type reflect.Type 337 } 338 339 type gobFact struct { 340 PkgPath string 341 ObjPath string 342 Fact analysis.Fact 343 } 344 345 // TestFact is a serialization of facts that is specific to the test mode. 346 type TestFact struct { 347 ObjectName string 348 Position token.Position 349 FactString string 350 Analyzer string 351 } 352 353 // analyzerAction describes the act of analyzing a package with a 354 // single analyzer. 355 type analyzerAction struct { 356 baseAction 357 358 // Action description 359 360 Analyzer *analysis.Analyzer 361 362 // Action results 363 364 // We can store actual results here without worrying about memory 365 // consumption because analyzer actions get garbage collected once 366 // a package has been fully analyzed. 367 Result interface{} 368 Diagnostics []Diagnostic 369 ObjectFacts map[objectFactKey]objectFact 370 PackageFacts map[packageFactKey]analysis.Fact 371 Pass *analysis.Pass 372 } 373 374 func (act *analyzerAction) String() string { 375 return fmt.Sprintf("analyzerAction(%s)", act.Analyzer) 376 } 377 378 // A Runner executes analyzers on packages. 379 type Runner struct { 380 Stats Stats 381 GoVersion string 382 // if GoVersion == "module", and we couldn't determine the 383 // module's Go version, use this as the fallback 384 FallbackGoVersion string 385 // If set to true, Runner will populate results with data relevant to testing analyzers 386 TestMode bool 387 388 // GoVersion might be "module"; actualGoVersion contains the resolved version 389 actualGoVersion string 390 391 // Config that gets merged with per-package configs 392 cfg config.Config 393 cache *cache.Cache 394 semaphore tsync.Semaphore 395 } 396 397 type subrunner struct { 398 *Runner 399 analyzers []*analysis.Analyzer 400 factAnalyzers []*analysis.Analyzer 401 analyzerNames string 402 cache *cache.Cache 403 } 404 405 // New returns a new Runner. 406 func New(cfg config.Config, c *cache.Cache) (*Runner, error) { 407 return &Runner{ 408 cfg: cfg, 409 cache: c, 410 semaphore: tsync.NewSemaphore(runtime.GOMAXPROCS(0)), 411 }, nil 412 } 413 414 func newSubrunner(r *Runner, analyzers []*analysis.Analyzer) *subrunner { 415 analyzerNames := make([]string, len(analyzers)) 416 for i, a := range analyzers { 417 analyzerNames[i] = a.Name 418 } 419 sort.Strings(analyzerNames) 420 421 var factAnalyzers []*analysis.Analyzer 422 for _, a := range analyzers { 423 if len(a.FactTypes) > 0 { 424 factAnalyzers = append(factAnalyzers, a) 425 } 426 } 427 return &subrunner{ 428 Runner: r, 429 analyzers: analyzers, 430 factAnalyzers: factAnalyzers, 431 analyzerNames: strings.Join(analyzerNames, ","), 432 cache: r.cache, 433 } 434 } 435 436 func newPackageActionRoot(pkg *loader.PackageSpec, cache map[*loader.PackageSpec]*packageAction) *packageAction { 437 a := newPackageAction(pkg, cache) 438 a.factsOnly = false 439 return a 440 } 441 442 func newPackageAction(pkg *loader.PackageSpec, cache map[*loader.PackageSpec]*packageAction) *packageAction { 443 if a, ok := cache[pkg]; ok { 444 return a 445 } 446 447 a := &packageAction{ 448 Package: pkg, 449 factsOnly: true, // will be overwritten by any call to Action 450 } 451 cache[pkg] = a 452 453 if len(pkg.Errors) > 0 { 454 a.errors = make([]error, len(pkg.Errors)) 455 for i, err := range pkg.Errors { 456 a.errors[i] = err 457 } 458 a.failed = true 459 460 // We don't need to process our imports if this package is 461 // already broken. 462 return a 463 } 464 465 a.deps = make([]action, 0, len(pkg.Imports)) 466 for _, dep := range pkg.Imports { 467 depa := newPackageAction(dep, cache) 468 depa.triggers = append(depa.triggers, a) 469 a.deps = append(a.deps, depa) 470 471 if depa.failed { 472 a.failed = true 473 } 474 } 475 // sort dependencies because the list of dependencies is part of 476 // the cache key 477 sort.Slice(a.deps, func(i, j int) bool { 478 return a.deps[i].(*packageAction).Package.ID < a.deps[j].(*packageAction).Package.ID 479 }) 480 481 a.pending = uint32(len(a.deps)) 482 483 return a 484 } 485 486 func newAnalyzerAction(an *analysis.Analyzer, cache map[*analysis.Analyzer]*analyzerAction) *analyzerAction { 487 if a, ok := cache[an]; ok { 488 return a 489 } 490 491 a := &analyzerAction{ 492 Analyzer: an, 493 ObjectFacts: map[objectFactKey]objectFact{}, 494 PackageFacts: map[packageFactKey]analysis.Fact{}, 495 } 496 cache[an] = a 497 for _, dep := range an.Requires { 498 depa := newAnalyzerAction(dep, cache) 499 depa.triggers = append(depa.triggers, a) 500 a.deps = append(a.deps, depa) 501 } 502 a.pending = uint32(len(a.deps)) 503 return a 504 } 505 506 func getCachedFiles(cache *cache.Cache, ids []cache.ActionID, out []*string) error { 507 for i, id := range ids { 508 var err error 509 *out[i], _, err = cache.GetFile(id) 510 if err != nil { 511 return err 512 } 513 } 514 return nil 515 } 516 517 func (r *subrunner) do(act action) error { 518 a := act.(*packageAction) 519 defer func() { 520 r.Stats.finishPackage() 521 if !a.factsOnly { 522 r.Stats.finishInitialPackage() 523 } 524 }() 525 526 // compute hash of action 527 a.cfg = a.Package.Config.Merge(r.cfg) 528 h := r.cache.NewHash("staticcheck " + a.Package.PkgPath) 529 530 // Note that we do not filter the list of analyzers by the 531 // package's configuration. We don't allow configuration to 532 // accidentally break dependencies between analyzers, and it's 533 // easier to always run all checks and filter the output. This 534 // also makes cached data more reusable. 535 536 // OPT(dh): not all changes in configuration invalidate cached 537 // data. specifically, when a.factsOnly == true, we only care 538 // about checks that produce facts, and settings that affect those 539 // checks. 540 541 // Config used for constructing the hash; this config doesn't have 542 // Checks populated, because we always run all checks. 543 // 544 // This even works for users who add custom checks, because we include the binary's hash. 545 hashCfg := a.cfg 546 hashCfg.Checks = nil 547 // note that we don't hash staticcheck's version; it is set as the 548 // salt by a package main. 549 fmt.Fprintf(h, "cfg %#v\n", hashCfg) 550 fmt.Fprintf(h, "pkg %x\n", a.Package.Hash) 551 fmt.Fprintf(h, "analyzers %s\n", r.analyzerNames) 552 fmt.Fprintf(h, "go %s\n", r.actualGoVersion) 553 fmt.Fprintf(h, "env godebug %q\n", os.Getenv("GODEBUG")) 554 555 // OPT(dh): do we actually need to hash vetx? can we not assume 556 // that for identical inputs, staticcheck will produce identical 557 // vetx? 558 for _, dep := range a.deps { 559 dep := dep.(*packageAction) 560 vetxHash, err := cache.FileHash(dep.vetx) 561 if err != nil { 562 return fmt.Errorf("failed computing hash: %w", err) 563 } 564 fmt.Fprintf(h, "vetout %q %x\n", dep.Package.PkgPath, vetxHash) 565 } 566 a.hash = cache.ActionID(h.Sum()) 567 568 // try to fetch hashed data 569 ids := make([]cache.ActionID, 0, 2) 570 ids = append(ids, cache.Subkey(a.hash, "vetx")) 571 if !a.factsOnly { 572 ids = append(ids, cache.Subkey(a.hash, "results")) 573 if r.TestMode { 574 ids = append(ids, cache.Subkey(a.hash, "testdata")) 575 } 576 } 577 if err := getCachedFiles(r.cache, ids, []*string{&a.vetx, &a.results, &a.testData}); err != nil { 578 result, err := r.doUncached(a) 579 if err != nil { 580 return err 581 } 582 if a.failed { 583 return nil 584 } 585 586 a.skipped = result.skipped 587 588 // OPT(dh) instead of collecting all object facts and encoding 589 // them after analysis finishes, we could encode them as we 590 // go. however, that would require some locking. 591 // 592 // OPT(dh): We could sort gobFacts for more consistent output, 593 // but it doesn't matter. The hash of a package includes all 594 // of its files, so whether the vetx hash changes or not, a 595 // change to a package requires re-analyzing all dependents, 596 // even if the vetx data stayed the same. See also the note at 597 // the top of loader/hash.go. 598 599 tf := &bytes.Buffer{} 600 enc := gob.NewEncoder(tf) 601 for _, gf := range result.facts { 602 if err := enc.Encode(gf); err != nil { 603 return fmt.Errorf("failed gob encoding data: %w", err) 604 } 605 } 606 607 a.vetx, err = r.writeCacheReader(a, "vetx", bytes.NewReader(tf.Bytes())) 608 if err != nil { 609 return err 610 } 611 612 if a.factsOnly { 613 return nil 614 } 615 616 var out ResultData 617 out.Directives = make([]SerializedDirective, len(result.dirs)) 618 for i, dir := range result.dirs { 619 out.Directives[i] = serializeDirective(dir, result.lpkg.Fset) 620 } 621 622 out.Diagnostics = result.diags 623 out.Unused = result.unused 624 a.results, err = r.writeCacheGob(a, "results", out) 625 if err != nil { 626 return err 627 } 628 629 if r.TestMode { 630 out := TestData{ 631 Facts: result.testFacts, 632 Files: result.lpkg.GoFiles, 633 } 634 a.testData, err = r.writeCacheGob(a, "testdata", out) 635 if err != nil { 636 return err 637 } 638 } 639 } 640 return nil 641 } 642 643 // ActiveWorkers returns the number of currently running workers. 644 func (r *Runner) ActiveWorkers() int { 645 return r.semaphore.Len() 646 } 647 648 // TotalWorkers returns the maximum number of possible workers. 649 func (r *Runner) TotalWorkers() int { 650 return r.semaphore.Cap() 651 } 652 653 func (r *Runner) writeCacheReader(a *packageAction, kind string, rs io.ReadSeeker) (string, error) { 654 h := cache.Subkey(a.hash, kind) 655 out, _, err := r.cache.Put(h, rs) 656 if err != nil { 657 return "", fmt.Errorf("failed caching data: %w", err) 658 } 659 return r.cache.OutputFile(out), nil 660 } 661 662 func (r *Runner) writeCacheGob(a *packageAction, kind string, data interface{}) (string, error) { 663 f, err := os.CreateTemp("", "staticcheck") 664 if err != nil { 665 return "", err 666 } 667 defer f.Close() 668 os.Remove(f.Name()) 669 if err := gob.NewEncoder(f).Encode(data); err != nil { 670 return "", fmt.Errorf("failed gob encoding data: %w", err) 671 } 672 if _, err := f.Seek(0, io.SeekStart); err != nil { 673 return "", err 674 } 675 return r.writeCacheReader(a, kind, f) 676 } 677 678 type packageActionResult struct { 679 facts []gobFact 680 diags []Diagnostic 681 unused unused.Result 682 dirs []lint.Directive 683 lpkg *loader.Package 684 skipped bool 685 686 // Only set when using test mode 687 testFacts []TestFact 688 } 689 690 func (r *subrunner) doUncached(a *packageAction) (packageActionResult, error) { 691 // OPT(dh): for a -> b; c -> b; if both a and b are being 692 // processed concurrently, we shouldn't load b's export data 693 // twice. 694 695 pkg, _, err := loader.Load(a.Package) 696 if err != nil { 697 return packageActionResult{}, err 698 } 699 700 if len(pkg.Errors) > 0 { 701 // this handles errors that occurred during type-checking the 702 // package in loader.Load 703 for _, err := range pkg.Errors { 704 a.errors = append(a.errors, err) 705 } 706 a.failed = true 707 return packageActionResult{}, nil 708 } 709 710 if len(pkg.Syntax) == 0 && pkg.PkgPath != "unsafe" { 711 return packageActionResult{lpkg: pkg, skipped: true}, nil 712 } 713 714 // OPT(dh): instead of parsing directives twice (twice because 715 // U1000 depends on the facts.Directives analyzer), reuse the 716 // existing result 717 var dirs []lint.Directive 718 if !a.factsOnly { 719 dirs = lint.ParseDirectives(pkg.Syntax, pkg.Fset) 720 } 721 res, err := r.runAnalyzers(a, pkg) 722 723 return packageActionResult{ 724 facts: res.facts, 725 testFacts: res.testFacts, 726 diags: res.diagnostics, 727 unused: res.unused, 728 dirs: dirs, 729 lpkg: pkg, 730 }, err 731 } 732 733 func pkgPaths(root *types.Package) map[string]*types.Package { 734 out := map[string]*types.Package{} 735 var dfs func(*types.Package) 736 dfs = func(pkg *types.Package) { 737 if _, ok := out[pkg.Path()]; ok { 738 return 739 } 740 out[pkg.Path()] = pkg 741 for _, imp := range pkg.Imports() { 742 dfs(imp) 743 } 744 } 745 dfs(root) 746 return out 747 } 748 749 func (r *Runner) loadFacts(root *types.Package, dep *packageAction, objFacts map[objectFactKey]objectFact, pkgFacts map[packageFactKey]analysis.Fact) error { 750 // Load facts of all imported packages 751 vetx, err := os.Open(dep.vetx) 752 if err != nil { 753 return fmt.Errorf("failed loading cached facts: %w", err) 754 } 755 defer vetx.Close() 756 757 pathToPkg := pkgPaths(root) 758 dec := gob.NewDecoder(vetx) 759 for { 760 var gf gobFact 761 err := dec.Decode(&gf) 762 if err != nil { 763 if err == io.EOF { 764 break 765 } 766 return fmt.Errorf("failed loading cached facts: %w", err) 767 } 768 769 pkg, ok := pathToPkg[gf.PkgPath] 770 if !ok { 771 continue 772 } 773 if gf.ObjPath == "" { 774 pkgFacts[packageFactKey{ 775 Pkg: pkg, 776 Type: reflect.TypeOf(gf.Fact), 777 }] = gf.Fact 778 } else { 779 obj, err := objectpath.Object(pkg, objectpath.Path(gf.ObjPath)) 780 if err != nil { 781 continue 782 } 783 objFacts[objectFactKey{ 784 Obj: obj, 785 Type: reflect.TypeOf(gf.Fact), 786 }] = objectFact{gf.Fact, objectpath.Path(gf.ObjPath)} 787 } 788 } 789 return nil 790 } 791 792 func genericHandle(a action, root action, queue chan action, sem *tsync.Semaphore, exec func(a action) error) { 793 if a == root { 794 close(queue) 795 if sem != nil { 796 sem.Release() 797 } 798 return 799 } 800 if !a.IsFailed() { 801 // the action may have already been marked as failed during 802 // construction of the action graph, for example because of 803 // unresolved imports. 804 805 for _, dep := range a.Deps() { 806 if dep.IsFailed() { 807 // One of our dependencies failed, so mark this package as 808 // failed and bail. We don't need to record an error for 809 // this package, the relevant error will have been 810 // reported by the first package in the chain that failed. 811 a.MarkFailed() 812 break 813 } 814 } 815 } 816 817 if !a.IsFailed() { 818 if err := exec(a); err != nil { 819 a.MarkFailed() 820 a.AddError(err) 821 } 822 } 823 if sem != nil { 824 sem.Release() 825 } 826 827 for _, t := range a.Triggers() { 828 if t.DecrementPending() { 829 queue <- t 830 } 831 } 832 } 833 834 type analyzerRunner struct { 835 pkg *loader.Package 836 // object facts of our dependencies; may contain facts of 837 // analyzers other than the current one 838 depObjFacts map[objectFactKey]objectFact 839 // package facts of our dependencies; may contain facts of 840 // analyzers other than the current one 841 depPkgFacts map[packageFactKey]analysis.Fact 842 factsOnly bool 843 844 stats *Stats 845 } 846 847 func (ar *analyzerRunner) do(act action) error { 848 a := act.(*analyzerAction) 849 results := map[*analysis.Analyzer]interface{}{} 850 // TODO(dh): does this have to be recursive? 851 for _, dep := range a.deps { 852 dep := dep.(*analyzerAction) 853 results[dep.Analyzer] = dep.Result 854 } 855 // OPT(dh): cache factTypes, it is the same for all packages for a given analyzer 856 // 857 // OPT(dh): do we need the factTypes map? most analyzers have 0-1 858 // fact types. iterating over the slice is probably faster than 859 // indexing a map. 860 factTypes := map[reflect.Type]struct{}{} 861 for _, typ := range a.Analyzer.FactTypes { 862 factTypes[reflect.TypeOf(typ)] = struct{}{} 863 } 864 filterFactType := func(typ reflect.Type) bool { 865 _, ok := factTypes[typ] 866 return ok 867 } 868 a.Pass = &analysis.Pass{ 869 Analyzer: a.Analyzer, 870 Fset: ar.pkg.Fset, 871 Files: ar.pkg.Syntax, 872 OtherFiles: ar.pkg.OtherFiles, 873 Pkg: ar.pkg.Types, 874 TypesInfo: ar.pkg.TypesInfo, 875 TypesSizes: ar.pkg.TypesSizes, 876 Report: func(diag analysis.Diagnostic) { 877 if !ar.factsOnly { 878 if diag.Category == "" { 879 diag.Category = a.Analyzer.Name 880 } 881 d := Diagnostic{ 882 Position: report.DisplayPosition(ar.pkg.Fset, diag.Pos), 883 End: report.DisplayPosition(ar.pkg.Fset, diag.End), 884 Category: diag.Category, 885 Message: diag.Message, 886 } 887 for _, sugg := range diag.SuggestedFixes { 888 s := SuggestedFix{ 889 Message: sugg.Message, 890 } 891 for _, edit := range sugg.TextEdits { 892 s.TextEdits = append(s.TextEdits, TextEdit{ 893 Position: report.DisplayPosition(ar.pkg.Fset, edit.Pos), 894 End: report.DisplayPosition(ar.pkg.Fset, edit.End), 895 NewText: edit.NewText, 896 }) 897 } 898 d.SuggestedFixes = append(d.SuggestedFixes, s) 899 } 900 for _, rel := range diag.Related { 901 d.Related = append(d.Related, RelatedInformation{ 902 Position: report.DisplayPosition(ar.pkg.Fset, rel.Pos), 903 End: report.DisplayPosition(ar.pkg.Fset, rel.End), 904 Message: rel.Message, 905 }) 906 } 907 a.Diagnostics = append(a.Diagnostics, d) 908 } 909 }, 910 ResultOf: results, 911 ImportObjectFact: func(obj types.Object, fact analysis.Fact) bool { 912 key := objectFactKey{ 913 Obj: obj, 914 Type: reflect.TypeOf(fact), 915 } 916 if f, ok := ar.depObjFacts[key]; ok { 917 reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f.fact).Elem()) 918 return true 919 } else if f, ok := a.ObjectFacts[key]; ok { 920 reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f.fact).Elem()) 921 return true 922 } 923 return false 924 }, 925 ImportPackageFact: func(pkg *types.Package, fact analysis.Fact) bool { 926 key := packageFactKey{ 927 Pkg: pkg, 928 Type: reflect.TypeOf(fact), 929 } 930 if f, ok := ar.depPkgFacts[key]; ok { 931 reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f).Elem()) 932 return true 933 } else if f, ok := a.PackageFacts[key]; ok { 934 reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f).Elem()) 935 return true 936 } 937 return false 938 }, 939 ExportObjectFact: func(obj types.Object, fact analysis.Fact) { 940 key := objectFactKey{ 941 Obj: obj, 942 Type: reflect.TypeOf(fact), 943 } 944 path, _ := objectpath.For(obj) 945 a.ObjectFacts[key] = objectFact{fact, path} 946 }, 947 ExportPackageFact: func(fact analysis.Fact) { 948 key := packageFactKey{ 949 Pkg: ar.pkg.Types, 950 Type: reflect.TypeOf(fact), 951 } 952 a.PackageFacts[key] = fact 953 }, 954 AllPackageFacts: func() []analysis.PackageFact { 955 out := make([]analysis.PackageFact, 0, len(ar.depPkgFacts)+len(a.PackageFacts)) 956 for key, fact := range ar.depPkgFacts { 957 out = append(out, analysis.PackageFact{ 958 Package: key.Pkg, 959 Fact: fact, 960 }) 961 } 962 for key, fact := range a.PackageFacts { 963 out = append(out, analysis.PackageFact{ 964 Package: key.Pkg, 965 Fact: fact, 966 }) 967 } 968 return out 969 }, 970 AllObjectFacts: func() []analysis.ObjectFact { 971 out := make([]analysis.ObjectFact, 0, len(ar.depObjFacts)+len(a.ObjectFacts)) 972 for key, fact := range ar.depObjFacts { 973 if filterFactType(key.Type) { 974 out = append(out, analysis.ObjectFact{ 975 Object: key.Obj, 976 Fact: fact.fact, 977 }) 978 } 979 } 980 for key, fact := range a.ObjectFacts { 981 if filterFactType(key.Type) { 982 out = append(out, analysis.ObjectFact{ 983 Object: key.Obj, 984 Fact: fact.fact, 985 }) 986 } 987 } 988 return out 989 }, 990 } 991 992 t := time.Now() 993 res, err := a.Analyzer.Run(a.Pass) 994 ar.stats.measureAnalyzer(a.Analyzer, ar.pkg.PackageSpec, time.Since(t)) 995 if err != nil { 996 return err 997 } 998 a.Result = res 999 return nil 1000 } 1001 1002 type analysisResult struct { 1003 facts []gobFact 1004 diagnostics []Diagnostic 1005 unused unused.Result 1006 1007 // Only set when using test mode 1008 testFacts []TestFact 1009 } 1010 1011 func (r *subrunner) runAnalyzers(pkgAct *packageAction, pkg *loader.Package) (analysisResult, error) { 1012 depObjFacts := map[objectFactKey]objectFact{} 1013 depPkgFacts := map[packageFactKey]analysis.Fact{} 1014 1015 for _, dep := range pkgAct.deps { 1016 if err := r.loadFacts(pkg.Types, dep.(*packageAction), depObjFacts, depPkgFacts); err != nil { 1017 return analysisResult{}, err 1018 } 1019 } 1020 1021 root := &analyzerAction{} 1022 var analyzers []*analysis.Analyzer 1023 if pkgAct.factsOnly { 1024 // When analyzing non-initial packages, we only care about 1025 // analyzers that produce facts. 1026 analyzers = r.factAnalyzers 1027 } else { 1028 analyzers = r.analyzers 1029 } 1030 1031 all := map[*analysis.Analyzer]*analyzerAction{} 1032 for _, a := range analyzers { 1033 a := newAnalyzerAction(a, all) 1034 root.deps = append(root.deps, a) 1035 a.triggers = append(a.triggers, root) 1036 } 1037 root.pending = uint32(len(root.deps)) 1038 1039 ar := &analyzerRunner{ 1040 pkg: pkg, 1041 factsOnly: pkgAct.factsOnly, 1042 depObjFacts: depObjFacts, 1043 depPkgFacts: depPkgFacts, 1044 stats: &r.Stats, 1045 } 1046 queue := make(chan action, len(all)) 1047 for _, a := range all { 1048 if len(a.Deps()) == 0 { 1049 queue <- a 1050 } 1051 } 1052 1053 // Don't hang if there are no analyzers to run; for example 1054 // because we are analyzing a dependency but have no analyzers 1055 // that produce facts. 1056 if len(all) == 0 { 1057 close(queue) 1058 } 1059 for item := range queue { 1060 b := r.semaphore.AcquireMaybe() 1061 if b { 1062 go genericHandle(item, root, queue, &r.semaphore, ar.do) 1063 } else { 1064 // the semaphore is exhausted; run the analysis under the 1065 // token we've acquired for analyzing the package. 1066 genericHandle(item, root, queue, nil, ar.do) 1067 } 1068 } 1069 1070 var unusedResult unused.Result 1071 for _, a := range all { 1072 if a != root && a.Analyzer.Name == "U1000" && !a.failed { 1073 // TODO(dh): figure out a clean abstraction, instead of 1074 // special-casing U1000. 1075 unusedResult = a.Result.(unused.Result) 1076 } 1077 1078 for key, fact := range a.ObjectFacts { 1079 depObjFacts[key] = fact 1080 } 1081 for key, fact := range a.PackageFacts { 1082 depPkgFacts[key] = fact 1083 } 1084 } 1085 1086 // OPT(dh): cull objects not reachable via the exported closure 1087 var testFacts []TestFact 1088 gobFacts := make([]gobFact, 0, len(depObjFacts)+len(depPkgFacts)) 1089 for key, fact := range depObjFacts { 1090 if fact.path == "" { 1091 continue 1092 } 1093 if sanityCheck { 1094 p, _ := objectpath.For(key.Obj) 1095 if p != fact.path { 1096 panic(fmt.Sprintf("got different object paths for %v. old: %q new: %q", key.Obj, fact.path, p)) 1097 } 1098 } 1099 gf := gobFact{ 1100 PkgPath: key.Obj.Pkg().Path(), 1101 ObjPath: string(fact.path), 1102 Fact: fact.fact, 1103 } 1104 gobFacts = append(gobFacts, gf) 1105 } 1106 1107 for key, fact := range depPkgFacts { 1108 gf := gobFact{ 1109 PkgPath: key.Pkg.Path(), 1110 Fact: fact, 1111 } 1112 gobFacts = append(gobFacts, gf) 1113 } 1114 1115 if r.TestMode { 1116 for _, a := range all { 1117 for key, fact := range a.ObjectFacts { 1118 tgf := TestFact{ 1119 ObjectName: key.Obj.Name(), 1120 Position: pkg.Fset.Position(key.Obj.Pos()), 1121 FactString: fmt.Sprint(fact.fact), 1122 Analyzer: a.Analyzer.Name, 1123 } 1124 testFacts = append(testFacts, tgf) 1125 } 1126 1127 for _, fact := range a.PackageFacts { 1128 tgf := TestFact{ 1129 ObjectName: "", 1130 Position: pkg.Fset.Position(pkg.Syntax[0].Pos()), 1131 FactString: fmt.Sprint(fact), 1132 Analyzer: a.Analyzer.Name, 1133 } 1134 testFacts = append(testFacts, tgf) 1135 } 1136 } 1137 } 1138 1139 var diags []Diagnostic 1140 for _, a := range root.deps { 1141 a := a.(*analyzerAction) 1142 diags = append(diags, a.Diagnostics...) 1143 } 1144 return analysisResult{ 1145 facts: gobFacts, 1146 testFacts: testFacts, 1147 diagnostics: diags, 1148 unused: unusedResult, 1149 }, nil 1150 } 1151 1152 func registerGobTypes(analyzers []*analysis.Analyzer) { 1153 for _, a := range analyzers { 1154 for _, typ := range a.FactTypes { 1155 // FIXME(dh): use RegisterName so we can work around collisions 1156 // in names. For pointer-types, gob incorrectly qualifies 1157 // type names with the package name, not the import path. 1158 gob.Register(typ) 1159 } 1160 } 1161 } 1162 1163 func allAnalyzers(analyzers []*analysis.Analyzer) []*analysis.Analyzer { 1164 seen := map[*analysis.Analyzer]struct{}{} 1165 out := make([]*analysis.Analyzer, 0, len(analyzers)) 1166 var dfs func(*analysis.Analyzer) 1167 dfs = func(a *analysis.Analyzer) { 1168 if _, ok := seen[a]; ok { 1169 return 1170 } 1171 seen[a] = struct{}{} 1172 out = append(out, a) 1173 for _, dep := range a.Requires { 1174 dfs(dep) 1175 } 1176 } 1177 for _, a := range analyzers { 1178 dfs(a) 1179 } 1180 return out 1181 } 1182 1183 // Run loads the packages specified by patterns, runs analyzers on 1184 // them and returns the results. Each result corresponds to a single 1185 // package. Results will be returned for all packages, including 1186 // dependencies. Errors specific to packages will be reported in the 1187 // respective results. 1188 // 1189 // If cfg is nil, a default config will be used. Otherwise, cfg will 1190 // be used, with the exception of the Mode field. 1191 func (r *Runner) Run(cfg *packages.Config, analyzers []*analysis.Analyzer, patterns []string) ([]Result, error) { 1192 analyzers = allAnalyzers(analyzers) 1193 registerGobTypes(analyzers) 1194 1195 r.Stats.setState(StateLoadPackageGraph) 1196 lpkgs, err := loader.Graph(r.cache, cfg, patterns...) 1197 if err != nil { 1198 return nil, err 1199 } 1200 r.Stats.setInitialPackages(len(lpkgs)) 1201 1202 if len(lpkgs) == 0 { 1203 return nil, nil 1204 } 1205 1206 var goVersion string 1207 if r.GoVersion == "module" { 1208 for _, lpkg := range lpkgs { 1209 if m := lpkg.Module; m != nil { 1210 if goVersion == "" { 1211 goVersion = m.GoVersion 1212 } else if goVersion != m.GoVersion { 1213 // Theoretically, we should only ever see a single Go 1214 // module. At least that's currently (as of Go 1.15) 1215 // true when using 'go list'. 1216 fmt.Fprintln(os.Stderr, "warning: encountered multiple modules and could not deduce targeted Go version") 1217 goVersion = "" 1218 break 1219 } 1220 } 1221 } 1222 } else { 1223 goVersion = r.GoVersion 1224 } 1225 1226 if goVersion == "" { 1227 if r.FallbackGoVersion == "" { 1228 panic("could not determine Go version of module, and fallback version hasn't been set") 1229 } 1230 goVersion = r.FallbackGoVersion 1231 } 1232 r.actualGoVersion = goVersion 1233 for _, a := range analyzers { 1234 flag := a.Flags.Lookup("go") 1235 if flag == nil { 1236 continue 1237 } 1238 if err := flag.Value.Set(goVersion); err != nil { 1239 return nil, err 1240 } 1241 } 1242 1243 r.Stats.setState(StateBuildActionGraph) 1244 all := map[*loader.PackageSpec]*packageAction{} 1245 root := &packageAction{} 1246 for _, lpkg := range lpkgs { 1247 a := newPackageActionRoot(lpkg, all) 1248 root.deps = append(root.deps, a) 1249 a.triggers = append(a.triggers, root) 1250 } 1251 root.pending = uint32(len(root.deps)) 1252 1253 queue := make(chan action) 1254 r.Stats.setTotalPackages(len(all) - 1) 1255 1256 r.Stats.setState(StateProcessing) 1257 go func() { 1258 for _, a := range all { 1259 if len(a.Deps()) == 0 { 1260 queue <- a 1261 } 1262 } 1263 }() 1264 1265 sr := newSubrunner(r, analyzers) 1266 for item := range queue { 1267 r.semaphore.Acquire() 1268 go genericHandle(item, root, queue, &r.semaphore, func(act action) error { 1269 return sr.do(act) 1270 }) 1271 } 1272 1273 r.Stats.setState(StateFinalizing) 1274 out := make([]Result, 0, len(all)) 1275 for _, item := range all { 1276 if item.Package == nil { 1277 continue 1278 } 1279 out = append(out, Result{ 1280 Package: item.Package, 1281 Config: item.cfg, 1282 Initial: !item.factsOnly, 1283 Skipped: item.skipped, 1284 Failed: item.failed, 1285 Errors: item.errors, 1286 results: item.results, 1287 testData: item.testData, 1288 }) 1289 } 1290 return out, nil 1291 }