gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/simpler/lint/lint.go (about)

     1  // Copyright (c) 2013 The Go Authors. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file or at
     5  // https://developers.google.com/open-source/licenses/bsd.
     6  
     7  // Package lint provides the foundation for tools like gosimple.
     8  package lint // import "github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/lint"
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"go/ast"
    14  	"go/constant"
    15  	"go/printer"
    16  	"go/token"
    17  	"go/types"
    18  	"path/filepath"
    19  	"runtime"
    20  	"sort"
    21  	"strings"
    22  	"sync"
    23  
    24  	"github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/ssa"
    25  	"github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/ssa/ssautil"
    26  	"golang.org/x/tools/go/ast/astutil"
    27  	"golang.org/x/tools/go/loader"
    28  )
    29  
    30  type Job struct {
    31  	Program *Program
    32  
    33  	check    string
    34  	problems []Problem
    35  }
    36  
    37  type Ignore struct {
    38  	Pattern string
    39  	Checks  []string
    40  }
    41  
    42  type Program struct {
    43  	SSA  *ssa.Program
    44  	Prog *loader.Program
    45  	// TODO(dh): Rename to InitialPackages?
    46  	Packages         []*Pkg
    47  	InitialFunctions []*ssa.Function
    48  	AllFunctions     []*ssa.Function
    49  	Files            []*ast.File
    50  	Info             *types.Info
    51  	GoVersion        int
    52  
    53  	tokenFileMap map[*token.File]*ast.File
    54  	astFileMap   map[*ast.File]*Pkg
    55  }
    56  
    57  type Func func(*Job)
    58  
    59  // Problem represents a problem in some source code.
    60  type Problem struct {
    61  	Position token.Pos // position in source file
    62  	Text     string    // the prose that describes the problem
    63  }
    64  
    65  func (p *Problem) String() string {
    66  	return p.Text
    67  }
    68  
    69  type Checker interface {
    70  	Init(*Program)
    71  	Funcs() map[string]Func
    72  }
    73  
    74  // A Linter lints Go source code.
    75  type Linter struct {
    76  	Checker   Checker
    77  	Ignores   []Ignore
    78  	GoVersion int
    79  }
    80  
    81  func (l *Linter) ignore(j *Job, p Problem) bool {
    82  	tf := j.Program.SSA.Fset.File(p.Position)
    83  	f := j.Program.tokenFileMap[tf]
    84  	pkg := j.Program.astFileMap[f].Pkg
    85  
    86  	for _, ig := range l.Ignores {
    87  		pkgpath := pkg.Path()
    88  		if strings.HasSuffix(pkgpath, "_test") {
    89  			pkgpath = pkgpath[:len(pkgpath)-len("_test")]
    90  		}
    91  		name := filepath.Join(pkgpath, filepath.Base(tf.Name()))
    92  		if m, _ := filepath.Match(ig.Pattern, name); !m {
    93  			continue
    94  		}
    95  		for _, c := range ig.Checks {
    96  			if m, _ := filepath.Match(c, j.check); m {
    97  				return true
    98  			}
    99  		}
   100  	}
   101  	return false
   102  }
   103  
   104  func (j *Job) File(node Positioner) *ast.File {
   105  	return j.Program.tokenFileMap[j.Program.SSA.Fset.File(node.Pos())]
   106  }
   107  
   108  // TODO(dh): switch to sort.Slice when Go 1.9 lands.
   109  type byPosition struct {
   110  	fset *token.FileSet
   111  	ps   []Problem
   112  }
   113  
   114  func (ps byPosition) Len() int {
   115  	return len(ps.ps)
   116  }
   117  
   118  func (ps byPosition) Less(i int, j int) bool {
   119  	pi, pj := ps.fset.Position(ps.ps[i].Position), ps.fset.Position(ps.ps[j].Position)
   120  
   121  	if pi.Filename != pj.Filename {
   122  		return pi.Filename < pj.Filename
   123  	}
   124  	if pi.Line != pj.Line {
   125  		return pi.Line < pj.Line
   126  	}
   127  	if pi.Column != pj.Column {
   128  		return pi.Column < pj.Column
   129  	}
   130  
   131  	return ps.ps[i].Text < ps.ps[j].Text
   132  }
   133  
   134  func (ps byPosition) Swap(i int, j int) {
   135  	ps.ps[i], ps.ps[j] = ps.ps[j], ps.ps[i]
   136  }
   137  
   138  func (l *Linter) Lint(lprog *loader.Program) []Problem {
   139  	ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
   140  	ssaprog.Build()
   141  	pkgMap := map[*ssa.Package]*Pkg{}
   142  	var pkgs []*Pkg
   143  	for _, pkginfo := range lprog.InitialPackages() {
   144  		ssapkg := ssaprog.Package(pkginfo.Pkg)
   145  		pkg := &Pkg{
   146  			Package: ssapkg,
   147  			Info:    pkginfo,
   148  		}
   149  		pkgMap[ssapkg] = pkg
   150  		pkgs = append(pkgs, pkg)
   151  	}
   152  	prog := &Program{
   153  		SSA:          ssaprog,
   154  		Prog:         lprog,
   155  		Packages:     pkgs,
   156  		Info:         &types.Info{},
   157  		GoVersion:    l.GoVersion,
   158  		tokenFileMap: map[*token.File]*ast.File{},
   159  		astFileMap:   map[*ast.File]*Pkg{},
   160  	}
   161  	initial := map[*types.Package]struct{}{}
   162  	for _, pkg := range pkgs {
   163  		initial[pkg.Info.Pkg] = struct{}{}
   164  	}
   165  	for fn := range ssautil.AllFunctions(ssaprog) {
   166  		if fn.Pkg == nil {
   167  			continue
   168  		}
   169  		prog.AllFunctions = append(prog.AllFunctions, fn)
   170  		if _, ok := initial[fn.Pkg.Pkg]; ok {
   171  			prog.InitialFunctions = append(prog.InitialFunctions, fn)
   172  		}
   173  	}
   174  	for _, pkg := range pkgs {
   175  		prog.Files = append(prog.Files, pkg.Info.Files...)
   176  
   177  		ssapkg := ssaprog.Package(pkg.Info.Pkg)
   178  		for _, f := range pkg.Info.Files {
   179  			tf := lprog.Fset.File(f.Pos())
   180  			prog.tokenFileMap[tf] = f
   181  			prog.astFileMap[f] = pkgMap[ssapkg]
   182  		}
   183  	}
   184  
   185  	sizes := struct {
   186  		types      int
   187  		defs       int
   188  		uses       int
   189  		implicits  int
   190  		selections int
   191  		scopes     int
   192  	}{}
   193  	for _, pkg := range pkgs {
   194  		sizes.types += len(pkg.Info.Info.Types)
   195  		sizes.defs += len(pkg.Info.Info.Defs)
   196  		sizes.uses += len(pkg.Info.Info.Uses)
   197  		sizes.implicits += len(pkg.Info.Info.Implicits)
   198  		sizes.selections += len(pkg.Info.Info.Selections)
   199  		sizes.scopes += len(pkg.Info.Info.Scopes)
   200  	}
   201  	prog.Info.Types = make(map[ast.Expr]types.TypeAndValue, sizes.types)
   202  	prog.Info.Defs = make(map[*ast.Ident]types.Object, sizes.defs)
   203  	prog.Info.Uses = make(map[*ast.Ident]types.Object, sizes.uses)
   204  	prog.Info.Implicits = make(map[ast.Node]types.Object, sizes.implicits)
   205  	prog.Info.Selections = make(map[*ast.SelectorExpr]*types.Selection, sizes.selections)
   206  	prog.Info.Scopes = make(map[ast.Node]*types.Scope, sizes.scopes)
   207  	for _, pkg := range pkgs {
   208  		for k, v := range pkg.Info.Info.Types {
   209  			prog.Info.Types[k] = v
   210  		}
   211  		for k, v := range pkg.Info.Info.Defs {
   212  			prog.Info.Defs[k] = v
   213  		}
   214  		for k, v := range pkg.Info.Info.Uses {
   215  			prog.Info.Uses[k] = v
   216  		}
   217  		for k, v := range pkg.Info.Info.Implicits {
   218  			prog.Info.Implicits[k] = v
   219  		}
   220  		for k, v := range pkg.Info.Info.Selections {
   221  			prog.Info.Selections[k] = v
   222  		}
   223  		for k, v := range pkg.Info.Info.Scopes {
   224  			prog.Info.Scopes[k] = v
   225  		}
   226  	}
   227  	l.Checker.Init(prog)
   228  
   229  	funcs := l.Checker.Funcs()
   230  	var keys []string
   231  	for k := range funcs {
   232  		keys = append(keys, k)
   233  	}
   234  	sort.Strings(keys)
   235  
   236  	var jobs []*Job
   237  	for _, k := range keys {
   238  		j := &Job{
   239  			Program: prog,
   240  			check:   k,
   241  		}
   242  		jobs = append(jobs, j)
   243  	}
   244  	wg := &sync.WaitGroup{}
   245  	for _, j := range jobs {
   246  		wg.Add(1)
   247  		go func(j *Job) {
   248  			defer wg.Done()
   249  			fn := funcs[j.check]
   250  			if fn == nil {
   251  				return
   252  			}
   253  			fn(j)
   254  		}(j)
   255  	}
   256  	wg.Wait()
   257  
   258  	var out []Problem
   259  	for _, j := range jobs {
   260  		for _, p := range j.problems {
   261  			if !l.ignore(j, p) {
   262  				out = append(out, p)
   263  			}
   264  		}
   265  	}
   266  
   267  	sort.Sort(byPosition{lprog.Fset, out})
   268  	return out
   269  }
   270  
   271  // Pkg represents a package being linted.
   272  type Pkg struct {
   273  	*ssa.Package
   274  	Info *loader.PackageInfo
   275  }
   276  
   277  type packager interface {
   278  	Package() *ssa.Package
   279  }
   280  
   281  func IsExample(fn *ssa.Function) bool {
   282  	if !strings.HasPrefix(fn.Name(), "Example") {
   283  		return false
   284  	}
   285  	f := fn.Prog.Fset.File(fn.Pos())
   286  	if f == nil {
   287  		return false
   288  	}
   289  	return strings.HasSuffix(f.Name(), "_test.go")
   290  }
   291  
   292  func (j *Job) IsInTest(node Positioner) bool {
   293  	f := j.Program.SSA.Fset.File(node.Pos())
   294  	return f != nil && strings.HasSuffix(f.Name(), "_test.go")
   295  }
   296  
   297  func (j *Job) IsInMain(node Positioner) bool {
   298  	if node, ok := node.(packager); ok {
   299  		return node.Package().Pkg.Name() == "main"
   300  	}
   301  	pkg := j.NodePackage(node)
   302  	if pkg == nil {
   303  		return false
   304  	}
   305  	return pkg.Pkg.Name() == "main"
   306  }
   307  
   308  type Positioner interface {
   309  	Pos() token.Pos
   310  }
   311  
   312  func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem {
   313  	problem := Problem{
   314  		Position: n.Pos(),
   315  		Text:     fmt.Sprintf(format, args...) + fmt.Sprintf(" (%s)", j.check),
   316  	}
   317  	j.problems = append(j.problems, problem)
   318  	return &j.problems[len(j.problems)-1]
   319  }
   320  
   321  func (j *Job) Render(x interface{}) string {
   322  	fset := j.Program.SSA.Fset
   323  	var buf bytes.Buffer
   324  	if err := printer.Fprint(&buf, fset, x); err != nil {
   325  		panic(err)
   326  	}
   327  	return buf.String()
   328  }
   329  
   330  func (j *Job) RenderArgs(args []ast.Expr) string {
   331  	var ss []string
   332  	for _, arg := range args {
   333  		ss = append(ss, j.Render(arg))
   334  	}
   335  	return strings.Join(ss, ", ")
   336  }
   337  
   338  func IsIdent(expr ast.Expr, ident string) bool {
   339  	id, ok := expr.(*ast.Ident)
   340  	return ok && id.Name == ident
   341  }
   342  
   343  // isBlank returns whether id is the blank identifier "_".
   344  // If id == nil, the answer is false.
   345  func IsBlank(id ast.Expr) bool {
   346  	ident, ok := id.(*ast.Ident)
   347  	return ok && ident.Name == "_"
   348  }
   349  
   350  func IsZero(expr ast.Expr) bool {
   351  	lit, ok := expr.(*ast.BasicLit)
   352  	return ok && lit.Kind == token.INT && lit.Value == "0"
   353  }
   354  
   355  func (j *Job) IsNil(expr ast.Expr) bool {
   356  	return j.Program.Info.Types[expr].IsNil()
   357  }
   358  
   359  func (j *Job) BoolConst(expr ast.Expr) bool {
   360  	val := j.Program.Info.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
   361  	return constant.BoolVal(val)
   362  }
   363  
   364  func (j *Job) IsBoolConst(expr ast.Expr) bool {
   365  	// We explicitly don't support typed bools because more often than
   366  	// not, custom bool types are used as binary enums and the
   367  	// explicit comparison is desired.
   368  
   369  	ident, ok := expr.(*ast.Ident)
   370  	if !ok {
   371  		return false
   372  	}
   373  	obj := j.Program.Info.ObjectOf(ident)
   374  	c, ok := obj.(*types.Const)
   375  	if !ok {
   376  		return false
   377  	}
   378  	basic, ok := c.Type().(*types.Basic)
   379  	if !ok {
   380  		return false
   381  	}
   382  	if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
   383  		return false
   384  	}
   385  	return true
   386  }
   387  
   388  func (j *Job) ExprToInt(expr ast.Expr) (int64, bool) {
   389  	tv := j.Program.Info.Types[expr]
   390  	if tv.Value == nil {
   391  		return 0, false
   392  	}
   393  	if tv.Value.Kind() != constant.Int {
   394  		return 0, false
   395  	}
   396  	return constant.Int64Val(tv.Value)
   397  }
   398  
   399  func (j *Job) ExprToString(expr ast.Expr) (string, bool) {
   400  	val := j.Program.Info.Types[expr].Value
   401  	if val == nil {
   402  		return "", false
   403  	}
   404  	if val.Kind() != constant.String {
   405  		return "", false
   406  	}
   407  	return constant.StringVal(val), true
   408  }
   409  
   410  func (j *Job) NodePackage(node Positioner) *Pkg {
   411  	f := j.File(node)
   412  	return j.Program.astFileMap[f]
   413  }
   414  
   415  func IsGenerated(f *ast.File) bool {
   416  	comments := f.Comments
   417  	if len(comments) > 0 {
   418  		comment := comments[0].Text()
   419  		return strings.Contains(comment, "Code generated by") ||
   420  			strings.Contains(comment, "DO NOT EDIT")
   421  	}
   422  	return false
   423  }
   424  
   425  func (j *Job) IsGoVersion(minor int) bool {
   426  	return j.Program.GoVersion >= minor
   427  }
   428  
   429  func (j *Job) IsCallToAST(node ast.Node, name string) bool {
   430  	call, ok := node.(*ast.CallExpr)
   431  	if !ok {
   432  		return false
   433  	}
   434  	sel, ok := call.Fun.(*ast.SelectorExpr)
   435  	if !ok {
   436  		return false
   437  	}
   438  	fn, ok := j.Program.Info.ObjectOf(sel.Sel).(*types.Func)
   439  	return ok && fn.FullName() == name
   440  }
   441  
   442  func (j *Job) IsCallToAnyAST(node ast.Node, names ...string) bool {
   443  	for _, name := range names {
   444  		if j.IsCallToAST(node, name) {
   445  			return true
   446  		}
   447  	}
   448  	return false
   449  }
   450  
   451  func CallName(call *ssa.CallCommon) string {
   452  	if call.IsInvoke() {
   453  		return ""
   454  	}
   455  	switch v := call.Value.(type) {
   456  	case *ssa.Function:
   457  		fn, ok := v.Object().(*types.Func)
   458  		if !ok {
   459  			return ""
   460  		}
   461  		return fn.FullName()
   462  	case *ssa.Builtin:
   463  		return v.Name()
   464  	}
   465  	return ""
   466  }
   467  
   468  func IsCallTo(call *ssa.CallCommon, name string) bool {
   469  	return CallName(call) == name
   470  }
   471  
   472  func FilterDebug(instr []ssa.Instruction) []ssa.Instruction {
   473  	var out []ssa.Instruction
   474  	for _, ins := range instr {
   475  		if _, ok := ins.(*ssa.DebugRef); !ok {
   476  			out = append(out, ins)
   477  		}
   478  	}
   479  	return out
   480  }
   481  
   482  func NodeFns(pkgs []*Pkg) map[ast.Node]*ssa.Function {
   483  	out := map[ast.Node]*ssa.Function{}
   484  
   485  	wg := &sync.WaitGroup{}
   486  	chNodeFns := make(chan map[ast.Node]*ssa.Function, runtime.NumCPU()*2)
   487  	for _, pkg := range pkgs {
   488  		pkg := pkg
   489  		wg.Add(1)
   490  		go func() {
   491  			m := map[ast.Node]*ssa.Function{}
   492  			for _, f := range pkg.Info.Files {
   493  				ast.Walk(&globalVisitor{m, pkg, f}, f)
   494  			}
   495  			chNodeFns <- m
   496  			wg.Done()
   497  		}()
   498  	}
   499  	go func() {
   500  		wg.Wait()
   501  		close(chNodeFns)
   502  	}()
   503  
   504  	for nodeFns := range chNodeFns {
   505  		for k, v := range nodeFns {
   506  			out[k] = v
   507  		}
   508  	}
   509  
   510  	return out
   511  }
   512  
   513  type globalVisitor struct {
   514  	m   map[ast.Node]*ssa.Function
   515  	pkg *Pkg
   516  	f   *ast.File
   517  }
   518  
   519  func (v *globalVisitor) Visit(node ast.Node) ast.Visitor {
   520  	switch node := node.(type) {
   521  	case *ast.CallExpr:
   522  		v.m[node] = v.pkg.Func("init")
   523  		return v
   524  	case *ast.FuncDecl, *ast.FuncLit:
   525  		nv := &fnVisitor{v.m, v.f, v.pkg, nil}
   526  		return nv.Visit(node)
   527  	default:
   528  		return v
   529  	}
   530  }
   531  
   532  type fnVisitor struct {
   533  	m     map[ast.Node]*ssa.Function
   534  	f     *ast.File
   535  	pkg   *Pkg
   536  	ssafn *ssa.Function
   537  }
   538  
   539  func (v *fnVisitor) Visit(node ast.Node) ast.Visitor {
   540  	switch node := node.(type) {
   541  	case *ast.FuncDecl:
   542  		var ssafn *ssa.Function
   543  		ssafn = v.pkg.Prog.FuncValue(v.pkg.Info.ObjectOf(node.Name).(*types.Func))
   544  		v.m[node] = ssafn
   545  		if ssafn == nil {
   546  			return nil
   547  		}
   548  		return &fnVisitor{v.m, v.f, v.pkg, ssafn}
   549  	case *ast.FuncLit:
   550  		var ssafn *ssa.Function
   551  		path, _ := astutil.PathEnclosingInterval(v.f, node.Pos(), node.Pos())
   552  		ssafn = ssa.EnclosingFunction(v.pkg.Package, path)
   553  		v.m[node] = ssafn
   554  		if ssafn == nil {
   555  			return nil
   556  		}
   557  		return &fnVisitor{v.m, v.f, v.pkg, ssafn}
   558  	case nil:
   559  		return nil
   560  	default:
   561  		v.m[node] = v.ssafn
   562  		return v
   563  	}
   564  }