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