Skip to content

Commit 558eeb2

Browse files
committed
cmd/vet: limit printf check to known Printf-like functions
The name-based heuristics fail too often to be on during "go test", but we really want the printf vet check in "go test", so change to a list of exactly which standard library functions are print-like. For a later release we'd like to bring back checking for user-defined wrappers, but in a completely precise way. Not for Go 1.10, though. The new, more precise list includes t.Skipf, which caught some mistakes in standard library tests. Fixes #22936. Change-Id: I110448e3f6b75afd4327cf87b6abb4cc2021fd0d Reviewed-on: https://go-review.googlesource.com/83838 Run-TryBot: Russ Cox <[email protected]> Reviewed-by: Rob Pike <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 9006d1f commit 558eeb2

File tree

4 files changed

+195
-100
lines changed

4 files changed

+195
-100
lines changed

src/cmd/vet/main.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ var (
3434
tags = flag.String("tags", "", "space-separated list of build tags to apply when parsing")
3535
tagList = []string{} // exploded version of tags flag; set in main
3636

37+
vcfg vetConfig
3738
mustTypecheck bool
38-
39-
succeedOnTypecheckFailure bool // during go test, we ignore potential bugs in go/types
4039
)
4140

4241
var exitCode = 0
@@ -289,6 +288,7 @@ func prefixDirectory(directory string, names []string) {
289288
type vetConfig struct {
290289
Compiler string
291290
Dir string
291+
ImportPath string
292292
GoFiles []string
293293
ImportMap map[string]string
294294
PackageFile map[string]string
@@ -336,11 +336,9 @@ func doPackageCfg(cfgFile string) {
336336
if err != nil {
337337
errorf("%v", err)
338338
}
339-
var vcfg vetConfig
340339
if err := json.Unmarshal(js, &vcfg); err != nil {
341340
errorf("parsing vet config %s: %v", cfgFile, err)
342341
}
343-
succeedOnTypecheckFailure = vcfg.SucceedOnTypecheckFailure
344342
stdImporter = &vcfg
345343
inittypes()
346344
mustTypecheck = true
@@ -432,7 +430,7 @@ func doPackage(names []string, basePkg *Package) *Package {
432430
// Type check the package.
433431
errs := pkg.check(fs, astFiles)
434432
if errs != nil {
435-
if succeedOnTypecheckFailure {
433+
if vcfg.SucceedOnTypecheckFailure {
436434
os.Exit(0)
437435
}
438436
if *verbose || mustTypecheck {

src/cmd/vet/print.go

+122-42
Original file line numberDiff line numberDiff line change
@@ -44,41 +44,73 @@ func initPrintFlags() {
4444
name = name[:colon]
4545
}
4646

47-
name = strings.ToLower(name)
48-
if name[len(name)-1] == 'f' {
49-
isFormattedPrint[name] = true
50-
} else {
51-
isPrint[name] = true
52-
}
47+
isPrint[strings.ToLower(name)] = true
5348
}
5449
}
5550

56-
// isFormattedPrint records the formatted-print functions. Names are
57-
// lower-cased so the lookup is case insensitive.
58-
var isFormattedPrint = map[string]bool{
59-
"errorf": true,
60-
"fatalf": true,
61-
"fprintf": true,
62-
"logf": true,
63-
"panicf": true,
64-
"printf": true,
65-
"sprintf": true,
66-
}
67-
68-
// isPrint records the unformatted-print functions. Names are lower-cased
69-
// so the lookup is case insensitive.
51+
// TODO(rsc): Incorporate user-defined printf wrappers again.
52+
// The general plan is to allow vet of one package P to output
53+
// additional information to supply to later vets of packages
54+
// importing P. Then vet of P can record a list of printf wrappers
55+
// and the later vet using P.Printf will find it in the list and check it.
56+
// That's not ready for Go 1.10.
57+
// When that does happen, uncomment the user-defined printf
58+
// wrapper tests in testdata/print.go.
59+
60+
// isPrint records the print functions.
61+
// If a key ends in 'f' then it is assumed to be a formatted print.
7062
var isPrint = map[string]bool{
71-
"error": true,
72-
"fatal": true,
73-
"fprint": true,
74-
"fprintln": true,
75-
"log": true,
76-
"panic": true,
77-
"panicln": true,
78-
"print": true,
79-
"println": true,
80-
"sprint": true,
81-
"sprintln": true,
63+
"fmt.Errorf": true,
64+
"fmt.Fprint": true,
65+
"fmt.Fprintf": true,
66+
"fmt.Fprintln": true,
67+
"fmt.Print": true,
68+
"fmt.Printf": true,
69+
"fmt.Println": true,
70+
"fmt.Sprint": true,
71+
"fmt.Sprintf": true,
72+
"fmt.Sprintln": true,
73+
"log.Fatal": true,
74+
"log.Fatalf": true,
75+
"log.Fatalln": true,
76+
"log.Logger.Fatal": true,
77+
"log.Logger.Fatalf": true,
78+
"log.Logger.Fatalln": true,
79+
"log.Logger.Panic": true,
80+
"log.Logger.Panicf": true,
81+
"log.Logger.Panicln": true,
82+
"log.Logger.Printf": true,
83+
"log.Logger.Println": true,
84+
"log.Panic": true,
85+
"log.Panicf": true,
86+
"log.Panicln": true,
87+
"log.Print": true,
88+
"log.Printf": true,
89+
"log.Println": true,
90+
"testing.B.Error": true,
91+
"testing.B.Errorf": true,
92+
"testing.B.Fatal": true,
93+
"testing.B.Fatalf": true,
94+
"testing.B.Log": true,
95+
"testing.B.Logf": true,
96+
"testing.B.Skip": true,
97+
"testing.B.Skipf": true,
98+
"testing.T.Error": true,
99+
"testing.T.Errorf": true,
100+
"testing.T.Fatal": true,
101+
"testing.T.Fatalf": true,
102+
"testing.T.Log": true,
103+
"testing.T.Logf": true,
104+
"testing.T.Skip": true,
105+
"testing.T.Skipf": true,
106+
"testing.TB.Error": true,
107+
"testing.TB.Errorf": true,
108+
"testing.TB.Fatal": true,
109+
"testing.TB.Fatalf": true,
110+
"testing.TB.Log": true,
111+
"testing.TB.Logf": true,
112+
"testing.TB.Skip": true,
113+
"testing.TB.Skipf": true,
82114
}
83115

84116
// formatString returns the format string argument and its index within
@@ -148,6 +180,11 @@ func stringConstantArg(f *File, call *ast.CallExpr, idx int) (string, bool) {
148180

149181
// checkCall triggers the print-specific checks if the call invokes a print function.
150182
func checkFmtPrintfCall(f *File, node ast.Node) {
183+
if f.pkg.typesPkg == nil {
184+
// This check now requires type information.
185+
return
186+
}
187+
151188
if d, ok := node.(*ast.FuncDecl); ok && isStringer(f, d) {
152189
// Remember we saw this.
153190
if f.stringers == nil {
@@ -165,24 +202,67 @@ func checkFmtPrintfCall(f *File, node ast.Node) {
165202
if !ok {
166203
return
167204
}
168-
var Name string
205+
206+
// Construct name like pkg.Printf or pkg.Type.Printf for lookup.
207+
var name string
169208
switch x := call.Fun.(type) {
170209
case *ast.Ident:
171-
Name = x.Name
210+
if fn, ok := f.pkg.uses[x].(*types.Func); ok {
211+
var pkg string
212+
if fn.Pkg() == nil || fn.Pkg() == f.pkg.typesPkg {
213+
pkg = vcfg.ImportPath
214+
} else {
215+
pkg = fn.Pkg().Path()
216+
}
217+
name = pkg + "." + x.Name
218+
break
219+
}
220+
172221
case *ast.SelectorExpr:
173-
Name = x.Sel.Name
174-
default:
222+
// Check for "fmt.Printf".
223+
if id, ok := x.X.(*ast.Ident); ok {
224+
if pkgName, ok := f.pkg.uses[id].(*types.PkgName); ok {
225+
name = pkgName.Imported().Path() + "." + x.Sel.Name
226+
break
227+
}
228+
}
229+
230+
// Check for t.Logf where t is a *testing.T.
231+
if sel := f.pkg.selectors[x]; sel != nil {
232+
recv := sel.Recv()
233+
if p, ok := recv.(*types.Pointer); ok {
234+
recv = p.Elem()
235+
}
236+
if named, ok := recv.(*types.Named); ok {
237+
obj := named.Obj()
238+
var pkg string
239+
if obj.Pkg() == nil || obj.Pkg() == f.pkg.typesPkg {
240+
pkg = vcfg.ImportPath
241+
} else {
242+
pkg = obj.Pkg().Path()
243+
}
244+
name = pkg + "." + obj.Name() + "." + x.Sel.Name
245+
break
246+
}
247+
}
248+
}
249+
if name == "" {
175250
return
176251
}
177252

178-
name := strings.ToLower(Name)
179-
if _, ok := isFormattedPrint[name]; ok {
180-
f.checkPrintf(call, Name)
181-
return
253+
shortName := name[strings.LastIndex(name, ".")+1:]
254+
255+
_, ok = isPrint[name]
256+
if !ok {
257+
// Next look up just "printf", for use with -printfuncs.
258+
_, ok = isPrint[strings.ToLower(shortName)]
182259
}
183-
if _, ok := isPrint[name]; ok {
184-
f.checkPrint(call, Name)
185-
return
260+
if ok {
261+
if strings.HasSuffix(name, "f") {
262+
f.checkPrintf(call, shortName)
263+
} else {
264+
f.checkPrint(call, shortName)
265+
}
186266
}
187267
}
188268

0 commit comments

Comments
 (0)