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