--- /dev/null
+package lint
+
+/*
+Package loading
+
+Conceptually, package loading in the runner can be imagined as a
+graph-shaped work list. We iteratively pop off leaf nodes (packages
+that have no unloaded dependencies) and load data from export data,
+our cache, or source.
+
+Specifically, non-initial packages are loaded from export data and the
+fact cache if possible, otherwise from source. Initial packages are
+loaded from export data, the fact cache and the (problems, ignores,
+config) cache if possible, otherwise from source.
+
+The appeal of this approach is that it is both simple to implement and
+easily parallelizable. Each leaf node can be processed independently,
+and new leaf nodes appear as their dependencies are being processed.
+
+The downside of this approach, however, is that we're doing more work
+than necessary. Imagine an initial package A, which has the following
+dependency chain: A->B->C->D – in the current implementation, we will
+load all 4 packages. However, if package A can be loaded fully from
+cached information, then none of its dependencies are necessary, and
+we could avoid loading them.
+
+
+Parallelism
+
+Runner implements parallel processing of packages by spawning one
+goroutine per package in the dependency graph, without any semaphores.
+Each goroutine initially waits on the completion of all of its
+dependencies, thus establishing correct order of processing. Once all
+dependencies finish processing, the goroutine will load the package
+from export data or source – this loading is guarded by a semaphore,
+sized according to the number of CPU cores. This way, we only have as
+many packages occupying memory and CPU resources as there are actual
+cores to process them.
+
+This combination of unbounded goroutines but bounded package loading
+means that if we have many parallel, independent subgraphs, they will
+all execute in parallel, while not wasting resources for long linear
+chains or trying to process more subgraphs in parallel than the system
+can handle.
+
+
+Caching
+
+We make use of several caches. These caches are Go's export data, our
+facts cache, and our (problems, ignores, config) cache.
+
+Initial packages will either be loaded from a combination of all three
+caches, or from source. Non-initial packages will either be loaded
+from a combination of export data and facts cache, or from source.
+
+The facts cache is separate from the (problems, ignores, config) cache
+because when we process non-initial packages, we generate facts, but
+we discard problems and ignores.
+
+The facts cache is keyed by (package, analyzer), whereas the
+(problems, ignores, config) cache is keyed by (package, list of
+analyzes). The difference between the two exists because there are
+only a handful of analyses that produce facts, but hundreds of
+analyses that don't. Creating one cache entry per fact-generating
+analysis is feasible, creating one cache entry per normal analysis has
+significant performance and storage overheads.
+
+The downside of keying by the list of analyzes is, naturally, that a
+change in list of analyzes changes the cache key. `staticcheck -checks
+A` and `staticcheck -checks A,B` will therefore need their own cache
+entries and not reuse each other's work. This problem does not affect
+the facts cache.
+
+*/
+
+import (
+ "bytes"
+ "encoding/gob"
+ "encoding/hex"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "reflect"
+ "regexp"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/packages"
+ "golang.org/x/tools/go/types/objectpath"
+ "honnef.co/go/tools/config"
+ "honnef.co/go/tools/facts"
+ "honnef.co/go/tools/internal/cache"
+ "honnef.co/go/tools/loader"
+)
+
+func init() {
+ gob.Register(&FileIgnore{})
+ gob.Register(&LineIgnore{})
+}
+
+// If enabled, abuse of the go/analysis API will lead to panics
+const sanityCheck = true
+
+// OPT(dh): for a dependency tree A->B->C->D, if we have cached data
+// for B, there should be no need to load C and D individually. Go's
+// export data for B contains all the data we need on types, and our
+// fact cache could store the union of B, C and D in B.
+//
+// This may change unused's behavior, however, as it may observe fewer
+// interfaces from transitive dependencies.
+
+// OPT(dh): every single package will have the same value for
+// canClearTypes. We could move the Package.decUse method to runner to
+// eliminate this field. This is probably not worth it, though. There
+// are only thousands of packages, so the field only takes up
+// kilobytes of memory.
+
+// OPT(dh): do we really need the Package.gen field? it's based
+// trivially on pkg.results and merely caches the result of a type
+// assertion. How often do we actually use the field?
+
+type Package struct {
+ // dependents is initially set to 1 plus the number of packages
+ // that directly import this package. It is atomically decreased
+ // by 1 every time a dependent has been processed or when the
+ // package itself has been processed. Once the value reaches zero,
+ // the package is no longer needed.
+ dependents uint64
+
+ *packages.Package
+ Imports []*Package
+ initial bool
+ // fromSource is set to true for packages that have been loaded
+ // from source. This is the case for initial packages, packages
+ // with missing export data, and packages with no cached facts.
+ fromSource bool
+ // hash stores the package hash, as computed by packageHash
+ hash string
+ actionID cache.ActionID
+ done chan struct{}
+
+ resultsMu sync.Mutex
+ // results maps analyzer IDs to analyzer results. it is
+ // implemented as a deduplicating concurrent cache.
+ results []*result
+
+ cfg *config.Config
+ // gen maps file names to the code generator that created them
+ gen map[string]facts.Generator
+ problems []Problem
+ ignores []Ignore
+ errs []error
+
+ // these slices are indexed by analysis
+ facts []map[types.Object][]analysis.Fact
+ pkgFacts [][]analysis.Fact
+
+ // canClearTypes is set to true if we can discard type
+ // information after the package and its dependents have been
+ // processed. This is the case when no cumulative checkers are
+ // being run.
+ canClearTypes bool
+}
+
+type cachedPackage struct {
+ Problems []Problem
+ Ignores []Ignore
+ Config *config.Config
+}
+
+func (pkg *Package) decUse() {
+ ret := atomic.AddUint64(&pkg.dependents, ^uint64(0))
+ if ret == 0 {
+ // nobody depends on this package anymore
+ if pkg.canClearTypes {
+ pkg.Types = nil
+ }
+ pkg.facts = nil
+ pkg.pkgFacts = nil
+
+ for _, imp := range pkg.Imports {
+ imp.decUse()
+ }
+ }
+}
+
+type result struct {
+ v interface{}
+ err error
+ ready chan struct{}
+}
+
+type Runner struct {
+ cache *cache.Cache
+ goVersion int
+ stats *Stats
+ repeatAnalyzers uint
+
+ analyzerIDs analyzerIDs
+ problemsCacheKey string
+
+ // limits parallelism of loading packages
+ loadSem chan struct{}
+}
+
+type analyzerIDs struct {
+ m map[*analysis.Analyzer]int
+}
+
+func (ids analyzerIDs) get(a *analysis.Analyzer) int {
+ id, ok := ids.m[a]
+ if !ok {
+ panic(fmt.Sprintf("no analyzer ID for %s", a.Name))
+ }
+ return id
+}
+
+type Fact struct {
+ Path string
+ Fact analysis.Fact
+}
+
+type analysisAction struct {
+ analyzer *analysis.Analyzer
+ analyzerID int
+ pkg *Package
+ newPackageFacts []analysis.Fact
+ problems []Problem
+
+ pkgFacts map[*types.Package][]analysis.Fact
+}
+
+func (ac *analysisAction) String() string {
+ return fmt.Sprintf("%s @ %s", ac.analyzer, ac.pkg)
+}
+
+func (ac *analysisAction) allObjectFacts() []analysis.ObjectFact {
+ out := make([]analysis.ObjectFact, 0, len(ac.pkg.facts[ac.analyzerID]))
+ for obj, facts := range ac.pkg.facts[ac.analyzerID] {
+ for _, fact := range facts {
+ out = append(out, analysis.ObjectFact{
+ Object: obj,
+ Fact: fact,
+ })
+ }
+ }
+ return out
+}
+
+func (ac *analysisAction) allPackageFacts() []analysis.PackageFact {
+ out := make([]analysis.PackageFact, 0, len(ac.pkgFacts))
+ for pkg, facts := range ac.pkgFacts {
+ for _, fact := range facts {
+ out = append(out, analysis.PackageFact{
+ Package: pkg,
+ Fact: fact,
+ })
+ }
+ }
+ return out
+}
+
+func (ac *analysisAction) importObjectFact(obj types.Object, fact analysis.Fact) bool {
+ if sanityCheck && len(ac.analyzer.FactTypes) == 0 {
+ panic("analysis doesn't export any facts")
+ }
+ for _, f := range ac.pkg.facts[ac.analyzerID][obj] {
+ if reflect.TypeOf(f) == reflect.TypeOf(fact) {
+ reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f).Elem())
+ return true
+ }
+ }
+ return false
+}
+
+func (ac *analysisAction) importPackageFact(pkg *types.Package, fact analysis.Fact) bool {
+ if sanityCheck && len(ac.analyzer.FactTypes) == 0 {
+ panic("analysis doesn't export any facts")
+ }
+ for _, f := range ac.pkgFacts[pkg] {
+ if reflect.TypeOf(f) == reflect.TypeOf(fact) {
+ reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f).Elem())
+ return true
+ }
+ }
+ return false
+}
+
+func (ac *analysisAction) exportObjectFact(obj types.Object, fact analysis.Fact) {
+ if sanityCheck && len(ac.analyzer.FactTypes) == 0 {
+ panic("analysis doesn't export any facts")
+ }
+ ac.pkg.facts[ac.analyzerID][obj] = append(ac.pkg.facts[ac.analyzerID][obj], fact)
+}
+
+func (ac *analysisAction) exportPackageFact(fact analysis.Fact) {
+ if sanityCheck && len(ac.analyzer.FactTypes) == 0 {
+ panic("analysis doesn't export any facts")
+ }
+ ac.pkgFacts[ac.pkg.Types] = append(ac.pkgFacts[ac.pkg.Types], fact)
+ ac.newPackageFacts = append(ac.newPackageFacts, fact)
+}
+
+func (ac *analysisAction) report(pass *analysis.Pass, d analysis.Diagnostic) {
+ p := Problem{
+ Pos: DisplayPosition(pass.Fset, d.Pos),
+ End: DisplayPosition(pass.Fset, d.End),
+ Message: d.Message,
+ Check: pass.Analyzer.Name,
+ }
+ for _, r := range d.Related {
+ p.Related = append(p.Related, Related{
+ Pos: DisplayPosition(pass.Fset, r.Pos),
+ End: DisplayPosition(pass.Fset, r.End),
+ Message: r.Message,
+ })
+ }
+ ac.problems = append(ac.problems, p)
+}
+
+func (r *Runner) runAnalysis(ac *analysisAction) (ret interface{}, err error) {
+ ac.pkg.resultsMu.Lock()
+ res := ac.pkg.results[r.analyzerIDs.get(ac.analyzer)]
+ if res != nil {
+ ac.pkg.resultsMu.Unlock()
+ <-res.ready
+ return res.v, res.err
+ } else {
+ res = &result{
+ ready: make(chan struct{}),
+ }
+ ac.pkg.results[r.analyzerIDs.get(ac.analyzer)] = res
+ ac.pkg.resultsMu.Unlock()
+
+ defer func() {
+ res.v = ret
+ res.err = err
+ close(res.ready)
+ }()
+
+ pass := new(analysis.Pass)
+ *pass = analysis.Pass{
+ Analyzer: ac.analyzer,
+ Fset: ac.pkg.Fset,
+ Files: ac.pkg.Syntax,
+ // type information may be nil or may be populated. if it is
+ // nil, it will get populated later.
+ Pkg: ac.pkg.Types,
+ TypesInfo: ac.pkg.TypesInfo,
+ TypesSizes: ac.pkg.TypesSizes,
+ ResultOf: map[*analysis.Analyzer]interface{}{},
+ ImportObjectFact: ac.importObjectFact,
+ ImportPackageFact: ac.importPackageFact,
+ ExportObjectFact: ac.exportObjectFact,
+ ExportPackageFact: ac.exportPackageFact,
+ Report: func(d analysis.Diagnostic) {
+ ac.report(pass, d)
+ },
+ AllObjectFacts: ac.allObjectFacts,
+ AllPackageFacts: ac.allPackageFacts,
+ }
+
+ if !ac.pkg.initial {
+ // Don't report problems in dependencies
+ pass.Report = func(analysis.Diagnostic) {}
+ }
+ return r.runAnalysisUser(pass, ac)
+ }
+}
+
+func (r *Runner) loadCachedPackage(pkg *Package, analyzers []*analysis.Analyzer) (cachedPackage, bool) {
+ // OPT(dh): we can cache this computation, it'll be the same for all packages
+ id := cache.Subkey(pkg.actionID, "data "+r.problemsCacheKey)
+
+ b, _, err := r.cache.GetBytes(id)
+ if err != nil {
+ return cachedPackage{}, false
+ }
+ var cpkg cachedPackage
+ if err := gob.NewDecoder(bytes.NewReader(b)).Decode(&cpkg); err != nil {
+ return cachedPackage{}, false
+ }
+ return cpkg, true
+}
+
+func (r *Runner) loadCachedFacts(a *analysis.Analyzer, pkg *Package) ([]Fact, bool) {
+ if len(a.FactTypes) == 0 {
+ return nil, true
+ }
+
+ var facts []Fact
+ // Look in the cache for facts
+ aID := passActionID(pkg, a)
+ aID = cache.Subkey(aID, "facts")
+ b, _, err := r.cache.GetBytes(aID)
+ if err != nil {
+ // No cached facts, analyse this package like a user-provided one, but ignore diagnostics
+ return nil, false
+ }
+
+ if err := gob.NewDecoder(bytes.NewReader(b)).Decode(&facts); err != nil {
+ // Cached facts are broken, analyse this package like a user-provided one, but ignore diagnostics
+ return nil, false
+ }
+ return facts, true
+}
+
+type dependencyError struct {
+ dep string
+ err error
+}
+
+func (err dependencyError) nested() dependencyError {
+ if o, ok := err.err.(dependencyError); ok {
+ return o.nested()
+ }
+ return err
+}
+
+func (err dependencyError) Error() string {
+ if o, ok := err.err.(dependencyError); ok {
+ return o.Error()
+ }
+ return fmt.Sprintf("error running dependency %s: %s", err.dep, err.err)
+}
+
+func (r *Runner) makeAnalysisAction(a *analysis.Analyzer, pkg *Package) *analysisAction {
+ aid := r.analyzerIDs.get(a)
+ ac := &analysisAction{
+ analyzer: a,
+ analyzerID: aid,
+ pkg: pkg,
+ }
+
+ if len(a.FactTypes) == 0 {
+ return ac
+ }
+
+ // Merge all package facts of dependencies
+ ac.pkgFacts = map[*types.Package][]analysis.Fact{}
+ seen := map[*Package]struct{}{}
+ var dfs func(*Package)
+ dfs = func(pkg *Package) {
+ if _, ok := seen[pkg]; ok {
+ return
+ }
+ seen[pkg] = struct{}{}
+ s := pkg.pkgFacts[aid]
+ ac.pkgFacts[pkg.Types] = s[0:len(s):len(s)]
+ for _, imp := range pkg.Imports {
+ dfs(imp)
+ }
+ }
+ dfs(pkg)
+
+ return ac
+}
+
+// analyzes that we always want to run, even if they're not being run
+// explicitly or as dependencies. these are necessary for the inner
+// workings of the runner.
+var injectedAnalyses = []*analysis.Analyzer{facts.Generated, config.Analyzer}
+
+func (r *Runner) runAnalysisUser(pass *analysis.Pass, ac *analysisAction) (interface{}, error) {
+ if !ac.pkg.fromSource {
+ panic(fmt.Sprintf("internal error: %s was not loaded from source", ac.pkg))
+ }
+
+ // User-provided package, analyse it
+ // First analyze it with dependencies
+ for _, req := range ac.analyzer.Requires {
+ acReq := r.makeAnalysisAction(req, ac.pkg)
+ ret, err := r.runAnalysis(acReq)
+ if err != nil {
+ // We couldn't run a dependency, no point in going on
+ return nil, dependencyError{req.Name, err}
+ }
+
+ pass.ResultOf[req] = ret
+ }
+
+ // Then with this analyzer
+ var ret interface{}
+ for i := uint(0); i < r.repeatAnalyzers+1; i++ {
+ var err error
+ t := time.Now()
+ ret, err = ac.analyzer.Run(pass)
+ r.stats.MeasureAnalyzer(ac.analyzer, ac.pkg, time.Since(t))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if len(ac.analyzer.FactTypes) > 0 {
+ // Merge new facts into the package and persist them.
+ var facts []Fact
+ for _, fact := range ac.newPackageFacts {
+ id := r.analyzerIDs.get(ac.analyzer)
+ ac.pkg.pkgFacts[id] = append(ac.pkg.pkgFacts[id], fact)
+ facts = append(facts, Fact{"", fact})
+ }
+ for obj, afacts := range ac.pkg.facts[ac.analyzerID] {
+ if obj.Pkg() != ac.pkg.Package.Types {
+ continue
+ }
+ path, err := objectpath.For(obj)
+ if err != nil {
+ continue
+ }
+ for _, fact := range afacts {
+ facts = append(facts, Fact{string(path), fact})
+ }
+ }
+
+ if err := r.cacheData(facts, ac.pkg, ac.analyzer, "facts"); err != nil {
+ return nil, err
+ }
+ }
+
+ return ret, nil
+}
+
+func (r *Runner) cacheData(v interface{}, pkg *Package, a *analysis.Analyzer, subkey string) error {
+ buf := &bytes.Buffer{}
+ if err := gob.NewEncoder(buf).Encode(v); err != nil {
+ return err
+ }
+ aID := passActionID(pkg, a)
+ aID = cache.Subkey(aID, subkey)
+ if err := r.cache.PutBytes(aID, buf.Bytes()); err != nil {
+ return err
+ }
+ return nil
+}
+
+func NewRunner(stats *Stats) (*Runner, error) {
+ cache, err := cache.Default()
+ if err != nil {
+ return nil, err
+ }
+
+ return &Runner{
+ cache: cache,
+ stats: stats,
+ }, nil
+}
+
+// Run loads packages corresponding to patterns and analyses them with
+// analyzers. It returns the loaded packages, which contain reported
+// diagnostics as well as extracted ignore directives.
+//
+// Note that diagnostics have not been filtered at this point yet, to
+// accommodate cumulative analyzes that require additional steps to
+// produce diagnostics.
+func (r *Runner) Run(cfg *packages.Config, patterns []string, analyzers []*analysis.Analyzer, hasCumulative bool) ([]*Package, error) {
+ checkerNames := make([]string, len(analyzers))
+ for i, a := range analyzers {
+ checkerNames[i] = a.Name
+ }
+ sort.Strings(checkerNames)
+ r.problemsCacheKey = strings.Join(checkerNames, " ")
+
+ var allAnalyzers []*analysis.Analyzer
+ r.analyzerIDs = analyzerIDs{m: map[*analysis.Analyzer]int{}}
+ id := 0
+ seen := map[*analysis.Analyzer]struct{}{}
+ var dfs func(a *analysis.Analyzer)
+ dfs = func(a *analysis.Analyzer) {
+ if _, ok := seen[a]; ok {
+ return
+ }
+ seen[a] = struct{}{}
+ allAnalyzers = append(allAnalyzers, a)
+ r.analyzerIDs.m[a] = id
+ id++
+ for _, f := range a.FactTypes {
+ gob.Register(f)
+ }
+ for _, req := range a.Requires {
+ dfs(req)
+ }
+ }
+ for _, a := range analyzers {
+ if v := a.Flags.Lookup("go"); v != nil {
+ v.Value.Set(fmt.Sprintf("1.%d", r.goVersion))
+ }
+ dfs(a)
+ }
+ for _, a := range injectedAnalyses {
+ dfs(a)
+ }
+ // Run all analyzers on all packages (subject to further
+ // restrictions enforced later). This guarantees that if analyzer
+ // A1 depends on A2, and A2 has facts, that A2 will run on the
+ // dependencies of user-provided packages, even though A1 won't.
+ analyzers = allAnalyzers
+
+ var dcfg packages.Config
+ if cfg != nil {
+ dcfg = *cfg
+ }
+
+ atomic.StoreUint32(&r.stats.State, StateGraph)
+ initialPkgs, err := loader.Graph(dcfg, patterns...)
+ if err != nil {
+ return nil, err
+ }
+ defer r.cache.Trim()
+
+ var allPkgs []*Package
+ m := map[*packages.Package]*Package{}
+ packages.Visit(initialPkgs, nil, func(l *packages.Package) {
+ m[l] = &Package{
+ Package: l,
+ results: make([]*result, len(r.analyzerIDs.m)),
+ facts: make([]map[types.Object][]analysis.Fact, len(r.analyzerIDs.m)),
+ pkgFacts: make([][]analysis.Fact, len(r.analyzerIDs.m)),
+ done: make(chan struct{}),
+ // every package needs itself
+ dependents: 1,
+ canClearTypes: !hasCumulative,
+ }
+ allPkgs = append(allPkgs, m[l])
+ for i := range m[l].facts {
+ m[l].facts[i] = map[types.Object][]analysis.Fact{}
+ }
+ for _, err := range l.Errors {
+ m[l].errs = append(m[l].errs, err)
+ }
+ for _, v := range l.Imports {
+ m[v].dependents++
+ m[l].Imports = append(m[l].Imports, m[v])
+ }
+
+ m[l].hash, err = r.packageHash(m[l])
+ m[l].actionID = packageActionID(m[l])
+ if err != nil {
+ m[l].errs = append(m[l].errs, err)
+ }
+ })
+
+ pkgs := make([]*Package, len(initialPkgs))
+ for i, l := range initialPkgs {
+ pkgs[i] = m[l]
+ pkgs[i].initial = true
+ }
+
+ atomic.StoreUint32(&r.stats.InitialPackages, uint32(len(initialPkgs)))
+ atomic.StoreUint32(&r.stats.TotalPackages, uint32(len(allPkgs)))
+ atomic.StoreUint32(&r.stats.State, StateProcessing)
+
+ var wg sync.WaitGroup
+ wg.Add(len(allPkgs))
+ r.loadSem = make(chan struct{}, runtime.GOMAXPROCS(-1))
+ atomic.StoreUint32(&r.stats.TotalWorkers, uint32(cap(r.loadSem)))
+ for _, pkg := range allPkgs {
+ pkg := pkg
+ go func() {
+ r.processPkg(pkg, analyzers)
+
+ if pkg.initial {
+ atomic.AddUint32(&r.stats.ProcessedInitialPackages, 1)
+ }
+ atomic.AddUint32(&r.stats.Problems, uint32(len(pkg.problems)))
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+
+ return pkgs, nil
+}
+
+var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?`)
+
+func parsePos(pos string) (token.Position, int, error) {
+ if pos == "-" || pos == "" {
+ return token.Position{}, 0, nil
+ }
+ parts := posRe.FindStringSubmatch(pos)
+ if parts == nil {
+ return token.Position{}, 0, fmt.Errorf("malformed position %q", pos)
+ }
+ file := parts[1]
+ line, _ := strconv.Atoi(parts[2])
+ col, _ := strconv.Atoi(parts[3])
+ return token.Position{
+ Filename: file,
+ Line: line,
+ Column: col,
+ }, len(parts[0]), nil
+}
+
+// loadPkg loads a Go package. It may be loaded from a combination of
+// caches, or from source.
+func (r *Runner) loadPkg(pkg *Package, analyzers []*analysis.Analyzer) error {
+ if pkg.Types != nil {
+ panic(fmt.Sprintf("internal error: %s has already been loaded", pkg.Package))
+ }
+
+ if pkg.initial {
+ // Try to load cached package
+ cpkg, ok := r.loadCachedPackage(pkg, analyzers)
+ if ok {
+ pkg.problems = cpkg.Problems
+ pkg.ignores = cpkg.Ignores
+ pkg.cfg = cpkg.Config
+ } else {
+ pkg.fromSource = true
+ return loader.LoadFromSource(pkg.Package)
+ }
+ }
+
+ // At this point we're either working with a non-initial package,
+ // or we managed to load cached problems for the package. We still
+ // need export data and facts.
+
+ // OPT(dh): we don't need type information for this package if no
+ // other package depends on it. this may be the case for initial
+ // packages.
+
+ // Load package from export data
+ if err := loader.LoadFromExport(pkg.Package); err != nil {
+ // We asked Go to give us up to date export data, yet
+ // we can't load it. There must be something wrong.
+ //
+ // Attempt loading from source. This should fail (because
+ // otherwise there would be export data); we just want to
+ // get the compile errors. If loading from source succeeds
+ // we discard the result, anyway. Otherwise we'll fail
+ // when trying to reload from export data later.
+ //
+ // FIXME(dh): we no longer reload from export data, so
+ // theoretically we should be able to continue
+ pkg.fromSource = true
+ if err := loader.LoadFromSource(pkg.Package); err != nil {
+ return err
+ }
+ // Make sure this package can't be imported successfully
+ pkg.Package.Errors = append(pkg.Package.Errors, packages.Error{
+ Pos: "-",
+ Msg: fmt.Sprintf("could not load export data: %s", err),
+ Kind: packages.ParseError,
+ })
+ return fmt.Errorf("could not load export data: %s", err)
+ }
+
+ failed := false
+ seen := make([]bool, len(r.analyzerIDs.m))
+ var dfs func(*analysis.Analyzer)
+ dfs = func(a *analysis.Analyzer) {
+ if seen[r.analyzerIDs.get(a)] {
+ return
+ }
+ seen[r.analyzerIDs.get(a)] = true
+
+ if len(a.FactTypes) > 0 {
+ facts, ok := r.loadCachedFacts(a, pkg)
+ if !ok {
+ failed = true
+ return
+ }
+
+ for _, f := range facts {
+ if f.Path == "" {
+ // This is a package fact
+ pkg.pkgFacts[r.analyzerIDs.get(a)] = append(pkg.pkgFacts[r.analyzerIDs.get(a)], f.Fact)
+ continue
+ }
+ obj, err := objectpath.Object(pkg.Types, objectpath.Path(f.Path))
+ if err != nil {
+ // Be lenient about these errors. For example, when
+ // analysing io/ioutil from source, we may get a fact
+ // for methods on the devNull type, and objectpath
+ // will happily create a path for them. However, when
+ // we later load io/ioutil from export data, the path
+ // no longer resolves.
+ //
+ // If an exported type embeds the unexported type,
+ // then (part of) the unexported type will become part
+ // of the type information and our path will resolve
+ // again.
+ continue
+ }
+ pkg.facts[r.analyzerIDs.get(a)][obj] = append(pkg.facts[r.analyzerIDs.get(a)][obj], f.Fact)
+ }
+ }
+
+ for _, req := range a.Requires {
+ dfs(req)
+ }
+ }
+ for _, a := range analyzers {
+ dfs(a)
+ }
+
+ if !failed {
+ return nil
+ }
+
+ // We failed to load some cached facts
+ pkg.fromSource = true
+ // XXX we added facts to the maps, we need to get rid of those
+ return loader.LoadFromSource(pkg.Package)
+}
+
+type analysisError struct {
+ analyzer *analysis.Analyzer
+ pkg *Package
+ err error
+}
+
+func (err analysisError) Error() string {
+ return fmt.Sprintf("error running analyzer %s on %s: %s", err.analyzer, err.pkg, err.err)
+}
+
+// processPkg processes a package. This involves loading the package,
+// either from export data or from source. For packages loaded from
+// source, the provides analyzers will be run on the package.
+func (r *Runner) processPkg(pkg *Package, analyzers []*analysis.Analyzer) {
+ defer func() {
+ // Clear information we no longer need. Make sure to do this
+ // when returning from processPkg so that we clear
+ // dependencies, not just initial packages.
+ pkg.TypesInfo = nil
+ pkg.Syntax = nil
+ pkg.results = nil
+
+ atomic.AddUint32(&r.stats.ProcessedPackages, 1)
+ pkg.decUse()
+ close(pkg.done)
+ }()
+
+ // Ensure all packages have the generated map and config. This is
+ // required by internals of the runner. Analyses that themselves
+ // make use of either have an explicit dependency so that other
+ // runners work correctly, too.
+ analyzers = append(analyzers[0:len(analyzers):len(analyzers)], injectedAnalyses...)
+
+ if len(pkg.errs) != 0 {
+ return
+ }
+
+ for _, imp := range pkg.Imports {
+ <-imp.done
+ if len(imp.errs) > 0 {
+ if imp.initial {
+ // Don't print the error of the dependency since it's
+ // an initial package and we're already printing the
+ // error.
+ pkg.errs = append(pkg.errs, fmt.Errorf("could not analyze dependency %s of %s", imp, pkg))
+ } else {
+ var s string
+ for _, err := range imp.errs {
+ s += "\n\t" + err.Error()
+ }
+ pkg.errs = append(pkg.errs, fmt.Errorf("could not analyze dependency %s of %s: %s", imp, pkg, s))
+ }
+ return
+ }
+ }
+ if pkg.PkgPath == "unsafe" {
+ pkg.Types = types.Unsafe
+ return
+ }
+
+ r.loadSem <- struct{}{}
+ atomic.AddUint32(&r.stats.ActiveWorkers, 1)
+ defer func() {
+ <-r.loadSem
+ atomic.AddUint32(&r.stats.ActiveWorkers, ^uint32(0))
+ }()
+ if err := r.loadPkg(pkg, analyzers); err != nil {
+ pkg.errs = append(pkg.errs, err)
+ return
+ }
+
+ // A package's object facts is the union of all of its dependencies.
+ for _, imp := range pkg.Imports {
+ for ai, m := range imp.facts {
+ for obj, facts := range m {
+ pkg.facts[ai][obj] = facts[0:len(facts):len(facts)]
+ }
+ }
+ }
+
+ if !pkg.fromSource {
+ // Nothing left to do for the package.
+ return
+ }
+
+ // Run analyses on initial packages and those missing facts
+ var wg sync.WaitGroup
+ wg.Add(len(analyzers))
+ errs := make([]error, len(analyzers))
+ var acs []*analysisAction
+ for i, a := range analyzers {
+ i := i
+ a := a
+ ac := r.makeAnalysisAction(a, pkg)
+ acs = append(acs, ac)
+ go func() {
+ defer wg.Done()
+ // Only initial packages and packages with missing
+ // facts will have been loaded from source.
+ if pkg.initial || len(a.FactTypes) > 0 {
+ if _, err := r.runAnalysis(ac); err != nil {
+ errs[i] = analysisError{a, pkg, err}
+ return
+ }
+ }
+ }()
+ }
+ wg.Wait()
+
+ depErrors := map[dependencyError]int{}
+ for _, err := range errs {
+ if err == nil {
+ continue
+ }
+ switch err := err.(type) {
+ case analysisError:
+ switch err := err.err.(type) {
+ case dependencyError:
+ depErrors[err.nested()]++
+ default:
+ pkg.errs = append(pkg.errs, err)
+ }
+ default:
+ pkg.errs = append(pkg.errs, err)
+ }
+ }
+ for err, count := range depErrors {
+ pkg.errs = append(pkg.errs,
+ fmt.Errorf("could not run %s@%s, preventing %d analyzers from running: %s", err.dep, pkg, count, err.err))
+ }
+
+ // We can't process ignores at this point because `unused` needs
+ // to see more than one package to make its decision.
+ //
+ // OPT(dh): can't we guard this block of code by pkg.initial?
+ ignores, problems := parseDirectives(pkg.Package)
+ pkg.ignores = append(pkg.ignores, ignores...)
+ pkg.problems = append(pkg.problems, problems...)
+ for _, ac := range acs {
+ pkg.problems = append(pkg.problems, ac.problems...)
+ }
+
+ if pkg.initial {
+ // Only initial packages have these analyzers run, and only
+ // initial packages need these.
+ if pkg.results[r.analyzerIDs.get(config.Analyzer)].v != nil {
+ pkg.cfg = pkg.results[r.analyzerIDs.get(config.Analyzer)].v.(*config.Config)
+ }
+ pkg.gen = pkg.results[r.analyzerIDs.get(facts.Generated)].v.(map[string]facts.Generator)
+ }
+
+ // In a previous version of the code, we would throw away all type
+ // information and reload it from export data. That was
+ // nonsensical. The *types.Package doesn't keep any information
+ // live that export data wouldn't also. We only need to discard
+ // the AST and the TypesInfo maps; that happens after we return
+ // from processPkg.
+}
+
+func parseDirective(s string) (cmd string, args []string) {
+ if !strings.HasPrefix(s, "//lint:") {
+ return "", nil
+ }
+ s = strings.TrimPrefix(s, "//lint:")
+ fields := strings.Split(s, " ")
+ return fields[0], fields[1:]
+}
+
+// parseDirectives extracts all linter directives from the source
+// files of the package. Malformed directives are returned as problems.
+func parseDirectives(pkg *packages.Package) ([]Ignore, []Problem) {
+ var ignores []Ignore
+ var problems []Problem
+
+ for _, f := range pkg.Syntax {
+ found := false
+ commentLoop:
+ for _, cg := range f.Comments {
+ for _, c := range cg.List {
+ if strings.Contains(c.Text, "//lint:") {
+ found = true
+ break commentLoop
+ }
+ }
+ }
+ if !found {
+ continue
+ }
+ cm := ast.NewCommentMap(pkg.Fset, f, f.Comments)
+ for node, cgs := range cm {
+ for _, cg := range cgs {
+ for _, c := range cg.List {
+ if !strings.HasPrefix(c.Text, "//lint:") {
+ continue
+ }
+ cmd, args := parseDirective(c.Text)
+ switch cmd {
+ case "ignore", "file-ignore":
+ if len(args) < 2 {
+ p := Problem{
+ Pos: DisplayPosition(pkg.Fset, c.Pos()),
+ Message: "malformed linter directive; missing the required reason field?",
+ Severity: Error,
+ Check: "compile",
+ }
+ problems = append(problems, p)
+ continue
+ }
+ default:
+ // unknown directive, ignore
+ continue
+ }
+ checks := strings.Split(args[0], ",")
+ pos := DisplayPosition(pkg.Fset, node.Pos())
+ var ig Ignore
+ switch cmd {
+ case "ignore":
+ ig = &LineIgnore{
+ File: pos.Filename,
+ Line: pos.Line,
+ Checks: checks,
+ Pos: DisplayPosition(pkg.Fset, c.Pos()),
+ }
+ case "file-ignore":
+ ig = &FileIgnore{
+ File: pos.Filename,
+ Checks: checks,
+ }
+ }
+ ignores = append(ignores, ig)
+ }
+ }
+ }
+ }
+
+ return ignores, problems
+}
+
+// packageHash computes a package's hash. The hash is based on all Go
+// files that make up the package, as well as the hashes of imported
+// packages.
+func (r *Runner) packageHash(pkg *Package) (string, error) {
+ key := cache.NewHash("package hash")
+ fmt.Fprintf(key, "pkgpath %s\n", pkg.PkgPath)
+ fmt.Fprintf(key, "go %d\n", r.goVersion)
+ for _, f := range pkg.CompiledGoFiles {
+ h, err := cache.FileHash(f)
+ if err != nil {
+ return "", err
+ }
+ fmt.Fprintf(key, "file %s %x\n", f, h)
+ }
+
+ // Actually load the configuration to calculate its hash. This
+ // will take into consideration inheritance of configuration
+ // files, as well as the default configuration.
+ //
+ // OPT(dh): doing this means we'll load the config twice: once for
+ // computing the hash, and once when analyzing the package from
+ // source.
+ cdir := config.Dir(pkg.GoFiles)
+ if cdir == "" {
+ fmt.Fprintf(key, "file %s %x\n", config.ConfigName, [cache.HashSize]byte{})
+ } else {
+ cfg, err := config.Load(cdir)
+ if err != nil {
+ return "", err
+ }
+ h := cache.NewHash(config.ConfigName)
+ if _, err := h.Write([]byte(cfg.String())); err != nil {
+ return "", err
+ }
+ fmt.Fprintf(key, "file %s %x\n", config.ConfigName, h.Sum())
+ }
+
+ imps := make([]*Package, len(pkg.Imports))
+ copy(imps, pkg.Imports)
+ sort.Slice(imps, func(i, j int) bool {
+ return imps[i].PkgPath < imps[j].PkgPath
+ })
+ for _, dep := range imps {
+ if dep.PkgPath == "unsafe" {
+ continue
+ }
+
+ fmt.Fprintf(key, "import %s %s\n", dep.PkgPath, dep.hash)
+ }
+ h := key.Sum()
+ return hex.EncodeToString(h[:]), nil
+}
+
+func packageActionID(pkg *Package) cache.ActionID {
+ key := cache.NewHash("package ID")
+ fmt.Fprintf(key, "pkgpath %s\n", pkg.PkgPath)
+ fmt.Fprintf(key, "pkghash %s\n", pkg.hash)
+ return key.Sum()
+}
+
+// passActionID computes an ActionID for an analysis pass.
+func passActionID(pkg *Package, analyzer *analysis.Analyzer) cache.ActionID {
+ return cache.Subkey(pkg.actionID, fmt.Sprintf("analyzer %s", analyzer.Name))
+}