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