Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fc768da

Browse files
committed
cmd/vet: tighten printf format error messages
Every time I see an error that begins `missing argument for Fprintf("%s")` my mental type-checker goes off, since obviously "%s" is not a valid first argument to Fprintf. Writing Printf("%s") to report an error in Printf("hello %s") is almost as confusing. This CL rewords the errors reported by vet's printf check to be more consistent with each other, avoid placing context like "in printf call" in the middle of the message, and to avoid the imprecisions above by not quoting the format string at all. Before: bad.go:9: no formatting directive in Printf call bad.go:10: missing argument for Printf("%s"): format reads arg 1, have only 0 args bad.go:11: wrong number of args for format in Printf call: 1 needed but 2 args bad.go:12: bad syntax for printf argument index: [1] bad.go:13: index value [0] for Printf("%[0]s"); indexes start at 1 bad.go:14: missing argument for Printf("%[2]s"): format reads arg 2, have only 1 args bad.go:15: bad syntax for printf argument index: [abc] bad.go:16: unrecognized printf verb 'z' bad.go:17: arg "hello" for * in printf format not of type int bad.go:18: arg fmt.Sprint in printf call is a function value, not a function call bad.go:19: arg fmt.Sprint in Print call is a function value, not a function call bad.go:20: arg "world" for printf verb %d of wrong type: string bad.go:21: missing argument for Printf("%q"): format reads arg 2, have only 1 args bad.go:22: first argument to Print is os.Stderr bad.go:23: Println call ends with newline bad.go:32: arg r in Sprint call causes recursive call to String method bad.go:34: arg r for printf causes recursive call to String method After: bad.go:9: Printf call has arguments but no formatting directives bad.go:10: Printf format %s reads arg #1, but have only 0 args bad.go:11: Printf call needs 1 args but has 2 args bad.go:12: Printf format %[1 is missing closing ] bad.go:13: Printf format has invalid argument index [0] bad.go:14: Printf format has invalid argument index [2] bad.go:15: Printf format has invalid argument index [abc] bad.go:16: Printf format %.234z has unknown verb z bad.go:17: Printf format %.*s uses non-int "hello" as argument of * bad.go:18: Printf format %s arg fmt.Sprint is a func value, not called bad.go:19: Print arg fmt.Sprint is a func value, not called bad.go:20: Printf format %d has arg "world" of wrong type string bad.go:21: Printf format %q reads arg #2, but have only 1 args bad.go:22: Print does not take io.Writer but has first arg os.Stderr bad.go:23: Println args end with redundant newline bad.go:32: Sprint arg r causes recursive call to String method bad.go:34: Sprintf format %s with arg r causes recursive String method call Change-Id: I5719f0fb9f2cd84df8ad4c7754ab9b79c691b060 Reviewed-on: https://go-review.googlesource.com/74352 Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rob Pike <[email protected]>
1 parent 9aa6f80 commit fc768da

File tree

6 files changed

+173
-140
lines changed

6 files changed

+173
-140
lines changed

src/cmd/dist/test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ func (t *tester) registerTests() {
343343
osarch := k
344344
t.tests = append(t.tests, distTest{
345345
name: "vet/" + osarch,
346-
heading: "go vet std cmd",
346+
heading: "cmd/vet/all",
347347
fn: func(dt *distTest) error {
348348
t.addCmd(dt, "src/cmd/vet/all", "go", "run", "main.go", "-p="+osarch)
349349
return nil

src/cmd/go/go_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2909,7 +2909,7 @@ func TestGoVetWithExternalTests(t *testing.T) {
29092909
tg.run("install", "cmd/vet")
29102910
tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata"))
29112911
tg.runFail("vet", "vetpkg")
2912-
tg.grepBoth("missing argument for Printf", "go vet vetpkg did not find missing argument for Printf")
2912+
tg.grepBoth("Printf", "go vet vetpkg did not find missing argument for Printf")
29132913
}
29142914

29152915
func TestGoVetWithTags(t *testing.T) {
@@ -2919,7 +2919,7 @@ func TestGoVetWithTags(t *testing.T) {
29192919
tg.run("install", "cmd/vet")
29202920
tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata"))
29212921
tg.runFail("vet", "-tags", "tagtest", "vetpkg")
2922-
tg.grepBoth(`c\.go.*wrong number of args for format`, "go vet vetpkg did not run scan tagged file")
2922+
tg.grepBoth(`c\.go.*Printf`, "go vet vetpkg did not run scan tagged file")
29232923
}
29242924

29252925
func TestGoVetWithFlagsOn(t *testing.T) {
@@ -2929,7 +2929,7 @@ func TestGoVetWithFlagsOn(t *testing.T) {
29292929
tg.run("install", "cmd/vet")
29302930
tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata"))
29312931
tg.runFail("vet", "-printf", "vetpkg")
2932-
tg.grepBoth("missing argument for Printf", "go vet -printf vetpkg did not find missing argument for Printf")
2932+
tg.grepBoth("Printf", "go vet -printf vetpkg did not find missing argument for Printf")
29332933
}
29342934

29352935
func TestGoVetWithFlagsOff(t *testing.T) {

src/cmd/vet/all/whitelist/windows.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,5 @@
33
// Issue 18609
44
crypto/x509/root_windows.go: unreachable code
55

6-
path/filepath/path_windows_test.go: possible formatting directive in Fatal call
7-
86
runtime/sys_windows_ARCHSUFF.s: [GOARCH] sigtramp: function sigtramp missing Go declaration
97
runtime/sys_windows_ARCHSUFF.s: [GOARCH] onosstack: unknown variable usec; offset 0 is fn+0(FP)

src/cmd/vet/print.go

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ package main
99
import (
1010
"bytes"
1111
"flag"
12+
"fmt"
1213
"go/ast"
1314
"go/constant"
1415
"go/token"
1516
"go/types"
17+
"regexp"
1618
"strconv"
1719
"strings"
1820
"unicode/utf8"
@@ -88,8 +90,8 @@ var isPrint = map[string]bool{
8890
// The first string literal or string constant is assumed to be a format string
8991
// if the call's signature cannot be determined.
9092
//
91-
// If it cannot find any format string parameter, it returns ("", -1).
92-
func formatString(f *File, call *ast.CallExpr) (string, int) {
93+
// If it cannot find any format string parameter, it returns ("", -1).
94+
func formatString(f *File, call *ast.CallExpr) (format string, idx int) {
9395
typ := f.pkg.types[call.Fun].Type
9496
if typ != nil {
9597
if sig, ok := typ.(*types.Signature); ok {
@@ -228,7 +230,7 @@ func (f *File) checkPrintf(call *ast.CallExpr, name string) {
228230
firstArg := idx + 1 // Arguments are immediately after format string.
229231
if !strings.Contains(format, "%") {
230232
if len(call.Args) > firstArg {
231-
f.Badf(call.Pos(), "no formatting directive in %s call", name)
233+
f.Badf(call.Pos(), "%s call has arguments but no formatting directives", name)
232234
}
233235
return
234236
}
@@ -266,7 +268,7 @@ func (f *File) checkPrintf(call *ast.CallExpr, name string) {
266268
if maxArgNum != len(call.Args) {
267269
expect := maxArgNum - firstArg
268270
numArgs := len(call.Args) - firstArg
269-
f.Badf(call.Pos(), "wrong number of args for format in %s call: %d needed but %d args", name, expect, numArgs)
271+
f.Badf(call.Pos(), "%s call needs %v but has %v", name, count(expect, "arg"), count(numArgs, "arg"))
270272
}
271273
}
272274

@@ -302,17 +304,18 @@ func (s *formatState) parseIndex() bool {
302304
s.nbytes++ // skip '['
303305
start := s.nbytes
304306
s.scanNum()
307+
ok := true
305308
if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' {
306-
end := strings.Index(s.format, "]")
307-
if end < 0 {
308-
end = len(s.format)
309+
ok = false
310+
s.nbytes = strings.Index(s.format, "]")
311+
if s.nbytes < 0 {
312+
s.file.Badf(s.call.Pos(), "%s format %s is missing closing ]", s.name, s.format)
313+
return false
309314
}
310-
s.file.Badf(s.call.Pos(), "bad syntax for printf argument index: [%s]", s.format[start:end])
311-
return false
312315
}
313316
arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32)
314-
if err != nil {
315-
s.file.Badf(s.call.Pos(), "bad syntax for printf argument index: %s", err)
317+
if err != nil || !ok || arg32 <= 0 || arg32 > int64(len(s.call.Args)-s.firstArg) {
318+
s.file.Badf(s.call.Pos(), "%s format has invalid argument index [%s]", s.name, s.format[start:s.nbytes])
316319
return false
317320
}
318321
s.nbytes++ // skip ']'
@@ -388,7 +391,7 @@ func (f *File) parsePrintfVerb(call *ast.CallExpr, name, format string, firstArg
388391
return nil
389392
}
390393
if state.nbytes == len(state.format) {
391-
f.Badf(call.Pos(), "missing verb at end of format string in %s call", name)
394+
f.Badf(call.Pos(), "%s format %s is missing verb at end of string", name, state.format)
392395
return nil
393396
}
394397
verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:])
@@ -481,12 +484,12 @@ func (f *File) okPrintfArg(call *ast.CallExpr, state *formatState) (ok bool) {
481484
}
482485

483486
if !found && !formatter {
484-
f.Badf(call.Pos(), "unrecognized printf verb %q", state.verb)
487+
f.Badf(call.Pos(), "%s format %s has unknown verb %c", state.name, state.format, state.verb)
485488
return false
486489
}
487490
for _, flag := range state.flags {
488491
if !strings.ContainsRune(v.flags, rune(flag)) {
489-
f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", state.verb, flag)
492+
f.Badf(call.Pos(), "%s format %s has unrecognized flag %c", state.name, state.format, flag)
490493
return false
491494
}
492495
}
@@ -504,7 +507,7 @@ func (f *File) okPrintfArg(call *ast.CallExpr, state *formatState) (ok bool) {
504507
}
505508
arg := call.Args[argNum]
506509
if !f.matchArgType(argInt, nil, arg) {
507-
f.Badf(call.Pos(), "arg %s for * in printf format not of type int", f.gofmt(arg))
510+
f.Badf(call.Pos(), "%s format %s uses non-int %s as argument of *", state.name, state.format, f.gofmt(arg))
508511
return false
509512
}
510513
}
@@ -517,19 +520,19 @@ func (f *File) okPrintfArg(call *ast.CallExpr, state *formatState) (ok bool) {
517520
}
518521
arg := call.Args[argNum]
519522
if f.isFunctionValue(arg) && state.verb != 'p' && state.verb != 'T' {
520-
f.Badf(call.Pos(), "arg %s in printf call is a function value, not a function call", f.gofmt(arg))
523+
f.Badf(call.Pos(), "%s format %s arg %s is a func value, not called", state.name, state.format, f.gofmt(arg))
521524
return false
522525
}
523526
if !f.matchArgType(v.typ, nil, arg) {
524527
typeString := ""
525528
if typ := f.pkg.types[arg].Type; typ != nil {
526529
typeString = typ.String()
527530
}
528-
f.Badf(call.Pos(), "arg %s for printf verb %%%c of wrong type: %s", f.gofmt(arg), state.verb, typeString)
531+
f.Badf(call.Pos(), "%s format %s has arg %s of wrong type %s", state.name, state.format, f.gofmt(arg), typeString)
529532
return false
530533
}
531534
if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) && f.recursiveStringer(arg) {
532-
f.Badf(call.Pos(), "arg %s for printf causes recursive call to String method", f.gofmt(arg))
535+
f.Badf(call.Pos(), "%s format %s with arg %s causes recursive String method call", state.name, state.format, f.gofmt(arg))
533536
return false
534537
}
535538
return true
@@ -580,14 +583,10 @@ func (f *File) isFunctionValue(e ast.Expr) bool {
580583
// means we can't see it.
581584
func (f *File) argCanBeChecked(call *ast.CallExpr, formatArg int, state *formatState) bool {
582585
argNum := state.argNums[formatArg]
583-
if argNum < 0 {
586+
if argNum <= 0 {
584587
// Shouldn't happen, so catch it with prejudice.
585588
panic("negative arg num")
586589
}
587-
if argNum == 0 {
588-
f.Badf(call.Pos(), `index value [0] for %s("%s"); indexes start at 1`, state.name, state.format)
589-
return false
590-
}
591590
if argNum < len(call.Args)-1 {
592591
return true // Always OK.
593592
}
@@ -600,10 +599,22 @@ func (f *File) argCanBeChecked(call *ast.CallExpr, formatArg int, state *formatS
600599
// There are bad indexes in the format or there are fewer arguments than the format needs.
601600
// This is the argument number relative to the format: Printf("%s", "hi") will give 1 for the "hi".
602601
arg := argNum - state.firstArg + 1 // People think of arguments as 1-indexed.
603-
f.Badf(call.Pos(), `missing argument for %s("%s"): format reads arg %d, have only %d args`, state.name, state.format, arg, len(call.Args)-state.firstArg)
602+
f.Badf(call.Pos(), "%s format %s reads arg #%d, but call has only %v", state.name, state.format, arg, count(len(call.Args)-state.firstArg, "arg"))
604603
return false
605604
}
606605

606+
// printFormatRE is the regexp we match and report as a possible format string
607+
// in the first argument to unformatted prints like fmt.Print.
608+
// We exclude the space flag, so that printing a string like "x % y" is not reported as a format.
609+
var printFormatRE = regexp.MustCompile(`%` + flagsRE + numOptRE + `\.?` + numOptRE + indexOptRE + verbRE)
610+
611+
const (
612+
flagsRE = `[+\-#]*`
613+
indexOptRE = `(\[[0-9]+\])?`
614+
numOptRE = `([0-9]+|` + indexOptRE + `\*)?`
615+
verbRE = `[bcdefgopqstxEFGUX]`
616+
)
617+
607618
// checkPrint checks a call to an unformatted print routine such as Println.
608619
func (f *File) checkPrint(call *ast.CallExpr, name string) {
609620
firstArg := 0
@@ -635,40 +646,52 @@ func (f *File) checkPrint(call *ast.CallExpr, name string) {
635646
}
636647
args = args[firstArg:]
637648

638-
// check for Println(os.Stderr, ...)
639649
if firstArg == 0 {
640-
if sel, ok := args[0].(*ast.SelectorExpr); ok {
650+
if sel, ok := call.Args[0].(*ast.SelectorExpr); ok {
641651
if x, ok := sel.X.(*ast.Ident); ok {
642652
if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
643-
f.Badf(call.Pos(), "first argument to %s is %s.%s", name, x.Name, sel.Sel.Name)
653+
f.Badf(call.Pos(), "%s does not take io.Writer but has first arg %s", name, f.gofmt(call.Args[0]))
644654
}
645655
}
646656
}
647657
}
658+
648659
arg := args[0]
649660
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
650661
// Ignore trailing % character in lit.Value.
651662
// The % in "abc 0.0%" couldn't be a formatting directive.
652663
s := strings.TrimSuffix(lit.Value, `%"`)
653664
if strings.Contains(s, "%") {
654-
f.Badf(call.Pos(), "possible formatting directive in %s call", name)
665+
m := printFormatRE.FindStringSubmatch(s)
666+
if m != nil {
667+
f.Badf(call.Pos(), "%s call has possible formatting directive %s", name, m[0])
668+
}
655669
}
656670
}
657671
if strings.HasSuffix(name, "ln") {
658672
// The last item, if a string, should not have a newline.
659673
arg = args[len(args)-1]
660674
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
661675
if strings.HasSuffix(lit.Value, `\n"`) {
662-
f.Badf(call.Pos(), "%s call ends with newline", name)
676+
f.Badf(call.Pos(), "%s args end with redundant newline", name)
663677
}
664678
}
665679
}
666680
for _, arg := range args {
667681
if f.isFunctionValue(arg) {
668-
f.Badf(call.Pos(), "arg %s in %s call is a function value, not a function call", f.gofmt(arg), name)
682+
f.Badf(call.Pos(), "%s arg %s is a func value, not called", name, f.gofmt(arg))
669683
}
670684
if f.recursiveStringer(arg) {
671-
f.Badf(call.Pos(), "arg %s in %s call causes recursive call to String method", f.gofmt(arg), name)
685+
f.Badf(call.Pos(), "%s arg %s causes recursive call to String method", name, f.gofmt(arg))
672686
}
673687
}
674688
}
689+
690+
// count(n, what) returns "1 what" or "N whats"
691+
// (assuming the plural of what is whats).
692+
func count(n int, what string) string {
693+
if n == 1 {
694+
return "1 " + what
695+
}
696+
return fmt.Sprintf("%d %ss", n, what)
697+
}

0 commit comments

Comments
 (0)