diff options
Diffstat (limited to 'src/cmd/govet/govet.go')
-rw-r--r-- | src/cmd/govet/govet.go | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/src/cmd/govet/govet.go b/src/cmd/govet/govet.go new file mode 100644 index 000000000..98d3d5c17 --- /dev/null +++ b/src/cmd/govet/govet.go @@ -0,0 +1,404 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Govet is a simple checker for static errors in Go source code. +// See doc.go for more information. +package main + +import ( + "flag" + "fmt" + "io" + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + "utf8" +) + +var verbose = flag.Bool("v", false, "verbose") +var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check") +var exitCode = 0 + +// setExit sets the value for os.Exit when it is called, later. It +// remembers the highest value. +func setExit(err int) { + if err > exitCode { + exitCode = err + } +} + +// Usage is a replacement usage function for the flags package. +func Usage() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + flag.PrintDefaults() + os.Exit(2) +} + +// 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 { + file *token.File +} + +func main() { + flag.Usage = Usage + flag.Parse() + + if *printfuncs != "" { + for _, name := range strings.Split(*printfuncs, ",") { + if len(name) == 0 { + flag.Usage() + } + skip := 0 + if colon := strings.LastIndex(name, ":"); colon > 0 { + var err os.Error + skip, err = strconv.Atoi(name[colon+1:]) + if err != nil { + errorf(`illegal format for "Func:N" argument %q; %s`, name, err) + } + name = name[:colon] + } + name = strings.ToLower(name) + if name[len(name)-1] == 'f' { + printfList[name] = skip + } else { + printList[name] = skip + } + } + } + + if flag.NArg() == 0 { + doFile("stdin", os.Stdin) + } else { + for _, name := range flag.Args() { + // Is it a directory? + if fi, err := os.Stat(name); err == nil && fi.IsDirectory() { + walkDir(name) + } else { + doFile(name, nil) + } + } + } + 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) + if err != nil { + errorf("%s: %s", name, err) + return + } + file := &File{fs.File(parsedFile.Pos())} + file.checkFile(name, parsedFile) +} + +// Visitor for filepath.Walk - trivial. Just calls doFile on each file. +// TODO: if govet becomes richer, might want to process +// a directory (package) at a time. +type V struct{} + +func (v V) VisitDir(path string, f *os.FileInfo) bool { + return true +} + +func (v V) VisitFile(path string, f *os.FileInfo) { + if strings.HasSuffix(path, ".go") { + doFile(path, nil) + } +} + +// walkDir recursively walks the tree looking for .go files. +func walkDir(root string) { + errors := make(chan os.Error) + done := make(chan bool) + go func() { + for e := range errors { + errorf("walk error: %s", e) + } + done <- true + }() + filepath.Walk(root, V{}, errors) + close(errors) + <-done +} + +// error formats the error to standard error, adding program +// identification and a newline +func errorf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "govet: "+format+"\n", args...) + setExit(2) +} + +// Println is fmt.Println guarded by -v. +func Println(args ...interface{}) { + if !*verbose { + return + } + fmt.Println(args...) +} + +// Printf is fmt.Printf guarded by -v. +func Printf(format string, args ...interface{}) { + if !*verbose { + return + } + fmt.Printf(format+"\n", args...) +} + +// Bad reports an error and sets the exit code.. +func (f *File) Bad(pos token.Pos, args ...interface{}) { + f.Warn(pos, args...) + setExit(1) +} + +// Badf reports a formatted error and sets the exit code. +func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { + f.Warnf(pos, format, args...) + setExit(1) +} + +// Warn reports an error but does not set the exit code. +func (f *File) Warn(pos token.Pos, args ...interface{}) { + loc := f.file.Position(pos).String() + ": " + fmt.Fprint(os.Stderr, loc+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.file.Position(pos).String() + ": " + fmt.Fprintf(os.Stderr, loc+format+"\n", args...) +} + +// checkFile checks all the top-level declarations in a file. +func (f *File) checkFile(name string, file *ast.File) { + Println("Checking file", name) + ast.Walk(f, file) +} + +// Visit implements the ast.Visitor interface. +func (f *File) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.CallExpr: + f.checkCallExpr(n) + case *ast.Field: + f.checkFieldTag(n) + } + return f +} + +// checkField checks a struct field tag. +func (f *File) checkFieldTag(field *ast.Field) { + if field.Tag == nil { + return + } + + tag, err := strconv.Unquote(field.Tag.Value) + if err != nil { + f.Warnf(field.Pos(), "unable to read struct tag %s", field.Tag.Value) + return + } + + // Check tag for validity by appending + // new key:value to end and checking that + // the tag parsing code can find it. + if reflect.StructTag(tag+` _gofix:"_magic"`).Get("_gofix") != "_magic" { + f.Warnf(field.Pos(), "struct field tag %s not compatible with reflect.StructTag.Get", field.Tag.Value) + return + } +} + +// checkCallExpr checks a call expression. +func (f *File) checkCallExpr(call *ast.CallExpr) { + switch x := call.Fun.(type) { + case *ast.Ident: + f.checkCall(call, x.Name) + case *ast.SelectorExpr: + f.checkCall(call, x.Sel.Name) + } +} + +// printfList records the formatted-print functions. The value is the location +// of the format parameter. Names are lower-cased so the lookup is +// case insensitive. +var printfList = map[string]int{ + "errorf": 0, + "fatalf": 0, + "fprintf": 1, + "panicf": 0, + "printf": 0, + "sprintf": 0, +} + +// printList records the unformatted-print functions. The value is the location +// of the first parameter to be printed. Names are lower-cased so the lookup is +// case insensitive. +var printList = map[string]int{ + "error": 0, + "fatal": 0, + "fprint": 1, "fprintln": 1, + "panic": 0, "panicln": 0, + "print": 0, "println": 0, + "sprint": 0, "sprintln": 0, +} + +// checkCall triggers the print-specific checks if the call invokes a print function. +func (f *File) checkCall(call *ast.CallExpr, Name string) { + name := strings.ToLower(Name) + if skip, ok := printfList[name]; ok { + f.checkPrintf(call, Name, skip) + return + } + if skip, ok := printList[name]; ok { + f.checkPrint(call, Name, skip) + return + } +} + +// checkPrintf checks a call to a formatted print routine such as Printf. +// The skip argument records how many arguments to ignore; that is, +// call.Args[skip] is (well, should be) the format argument. +func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) { + if len(call.Args) <= skip { + return + } + // Common case: literal is first argument. + arg := call.Args[skip] + lit, ok := arg.(*ast.BasicLit) + if !ok { + // Too hard to check. + if *verbose { + f.Warn(call.Pos(), "can't check args for call to", name) + } + return + } + if lit.Kind == token.STRING { + if !strings.Contains(lit.Value, "%") { + if len(call.Args) > skip+1 { + f.Badf(call.Pos(), "no formatting directive in %s call", name) + } + return + } + } + // Hard part: check formats against args. + // Trivial but useful test: count. + numArgs := 0 + for i, w := 0, 0; i < len(lit.Value); i += w { + w = 1 + if lit.Value[i] == '%' { + nbytes, nargs := parsePrintfVerb(lit.Value[i:]) + w = nbytes + numArgs += nargs + } + } + expect := len(call.Args) - (skip + 1) + if numArgs != expect { + f.Badf(call.Pos(), "wrong number of args in %s call: %d needed but %d args", name, numArgs, expect) + } +} + +// parsePrintfVerb returns the number of bytes and number of arguments +// consumed by the Printf directive that begins s, including its percent sign +// and verb. +func parsePrintfVerb(s string) (nbytes, nargs int) { + // There's guaranteed a percent sign. + nbytes = 1 + end := len(s) + // There may be flags. +FlagLoop: + for nbytes < end { + switch s[nbytes] { + case '#', '0', '+', '-', ' ': + nbytes++ + default: + break FlagLoop + } + } + getNum := func() { + if nbytes < end && s[nbytes] == '*' { + nbytes++ + nargs++ + } else { + for nbytes < end && '0' <= s[nbytes] && s[nbytes] <= '9' { + nbytes++ + } + } + } + // There may be a width. + getNum() + // If there's a period, there may be a precision. + if nbytes < end && s[nbytes] == '.' { + nbytes++ + getNum() + } + // Now a verb. + c, w := utf8.DecodeRuneInString(s[nbytes:]) + nbytes += w + if c != '%' { + nargs++ + } + return +} + +// checkPrint checks a call to an unformatted print routine such as Println. +// The skip argument records how many arguments to ignore; that is, +// call.Args[skip] is the first argument to be printed. +func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) { + isLn := strings.HasSuffix(name, "ln") + args := call.Args + if len(args) <= skip { + if *verbose && !isLn { + f.Badf(call.Pos(), "no args in %s call", name) + } + return + } + arg := args[skip] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + if strings.Contains(lit.Value, "%") { + f.Badf(call.Pos(), "possible formatting directive in %s call", name) + } + } + if isLn { + // The last item, if a string, should not have a newline. + arg = args[len(call.Args)-1] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + if strings.HasSuffix(lit.Value, `\n"`) { + f.Badf(call.Pos(), "%s call ends with newline", name) + } + } + } +} + +// This function never executes, but it serves as a simple test for the program. +// Test with make test. +func BadFunctionUsedInTests() { + fmt.Println() // not an error + fmt.Println("%s", "hi") // ERROR "possible formatting directive in Println call" + fmt.Printf("%s", "hi", 3) // ERROR "wrong number of args in Printf call" + fmt.Printf("%s%%%d", "hi", 3) // correct + fmt.Printf("%.*d", 3, 3) // correct + fmt.Printf("%.*d", 3, 3, 3) // ERROR "wrong number of args in Printf call" + printf("now is the time", "buddy") // ERROR "no formatting directive" + Printf("now is the time", "buddy") // ERROR "no formatting directive" + Printf("hi") // ok + f := new(File) + f.Warn(0, "%s", "hello", 3) // ERROR "possible formatting directive in Warn call" + f.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args in Warnf call" +} + +type BadTypeUsedInTests struct { + X int "hello" // ERROR "struct field tag" +} + +// printf is used by the test. +func printf(format string, args ...interface{}) { + panic("don't call - testing only") +} |