Skip to content

Commit 9f450b0

Browse files
findleyrgopherbot
authored andcommitted
go/analysis/passes/printf: suppress errors for non-const format strings
The new check added in golang/go#60529 reports errors for non-constant format strings with no arguments. These are almost always bugs, but are often mild or inconsequential, and can be numerous in existing code bases. To reduce friction from this change, gate the new check on the implied language version. For golang/go#71485 Change-Id: I4926da2809dd14ba70ae530cd1657119f5377ad5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/645595 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Robert Findley <rfindley@google.com>
1 parent e426616 commit 9f450b0

File tree

7 files changed

+195
-56
lines changed

7 files changed

+195
-56
lines changed

go/analysis/passes/printf/printf.go

+34-14
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"golang.org/x/tools/internal/analysisinternal"
2525
"golang.org/x/tools/internal/fmtstr"
2626
"golang.org/x/tools/internal/typeparams"
27+
"golang.org/x/tools/internal/versions"
2728
)
2829

2930
func init() {
@@ -107,12 +108,12 @@ func (f *isWrapper) String() string {
107108
}
108109
}
109110

110-
func run(pass *analysis.Pass) (interface{}, error) {
111+
func run(pass *analysis.Pass) (any, error) {
111112
res := &Result{
112113
funcs: make(map[*types.Func]Kind),
113114
}
114115
findPrintfLike(pass, res)
115-
checkCall(pass)
116+
checkCalls(pass)
116117
return res, nil
117118
}
118119

@@ -181,7 +182,7 @@ func maybePrintfWrapper(info *types.Info, decl ast.Decl) *printfWrapper {
181182
}
182183

183184
// findPrintfLike scans the entire package to find printf-like functions.
184-
func findPrintfLike(pass *analysis.Pass, res *Result) (interface{}, error) {
185+
func findPrintfLike(pass *analysis.Pass, res *Result) (any, error) {
185186
// Gather potential wrappers and call graph between them.
186187
byObj := make(map[*types.Func]*printfWrapper)
187188
var wrappers []*printfWrapper
@@ -408,20 +409,29 @@ func stringConstantExpr(pass *analysis.Pass, expr ast.Expr) (string, bool) {
408409
return "", false
409410
}
410411

411-
// checkCall triggers the print-specific checks if the call invokes a print function.
412-
func checkCall(pass *analysis.Pass) {
412+
// checkCalls triggers the print-specific checks for calls that invoke a print
413+
// function.
414+
func checkCalls(pass *analysis.Pass) {
413415
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
414416
nodeFilter := []ast.Node{
417+
(*ast.File)(nil),
415418
(*ast.CallExpr)(nil),
416419
}
420+
421+
var fileVersion string // for selectively suppressing checks; "" if unknown.
417422
inspect.Preorder(nodeFilter, func(n ast.Node) {
418-
call := n.(*ast.CallExpr)
419-
fn, kind := printfNameAndKind(pass, call)
420-
switch kind {
421-
case KindPrintf, KindErrorf:
422-
checkPrintf(pass, kind, call, fn.FullName())
423-
case KindPrint:
424-
checkPrint(pass, call, fn.FullName())
423+
switch n := n.(type) {
424+
case *ast.File:
425+
fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
426+
427+
case *ast.CallExpr:
428+
fn, kind := printfNameAndKind(pass, n)
429+
switch kind {
430+
case KindPrintf, KindErrorf:
431+
checkPrintf(pass, fileVersion, kind, n, fn.FullName())
432+
case KindPrint:
433+
checkPrint(pass, n, fn.FullName())
434+
}
425435
}
426436
})
427437
}
@@ -484,7 +494,7 @@ func isFormatter(typ types.Type) bool {
484494
}
485495

486496
// checkPrintf checks a call to a formatted print routine such as Printf.
487-
func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, name string) {
497+
func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.CallExpr, name string) {
488498
idx := formatStringIndex(pass, call)
489499
if idx < 0 || idx >= len(call.Args) {
490500
return
@@ -498,7 +508,17 @@ func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, name string
498508
// non-constant format string and no arguments:
499509
// if msg contains "%", misformatting occurs.
500510
// Report the problem and suggest a fix: fmt.Printf("%s", msg).
501-
if !suppressNonconstants && idx == len(call.Args)-1 {
511+
//
512+
// However, as described in golang/go#71485, this analysis can produce a
513+
// significant number of diagnostics in existing code, and the bugs it
514+
// finds are sometimes unlikely or inconsequential, and may not be worth
515+
// fixing for some users. Gating on language version allows us to avoid
516+
// breaking existing tests and CI scripts.
517+
if !suppressNonconstants &&
518+
idx == len(call.Args)-1 &&
519+
fileVersion != "" && // fail open
520+
versions.AtLeast(fileVersion, "go1.24") {
521+
502522
pass.Report(analysis.Diagnostic{
503523
Pos: formatArg.Pos(),
504524
End: formatArg.End(),

go/analysis/passes/printf/printf_test.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,33 @@
55
package printf_test
66

77
import (
8+
"path/filepath"
89
"testing"
910

1011
"golang.org/x/tools/go/analysis/analysistest"
1112
"golang.org/x/tools/go/analysis/passes/printf"
13+
"golang.org/x/tools/internal/testenv"
14+
"golang.org/x/tools/internal/testfiles"
1215
)
1316

1417
func Test(t *testing.T) {
1518
testdata := analysistest.TestData()
1619
printf.Analyzer.Flags.Set("funcs", "Warn,Warnf")
1720

1821
analysistest.Run(t, testdata, printf.Analyzer,
19-
"a", "b", "nofmt", "typeparams", "issue68744", "issue70572")
20-
analysistest.RunWithSuggestedFixes(t, testdata, printf.Analyzer, "fix")
22+
"a", "b", "nofmt", "nonconst", "typeparams", "issue68744", "issue70572")
23+
}
24+
25+
func TestNonConstantFmtString_Go123(t *testing.T) {
26+
testenv.NeedsGo1Point(t, 23)
27+
28+
dir := testfiles.ExtractTxtarFileToTmp(t, filepath.Join(analysistest.TestData(), "nonconst_go123.txtar"))
29+
analysistest.RunWithSuggestedFixes(t, dir, printf.Analyzer, "example.com/nonconst")
30+
}
31+
32+
func TestNonConstantFmtString_Go124(t *testing.T) {
33+
testenv.NeedsGo1Point(t, 24)
34+
35+
dir := testfiles.ExtractTxtarFileToTmp(t, filepath.Join(analysistest.TestData(), "nonconst_go124.txtar"))
36+
analysistest.RunWithSuggestedFixes(t, dir, printf.Analyzer, "example.com/nonconst")
2137
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
This test checks for the correct suppression (or activation) of the
2+
non-constant format string check (golang/go#60529), in a go1.23 module.
3+
4+
See golang/go#71485 for details.
5+
6+
-- go.mod --
7+
module example.com/nonconst
8+
9+
go 1.23
10+
11+
-- nonconst.go --
12+
package nonconst
13+
14+
import (
15+
"fmt"
16+
"log"
17+
"os"
18+
)
19+
20+
func _(s string) {
21+
fmt.Printf(s)
22+
fmt.Printf(s, "arg")
23+
fmt.Fprintf(os.Stderr, s)
24+
log.Printf(s)
25+
}
26+
27+
-- nonconst_go124.go --
28+
//go:build go1.24
29+
package nonconst
30+
31+
import (
32+
"fmt"
33+
"log"
34+
"os"
35+
)
36+
37+
// With Go 1.24, the analyzer should be activated, as this is a go1.24 file.
38+
func _(s string) {
39+
fmt.Printf(s) // want `non-constant format string in call to fmt.Printf`
40+
fmt.Printf(s, "arg")
41+
fmt.Fprintf(os.Stderr, s) // want `non-constant format string in call to fmt.Fprintf`
42+
log.Printf(s) // want `non-constant format string in call to log.Printf`
43+
}
44+
45+
-- nonconst_go124.go.golden --
46+
//go:build go1.24
47+
package nonconst
48+
49+
import (
50+
"fmt"
51+
"log"
52+
"os"
53+
)
54+
55+
// With Go 1.24, the analyzer should be activated, as this is a go1.24 file.
56+
func _(s string) {
57+
fmt.Printf("%s", s) // want `non-constant format string in call to fmt.Printf`
58+
fmt.Printf(s, "arg")
59+
fmt.Fprintf(os.Stderr, "%s", s) // want `non-constant format string in call to fmt.Fprintf`
60+
log.Printf("%s", s) // want `non-constant format string in call to log.Printf`
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
This test checks for the correct suppression (or activation) of the
2+
non-constant format string check (golang/go#60529), in a go1.24 module.
3+
4+
See golang/go#71485 for details.
5+
6+
-- go.mod --
7+
module example.com/nonconst
8+
9+
go 1.24
10+
11+
-- nonconst.go --
12+
package nonconst
13+
14+
import (
15+
"fmt"
16+
"log"
17+
"os"
18+
)
19+
20+
func _(s string) {
21+
fmt.Printf(s) // want `non-constant format string in call to fmt.Printf`
22+
fmt.Printf(s, "arg")
23+
fmt.Fprintf(os.Stderr, s) // want `non-constant format string in call to fmt.Fprintf`
24+
log.Printf(s) // want `non-constant format string in call to log.Printf`
25+
}
26+
27+
-- nonconst.go.golden --
28+
package nonconst
29+
30+
import (
31+
"fmt"
32+
"log"
33+
"os"
34+
)
35+
36+
func _(s string) {
37+
fmt.Printf("%s", s) // want `non-constant format string in call to fmt.Printf`
38+
fmt.Printf(s, "arg")
39+
fmt.Fprintf(os.Stderr, "%s", s) // want `non-constant format string in call to fmt.Fprintf`
40+
log.Printf("%s", s) // want `non-constant format string in call to log.Printf`
41+
}
42+
43+
-- nonconst_go123.go --
44+
//go:build go1.23
45+
package nonconst
46+
47+
import (
48+
"fmt"
49+
"log"
50+
"os"
51+
)
52+
53+
// The analyzer should be silent, as this is a go1.23 file.
54+
func _(s string) {
55+
fmt.Printf(s)
56+
fmt.Printf(s, "arg")
57+
fmt.Fprintf(os.Stderr, s)
58+
log.Printf(s)
59+
}

go/analysis/passes/printf/testdata/src/fix/fix.go

-20
This file was deleted.

go/analysis/passes/printf/testdata/src/fix/fix.go.golden

-20
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// This file contains tests of the printf checker's handling of non-constant
6+
// format strings (golang/go#60529).
7+
8+
package nonconst
9+
10+
import (
11+
"fmt"
12+
"log"
13+
"os"
14+
)
15+
16+
// As the language version is empty here, and the new check is gated on go1.24,
17+
// diagnostics are suppressed here.
18+
func nonConstantFormat(s string) {
19+
fmt.Printf(s)
20+
fmt.Printf(s, "arg")
21+
fmt.Fprintf(os.Stderr, s)
22+
log.Printf(s)
23+
}

0 commit comments

Comments
 (0)