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