Skip to content

Commit 9874647

Browse files
findleyrcagedmantis
authored andcommitted
[internal-branch.go1.24-vendor] 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 <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Robert Findley <[email protected]> (cherry picked from commit 9f450b0) Reviewed-on: https://go-review.googlesource.com/c/tools/+/645697
1 parent 44670c7 commit 9874647

File tree

7 files changed

+195
-56
lines changed

7 files changed

+195
-56
lines changed

go/analysis/passes/printf/printf.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"golang.org/x/tools/go/ast/inspector"
2626
"golang.org/x/tools/go/types/typeutil"
2727
"golang.org/x/tools/internal/typeparams"
28+
"golang.org/x/tools/internal/versions"
2829
)
2930

3031
func init() {
@@ -108,12 +109,12 @@ func (f *isWrapper) String() string {
108109
}
109110
}
110111

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

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

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

412-
// checkCall triggers the print-specific checks if the call invokes a print function.
413-
func checkCall(pass *analysis.Pass) {
413+
// checkCalls triggers the print-specific checks for calls that invoke a print
414+
// function.
415+
func checkCalls(pass *analysis.Pass) {
414416
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
415417
nodeFilter := []ast.Node{
418+
(*ast.File)(nil),
416419
(*ast.CallExpr)(nil),
417420
}
421+
422+
var fileVersion string // for selectively suppressing checks; "" if unknown.
418423
inspect.Preorder(nodeFilter, func(n ast.Node) {
419-
call := n.(*ast.CallExpr)
420-
fn, kind := printfNameAndKind(pass, call)
421-
switch kind {
422-
case KindPrintf, KindErrorf:
423-
checkPrintf(pass, kind, call, fn)
424-
case KindPrint:
425-
checkPrint(pass, call, fn)
424+
switch n := n.(type) {
425+
case *ast.File:
426+
fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
427+
428+
case *ast.CallExpr:
429+
fn, kind := printfNameAndKind(pass, n)
430+
switch kind {
431+
case KindPrintf, KindErrorf:
432+
checkPrintf(pass, fileVersion, kind, n, fn)
433+
case KindPrint:
434+
checkPrint(pass, n, fn)
435+
}
426436
}
427437
})
428438
}
@@ -503,7 +513,7 @@ type formatState struct {
503513
}
504514

505515
// checkPrintf checks a call to a formatted print routine such as Printf.
506-
func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, fn *types.Func) {
516+
func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.CallExpr, fn *types.Func) {
507517
idx := formatStringIndex(pass, call)
508518
if idx < 0 || idx >= len(call.Args) {
509519
return
@@ -517,7 +527,17 @@ func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, fn *types.F
517527
// non-constant format string and no arguments:
518528
// if msg contains "%", misformatting occurs.
519529
// Report the problem and suggest a fix: fmt.Printf("%s", msg).
520-
if !suppressNonconstants && idx == len(call.Args)-1 {
530+
//
531+
// However, as described in golang/go#71485, this analysis can produce a
532+
// significant number of diagnostics in existing code, and the bugs it
533+
// finds are sometimes unlikely or inconsequential, and may not be worth
534+
// fixing for some users. Gating on language version allows us to avoid
535+
// breaking existing tests and CI scripts.
536+
if !suppressNonconstants &&
537+
idx == len(call.Args)-1 &&
538+
fileVersion != "" && // fail open
539+
versions.AtLeast(fileVersion, "go1.24") {
540+
521541
pass.Report(analysis.Diagnostic{
522542
Pos: formatArg.Pos(),
523543
End: formatArg.End(),

go/analysis/passes/printf/printf_test.go

Lines changed: 18 additions & 2 deletions
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
}
Lines changed: 61 additions & 0 deletions
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+
}
Lines changed: 59 additions & 0 deletions
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

Lines changed: 0 additions & 20 deletions
This file was deleted.

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

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
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)