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