summaryrefslogtreecommitdiff
path: root/src/cmd/vet
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2012-04-06 15:14:11 +0200
committerOndřej Surý <ondrej@sury.org>2012-04-06 15:14:11 +0200
commit505c19580e0f43fe5224431459cacb7c21edd93d (patch)
tree79e2634c253d60afc0cc0b2f510dc7dcbb48497b /src/cmd/vet
parent1336a7c91e596c423a49d1194ea42d98bca0d958 (diff)
downloadgolang-505c19580e0f43fe5224431459cacb7c21edd93d.tar.gz
Imported Upstream version 1upstream/1
Diffstat (limited to 'src/cmd/vet')
-rw-r--r--src/cmd/vet/Makefile7
-rw-r--r--src/cmd/vet/doc.go62
-rw-r--r--src/cmd/vet/main.go239
-rw-r--r--src/cmd/vet/method.go161
-rw-r--r--src/cmd/vet/print.go278
-rw-r--r--src/cmd/vet/structtag.go34
-rw-r--r--src/cmd/vet/taglit.go121
7 files changed, 902 insertions, 0 deletions
diff --git a/src/cmd/vet/Makefile b/src/cmd/vet/Makefile
new file mode 100644
index 000000000..2a35d1ae3
--- /dev/null
+++ b/src/cmd/vet/Makefile
@@ -0,0 +1,7 @@
+# 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.
+
+test testshort:
+ go build
+ ../../../test/errchk ./vet -printfuncs='Warn:1,Warnf:1' print.go
diff --git a/src/cmd/vet/doc.go b/src/cmd/vet/doc.go
new file mode 100644
index 000000000..620964aaf
--- /dev/null
+++ b/src/cmd/vet/doc.go
@@ -0,0 +1,62 @@
+// 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.
+
+/*
+
+Vet examines Go source code and reports suspicious constructs, such as Printf
+calls whose arguments do not align with the format string. Vet uses heuristics
+that do not guarantee all reports are genuine problems, but it can find errors
+not caught by the compilers.
+
+Available checks:
+
+1. Printf family
+
+Suspicious calls to functions in the Printf family, including any functions
+with these names:
+ Print Printf Println
+ Fprint Fprintf Fprintln
+ Sprint Sprintf Sprintln
+ Error Errorf
+ Fatal Fatalf
+ Panic Panicf Panicln
+If the function name ends with an 'f', the function is assumed to take
+a format descriptor string in the manner of fmt.Printf. If not, vet
+complains about arguments that look like format descriptor strings.
+
+It also checks for errors such as using a Writer as the first argument of
+Printf.
+
+2. Methods
+
+Non-standard signatures for methods with familiar names, including:
+ Format GobEncode GobDecode MarshalJSON MarshalXML
+ Peek ReadByte ReadFrom ReadRune Scan Seek
+ UnmarshalJSON UnreadByte UnreadRune WriteByte
+ WriteTo
+
+3. Struct tags
+
+Struct tags that do not follow the format understood by reflect.StructTag.Get.
+
+Usage:
+
+ go tool vet [flag] [file.go ...]
+ go tool vet [flag] [directory ...] # Scan all .go files under directory, recursively
+
+The flags are:
+ -v
+ Verbose mode
+ -printfuncs
+ A comma-separated list of print-like functions to supplement
+ the standard list. Each entry is in the form Name:N where N
+ is the zero-based argument position of the first argument
+ involved in the print: either the format or the first print
+ argument for non-formatted prints. For example,
+ if you have Warn and Warnf functions that take an
+ io.Writer as their first argument, like Fprintf,
+ -printfuncs=Warn:1,Warnf:1
+
+*/
+package documentation
diff --git a/src/cmd/vet/main.go b/src/cmd/vet/main.go
new file mode 100644
index 000000000..625133315
--- /dev/null
+++ b/src/cmd/vet/main.go
@@ -0,0 +1,239 @@
+// 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.
+
+// Vet is a simple checker for static errors in Go source code.
+// See doc.go for more information.
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+)
+
+var verbose = flag.Bool("v", false, "verbose")
+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 {
+ fset *token.FileSet
+ file *ast.File
+ b bytes.Buffer // for use by methods
+}
+
+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 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.IsDir() {
+ 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{fset: fs, file: parsedFile}
+ file.walkFile(name, parsedFile)
+}
+
+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)
+ }
+ return nil
+}
+
+// walkDir recursively walks the tree looking for .go files.
+func walkDir(root string) {
+ filepath.Walk(root, visit)
+}
+
+// error formats the error to standard error, adding program
+// identification and a newline
+func errorf(format string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, "vet: "+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.fset.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.fset.Position(pos).String() + ": "
+ fmt.Fprintf(os.Stderr, loc+format+"\n", args...)
+}
+
+// walkFile walks the file's tree.
+func (f *File) walkFile(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.walkCallExpr(n)
+ case *ast.CompositeLit:
+ f.walkCompositeLit(n)
+ case *ast.Field:
+ f.walkFieldTag(n)
+ case *ast.FuncDecl:
+ f.walkMethodDecl(n)
+ case *ast.InterfaceType:
+ f.walkInterfaceType(n)
+ }
+ return f
+}
+
+// walkCall walks a call expression.
+func (f *File) walkCall(call *ast.CallExpr, name string) {
+ f.checkFmtPrintfCall(call, name)
+}
+
+// walkCompositeLit walks a composite literal.
+func (f *File) walkCompositeLit(c *ast.CompositeLit) {
+ f.checkUntaggedLiteral(c)
+}
+
+// walkFieldTag walks a struct field tag.
+func (f *File) walkFieldTag(field *ast.Field) {
+ if field.Tag == nil {
+ return
+ }
+ f.checkCanonicalFieldTag(field)
+}
+
+// walkMethodDecl walks the method's signature.
+func (f *File) walkMethod(id *ast.Ident, t *ast.FuncType) {
+ f.checkCanonicalMethod(id, t)
+}
+
+// walkMethodDecl walks the method signature in the declaration.
+func (f *File) walkMethodDecl(d *ast.FuncDecl) {
+ if d.Recv == nil {
+ // not a method
+ return
+ }
+ f.walkMethod(d.Name, d.Type)
+}
+
+// walkInterfaceType walks the method signatures of an interface.
+func (f *File) walkInterfaceType(t *ast.InterfaceType) {
+ for _, field := range t.Methods.List {
+ for _, id := range field.Names {
+ f.walkMethod(id, field.Type.(*ast.FuncType))
+ }
+ }
+}
+
+// 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)
+ }
+}
diff --git a/src/cmd/vet/method.go b/src/cmd/vet/method.go
new file mode 100644
index 000000000..41cb40ff9
--- /dev/null
+++ b/src/cmd/vet/method.go
@@ -0,0 +1,161 @@
+// 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.
+
+// This file contains the code to check canonical methods.
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/printer"
+ "strings"
+)
+
+type MethodSig struct {
+ args []string
+ results []string
+}
+
+// canonicalMethods lists the input and output types for Go methods
+// that are checked using dynamic interface checks. Because the
+// checks are dynamic, such methods would not cause a compile error
+// if they have the wrong signature: instead the dynamic check would
+// fail, sometimes mysteriously. If a method is found with a name listed
+// here but not the input/output types listed here, vet complains.
+//
+// A few of the canonical methods have very common names.
+// For example, a type might implement a Scan method that
+// has nothing to do with fmt.Scanner, but we still want to check
+// the methods that are intended to implement fmt.Scanner.
+// To do that, the arguments that have a = prefix are treated as
+// signals that the canonical meaning is intended: if a Scan
+// method doesn't have a fmt.ScanState as its first argument,
+// we let it go. But if it does have a fmt.ScanState, then the
+// rest has to match.
+var canonicalMethods = map[string]MethodSig{
+ // "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict
+ "Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter
+ "GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder
+ "GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder
+ "MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler
+ "MarshalXML": {[]string{}, []string{"[]byte", "error"}}, // xml.Marshaler
+ "Peek": {[]string{"=int"}, []string{"[]byte", "error"}}, // image.reader (matching bufio.Reader)
+ "ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader
+ "ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom
+ "ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader
+ "Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner
+ "Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker
+ "UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler
+ "UnreadByte": {[]string{}, []string{"error"}},
+ "UnreadRune": {[]string{}, []string{"error"}},
+ "WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer)
+ "WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo
+}
+
+func (f *File) checkCanonicalMethod(id *ast.Ident, t *ast.FuncType) {
+ // Expected input/output.
+ expect, ok := canonicalMethods[id.Name]
+ if !ok {
+ return
+ }
+
+ // Actual input/output
+ args := typeFlatten(t.Params.List)
+ var results []ast.Expr
+ if t.Results != nil {
+ results = typeFlatten(t.Results.List)
+ }
+
+ // Do the =s (if any) all match?
+ if !f.matchParams(expect.args, args, "=") || !f.matchParams(expect.results, results, "=") {
+ return
+ }
+
+ // Everything must match.
+ if !f.matchParams(expect.args, args, "") || !f.matchParams(expect.results, results, "") {
+ expectFmt := id.Name + "(" + argjoin(expect.args) + ")"
+ if len(expect.results) == 1 {
+ expectFmt += " " + argjoin(expect.results)
+ } else if len(expect.results) > 1 {
+ expectFmt += " (" + argjoin(expect.results) + ")"
+ }
+
+ f.b.Reset()
+ if err := printer.Fprint(&f.b, f.fset, t); err != nil {
+ fmt.Fprintf(&f.b, "<%s>", err)
+ }
+ actual := f.b.String()
+ if strings.HasPrefix(actual, "func(") {
+ actual = actual[4:]
+ }
+ actual = id.Name + actual
+
+ f.Warnf(id.Pos(), "method %s should have signature %s", actual, expectFmt)
+ }
+}
+
+func argjoin(x []string) string {
+ y := make([]string, len(x))
+ for i, s := range x {
+ if s[0] == '=' {
+ s = s[1:]
+ }
+ y[i] = s
+ }
+ return strings.Join(y, ", ")
+}
+
+// Turn parameter list into slice of types
+// (in the ast, types are Exprs).
+// Have to handle f(int, bool) and f(x, y, z int)
+// so not a simple 1-to-1 conversion.
+func typeFlatten(l []*ast.Field) []ast.Expr {
+ var t []ast.Expr
+ for _, f := range l {
+ if len(f.Names) == 0 {
+ t = append(t, f.Type)
+ continue
+ }
+ for _ = range f.Names {
+ t = append(t, f.Type)
+ }
+ }
+ return t
+}
+
+// Does each type in expect with the given prefix match the corresponding type in actual?
+func (f *File) matchParams(expect []string, actual []ast.Expr, prefix string) bool {
+ for i, x := range expect {
+ if !strings.HasPrefix(x, prefix) {
+ continue
+ }
+ if i >= len(actual) {
+ return false
+ }
+ if !f.matchParamType(x, actual[i]) {
+ return false
+ }
+ }
+ if prefix == "" && len(actual) > len(expect) {
+ return false
+ }
+ return true
+}
+
+// Does this one type match?
+func (f *File) matchParamType(expect string, actual ast.Expr) bool {
+ if strings.HasPrefix(expect, "=") {
+ expect = expect[1:]
+ }
+ // Strip package name if we're in that package.
+ if n := len(f.file.Name.Name); len(expect) > n && expect[:n] == f.file.Name.Name && expect[n] == '.' {
+ expect = expect[n+1:]
+ }
+
+ // Overkill but easy.
+ f.b.Reset()
+ printer.Fprint(&f.b, f.fset, actual)
+ return f.b.String() == expect
+}
diff --git a/src/cmd/vet/print.go b/src/cmd/vet/print.go
new file mode 100644
index 000000000..ee9a33c70
--- /dev/null
+++ b/src/cmd/vet/print.go
@@ -0,0 +1,278 @@
+// 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.
+
+// This file contains the printf-checker.
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "strings"
+ "unicode/utf8"
+)
+
+var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check")
+
+// 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) checkFmtPrintfCall(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 non-literal format in 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 := f.parsePrintfVerb(call, 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 (f *File) parsePrintfVerb(call *ast.CallExpr, s string) (nbytes, nargs int) {
+ // There's guaranteed a percent sign.
+ flags := make([]byte, 0, 5)
+ nbytes = 1
+ end := len(s)
+ // There may be flags.
+FlagLoop:
+ for nbytes < end {
+ switch s[nbytes] {
+ case '#', '0', '+', '-', ' ':
+ flags = append(flags, s[nbytes])
+ 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] == '.' {
+ flags = append(flags, '.') // Treat precision as a flag.
+ nbytes++
+ getNum()
+ }
+ // Now a verb.
+ c, w := utf8.DecodeRuneInString(s[nbytes:])
+ nbytes += w
+ if c != '%' {
+ nargs++
+ f.checkPrintfVerb(call, c, flags)
+ }
+ return
+}
+
+type printVerb struct {
+ verb rune
+ flags string // known flags are all ASCII
+}
+
+// Common flag sets for printf verbs.
+const (
+ numFlag = " -+.0"
+ sharpNumFlag = " -+.0#"
+ allFlags = " -+.0#"
+)
+
+// printVerbs identifies which flags are known to printf for each verb.
+// TODO: A type that implements Formatter may do what it wants, and vet
+// will complain incorrectly.
+var printVerbs = []printVerb{
+ // '-' is a width modifier, always valid.
+ // '.' is a precision for float, max width for strings.
+ // '+' is required sign for numbers, Go format for %v.
+ // '#' is alternate format for several verbs.
+ // ' ' is spacer for numbers
+ {'b', numFlag},
+ {'c', "-"},
+ {'d', numFlag},
+ {'e', numFlag},
+ {'E', numFlag},
+ {'f', numFlag},
+ {'F', numFlag},
+ {'g', numFlag},
+ {'G', numFlag},
+ {'o', sharpNumFlag},
+ {'p', "-#"},
+ {'q', "-+#."},
+ {'s', "-."},
+ {'t', "-"},
+ {'T', "-"},
+ {'U', "-#"},
+ {'v', allFlags},
+ {'x', sharpNumFlag},
+ {'X', sharpNumFlag},
+}
+
+const printfVerbs = "bcdeEfFgGopqstTvxUX"
+
+func (f *File) checkPrintfVerb(call *ast.CallExpr, verb rune, flags []byte) {
+ // Linear scan is fast enough for a small list.
+ for _, v := range printVerbs {
+ if v.verb == verb {
+ for _, flag := range flags {
+ if !strings.ContainsRune(v.flags, rune(flag)) {
+ f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", verb, flag)
+ }
+ }
+ return
+ }
+ }
+ f.Badf(call.Pos(), "unrecognized printf verb %q", verb)
+}
+
+// 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")
+ isF := strings.HasPrefix(name, "F")
+ args := call.Args
+ // check for Println(os.Stderr, ...)
+ if skip == 0 && !isF && len(args) > 0 {
+ if sel, ok := args[0].(*ast.SelectorExpr); ok {
+ if x, ok := sel.X.(*ast.Ident); ok {
+ if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
+ f.Warnf(call.Pos(), "first argument to %s is %s.%s", name, x.Name, sel.Sel.Name)
+ }
+ }
+ }
+ }
+ 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"
+ f.Warnf(0, "%r", "hello") // ERROR "unrecognized printf verb"
+ f.Warnf(0, "%#s", "hello") // ERROR "unrecognized printf flag"
+}
+
+type BadTypeUsedInTests struct {
+ X int "hello" // ERROR "struct field tag"
+}
+
+func (t *BadTypeUsedInTests) Scan(x fmt.ScanState, c byte) { // ERROR "method Scan[(]x fmt.ScanState, c byte[)] should have signature Scan[(]fmt.ScanState, rune[)] error"
+}
+
+type BadInterfaceUsedInTests interface {
+ ReadByte() byte // ERROR "method ReadByte[(][)] byte should have signature ReadByte[(][)] [(]byte, error[)]"
+}
+
+// printf is used by the test.
+func printf(format string, args ...interface{}) {
+ panic("don't call - testing only")
+}
diff --git a/src/cmd/vet/structtag.go b/src/cmd/vet/structtag.go
new file mode 100644
index 000000000..ea2a9d863
--- /dev/null
+++ b/src/cmd/vet/structtag.go
@@ -0,0 +1,34 @@
+// 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.
+
+// This file contains the test for canonical struct tags.
+
+package main
+
+import (
+ "go/ast"
+ "reflect"
+ "strconv"
+)
+
+// checkField checks a struct field tag.
+func (f *File) checkCanonicalFieldTag(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
+ }
+}
diff --git a/src/cmd/vet/taglit.go b/src/cmd/vet/taglit.go
new file mode 100644
index 000000000..c3c4f3234
--- /dev/null
+++ b/src/cmd/vet/taglit.go
@@ -0,0 +1,121 @@
+// Copyright 2012 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.
+
+// This file contains the test for untagged struct literals.
+
+package main
+
+import (
+ "go/ast"
+ "strings"
+)
+
+// checkUntaggedLiteral checks if a composite literal is an struct literal with
+// untagged fields.
+func (f *File) checkUntaggedLiteral(c *ast.CompositeLit) {
+ // Check if the CompositeLit contains an untagged field.
+ allKeyValue := true
+ for _, e := range c.Elts {
+ if _, ok := e.(*ast.KeyValueExpr); !ok {
+ allKeyValue = false
+ break
+ }
+ }
+ if allKeyValue {
+ return
+ }
+
+ // Check that the CompositeLit's type has the form pkg.Typ.
+ s, ok := c.Type.(*ast.SelectorExpr)
+ if !ok {
+ return
+ }
+ pkg, ok := s.X.(*ast.Ident)
+ if !ok {
+ return
+ }
+
+ // Convert the package name to an import path, and compare to a whitelist.
+ path := pkgPath(f, pkg.Name)
+ if path == "" {
+ f.Warnf(c.Pos(), "unresolvable package for %s.%s literal", pkg.Name, s.Sel.Name)
+ return
+ }
+ typ := path + "." + s.Sel.Name
+ if untaggedLiteralWhitelist[typ] {
+ return
+ }
+
+ f.Warnf(c.Pos(), "%s struct literal uses untagged fields", typ)
+}
+
+// pkgPath returns the import path "image/png" for the package name "png".
+//
+// This is based purely on syntax and convention, and not on the imported
+// package's contents. It will be incorrect if a package name differs from the
+// leaf element of the import path, or if the package was a dot import.
+func pkgPath(f *File, pkgName string) (path string) {
+ for _, x := range f.file.Imports {
+ s := strings.Trim(x.Path.Value, `"`)
+ if x.Name != nil {
+ // Catch `import pkgName "foo/bar"`.
+ if x.Name.Name == pkgName {
+ return s
+ }
+ } else {
+ // Catch `import "pkgName"` or `import "foo/bar/pkgName"`.
+ if s == pkgName || strings.HasSuffix(s, "/"+pkgName) {
+ return s
+ }
+ }
+ }
+ return ""
+}
+
+var untaggedLiteralWhitelist = map[string]bool{
+ /*
+ These types are actually slices. Syntactically, we cannot tell
+ whether the Typ in pkg.Typ{1, 2, 3} is a slice or a struct, so we
+ whitelist all the standard package library's exported slice types.
+
+ find $GOROOT/src/pkg -type f | grep -v _test.go | xargs grep '^type.*\[\]' | \
+ grep -v ' map\[' | sed 's,/[^/]*go.type,,' | sed 's,.*src/pkg/,,' | \
+ sed 's, ,.,' | sed 's, .*,,' | grep -v '\.[a-z]' | \
+ sort | awk '{ print "\"" $0 "\": true," }'
+ */
+ "crypto/x509/pkix.RDNSequence": true,
+ "crypto/x509/pkix.RelativeDistinguishedNameSET": true,
+ "database/sql.RawBytes": true,
+ "debug/macho.LoadBytes": true,
+ "encoding/asn1.ObjectIdentifier": true,
+ "encoding/asn1.RawContent": true,
+ "encoding/json.RawMessage": true,
+ "encoding/xml.CharData": true,
+ "encoding/xml.Comment": true,
+ "encoding/xml.Directive": true,
+ "exp/norm.Decomposition": true,
+ "exp/types.ObjList": true,
+ "go/scanner.ErrorList": true,
+ "image/color.Palette": true,
+ "net.HardwareAddr": true,
+ "net.IP": true,
+ "net.IPMask": true,
+ "sort.Float64Slice": true,
+ "sort.IntSlice": true,
+ "sort.StringSlice": true,
+ "unicode.SpecialCase": true,
+
+ // These image and image/color struct types are frozen. We will never add fields to them.
+ "image/color.Alpha16": true,
+ "image/color.Alpha": true,
+ "image/color.Gray16": true,
+ "image/color.Gray": true,
+ "image/color.NRGBA64": true,
+ "image/color.NRGBA": true,
+ "image/color.RGBA64": true,
+ "image/color.RGBA": true,
+ "image/color.YCbCr": true,
+ "image.Point": true,
+ "image.Rectangle": true,
+}