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