github.com/mattn/go@v0.0.0-20171011075504-07f7db3ea99f/src/cmd/vet/main.go (about)

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Vet is a simple checker for static errors in Go source code.
     6  // See doc.go for more information.
     7  package main
     8  
     9  import (
    10  	"bytes"
    11  	"flag"
    12  	"fmt"
    13  	"go/ast"
    14  	"go/build"
    15  	"go/parser"
    16  	"go/printer"
    17  	"go/token"
    18  	"go/types"
    19  	"io/ioutil"
    20  	"os"
    21  	"path/filepath"
    22  	"strconv"
    23  	"strings"
    24  )
    25  
    26  // Important! If you add flags here, make sure to update cmd/go/internal/vet/vetflag.go.
    27  
    28  var (
    29  	verbose = flag.Bool("v", false, "verbose")
    30  	source  = flag.Bool("source", false, "import from source instead of compiled object files")
    31  	tags    = flag.String("tags", "", "space-separated list of build tags to apply when parsing")
    32  	tagList = []string{} // exploded version of tags flag; set in main
    33  )
    34  
    35  var exitCode = 0
    36  
    37  // "-all" flag enables all non-experimental checks
    38  var all = triStateFlag("all", unset, "enable all non-experimental checks")
    39  
    40  // Flags to control which individual checks to perform.
    41  var report = map[string]*triState{
    42  	// Only unusual checks are written here.
    43  	// Most checks that operate during the AST walk are added by register.
    44  	"asmdecl":   triStateFlag("asmdecl", unset, "check assembly against Go declarations"),
    45  	"buildtags": triStateFlag("buildtags", unset, "check that +build tags are valid"),
    46  }
    47  
    48  // experimental records the flags enabling experimental features. These must be
    49  // requested explicitly; they are not enabled by -all.
    50  var experimental = map[string]bool{}
    51  
    52  // setTrueCount record how many flags are explicitly set to true.
    53  var setTrueCount int
    54  
    55  // dirsRun and filesRun indicate whether the vet is applied to directory or
    56  // file targets. The distinction affects which checks are run.
    57  var dirsRun, filesRun bool
    58  
    59  // includesNonTest indicates whether the vet is applied to non-test targets.
    60  // Certain checks are relevant only if they touch both test and non-test files.
    61  var includesNonTest bool
    62  
    63  // A triState is a boolean that knows whether it has been set to either true or false.
    64  // It is used to identify if a flag appears; the standard boolean flag cannot
    65  // distinguish missing from unset. It also satisfies flag.Value.
    66  type triState int
    67  
    68  const (
    69  	unset triState = iota
    70  	setTrue
    71  	setFalse
    72  )
    73  
    74  func triStateFlag(name string, value triState, usage string) *triState {
    75  	flag.Var(&value, name, usage)
    76  	return &value
    77  }
    78  
    79  // triState implements flag.Value, flag.Getter, and flag.boolFlag.
    80  // They work like boolean flags: we can say vet -printf as well as vet -printf=true
    81  func (ts *triState) Get() interface{} {
    82  	return *ts == setTrue
    83  }
    84  
    85  func (ts triState) isTrue() bool {
    86  	return ts == setTrue
    87  }
    88  
    89  func (ts *triState) Set(value string) error {
    90  	b, err := strconv.ParseBool(value)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	if b {
    95  		*ts = setTrue
    96  		setTrueCount++
    97  	} else {
    98  		*ts = setFalse
    99  	}
   100  	return nil
   101  }
   102  
   103  func (ts *triState) String() string {
   104  	switch *ts {
   105  	case unset:
   106  		return "true" // An unset flag will be set by -all, so defaults to true.
   107  	case setTrue:
   108  		return "true"
   109  	case setFalse:
   110  		return "false"
   111  	}
   112  	panic("not reached")
   113  }
   114  
   115  func (ts triState) IsBoolFlag() bool {
   116  	return true
   117  }
   118  
   119  // vet tells whether to report errors for the named check, a flag name.
   120  func vet(name string) bool {
   121  	return report[name].isTrue()
   122  }
   123  
   124  // setExit sets the value for os.Exit when it is called, later. It
   125  // remembers the highest value.
   126  func setExit(err int) {
   127  	if err > exitCode {
   128  		exitCode = err
   129  	}
   130  }
   131  
   132  var (
   133  	// Each of these vars has a corresponding case in (*File).Visit.
   134  	assignStmt    *ast.AssignStmt
   135  	binaryExpr    *ast.BinaryExpr
   136  	callExpr      *ast.CallExpr
   137  	compositeLit  *ast.CompositeLit
   138  	exprStmt      *ast.ExprStmt
   139  	forStmt       *ast.ForStmt
   140  	funcDecl      *ast.FuncDecl
   141  	funcLit       *ast.FuncLit
   142  	genDecl       *ast.GenDecl
   143  	interfaceType *ast.InterfaceType
   144  	rangeStmt     *ast.RangeStmt
   145  	returnStmt    *ast.ReturnStmt
   146  	structType    *ast.StructType
   147  
   148  	// checkers is a two-level map.
   149  	// The outer level is keyed by a nil pointer, one of the AST vars above.
   150  	// The inner level is keyed by checker name.
   151  	checkers = make(map[ast.Node]map[string]func(*File, ast.Node))
   152  )
   153  
   154  func register(name, usage string, fn func(*File, ast.Node), types ...ast.Node) {
   155  	report[name] = triStateFlag(name, unset, usage)
   156  	for _, typ := range types {
   157  		m := checkers[typ]
   158  		if m == nil {
   159  			m = make(map[string]func(*File, ast.Node))
   160  			checkers[typ] = m
   161  		}
   162  		m[name] = fn
   163  	}
   164  }
   165  
   166  // Usage is a replacement usage function for the flags package.
   167  func Usage() {
   168  	fmt.Fprintf(os.Stderr, "Usage of vet:\n")
   169  	fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n")
   170  	fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n")
   171  	fmt.Fprintf(os.Stderr, "By default, -all is set and all non-experimental checks are run.\n")
   172  	fmt.Fprintf(os.Stderr, "For more information run\n")
   173  	fmt.Fprintf(os.Stderr, "\tgo doc cmd/vet\n\n")
   174  	fmt.Fprintf(os.Stderr, "Flags:\n")
   175  	flag.PrintDefaults()
   176  	os.Exit(2)
   177  }
   178  
   179  // File is a wrapper for the state of a file used in the parser.
   180  // The parse tree walkers are all methods of this type.
   181  type File struct {
   182  	pkg     *Package
   183  	fset    *token.FileSet
   184  	name    string
   185  	content []byte
   186  	file    *ast.File
   187  	b       bytes.Buffer // for use by methods
   188  
   189  	// Parsed package "foo" when checking package "foo_test"
   190  	basePkg *Package
   191  
   192  	// The objects that are receivers of a "String() string" method.
   193  	// This is used by the recursiveStringer method in print.go.
   194  	stringers map[*ast.Object]bool
   195  
   196  	// Registered checkers to run.
   197  	checkers map[ast.Node][]func(*File, ast.Node)
   198  
   199  	// Unreachable nodes; can be ignored in shift check.
   200  	dead map[ast.Node]bool
   201  }
   202  
   203  func main() {
   204  	flag.Usage = Usage
   205  	flag.Parse()
   206  
   207  	// If any flag is set, we run only those checks requested.
   208  	// If all flag is set true or if no flags are set true, set all the non-experimental ones
   209  	// not explicitly set (in effect, set the "-all" flag).
   210  	if setTrueCount == 0 || *all == setTrue {
   211  		for name, setting := range report {
   212  			if *setting == unset && !experimental[name] {
   213  				*setting = setTrue
   214  			}
   215  		}
   216  	}
   217  
   218  	// Accept space-separated tags because that matches
   219  	// the go command's other subcommands.
   220  	// Accept commas because go tool vet traditionally has.
   221  	tagList = strings.Fields(strings.Replace(*tags, ",", " ", -1))
   222  
   223  	initPrintFlags()
   224  	initUnusedFlags()
   225  
   226  	if flag.NArg() == 0 {
   227  		Usage()
   228  	}
   229  	for _, name := range flag.Args() {
   230  		// Is it a directory?
   231  		fi, err := os.Stat(name)
   232  		if err != nil {
   233  			warnf("error walking tree: %s", err)
   234  			continue
   235  		}
   236  		if fi.IsDir() {
   237  			dirsRun = true
   238  		} else {
   239  			filesRun = true
   240  			if !strings.HasSuffix(name, "_test.go") {
   241  				includesNonTest = true
   242  			}
   243  		}
   244  	}
   245  	if dirsRun && filesRun {
   246  		Usage()
   247  	}
   248  	if dirsRun {
   249  		for _, name := range flag.Args() {
   250  			walkDir(name)
   251  		}
   252  		os.Exit(exitCode)
   253  	}
   254  	if doPackage(flag.Args(), nil) == nil {
   255  		warnf("no files checked")
   256  	}
   257  	os.Exit(exitCode)
   258  }
   259  
   260  // prefixDirectory places the directory name on the beginning of each name in the list.
   261  func prefixDirectory(directory string, names []string) {
   262  	if directory != "." {
   263  		for i, name := range names {
   264  			names[i] = filepath.Join(directory, name)
   265  		}
   266  	}
   267  }
   268  
   269  // doPackageDir analyzes the single package found in the directory, if there is one,
   270  // plus a test package, if there is one.
   271  func doPackageDir(directory string) {
   272  	context := build.Default
   273  	if len(context.BuildTags) != 0 {
   274  		warnf("build tags %s previously set", context.BuildTags)
   275  	}
   276  	context.BuildTags = append(tagList, context.BuildTags...)
   277  
   278  	pkg, err := context.ImportDir(directory, 0)
   279  	if err != nil {
   280  		// If it's just that there are no go source files, that's fine.
   281  		if _, nogo := err.(*build.NoGoError); nogo {
   282  			return
   283  		}
   284  		// Non-fatal: we are doing a recursive walk and there may be other directories.
   285  		warnf("cannot process directory %s: %s", directory, err)
   286  		return
   287  	}
   288  	var names []string
   289  	names = append(names, pkg.GoFiles...)
   290  	names = append(names, pkg.CgoFiles...)
   291  	names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
   292  	names = append(names, pkg.SFiles...)
   293  	prefixDirectory(directory, names)
   294  	basePkg := doPackage(names, nil)
   295  	// Is there also a "foo_test" package? If so, do that one as well.
   296  	if len(pkg.XTestGoFiles) > 0 {
   297  		names = pkg.XTestGoFiles
   298  		prefixDirectory(directory, names)
   299  		doPackage(names, basePkg)
   300  	}
   301  }
   302  
   303  type Package struct {
   304  	path      string
   305  	defs      map[*ast.Ident]types.Object
   306  	uses      map[*ast.Ident]types.Object
   307  	selectors map[*ast.SelectorExpr]*types.Selection
   308  	types     map[ast.Expr]types.TypeAndValue
   309  	spans     map[types.Object]Span
   310  	files     []*File
   311  	typesPkg  *types.Package
   312  }
   313  
   314  // doPackage analyzes the single package constructed from the named files.
   315  // It returns the parsed Package or nil if none of the files have been checked.
   316  func doPackage(names []string, basePkg *Package) *Package {
   317  	var files []*File
   318  	var astFiles []*ast.File
   319  	fs := token.NewFileSet()
   320  	for _, name := range names {
   321  		data, err := ioutil.ReadFile(name)
   322  		if err != nil {
   323  			// Warn but continue to next package.
   324  			warnf("%s: %s", name, err)
   325  			return nil
   326  		}
   327  		checkBuildTag(name, data)
   328  		var parsedFile *ast.File
   329  		if strings.HasSuffix(name, ".go") {
   330  			parsedFile, err = parser.ParseFile(fs, name, data, 0)
   331  			if err != nil {
   332  				warnf("%s: %s", name, err)
   333  				return nil
   334  			}
   335  			astFiles = append(astFiles, parsedFile)
   336  		}
   337  		files = append(files, &File{
   338  			fset:    fs,
   339  			content: data,
   340  			name:    name,
   341  			file:    parsedFile,
   342  			dead:    make(map[ast.Node]bool),
   343  		})
   344  	}
   345  	if len(astFiles) == 0 {
   346  		return nil
   347  	}
   348  	pkg := new(Package)
   349  	pkg.path = astFiles[0].Name.Name
   350  	pkg.files = files
   351  	// Type check the package.
   352  	err := pkg.check(fs, astFiles)
   353  	if err != nil {
   354  		// Note that we only report this error when *verbose.
   355  		Println(err)
   356  	}
   357  
   358  	// Check.
   359  	chk := make(map[ast.Node][]func(*File, ast.Node))
   360  	for typ, set := range checkers {
   361  		for name, fn := range set {
   362  			if vet(name) {
   363  				chk[typ] = append(chk[typ], fn)
   364  			}
   365  		}
   366  	}
   367  	for _, file := range files {
   368  		file.pkg = pkg
   369  		file.basePkg = basePkg
   370  		file.checkers = chk
   371  		if file.file != nil {
   372  			file.walkFile(file.name, file.file)
   373  		}
   374  	}
   375  	asmCheck(pkg)
   376  	return pkg
   377  }
   378  
   379  func visit(path string, f os.FileInfo, err error) error {
   380  	if err != nil {
   381  		warnf("walk error: %s", err)
   382  		return err
   383  	}
   384  	// One package per directory. Ignore the files themselves.
   385  	if !f.IsDir() {
   386  		return nil
   387  	}
   388  	doPackageDir(path)
   389  	return nil
   390  }
   391  
   392  func (pkg *Package) hasFileWithSuffix(suffix string) bool {
   393  	for _, f := range pkg.files {
   394  		if strings.HasSuffix(f.name, suffix) {
   395  			return true
   396  		}
   397  	}
   398  	return false
   399  }
   400  
   401  // walkDir recursively walks the tree looking for Go packages.
   402  func walkDir(root string) {
   403  	filepath.Walk(root, visit)
   404  }
   405  
   406  // errorf formats the error to standard error, adding program
   407  // identification and a newline, and exits.
   408  func errorf(format string, args ...interface{}) {
   409  	fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
   410  	os.Exit(2)
   411  }
   412  
   413  // warnf formats the error to standard error, adding program
   414  // identification and a newline, but does not exit.
   415  func warnf(format string, args ...interface{}) {
   416  	fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
   417  	setExit(1)
   418  }
   419  
   420  // Println is fmt.Println guarded by -v.
   421  func Println(args ...interface{}) {
   422  	if !*verbose {
   423  		return
   424  	}
   425  	fmt.Println(args...)
   426  }
   427  
   428  // Printf is fmt.Printf guarded by -v.
   429  func Printf(format string, args ...interface{}) {
   430  	if !*verbose {
   431  		return
   432  	}
   433  	fmt.Printf(format+"\n", args...)
   434  }
   435  
   436  // Bad reports an error and sets the exit code..
   437  func (f *File) Bad(pos token.Pos, args ...interface{}) {
   438  	f.Warn(pos, args...)
   439  	setExit(1)
   440  }
   441  
   442  // Badf reports a formatted error and sets the exit code.
   443  func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
   444  	f.Warnf(pos, format, args...)
   445  	setExit(1)
   446  }
   447  
   448  // loc returns a formatted representation of the position.
   449  func (f *File) loc(pos token.Pos) string {
   450  	if pos == token.NoPos {
   451  		return ""
   452  	}
   453  	// Do not print columns. Because the pos often points to the start of an
   454  	// expression instead of the inner part with the actual error, the
   455  	// precision can mislead.
   456  	posn := f.fset.Position(pos)
   457  	return fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
   458  }
   459  
   460  // locPrefix returns a formatted representation of the position for use as a line prefix.
   461  func (f *File) locPrefix(pos token.Pos) string {
   462  	if pos == token.NoPos {
   463  		return ""
   464  	}
   465  	return fmt.Sprintf("%s: ", f.loc(pos))
   466  }
   467  
   468  // Warn reports an error but does not set the exit code.
   469  func (f *File) Warn(pos token.Pos, args ...interface{}) {
   470  	fmt.Fprintf(os.Stderr, "%s%s", f.locPrefix(pos), fmt.Sprintln(args...))
   471  }
   472  
   473  // Warnf reports a formatted error but does not set the exit code.
   474  func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
   475  	fmt.Fprintf(os.Stderr, "%s%s\n", f.locPrefix(pos), fmt.Sprintf(format, args...))
   476  }
   477  
   478  // walkFile walks the file's tree.
   479  func (f *File) walkFile(name string, file *ast.File) {
   480  	Println("Checking file", name)
   481  	ast.Walk(f, file)
   482  }
   483  
   484  // Visit implements the ast.Visitor interface.
   485  func (f *File) Visit(node ast.Node) ast.Visitor {
   486  	f.updateDead(node)
   487  	var key ast.Node
   488  	switch node.(type) {
   489  	case *ast.AssignStmt:
   490  		key = assignStmt
   491  	case *ast.BinaryExpr:
   492  		key = binaryExpr
   493  	case *ast.CallExpr:
   494  		key = callExpr
   495  	case *ast.CompositeLit:
   496  		key = compositeLit
   497  	case *ast.ExprStmt:
   498  		key = exprStmt
   499  	case *ast.ForStmt:
   500  		key = forStmt
   501  	case *ast.FuncDecl:
   502  		key = funcDecl
   503  	case *ast.FuncLit:
   504  		key = funcLit
   505  	case *ast.GenDecl:
   506  		key = genDecl
   507  	case *ast.InterfaceType:
   508  		key = interfaceType
   509  	case *ast.RangeStmt:
   510  		key = rangeStmt
   511  	case *ast.ReturnStmt:
   512  		key = returnStmt
   513  	case *ast.StructType:
   514  		key = structType
   515  	}
   516  	for _, fn := range f.checkers[key] {
   517  		fn(f, node)
   518  	}
   519  	return f
   520  }
   521  
   522  // gofmt returns a string representation of the expression.
   523  func (f *File) gofmt(x ast.Expr) string {
   524  	f.b.Reset()
   525  	printer.Fprint(&f.b, f.fset, x)
   526  	return f.b.String()
   527  }