
Some checks failed
Extra / Vulnerability scanner (push) Has been cancelled
CI / go-mod (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Release a tag / release (push) Has been cancelled
CI / tests-on-windows (push) Has been cancelled
CI / tests-on-macos (push) Has been cancelled
CI / tests-on-unix (1.14) (push) Has been cancelled
CI / tests-on-unix (1.15) (push) Has been cancelled
CI / tests-on-unix (1.16) (push) Has been cancelled
CI / check_generated (push) Has been cancelled
Release a tag / docker-release (map[Dockerfile:build/Dockerfile.alpine]) (push) Has been cancelled
Release a tag / docker-release (map[Dockerfile:build/Dockerfile]) (push) Has been cancelled
270 lines
7.4 KiB
Go
270 lines
7.4 KiB
Go
package goanalysis
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"golang.org/x/tools/go/analysis"
|
|
"golang.org/x/tools/go/packages"
|
|
|
|
"github.com/golangci/golangci-lint/internal/pkgcache"
|
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
|
"github.com/golangci/golangci-lint/pkg/result"
|
|
"github.com/golangci/golangci-lint/pkg/timeutils"
|
|
)
|
|
|
|
type runAnalyzersConfig interface {
|
|
getName() string
|
|
getLinterNameForDiagnostic(*Diagnostic) string
|
|
getAnalyzers() []*analysis.Analyzer
|
|
useOriginalPackages() bool
|
|
reportIssues(*linter.Context) []Issue
|
|
getLoadMode() LoadMode
|
|
}
|
|
|
|
func runAnalyzers(cfg runAnalyzersConfig, lintCtx *linter.Context) ([]result.Issue, error) {
|
|
log := lintCtx.Log.Child("goanalysis")
|
|
sw := timeutils.NewStopwatch("analyzers", log)
|
|
|
|
const stagesToPrint = 10
|
|
defer sw.PrintTopStages(stagesToPrint)
|
|
|
|
runner := newRunner(cfg.getName(), log, lintCtx.PkgCache, lintCtx.LoadGuard, cfg.getLoadMode(), sw)
|
|
|
|
pkgs := lintCtx.Packages
|
|
if cfg.useOriginalPackages() {
|
|
pkgs = lintCtx.OriginalPackages
|
|
}
|
|
|
|
issues, pkgsFromCache := loadIssuesFromCache(pkgs, lintCtx, cfg.getAnalyzers())
|
|
var pkgsToAnalyze []*packages.Package
|
|
for _, pkg := range pkgs {
|
|
if !pkgsFromCache[pkg] {
|
|
pkgsToAnalyze = append(pkgsToAnalyze, pkg)
|
|
}
|
|
}
|
|
|
|
diags, errs, passToPkg := runner.run(cfg.getAnalyzers(), pkgsToAnalyze)
|
|
|
|
defer func() {
|
|
if len(errs) == 0 {
|
|
// If we try to save to cache even if we have compilation errors
|
|
// we won't see them on repeated runs.
|
|
saveIssuesToCache(pkgs, pkgsFromCache, issues, lintCtx, cfg.getAnalyzers())
|
|
}
|
|
}()
|
|
|
|
buildAllIssues := func() []result.Issue {
|
|
var retIssues []result.Issue
|
|
reportedIssues := cfg.reportIssues(lintCtx)
|
|
for i := range reportedIssues {
|
|
issue := &reportedIssues[i].Issue
|
|
if issue.Pkg == nil {
|
|
issue.Pkg = passToPkg[reportedIssues[i].Pass]
|
|
}
|
|
retIssues = append(retIssues, *issue)
|
|
}
|
|
retIssues = append(retIssues, buildIssues(diags, cfg.getLinterNameForDiagnostic)...)
|
|
return retIssues
|
|
}
|
|
|
|
errIssues, err := buildIssuesFromIllTypedError(errs, lintCtx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
issues = append(issues, errIssues...)
|
|
issues = append(issues, buildAllIssues()...)
|
|
|
|
return issues, nil
|
|
}
|
|
|
|
func buildIssues(diags []Diagnostic, linterNameBuilder func(diag *Diagnostic) string) []result.Issue {
|
|
var issues []result.Issue
|
|
for i := range diags {
|
|
diag := &diags[i]
|
|
linterName := linterNameBuilder(diag)
|
|
|
|
var text string
|
|
if diag.Analyzer.Name == linterName {
|
|
text = diag.Message
|
|
} else {
|
|
text = fmt.Sprintf("%s: %s", diag.Analyzer.Name, diag.Message)
|
|
}
|
|
|
|
issues = append(issues, result.Issue{
|
|
FromLinter: linterName,
|
|
Text: text,
|
|
Pos: diag.Position,
|
|
Pkg: diag.Pkg,
|
|
})
|
|
|
|
if len(diag.Related) > 0 {
|
|
for _, info := range diag.Related {
|
|
issues = append(issues, result.Issue{
|
|
FromLinter: linterName,
|
|
Text: fmt.Sprintf("%s(related information): %s", diag.Analyzer.Name, info.Message),
|
|
Pos: diag.Pkg.Fset.Position(info.Pos),
|
|
Pkg: diag.Pkg,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return issues
|
|
}
|
|
|
|
func getIssuesCacheKey(analyzers []*analysis.Analyzer) string {
|
|
return "lint/result:" + analyzersHashID(analyzers)
|
|
}
|
|
|
|
func saveIssuesToCache(allPkgs []*packages.Package, pkgsFromCache map[*packages.Package]bool,
|
|
issues []result.Issue, lintCtx *linter.Context, analyzers []*analysis.Analyzer) {
|
|
startedAt := time.Now()
|
|
perPkgIssues := map[*packages.Package][]result.Issue{}
|
|
for ind := range issues {
|
|
i := &issues[ind]
|
|
perPkgIssues[i.Pkg] = append(perPkgIssues[i.Pkg], *i)
|
|
}
|
|
|
|
savedIssuesCount := int32(0)
|
|
lintResKey := getIssuesCacheKey(analyzers)
|
|
|
|
workerCount := runtime.GOMAXPROCS(-1)
|
|
var wg sync.WaitGroup
|
|
wg.Add(workerCount)
|
|
|
|
pkgCh := make(chan *packages.Package, len(allPkgs))
|
|
for i := 0; i < workerCount; i++ {
|
|
go func() {
|
|
defer wg.Done()
|
|
for pkg := range pkgCh {
|
|
pkgIssues := perPkgIssues[pkg]
|
|
encodedIssues := make([]EncodingIssue, 0, len(pkgIssues))
|
|
for ind := range pkgIssues {
|
|
i := &pkgIssues[ind]
|
|
encodedIssues = append(encodedIssues, EncodingIssue{
|
|
FromLinter: i.FromLinter,
|
|
Text: i.Text,
|
|
Pos: i.Pos,
|
|
LineRange: i.LineRange,
|
|
Replacement: i.Replacement,
|
|
ExpectNoLint: i.ExpectNoLint,
|
|
ExpectedNoLintLinter: i.ExpectedNoLintLinter,
|
|
})
|
|
}
|
|
|
|
atomic.AddInt32(&savedIssuesCount, int32(len(encodedIssues)))
|
|
if err := lintCtx.PkgCache.Put(pkg, pkgcache.HashModeNeedAllDeps, lintResKey, encodedIssues); err != nil {
|
|
lintCtx.Log.Infof("Failed to save package %s issues (%d) to cache: %s", pkg, len(pkgIssues), err)
|
|
} else {
|
|
issuesCacheDebugf("Saved package %s issues (%d) to cache", pkg, len(pkgIssues))
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
for _, pkg := range allPkgs {
|
|
if pkgsFromCache[pkg] {
|
|
continue
|
|
}
|
|
|
|
pkgCh <- pkg
|
|
}
|
|
close(pkgCh)
|
|
wg.Wait()
|
|
|
|
issuesCacheDebugf("Saved %d issues from %d packages to cache in %s", savedIssuesCount, len(allPkgs), time.Since(startedAt))
|
|
}
|
|
|
|
//nolint:gocritic
|
|
func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context,
|
|
analyzers []*analysis.Analyzer) ([]result.Issue, map[*packages.Package]bool) {
|
|
startedAt := time.Now()
|
|
|
|
lintResKey := getIssuesCacheKey(analyzers)
|
|
type cacheRes struct {
|
|
issues []result.Issue
|
|
loadErr error
|
|
}
|
|
pkgToCacheRes := make(map[*packages.Package]*cacheRes, len(pkgs))
|
|
for _, pkg := range pkgs {
|
|
pkgToCacheRes[pkg] = &cacheRes{}
|
|
}
|
|
|
|
workerCount := runtime.GOMAXPROCS(-1)
|
|
var wg sync.WaitGroup
|
|
wg.Add(workerCount)
|
|
|
|
pkgCh := make(chan *packages.Package, len(pkgs))
|
|
for i := 0; i < workerCount; i++ {
|
|
go func() {
|
|
defer wg.Done()
|
|
for pkg := range pkgCh {
|
|
var pkgIssues []EncodingIssue
|
|
err := lintCtx.PkgCache.Get(pkg, pkgcache.HashModeNeedAllDeps, lintResKey, &pkgIssues)
|
|
cacheRes := pkgToCacheRes[pkg]
|
|
cacheRes.loadErr = err
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if len(pkgIssues) == 0 {
|
|
continue
|
|
}
|
|
|
|
issues := make([]result.Issue, 0, len(pkgIssues))
|
|
for _, i := range pkgIssues {
|
|
issues = append(issues, result.Issue{
|
|
FromLinter: i.FromLinter,
|
|
Text: i.Text,
|
|
Pos: i.Pos,
|
|
LineRange: i.LineRange,
|
|
Replacement: i.Replacement,
|
|
Pkg: pkg,
|
|
ExpectNoLint: i.ExpectNoLint,
|
|
ExpectedNoLintLinter: i.ExpectedNoLintLinter,
|
|
})
|
|
}
|
|
cacheRes.issues = issues
|
|
}
|
|
}()
|
|
}
|
|
|
|
for _, pkg := range pkgs {
|
|
pkgCh <- pkg
|
|
}
|
|
close(pkgCh)
|
|
wg.Wait()
|
|
|
|
loadedIssuesCount := 0
|
|
var issues []result.Issue
|
|
pkgsFromCache := map[*packages.Package]bool{}
|
|
for pkg, cacheRes := range pkgToCacheRes {
|
|
if cacheRes.loadErr == nil {
|
|
loadedIssuesCount += len(cacheRes.issues)
|
|
pkgsFromCache[pkg] = true
|
|
issues = append(issues, cacheRes.issues...)
|
|
issuesCacheDebugf("Loaded package %s issues (%d) from cache", pkg, len(cacheRes.issues))
|
|
} else {
|
|
issuesCacheDebugf("Didn't load package %s issues from cache: %s", pkg, cacheRes.loadErr)
|
|
}
|
|
}
|
|
issuesCacheDebugf("Loaded %d issues from cache in %s, analyzing %d/%d packages",
|
|
loadedIssuesCount, time.Since(startedAt), len(pkgs)-len(pkgsFromCache), len(pkgs))
|
|
return issues, pkgsFromCache
|
|
}
|
|
|
|
func analyzersHashID(analyzers []*analysis.Analyzer) string {
|
|
names := make([]string, 0, len(analyzers))
|
|
for _, a := range analyzers {
|
|
names = append(names, a.Name)
|
|
}
|
|
|
|
sort.Strings(names)
|
|
return strings.Join(names, ",")
|
|
}
|