 bf27481efd
			
		
	
	
		bf27481efd
		
			
		
	
	
	
	
		
			
			full diff: https://github.com/dominikh/go-tools/compare/2019.2.3...2020.1.3 Also updates tests to accomodate updated rules: --- FAIL: TestSourcesFromTestdataWithIssuesDir/staticcheck.go (0.43s) linters_test.go:137: [run --disable-all --print-issued-lines=false --print-linter-name=false --out-format=line-number --max-same-issues=10 -Estaticcheck --no-config testdata/staticcheck.go] linters_test.go:33: Error Trace: linters_test.go:33 linters_test.go:138 linters_test.go:53 Error: Received unexpected error: staticcheck.go:11: no match for `self-assignment of x to x` vs ["SA4006: this value of `x` is never used"] in: staticcheck.go:11:2: SA4006: this value of `x` is never used unmatched errors staticcheck.go:11:2: SA4006: this value of `x` is never used Test: TestSourcesFromTestdataWithIssuesDir/staticcheck.go Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
		
			
				
	
	
		
			1115 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1115 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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))
 | ||
| }
 |