diff options
author | Michael Stapelberg <stapelberg@debian.org> | 2013-03-04 21:27:36 +0100 |
---|---|---|
committer | Michael Stapelberg <michael@stapelberg.de> | 2013-03-04 21:27:36 +0100 |
commit | 04b08da9af0c450d645ab7389d1467308cfc2db8 (patch) | |
tree | db247935fa4f2f94408edc3acd5d0d4f997aa0d8 /src/cmd/vet/main.go | |
parent | 917c5fb8ec48e22459d77e3849e6d388f93d3260 (diff) | |
download | golang-upstream/1.1_hg20130304.tar.gz |
Imported Upstream version 1.1~hg20130304upstream/1.1_hg20130304
Diffstat (limited to 'src/cmd/vet/main.go')
-rw-r--r-- | src/cmd/vet/main.go | 231 |
1 files changed, 196 insertions, 35 deletions
diff --git a/src/cmd/vet/main.go b/src/cmd/vet/main.go index 625133315..20f6cca1a 100644 --- a/src/cmd/vet/main.go +++ b/src/cmd/vet/main.go @@ -11,9 +11,12 @@ import ( "flag" "fmt" "go/ast" + "go/build" "go/parser" + "go/printer" "go/token" - "io" + "go/types" + "io/ioutil" "os" "path/filepath" "strconv" @@ -23,6 +26,24 @@ import ( var verbose = flag.Bool("v", false, "verbose") var exitCode = 0 +// Flags to control which checks to perform. "all" is set to true here, and disabled later if +// a flag is set explicitly. +var report = map[string]*bool{ + "all": flag.Bool("all", true, "check everything; disabled if any explicit check is requested"), + "atomic": flag.Bool("atomic", false, "check for common mistaken usages of the sync/atomic package"), + "buildtags": flag.Bool("buildtags", false, "check that +build tags are valid"), + "composites": flag.Bool("composites", false, "check that composite literals used type-tagged elements"), + "methods": flag.Bool("methods", false, "check that canonically named methods are canonically defined"), + "printf": flag.Bool("printf", false, "check printf-like invocations"), + "structtags": flag.Bool("structtags", false, "check that struct field tags have canonical format"), + "rangeloops": flag.Bool("rangeloops", false, "check that range loop variables are used correctly"), +} + +// vet tells whether to report errors for the named check, a flag name. +func vet(name string) bool { + return *report["all"] || *report[name] +} + // setExit sets the value for os.Exit when it is called, later. It // remembers the highest value. func setExit(err int) { @@ -34,6 +55,8 @@ func setExit(err int) { // Usage is a replacement usage function for the flags package. func Usage() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n") + fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n") flag.PrintDefaults() os.Exit(2) } @@ -41,7 +64,9 @@ func Usage() { // File is a wrapper for the state of a file used in the parser. // The parse tree walkers are all methods of this type. type File struct { + pkg *Package fset *token.FileSet + name string file *ast.File b bytes.Buffer // for use by methods } @@ -50,6 +75,14 @@ func main() { flag.Usage = Usage flag.Parse() + // If a check is named explicitly, turn off the 'all' flag. + for name, ptr := range report { + if name != "all" && *ptr { + *report["all"] = false + break + } + } + if *printfuncs != "" { for _, name := range strings.Split(*printfuncs, ",") { if len(name) == 0 { @@ -74,41 +107,135 @@ func main() { } if flag.NArg() == 0 { - doFile("stdin", os.Stdin) - } else { + Usage() + } + dirs := false + files := false + for _, name := range flag.Args() { + // Is it a directory? + fi, err := os.Stat(name) + if err != nil { + warnf("error walking tree: %s", err) + continue + } + if fi.IsDir() { + dirs = true + } else { + files = true + } + } + if dirs && files { + Usage() + } + if dirs { for _, name := range flag.Args() { - // Is it a directory? - if fi, err := os.Stat(name); err == nil && fi.IsDir() { - walkDir(name) - } else { - doFile(name, nil) - } + walkDir(name) } + return } + doPackage(flag.Args()) os.Exit(exitCode) } -// doFile analyzes one file. If the reader is nil, the source code is read from the -// named file. -func doFile(name string, reader io.Reader) { - fs := token.NewFileSet() - parsedFile, err := parser.ParseFile(fs, name, reader, 0) +// prefixDirectory places the directory name on the beginning of each name in the list. +func prefixDirectory(directory string, names []string) { + if directory != "." { + for i, name := range names { + names[i] = filepath.Join(directory, name) + } + } +} + +// doPackageDir analyzes the single package found in the directory, if there is one, +// plus a test package, if there is one. +func doPackageDir(directory string) { + pkg, err := build.Default.ImportDir(directory, 0) if err != nil { - errorf("%s: %s", name, err) + // If it's just that there are no go source files, that's fine. + if _, nogo := err.(*build.NoGoError); nogo { + return + } + // Non-fatal: we are doing a recursive walk and there may be other directories. + warnf("cannot process directory %s: %s", directory, err) return } - file := &File{fset: fs, file: parsedFile} - file.walkFile(name, parsedFile) + var names []string + names = append(names, pkg.GoFiles...) + names = append(names, pkg.CgoFiles...) + names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. + prefixDirectory(directory, names) + doPackage(names) + // Is there also a "foo_test" package? If so, do that one as well. + if len(pkg.XTestGoFiles) > 0 { + names = pkg.XTestGoFiles + prefixDirectory(directory, names) + doPackage(names) + } +} + +type Package struct { + types map[ast.Expr]types.Type + values map[ast.Expr]interface{} +} + +// doPackage analyzes the single package constructed from the named files. +func doPackage(names []string) { + var files []*File + var astFiles []*ast.File + fs := token.NewFileSet() + for _, name := range names { + f, err := os.Open(name) + if err != nil { + errorf("%s: %s", name, err) + } + defer f.Close() + data, err := ioutil.ReadAll(f) + if err != nil { + errorf("%s: %s", name, err) + } + checkBuildTag(name, data) + parsedFile, err := parser.ParseFile(fs, name, bytes.NewReader(data), 0) + if err != nil { + errorf("%s: %s", name, err) + } + files = append(files, &File{fset: fs, name: name, file: parsedFile}) + astFiles = append(astFiles, parsedFile) + } + pkg := new(Package) + pkg.types = make(map[ast.Expr]types.Type) + pkg.values = make(map[ast.Expr]interface{}) + exprFn := func(x ast.Expr, typ types.Type, val interface{}) { + pkg.types[x] = typ + if val != nil { + pkg.values[x] = val + } + } + // By providing the Context with our own error function, it will continue + // past the first error. There is no need for that function to do anything. + context := types.Context{ + Expr: exprFn, + Error: func(error) {}, + } + // Type check the package. + _, err := context.Check(fs, astFiles) + if err != nil && *verbose { + warnf("%s", err) + } + for _, file := range files { + file.pkg = pkg + file.walkFile(file.name, file.file) + } } func visit(path string, f os.FileInfo, err error) error { if err != nil { errorf("walk error: %s", err) - return nil } - if !f.IsDir() && strings.HasSuffix(path, ".go") { - doFile(path, nil) + // One package per directory. Ignore the files themselves. + if !f.IsDir() { + return nil } + doPackageDir(path) return nil } @@ -117,11 +244,18 @@ func walkDir(root string) { filepath.Walk(root, visit) } -// error formats the error to standard error, adding program -// identification and a newline +// errorf formats the error to standard error, adding program +// identification and a newline, and exits. func errorf(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) - setExit(2) + os.Exit(2) +} + +// warnf formats the error to standard error, adding program +// identification and a newline, but does not exit. +func warnf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) + setExit(1) } // Println is fmt.Println guarded by -v. @@ -152,16 +286,22 @@ func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { setExit(1) } +func (f *File) loc(pos token.Pos) string { + // Do not print columns. Because the pos often points to the start of an + // expression instead of the inner part with the actual error, the + // precision can mislead. + posn := f.fset.Position(pos) + return fmt.Sprintf("%s:%d: ", posn.Filename, posn.Line) +} + // Warn reports an error but does not set the exit code. func (f *File) Warn(pos token.Pos, args ...interface{}) { - loc := f.fset.Position(pos).String() + ": " - fmt.Fprint(os.Stderr, loc+fmt.Sprintln(args...)) + fmt.Fprint(os.Stderr, f.loc(pos)+fmt.Sprintln(args...)) } // Warnf reports a formatted error but does not set the exit code. func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) { - loc := f.fset.Position(pos).String() + ": " - fmt.Fprintf(os.Stderr, loc+format+"\n", args...) + fmt.Fprintf(os.Stderr, f.loc(pos)+format+"\n", args...) } // walkFile walks the file's tree. @@ -173,6 +313,8 @@ func (f *File) walkFile(name string, file *ast.File) { // Visit implements the ast.Visitor interface. func (f *File) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { + case *ast.AssignStmt: + f.walkAssignStmt(n) case *ast.CallExpr: f.walkCallExpr(n) case *ast.CompositeLit: @@ -183,15 +325,32 @@ func (f *File) Visit(node ast.Node) ast.Visitor { f.walkMethodDecl(n) case *ast.InterfaceType: f.walkInterfaceType(n) + case *ast.RangeStmt: + f.walkRangeStmt(n) } return f } +// walkAssignStmt walks an assignment statement +func (f *File) walkAssignStmt(stmt *ast.AssignStmt) { + f.checkAtomicAssignment(stmt) +} + // walkCall walks a call expression. func (f *File) walkCall(call *ast.CallExpr, name string) { f.checkFmtPrintfCall(call, name) } +// walkCallExpr walks a call expression. +func (f *File) walkCallExpr(call *ast.CallExpr) { + switch x := call.Fun.(type) { + case *ast.Ident: + f.walkCall(call, x.Name) + case *ast.SelectorExpr: + f.walkCall(call, x.Sel.Name) + } +} + // walkCompositeLit walks a composite literal. func (f *File) walkCompositeLit(c *ast.CompositeLit) { f.checkUntaggedLiteral(c) @@ -228,12 +387,14 @@ func (f *File) walkInterfaceType(t *ast.InterfaceType) { } } -// walkCallExpr walks a call expression. -func (f *File) walkCallExpr(call *ast.CallExpr) { - switch x := call.Fun.(type) { - case *ast.Ident: - f.walkCall(call, x.Name) - case *ast.SelectorExpr: - f.walkCall(call, x.Sel.Name) - } +// walkRangeStmt walks a range statement. +func (f *File) walkRangeStmt(n *ast.RangeStmt) { + checkRangeLoop(f, n) +} + +// gofmt returns a string representation of the expression. +func (f *File) gofmt(x ast.Expr) string { + f.b.Reset() + printer.Fprint(&f.b, f.fset, x) + return f.b.String() } |