| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330 |
- // 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
- }
|