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