390 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			390 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package goanalysis
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"go/types"
 | 
						|
	"io"
 | 
						|
	"reflect"
 | 
						|
	"runtime/debug"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/hashicorp/go-multierror"
 | 
						|
	"golang.org/x/tools/go/analysis"
 | 
						|
	"golang.org/x/tools/go/packages"
 | 
						|
	"golang.org/x/tools/go/types/objectpath"
 | 
						|
 | 
						|
	"github.com/golangci/golangci-lint/internal/errorutil"
 | 
						|
	"github.com/golangci/golangci-lint/internal/pkgcache"
 | 
						|
)
 | 
						|
 | 
						|
type actionAllocator struct {
 | 
						|
	allocatedActions []action
 | 
						|
	nextFreeIndex    int
 | 
						|
}
 | 
						|
 | 
						|
func newActionAllocator(maxCount int) *actionAllocator {
 | 
						|
	return &actionAllocator{
 | 
						|
		allocatedActions: make([]action, maxCount),
 | 
						|
		nextFreeIndex:    0,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (actAlloc *actionAllocator) alloc() *action {
 | 
						|
	if actAlloc.nextFreeIndex == len(actAlloc.allocatedActions) {
 | 
						|
		panic(fmt.Sprintf("Made too many allocations of actions: %d allowed", len(actAlloc.allocatedActions)))
 | 
						|
	}
 | 
						|
	act := &actAlloc.allocatedActions[actAlloc.nextFreeIndex]
 | 
						|
	actAlloc.nextFreeIndex++
 | 
						|
	return act
 | 
						|
}
 | 
						|
 | 
						|
// An action represents one unit of analysis work: the application of
 | 
						|
// one analysis to one package. Actions form a DAG, both within a
 | 
						|
// package (as different analyzers are applied, either in sequence or
 | 
						|
// parallel), and across packages (as dependencies are analyzed).
 | 
						|
type action struct {
 | 
						|
	a                   *analysis.Analyzer
 | 
						|
	pkg                 *packages.Package
 | 
						|
	pass                *analysis.Pass
 | 
						|
	deps                []*action
 | 
						|
	objectFacts         map[objectFactKey]analysis.Fact
 | 
						|
	packageFacts        map[packageFactKey]analysis.Fact
 | 
						|
	result              any
 | 
						|
	diagnostics         []analysis.Diagnostic
 | 
						|
	err                 error
 | 
						|
	r                   *runner
 | 
						|
	analysisDoneCh      chan struct{}
 | 
						|
	loadCachedFactsDone bool
 | 
						|
	loadCachedFactsOk   bool
 | 
						|
	isroot              bool
 | 
						|
	isInitialPkg        bool
 | 
						|
	needAnalyzeSource   bool
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) String() string {
 | 
						|
	return fmt.Sprintf("%s@%s", act.a, act.pkg)
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) loadCachedFacts() bool {
 | 
						|
	if act.loadCachedFactsDone { // can't be set in parallel
 | 
						|
		return act.loadCachedFactsOk
 | 
						|
	}
 | 
						|
 | 
						|
	res := func() bool {
 | 
						|
		if act.isInitialPkg {
 | 
						|
			return true // load cached facts only for non-initial packages
 | 
						|
		}
 | 
						|
 | 
						|
		if len(act.a.FactTypes) == 0 {
 | 
						|
			return true // no need to load facts
 | 
						|
		}
 | 
						|
 | 
						|
		return act.loadPersistedFacts()
 | 
						|
	}()
 | 
						|
	act.loadCachedFactsDone = true
 | 
						|
	act.loadCachedFactsOk = res
 | 
						|
	return res
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) waitUntilDependingAnalyzersWorked() {
 | 
						|
	for _, dep := range act.deps {
 | 
						|
		if dep.pkg == act.pkg {
 | 
						|
			<-dep.analysisDoneCh
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) analyzeSafe() {
 | 
						|
	defer func() {
 | 
						|
		if p := recover(); p != nil {
 | 
						|
			if !act.isroot {
 | 
						|
				// This line allows to display "hidden" panic with analyzers like buildssa.
 | 
						|
				// Some linters are dependent of sub-analyzers but when a sub-analyzer fails the linter is not aware of that,
 | 
						|
				// this results to another panic (ex: "interface conversion: interface {} is nil, not *buildssa.SSA").
 | 
						|
				act.r.log.Errorf("%s: panic during analysis: %v, %s", act.a.Name, p, string(debug.Stack()))
 | 
						|
			}
 | 
						|
 | 
						|
			act.err = errorutil.NewPanicError(fmt.Sprintf("%s: package %q (isInitialPkg: %t, needAnalyzeSource: %t): %s",
 | 
						|
				act.a.Name, act.pkg.Name, act.isInitialPkg, act.needAnalyzeSource, p), debug.Stack())
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	act.r.sw.TrackStage(act.a.Name, func() {
 | 
						|
		act.analyze()
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) analyze() {
 | 
						|
	defer close(act.analysisDoneCh) // unblock actions depending on this action
 | 
						|
 | 
						|
	if !act.needAnalyzeSource {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	defer func(now time.Time) {
 | 
						|
		analyzeDebugf("go/analysis: %s: %s: analyzed package %q in %s", act.r.prefix, act.a.Name, act.pkg.Name, time.Since(now))
 | 
						|
	}(time.Now())
 | 
						|
 | 
						|
	// Report an error if any dependency failures.
 | 
						|
	var depErrors *multierror.Error
 | 
						|
	for _, dep := range act.deps {
 | 
						|
		if dep.err == nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		depErrors = multierror.Append(depErrors, errors.Unwrap(dep.err))
 | 
						|
	}
 | 
						|
	if depErrors != nil {
 | 
						|
		depErrors.ErrorFormat = func(e []error) string {
 | 
						|
			return fmt.Sprintf("failed prerequisites: %v", e)
 | 
						|
		}
 | 
						|
 | 
						|
		act.err = depErrors
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Plumb the output values of the dependencies
 | 
						|
	// into the inputs of this action.  Also facts.
 | 
						|
	inputs := make(map[*analysis.Analyzer]any)
 | 
						|
	startedAt := time.Now()
 | 
						|
	for _, dep := range act.deps {
 | 
						|
		if dep.pkg == act.pkg {
 | 
						|
			// Same package, different analysis (horizontal edge):
 | 
						|
			// in-memory outputs of prerequisite analyzers
 | 
						|
			// become inputs to this analysis pass.
 | 
						|
			inputs[dep.a] = dep.result
 | 
						|
		} else if dep.a == act.a { // (always true)
 | 
						|
			// Same analysis, different package (vertical edge):
 | 
						|
			// serialized facts produced by prerequisite analysis
 | 
						|
			// become available to this analysis pass.
 | 
						|
			inheritFacts(act, dep)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	factsDebugf("%s: Inherited facts in %s", act, time.Since(startedAt))
 | 
						|
 | 
						|
	// Run the analysis.
 | 
						|
	pass := &analysis.Pass{
 | 
						|
		Analyzer:          act.a,
 | 
						|
		Fset:              act.pkg.Fset,
 | 
						|
		Files:             act.pkg.Syntax,
 | 
						|
		OtherFiles:        act.pkg.OtherFiles,
 | 
						|
		Pkg:               act.pkg.Types,
 | 
						|
		TypesInfo:         act.pkg.TypesInfo,
 | 
						|
		TypesSizes:        act.pkg.TypesSizes,
 | 
						|
		ResultOf:          inputs,
 | 
						|
		Report:            func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
 | 
						|
		ImportObjectFact:  act.importObjectFact,
 | 
						|
		ExportObjectFact:  act.exportObjectFact,
 | 
						|
		ImportPackageFact: act.importPackageFact,
 | 
						|
		ExportPackageFact: act.exportPackageFact,
 | 
						|
		AllObjectFacts:    act.allObjectFacts,
 | 
						|
		AllPackageFacts:   act.allPackageFacts,
 | 
						|
	}
 | 
						|
	act.pass = pass
 | 
						|
	act.r.passToPkgGuard.Lock()
 | 
						|
	act.r.passToPkg[pass] = act.pkg
 | 
						|
	act.r.passToPkgGuard.Unlock()
 | 
						|
 | 
						|
	if act.pkg.IllTyped {
 | 
						|
		// It looks like there should be !pass.Analyzer.RunDespiteErrors
 | 
						|
		// but govet's cgocall crashes on it. Govet itself contains !pass.Analyzer.RunDespiteErrors condition here,
 | 
						|
		// but it exits before it if packages.Load have failed.
 | 
						|
		act.err = fmt.Errorf("analysis skipped: %w", &IllTypedError{Pkg: act.pkg})
 | 
						|
	} else {
 | 
						|
		startedAt = time.Now()
 | 
						|
		act.result, act.err = pass.Analyzer.Run(pass)
 | 
						|
		analyzedIn := time.Since(startedAt)
 | 
						|
		if analyzedIn > time.Millisecond*10 {
 | 
						|
			debugf("%s: run analyzer in %s", act, analyzedIn)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// disallow calls after Run
 | 
						|
	pass.ExportObjectFact = nil
 | 
						|
	pass.ExportPackageFact = nil
 | 
						|
 | 
						|
	if err := act.persistFactsToCache(); err != nil {
 | 
						|
		act.r.log.Warnf("Failed to persist facts to cache: %s", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// importObjectFact implements Pass.ImportObjectFact.
 | 
						|
// Given a non-nil pointer ptr of type *T, where *T satisfies Fact,
 | 
						|
// importObjectFact copies the fact value to *ptr.
 | 
						|
func (act *action) importObjectFact(obj types.Object, ptr analysis.Fact) bool {
 | 
						|
	if obj == nil {
 | 
						|
		panic("nil object")
 | 
						|
	}
 | 
						|
	key := objectFactKey{obj, act.factType(ptr)}
 | 
						|
	if v, ok := act.objectFacts[key]; ok {
 | 
						|
		reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// exportObjectFact implements Pass.ExportObjectFact.
 | 
						|
func (act *action) exportObjectFact(obj types.Object, fact analysis.Fact) {
 | 
						|
	if obj.Pkg() != act.pkg.Types {
 | 
						|
		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",
 | 
						|
			act.a, act.pkg, obj, fact)
 | 
						|
	}
 | 
						|
 | 
						|
	key := objectFactKey{obj, act.factType(fact)}
 | 
						|
	act.objectFacts[key] = fact // clobber any existing entry
 | 
						|
	if isFactsExportDebug {
 | 
						|
		objstr := types.ObjectString(obj, (*types.Package).Name)
 | 
						|
		factsExportDebugf("%s: object %s has fact %s\n",
 | 
						|
			act.pkg.Fset.Position(obj.Pos()), objstr, fact)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) allObjectFacts() []analysis.ObjectFact {
 | 
						|
	out := make([]analysis.ObjectFact, 0, len(act.objectFacts))
 | 
						|
	for key, fact := range act.objectFacts {
 | 
						|
		out = append(out, analysis.ObjectFact{
 | 
						|
			Object: key.obj,
 | 
						|
			Fact:   fact,
 | 
						|
		})
 | 
						|
	}
 | 
						|
	return out
 | 
						|
}
 | 
						|
 | 
						|
// importPackageFact implements Pass.ImportPackageFact.
 | 
						|
// Given a non-nil pointer ptr of type *T, where *T satisfies Fact,
 | 
						|
// fact copies the fact value to *ptr.
 | 
						|
func (act *action) importPackageFact(pkg *types.Package, ptr analysis.Fact) bool {
 | 
						|
	if pkg == nil {
 | 
						|
		panic("nil package")
 | 
						|
	}
 | 
						|
	key := packageFactKey{pkg, act.factType(ptr)}
 | 
						|
	if v, ok := act.packageFacts[key]; ok {
 | 
						|
		reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// exportPackageFact implements Pass.ExportPackageFact.
 | 
						|
func (act *action) exportPackageFact(fact analysis.Fact) {
 | 
						|
	key := packageFactKey{act.pass.Pkg, act.factType(fact)}
 | 
						|
	act.packageFacts[key] = fact // clobber any existing entry
 | 
						|
	factsDebugf("%s: package %s has fact %s\n",
 | 
						|
		act.pkg.Fset.Position(act.pass.Files[0].Pos()), act.pass.Pkg.Path(), fact)
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) allPackageFacts() []analysis.PackageFact {
 | 
						|
	out := make([]analysis.PackageFact, 0, len(act.packageFacts))
 | 
						|
	for key, fact := range act.packageFacts {
 | 
						|
		out = append(out, analysis.PackageFact{
 | 
						|
			Package: key.pkg,
 | 
						|
			Fact:    fact,
 | 
						|
		})
 | 
						|
	}
 | 
						|
	return out
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) factType(fact analysis.Fact) reflect.Type {
 | 
						|
	t := reflect.TypeOf(fact)
 | 
						|
	if t.Kind() != reflect.Ptr {
 | 
						|
		act.r.log.Fatalf("invalid Fact type: got %T, want pointer", t)
 | 
						|
	}
 | 
						|
	return t
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) persistFactsToCache() error {
 | 
						|
	analyzer := act.a
 | 
						|
	if len(analyzer.FactTypes) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Merge new facts into the package and persist them.
 | 
						|
	var facts []Fact
 | 
						|
	for key, fact := range act.packageFacts {
 | 
						|
		if key.pkg != act.pkg.Types {
 | 
						|
			// The fact is from inherited facts from another package
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		facts = append(facts, Fact{
 | 
						|
			Path: "",
 | 
						|
			Fact: fact,
 | 
						|
		})
 | 
						|
	}
 | 
						|
	for key, fact := range act.objectFacts {
 | 
						|
		obj := key.obj
 | 
						|
		if obj.Pkg() != act.pkg.Types {
 | 
						|
			// The fact is from inherited facts from another package
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		path, err := objectpath.For(obj)
 | 
						|
		if err != nil {
 | 
						|
			// The object is not globally addressable
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		facts = append(facts, Fact{
 | 
						|
			Path: string(path),
 | 
						|
			Fact: fact,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	factsCacheDebugf("Caching %d facts for package %q and analyzer %s", len(facts), act.pkg.Name, act.a.Name)
 | 
						|
 | 
						|
	key := fmt.Sprintf("%s/facts", analyzer.Name)
 | 
						|
	return act.r.pkgCache.Put(act.pkg, pkgcache.HashModeNeedAllDeps, key, facts)
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) loadPersistedFacts() bool {
 | 
						|
	var facts []Fact
 | 
						|
	key := fmt.Sprintf("%s/facts", act.a.Name)
 | 
						|
	if err := act.r.pkgCache.Get(act.pkg, pkgcache.HashModeNeedAllDeps, key, &facts); err != nil {
 | 
						|
		if !errors.Is(err, pkgcache.ErrMissing) && !errors.Is(err, io.EOF) {
 | 
						|
			act.r.log.Warnf("Failed to get persisted facts: %s", err)
 | 
						|
		}
 | 
						|
 | 
						|
		factsCacheDebugf("No cached facts for package %q and analyzer %s", act.pkg.Name, act.a.Name)
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	factsCacheDebugf("Loaded %d cached facts for package %q and analyzer %s", len(facts), act.pkg.Name, act.a.Name)
 | 
						|
 | 
						|
	for _, f := range facts {
 | 
						|
		if f.Path == "" { // this is a package fact
 | 
						|
			key := packageFactKey{act.pkg.Types, act.factType(f.Fact)}
 | 
						|
			act.packageFacts[key] = f.Fact
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		obj, err := objectpath.Object(act.pkg.Types, objectpath.Path(f.Path))
 | 
						|
		if err != nil {
 | 
						|
			// Be lenient about these errors. For example, when
 | 
						|
			// analyzing io/ioutil from source, we may get a fact
 | 
						|
			// for methods on the devNull type, and objectpath
 | 
						|
			// will happily create a path for them. However, when
 | 
						|
			// we later load io/ioutil from export data, the path
 | 
						|
			// no longer resolves.
 | 
						|
			//
 | 
						|
			// If an exported type embeds the unexported type,
 | 
						|
			// then (part of) the unexported type will become part
 | 
						|
			// of the type information and our path will resolve
 | 
						|
			// again.
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		factKey := objectFactKey{obj, act.factType(f.Fact)}
 | 
						|
		act.objectFacts[factKey] = f.Fact
 | 
						|
	}
 | 
						|
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func (act *action) markDepsForAnalyzingSource() {
 | 
						|
	// Horizontal deps (analyzer.Requires) must be loaded from source and analyzed before analyzing
 | 
						|
	// this action.
 | 
						|
	for _, dep := range act.deps {
 | 
						|
		if dep.pkg == act.pkg {
 | 
						|
			// Analyze source only for horizontal dependencies, e.g. from "buildssa".
 | 
						|
			dep.needAnalyzeSource = true // can't be set in parallel
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |