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