mirror of
https://github.com/golang/go
synced 2024-11-19 05:54:44 -07:00
go/analysis/passes/printf: fix big.Int false positive
It's possible to use a type which implements fmt.Formatter without importing fmt directly, if the type is imported from another package such as math/big. On top of that, it's possible to use printf-like functions without importing fmt directly, such as using testing.T.Logf. These two scenarios combined can lead to the printf check not finding the fmt.Formatter type, since it's not a direct dependency of the root package. fmt must still be in the import graph somewhere, so we could search for it via types.Package.Imports. However, at that point it's simpler to just look for the Format method manually via go/types. Fixes #30399. Change-Id: Id78454bb6a51b3c5e1bcb1984a7fbfb4a29a5be0 Reviewed-on: https://go-review.googlesource.com/c/163817 Run-TryBot: Daniel Martí <mvdan@mvdan.cc> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
4a0f391d88
commit
589c23e65e
@ -453,15 +453,23 @@ func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isFormatter reports whether t satisfies fmt.Formatter.
|
// isFormatter reports whether t satisfies fmt.Formatter.
|
||||||
// Unlike fmt.Stringer, it's impossible to satisfy fmt.Formatter without importing fmt.
|
// The only interface method to look for is "Format(State, rune)".
|
||||||
func isFormatter(pass *analysis.Pass, t types.Type) bool {
|
func isFormatter(typ types.Type) bool {
|
||||||
for _, imp := range pass.Pkg.Imports() {
|
obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "Format")
|
||||||
if imp.Path() == "fmt" {
|
fn, ok := obj.(*types.Func)
|
||||||
formatter := imp.Scope().Lookup("Formatter").Type().Underlying().(*types.Interface)
|
if !ok {
|
||||||
return types.Implements(t, formatter)
|
return false
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
sig := fn.Type().(*types.Signature)
|
||||||
|
return sig.Params().Len() == 2 &&
|
||||||
|
sig.Results().Len() == 0 &&
|
||||||
|
isNamed(sig.Params().At(0).Type(), "fmt", "State") &&
|
||||||
|
types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune])
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNamed(T types.Type, pkgpath, name string) bool {
|
||||||
|
named, ok := T.(*types.Named)
|
||||||
|
return ok && named.Obj().Pkg().Path() == pkgpath && named.Obj().Name() == name
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatState holds the parsed representation of a printf directive such as "%3.*[4]d".
|
// formatState holds the parsed representation of a printf directive such as "%3.*[4]d".
|
||||||
@ -754,7 +762,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, state *formatState) (o
|
|||||||
formatter := false
|
formatter := false
|
||||||
if state.argNum < len(call.Args) {
|
if state.argNum < len(call.Args) {
|
||||||
if tv, ok := pass.TypesInfo.Types[call.Args[state.argNum]]; ok {
|
if tv, ok := pass.TypesInfo.Types[call.Args[state.argNum]]; ok {
|
||||||
formatter = isFormatter(pass, tv.Type)
|
formatter = isFormatter(tv.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,7 +840,7 @@ func recursiveStringer(pass *analysis.Pass, e ast.Expr) bool {
|
|||||||
typ := pass.TypesInfo.Types[e].Type
|
typ := pass.TypesInfo.Types[e].Type
|
||||||
|
|
||||||
// It's unlikely to be a recursive stringer if it has a Format method.
|
// It's unlikely to be a recursive stringer if it has a Format method.
|
||||||
if isFormatter(pass, typ) {
|
if isFormatter(typ) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,5 +10,5 @@ import (
|
|||||||
func Test(t *testing.T) {
|
func Test(t *testing.T) {
|
||||||
testdata := analysistest.TestData()
|
testdata := analysistest.TestData()
|
||||||
printf.Analyzer.Flags.Set("funcs", "Warn,Warnf")
|
printf.Analyzer.Flags.Set("funcs", "Warn,Warnf")
|
||||||
analysistest.Run(t, testdata, printf.Analyzer, "a", "b")
|
analysistest.Run(t, testdata, printf.Analyzer, "a", "b", "nofmt")
|
||||||
}
|
}
|
||||||
|
10
go/analysis/passes/printf/testdata/src/nofmt/nofmt.go
vendored
Normal file
10
go/analysis/passes/printf/testdata/src/nofmt/nofmt.go
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package b
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatBigInt(t *testing.T) {
|
||||||
|
t.Logf("%d\n", big.NewInt(4))
|
||||||
|
}
|
@ -38,7 +38,7 @@ func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the type implements fmt.Formatter, we have nothing to check.
|
// If the type implements fmt.Formatter, we have nothing to check.
|
||||||
if isFormatter(pass, typ) {
|
if isFormatter(typ) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
|
// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
|
||||||
|
Loading…
Reference in New Issue
Block a user