Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Go version propagation #5109

Merged
merged 19 commits into from
Nov 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions pkg/config/loader.go
Original file line number Diff line number Diff line change
@@ -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)
}

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
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 {
@@ -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 {
@@ -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.
Loading