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