github.com/nozzle/golangci-lint@v1.49.0-nz3/pkg/golinters/goanalysis/runner.go (about) 1 // checker is a partial copy of https://github.com/golang/tools/blob/master/go/analysis/internal/checker 2 // Copyright 2018 The Go Authors. All rights reserved. 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 // Package goanalysis defines the implementation of the checker commands. 7 // The same code drives the multi-analysis driver, the single-analysis 8 // driver that is conventionally provided for convenience along with 9 // each analysis package, and the test driver. 10 package goanalysis 11 12 import ( 13 "encoding/gob" 14 "go/token" 15 "runtime" 16 "sort" 17 "sync" 18 19 "github.com/pkg/errors" 20 "golang.org/x/tools/go/analysis" 21 "golang.org/x/tools/go/packages" 22 23 "github.com/golangci/golangci-lint/internal/errorutil" 24 "github.com/golangci/golangci-lint/internal/pkgcache" 25 "github.com/golangci/golangci-lint/pkg/golinters/goanalysis/load" 26 "github.com/golangci/golangci-lint/pkg/logutils" 27 "github.com/golangci/golangci-lint/pkg/timeutils" 28 ) 29 30 var ( 31 debugf = logutils.Debug(logutils.DebugKeyGoAnalysis) 32 33 analyzeDebugf = logutils.Debug(logutils.DebugKeyGoAnalysisAnalyze) 34 isMemoryDebug = logutils.HaveDebugTag(logutils.DebugKeyGoAnalysisMemory) 35 issuesCacheDebugf = logutils.Debug(logutils.DebugKeyGoAnalysisIssuesCache) 36 37 factsDebugf = logutils.Debug(logutils.DebugKeyGoAnalysisFacts) 38 factsCacheDebugf = logutils.Debug(logutils.DebugKeyGoAnalysisFactsCache) 39 factsInheritDebugf = logutils.Debug(logutils.DebugKeyGoAnalysisFactsInherit) 40 factsExportDebugf = logutils.Debug(logutils.DebugKeyGoAnalysisFacts) 41 isFactsExportDebug = logutils.HaveDebugTag(logutils.DebugKeyGoAnalysisFactsExport) 42 ) 43 44 type Diagnostic struct { 45 analysis.Diagnostic 46 Analyzer *analysis.Analyzer 47 Position token.Position 48 Pkg *packages.Package 49 } 50 51 type runner struct { 52 log logutils.Log 53 prefix string // ensure unique analyzer names 54 pkgCache *pkgcache.Cache 55 loadGuard *load.Guard 56 loadMode LoadMode 57 passToPkg map[*analysis.Pass]*packages.Package 58 passToPkgGuard sync.Mutex 59 sw *timeutils.Stopwatch 60 } 61 62 func newRunner(prefix string, logger logutils.Log, pkgCache *pkgcache.Cache, loadGuard *load.Guard, 63 loadMode LoadMode, sw *timeutils.Stopwatch) *runner { 64 return &runner{ 65 prefix: prefix, 66 log: logger, 67 pkgCache: pkgCache, 68 loadGuard: loadGuard, 69 loadMode: loadMode, 70 passToPkg: map[*analysis.Pass]*packages.Package{}, 71 sw: sw, 72 } 73 } 74 75 // Run loads the packages specified by args using go/packages, 76 // then applies the specified analyzers to them. 77 // Analysis flags must already have been set. 78 // It provides most of the logic for the main functions of both the 79 // singlechecker and the multi-analysis commands. 80 // It returns the appropriate exit code. 81 func (r *runner) run(analyzers []*analysis.Analyzer, initialPackages []*packages.Package) ([]Diagnostic, 82 []error, map[*analysis.Pass]*packages.Package) { 83 debugf("Analyzing %d packages on load mode %s", len(initialPackages), r.loadMode) 84 defer r.pkgCache.Trim() 85 86 roots := r.analyze(initialPackages, analyzers) 87 88 diags, errs := extractDiagnostics(roots) 89 90 return diags, errs, r.passToPkg 91 } 92 93 type actKey struct { 94 *analysis.Analyzer 95 *packages.Package 96 } 97 98 func (r *runner) markAllActions(a *analysis.Analyzer, pkg *packages.Package, markedActions map[actKey]struct{}) { 99 k := actKey{a, pkg} 100 if _, ok := markedActions[k]; ok { 101 return 102 } 103 104 for _, req := range a.Requires { 105 r.markAllActions(req, pkg, markedActions) 106 } 107 108 if len(a.FactTypes) != 0 { 109 for path := range pkg.Imports { 110 r.markAllActions(a, pkg.Imports[path], markedActions) 111 } 112 } 113 114 markedActions[k] = struct{}{} 115 } 116 117 func (r *runner) makeAction(a *analysis.Analyzer, pkg *packages.Package, 118 initialPkgs map[*packages.Package]bool, actions map[actKey]*action, actAlloc *actionAllocator) *action { 119 k := actKey{a, pkg} 120 act, ok := actions[k] 121 if ok { 122 return act 123 } 124 125 act = actAlloc.alloc() 126 act.a = a 127 act.pkg = pkg 128 act.r = r 129 act.isInitialPkg = initialPkgs[pkg] 130 act.needAnalyzeSource = initialPkgs[pkg] 131 act.analysisDoneCh = make(chan struct{}) 132 133 depsCount := len(a.Requires) 134 if len(a.FactTypes) > 0 { 135 depsCount += len(pkg.Imports) 136 } 137 act.deps = make([]*action, 0, depsCount) 138 139 // Add a dependency on each required analyzers. 140 for _, req := range a.Requires { 141 act.deps = append(act.deps, r.makeAction(req, pkg, initialPkgs, actions, actAlloc)) 142 } 143 144 r.buildActionFactDeps(act, a, pkg, initialPkgs, actions, actAlloc) 145 146 actions[k] = act 147 148 return act 149 } 150 151 func (r *runner) buildActionFactDeps(act *action, a *analysis.Analyzer, pkg *packages.Package, 152 initialPkgs map[*packages.Package]bool, actions map[actKey]*action, actAlloc *actionAllocator) { 153 // An analysis that consumes/produces facts 154 // must run on the package's dependencies too. 155 if len(a.FactTypes) == 0 { 156 return 157 } 158 159 act.objectFacts = make(map[objectFactKey]analysis.Fact) 160 act.packageFacts = make(map[packageFactKey]analysis.Fact) 161 162 paths := make([]string, 0, len(pkg.Imports)) 163 for path := range pkg.Imports { 164 paths = append(paths, path) 165 } 166 sort.Strings(paths) // for determinism 167 for _, path := range paths { 168 dep := r.makeAction(a, pkg.Imports[path], initialPkgs, actions, actAlloc) 169 act.deps = append(act.deps, dep) 170 } 171 172 // Need to register fact types for pkgcache proper gob encoding. 173 for _, f := range a.FactTypes { 174 gob.Register(f) 175 } 176 } 177 178 //nolint:gocritic 179 func (r *runner) prepareAnalysis(pkgs []*packages.Package, 180 analyzers []*analysis.Analyzer) (map[*packages.Package]bool, []*action, []*action) { 181 // Construct the action graph. 182 183 // Each graph node (action) is one unit of analysis. 184 // Edges express package-to-package (vertical) dependencies, 185 // and analysis-to-analysis (horizontal) dependencies. 186 187 // This place is memory-intensive: e.g. Istio project has 120k total actions. 188 // Therefore, optimize it carefully. 189 markedActions := make(map[actKey]struct{}, len(analyzers)*len(pkgs)) 190 for _, a := range analyzers { 191 for _, pkg := range pkgs { 192 r.markAllActions(a, pkg, markedActions) 193 } 194 } 195 totalActionsCount := len(markedActions) 196 197 actions := make(map[actKey]*action, totalActionsCount) 198 actAlloc := newActionAllocator(totalActionsCount) 199 200 initialPkgs := make(map[*packages.Package]bool, len(pkgs)) 201 for _, pkg := range pkgs { 202 initialPkgs[pkg] = true 203 } 204 205 // Build nodes for initial packages. 206 roots := make([]*action, 0, len(pkgs)*len(analyzers)) 207 for _, a := range analyzers { 208 for _, pkg := range pkgs { 209 root := r.makeAction(a, pkg, initialPkgs, actions, actAlloc) 210 root.isroot = true 211 roots = append(roots, root) 212 } 213 } 214 215 allActions := make([]*action, 0, len(actions)) 216 for _, act := range actions { 217 allActions = append(allActions, act) 218 } 219 220 debugf("Built %d actions", len(actions)) 221 222 return initialPkgs, allActions, roots 223 } 224 225 func (r *runner) analyze(pkgs []*packages.Package, analyzers []*analysis.Analyzer) []*action { 226 initialPkgs, actions, rootActions := r.prepareAnalysis(pkgs, analyzers) 227 228 actionPerPkg := map[*packages.Package][]*action{} 229 for _, act := range actions { 230 actionPerPkg[act.pkg] = append(actionPerPkg[act.pkg], act) 231 } 232 233 // Fill Imports field. 234 loadingPackages := map[*packages.Package]*loadingPackage{} 235 var dfs func(pkg *packages.Package) 236 dfs = func(pkg *packages.Package) { 237 if loadingPackages[pkg] != nil { 238 return 239 } 240 241 imports := map[string]*loadingPackage{} 242 for impPath, imp := range pkg.Imports { 243 dfs(imp) 244 impLp := loadingPackages[imp] 245 impLp.dependents++ 246 imports[impPath] = impLp 247 } 248 249 loadingPackages[pkg] = &loadingPackage{ 250 pkg: pkg, 251 imports: imports, 252 isInitial: initialPkgs[pkg], 253 log: r.log, 254 actions: actionPerPkg[pkg], 255 loadGuard: r.loadGuard, 256 dependents: 1, // self dependent 257 } 258 } 259 for _, act := range actions { 260 dfs(act.pkg) 261 } 262 263 // Limit memory and IO usage. 264 gomaxprocs := runtime.GOMAXPROCS(-1) 265 debugf("Analyzing at most %d packages in parallel", gomaxprocs) 266 loadSem := make(chan struct{}, gomaxprocs) 267 268 var wg sync.WaitGroup 269 debugf("There are %d initial and %d total packages", len(initialPkgs), len(loadingPackages)) 270 for _, lp := range loadingPackages { 271 if lp.isInitial { 272 wg.Add(1) 273 go func(lp *loadingPackage) { 274 lp.analyzeRecursive(r.loadMode, loadSem) 275 wg.Done() 276 }(lp) 277 } 278 } 279 wg.Wait() 280 281 return rootActions 282 } 283 284 //nolint:nakedret 285 func extractDiagnostics(roots []*action) (retDiags []Diagnostic, retErrors []error) { 286 extracted := make(map[*action]bool) 287 var extract func(*action) 288 var visitAll func(actions []*action) 289 visitAll = func(actions []*action) { 290 for _, act := range actions { 291 if !extracted[act] { 292 extracted[act] = true 293 visitAll(act.deps) 294 extract(act) 295 } 296 } 297 } 298 299 // De-duplicate diagnostics by position (not token.Pos) to 300 // avoid double-reporting in source files that belong to 301 // multiple packages, such as foo and foo.test. 302 type key struct { 303 token.Position 304 *analysis.Analyzer 305 message string 306 } 307 seen := make(map[key]bool) 308 309 extract = func(act *action) { 310 if act.err != nil { 311 if pe, ok := act.err.(*errorutil.PanicError); ok { 312 panic(pe) 313 } 314 retErrors = append(retErrors, errors.Wrap(act.err, act.a.Name)) 315 return 316 } 317 318 if act.isroot { 319 for _, diag := range act.diagnostics { 320 // We don't display a.Name/f.Category 321 // as most users don't care. 322 323 posn := act.pkg.Fset.Position(diag.Pos) 324 k := key{posn, act.a, diag.Message} 325 if seen[k] { 326 continue // duplicate 327 } 328 seen[k] = true 329 330 retDiag := Diagnostic{ 331 Diagnostic: diag, 332 Analyzer: act.a, 333 Position: posn, 334 Pkg: act.pkg, 335 } 336 retDiags = append(retDiags, retDiag) 337 } 338 } 339 } 340 visitAll(roots) 341 return 342 }