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