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