github.com/vanstinator/golangci-lint@v0.0.0-20240223191551-cc572f00d9d1/pkg/golinters/goanalysis/runner_action.go (about) 1 package goanalysis 2 3 import ( 4 "errors" 5 "fmt" 6 "go/types" 7 "io" 8 "reflect" 9 "runtime/debug" 10 "time" 11 12 "golang.org/x/tools/go/analysis" 13 "golang.org/x/tools/go/packages" 14 "golang.org/x/tools/go/types/objectpath" 15 16 "github.com/vanstinator/golangci-lint/internal/errorutil" 17 "github.com/vanstinator/golangci-lint/internal/pkgcache" 18 ) 19 20 type actionAllocator struct { 21 allocatedActions []action 22 nextFreeIndex int 23 } 24 25 func newActionAllocator(maxCount int) *actionAllocator { 26 return &actionAllocator{ 27 allocatedActions: make([]action, maxCount), 28 nextFreeIndex: 0, 29 } 30 } 31 32 func (actAlloc *actionAllocator) alloc() *action { 33 if actAlloc.nextFreeIndex == len(actAlloc.allocatedActions) { 34 panic(fmt.Sprintf("Made too many allocations of actions: %d allowed", len(actAlloc.allocatedActions))) 35 } 36 act := &actAlloc.allocatedActions[actAlloc.nextFreeIndex] 37 actAlloc.nextFreeIndex++ 38 return act 39 } 40 41 // An action represents one unit of analysis work: the application of 42 // one analysis to one package. Actions form a DAG, both within a 43 // package (as different analyzers are applied, either in sequence or 44 // parallel), and across packages (as dependencies are analyzed). 45 type action struct { 46 a *analysis.Analyzer 47 pkg *packages.Package 48 pass *analysis.Pass 49 deps []*action 50 objectFacts map[objectFactKey]analysis.Fact 51 packageFacts map[packageFactKey]analysis.Fact 52 result any 53 diagnostics []analysis.Diagnostic 54 err error 55 r *runner 56 analysisDoneCh chan struct{} 57 loadCachedFactsDone bool 58 loadCachedFactsOk bool 59 isroot bool 60 isInitialPkg bool 61 needAnalyzeSource bool 62 } 63 64 func (act *action) String() string { 65 return fmt.Sprintf("%s@%s", act.a, act.pkg) 66 } 67 68 func (act *action) loadCachedFacts() bool { 69 if act.loadCachedFactsDone { // can't be set in parallel 70 return act.loadCachedFactsOk 71 } 72 73 res := func() bool { 74 if act.isInitialPkg { 75 return true // load cached facts only for non-initial packages 76 } 77 78 if len(act.a.FactTypes) == 0 { 79 return true // no need to load facts 80 } 81 82 return act.loadPersistedFacts() 83 }() 84 act.loadCachedFactsDone = true 85 act.loadCachedFactsOk = res 86 return res 87 } 88 89 func (act *action) waitUntilDependingAnalyzersWorked() { 90 for _, dep := range act.deps { 91 if dep.pkg == act.pkg { 92 <-dep.analysisDoneCh 93 } 94 } 95 } 96 97 func (act *action) analyzeSafe() { 98 defer func() { 99 if p := recover(); p != nil { 100 if !act.isroot { 101 // This line allows to display "hidden" panic with analyzers like buildssa. 102 // Some linters are dependent of sub-analyzers but when a sub-analyzer fails the linter is not aware of that, 103 // this results to another panic (ex: "interface conversion: interface {} is nil, not *buildssa.SSA"). 104 act.r.log.Errorf("%s: panic during analysis: %v, %s", act.a.Name, p, string(debug.Stack())) 105 } 106 107 act.err = errorutil.NewPanicError(fmt.Sprintf("%s: package %q (isInitialPkg: %t, needAnalyzeSource: %t): %s", 108 act.a.Name, act.pkg.Name, act.isInitialPkg, act.needAnalyzeSource, p), debug.Stack()) 109 } 110 }() 111 act.r.sw.TrackStage(act.a.Name, func() { 112 act.analyze() 113 }) 114 } 115 116 func (act *action) analyze() { 117 defer close(act.analysisDoneCh) // unblock actions depending on this action 118 119 if !act.needAnalyzeSource { 120 return 121 } 122 123 defer func(now time.Time) { 124 analyzeDebugf("go/analysis: %s: %s: analyzed package %q in %s", act.r.prefix, act.a.Name, act.pkg.Name, time.Since(now)) 125 }(time.Now()) 126 127 // Report an error if any dependency failures. 128 var depErrors error 129 for _, dep := range act.deps { 130 if dep.err == nil { 131 continue 132 } 133 134 depErrors = errors.Join(depErrors, errors.Unwrap(dep.err)) 135 } 136 if depErrors != nil { 137 act.err = fmt.Errorf("failed prerequisites: %w", depErrors) 138 return 139 } 140 141 // Plumb the output values of the dependencies 142 // into the inputs of this action. Also facts. 143 inputs := make(map[*analysis.Analyzer]any) 144 startedAt := time.Now() 145 for _, dep := range act.deps { 146 if dep.pkg == act.pkg { 147 // Same package, different analysis (horizontal edge): 148 // in-memory outputs of prerequisite analyzers 149 // become inputs to this analysis pass. 150 inputs[dep.a] = dep.result 151 } else if dep.a == act.a { // (always true) 152 // Same analysis, different package (vertical edge): 153 // serialized facts produced by prerequisite analysis 154 // become available to this analysis pass. 155 inheritFacts(act, dep) 156 } 157 } 158 factsDebugf("%s: Inherited facts in %s", act, time.Since(startedAt)) 159 160 // Run the analysis. 161 pass := &analysis.Pass{ 162 Analyzer: act.a, 163 Fset: act.pkg.Fset, 164 Files: act.pkg.Syntax, 165 OtherFiles: act.pkg.OtherFiles, 166 Pkg: act.pkg.Types, 167 TypesInfo: act.pkg.TypesInfo, 168 TypesSizes: act.pkg.TypesSizes, 169 ResultOf: inputs, 170 Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) }, 171 ImportObjectFact: act.importObjectFact, 172 ExportObjectFact: act.exportObjectFact, 173 ImportPackageFact: act.importPackageFact, 174 ExportPackageFact: act.exportPackageFact, 175 AllObjectFacts: act.allObjectFacts, 176 AllPackageFacts: act.allPackageFacts, 177 } 178 act.pass = pass 179 act.r.passToPkgGuard.Lock() 180 act.r.passToPkg[pass] = act.pkg 181 act.r.passToPkgGuard.Unlock() 182 183 if act.pkg.IllTyped { 184 // It looks like there should be !pass.Analyzer.RunDespiteErrors 185 // but govet's cgocall crashes on it. Govet itself contains !pass.Analyzer.RunDespiteErrors condition here, 186 // but it exits before it if packages.Load have failed. 187 act.err = fmt.Errorf("analysis skipped: %w", &IllTypedError{Pkg: act.pkg}) 188 } else { 189 startedAt = time.Now() 190 act.result, act.err = pass.Analyzer.Run(pass) 191 analyzedIn := time.Since(startedAt) 192 if analyzedIn > time.Millisecond*10 { 193 debugf("%s: run analyzer in %s", act, analyzedIn) 194 } 195 } 196 197 // disallow calls after Run 198 pass.ExportObjectFact = nil 199 pass.ExportPackageFact = nil 200 201 if err := act.persistFactsToCache(); err != nil { 202 act.r.log.Warnf("Failed to persist facts to cache: %s", err) 203 } 204 } 205 206 // importObjectFact implements Pass.ImportObjectFact. 207 // Given a non-nil pointer ptr of type *T, where *T satisfies Fact, 208 // importObjectFact copies the fact value to *ptr. 209 func (act *action) importObjectFact(obj types.Object, ptr analysis.Fact) bool { 210 if obj == nil { 211 panic("nil object") 212 } 213 key := objectFactKey{obj, act.factType(ptr)} 214 if v, ok := act.objectFacts[key]; ok { 215 reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) 216 return true 217 } 218 return false 219 } 220 221 // exportObjectFact implements Pass.ExportObjectFact. 222 func (act *action) exportObjectFact(obj types.Object, fact analysis.Fact) { 223 if obj.Pkg() != act.pkg.Types { 224 act.r.log.Panicf("internal error: in analysis %s of package %s: Fact.Set(%s, %T): can't set facts on objects belonging another package", 225 act.a, act.pkg, obj, fact) 226 } 227 228 key := objectFactKey{obj, act.factType(fact)} 229 act.objectFacts[key] = fact // clobber any existing entry 230 if isFactsExportDebug { 231 objstr := types.ObjectString(obj, (*types.Package).Name) 232 factsExportDebugf("%s: object %s has fact %s\n", 233 act.pkg.Fset.Position(obj.Pos()), objstr, fact) 234 } 235 } 236 237 func (act *action) allObjectFacts() []analysis.ObjectFact { 238 out := make([]analysis.ObjectFact, 0, len(act.objectFacts)) 239 for key, fact := range act.objectFacts { 240 out = append(out, analysis.ObjectFact{ 241 Object: key.obj, 242 Fact: fact, 243 }) 244 } 245 return out 246 } 247 248 // importPackageFact implements Pass.ImportPackageFact. 249 // Given a non-nil pointer ptr of type *T, where *T satisfies Fact, 250 // fact copies the fact value to *ptr. 251 func (act *action) importPackageFact(pkg *types.Package, ptr analysis.Fact) bool { 252 if pkg == nil { 253 panic("nil package") 254 } 255 key := packageFactKey{pkg, act.factType(ptr)} 256 if v, ok := act.packageFacts[key]; ok { 257 reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) 258 return true 259 } 260 return false 261 } 262 263 // exportPackageFact implements Pass.ExportPackageFact. 264 func (act *action) exportPackageFact(fact analysis.Fact) { 265 key := packageFactKey{act.pass.Pkg, act.factType(fact)} 266 act.packageFacts[key] = fact // clobber any existing entry 267 factsDebugf("%s: package %s has fact %s\n", 268 act.pkg.Fset.Position(act.pass.Files[0].Pos()), act.pass.Pkg.Path(), fact) 269 } 270 271 func (act *action) allPackageFacts() []analysis.PackageFact { 272 out := make([]analysis.PackageFact, 0, len(act.packageFacts)) 273 for key, fact := range act.packageFacts { 274 out = append(out, analysis.PackageFact{ 275 Package: key.pkg, 276 Fact: fact, 277 }) 278 } 279 return out 280 } 281 282 func (act *action) factType(fact analysis.Fact) reflect.Type { 283 t := reflect.TypeOf(fact) 284 if t.Kind() != reflect.Ptr { 285 act.r.log.Fatalf("invalid Fact type: got %T, want pointer", t) 286 } 287 return t 288 } 289 290 func (act *action) persistFactsToCache() error { 291 analyzer := act.a 292 if len(analyzer.FactTypes) == 0 { 293 return nil 294 } 295 296 // Merge new facts into the package and persist them. 297 var facts []Fact 298 for key, fact := range act.packageFacts { 299 if key.pkg != act.pkg.Types { 300 // The fact is from inherited facts from another package 301 continue 302 } 303 facts = append(facts, Fact{ 304 Path: "", 305 Fact: fact, 306 }) 307 } 308 for key, fact := range act.objectFacts { 309 obj := key.obj 310 if obj.Pkg() != act.pkg.Types { 311 // The fact is from inherited facts from another package 312 continue 313 } 314 315 path, err := objectpath.For(obj) 316 if err != nil { 317 // The object is not globally addressable 318 continue 319 } 320 321 facts = append(facts, Fact{ 322 Path: string(path), 323 Fact: fact, 324 }) 325 } 326 327 factsCacheDebugf("Caching %d facts for package %q and analyzer %s", len(facts), act.pkg.Name, act.a.Name) 328 329 key := fmt.Sprintf("%s/facts", analyzer.Name) 330 return act.r.pkgCache.Put(act.pkg, pkgcache.HashModeNeedAllDeps, key, facts) 331 } 332 333 func (act *action) loadPersistedFacts() bool { 334 var facts []Fact 335 key := fmt.Sprintf("%s/facts", act.a.Name) 336 if err := act.r.pkgCache.Get(act.pkg, pkgcache.HashModeNeedAllDeps, key, &facts); err != nil { 337 if !errors.Is(err, pkgcache.ErrMissing) && !errors.Is(err, io.EOF) { 338 act.r.log.Warnf("Failed to get persisted facts: %s", err) 339 } 340 341 factsCacheDebugf("No cached facts for package %q and analyzer %s", act.pkg.Name, act.a.Name) 342 return false 343 } 344 345 factsCacheDebugf("Loaded %d cached facts for package %q and analyzer %s", len(facts), act.pkg.Name, act.a.Name) 346 347 for _, f := range facts { 348 if f.Path == "" { // this is a package fact 349 key := packageFactKey{act.pkg.Types, act.factType(f.Fact)} 350 act.packageFacts[key] = f.Fact 351 continue 352 } 353 obj, err := objectpath.Object(act.pkg.Types, objectpath.Path(f.Path)) 354 if err != nil { 355 // Be lenient about these errors. For example, when 356 // analyzing io/ioutil from source, we may get a fact 357 // for methods on the devNull type, and objectpath 358 // will happily create a path for them. However, when 359 // we later load io/ioutil from export data, the path 360 // no longer resolves. 361 // 362 // If an exported type embeds the unexported type, 363 // then (part of) the unexported type will become part 364 // of the type information and our path will resolve 365 // again. 366 continue 367 } 368 factKey := objectFactKey{obj, act.factType(f.Fact)} 369 act.objectFacts[factKey] = f.Fact 370 } 371 372 return true 373 } 374 375 func (act *action) markDepsForAnalyzingSource() { 376 // Horizontal deps (analyzer.Requires) must be loaded from source and analyzed before analyzing 377 // this action. 378 for _, dep := range act.deps { 379 if dep.pkg == act.pkg { 380 // Analyze source only for horizontal dependencies, e.g. from "buildssa". 381 dep.needAnalyzeSource = true // can't be set in parallel 382 } 383 } 384 }