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