github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/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 funcDecl *ast.FuncDecl 140 funcLit *ast.FuncLit 141 genDecl *ast.GenDecl 142 interfaceType *ast.InterfaceType 143 rangeStmt *ast.RangeStmt 144 returnStmt *ast.ReturnStmt 145 structType *ast.StructType 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 vet:\n") 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, "By default, -all is set and all non-experimental checks are run.\n") 171 fmt.Fprintf(os.Stderr, "For more information run\n") 172 fmt.Fprintf(os.Stderr, "\tgo doc cmd/vet\n\n") 173 fmt.Fprintf(os.Stderr, "Flags:\n") 174 flag.PrintDefaults() 175 os.Exit(2) 176 } 177 178 // File is a wrapper for the state of a file used in the parser. 179 // The parse tree walkers are all methods of this type. 180 type File struct { 181 pkg *Package 182 fset *token.FileSet 183 name string 184 content []byte 185 file *ast.File 186 b bytes.Buffer // for use by methods 187 188 // Parsed package "foo" when checking package "foo_test" 189 basePkg *Package 190 191 // The objects that are receivers of a "String() string" method. 192 // This is used by the recursiveStringer method in print.go. 193 stringers map[*ast.Object]bool 194 195 // Registered checkers to run. 196 checkers map[ast.Node][]func(*File, ast.Node) 197 } 198 199 func main() { 200 flag.Usage = Usage 201 flag.Parse() 202 203 // If any flag is set, we run only those checks requested. 204 // If all flag is set true or if no flags are set true, set all the non-experimental ones 205 // not explicitly set (in effect, set the "-all" flag). 206 if setTrueCount == 0 || *all == setTrue { 207 for name, setting := range report { 208 if *setting == unset && !experimental[name] { 209 *setting = setTrue 210 } 211 } 212 } 213 214 // Accept space-separated tags because that matches 215 // the go command's other subcommands. 216 // Accept commas because go tool vet traditionally has. 217 tagList = strings.Fields(strings.Replace(*tags, ",", " ", -1)) 218 219 initPrintFlags() 220 initUnusedFlags() 221 222 if flag.NArg() == 0 { 223 Usage() 224 } 225 for _, name := range flag.Args() { 226 // Is it a directory? 227 fi, err := os.Stat(name) 228 if err != nil { 229 warnf("error walking tree: %s", err) 230 continue 231 } 232 if fi.IsDir() { 233 dirsRun = true 234 } else { 235 filesRun = true 236 if !strings.HasSuffix(name, "_test.go") { 237 includesNonTest = true 238 } 239 } 240 } 241 if dirsRun && filesRun { 242 Usage() 243 } 244 if dirsRun { 245 for _, name := range flag.Args() { 246 walkDir(name) 247 } 248 os.Exit(exitCode) 249 } 250 if doPackage(".", flag.Args(), nil) == nil { 251 warnf("no files checked") 252 } 253 os.Exit(exitCode) 254 } 255 256 // prefixDirectory places the directory name on the beginning of each name in the list. 257 func prefixDirectory(directory string, names []string) { 258 if directory != "." { 259 for i, name := range names { 260 names[i] = filepath.Join(directory, name) 261 } 262 } 263 } 264 265 // doPackageDir analyzes the single package found in the directory, if there is one, 266 // plus a test package, if there is one. 267 func doPackageDir(directory string) { 268 context := build.Default 269 if len(context.BuildTags) != 0 { 270 warnf("build tags %s previously set", context.BuildTags) 271 } 272 context.BuildTags = append(tagList, context.BuildTags...) 273 274 pkg, err := context.ImportDir(directory, 0) 275 if err != nil { 276 // If it's just that there are no go source files, that's fine. 277 if _, nogo := err.(*build.NoGoError); nogo { 278 return 279 } 280 // Non-fatal: we are doing a recursive walk and there may be other directories. 281 warnf("cannot process directory %s: %s", directory, err) 282 return 283 } 284 var names []string 285 names = append(names, pkg.GoFiles...) 286 names = append(names, pkg.CgoFiles...) 287 names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. 288 names = append(names, pkg.SFiles...) 289 prefixDirectory(directory, names) 290 basePkg := doPackage(directory, names, nil) 291 // Is there also a "foo_test" package? If so, do that one as well. 292 if len(pkg.XTestGoFiles) > 0 { 293 names = pkg.XTestGoFiles 294 prefixDirectory(directory, names) 295 doPackage(directory, names, basePkg) 296 } 297 } 298 299 type Package struct { 300 path string 301 defs map[*ast.Ident]types.Object 302 uses map[*ast.Ident]types.Object 303 selectors map[*ast.SelectorExpr]*types.Selection 304 types map[ast.Expr]types.TypeAndValue 305 spans map[types.Object]Span 306 files []*File 307 typesPkg *types.Package 308 } 309 310 // doPackage analyzes the single package constructed from the named files. 311 // It returns the parsed Package or nil if none of the files have been checked. 312 func doPackage(directory string, names []string, basePkg *Package) *Package { 313 var files []*File 314 var astFiles []*ast.File 315 fs := token.NewFileSet() 316 for _, name := range names { 317 data, err := ioutil.ReadFile(name) 318 if err != nil { 319 // Warn but continue to next package. 320 warnf("%s: %s", name, err) 321 return nil 322 } 323 checkBuildTag(name, data) 324 var parsedFile *ast.File 325 if strings.HasSuffix(name, ".go") { 326 parsedFile, err = parser.ParseFile(fs, name, data, 0) 327 if err != nil { 328 warnf("%s: %s", name, err) 329 return nil 330 } 331 astFiles = append(astFiles, parsedFile) 332 } 333 files = append(files, &File{fset: fs, content: data, name: name, file: parsedFile}) 334 } 335 if len(astFiles) == 0 { 336 return nil 337 } 338 pkg := new(Package) 339 pkg.path = astFiles[0].Name.Name 340 pkg.files = files 341 // Type check the package. 342 err := pkg.check(fs, astFiles) 343 if err != nil && *verbose { 344 warnf("%s", err) 345 } 346 347 // Check. 348 chk := make(map[ast.Node][]func(*File, ast.Node)) 349 for typ, set := range checkers { 350 for name, fn := range set { 351 if vet(name) { 352 chk[typ] = append(chk[typ], fn) 353 } 354 } 355 } 356 for _, file := range files { 357 file.pkg = pkg 358 file.basePkg = basePkg 359 file.checkers = chk 360 if file.file != nil { 361 file.walkFile(file.name, file.file) 362 } 363 } 364 asmCheck(pkg) 365 return pkg 366 } 367 368 func visit(path string, f os.FileInfo, err error) error { 369 if err != nil { 370 warnf("walk error: %s", err) 371 return err 372 } 373 // One package per directory. Ignore the files themselves. 374 if !f.IsDir() { 375 return nil 376 } 377 doPackageDir(path) 378 return nil 379 } 380 381 func (pkg *Package) hasFileWithSuffix(suffix string) bool { 382 for _, f := range pkg.files { 383 if strings.HasSuffix(f.name, suffix) { 384 return true 385 } 386 } 387 return false 388 } 389 390 // walkDir recursively walks the tree looking for Go packages. 391 func walkDir(root string) { 392 filepath.Walk(root, visit) 393 } 394 395 // errorf formats the error to standard error, adding program 396 // identification and a newline, and exits. 397 func errorf(format string, args ...interface{}) { 398 fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) 399 os.Exit(2) 400 } 401 402 // warnf formats the error to standard error, adding program 403 // identification and a newline, but does not exit. 404 func warnf(format string, args ...interface{}) { 405 fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) 406 setExit(1) 407 } 408 409 // Println is fmt.Println guarded by -v. 410 func Println(args ...interface{}) { 411 if !*verbose { 412 return 413 } 414 fmt.Println(args...) 415 } 416 417 // Printf is fmt.Printf guarded by -v. 418 func Printf(format string, args ...interface{}) { 419 if !*verbose { 420 return 421 } 422 fmt.Printf(format+"\n", args...) 423 } 424 425 // Bad reports an error and sets the exit code.. 426 func (f *File) Bad(pos token.Pos, args ...interface{}) { 427 f.Warn(pos, args...) 428 setExit(1) 429 } 430 431 // Badf reports a formatted error and sets the exit code. 432 func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { 433 f.Warnf(pos, format, args...) 434 setExit(1) 435 } 436 437 // loc returns a formatted representation of the position. 438 func (f *File) loc(pos token.Pos) string { 439 if pos == token.NoPos { 440 return "" 441 } 442 // Do not print columns. Because the pos often points to the start of an 443 // expression instead of the inner part with the actual error, the 444 // precision can mislead. 445 posn := f.fset.Position(pos) 446 return fmt.Sprintf("%s:%d", posn.Filename, posn.Line) 447 } 448 449 // locPrefix returns a formatted representation of the position for use as a line prefix. 450 func (f *File) locPrefix(pos token.Pos) string { 451 if pos == token.NoPos { 452 return "" 453 } 454 return fmt.Sprintf("%s: ", f.loc(pos)) 455 } 456 457 // Warn reports an error but does not set the exit code. 458 func (f *File) Warn(pos token.Pos, args ...interface{}) { 459 fmt.Fprintf(os.Stderr, "%s%s", f.locPrefix(pos), fmt.Sprintln(args...)) 460 } 461 462 // Warnf reports a formatted error but does not set the exit code. 463 func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) { 464 fmt.Fprintf(os.Stderr, "%s%s\n", f.locPrefix(pos), fmt.Sprintf(format, args...)) 465 } 466 467 // walkFile walks the file's tree. 468 func (f *File) walkFile(name string, file *ast.File) { 469 Println("Checking file", name) 470 ast.Walk(f, file) 471 } 472 473 // Visit implements the ast.Visitor interface. 474 func (f *File) Visit(node ast.Node) ast.Visitor { 475 var key ast.Node 476 switch node.(type) { 477 case *ast.AssignStmt: 478 key = assignStmt 479 case *ast.BinaryExpr: 480 key = binaryExpr 481 case *ast.CallExpr: 482 key = callExpr 483 case *ast.CompositeLit: 484 key = compositeLit 485 case *ast.ExprStmt: 486 key = exprStmt 487 case *ast.FuncDecl: 488 key = funcDecl 489 case *ast.FuncLit: 490 key = funcLit 491 case *ast.GenDecl: 492 key = genDecl 493 case *ast.InterfaceType: 494 key = interfaceType 495 case *ast.RangeStmt: 496 key = rangeStmt 497 case *ast.ReturnStmt: 498 key = returnStmt 499 case *ast.StructType: 500 key = structType 501 } 502 for _, fn := range f.checkers[key] { 503 fn(f, node) 504 } 505 return f 506 } 507 508 // gofmt returns a string representation of the expression. 509 func (f *File) gofmt(x ast.Expr) string { 510 f.b.Reset() 511 printer.Fprint(&f.b, f.fset, x) 512 return f.b.String() 513 }