summaryrefslogtreecommitdiff
path: root/src/cmd/govet/govet.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/govet/govet.go')
-rw-r--r--src/cmd/govet/govet.go116
1 files changed, 84 insertions, 32 deletions
diff --git a/src/cmd/govet/govet.go b/src/cmd/govet/govet.go
index ff6421de8..b811c61a2 100644
--- a/src/cmd/govet/govet.go
+++ b/src/cmd/govet/govet.go
@@ -7,7 +7,6 @@
package main
import (
- "bytes"
"flag"
"fmt"
"io"
@@ -18,6 +17,7 @@ import (
"path/filepath"
"strconv"
"strings"
+ "utf8"
)
var verbose = flag.Bool("v", false, "verbose")
@@ -63,6 +63,7 @@ func main() {
}
name = name[:colon]
}
+ name = strings.ToLower(name)
if name[len(name)-1] == 'f' {
printfList[name] = skip
} else {
@@ -205,35 +206,38 @@ func (f *File) checkCallExpr(call *ast.CallExpr) {
}
// printfList records the formatted-print functions. The value is the location
-// of the format parameter.
+// 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,
+ "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.
+// 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,
+ "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) {
+func (f *File) checkCall(call *ast.CallExpr, Name string) {
+ name := strings.ToLower(Name)
if skip, ok := printfList[name]; ok {
- f.checkPrintf(call, name, skip)
+ f.checkPrintf(call, Name, skip)
return
}
if skip, ok := printList[name]; ok {
- f.checkPrint(call, name, skip)
+ f.checkPrint(call, Name, skip)
return
}
}
@@ -256,7 +260,7 @@ func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) {
return
}
if lit.Kind == token.STRING {
- if bytes.IndexByte(lit.Value, '%') < 0 {
+ if !strings.Contains(lit.Value, "%") {
if len(call.Args) > skip+1 {
f.Badf(call.Pos(), "no formatting directive in %s call", name)
}
@@ -265,24 +269,64 @@ func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) {
}
// Hard part: check formats against args.
// Trivial but useful test: count.
- numPercent := 0
- for i := 0; i < len(lit.Value); i++ {
+ numArgs := 0
+ for i, w := 0, 0; i < len(lit.Value); i += w {
+ w = 1
if lit.Value[i] == '%' {
- if i+1 < len(lit.Value) && lit.Value[i+1] == '%' {
- // %% doesn't count.
- i++
- } else {
- numPercent++
- }
+ nbytes, nargs := parsePrintfVerb(lit.Value[i:])
+ w = nbytes
+ numArgs += nargs
}
}
expect := len(call.Args) - (skip + 1)
- if numPercent != expect {
- f.Badf(call.Pos(), "wrong number of formatting directives in %s call: %d percent(s) for %d args", name, numPercent, expect)
+ if numArgs != expect {
+ f.Badf(call.Pos(), "wrong number of args in %s call: %d needed but %d args", name, numArgs, expect)
}
}
-var terminalNewline = []byte(`\n"`) // \n at end of interpreted string
+// 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,
@@ -298,7 +342,7 @@ func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) {
}
arg := args[skip]
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
- if bytes.IndexByte(lit.Value, '%') >= 0 {
+ if strings.Contains(lit.Value, "%") {
f.Badf(call.Pos(), "possible formatting directive in %s call", name)
}
}
@@ -306,7 +350,7 @@ func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) {
// 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 bytes.HasSuffix(lit.Value, terminalNewline) {
+ if strings.HasSuffix(lit.Value, `\n"`) {
f.Badf(call.Pos(), "%s call ends with newline", name)
}
}
@@ -320,8 +364,16 @@ func BadFunctionUsedInTests() {
fmt.Println("%s", "hi") // % in call to Println
fmt.Printf("%s", "hi", 3) // wrong # percents
fmt.Printf("%s%%%d", "hi", 3) // right # percents
+ fmt.Printf("%.*d", 3, 3) // right # percents, with a *
+ fmt.Printf("%.*d", 3, 3, 3) // wrong # percents, with a *
+ printf("now is the time", "buddy") // no %s
Printf("now is the time", "buddy") // no %s
f := new(File)
f.Warn(0, "%s", "hello", 3) // % in call to added function
f.Warnf(0, "%s", "hello", 3) // wrong # %s in call to added function
}
+
+// printf is used by the test.
+func printf(format string, args ...interface{}) {
+ panic("don't call - testing only")
+}