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