1
0
mirror of https://github.com/golang/go synced 2024-10-01 12:48:33 -06: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:
Daniel Martí 2019-02-26 13:40:04 +01:00
parent 4a0f391d88
commit 589c23e65e
4 changed files with 30 additions and 12 deletions

View File

@ -453,15 +453,23 @@ func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func,
}
// isFormatter reports whether t satisfies fmt.Formatter.
// Unlike fmt.Stringer, it's impossible to satisfy fmt.Formatter without importing fmt.
func isFormatter(pass *analysis.Pass, t types.Type) bool {
for _, imp := range pass.Pkg.Imports() {
if imp.Path() == "fmt" {
formatter := imp.Scope().Lookup("Formatter").Type().Underlying().(*types.Interface)
return types.Implements(t, formatter)
}
// The only interface method to look for is "Format(State, rune)".
func isFormatter(typ types.Type) bool {
obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "Format")
fn, ok := obj.(*types.Func)
if !ok {
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".
@ -754,7 +762,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, state *formatState) (o
formatter := false
if state.argNum < len(call.Args) {
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
// It's unlikely to be a recursive stringer if it has a Format method.
if isFormatter(pass, typ) {
if isFormatter(typ) {
return false
}

View File

@ -10,5 +10,5 @@ import (
func Test(t *testing.T) {
testdata := analysistest.TestData()
printf.Analyzer.Flags.Set("funcs", "Warn,Warnf")
analysistest.Run(t, testdata, printf.Analyzer, "a", "b")
analysistest.Run(t, testdata, printf.Analyzer, "a", "b", "nofmt")
}

View File

@ -0,0 +1,10 @@
package b
import (
"math/big"
"testing"
)
func formatBigInt(t *testing.T) {
t.Logf("%d\n", big.NewInt(4))
}

View File

@ -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 isFormatter(pass, typ) {
if isFormatter(typ) {
return true
}
// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?