src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

runner.go (34628B)


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