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