github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/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 "io/ioutil" 19 "os" 20 "path/filepath" 21 "strconv" 22 "strings" 23 ) 24 25 var verbose = flag.Bool("v", false, "verbose") 26 var exitCode = 0 27 28 // Flags to control which checks to perform. "all" is set to true here, and disabled later if 29 // a flag is set explicitly. 30 var report = map[string]*bool{ 31 "all": flag.Bool("all", true, "check everything; disabled if any explicit check is requested"), 32 "asmdecl": flag.Bool("asmdecl", false, "check assembly against Go declarations"), 33 "assign": flag.Bool("assign", false, "check for useless assignments"), 34 "atomic": flag.Bool("atomic", false, "check for common mistaken usages of the sync/atomic package"), 35 "buildtags": flag.Bool("buildtags", false, "check that +build tags are valid"), 36 "composites": flag.Bool("composites", false, "check that composite literals used type-tagged elements"), 37 "methods": flag.Bool("methods", false, "check that canonically named methods are canonically defined"), 38 "printf": flag.Bool("printf", false, "check printf-like invocations"), 39 "rangeloops": flag.Bool("rangeloops", false, "check that range loop variables are used correctly"), 40 "structtags": flag.Bool("structtags", false, "check that struct field tags have canonical format"), 41 "unreachable": flag.Bool("unreachable", false, "check for unreachable code"), 42 } 43 44 // vet tells whether to report errors for the named check, a flag name. 45 func vet(name string) bool { 46 return *report["all"] || *report[name] 47 } 48 49 // setExit sets the value for os.Exit when it is called, later. It 50 // remembers the highest value. 51 func setExit(err int) { 52 if err > exitCode { 53 exitCode = err 54 } 55 } 56 57 // Usage is a replacement usage function for the flags package. 58 func Usage() { 59 fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 60 fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n") 61 fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n") 62 flag.PrintDefaults() 63 os.Exit(2) 64 } 65 66 // File is a wrapper for the state of a file used in the parser. 67 // The parse tree walkers are all methods of this type. 68 type File struct { 69 pkg *Package 70 fset *token.FileSet 71 name string 72 content []byte 73 file *ast.File 74 b bytes.Buffer // for use by methods 75 } 76 77 func main() { 78 flag.Usage = Usage 79 flag.Parse() 80 81 // If a check is named explicitly, turn off the 'all' flag. 82 for name, ptr := range report { 83 if name != "all" && *ptr { 84 *report["all"] = false 85 break 86 } 87 } 88 89 if *printfuncs != "" { 90 for _, name := range strings.Split(*printfuncs, ",") { 91 if len(name) == 0 { 92 flag.Usage() 93 } 94 skip := 0 95 if colon := strings.LastIndex(name, ":"); colon > 0 { 96 var err error 97 skip, err = strconv.Atoi(name[colon+1:]) 98 if err != nil { 99 errorf(`illegal format for "Func:N" argument %q; %s`, name, err) 100 } 101 name = name[:colon] 102 } 103 name = strings.ToLower(name) 104 if name[len(name)-1] == 'f' { 105 printfList[name] = skip 106 } else { 107 printList[name] = skip 108 } 109 } 110 } 111 112 if flag.NArg() == 0 { 113 Usage() 114 } 115 dirs := false 116 files := false 117 for _, name := range flag.Args() { 118 // Is it a directory? 119 fi, err := os.Stat(name) 120 if err != nil { 121 warnf("error walking tree: %s", err) 122 continue 123 } 124 if fi.IsDir() { 125 dirs = true 126 } else { 127 files = true 128 } 129 } 130 if dirs && files { 131 Usage() 132 } 133 if dirs { 134 for _, name := range flag.Args() { 135 walkDir(name) 136 } 137 return 138 } 139 doPackage(flag.Args()) 140 os.Exit(exitCode) 141 } 142 143 // prefixDirectory places the directory name on the beginning of each name in the list. 144 func prefixDirectory(directory string, names []string) { 145 if directory != "." { 146 for i, name := range names { 147 names[i] = filepath.Join(directory, name) 148 } 149 } 150 } 151 152 // doPackageDir analyzes the single package found in the directory, if there is one, 153 // plus a test package, if there is one. 154 func doPackageDir(directory string) { 155 pkg, err := build.Default.ImportDir(directory, 0) 156 if err != nil { 157 // If it's just that there are no go source files, that's fine. 158 if _, nogo := err.(*build.NoGoError); nogo { 159 return 160 } 161 // Non-fatal: we are doing a recursive walk and there may be other directories. 162 warnf("cannot process directory %s: %s", directory, err) 163 return 164 } 165 var names []string 166 names = append(names, pkg.GoFiles...) 167 names = append(names, pkg.CgoFiles...) 168 names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. 169 names = append(names, pkg.SFiles...) 170 prefixDirectory(directory, names) 171 doPackage(names) 172 // Is there also a "foo_test" package? If so, do that one as well. 173 if len(pkg.XTestGoFiles) > 0 { 174 names = pkg.XTestGoFiles 175 prefixDirectory(directory, names) 176 doPackage(names) 177 } 178 } 179 180 type Package struct { 181 types map[ast.Expr]Type 182 values map[ast.Expr]interface{} 183 files []*File 184 } 185 186 // doPackage analyzes the single package constructed from the named files. 187 func doPackage(names []string) { 188 var files []*File 189 var astFiles []*ast.File 190 fs := token.NewFileSet() 191 for _, name := range names { 192 f, err := os.Open(name) 193 if err != nil { 194 // Warn but continue to next package. 195 warnf("%s: %s", name, err) 196 return 197 } 198 defer f.Close() 199 data, err := ioutil.ReadAll(f) 200 if err != nil { 201 warnf("%s: %s", name, err) 202 return 203 } 204 checkBuildTag(name, data) 205 var parsedFile *ast.File 206 if strings.HasSuffix(name, ".go") { 207 parsedFile, err = parser.ParseFile(fs, name, bytes.NewReader(data), 0) 208 if err != nil { 209 warnf("%s: %s", name, err) 210 return 211 } 212 astFiles = append(astFiles, parsedFile) 213 } 214 files = append(files, &File{fset: fs, content: data, name: name, file: parsedFile}) 215 } 216 pkg := new(Package) 217 pkg.files = files 218 // Type check the package. 219 err := pkg.check(fs, astFiles) 220 if err != nil && *verbose { 221 warnf("%s", err) 222 } 223 for _, file := range files { 224 file.pkg = pkg 225 if file.file != nil { 226 file.walkFile(file.name, file.file) 227 } 228 } 229 asmCheck(pkg) 230 } 231 232 func visit(path string, f os.FileInfo, err error) error { 233 if err != nil { 234 warnf("walk error: %s", err) 235 return err 236 } 237 // One package per directory. Ignore the files themselves. 238 if !f.IsDir() { 239 return nil 240 } 241 doPackageDir(path) 242 return nil 243 } 244 245 func (pkg *Package) hasFileWithSuffix(suffix string) bool { 246 for _, f := range pkg.files { 247 if strings.HasSuffix(f.name, suffix) { 248 return true 249 } 250 } 251 return false 252 } 253 254 // walkDir recursively walks the tree looking for Go packages. 255 func walkDir(root string) { 256 filepath.Walk(root, visit) 257 } 258 259 // errorf formats the error to standard error, adding program 260 // identification and a newline, and exits. 261 func errorf(format string, args ...interface{}) { 262 fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) 263 os.Exit(2) 264 } 265 266 // warnf formats the error to standard error, adding program 267 // identification and a newline, but does not exit. 268 func warnf(format string, args ...interface{}) { 269 fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) 270 setExit(1) 271 } 272 273 // Println is fmt.Println guarded by -v. 274 func Println(args ...interface{}) { 275 if !*verbose { 276 return 277 } 278 fmt.Println(args...) 279 } 280 281 // Printf is fmt.Printf guarded by -v. 282 func Printf(format string, args ...interface{}) { 283 if !*verbose { 284 return 285 } 286 fmt.Printf(format+"\n", args...) 287 } 288 289 // Bad reports an error and sets the exit code.. 290 func (f *File) Bad(pos token.Pos, args ...interface{}) { 291 f.Warn(pos, args...) 292 setExit(1) 293 } 294 295 // Badf reports a formatted error and sets the exit code. 296 func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { 297 f.Warnf(pos, format, args...) 298 setExit(1) 299 } 300 301 func (f *File) loc(pos token.Pos) string { 302 if pos == token.NoPos { 303 return "" 304 } 305 // Do not print columns. Because the pos often points to the start of an 306 // expression instead of the inner part with the actual error, the 307 // precision can mislead. 308 posn := f.fset.Position(pos) 309 return fmt.Sprintf("%s:%d: ", posn.Filename, posn.Line) 310 } 311 312 // Warn reports an error but does not set the exit code. 313 func (f *File) Warn(pos token.Pos, args ...interface{}) { 314 fmt.Fprint(os.Stderr, f.loc(pos)+fmt.Sprintln(args...)) 315 } 316 317 // Warnf reports a formatted error but does not set the exit code. 318 func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) { 319 fmt.Fprintf(os.Stderr, f.loc(pos)+format+"\n", args...) 320 } 321 322 // walkFile walks the file's tree. 323 func (f *File) walkFile(name string, file *ast.File) { 324 Println("Checking file", name) 325 ast.Walk(f, file) 326 } 327 328 // Visit implements the ast.Visitor interface. 329 func (f *File) Visit(node ast.Node) ast.Visitor { 330 switch n := node.(type) { 331 case *ast.AssignStmt: 332 f.walkAssignStmt(n) 333 case *ast.CallExpr: 334 f.walkCallExpr(n) 335 case *ast.CompositeLit: 336 f.walkCompositeLit(n) 337 case *ast.Field: 338 f.walkFieldTag(n) 339 case *ast.FuncDecl: 340 f.walkFuncDecl(n) 341 case *ast.FuncLit: 342 f.walkFuncLit(n) 343 case *ast.InterfaceType: 344 f.walkInterfaceType(n) 345 case *ast.RangeStmt: 346 f.walkRangeStmt(n) 347 } 348 return f 349 } 350 351 // walkAssignStmt walks an assignment statement 352 func (f *File) walkAssignStmt(stmt *ast.AssignStmt) { 353 f.checkAssignStmt(stmt) 354 f.checkAtomicAssignment(stmt) 355 } 356 357 // walkCall walks a call expression. 358 func (f *File) walkCall(call *ast.CallExpr, name string) { 359 f.checkFmtPrintfCall(call, name) 360 } 361 362 // walkCallExpr walks a call expression. 363 func (f *File) walkCallExpr(call *ast.CallExpr) { 364 switch x := call.Fun.(type) { 365 case *ast.Ident: 366 f.walkCall(call, x.Name) 367 case *ast.SelectorExpr: 368 f.walkCall(call, x.Sel.Name) 369 } 370 } 371 372 // walkCompositeLit walks a composite literal. 373 func (f *File) walkCompositeLit(c *ast.CompositeLit) { 374 f.checkUntaggedLiteral(c) 375 } 376 377 // walkFieldTag walks a struct field tag. 378 func (f *File) walkFieldTag(field *ast.Field) { 379 if field.Tag == nil { 380 return 381 } 382 f.checkCanonicalFieldTag(field) 383 } 384 385 // walkMethod walks the method's signature. 386 func (f *File) walkMethod(id *ast.Ident, t *ast.FuncType) { 387 f.checkCanonicalMethod(id, t) 388 } 389 390 // walkFuncDecl walks a function declaration. 391 func (f *File) walkFuncDecl(d *ast.FuncDecl) { 392 f.checkUnreachable(d.Body) 393 if d.Recv != nil { 394 f.walkMethod(d.Name, d.Type) 395 } 396 } 397 398 // walkFuncLit walks a function literal. 399 func (f *File) walkFuncLit(x *ast.FuncLit) { 400 f.checkUnreachable(x.Body) 401 } 402 403 // walkInterfaceType walks the method signatures of an interface. 404 func (f *File) walkInterfaceType(t *ast.InterfaceType) { 405 for _, field := range t.Methods.List { 406 for _, id := range field.Names { 407 f.walkMethod(id, field.Type.(*ast.FuncType)) 408 } 409 } 410 } 411 412 // walkRangeStmt walks a range statement. 413 func (f *File) walkRangeStmt(n *ast.RangeStmt) { 414 checkRangeLoop(f, n) 415 } 416 417 // gofmt returns a string representation of the expression. 418 func (f *File) gofmt(x ast.Expr) string { 419 f.b.Reset() 420 printer.Fprint(&f.b, f.fset, x) 421 return f.b.String() 422 }