Skip to content

Commit 197eb8f

Browse files
committed
govet: add checking for printf verbs
Also fix the errors it catches. Fixes #1654. R=rsc CC=golang-dev https://golang.org/cl/5489060
1 parent cb7d6e3 commit 197eb8f

File tree

6 files changed

+73
-8
lines changed

6 files changed

+73
-8
lines changed

src/cmd/govet/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ GOFILES=\
1414
include ../../Make.cmd
1515

1616
test testshort: $(TARG)
17-
../../../test/errchk $(TARG) -printfuncs='Warn:1,Warnf:1' govet.go
17+
../../../test/errchk $(TARG) -printfuncs='Warn:1,Warnf:1' print.go

src/cmd/govet/print.go

+68-3
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) {
6767
if !ok {
6868
// Too hard to check.
6969
if *verbose {
70-
f.Warn(call.Pos(), "can't check args for call to", name)
70+
f.Warn(call.Pos(), "can't check non-literal format in call to", name)
7171
}
7272
return
7373
}
@@ -85,7 +85,7 @@ func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) {
8585
for i, w := 0, 0; i < len(lit.Value); i += w {
8686
w = 1
8787
if lit.Value[i] == '%' {
88-
nbytes, nargs := parsePrintfVerb(lit.Value[i:])
88+
nbytes, nargs := f.parsePrintfVerb(call, lit.Value[i:])
8989
w = nbytes
9090
numArgs += nargs
9191
}
@@ -99,15 +99,17 @@ func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) {
9999
// parsePrintfVerb returns the number of bytes and number of arguments
100100
// consumed by the Printf directive that begins s, including its percent sign
101101
// and verb.
102-
func parsePrintfVerb(s string) (nbytes, nargs int) {
102+
func (f *File) parsePrintfVerb(call *ast.CallExpr, s string) (nbytes, nargs int) {
103103
// There's guaranteed a percent sign.
104+
flags := make([]byte, 0, 5)
104105
nbytes = 1
105106
end := len(s)
106107
// There may be flags.
107108
FlagLoop:
108109
for nbytes < end {
109110
switch s[nbytes] {
110111
case '#', '0', '+', '-', ' ':
112+
flags = append(flags, s[nbytes])
111113
nbytes++
112114
default:
113115
break FlagLoop
@@ -127,6 +129,7 @@ FlagLoop:
127129
getNum()
128130
// If there's a period, there may be a precision.
129131
if nbytes < end && s[nbytes] == '.' {
132+
flags = append(flags, '.') // Treat precision as a flag.
130133
nbytes++
131134
getNum()
132135
}
@@ -135,10 +138,70 @@ FlagLoop:
135138
nbytes += w
136139
if c != '%' {
137140
nargs++
141+
f.checkPrintfVerb(call, c, flags)
138142
}
139143
return
140144
}
141145

146+
type printVerb struct {
147+
verb rune
148+
flags string // known flags are all ASCII
149+
}
150+
151+
// Common flag sets for printf verbs.
152+
const (
153+
numFlag = " -+.0"
154+
sharpNumFlag = " -+.0#"
155+
allFlags = " -+.0#"
156+
)
157+
158+
// printVerbs identifies which flags are known to printf for each verb.
159+
// TODO: A type that implements Formatter may do what it wants, and govet
160+
// will complain incorrectly.
161+
var printVerbs = []printVerb{
162+
// '-' is a width modifier, always valid.
163+
// '.' is a precision for float, max width for strings.
164+
// '+' is required sign for numbers, Go format for %v.
165+
// '#' is alternate format for several verbs.
166+
// ' ' is spacer for numbers
167+
{'b', numFlag},
168+
{'c', "-"},
169+
{'d', numFlag},
170+
{'e', "-."},
171+
{'E', numFlag},
172+
{'f', numFlag},
173+
{'F', numFlag},
174+
{'g', numFlag},
175+
{'G', numFlag},
176+
{'o', sharpNumFlag},
177+
{'p', "-#"},
178+
{'q', "-+#."},
179+
{'s', "-."},
180+
{'t', "-"},
181+
{'T', "-"},
182+
{'U', "-#"},
183+
{'v', allFlags},
184+
{'x', sharpNumFlag},
185+
{'X', sharpNumFlag},
186+
}
187+
188+
const printfVerbs = "bcdeEfFgGopqstTvxUX"
189+
190+
func (f *File) checkPrintfVerb(call *ast.CallExpr, verb rune, flags []byte) {
191+
// Linear scan is fast enough for a small list.
192+
for _, v := range printVerbs {
193+
if v.verb == verb {
194+
for _, flag := range flags {
195+
if !strings.ContainsRune(v.flags, rune(flag)) {
196+
f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", verb, flag)
197+
}
198+
}
199+
return
200+
}
201+
}
202+
f.Badf(call.Pos(), "unrecognized printf verb %q", verb)
203+
}
204+
142205
// checkPrint checks a call to an unformatted print routine such as Println.
143206
// The skip argument records how many arguments to ignore; that is,
144207
// call.Args[skip] is the first argument to be printed.
@@ -183,6 +246,8 @@ func BadFunctionUsedInTests() {
183246
f := new(File)
184247
f.Warn(0, "%s", "hello", 3) // ERROR "possible formatting directive in Warn call"
185248
f.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args in Warnf call"
249+
f.Warnf(0, "%r", "hello") // ERROR "unrecognized printf verb"
250+
f.Warnf(0, "%#s", "hello") // ERROR "unrecognized printf flag"
186251
}
187252

188253
type BadTypeUsedInTests struct {

src/pkg/encoding/xml/marshal_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ func TestUnmarshal(t *testing.T) {
394394
if err != nil {
395395
t.Errorf("#%d: unexpected error: %#v", i, err)
396396
} else if got, want := dest, test.Value; !reflect.DeepEqual(got, want) {
397-
t.Errorf("#%d: unmarshal(%#s) = %#v, want %#v", i, test.ExpectXML, got, want)
397+
t.Errorf("#%d: unmarshal(%q) = %#v, want %#v", i, test.ExpectXML, got, want)
398398
}
399399
}
400400
}

src/pkg/net/http/readrequest_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func TestReadRequest(t *testing.T) {
219219
t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
220220
}
221221
if !reflect.DeepEqual(tt.Trailer, req.Trailer) {
222-
t.Errorf("%#d. Trailers differ.\n got: %v\nwant: %v", i, req.Trailer, tt.Trailer)
222+
t.Errorf("#%d. Trailers differ.\n got: %v\nwant: %v", i, req.Trailer, tt.Trailer)
223223
}
224224
}
225225
}

src/pkg/net/textproto/reader_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ func TestRFC959Lines(t *testing.T) {
203203
t.Errorf("#%d: code=%d, want %d", i, code, tt.wantCode)
204204
}
205205
if msg != tt.wantMsg {
206-
t.Errorf("%#d: msg=%q, want %q", i, msg, tt.wantMsg)
206+
t.Errorf("#%d: msg=%q, want %q", i, msg, tt.wantMsg)
207207
}
208208
}
209209
}

src/pkg/os/os_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -919,7 +919,7 @@ func TestReadAt(t *testing.T) {
919919
b := make([]byte, 5)
920920
n, err := f.ReadAt(b, 7)
921921
if err != nil || n != len(b) {
922-
t.Fatalf("ReadAt 7: %d, %r", n, err)
922+
t.Fatalf("ReadAt 7: %d, %v", n, err)
923923
}
924924
if string(b) != "world" {
925925
t.Fatalf("ReadAt 7: have %q want %q", string(b), "world")

0 commit comments

Comments
 (0)