// Package runner implements a go/analysis runner. It makes heavy use
// of on-disk caching to reduce overall memory usage and to speed up
// repeat runs.
//
// Public API
//
// A Runner maps a list of analyzers and package patterns to a list of
// results. Results provide access to diagnostics, directives, errors
// encountered, and information about packages. Results explicitly do
// not contain ASTs or type information. All position information is
// returned in the form of token.Position, not token.Pos. All work
// that requires access to the loaded representation of a package has
// to occur inside analyzers.
//
// Planning and execution
//
// Analyzing packages is split into two phases: planning and
// execution.
//
// During planning, a directed acyclic graph of package dependencies
// is computed. We materialize the full graph so that we can execute
// the graph from the bottom up, without keeping unnecessary data in
// memory during a DFS and with simplified parallel execution.
//
// During execution, leaf nodes (nodes with no outstanding
// dependencies) get executed in parallel, bounded by a semaphore
// sized according to the number of CPUs. Conceptually, this happens
// in a loop, processing new leaf nodes as they appear, until no more
// nodes are left. In the actual implementation, nodes know their
// dependents, and the last dependency of a node to be processed is
// responsible for scheduling its dependent.
//
// The graph is rooted at a synthetic root node. Upon execution of the
// root node, the algorithm terminates.
//
// Analyzing a package repeats the same planning + execution steps,
// but this time on a graph of analyzers for the package. Parallel
// execution of individual analyzers is bounded by the same semaphore
// as executing packages.
//
// Parallelism
//
// Actions are executed in parallel where the dependency graph allows.
// Overall parallelism is bounded by a semaphore, sized according to
// GOMAXPROCS. Each concurrently processed package takes up a
// token, as does each analyzer – but a package can always execute at
// least one analyzer, using the package's token.
//
// Depending on the overall shape of the graph, there may be GOMAXPROCS
// packages running a single analyzer each, a single package running
// GOMAXPROCS analyzers, or anything in between.
//
// Total memory consumption grows roughly linearly with the number of
// CPUs, while total execution time is inversely proportional to the
// number of CPUs. Overall, parallelism is affected by the shape of
// the dependency graph. A lot of inter-connected packages will see
// less parallelism than a lot of independent packages.
//
// Caching
//
// The runner caches facts, directives and diagnostics in a
// content-addressable cache that is designed after Go's own cache.
// Additionally, it makes use of Go's export data.
//
// This cache not only speeds up repeat runs, it also reduces peak
// memory usage. When we've analyzed a package, we cache the results
// and drop them from memory. When a dependent needs any of this
// information, or when analysis is complete and we wish to render the
// results, the data gets loaded from disk again.
//
// Data only exists in memory when it is immediately needed, not
// retained for possible future uses. This trades increased CPU usage
// for reduced memory usage. A single dependency may be loaded many
// times over, but it greatly reduces peak memory usage, as an
// arbitrary amount of time may pass between analyzing a dependency
// and its dependent, during which other packages will be processed.
package runner

// OPT(dh): we could reduce disk storage usage of cached data by
// compressing it, either directly at the cache layer, or by feeding
// compressed data to the cache. Of course doing so may negatively
// affect CPU usage, and there are lower hanging fruit, such as
// needing to cache less data in the first place.

// OPT(dh): right now, each package is analyzed completely
// independently. Each package loads all of its dependencies from
// export data and cached facts. If we have two packages A and B,
// which both depend on C, and which both get analyzed in parallel,
// then C will be loaded twice. This wastes CPU time and memory. It
// would be nice if we could reuse a single C for the analysis of both
// A and B.
//
// We can't reuse the actual types.Package or facts, because each
// package gets its own token.FileSet. Sharing a global FileSet has
// several drawbacks, including increased memory usage and running the
// risk of running out of FileSet address space.
//
// We could however avoid loading the same raw export data from disk
// twice, as well as deserializing gob data twice. One possible
// solution would be a duplicate-suppressing in-memory cache that
// caches data for a limited amount of time. When the same package
// needs to be loaded twice in close succession, we can reuse work,
// without holding unnecessary data in memory for an extended period
// of time.
//
// We would likely need to do extensive benchmarking to figure out how
// long to keep data around to find a sweet spot where we reduce CPU
// load without increasing memory usage.
//
// We can probably populate the cache after we've analyzed a package,
// on the assumption that it will have to be loaded again in the near
// future.

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"go/token"
	"go/types"
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"runtime"
	"sort"
	"strings"
	"sync/atomic"
	"time"

	"honnef.co/go/tools/analysis/lint"
	"honnef.co/go/tools/analysis/report"
	"honnef.co/go/tools/config"
	"honnef.co/go/tools/go/loader"
	tsync "honnef.co/go/tools/internal/sync"
	"honnef.co/go/tools/lintcmd/cache"
	"honnef.co/go/tools/unused"

	"golang.org/x/tools/go/analysis"
	"golang.org/x/tools/go/packages"
	"golang.org/x/tools/go/types/objectpath"
)

const sanityCheck = false

// Diagnostic is like go/analysis.Diagnostic, but with all token.Pos resolved to token.Position.
type Diagnostic struct {
	Position token.Position
	End      token.Position
	Category string
	Message  string

	SuggestedFixes []SuggestedFix
	Related        []RelatedInformation
}

// RelatedInformation provides additional context for a diagnostic.
type RelatedInformation struct {
	Position token.Position
	End      token.Position
	Message  string
}

type SuggestedFix struct {
	Message   string
	TextEdits []TextEdit
}

type TextEdit struct {
	Position token.Position
	End      token.Position
	NewText  []byte
}

// A Result describes the result of analyzing a single package.
//
// It holds references to cached diagnostics and directives. They can
// be loaded on demand with the Load method.
type Result struct {
	Package *loader.PackageSpec
	Config  config.Config
	Initial bool
	Skipped bool

	Failed bool
	Errors []error
	// Action results, path to file
	results string
	// Results relevant to testing, only set when test mode is enabled, path to file
	testData string
}

type SerializedDirective struct {
	Command   string
	Arguments []string
	// The position of the comment
	DirectivePosition token.Position
	// The position of the node that the comment is attached to
	NodePosition token.Position
}

func serializeDirective(dir lint.Directive, fset *token.FileSet) SerializedDirective {
	return SerializedDirective{
		Command:           dir.Command,
		Arguments:         dir.Arguments,
		DirectivePosition: report.DisplayPosition(fset, dir.Directive.Pos()),
		NodePosition:      report.DisplayPosition(fset, dir.Node.Pos()),
	}
}

type ResultData struct {
	Directives  []SerializedDirective
	Diagnostics []Diagnostic
	Unused      unused.SerializedResult
}

func (r Result) Load() (ResultData, error) {
	if r.Failed {
		panic("Load called on failed Result")
	}
	if r.results == "" {
		// this package was only a dependency
		return ResultData{}, nil
	}
	f, err := os.Open(r.results)
	if err != nil {
		return ResultData{}, fmt.Errorf("failed loading result: %w", err)
	}
	defer f.Close()
	var out ResultData
	err = gob.NewDecoder(f).Decode(&out)
	return out, err
}

type Want struct {
	Position token.Position
	Comment  string
}

// TestData contains extra information about analysis runs that is only available in test mode.
type TestData struct {
	// Wants contains a list of '// want' comments extracted from Go files.
	// These comments are used in unit tests.
	Wants []Want
	// Facts contains facts produced by analyzers for a package.
	// Unlike vetx, this list only contains facts specific to this package,
	// not all facts for the transitive closure of dependencies.
	Facts []TestFact
}

// LoadTest returns data relevant to testing.
// It should only be called if Runner.TestMode was set to true.
func (r Result) LoadTest() (TestData, error) {
	if r.Failed {
		panic("Load called on failed Result")
	}
	if r.results == "" {
		// this package was only a dependency
		return TestData{}, nil
	}
	f, err := os.Open(r.testData)
	if err != nil {
		return TestData{}, fmt.Errorf("failed loading test data: %w", err)
	}
	defer f.Close()
	var out TestData
	err = gob.NewDecoder(f).Decode(&out)
	return out, err
}

type action interface {
	Deps() []action
	Triggers() []action
	DecrementPending() bool
	MarkFailed()
	IsFailed() bool
	AddError(error)
}

type baseAction struct {
	// Action description

	deps     []action
	triggers []action
	pending  uint32

	// Action results

	// failed is set to true if the action couldn't be processed. This
	// may either be due to an error specific to this action, in
	// which case the errors field will be populated, or due to a
	// dependency being marked as failed, in which case errors will be
	// empty.
	failed bool
	errors []error
}

func (act *baseAction) Deps() []action     { return act.deps }
func (act *baseAction) Triggers() []action { return act.triggers }
func (act *baseAction) DecrementPending() bool {
	return atomic.AddUint32(&act.pending, ^uint32(0)) == 0
}
func (act *baseAction) MarkFailed()        { act.failed = true }
func (act *baseAction) IsFailed() bool     { return act.failed }
func (act *baseAction) AddError(err error) { act.errors = append(act.errors, err) }

// packageAction describes the act of loading a package, fully
// analyzing it, and storing the results.
type packageAction struct {
	baseAction

	// Action description
	Package   *loader.PackageSpec
	factsOnly bool
	hash      cache.ActionID

	// Action results
	cfg      config.Config
	vetx     string
	results  string
	testData string
	skipped  bool
}

func (act *packageAction) String() string {
	return fmt.Sprintf("packageAction(%s)", act.Package)
}

type objectFact struct {
	fact analysis.Fact
	// TODO(dh): why do we store the objectpath when producing the
	// fact? Is it just for the sanity checking, which compares the
	// stored path with a path recomputed from objectFactKey.Obj?
	path objectpath.Path
}

type objectFactKey struct {
	Obj  types.Object
	Type reflect.Type
}

type packageFactKey struct {
	Pkg  *types.Package
	Type reflect.Type
}

type gobFact struct {
	PkgPath string
	ObjPath string
	Fact    analysis.Fact
}

// TestFact is a serialization of facts that is specific to the test mode.
type TestFact struct {
	ObjectName string
	Position   token.Position
	FactString string
	Analyzer   string
}

// analyzerAction describes the act of analyzing a package with a
// single analyzer.
type analyzerAction struct {
	baseAction

	// Action description

	Analyzer *analysis.Analyzer

	// Action results

	// We can store actual results here without worrying about memory
	// consumption because analyzer actions get garbage collected once
	// a package has been fully analyzed.
	Result       interface{}
	Diagnostics  []Diagnostic
	ObjectFacts  map[objectFactKey]objectFact
	PackageFacts map[packageFactKey]analysis.Fact
	Pass         *analysis.Pass
}

func (act *analyzerAction) String() string {
	return fmt.Sprintf("analyzerAction(%s)", act.Analyzer)
}

// A Runner executes analyzers on packages.
type Runner struct {
	Stats     Stats
	GoVersion string
	// if GoVersion == "module", and we couldn't determine the
	// module's Go version, use this as the fallback
	FallbackGoVersion string
	// If set to true, Runner will populate results with data relevant to testing analyzers
	TestMode bool

	// GoVersion might be "module"; actualGoVersion contains the resolved version
	actualGoVersion string

	// Config that gets merged with per-package configs
	cfg       config.Config
	cache     *cache.Cache
	semaphore tsync.Semaphore
}

type subrunner struct {
	*Runner
	analyzers     []*analysis.Analyzer
	factAnalyzers []*analysis.Analyzer
	analyzerNames string
	cache         *cache.Cache
}

// New returns a new Runner.
func New(cfg config.Config, c *cache.Cache) (*Runner, error) {
	return &Runner{
		cfg:       cfg,
		cache:     c,
		semaphore: tsync.NewSemaphore(runtime.GOMAXPROCS(0)),
	}, nil
}

func newSubrunner(r *Runner, analyzers []*analysis.Analyzer) *subrunner {
	analyzerNames := make([]string, len(analyzers))
	for i, a := range analyzers {
		analyzerNames[i] = a.Name
	}
	sort.Strings(analyzerNames)

	var factAnalyzers []*analysis.Analyzer
	for _, a := range analyzers {
		if len(a.FactTypes) > 0 {
			factAnalyzers = append(factAnalyzers, a)
		}
	}
	return &subrunner{
		Runner:        r,
		analyzers:     analyzers,
		factAnalyzers: factAnalyzers,
		analyzerNames: strings.Join(analyzerNames, ","),
		cache:         r.cache,
	}
}

func newPackageActionRoot(pkg *loader.PackageSpec, cache map[*loader.PackageSpec]*packageAction) *packageAction {
	a := newPackageAction(pkg, cache)
	a.factsOnly = false
	return a
}

func newPackageAction(pkg *loader.PackageSpec, cache map[*loader.PackageSpec]*packageAction) *packageAction {
	if a, ok := cache[pkg]; ok {
		return a
	}

	a := &packageAction{
		Package:   pkg,
		factsOnly: true, // will be overwritten by any call to Action
	}
	cache[pkg] = a

	if len(pkg.Errors) > 0 {
		a.errors = make([]error, len(pkg.Errors))
		for i, err := range pkg.Errors {
			a.errors[i] = err
		}
		a.failed = true

		// We don't need to process our imports if this package is
		// already broken.
		return a
	}

	a.deps = make([]action, 0, len(pkg.Imports))
	for _, dep := range pkg.Imports {
		depa := newPackageAction(dep, cache)
		depa.triggers = append(depa.triggers, a)
		a.deps = append(a.deps, depa)

		if depa.failed {
			a.failed = true
		}
	}
	// sort dependencies because the list of dependencies is part of
	// the cache key
	sort.Slice(a.deps, func(i, j int) bool {
		return a.deps[i].(*packageAction).Package.ID < a.deps[j].(*packageAction).Package.ID
	})

	a.pending = uint32(len(a.deps))

	return a
}

func newAnalyzerAction(an *analysis.Analyzer, cache map[*analysis.Analyzer]*analyzerAction) *analyzerAction {
	if a, ok := cache[an]; ok {
		return a
	}

	a := &analyzerAction{
		Analyzer:     an,
		ObjectFacts:  map[objectFactKey]objectFact{},
		PackageFacts: map[packageFactKey]analysis.Fact{},
	}
	cache[an] = a
	for _, dep := range an.Requires {
		depa := newAnalyzerAction(dep, cache)
		depa.triggers = append(depa.triggers, a)
		a.deps = append(a.deps, depa)
	}
	a.pending = uint32(len(a.deps))
	return a
}

func getCachedFiles(cache *cache.Cache, ids []cache.ActionID, out []*string) error {
	for i, id := range ids {
		var err error
		*out[i], _, err = cache.GetFile(id)
		if err != nil {
			return err
		}
	}
	return nil
}

func (r *subrunner) do(act action) error {
	a := act.(*packageAction)
	defer func() {
		r.Stats.finishPackage()
		if !a.factsOnly {
			r.Stats.finishInitialPackage()
		}
	}()

	// compute hash of action
	a.cfg = a.Package.Config.Merge(r.cfg)
	h := r.cache.NewHash("staticcheck " + a.Package.PkgPath)

	// Note that we do not filter the list of analyzers by the
	// package's configuration. We don't allow configuration to
	// accidentally break dependencies between analyzers, and it's
	// easier to always run all checks and filter the output. This
	// also makes cached data more reusable.

	// OPT(dh): not all changes in configuration invalidate cached
	// data. specifically, when a.factsOnly == true, we only care
	// about checks that produce facts, and settings that affect those
	// checks.

	// Config used for constructing the hash; this config doesn't have
	// Checks populated, because we always run all checks.
	//
	// This even works for users who add custom checks, because we include the binary's hash.
	hashCfg := a.cfg
	hashCfg.Checks = nil
	// note that we don't hash staticcheck's version; it is set as the
	// salt by a package main.
	fmt.Fprintf(h, "cfg %#v\n", hashCfg)
	fmt.Fprintf(h, "pkg %x\n", a.Package.Hash)
	fmt.Fprintf(h, "analyzers %s\n", r.analyzerNames)
	fmt.Fprintf(h, "go %s\n", r.actualGoVersion)

	// OPT(dh): do we actually need to hash vetx? can we not assume
	// that for identical inputs, staticcheck will produce identical
	// vetx?
	for _, dep := range a.deps {
		dep := dep.(*packageAction)
		vetxHash, err := cache.FileHash(dep.vetx)
		if err != nil {
			return fmt.Errorf("failed computing hash: %w", err)
		}
		fmt.Fprintf(h, "vetout %q %x\n", dep.Package.PkgPath, vetxHash)
	}
	a.hash = cache.ActionID(h.Sum())

	// try to fetch hashed data
	ids := make([]cache.ActionID, 0, 2)
	ids = append(ids, cache.Subkey(a.hash, "vetx"))
	if !a.factsOnly {
		ids = append(ids, cache.Subkey(a.hash, "results"))
		if r.TestMode {
			ids = append(ids, cache.Subkey(a.hash, "testdata"))
		}
	}
	if err := getCachedFiles(r.cache, ids, []*string{&a.vetx, &a.results, &a.testData}); err != nil {
		result, err := r.doUncached(a)
		if err != nil {
			return err
		}
		if a.failed {
			return nil
		}

		a.skipped = result.skipped

		// OPT(dh) instead of collecting all object facts and encoding
		// them after analysis finishes, we could encode them as we
		// go. however, that would require some locking.
		//
		// OPT(dh): We could sort gobFacts for more consistent output,
		// but it doesn't matter. The hash of a package includes all
		// of its files, so whether the vetx hash changes or not, a
		// change to a package requires re-analyzing all dependents,
		// even if the vetx data stayed the same. See also the note at
		// the top of loader/hash.go.

		tf := &bytes.Buffer{}
		enc := gob.NewEncoder(tf)
		for _, gf := range result.facts {
			if err := enc.Encode(gf); err != nil {
				return fmt.Errorf("failed gob encoding data: %w", err)
			}
		}

		a.vetx, err = r.writeCacheReader(a, "vetx", bytes.NewReader(tf.Bytes()))
		if err != nil {
			return err
		}

		if a.factsOnly {
			return nil
		}

		var out ResultData
		out.Directives = make([]SerializedDirective, len(result.dirs))
		for i, dir := range result.dirs {
			out.Directives[i] = serializeDirective(dir, result.lpkg.Fset)
		}

		out.Diagnostics = result.diags
		out.Unused = result.unused
		a.results, err = r.writeCacheGob(a, "results", out)
		if err != nil {
			return err
		}

		if r.TestMode {
			out := TestData{
				Wants: result.wants,
				Facts: result.testFacts,
			}
			a.testData, err = r.writeCacheGob(a, "testdata", out)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// ActiveWorkers returns the number of currently running workers.
func (r *Runner) ActiveWorkers() int {
	return r.semaphore.Len()
}

// TotalWorkers returns the maximum number of possible workers.
func (r *Runner) TotalWorkers() int {
	return r.semaphore.Cap()
}

func (r *Runner) writeCacheReader(a *packageAction, kind string, rs io.ReadSeeker) (string, error) {
	h := cache.Subkey(a.hash, kind)
	out, _, err := r.cache.Put(h, rs)
	if err != nil {
		return "", fmt.Errorf("failed caching data: %w", err)
	}
	return r.cache.OutputFile(out), nil
}

func (r *Runner) writeCacheGob(a *packageAction, kind string, data interface{}) (string, error) {
	f, err := ioutil.TempFile("", "staticcheck")
	if err != nil {
		return "", err
	}
	defer f.Close()
	os.Remove(f.Name())
	if err := gob.NewEncoder(f).Encode(data); err != nil {
		return "", fmt.Errorf("failed gob encoding data: %w", err)
	}
	if _, err := f.Seek(0, io.SeekStart); err != nil {
		return "", err
	}
	return r.writeCacheReader(a, kind, f)
}

type packageActionResult struct {
	facts   []gobFact
	diags   []Diagnostic
	unused  unused.SerializedResult
	dirs    []lint.Directive
	lpkg    *loader.Package
	skipped bool

	// Only set when using test mode
	testFacts []TestFact
	wants     []Want
}

func (r *subrunner) doUncached(a *packageAction) (packageActionResult, error) {
	// OPT(dh): for a -> b; c -> b; if both a and b are being
	// processed concurrently, we shouldn't load b's export data
	// twice.

	pkg, _, err := loader.Load(a.Package)
	if err != nil {
		return packageActionResult{}, err
	}

	if len(pkg.Errors) > 0 {
		// this handles errors that occurred during type-checking the
		// package in loader.Load
		for _, err := range pkg.Errors {
			a.errors = append(a.errors, err)
		}
		a.failed = true
		return packageActionResult{}, nil
	}

	if len(pkg.Syntax) == 0 && pkg.PkgPath != "unsafe" {
		return packageActionResult{lpkg: pkg, skipped: true}, nil
	}

	// OPT(dh): instead of parsing directives twice (twice because
	// U1000 depends on the facts.Directives analyzer), reuse the
	// existing result
	var dirs []lint.Directive
	if !a.factsOnly {
		dirs = lint.ParseDirectives(pkg.Syntax, pkg.Fset)
	}
	res, err := r.runAnalyzers(a, pkg)

	var wants []Want
	if r.TestMode {
		// Extract 'want' comments from parsed Go files.
		for _, f := range pkg.Syntax {
			for _, cgroup := range f.Comments {
				for _, c := range cgroup.List {

					text := strings.TrimPrefix(c.Text, "//")
					if text == c.Text { // not a //-comment.
						text = strings.TrimPrefix(text, "/*")
						text = strings.TrimSuffix(text, "*/")
					}

					// Hack: treat a comment of the form "//...// want..."
					// or "/*...// want... */
					// as if it starts at 'want'.
					// This allows us to add comments on comments,
					// as required when testing the buildtag analyzer.
					if i := strings.Index(text, "// want"); i >= 0 {
						text = text[i+len("// "):]
					}

					posn := pkg.Fset.Position(c.Pos())
					wants = append(wants, Want{Position: posn, Comment: text})
				}
			}
		}

		// TODO(dh): add support for non-Go files
	}

	return packageActionResult{
		facts:     res.facts,
		testFacts: res.testFacts,
		wants:     wants,
		diags:     res.diagnostics,
		unused:    res.unused,
		dirs:      dirs,
		lpkg:      pkg,
	}, err
}

func pkgPaths(root *types.Package) map[string]*types.Package {
	out := map[string]*types.Package{}
	var dfs func(*types.Package)
	dfs = func(pkg *types.Package) {
		if _, ok := out[pkg.Path()]; ok {
			return
		}
		out[pkg.Path()] = pkg
		for _, imp := range pkg.Imports() {
			dfs(imp)
		}
	}
	dfs(root)
	return out
}

func (r *Runner) loadFacts(root *types.Package, dep *packageAction, objFacts map[objectFactKey]objectFact, pkgFacts map[packageFactKey]analysis.Fact) error {
	// Load facts of all imported packages
	vetx, err := os.Open(dep.vetx)
	if err != nil {
		return fmt.Errorf("failed loading cached facts: %w", err)
	}
	defer vetx.Close()

	pathToPkg := pkgPaths(root)
	dec := gob.NewDecoder(vetx)
	for {
		var gf gobFact
		err := dec.Decode(&gf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return fmt.Errorf("failed loading cached facts: %w", err)
		}

		pkg, ok := pathToPkg[gf.PkgPath]
		if !ok {
			continue
		}
		if gf.ObjPath == "" {
			pkgFacts[packageFactKey{
				Pkg:  pkg,
				Type: reflect.TypeOf(gf.Fact),
			}] = gf.Fact
		} else {
			obj, err := objectpath.Object(pkg, objectpath.Path(gf.ObjPath))
			if err != nil {
				continue
			}
			objFacts[objectFactKey{
				Obj:  obj,
				Type: reflect.TypeOf(gf.Fact),
			}] = objectFact{gf.Fact, objectpath.Path(gf.ObjPath)}
		}
	}
	return nil
}

func genericHandle(a action, root action, queue chan action, sem *tsync.Semaphore, exec func(a action) error) {
	if a == root {
		close(queue)
		if sem != nil {
			sem.Release()
		}
		return
	}
	if !a.IsFailed() {
		// the action may have already been marked as failed during
		// construction of the action graph, for example because of
		// unresolved imports.

		for _, dep := range a.Deps() {
			if dep.IsFailed() {
				// One of our dependencies failed, so mark this package as
				// failed and bail. We don't need to record an error for
				// this package, the relevant error will have been
				// reported by the first package in the chain that failed.
				a.MarkFailed()
				break
			}
		}
	}

	if !a.IsFailed() {
		if err := exec(a); err != nil {
			a.MarkFailed()
			a.AddError(err)
		}
	}
	if sem != nil {
		sem.Release()
	}

	for _, t := range a.Triggers() {
		if t.DecrementPending() {
			queue <- t
		}
	}
}

type analyzerRunner struct {
	pkg *loader.Package
	// object facts of our dependencies; may contain facts of
	// analyzers other than the current one
	depObjFacts map[objectFactKey]objectFact
	// package facts of our dependencies; may contain facts of
	// analyzers other than the current one
	depPkgFacts map[packageFactKey]analysis.Fact
	factsOnly   bool

	stats *Stats
}

func (ar *analyzerRunner) do(act action) error {
	a := act.(*analyzerAction)
	results := map[*analysis.Analyzer]interface{}{}
	// TODO(dh): does this have to be recursive?
	for _, dep := range a.deps {
		dep := dep.(*analyzerAction)
		results[dep.Analyzer] = dep.Result
	}
	// OPT(dh): cache factTypes, it is the same for all packages for a given analyzer
	//
	// OPT(dh): do we need the factTypes map? most analyzers have 0-1
	// fact types. iterating over the slice is probably faster than
	// indexing a map.
	factTypes := map[reflect.Type]struct{}{}
	for _, typ := range a.Analyzer.FactTypes {
		factTypes[reflect.TypeOf(typ)] = struct{}{}
	}
	filterFactType := func(typ reflect.Type) bool {
		_, ok := factTypes[typ]
		return ok
	}
	a.Pass = &analysis.Pass{
		Analyzer:   a.Analyzer,
		Fset:       ar.pkg.Fset,
		Files:      ar.pkg.Syntax,
		OtherFiles: ar.pkg.OtherFiles,
		Pkg:        ar.pkg.Types,
		TypesInfo:  ar.pkg.TypesInfo,
		TypesSizes: ar.pkg.TypesSizes,
		Report: func(diag analysis.Diagnostic) {
			if !ar.factsOnly {
				if diag.Category == "" {
					diag.Category = a.Analyzer.Name
				}
				d := Diagnostic{
					Position: report.DisplayPosition(ar.pkg.Fset, diag.Pos),
					End:      report.DisplayPosition(ar.pkg.Fset, diag.End),
					Category: diag.Category,
					Message:  diag.Message,
				}
				for _, sugg := range diag.SuggestedFixes {
					s := SuggestedFix{
						Message: sugg.Message,
					}
					for _, edit := range sugg.TextEdits {
						s.TextEdits = append(s.TextEdits, TextEdit{
							Position: report.DisplayPosition(ar.pkg.Fset, edit.Pos),
							End:      report.DisplayPosition(ar.pkg.Fset, edit.End),
							NewText:  edit.NewText,
						})
					}
					d.SuggestedFixes = append(d.SuggestedFixes, s)
				}
				for _, rel := range diag.Related {
					d.Related = append(d.Related, RelatedInformation{
						Position: report.DisplayPosition(ar.pkg.Fset, rel.Pos),
						End:      report.DisplayPosition(ar.pkg.Fset, rel.End),
						Message:  rel.Message,
					})
				}
				a.Diagnostics = append(a.Diagnostics, d)
			}
		},
		ResultOf: results,
		ImportObjectFact: func(obj types.Object, fact analysis.Fact) bool {
			key := objectFactKey{
				Obj:  obj,
				Type: reflect.TypeOf(fact),
			}
			if f, ok := ar.depObjFacts[key]; ok {
				reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f.fact).Elem())
				return true
			} else if f, ok := a.ObjectFacts[key]; ok {
				reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f.fact).Elem())
				return true
			}
			return false
		},
		ImportPackageFact: func(pkg *types.Package, fact analysis.Fact) bool {
			key := packageFactKey{
				Pkg:  pkg,
				Type: reflect.TypeOf(fact),
			}
			if f, ok := ar.depPkgFacts[key]; ok {
				reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f).Elem())
				return true
			} else if f, ok := a.PackageFacts[key]; ok {
				reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f).Elem())
				return true
			}
			return false
		},
		ExportObjectFact: func(obj types.Object, fact analysis.Fact) {
			key := objectFactKey{
				Obj:  obj,
				Type: reflect.TypeOf(fact),
			}
			path, _ := objectpath.For(obj)
			a.ObjectFacts[key] = objectFact{fact, path}
		},
		ExportPackageFact: func(fact analysis.Fact) {
			key := packageFactKey{
				Pkg:  ar.pkg.Types,
				Type: reflect.TypeOf(fact),
			}
			a.PackageFacts[key] = fact
		},
		AllPackageFacts: func() []analysis.PackageFact {
			out := make([]analysis.PackageFact, 0, len(ar.depPkgFacts)+len(a.PackageFacts))
			for key, fact := range ar.depPkgFacts {
				out = append(out, analysis.PackageFact{
					Package: key.Pkg,
					Fact:    fact,
				})
			}
			for key, fact := range a.PackageFacts {
				out = append(out, analysis.PackageFact{
					Package: key.Pkg,
					Fact:    fact,
				})
			}
			return out
		},
		AllObjectFacts: func() []analysis.ObjectFact {
			out := make([]analysis.ObjectFact, 0, len(ar.depObjFacts)+len(a.ObjectFacts))
			for key, fact := range ar.depObjFacts {
				if filterFactType(key.Type) {
					out = append(out, analysis.ObjectFact{
						Object: key.Obj,
						Fact:   fact.fact,
					})
				}
			}
			for key, fact := range a.ObjectFacts {
				if filterFactType(key.Type) {
					out = append(out, analysis.ObjectFact{
						Object: key.Obj,
						Fact:   fact.fact,
					})
				}
			}
			return out
		},
	}

	t := time.Now()
	res, err := a.Analyzer.Run(a.Pass)
	ar.stats.measureAnalyzer(a.Analyzer, ar.pkg.PackageSpec, time.Since(t))
	if err != nil {
		return err
	}
	a.Result = res
	return nil
}

type analysisResult struct {
	facts       []gobFact
	diagnostics []Diagnostic
	unused      unused.SerializedResult

	// Only set when using test mode
	testFacts []TestFact
}

func (r *subrunner) runAnalyzers(pkgAct *packageAction, pkg *loader.Package) (analysisResult, error) {
	depObjFacts := map[objectFactKey]objectFact{}
	depPkgFacts := map[packageFactKey]analysis.Fact{}

	for _, dep := range pkgAct.deps {
		if err := r.loadFacts(pkg.Types, dep.(*packageAction), depObjFacts, depPkgFacts); err != nil {
			return analysisResult{}, err
		}
	}

	root := &analyzerAction{}
	var analyzers []*analysis.Analyzer
	if pkgAct.factsOnly {
		// When analyzing non-initial packages, we only care about
		// analyzers that produce facts.
		analyzers = r.factAnalyzers
	} else {
		analyzers = r.analyzers
	}

	all := map[*analysis.Analyzer]*analyzerAction{}
	for _, a := range analyzers {
		a := newAnalyzerAction(a, all)
		root.deps = append(root.deps, a)
		a.triggers = append(a.triggers, root)
	}
	root.pending = uint32(len(root.deps))

	ar := &analyzerRunner{
		pkg:         pkg,
		factsOnly:   pkgAct.factsOnly,
		depObjFacts: depObjFacts,
		depPkgFacts: depPkgFacts,
		stats:       &r.Stats,
	}
	queue := make(chan action, len(all))
	for _, a := range all {
		if len(a.Deps()) == 0 {
			queue <- a
		}
	}

	// Don't hang if there are no analyzers to run; for example
	// because we are analyzing a dependency but have no analyzers
	// that produce facts.
	if len(all) == 0 {
		close(queue)
	}
	for item := range queue {
		b := r.semaphore.AcquireMaybe()
		if b {
			go genericHandle(item, root, queue, &r.semaphore, ar.do)
		} else {
			// the semaphore is exhausted; run the analysis under the
			// token we've acquired for analyzing the package.
			genericHandle(item, root, queue, nil, ar.do)
		}
	}

	var unusedResult unused.SerializedResult
	for _, a := range all {
		if a != root && a.Analyzer.Name == "U1000" && !a.failed {
			// TODO(dh): figure out a clean abstraction, instead of
			// special-casing U1000.
			unusedResult = unused.Serialize(a.Pass, a.Result.(unused.Result), pkg.Fset)
		}

		for key, fact := range a.ObjectFacts {
			depObjFacts[key] = fact
		}
		for key, fact := range a.PackageFacts {
			depPkgFacts[key] = fact
		}
	}

	// OPT(dh): cull objects not reachable via the exported closure
	var testFacts []TestFact
	gobFacts := make([]gobFact, 0, len(depObjFacts)+len(depPkgFacts))
	for key, fact := range depObjFacts {
		if fact.path == "" {
			continue
		}
		if sanityCheck {
			p, _ := objectpath.For(key.Obj)
			if p != fact.path {
				panic(fmt.Sprintf("got different object paths for %v. old: %q new: %q", key.Obj, fact.path, p))
			}
		}
		gf := gobFact{
			PkgPath: key.Obj.Pkg().Path(),
			ObjPath: string(fact.path),
			Fact:    fact.fact,
		}
		gobFacts = append(gobFacts, gf)
	}

	for key, fact := range depPkgFacts {
		gf := gobFact{
			PkgPath: key.Pkg.Path(),
			Fact:    fact,
		}
		gobFacts = append(gobFacts, gf)
	}

	if r.TestMode {
		for _, a := range all {
			for key, fact := range a.ObjectFacts {
				tgf := TestFact{
					ObjectName: key.Obj.Name(),
					Position:   pkg.Fset.Position(key.Obj.Pos()),
					FactString: fmt.Sprint(fact.fact),
					Analyzer:   a.Analyzer.Name,
				}
				testFacts = append(testFacts, tgf)
			}

			for _, fact := range a.PackageFacts {
				tgf := TestFact{
					ObjectName: "",
					Position:   pkg.Fset.Position(pkg.Syntax[0].Pos()),
					FactString: fmt.Sprint(fact),
					Analyzer:   a.Analyzer.Name,
				}
				testFacts = append(testFacts, tgf)
			}
		}
	}

	var diags []Diagnostic
	for _, a := range root.deps {
		a := a.(*analyzerAction)
		diags = append(diags, a.Diagnostics...)
	}
	return analysisResult{
		facts:       gobFacts,
		testFacts:   testFacts,
		diagnostics: diags,
		unused:      unusedResult,
	}, nil
}

func registerGobTypes(analyzers []*analysis.Analyzer) {
	for _, a := range analyzers {
		for _, typ := range a.FactTypes {
			// FIXME(dh): use RegisterName so we can work around collisions
			// in names. For pointer-types, gob incorrectly qualifies
			// type names with the package name, not the import path.
			gob.Register(typ)
		}
	}
}

func allAnalyzers(analyzers []*analysis.Analyzer) []*analysis.Analyzer {
	seen := map[*analysis.Analyzer]struct{}{}
	out := make([]*analysis.Analyzer, 0, len(analyzers))
	var dfs func(*analysis.Analyzer)
	dfs = func(a *analysis.Analyzer) {
		if _, ok := seen[a]; ok {
			return
		}
		seen[a] = struct{}{}
		out = append(out, a)
		for _, dep := range a.Requires {
			dfs(dep)
		}
	}
	for _, a := range analyzers {
		dfs(a)
	}
	return out
}

// Run loads the packages specified by patterns, runs analyzers on
// them and returns the results. Each result corresponds to a single
// package. Results will be returned for all packages, including
// dependencies. Errors specific to packages will be reported in the
// respective results.
//
// If cfg is nil, a default config will be used. Otherwise, cfg will
// be used, with the exception of the Mode field.
func (r *Runner) Run(cfg *packages.Config, analyzers []*analysis.Analyzer, patterns []string) ([]Result, error) {
	analyzers = allAnalyzers(analyzers)
	registerGobTypes(analyzers)

	r.Stats.setState(StateLoadPackageGraph)
	lpkgs, err := loader.Graph(r.cache, cfg, patterns...)
	if err != nil {
		return nil, err
	}
	r.Stats.setInitialPackages(len(lpkgs))

	if len(lpkgs) == 0 {
		return nil, nil
	}

	var goVersion string
	if r.GoVersion == "module" {
		for _, lpkg := range lpkgs {
			if m := lpkg.Module; m != nil {
				if goVersion == "" {
					goVersion = m.GoVersion
				} else if goVersion != m.GoVersion {
					// Theoretically, we should only ever see a single Go
					// module. At least that's currently (as of Go 1.15)
					// true when using 'go list'.
					fmt.Fprintln(os.Stderr, "warning: encountered multiple modules and could not deduce targeted Go version")
					goVersion = ""
					break
				}
			}
		}
	} else {
		goVersion = r.GoVersion
	}

	if goVersion == "" {
		if r.FallbackGoVersion == "" {
			panic("could not determine Go version of module, and fallback version hasn't been set")
		}
		goVersion = r.FallbackGoVersion
	}
	r.actualGoVersion = goVersion
	for _, a := range analyzers {
		flag := a.Flags.Lookup("go")
		if flag == nil {
			continue
		}
		if err := flag.Value.Set(goVersion); err != nil {
			return nil, err
		}
	}

	r.Stats.setState(StateBuildActionGraph)
	all := map[*loader.PackageSpec]*packageAction{}
	root := &packageAction{}
	for _, lpkg := range lpkgs {
		a := newPackageActionRoot(lpkg, all)
		root.deps = append(root.deps, a)
		a.triggers = append(a.triggers, root)
	}
	root.pending = uint32(len(root.deps))

	queue := make(chan action)
	r.Stats.setTotalPackages(len(all) - 1)

	r.Stats.setState(StateProcessing)
	go func() {
		for _, a := range all {
			if len(a.Deps()) == 0 {
				queue <- a
			}
		}
	}()

	sr := newSubrunner(r, analyzers)
	for item := range queue {
		r.semaphore.Acquire()
		go genericHandle(item, root, queue, &r.semaphore, func(act action) error {
			return sr.do(act)
		})
	}

	r.Stats.setState(StateFinalizing)
	out := make([]Result, 0, len(all))
	for _, item := range all {
		if item.Package == nil {
			continue
		}
		out = append(out, Result{
			Package:  item.Package,
			Config:   item.cfg,
			Initial:  !item.factsOnly,
			Skipped:  item.skipped,
			Failed:   item.failed,
			Errors:   item.errors,
			results:  item.results,
			testData: item.testData,
		})
	}
	return out, nil
}
