Skip to content

Commit

Permalink
fix: Go version propagation (#5109)
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez authored Nov 8, 2024
1 parent 874a8ba commit 8c0c515
Show file tree
Hide file tree
Showing 10 changed files with 681 additions and 627 deletions.
11 changes: 0 additions & 11 deletions pkg/config/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,17 +302,6 @@ func (l *Loader) handleGoVersion() {

l.cfg.LintersSettings.Gocritic.Go = trimmedGoVersion

// staticcheck related linters.
if l.cfg.LintersSettings.Staticcheck.GoVersion == "" {
l.cfg.LintersSettings.Staticcheck.GoVersion = trimmedGoVersion
}
if l.cfg.LintersSettings.Gosimple.GoVersion == "" {
l.cfg.LintersSettings.Gosimple.GoVersion = trimmedGoVersion
}
if l.cfg.LintersSettings.Stylecheck.GoVersion == "" {
l.cfg.LintersSettings.Stylecheck.GoVersion = trimmedGoVersion
}

os.Setenv("GOSECGOVERSION", l.cfg.Run.Go)
}

Expand Down
5 changes: 0 additions & 5 deletions pkg/goanalysis/runner.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
// checker is a partial copy of https://github.com/golang/tools/blob/master/go/analysis/internal/checker
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package goanalysis defines the implementation of the checker commands.
// The same code drives the multi-analysis driver, the single-analysis
// driver that is conventionally provided for convenience along with
Expand Down
318 changes: 0 additions & 318 deletions pkg/goanalysis/runner_action.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
package goanalysis

import (
"errors"
"fmt"
"go/types"
"io"
"reflect"
"runtime/debug"
"time"

"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/cache"
"github.com/golangci/golangci-lint/internal/errorutil"
"github.com/golangci/golangci-lint/pkg/goanalysis/pkgerrors"
)

type actionAllocator struct {
Expand All @@ -39,54 +28,6 @@ func (actAlloc *actionAllocator) alloc() *action {
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 {
Expand All @@ -113,265 +54,6 @@ func (act *action) analyzeSafe() {
act.r.sw.TrackStage(act.a.Name, 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 error
for _, dep := range act.deps {
if dep.err == nil {
continue
}

depErrors = errors.Join(depErrors, errors.Unwrap(dep.err))
}
if depErrors != nil {
act.err = fmt.Errorf("failed prerequisites: %w", 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", &pkgerrors.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, cache.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, cache.HashModeNeedAllDeps, key, &facts); err != nil {
if !errors.Is(err, cache.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.
Expand Down
Loading

0 comments on commit 8c0c515

Please sign in to comment.