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