github.com/gocuntian/go@v0.0.0-20160610041250-fee02d270bf8/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 var ( 27 verbose = flag.Bool("v", false, "verbose") 28 tags = flag.String("tags", "", "comma-separated list of build tags to apply when parsing") 29 tagList = []string{} // exploded version of tags flag; set in main 30 ) 31 32 var exitCode = 0 33 34 // "-all" flag enables all non-experimental checks 35 var all = triStateFlag("all", unset, "enable all non-experimental checks") 36 37 // Flags to control which individual checks to perform. 38 var report = map[string]*triState{ 39 // Only unusual checks are written here. 40 // Most checks that operate during the AST walk are added by register. 41 "asmdecl": triStateFlag("asmdecl", unset, "check assembly against Go declarations"), 42 "buildtags": triStateFlag("buildtags", unset, "check that +build tags are valid"), 43 } 44 45 // experimental records the flags enabling experimental features. These must be 46 // requested explicitly; they are not enabled by -all. 47 var experimental = map[string]bool{} 48 49 // setTrueCount record how many flags are explicitly set to true. 50 var setTrueCount int 51 52 // dirsRun and filesRun indicate whether the vet is applied to directory or 53 // file targets. The distinction affects which checks are run. 54 var dirsRun, filesRun bool 55 56 // includesNonTest indicates whether the vet is applied to non-test targets. 57 // Certain checks are relevant only if they touch both test and non-test files. 58 var includesNonTest bool 59 60 // A triState is a boolean that knows whether it has been set to either true or false. 61 // It is used to identify if a flag appears; the standard boolean flag cannot 62 // distinguish missing from unset. It also satisfies flag.Value. 63 type triState int 64 65 const ( 66 unset triState = iota 67 setTrue 68 setFalse 69 ) 70 71 func triStateFlag(name string, value triState, usage string) *triState { 72 flag.Var(&value, name, usage) 73 return &value 74 } 75 76 // triState implements flag.Value, flag.Getter, and flag.boolFlag. 77 // They work like boolean flags: we can say vet -printf as well as vet -printf=true 78 func (ts *triState) Get() interface{} { 79 return *ts == setTrue 80 } 81 82 func (ts triState) isTrue() bool { 83 return ts == setTrue 84 } 85 86 func (ts *triState) Set(value string) error { 87 b, err := strconv.ParseBool(value) 88 if err != nil { 89 return err 90 } 91 if b { 92 *ts = setTrue 93 setTrueCount++ 94 } else { 95 *ts = setFalse 96 } 97 return nil 98 } 99 100 func (ts *triState) String() string { 101 switch *ts { 102 case unset: 103 return "true" // An unset flag will be set by -all, so defaults to true. 104 case setTrue: 105 return "true" 106 case setFalse: 107 return "false" 108 } 109 panic("not reached") 110 } 111 112 func (ts triState) IsBoolFlag() bool { 113 return true 114 } 115 116 // vet tells whether to report errors for the named check, a flag name. 117 func vet(name string) bool { 118 return report[name].isTrue() 119 } 120 121 // setExit sets the value for os.Exit when it is called, later. It 122 // remembers the highest value. 123 func setExit(err int) { 124 if err > exitCode { 125 exitCode = err 126 } 127 } 128 129 var ( 130 // Each of these vars has a corresponding case in (*File).Visit. 131 assignStmt *ast.AssignStmt 132 binaryExpr *ast.BinaryExpr 133 callExpr *ast.CallExpr 134 compositeLit *ast.CompositeLit 135 exprStmt *ast.ExprStmt 136 field *ast.Field 137 funcDecl *ast.FuncDecl 138 funcLit *ast.FuncLit 139 genDecl *ast.GenDecl 140 interfaceType *ast.InterfaceType 141 rangeStmt *ast.RangeStmt 142 returnStmt *ast.ReturnStmt 143 144 // checkers is a two-level map. 145 // The outer level is keyed by a nil pointer, one of the AST vars above. 146 // The inner level is keyed by checker name. 147 checkers = make(map[ast.Node]map[string]func(*File, ast.Node)) 148 ) 149 150 func register(name, usage string, fn func(*File, ast.Node), types ...ast.Node) { 151 report[name] = triStateFlag(name, unset, usage) 152 for _, typ := range types { 153 m := checkers[typ] 154 if m == nil { 155 m = make(map[string]func(*File, ast.Node)) 156 checkers[typ] = m 157 } 158 m[name] = fn 159 } 160 } 161 162 // Usage is a replacement usage function for the flags package. 163 func Usage() { 164 fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 165 fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n") 166 fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n") 167 fmt.Fprintf(os.Stderr, "By default, -all is set and all non-experimental checks are run.\n") 168 fmt.Fprintf(os.Stderr, "For more information run\n") 169 fmt.Fprintf(os.Stderr, "\tgo doc cmd/vet\n\n") 170 fmt.Fprintf(os.Stderr, "Flags:\n") 171 flag.PrintDefaults() 172 os.Exit(2) 173 } 174 175 // File is a wrapper for the state of a file used in the parser. 176 // The parse tree walkers are all methods of this type. 177 type File struct { 178 pkg *Package 179 fset *token.FileSet 180 name string 181 content []byte 182 file *ast.File 183 b bytes.Buffer // for use by methods 184 185 // The objects that are receivers of a "String() string" method. 186 // This is used by the recursiveStringer method in print.go. 187 stringers map[*ast.Object]bool 188 189 // Registered checkers to run. 190 checkers map[ast.Node][]func(*File, ast.Node) 191 } 192 193 func main() { 194 flag.Usage = Usage 195 flag.Parse() 196 197 // If any flag is set, we run only those checks requested. 198 // If all flag is set true or if no flags are set true, set all the non-experimental ones 199 // not explicitly set (in effect, set the "-all" flag). 200 if setTrueCount == 0 || *all == setTrue { 201 for name, setting := range report { 202 if *setting == unset && !experimental[name] { 203 *setting = setTrue 204 } 205 } 206 } 207 208 tagList = strings.Split(*tags, ",") 209 210 initPrintFlags() 211 initUnusedFlags() 212 213 if flag.NArg() == 0 { 214 Usage() 215 } 216 for _, name := range flag.Args() { 217 // Is it a directory? 218 fi, err := os.Stat(name) 219 if err != nil { 220 warnf("error walking tree: %s", err) 221 continue 222 } 223 if fi.IsDir() { 224 dirsRun = true 225 } else { 226 filesRun = true 227 if !strings.HasSuffix(name, "_test.go") { 228 includesNonTest = true 229 } 230 } 231 } 232 if dirsRun && filesRun { 233 Usage() 234 } 235 if dirsRun { 236 for _, name := range flag.Args() { 237 walkDir(name) 238 } 239 os.Exit(exitCode) 240 } 241 if !doPackage(".", flag.Args()) { 242 warnf("no files checked") 243 } 244 os.Exit(exitCode) 245 } 246 247 // prefixDirectory places the directory name on the beginning of each name in the list. 248 func prefixDirectory(directory string, names []string) { 249 if directory != "." { 250 for i, name := range names { 251 names[i] = filepath.Join(directory, name) 252 } 253 } 254 } 255 256 // doPackageDir analyzes the single package found in the directory, if there is one, 257 // plus a test package, if there is one. 258 func doPackageDir(directory string) { 259 context := build.Default 260 if len(context.BuildTags) != 0 { 261 warnf("build tags %s previously set", context.BuildTags) 262 } 263 context.BuildTags = append(tagList, context.BuildTags...) 264 265 pkg, err := context.ImportDir(directory, 0) 266 if err != nil { 267 // If it's just that there are no go source files, that's fine. 268 if _, nogo := err.(*build.NoGoError); nogo { 269 return 270 } 271 // Non-fatal: we are doing a recursive walk and there may be other directories. 272 warnf("cannot process directory %s: %s", directory, err) 273 return 274 } 275 var names []string 276 names = append(names, pkg.GoFiles...) 277 names = append(names, pkg.CgoFiles...) 278 names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. 279 names = append(names, pkg.SFiles...) 280 prefixDirectory(directory, names) 281 doPackage(directory, names) 282 // Is there also a "foo_test" package? If so, do that one as well. 283 if len(pkg.XTestGoFiles) > 0 { 284 names = pkg.XTestGoFiles 285 prefixDirectory(directory, names) 286 doPackage(directory, names) 287 } 288 } 289 290 type Package struct { 291 path string 292 defs map[*ast.Ident]types.Object 293 uses map[*ast.Ident]types.Object 294 selectors map[*ast.SelectorExpr]*types.Selection 295 types map[ast.Expr]types.TypeAndValue 296 spans map[types.Object]Span 297 files []*File 298 typesPkg *types.Package 299 } 300 301 // doPackage analyzes the single package constructed from the named files. 302 // It returns whether any files were checked. 303 func doPackage(directory string, names []string) bool { 304 var files []*File 305 var astFiles []*ast.File 306 fs := token.NewFileSet() 307 for _, name := range names { 308 data, err := ioutil.ReadFile(name) 309 if err != nil { 310 // Warn but continue to next package. 311 warnf("%s: %s", name, err) 312 return false 313 } 314 checkBuildTag(name, data) 315 var parsedFile *ast.File 316 if strings.HasSuffix(name, ".go") { 317 parsedFile, err = parser.ParseFile(fs, name, data, 0) 318 if err != nil { 319 warnf("%s: %s", name, err) 320 return false 321 } 322 astFiles = append(astFiles, parsedFile) 323 } 324 files = append(files, &File{fset: fs, content: data, name: name, file: parsedFile}) 325 } 326 if len(astFiles) == 0 { 327 return false 328 } 329 pkg := new(Package) 330 pkg.path = astFiles[0].Name.Name 331 pkg.files = files 332 // Type check the package. 333 err := pkg.check(fs, astFiles) 334 if err != nil && *verbose { 335 warnf("%s", err) 336 } 337 338 // Check. 339 chk := make(map[ast.Node][]func(*File, ast.Node)) 340 for typ, set := range checkers { 341 for name, fn := range set { 342 if vet(name) { 343 chk[typ] = append(chk[typ], fn) 344 } 345 } 346 } 347 for _, file := range files { 348 file.pkg = pkg 349 file.checkers = chk 350 if file.file != nil { 351 file.walkFile(file.name, file.file) 352 } 353 } 354 asmCheck(pkg) 355 return true 356 } 357 358 func visit(path string, f os.FileInfo, err error) error { 359 if err != nil { 360 warnf("walk error: %s", err) 361 return err 362 } 363 // One package per directory. Ignore the files themselves. 364 if !f.IsDir() { 365 return nil 366 } 367 doPackageDir(path) 368 return nil 369 } 370 371 func (pkg *Package) hasFileWithSuffix(suffix string) bool { 372 for _, f := range pkg.files { 373 if strings.HasSuffix(f.name, suffix) { 374 return true 375 } 376 } 377 return false 378 } 379 380 // walkDir recursively walks the tree looking for Go packages. 381 func walkDir(root string) { 382 filepath.Walk(root, visit) 383 } 384 385 // errorf formats the error to standard error, adding program 386 // identification and a newline, and exits. 387 func errorf(format string, args ...interface{}) { 388 fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) 389 os.Exit(2) 390 } 391 392 // warnf formats the error to standard error, adding program 393 // identification and a newline, but does not exit. 394 func warnf(format string, args ...interface{}) { 395 fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) 396 setExit(1) 397 } 398 399 // Println is fmt.Println guarded by -v. 400 func Println(args ...interface{}) { 401 if !*verbose { 402 return 403 } 404 fmt.Println(args...) 405 } 406 407 // Printf is fmt.Printf guarded by -v. 408 func Printf(format string, args ...interface{}) { 409 if !*verbose { 410 return 411 } 412 fmt.Printf(format+"\n", args...) 413 } 414 415 // Bad reports an error and sets the exit code.. 416 func (f *File) Bad(pos token.Pos, args ...interface{}) { 417 f.Warn(pos, args...) 418 setExit(1) 419 } 420 421 // Badf reports a formatted error and sets the exit code. 422 func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { 423 f.Warnf(pos, format, args...) 424 setExit(1) 425 } 426 427 // loc returns a formatted representation of the position. 428 func (f *File) loc(pos token.Pos) string { 429 if pos == token.NoPos { 430 return "" 431 } 432 // Do not print columns. Because the pos often points to the start of an 433 // expression instead of the inner part with the actual error, the 434 // precision can mislead. 435 posn := f.fset.Position(pos) 436 return fmt.Sprintf("%s:%d", posn.Filename, posn.Line) 437 } 438 439 // Warn reports an error but does not set the exit code. 440 func (f *File) Warn(pos token.Pos, args ...interface{}) { 441 fmt.Fprintf(os.Stderr, "%s: %s", f.loc(pos), fmt.Sprintln(args...)) 442 } 443 444 // Warnf reports a formatted error but does not set the exit code. 445 func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) { 446 fmt.Fprintf(os.Stderr, "%s: %s\n", f.loc(pos), fmt.Sprintf(format, args...)) 447 } 448 449 // walkFile walks the file's tree. 450 func (f *File) walkFile(name string, file *ast.File) { 451 Println("Checking file", name) 452 ast.Walk(f, file) 453 } 454 455 // Visit implements the ast.Visitor interface. 456 func (f *File) Visit(node ast.Node) ast.Visitor { 457 var key ast.Node 458 switch node.(type) { 459 case *ast.AssignStmt: 460 key = assignStmt 461 case *ast.BinaryExpr: 462 key = binaryExpr 463 case *ast.CallExpr: 464 key = callExpr 465 case *ast.CompositeLit: 466 key = compositeLit 467 case *ast.ExprStmt: 468 key = exprStmt 469 case *ast.Field: 470 key = field 471 case *ast.FuncDecl: 472 key = funcDecl 473 case *ast.FuncLit: 474 key = funcLit 475 case *ast.GenDecl: 476 key = genDecl 477 case *ast.InterfaceType: 478 key = interfaceType 479 case *ast.RangeStmt: 480 key = rangeStmt 481 case *ast.ReturnStmt: 482 key = returnStmt 483 } 484 for _, fn := range f.checkers[key] { 485 fn(f, node) 486 } 487 return f 488 } 489 490 // gofmt returns a string representation of the expression. 491 func (f *File) gofmt(x ast.Expr) string { 492 f.b.Reset() 493 printer.Fprint(&f.b, f.fset, x) 494 return f.b.String() 495 }