1
0

Updated staticcheck

This commit is contained in:
kolaente
2019-04-22 12:59:42 +02:00
parent 5dc1fd0f86
commit 99f83542f6
34 changed files with 6144 additions and 2537 deletions

View File

@ -2,6 +2,7 @@
package lint // import "honnef.co/go/tools/lint"
import (
"bytes"
"fmt"
"go/ast"
"go/token"
@ -9,12 +10,14 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"time"
"unicode"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/packages"
"honnef.co/go/tools/config"
"honnef.co/go/tools/ssa"
@ -22,9 +25,9 @@ import (
)
type Job struct {
Program *Program
Pkg *Pkg
GoVersion int
checker string
check Check
problems []Problem
@ -106,20 +109,10 @@ func (gi *GlobIgnore) Match(p Problem) bool {
}
type Program struct {
SSA *ssa.Program
InitialPackages []*Pkg
InitialFunctions []*ssa.Function
AllPackages []*packages.Package
AllFunctions []*ssa.Function
Files []*ast.File
GoVersion int
tokenFileMap map[*token.File]*ast.File
astFileMap map[*ast.File]*Pkg
packagesMap map[string]*packages.Package
genMu sync.RWMutex
generatedMap map[string]bool
SSA *ssa.Program
InitialPackages []*Pkg
AllPackages []*packages.Package
AllFunctions []*ssa.Function
}
func (prog *Program) Fset() *token.FileSet {
@ -141,7 +134,6 @@ type Problem struct {
Position token.Position // position in source file
Text string // the prose that describes the problem
Check string
Checker string
Package *Pkg
Severity Severity
}
@ -164,6 +156,7 @@ type Check struct {
Fn Func
ID string
FilterGenerated bool
Doc string
}
// A Linter lints Go source code.
@ -205,12 +198,8 @@ func (l *Linter) ignore(p Problem) bool {
return false
}
func (prog *Program) File(node Positioner) *ast.File {
return prog.tokenFileMap[prog.SSA.Fset.File(node.Pos())]
}
func (j *Job) File(node Positioner) *ast.File {
return j.Program.File(node)
return j.Pkg.tokenFileMap[j.Pkg.Fset.File(node.Pos())]
}
func parseDirective(s string) (cmd string, args []string) {
@ -266,6 +255,7 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
if stats != nil {
stats.SSABuild = time.Since(t)
}
runtime.GC()
t = time.Now()
pkgMap := map[*ssa.Package]*Pkg{}
@ -291,9 +281,19 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
}
pkg := &Pkg{
SSA: ssapkg,
Package: pkg,
Config: cfg,
SSA: ssapkg,
Package: pkg,
Config: cfg,
Generated: map[string]bool{},
tokenFileMap: map[*token.File]*ast.File{},
}
pkg.Inspector = inspector.New(pkg.Syntax)
for _, f := range pkg.Syntax {
tf := pkg.Fset.File(f.Pos())
pkg.tokenFileMap[tf] = f
path := DisplayPosition(pkg.Fset, f.Pos()).Filename
pkg.Generated[path] = isGenerated(path)
}
pkgMap[ssapkg] = pkg
pkgs = append(pkgs, pkg)
@ -303,42 +303,15 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
SSA: ssaprog,
InitialPackages: pkgs,
AllPackages: allPkgs,
GoVersion: l.GoVersion,
tokenFileMap: map[*token.File]*ast.File{},
astFileMap: map[*ast.File]*Pkg{},
generatedMap: map[string]bool{},
}
prog.packagesMap = map[string]*packages.Package{}
for _, pkg := range allPkgs {
prog.packagesMap[pkg.Types.Path()] = pkg
}
isInitial := map[*types.Package]struct{}{}
for _, pkg := range pkgs {
isInitial[pkg.Types] = struct{}{}
}
for fn := range ssautil.AllFunctions(ssaprog) {
prog.AllFunctions = append(prog.AllFunctions, fn)
if fn.Pkg == nil {
continue
}
prog.AllFunctions = append(prog.AllFunctions, fn)
if _, ok := isInitial[fn.Pkg.Pkg]; ok {
prog.InitialFunctions = append(prog.InitialFunctions, fn)
}
}
for _, pkg := range pkgs {
prog.Files = append(prog.Files, pkg.Syntax...)
ssapkg := ssaprog.Package(pkg.Types)
for _, f := range pkg.Syntax {
prog.astFileMap[f] = pkgMap[ssapkg]
}
}
for _, pkg := range allPkgs {
for _, f := range pkg.Syntax {
tf := pkg.Fset.File(f.Pos())
prog.tokenFileMap[tf] = f
if pkg, ok := pkgMap[fn.Pkg]; ok {
pkg.InitialFunctions = append(pkg.InitialFunctions, fn)
}
}
@ -346,6 +319,19 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
l.automaticIgnores = nil
for _, pkg := range initial {
for _, f := range pkg.Syntax {
found := false
commentLoop:
for _, cg := range f.Comments {
for _, c := range cg.List {
if strings.Contains(c.Text, "//lint:") {
found = true
break commentLoop
}
}
}
if !found {
continue
}
cm := ast.NewCommentMap(pkg.Fset, f, f.Comments)
for node, cgs := range cm {
for _, cg := range cgs {
@ -359,10 +345,9 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
if len(args) < 2 {
// FIXME(dh): this causes duplicated warnings when using megacheck
p := Problem{
Position: prog.DisplayPosition(c.Pos()),
Position: DisplayPosition(prog.Fset(), c.Pos()),
Text: "malformed linter directive; missing the required reason field?",
Check: "",
Checker: "lint",
Package: nil,
}
out = append(out, p)
@ -373,7 +358,7 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
continue
}
checks := strings.Split(args[0], ",")
pos := prog.DisplayPosition(node.Pos())
pos := DisplayPosition(prog.Fset(), node.Pos())
var ig Ignore
switch cmd {
case "ignore":
@ -396,23 +381,6 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
}
}
sizes := struct {
types int
defs int
uses int
implicits int
selections int
scopes int
}{}
for _, pkg := range pkgs {
sizes.types += len(pkg.TypesInfo.Types)
sizes.defs += len(pkg.TypesInfo.Defs)
sizes.uses += len(pkg.TypesInfo.Uses)
sizes.implicits += len(pkg.TypesInfo.Implicits)
sizes.selections += len(pkg.TypesInfo.Selections)
sizes.scopes += len(pkg.TypesInfo.Scopes)
}
if stats != nil {
stats.OtherInitWork = time.Since(t)
}
@ -428,41 +396,31 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
var jobs []*Job
var allChecks []string
var wg sync.WaitGroup
for _, checker := range l.Checkers {
checks := checker.Checks()
for _, check := range checks {
for _, check := range checker.Checks() {
allChecks = append(allChecks, check.ID)
j := &Job{
Program: prog,
checker: checker.Name(),
check: check,
if check.Fn == nil {
continue
}
for _, pkg := range pkgs {
j := &Job{
Pkg: pkg,
check: check,
GoVersion: l.GoVersion,
}
jobs = append(jobs, j)
wg.Add(1)
go func(check Check, j *Job) {
t := time.Now()
check.Fn(j)
j.duration = time.Since(t)
wg.Done()
}(check, j)
}
jobs = append(jobs, j)
}
}
max := len(jobs)
if l.MaxConcurrentJobs > 0 {
max = l.MaxConcurrentJobs
}
sem := make(chan struct{}, max)
wg := &sync.WaitGroup{}
for _, j := range jobs {
wg.Add(1)
go func(j *Job) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
fn := j.check.Fn
if fn == nil {
return
}
t := time.Now()
fn(j)
j.duration = time.Since(t)
}(j)
}
wg.Wait()
for _, j := range jobs {
@ -470,6 +428,9 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
stats.Jobs = append(stats.Jobs, JobStat{j.check.ID, j.duration})
}
for _, p := range j.problems {
if p.Package == nil {
panic(fmt.Sprintf("internal error: problem at position %s has nil package", p.Position))
}
allowedChecks := FilterChecks(allChecks, p.Package.Config.Checks)
if l.ignore(p) {
@ -498,19 +459,21 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
}
couldveMatched := false
for f, pkg := range prog.astFileMap {
if prog.Fset().Position(f.Pos()).Filename != ig.File {
continue
}
allowedChecks := FilterChecks(allChecks, pkg.Config.Checks)
for _, c := range ig.Checks {
if !allowedChecks[c] {
for _, pkg := range pkgs {
for _, f := range pkg.tokenFileMap {
if prog.Fset().Position(f.Pos()).Filename != ig.File {
continue
}
couldveMatched = true
allowedChecks := FilterChecks(allChecks, pkg.Config.Checks)
for _, c := range ig.Checks {
if !allowedChecks[c] {
continue
}
couldveMatched = true
break
}
break
}
break
}
if !couldveMatched {
@ -519,10 +482,9 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
continue
}
p := Problem{
Position: prog.DisplayPosition(ig.pos),
Position: DisplayPosition(prog.Fset(), ig.pos),
Text: "this linter directive didn't match anything; should it be removed?",
Check: "",
Checker: "lint",
Package: nil,
}
out = append(out, p)
@ -609,28 +571,30 @@ func FilterChecks(allChecks []string, checks []string) map[string]bool {
return allowedChecks
}
func (prog *Program) Package(path string) *packages.Package {
return prog.packagesMap[path]
}
// Pkg represents a package being linted.
type Pkg struct {
SSA *ssa.Package
SSA *ssa.Package
InitialFunctions []*ssa.Function
*packages.Package
Config config.Config
Config config.Config
Inspector *inspector.Inspector
// TODO(dh): this map should probably map from *ast.File, not string
Generated map[string]bool
tokenFileMap map[*token.File]*ast.File
}
type Positioner interface {
Pos() token.Pos
}
func (prog *Program) DisplayPosition(p token.Pos) token.Position {
func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
// Only use the adjusted position if it points to another Go file.
// This means we'll point to the original file for cgo files, but
// we won't point to a YACC grammar file.
pos := prog.Fset().PositionFor(p, false)
adjPos := prog.Fset().PositionFor(p, true)
pos := fset.PositionFor(p, false)
adjPos := fset.PositionFor(p, true)
if filepath.Ext(adjPos.Filename) == ".go" {
return adjPos
@ -638,60 +602,21 @@ func (prog *Program) DisplayPosition(p token.Pos) token.Position {
return pos
}
func (prog *Program) isGenerated(path string) bool {
// This function isn't very efficient in terms of lock contention
// and lack of parallelism, but it really shouldn't matter.
// Projects consists of thousands of files, and have hundreds of
// errors. That's not a lot of calls to isGenerated.
prog.genMu.RLock()
if b, ok := prog.generatedMap[path]; ok {
prog.genMu.RUnlock()
return b
}
prog.genMu.RUnlock()
prog.genMu.Lock()
defer prog.genMu.Unlock()
// recheck to avoid doing extra work in case of race
if b, ok := prog.generatedMap[path]; ok {
return b
}
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
b := isGenerated(f)
prog.generatedMap[path] = b
return b
}
func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem {
tf := j.Program.SSA.Fset.File(n.Pos())
f := j.Program.tokenFileMap[tf]
pkg := j.Program.astFileMap[f]
pos := j.Program.DisplayPosition(n.Pos())
if j.Program.isGenerated(pos.Filename) && j.check.FilterGenerated {
pos := DisplayPosition(j.Pkg.Fset, n.Pos())
if j.Pkg.Generated[pos.Filename] && j.check.FilterGenerated {
return nil
}
problem := Problem{
Position: pos,
Text: fmt.Sprintf(format, args...),
Check: j.check.ID,
Checker: j.checker,
Package: pkg,
Package: j.Pkg,
}
j.problems = append(j.problems, problem)
return &j.problems[len(j.problems)-1]
}
func (j *Job) NodePackage(node Positioner) *Pkg {
f := j.File(node)
return j.Program.astFileMap[f]
}
func allPackages(pkgs []*packages.Package) []*packages.Package {
var out []*packages.Package
packages.Visit(
@ -704,3 +629,51 @@ func allPackages(pkgs []*packages.Package) []*packages.Package {
)
return out
}
var bufferPool = &sync.Pool{
New: func() interface{} {
buf := bytes.NewBuffer(nil)
buf.Grow(64)
return buf
},
}
func FuncName(f *types.Func) string {
buf := bufferPool.Get().(*bytes.Buffer)
buf.Reset()
if f.Type() != nil {
sig := f.Type().(*types.Signature)
if recv := sig.Recv(); recv != nil {
buf.WriteByte('(')
if _, ok := recv.Type().(*types.Interface); ok {
// gcimporter creates abstract methods of
// named interfaces using the interface type
// (not the named type) as the receiver.
// Don't print it in full.
buf.WriteString("interface")
} else {
types.WriteType(buf, recv.Type(), nil)
}
buf.WriteByte(')')
buf.WriteByte('.')
} else if f.Pkg() != nil {
writePackage(buf, f.Pkg())
}
}
buf.WriteString(f.Name())
s := buf.String()
bufferPool.Put(buf)
return s
}
func writePackage(buf *bytes.Buffer, pkg *types.Package) {
if pkg == nil {
return
}
var s string
s = pkg.Path()
if s != "" {
buf.WriteString(s)
buf.WriteByte('.')
}
}