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