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

@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"io"
"os"
)
var (
@ -15,8 +16,13 @@ var (
crnl = []byte("\r\n")
)
func isGenerated(r io.Reader) bool {
br := bufio.NewReader(r)
func isGenerated(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
br := bufio.NewReader(f)
for {
s, err := br.ReadBytes('\n')
if err != nil && err != io.EOF {

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('.')
}
}

View File

@ -30,7 +30,7 @@ func CallName(call *ssa.CallCommon) string {
if !ok {
return ""
}
return fn.FullName()
return lint.FuncName(fn)
case *ssa.Builtin:
return v.Name()
}
@ -63,7 +63,7 @@ func IsExample(fn *ssa.Function) bool {
func IsPointerLike(T types.Type) bool {
switch T := T.Underlying().(type) {
case *types.Interface, *types.Chan, *types.Map, *types.Pointer:
case *types.Interface, *types.Chan, *types.Map, *types.Signature, *types.Pointer:
return true
case *types.Basic:
return T.Kind() == types.UnsafePointer
@ -103,26 +103,14 @@ func IsZero(expr ast.Expr) bool {
return IsIntLiteral(expr, "0")
}
func TypeOf(j *lint.Job, expr ast.Expr) types.Type {
if expr == nil {
return nil
}
return j.NodePackage(expr).TypesInfo.TypeOf(expr)
}
func IsOfType(j *lint.Job, expr ast.Expr, name string) bool { return IsType(TypeOf(j, expr), name) }
func ObjectOf(j *lint.Job, ident *ast.Ident) types.Object {
if ident == nil {
return nil
}
return j.NodePackage(ident).TypesInfo.ObjectOf(ident)
func IsOfType(j *lint.Job, expr ast.Expr, name string) bool {
return IsType(j.Pkg.TypesInfo.TypeOf(expr), name)
}
func IsInTest(j *lint.Job, node lint.Positioner) bool {
// FIXME(dh): this doesn't work for global variables with
// initializers
f := j.Program.SSA.Fset.File(node.Pos())
f := j.Pkg.Fset.File(node.Pos())
return f != nil && strings.HasSuffix(f.Name(), "_test.go")
}
@ -130,15 +118,11 @@ func IsInMain(j *lint.Job, node lint.Positioner) bool {
if node, ok := node.(packager); ok {
return node.Package().Pkg.Name() == "main"
}
pkg := j.NodePackage(node)
if pkg == nil {
return false
}
return pkg.Types.Name() == "main"
return j.Pkg.Types.Name() == "main"
}
func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
info := j.NodePackage(expr).TypesInfo
info := j.Pkg.TypesInfo
sel := info.Selections[expr]
if sel == nil {
if x, ok := expr.X.(*ast.Ident); ok {
@ -155,11 +139,11 @@ func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
}
func IsNil(j *lint.Job, expr ast.Expr) bool {
return j.NodePackage(expr).TypesInfo.Types[expr].IsNil()
return j.Pkg.TypesInfo.Types[expr].IsNil()
}
func BoolConst(j *lint.Job, expr ast.Expr) bool {
val := j.NodePackage(expr).TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
val := j.Pkg.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
return constant.BoolVal(val)
}
@ -172,7 +156,7 @@ func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
if !ok {
return false
}
obj := j.NodePackage(expr).TypesInfo.ObjectOf(ident)
obj := j.Pkg.TypesInfo.ObjectOf(ident)
c, ok := obj.(*types.Const)
if !ok {
return false
@ -188,7 +172,7 @@ func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
}
func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
tv := j.NodePackage(expr).TypesInfo.Types[expr]
tv := j.Pkg.TypesInfo.Types[expr]
if tv.Value == nil {
return 0, false
}
@ -199,7 +183,7 @@ func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
}
func ExprToString(j *lint.Job, expr ast.Expr) (string, bool) {
val := j.NodePackage(expr).TypesInfo.Types[expr].Value
val := j.Pkg.TypesInfo.Types[expr].Value
if val == nil {
return "", false
}
@ -229,22 +213,22 @@ func DereferenceR(T types.Type) types.Type {
}
func IsGoVersion(j *lint.Job, minor int) bool {
return j.Program.GoVersion >= minor
return j.GoVersion >= minor
}
func CallNameAST(j *lint.Job, call *ast.CallExpr) string {
switch fun := call.Fun.(type) {
case *ast.SelectorExpr:
fn, ok := ObjectOf(j, fun.Sel).(*types.Func)
fn, ok := j.Pkg.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
if !ok {
return ""
}
return fn.FullName()
return lint.FuncName(fn)
case *ast.Ident:
obj := ObjectOf(j, fun)
obj := j.Pkg.TypesInfo.ObjectOf(fun)
switch obj := obj.(type) {
case *types.Func:
return obj.FullName()
return lint.FuncName(obj)
case *types.Builtin:
return obj.Name()
default:
@ -273,9 +257,8 @@ func IsCallToAnyAST(j *lint.Job, node ast.Node, names ...string) bool {
}
func Render(j *lint.Job, x interface{}) string {
fset := j.Program.SSA.Fset
var buf bytes.Buffer
if err := printer.Fprint(&buf, fset, x); err != nil {
if err := printer.Fprint(&buf, j.Pkg.Fset, x); err != nil {
panic(err)
}
return buf.String()
@ -311,11 +294,10 @@ func Inspect(node ast.Node, fn func(node ast.Node) bool) {
ast.Inspect(node, fn)
}
func GroupSpecs(j *lint.Job, specs []ast.Spec) [][]ast.Spec {
func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec {
if len(specs) == 0 {
return nil
}
fset := j.Program.SSA.Fset
groups := make([][]ast.Spec, 1)
groups[0] = append(groups[0], specs[0])

View File

@ -17,6 +17,7 @@ import (
"os"
"regexp"
"runtime"
"runtime/debug"
"runtime/pprof"
"strconv"
"strings"
@ -109,6 +110,7 @@ func FlagSet(name string) *flag.FlagSet {
flags.Bool("version", false, "Print version and exit")
flags.Bool("show-ignored", false, "Don't filter ignored problems")
flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
flags.String("explain", "", "Print description of `check`")
flags.Int("debug.max-concurrent-jobs", 0, "Number of jobs to run concurrently")
flags.Bool("debug.print-stats", false, "Print debug statistics")
@ -131,7 +133,22 @@ func FlagSet(name string) *flag.FlagSet {
return flags
}
func findCheck(cs []lint.Checker, check string) (lint.Check, bool) {
for _, c := range cs {
for _, cc := range c.Checks() {
if cc.ID == check {
return cc, true
}
}
}
return lint.Check{}, false
}
func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
if _, ok := os.LookupEnv("GOGC"); !ok {
debug.SetGCPercent(50)
}
tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string)
tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
@ -139,6 +156,7 @@ func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
formatter := fs.Lookup("f").Value.(flag.Getter).Get().(string)
printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
explain := fs.Lookup("explain").Value.(flag.Getter).Get().(string)
maxConcurrentJobs := fs.Lookup("debug.max-concurrent-jobs").Value.(flag.Getter).Get().(int)
printStats := fs.Lookup("debug.print-stats").Value.(flag.Getter).Get().(bool)
@ -175,6 +193,20 @@ func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
exit(0)
}
if explain != "" {
check, ok := findCheck(cs, explain)
if !ok {
fmt.Fprintln(os.Stderr, "Couldn't find check", explain)
exit(1)
}
if check.Doc == "" {
fmt.Fprintln(os.Stderr, explain, "has no documentation")
exit(1)
}
fmt.Println(check.Doc)
exit(0)
}
ps, err := Lint(cs, fs.Args(), &Options{
Tags: strings.Fields(tags),
LintTests: tests,
@ -279,6 +311,7 @@ func Lint(cs []lint.Checker, paths []string, opt *Options) ([]lint.Problem, erro
return nil, err
}
stats.PackageLoading = time.Since(t)
runtime.GC()
var problems []lint.Problem
workingPkgs := make([]*packages.Package, 0, len(pkgs))
@ -346,7 +379,6 @@ func compileErrors(pkg *packages.Package) []lint.Problem {
p := lint.Problem{
Position: parsePos(err.Pos),
Text: err.Msg,
Checker: "compiler",
Check: "compile",
}
ps = append(ps, p)