github.com/dara-project/godist@v0.0.0-20200823115410-e0c80c8f0c78/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 keys are the objects that are receivers of a "String() 199 // string" method. The value reports whether the method has a 200 // pointer receiver. 201 // This is used by the recursiveStringer method in print.go. 202 stringerPtrs map[*ast.Object]bool 203 204 // Registered checkers to run. 205 checkers map[ast.Node][]func(*File, ast.Node) 206 207 // Unreachable nodes; can be ignored in shift check. 208 dead map[ast.Node]bool 209 } 210 211 func main() { 212 flag.Usage = Usage 213 flag.Parse() 214 215 // If any flag is set, we run only those checks requested. 216 // If all flag is set true or if no flags are set true, set all the non-experimental ones 217 // not explicitly set (in effect, set the "-all" flag). 218 if setTrueCount == 0 || *all == setTrue { 219 for name, setting := range report { 220 if *setting == unset && !experimental[name] { 221 *setting = setTrue 222 } 223 } 224 } 225 226 // Accept space-separated tags because that matches 227 // the go command's other subcommands. 228 // Accept commas because go tool vet traditionally has. 229 tagList = strings.Fields(strings.Replace(*tags, ",", " ", -1)) 230 231 initPrintFlags() 232 initUnusedFlags() 233 234 if flag.NArg() == 0 { 235 Usage() 236 } 237 238 // Special case for "go vet" passing an explicit configuration: 239 // single argument ending in vet.cfg. 240 // Once we have a more general mechanism for obtaining this 241 // information from build tools like the go command, 242 // vet should be changed to use it. This vet.cfg hack is an 243 // experiment to learn about what form that information should take. 244 if flag.NArg() == 1 && strings.HasSuffix(flag.Arg(0), "vet.cfg") { 245 doPackageCfg(flag.Arg(0)) 246 os.Exit(exitCode) 247 } 248 249 for _, name := range flag.Args() { 250 // Is it a directory? 251 fi, err := os.Stat(name) 252 if err != nil { 253 warnf("error walking tree: %s", err) 254 continue 255 } 256 if fi.IsDir() { 257 dirsRun = true 258 } else { 259 filesRun = true 260 if !strings.HasSuffix(name, "_test.go") { 261 includesNonTest = true 262 } 263 } 264 } 265 if dirsRun && filesRun { 266 Usage() 267 } 268 if dirsRun { 269 for _, name := range flag.Args() { 270 walkDir(name) 271 } 272 os.Exit(exitCode) 273 } 274 if doPackage(flag.Args(), nil) == nil { 275 warnf("no files checked") 276 } 277 os.Exit(exitCode) 278 } 279 280 // prefixDirectory places the directory name on the beginning of each name in the list. 281 func prefixDirectory(directory string, names []string) { 282 if directory != "." { 283 for i, name := range names { 284 names[i] = filepath.Join(directory, name) 285 } 286 } 287 } 288 289 // vetConfig is the JSON config struct prepared by the Go command. 290 type vetConfig struct { 291 Compiler string 292 Dir string 293 ImportPath string 294 GoFiles []string 295 ImportMap map[string]string 296 PackageFile map[string]string 297 298 SucceedOnTypecheckFailure bool 299 300 imp types.Importer 301 } 302 303 func (v *vetConfig) Import(path string) (*types.Package, error) { 304 if v.imp == nil { 305 v.imp = importer.For(v.Compiler, v.openPackageFile) 306 } 307 if path == "unsafe" { 308 return v.imp.Import("unsafe") 309 } 310 p := v.ImportMap[path] 311 if p == "" { 312 return nil, fmt.Errorf("unknown import path %q", path) 313 } 314 if v.PackageFile[p] == "" { 315 return nil, fmt.Errorf("unknown package file for import %q", path) 316 } 317 return v.imp.Import(p) 318 } 319 320 func (v *vetConfig) openPackageFile(path string) (io.ReadCloser, error) { 321 file := v.PackageFile[path] 322 if file == "" { 323 // Note that path here has been translated via v.ImportMap, 324 // unlike in the error in Import above. We prefer the error in 325 // Import, but it's worth diagnosing this one too, just in case. 326 return nil, fmt.Errorf("unknown package file for %q", path) 327 } 328 f, err := os.Open(file) 329 if err != nil { 330 return nil, err 331 } 332 return f, nil 333 } 334 335 // doPackageCfg analyzes a single package described in a config file. 336 func doPackageCfg(cfgFile string) { 337 js, err := ioutil.ReadFile(cfgFile) 338 if err != nil { 339 errorf("%v", err) 340 } 341 if err := json.Unmarshal(js, &vcfg); err != nil { 342 errorf("parsing vet config %s: %v", cfgFile, err) 343 } 344 stdImporter = &vcfg 345 inittypes() 346 mustTypecheck = true 347 doPackage(vcfg.GoFiles, nil) 348 } 349 350 // doPackageDir analyzes the single package found in the directory, if there is one, 351 // plus a test package, if there is one. 352 func doPackageDir(directory string) { 353 context := build.Default 354 if len(context.BuildTags) != 0 { 355 warnf("build tags %s previously set", context.BuildTags) 356 } 357 context.BuildTags = append(tagList, context.BuildTags...) 358 359 pkg, err := context.ImportDir(directory, 0) 360 if err != nil { 361 // If it's just that there are no go source files, that's fine. 362 if _, nogo := err.(*build.NoGoError); nogo { 363 return 364 } 365 // Non-fatal: we are doing a recursive walk and there may be other directories. 366 warnf("cannot process directory %s: %s", directory, err) 367 return 368 } 369 var names []string 370 names = append(names, pkg.GoFiles...) 371 names = append(names, pkg.CgoFiles...) 372 names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. 373 names = append(names, pkg.SFiles...) 374 prefixDirectory(directory, names) 375 basePkg := doPackage(names, nil) 376 // Is there also a "foo_test" package? If so, do that one as well. 377 if len(pkg.XTestGoFiles) > 0 { 378 names = pkg.XTestGoFiles 379 prefixDirectory(directory, names) 380 doPackage(names, basePkg) 381 } 382 } 383 384 type Package struct { 385 path string 386 defs map[*ast.Ident]types.Object 387 uses map[*ast.Ident]types.Object 388 selectors map[*ast.SelectorExpr]*types.Selection 389 types map[ast.Expr]types.TypeAndValue 390 spans map[types.Object]Span 391 files []*File 392 typesPkg *types.Package 393 } 394 395 // doPackage analyzes the single package constructed from the named files. 396 // It returns the parsed Package or nil if none of the files have been checked. 397 func doPackage(names []string, basePkg *Package) *Package { 398 var files []*File 399 var astFiles []*ast.File 400 fs := token.NewFileSet() 401 for _, name := range names { 402 data, err := ioutil.ReadFile(name) 403 if err != nil { 404 // Warn but continue to next package. 405 warnf("%s: %s", name, err) 406 return nil 407 } 408 checkBuildTag(name, data) 409 var parsedFile *ast.File 410 if strings.HasSuffix(name, ".go") { 411 parsedFile, err = parser.ParseFile(fs, name, data, 0) 412 if err != nil { 413 warnf("%s: %s", name, err) 414 return nil 415 } 416 astFiles = append(astFiles, parsedFile) 417 } 418 files = append(files, &File{ 419 fset: fs, 420 content: data, 421 name: name, 422 file: parsedFile, 423 dead: make(map[ast.Node]bool), 424 }) 425 } 426 if len(astFiles) == 0 { 427 return nil 428 } 429 pkg := new(Package) 430 pkg.path = astFiles[0].Name.Name 431 pkg.files = files 432 // Type check the package. 433 errs := pkg.check(fs, astFiles) 434 if errs != nil { 435 if vcfg.SucceedOnTypecheckFailure { 436 os.Exit(0) 437 } 438 if *verbose || mustTypecheck { 439 for _, err := range errs { 440 fmt.Fprintf(os.Stderr, "%v\n", err) 441 } 442 if mustTypecheck { 443 // This message could be silenced, and we could just exit, 444 // but it might be helpful at least at first to make clear that the 445 // above errors are coming from vet and not the compiler 446 // (they often look like compiler errors, such as "declared but not used"). 447 errorf("typecheck failures") 448 } 449 } 450 } 451 452 // Check. 453 chk := make(map[ast.Node][]func(*File, ast.Node)) 454 for typ, set := range checkers { 455 for name, fn := range set { 456 if vet(name) { 457 chk[typ] = append(chk[typ], fn) 458 } 459 } 460 } 461 for _, file := range files { 462 file.pkg = pkg 463 file.basePkg = basePkg 464 file.checkers = chk 465 if file.file != nil { 466 file.walkFile(file.name, file.file) 467 } 468 } 469 asmCheck(pkg) 470 return pkg 471 } 472 473 func visit(path string, f os.FileInfo, err error) error { 474 if err != nil { 475 warnf("walk error: %s", err) 476 return err 477 } 478 // One package per directory. Ignore the files themselves. 479 if !f.IsDir() { 480 return nil 481 } 482 doPackageDir(path) 483 return nil 484 } 485 486 func (pkg *Package) hasFileWithSuffix(suffix string) bool { 487 for _, f := range pkg.files { 488 if strings.HasSuffix(f.name, suffix) { 489 return true 490 } 491 } 492 return false 493 } 494 495 // walkDir recursively walks the tree looking for Go packages. 496 func walkDir(root string) { 497 filepath.Walk(root, visit) 498 } 499 500 // errorf formats the error to standard error, adding program 501 // identification and a newline, and exits. 502 func errorf(format string, args ...interface{}) { 503 fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) 504 os.Exit(2) 505 } 506 507 // warnf formats the error to standard error, adding program 508 // identification and a newline, but does not exit. 509 func warnf(format string, args ...interface{}) { 510 fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) 511 setExit(1) 512 } 513 514 // Println is fmt.Println guarded by -v. 515 func Println(args ...interface{}) { 516 if !*verbose { 517 return 518 } 519 fmt.Println(args...) 520 } 521 522 // Printf is fmt.Printf guarded by -v. 523 func Printf(format string, args ...interface{}) { 524 if !*verbose { 525 return 526 } 527 fmt.Printf(format+"\n", args...) 528 } 529 530 // Bad reports an error and sets the exit code.. 531 func (f *File) Bad(pos token.Pos, args ...interface{}) { 532 f.Warn(pos, args...) 533 setExit(1) 534 } 535 536 // Badf reports a formatted error and sets the exit code. 537 func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { 538 f.Warnf(pos, format, args...) 539 setExit(1) 540 } 541 542 // loc returns a formatted representation of the position. 543 func (f *File) loc(pos token.Pos) string { 544 if pos == token.NoPos { 545 return "" 546 } 547 // Do not print columns. Because the pos often points to the start of an 548 // expression instead of the inner part with the actual error, the 549 // precision can mislead. 550 posn := f.fset.Position(pos) 551 return fmt.Sprintf("%s:%d", posn.Filename, posn.Line) 552 } 553 554 // locPrefix returns a formatted representation of the position for use as a line prefix. 555 func (f *File) locPrefix(pos token.Pos) string { 556 if pos == token.NoPos { 557 return "" 558 } 559 return fmt.Sprintf("%s: ", f.loc(pos)) 560 } 561 562 // Warn reports an error but does not set the exit code. 563 func (f *File) Warn(pos token.Pos, args ...interface{}) { 564 fmt.Fprintf(os.Stderr, "%s%s", f.locPrefix(pos), fmt.Sprintln(args...)) 565 } 566 567 // Warnf reports a formatted error but does not set the exit code. 568 func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) { 569 fmt.Fprintf(os.Stderr, "%s%s\n", f.locPrefix(pos), fmt.Sprintf(format, args...)) 570 } 571 572 // walkFile walks the file's tree. 573 func (f *File) walkFile(name string, file *ast.File) { 574 Println("Checking file", name) 575 ast.Walk(f, file) 576 } 577 578 // Visit implements the ast.Visitor interface. 579 func (f *File) Visit(node ast.Node) ast.Visitor { 580 f.updateDead(node) 581 var key ast.Node 582 switch node.(type) { 583 case *ast.AssignStmt: 584 key = assignStmt 585 case *ast.BinaryExpr: 586 key = binaryExpr 587 case *ast.CallExpr: 588 key = callExpr 589 case *ast.CompositeLit: 590 key = compositeLit 591 case *ast.ExprStmt: 592 key = exprStmt 593 case *ast.ForStmt: 594 key = forStmt 595 case *ast.FuncDecl: 596 key = funcDecl 597 case *ast.FuncLit: 598 key = funcLit 599 case *ast.GenDecl: 600 key = genDecl 601 case *ast.InterfaceType: 602 key = interfaceType 603 case *ast.RangeStmt: 604 key = rangeStmt 605 case *ast.ReturnStmt: 606 key = returnStmt 607 case *ast.StructType: 608 key = structType 609 } 610 for _, fn := range f.checkers[key] { 611 fn(f, node) 612 } 613 return f 614 } 615 616 // gofmt returns a string representation of the expression. 617 func (f *File) gofmt(x ast.Expr) string { 618 f.b.Reset() 619 printer.Fprint(&f.b, f.fset, x) 620 return f.b.String() 621 }