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. 7package main 8 9import ( 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 31var ( 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 41var exitCode = 0 42 43// "-all" flag enables all non-experimental checks 44var all = triStateFlag("all", unset, "enable all non-experimental checks") 45 46// Flags to control which individual checks to perform. 47var 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. 56var experimental = map[string]bool{} 57 58// setTrueCount record how many flags are explicitly set to true. 59var 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. 63var 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. 67var 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. 72type triState int 73 74const ( 75 unset triState = iota 76 setTrue 77 setFalse 78) 79 80func 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 87func (ts *triState) Get() interface{} { 88 return *ts == setTrue 89} 90 91func (ts triState) isTrue() bool { 92 return ts == setTrue 93} 94 95func (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 109func (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 121func (ts triState) IsBoolFlag() bool { 122 return true 123} 124 125// vet tells whether to report errors for the named check, a flag name. 126func 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. 132func setExit(err int) { 133 if err > exitCode { 134 exitCode = err 135 } 136} 137 138var ( 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 160func 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. 173func 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. 187type 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 209func 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. 279func 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. 288type 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 Standard map[string]bool 296 297 SucceedOnTypecheckFailure bool 298 299 imp types.Importer 300} 301 302func (v *vetConfig) Import(path string) (*types.Package, error) { 303 if v.imp == nil { 304 v.imp = importer.For(v.Compiler, v.openPackageFile) 305 } 306 if path == "unsafe" { 307 return v.imp.Import("unsafe") 308 } 309 p := v.ImportMap[path] 310 if p == "" { 311 return nil, fmt.Errorf("unknown import path %q", path) 312 } 313 if v.PackageFile[p] == "" { 314 if v.Compiler == "gccgo" && v.Standard[path] { 315 // gccgo doesn't have sources for standard library packages, 316 // but the importer will do the right thing. 317 return v.imp.Import(path) 318 } 319 return nil, fmt.Errorf("unknown package file for import %q", path) 320 } 321 return v.imp.Import(p) 322} 323 324func (v *vetConfig) openPackageFile(path string) (io.ReadCloser, error) { 325 file := v.PackageFile[path] 326 if file == "" { 327 if v.Compiler == "gccgo" && v.Standard[path] { 328 // The importer knows how to handle this. 329 return nil, nil 330 } 331 // Note that path here has been translated via v.ImportMap, 332 // unlike in the error in Import above. We prefer the error in 333 // Import, but it's worth diagnosing this one too, just in case. 334 return nil, fmt.Errorf("unknown package file for %q", path) 335 } 336 f, err := os.Open(file) 337 if err != nil { 338 return nil, err 339 } 340 return f, nil 341} 342 343// doPackageCfg analyzes a single package described in a config file. 344func doPackageCfg(cfgFile string) { 345 js, err := ioutil.ReadFile(cfgFile) 346 if err != nil { 347 errorf("%v", err) 348 } 349 if err := json.Unmarshal(js, &vcfg); err != nil { 350 errorf("parsing vet config %s: %v", cfgFile, err) 351 } 352 stdImporter = &vcfg 353 inittypes() 354 mustTypecheck = true 355 doPackage(vcfg.GoFiles, nil) 356} 357 358// doPackageDir analyzes the single package found in the directory, if there is one, 359// plus a test package, if there is one. 360func doPackageDir(directory string) { 361 context := build.Default 362 if len(context.BuildTags) != 0 { 363 warnf("build tags %s previously set", context.BuildTags) 364 } 365 context.BuildTags = append(tagList, context.BuildTags...) 366 367 pkg, err := context.ImportDir(directory, 0) 368 if err != nil { 369 // If it's just that there are no go source files, that's fine. 370 if _, nogo := err.(*build.NoGoError); nogo { 371 return 372 } 373 // Non-fatal: we are doing a recursive walk and there may be other directories. 374 warnf("cannot process directory %s: %s", directory, err) 375 return 376 } 377 var names []string 378 names = append(names, pkg.GoFiles...) 379 names = append(names, pkg.CgoFiles...) 380 names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. 381 names = append(names, pkg.SFiles...) 382 prefixDirectory(directory, names) 383 basePkg := doPackage(names, nil) 384 // Is there also a "foo_test" package? If so, do that one as well. 385 if len(pkg.XTestGoFiles) > 0 { 386 names = pkg.XTestGoFiles 387 prefixDirectory(directory, names) 388 doPackage(names, basePkg) 389 } 390} 391 392type Package struct { 393 path string 394 defs map[*ast.Ident]types.Object 395 uses map[*ast.Ident]types.Object 396 selectors map[*ast.SelectorExpr]*types.Selection 397 types map[ast.Expr]types.TypeAndValue 398 spans map[types.Object]Span 399 files []*File 400 typesPkg *types.Package 401} 402 403// doPackage analyzes the single package constructed from the named files. 404// It returns the parsed Package or nil if none of the files have been checked. 405func doPackage(names []string, basePkg *Package) *Package { 406 var files []*File 407 var astFiles []*ast.File 408 fs := token.NewFileSet() 409 for _, name := range names { 410 data, err := ioutil.ReadFile(name) 411 if err != nil { 412 // Warn but continue to next package. 413 warnf("%s: %s", name, err) 414 return nil 415 } 416 checkBuildTag(name, data) 417 var parsedFile *ast.File 418 if strings.HasSuffix(name, ".go") { 419 parsedFile, err = parser.ParseFile(fs, name, data, 0) 420 if err != nil { 421 warnf("%s: %s", name, err) 422 return nil 423 } 424 astFiles = append(astFiles, parsedFile) 425 } 426 files = append(files, &File{ 427 fset: fs, 428 content: data, 429 name: name, 430 file: parsedFile, 431 dead: make(map[ast.Node]bool), 432 }) 433 } 434 if len(astFiles) == 0 { 435 return nil 436 } 437 pkg := new(Package) 438 pkg.path = astFiles[0].Name.Name 439 pkg.files = files 440 // Type check the package. 441 errs := pkg.check(fs, astFiles) 442 if errs != nil { 443 if vcfg.SucceedOnTypecheckFailure { 444 os.Exit(0) 445 } 446 if *verbose || mustTypecheck { 447 for _, err := range errs { 448 fmt.Fprintf(os.Stderr, "%v\n", err) 449 } 450 if mustTypecheck { 451 // This message could be silenced, and we could just exit, 452 // but it might be helpful at least at first to make clear that the 453 // above errors are coming from vet and not the compiler 454 // (they often look like compiler errors, such as "declared but not used"). 455 errorf("typecheck failures") 456 } 457 } 458 } 459 460 // Check. 461 chk := make(map[ast.Node][]func(*File, ast.Node)) 462 for typ, set := range checkers { 463 for name, fn := range set { 464 if vet(name) { 465 chk[typ] = append(chk[typ], fn) 466 } 467 } 468 } 469 for _, file := range files { 470 file.pkg = pkg 471 file.basePkg = basePkg 472 file.checkers = chk 473 if file.file != nil { 474 file.walkFile(file.name, file.file) 475 } 476 } 477 asmCheck(pkg) 478 return pkg 479} 480 481func visit(path string, f os.FileInfo, err error) error { 482 if err != nil { 483 warnf("walk error: %s", err) 484 return err 485 } 486 // One package per directory. Ignore the files themselves. 487 if !f.IsDir() { 488 return nil 489 } 490 doPackageDir(path) 491 return nil 492} 493 494func (pkg *Package) hasFileWithSuffix(suffix string) bool { 495 for _, f := range pkg.files { 496 if strings.HasSuffix(f.name, suffix) { 497 return true 498 } 499 } 500 return false 501} 502 503// walkDir recursively walks the tree looking for Go packages. 504func walkDir(root string) { 505 filepath.Walk(root, visit) 506} 507 508// errorf formats the error to standard error, adding program 509// identification and a newline, and exits. 510func errorf(format string, args ...interface{}) { 511 fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) 512 os.Exit(2) 513} 514 515// warnf formats the error to standard error, adding program 516// identification and a newline, but does not exit. 517func warnf(format string, args ...interface{}) { 518 fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) 519 setExit(1) 520} 521 522// Println is fmt.Println guarded by -v. 523func Println(args ...interface{}) { 524 if !*verbose { 525 return 526 } 527 fmt.Println(args...) 528} 529 530// Printf is fmt.Printf guarded by -v. 531func Printf(format string, args ...interface{}) { 532 if !*verbose { 533 return 534 } 535 fmt.Printf(format+"\n", args...) 536} 537 538// Bad reports an error and sets the exit code.. 539func (f *File) Bad(pos token.Pos, args ...interface{}) { 540 f.Warn(pos, args...) 541 setExit(1) 542} 543 544// Badf reports a formatted error and sets the exit code. 545func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { 546 f.Warnf(pos, format, args...) 547 setExit(1) 548} 549 550// loc returns a formatted representation of the position. 551func (f *File) loc(pos token.Pos) string { 552 if pos == token.NoPos { 553 return "" 554 } 555 // Do not print columns. Because the pos often points to the start of an 556 // expression instead of the inner part with the actual error, the 557 // precision can mislead. 558 posn := f.fset.Position(pos) 559 return fmt.Sprintf("%s:%d", posn.Filename, posn.Line) 560} 561 562// locPrefix returns a formatted representation of the position for use as a line prefix. 563func (f *File) locPrefix(pos token.Pos) string { 564 if pos == token.NoPos { 565 return "" 566 } 567 return fmt.Sprintf("%s: ", f.loc(pos)) 568} 569 570// Warn reports an error but does not set the exit code. 571func (f *File) Warn(pos token.Pos, args ...interface{}) { 572 fmt.Fprintf(os.Stderr, "%s%s", f.locPrefix(pos), fmt.Sprintln(args...)) 573} 574 575// Warnf reports a formatted error but does not set the exit code. 576func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) { 577 fmt.Fprintf(os.Stderr, "%s%s\n", f.locPrefix(pos), fmt.Sprintf(format, args...)) 578} 579 580// walkFile walks the file's tree. 581func (f *File) walkFile(name string, file *ast.File) { 582 Println("Checking file", name) 583 ast.Walk(f, file) 584} 585 586// Visit implements the ast.Visitor interface. 587func (f *File) Visit(node ast.Node) ast.Visitor { 588 f.updateDead(node) 589 var key ast.Node 590 switch node.(type) { 591 case *ast.AssignStmt: 592 key = assignStmt 593 case *ast.BinaryExpr: 594 key = binaryExpr 595 case *ast.CallExpr: 596 key = callExpr 597 case *ast.CompositeLit: 598 key = compositeLit 599 case *ast.ExprStmt: 600 key = exprStmt 601 case *ast.ForStmt: 602 key = forStmt 603 case *ast.FuncDecl: 604 key = funcDecl 605 case *ast.FuncLit: 606 key = funcLit 607 case *ast.GenDecl: 608 key = genDecl 609 case *ast.InterfaceType: 610 key = interfaceType 611 case *ast.RangeStmt: 612 key = rangeStmt 613 case *ast.ReturnStmt: 614 key = returnStmt 615 case *ast.StructType: 616 key = structType 617 } 618 for _, fn := range f.checkers[key] { 619 fn(f, node) 620 } 621 return f 622} 623 624// gofmt returns a string representation of the expression. 625func (f *File) gofmt(x ast.Expr) string { 626 f.b.Reset() 627 printer.Fprint(&f.b, f.fset, x) 628 return f.b.String() 629} 630