github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/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  	"io/ioutil"
    19  	"os"
    20  	"path/filepath"
    21  	"strconv"
    22  	"strings"
    23  )
    24  
    25  var verbose = flag.Bool("v", false, "verbose")
    26  var exitCode = 0
    27  
    28  // Flags to control which checks to perform. "all" is set to true here, and disabled later if
    29  // a flag is set explicitly.
    30  var report = map[string]*bool{
    31  	"all":         flag.Bool("all", true, "check everything; disabled if any explicit check is requested"),
    32  	"asmdecl":     flag.Bool("asmdecl", false, "check assembly against Go declarations"),
    33  	"assign":      flag.Bool("assign", false, "check for useless assignments"),
    34  	"atomic":      flag.Bool("atomic", false, "check for common mistaken usages of the sync/atomic package"),
    35  	"buildtags":   flag.Bool("buildtags", false, "check that +build tags are valid"),
    36  	"composites":  flag.Bool("composites", false, "check that composite literals used type-tagged elements"),
    37  	"methods":     flag.Bool("methods", false, "check that canonically named methods are canonically defined"),
    38  	"printf":      flag.Bool("printf", false, "check printf-like invocations"),
    39  	"rangeloops":  flag.Bool("rangeloops", false, "check that range loop variables are used correctly"),
    40  	"structtags":  flag.Bool("structtags", false, "check that struct field tags have canonical format"),
    41  	"unreachable": flag.Bool("unreachable", false, "check for unreachable code"),
    42  }
    43  
    44  // vet tells whether to report errors for the named check, a flag name.
    45  func vet(name string) bool {
    46  	return *report["all"] || *report[name]
    47  }
    48  
    49  // setExit sets the value for os.Exit when it is called, later.  It
    50  // remembers the highest value.
    51  func setExit(err int) {
    52  	if err > exitCode {
    53  		exitCode = err
    54  	}
    55  }
    56  
    57  // Usage is a replacement usage function for the flags package.
    58  func Usage() {
    59  	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
    60  	fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n")
    61  	fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n")
    62  	flag.PrintDefaults()
    63  	os.Exit(2)
    64  }
    65  
    66  // File is a wrapper for the state of a file used in the parser.
    67  // The parse tree walkers are all methods of this type.
    68  type File struct {
    69  	pkg     *Package
    70  	fset    *token.FileSet
    71  	name    string
    72  	content []byte
    73  	file    *ast.File
    74  	b       bytes.Buffer // for use by methods
    75  }
    76  
    77  func main() {
    78  	flag.Usage = Usage
    79  	flag.Parse()
    80  
    81  	// If a check is named explicitly, turn off the 'all' flag.
    82  	for name, ptr := range report {
    83  		if name != "all" && *ptr {
    84  			*report["all"] = false
    85  			break
    86  		}
    87  	}
    88  
    89  	if *printfuncs != "" {
    90  		for _, name := range strings.Split(*printfuncs, ",") {
    91  			if len(name) == 0 {
    92  				flag.Usage()
    93  			}
    94  			skip := 0
    95  			if colon := strings.LastIndex(name, ":"); colon > 0 {
    96  				var err error
    97  				skip, err = strconv.Atoi(name[colon+1:])
    98  				if err != nil {
    99  					errorf(`illegal format for "Func:N" argument %q; %s`, name, err)
   100  				}
   101  				name = name[:colon]
   102  			}
   103  			name = strings.ToLower(name)
   104  			if name[len(name)-1] == 'f' {
   105  				printfList[name] = skip
   106  			} else {
   107  				printList[name] = skip
   108  			}
   109  		}
   110  	}
   111  
   112  	if flag.NArg() == 0 {
   113  		Usage()
   114  	}
   115  	dirs := false
   116  	files := false
   117  	for _, name := range flag.Args() {
   118  		// Is it a directory?
   119  		fi, err := os.Stat(name)
   120  		if err != nil {
   121  			warnf("error walking tree: %s", err)
   122  			continue
   123  		}
   124  		if fi.IsDir() {
   125  			dirs = true
   126  		} else {
   127  			files = true
   128  		}
   129  	}
   130  	if dirs && files {
   131  		Usage()
   132  	}
   133  	if dirs {
   134  		for _, name := range flag.Args() {
   135  			walkDir(name)
   136  		}
   137  		return
   138  	}
   139  	doPackage(flag.Args())
   140  	os.Exit(exitCode)
   141  }
   142  
   143  // prefixDirectory places the directory name on the beginning of each name in the list.
   144  func prefixDirectory(directory string, names []string) {
   145  	if directory != "." {
   146  		for i, name := range names {
   147  			names[i] = filepath.Join(directory, name)
   148  		}
   149  	}
   150  }
   151  
   152  // doPackageDir analyzes the single package found in the directory, if there is one,
   153  // plus a test package, if there is one.
   154  func doPackageDir(directory string) {
   155  	pkg, err := build.Default.ImportDir(directory, 0)
   156  	if err != nil {
   157  		// If it's just that there are no go source files, that's fine.
   158  		if _, nogo := err.(*build.NoGoError); nogo {
   159  			return
   160  		}
   161  		// Non-fatal: we are doing a recursive walk and there may be other directories.
   162  		warnf("cannot process directory %s: %s", directory, err)
   163  		return
   164  	}
   165  	var names []string
   166  	names = append(names, pkg.GoFiles...)
   167  	names = append(names, pkg.CgoFiles...)
   168  	names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
   169  	names = append(names, pkg.SFiles...)
   170  	prefixDirectory(directory, names)
   171  	doPackage(names)
   172  	// Is there also a "foo_test" package? If so, do that one as well.
   173  	if len(pkg.XTestGoFiles) > 0 {
   174  		names = pkg.XTestGoFiles
   175  		prefixDirectory(directory, names)
   176  		doPackage(names)
   177  	}
   178  }
   179  
   180  type Package struct {
   181  	types  map[ast.Expr]Type
   182  	values map[ast.Expr]interface{}
   183  	files  []*File
   184  }
   185  
   186  // doPackage analyzes the single package constructed from the named files.
   187  func doPackage(names []string) {
   188  	var files []*File
   189  	var astFiles []*ast.File
   190  	fs := token.NewFileSet()
   191  	for _, name := range names {
   192  		f, err := os.Open(name)
   193  		if err != nil {
   194  			// Warn but continue to next package.
   195  			warnf("%s: %s", name, err)
   196  			return
   197  		}
   198  		defer f.Close()
   199  		data, err := ioutil.ReadAll(f)
   200  		if err != nil {
   201  			warnf("%s: %s", name, err)
   202  			return
   203  		}
   204  		checkBuildTag(name, data)
   205  		var parsedFile *ast.File
   206  		if strings.HasSuffix(name, ".go") {
   207  			parsedFile, err = parser.ParseFile(fs, name, bytes.NewReader(data), 0)
   208  			if err != nil {
   209  				warnf("%s: %s", name, err)
   210  				return
   211  			}
   212  			astFiles = append(astFiles, parsedFile)
   213  		}
   214  		files = append(files, &File{fset: fs, content: data, name: name, file: parsedFile})
   215  	}
   216  	pkg := new(Package)
   217  	pkg.files = files
   218  	// Type check the package.
   219  	err := pkg.check(fs, astFiles)
   220  	if err != nil && *verbose {
   221  		warnf("%s", err)
   222  	}
   223  	for _, file := range files {
   224  		file.pkg = pkg
   225  		if file.file != nil {
   226  			file.walkFile(file.name, file.file)
   227  		}
   228  	}
   229  	asmCheck(pkg)
   230  }
   231  
   232  func visit(path string, f os.FileInfo, err error) error {
   233  	if err != nil {
   234  		warnf("walk error: %s", err)
   235  		return err
   236  	}
   237  	// One package per directory. Ignore the files themselves.
   238  	if !f.IsDir() {
   239  		return nil
   240  	}
   241  	doPackageDir(path)
   242  	return nil
   243  }
   244  
   245  func (pkg *Package) hasFileWithSuffix(suffix string) bool {
   246  	for _, f := range pkg.files {
   247  		if strings.HasSuffix(f.name, suffix) {
   248  			return true
   249  		}
   250  	}
   251  	return false
   252  }
   253  
   254  // walkDir recursively walks the tree looking for Go packages.
   255  func walkDir(root string) {
   256  	filepath.Walk(root, visit)
   257  }
   258  
   259  // errorf formats the error to standard error, adding program
   260  // identification and a newline, and exits.
   261  func errorf(format string, args ...interface{}) {
   262  	fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
   263  	os.Exit(2)
   264  }
   265  
   266  // warnf formats the error to standard error, adding program
   267  // identification and a newline, but does not exit.
   268  func warnf(format string, args ...interface{}) {
   269  	fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
   270  	setExit(1)
   271  }
   272  
   273  // Println is fmt.Println guarded by -v.
   274  func Println(args ...interface{}) {
   275  	if !*verbose {
   276  		return
   277  	}
   278  	fmt.Println(args...)
   279  }
   280  
   281  // Printf is fmt.Printf guarded by -v.
   282  func Printf(format string, args ...interface{}) {
   283  	if !*verbose {
   284  		return
   285  	}
   286  	fmt.Printf(format+"\n", args...)
   287  }
   288  
   289  // Bad reports an error and sets the exit code..
   290  func (f *File) Bad(pos token.Pos, args ...interface{}) {
   291  	f.Warn(pos, args...)
   292  	setExit(1)
   293  }
   294  
   295  // Badf reports a formatted error and sets the exit code.
   296  func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
   297  	f.Warnf(pos, format, args...)
   298  	setExit(1)
   299  }
   300  
   301  func (f *File) loc(pos token.Pos) string {
   302  	if pos == token.NoPos {
   303  		return ""
   304  	}
   305  	// Do not print columns. Because the pos often points to the start of an
   306  	// expression instead of the inner part with the actual error, the
   307  	// precision can mislead.
   308  	posn := f.fset.Position(pos)
   309  	return fmt.Sprintf("%s:%d: ", posn.Filename, posn.Line)
   310  }
   311  
   312  // Warn reports an error but does not set the exit code.
   313  func (f *File) Warn(pos token.Pos, args ...interface{}) {
   314  	fmt.Fprint(os.Stderr, f.loc(pos)+fmt.Sprintln(args...))
   315  }
   316  
   317  // Warnf reports a formatted error but does not set the exit code.
   318  func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
   319  	fmt.Fprintf(os.Stderr, f.loc(pos)+format+"\n", args...)
   320  }
   321  
   322  // walkFile walks the file's tree.
   323  func (f *File) walkFile(name string, file *ast.File) {
   324  	Println("Checking file", name)
   325  	ast.Walk(f, file)
   326  }
   327  
   328  // Visit implements the ast.Visitor interface.
   329  func (f *File) Visit(node ast.Node) ast.Visitor {
   330  	switch n := node.(type) {
   331  	case *ast.AssignStmt:
   332  		f.walkAssignStmt(n)
   333  	case *ast.CallExpr:
   334  		f.walkCallExpr(n)
   335  	case *ast.CompositeLit:
   336  		f.walkCompositeLit(n)
   337  	case *ast.Field:
   338  		f.walkFieldTag(n)
   339  	case *ast.FuncDecl:
   340  		f.walkFuncDecl(n)
   341  	case *ast.FuncLit:
   342  		f.walkFuncLit(n)
   343  	case *ast.InterfaceType:
   344  		f.walkInterfaceType(n)
   345  	case *ast.RangeStmt:
   346  		f.walkRangeStmt(n)
   347  	}
   348  	return f
   349  }
   350  
   351  // walkAssignStmt walks an assignment statement
   352  func (f *File) walkAssignStmt(stmt *ast.AssignStmt) {
   353  	f.checkAssignStmt(stmt)
   354  	f.checkAtomicAssignment(stmt)
   355  }
   356  
   357  // walkCall walks a call expression.
   358  func (f *File) walkCall(call *ast.CallExpr, name string) {
   359  	f.checkFmtPrintfCall(call, name)
   360  }
   361  
   362  // walkCallExpr walks a call expression.
   363  func (f *File) walkCallExpr(call *ast.CallExpr) {
   364  	switch x := call.Fun.(type) {
   365  	case *ast.Ident:
   366  		f.walkCall(call, x.Name)
   367  	case *ast.SelectorExpr:
   368  		f.walkCall(call, x.Sel.Name)
   369  	}
   370  }
   371  
   372  // walkCompositeLit walks a composite literal.
   373  func (f *File) walkCompositeLit(c *ast.CompositeLit) {
   374  	f.checkUntaggedLiteral(c)
   375  }
   376  
   377  // walkFieldTag walks a struct field tag.
   378  func (f *File) walkFieldTag(field *ast.Field) {
   379  	if field.Tag == nil {
   380  		return
   381  	}
   382  	f.checkCanonicalFieldTag(field)
   383  }
   384  
   385  // walkMethod walks the method's signature.
   386  func (f *File) walkMethod(id *ast.Ident, t *ast.FuncType) {
   387  	f.checkCanonicalMethod(id, t)
   388  }
   389  
   390  // walkFuncDecl walks a function declaration.
   391  func (f *File) walkFuncDecl(d *ast.FuncDecl) {
   392  	f.checkUnreachable(d.Body)
   393  	if d.Recv != nil {
   394  		f.walkMethod(d.Name, d.Type)
   395  	}
   396  }
   397  
   398  // walkFuncLit walks a function literal.
   399  func (f *File) walkFuncLit(x *ast.FuncLit) {
   400  	f.checkUnreachable(x.Body)
   401  }
   402  
   403  // walkInterfaceType walks the method signatures of an interface.
   404  func (f *File) walkInterfaceType(t *ast.InterfaceType) {
   405  	for _, field := range t.Methods.List {
   406  		for _, id := range field.Names {
   407  			f.walkMethod(id, field.Type.(*ast.FuncType))
   408  		}
   409  	}
   410  }
   411  
   412  // walkRangeStmt walks a range statement.
   413  func (f *File) walkRangeStmt(n *ast.RangeStmt) {
   414  	checkRangeLoop(f, n)
   415  }
   416  
   417  // gofmt returns a string representation of the expression.
   418  func (f *File) gofmt(x ast.Expr) string {
   419  	f.b.Reset()
   420  	printer.Fprint(&f.b, f.fset, x)
   421  	return f.b.String()
   422  }