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