cmd/vet: verify potentially-recursive Stringers are actually Stringers
The printf recursiveStringer check was checking for a function called String(),
but wasn't checking that it matched the actual function signature of Stringer.
Fixes golang/go#30441
Change-Id: I09d5fba035bb717036f7edf57efc63e2e3fe51d5
Reviewed-on: https://go-review.googlesource.com/c/tools/+/164217
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/go/analysis/passes/printf/printf.go b/go/analysis/passes/printf/printf.go
index d4697ea..f59e95d 100644
--- a/go/analysis/passes/printf/printf.go
+++ b/go/analysis/passes/printf/printf.go
@@ -856,20 +856,28 @@
return false
}
- // Is it the receiver r, or &r?
- recv := stringMethod.Type().(*types.Signature).Recv()
- if recv == nil {
+ sig := stringMethod.Type().(*types.Signature)
+ if !isStringer(sig) {
return false
}
+
+ // Is it the receiver r, or &r?
if u, ok := e.(*ast.UnaryExpr); ok && u.Op == token.AND {
e = u.X // strip off & from &r
}
if id, ok := e.(*ast.Ident); ok {
- return pass.TypesInfo.Uses[id] == recv
+ return pass.TypesInfo.Uses[id] == sig.Recv()
}
return false
}
+// isStringer reports whether the method signature matches the String() definition in fmt.Stringer.
+func isStringer(sig *types.Signature) bool {
+ return sig.Params().Len() == 0 &&
+ sig.Results().Len() == 1 &&
+ sig.Results().At(0).Type() == types.Typ[types.String]
+}
+
// isFunctionValue reports whether the expression is a function as opposed to a function call.
// It is almost always a mistake to print a function value.
func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool {
diff --git a/go/analysis/passes/printf/testdata/src/a/a.go b/go/analysis/passes/printf/testdata/src/a/a.go
index 4aeecd5..b783f10 100644
--- a/go/analysis/passes/printf/testdata/src/a/a.go
+++ b/go/analysis/passes/printf/testdata/src/a/a.go
@@ -516,6 +516,20 @@
return fmt.Sprintln(p) // want "Sprintln arg p causes recursive call to String method"
}
+// implements a String() method but with non-matching return types
+type nonStringerWrongReturn int
+
+func (s nonStringerWrongReturn) String() (string, error) {
+ return "", fmt.Errorf("%v", s)
+}
+
+// implements a String() method but with non-matching arguments
+type nonStringerWrongArgs int
+
+func (s nonStringerWrongArgs) String(i int) string {
+ return fmt.Sprintf("%d%v", i, s)
+}
+
type cons struct {
car int
cdr *cons