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  }