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