go/analysis/passes/printf: warn against using %v verb in Error() methods
go vet should warn against using %v verb in Error() methods just like it
does for String().
Fixes golang/go#33884
Change-Id: I14e30aae316dc84adc62e2b2a0144cc157bb2698
GitHub-Last-Rev: 12240ac78d9c8afb12b014eff8f81ea0bf42471f
GitHub-Pull-Request: golang/tools#214
Reviewed-on: https://go-review.googlesource.com/c/tools/+/223239
Reviewed-by: Michael Matloob <matloob@golang.org>
diff --git a/go/analysis/passes/printf/printf.go b/go/analysis/passes/printf/printf.go
index eb597f6..14f3a47 100644
--- a/go/analysis/passes/printf/printf.go
+++ b/go/analysis/passes/printf/printf.go
@@ -895,43 +895,51 @@
pass.ReportRangef(call, "%s format %s has arg %s of wrong type %s", state.name, state.format, analysisutil.Format(pass.Fset, arg), typeString)
return false
}
- if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) && recursiveStringer(pass, arg) {
- pass.ReportRangef(call, "%s format %s with arg %s causes recursive String method call", state.name, state.format, analysisutil.Format(pass.Fset, arg))
- return false
+ if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) {
+ if methodName, ok := recursiveStringer(pass, arg); ok {
+ pass.ReportRangef(call, "%s format %s with arg %s causes recursive %s method call", state.name, state.format, analysisutil.Format(pass.Fset, arg), methodName)
+ return false
+ }
}
return true
}
// recursiveStringer reports whether the argument e is a potential
-// recursive call to stringer, such as t and &t in these examples:
+// recursive call to stringer or is an error, such as t and &t in these examples:
//
// func (t *T) String() string { printf("%s", t) }
-// func (t T) String() string { printf("%s", t) }
+// func (t T) Error() string { printf("%s", t) }
// func (t T) String() string { printf("%s", &t) }
-//
-func recursiveStringer(pass *analysis.Pass, e ast.Expr) bool {
+func recursiveStringer(pass *analysis.Pass, e ast.Expr) (string, bool) {
typ := pass.TypesInfo.Types[e].Type
// It's unlikely to be a recursive stringer if it has a Format method.
if isFormatter(typ) {
- return false
+ return "", false
}
- // Does e allow e.String()?
- obj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "String")
- stringMethod, ok := obj.(*types.Func)
- if !ok {
- return false
+ // Does e allow e.String() or e.Error()?
+ strObj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "String")
+ strMethod, strOk := strObj.(*types.Func)
+ errObj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "Error")
+ errMethod, errOk := errObj.(*types.Func)
+ if !strOk && !errOk {
+ return "", false
}
- // Is the expression e within the body of that String method?
- if stringMethod.Pkg() != pass.Pkg || !stringMethod.Scope().Contains(e.Pos()) {
- return false
+ // Is the expression e within the body of that String or Error method?
+ var method *types.Func
+ if strOk && strMethod.Pkg() == pass.Pkg && strMethod.Scope().Contains(e.Pos()) {
+ method = strMethod
+ } else if errOk && errMethod.Pkg() == pass.Pkg && errMethod.Scope().Contains(e.Pos()) {
+ method = errMethod
+ } else {
+ return "", false
}
- sig := stringMethod.Type().(*types.Signature)
+ sig := method.Type().(*types.Signature)
if !isStringer(sig) {
- return false
+ return "", false
}
// Is it the receiver r, or &r?
@@ -939,9 +947,11 @@
e = u.X // strip off & from &r
}
if id, ok := e.(*ast.Ident); ok {
- return pass.TypesInfo.Uses[id] == sig.Recv()
+ if pass.TypesInfo.Uses[id] == sig.Recv() {
+ return method.Name(), true
+ }
}
- return false
+ return "", false
}
// isStringer reports whether the method signature matches the String() definition in fmt.Stringer.
@@ -1065,8 +1075,8 @@
if isFunctionValue(pass, arg) {
pass.ReportRangef(call, "%s arg %s is a func value, not called", fn.Name(), analysisutil.Format(pass.Fset, arg))
}
- if recursiveStringer(pass, arg) {
- pass.ReportRangef(call, "%s arg %s causes recursive call to String method", fn.Name(), analysisutil.Format(pass.Fset, arg))
+ if methodName, ok := recursiveStringer(pass, arg); ok {
+ pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", fn.Name(), analysisutil.Format(pass.Fset, arg), methodName)
}
}
}
diff --git a/go/analysis/passes/printf/testdata/src/a/a.go b/go/analysis/passes/printf/testdata/src/a/a.go
index 6596209..0f3dc8d 100644
--- a/go/analysis/passes/printf/testdata/src/a/a.go
+++ b/go/analysis/passes/printf/testdata/src/a/a.go
@@ -526,6 +526,59 @@
return fmt.Sprintln(p) // want "Sprintln arg p causes recursive call to String method"
}
+type recursiveError int
+
+func (s recursiveError) Error() string {
+ _ = fmt.Sprintf("%d", s)
+ _ = fmt.Sprintf("%#v", s)
+ _ = fmt.Sprintf("%v", s) // want "Sprintf format %v with arg s causes recursive Error method call"
+ _ = fmt.Sprintf("%v", &s) // want "Sprintf format %v with arg &s causes recursive Error method call"
+ _ = fmt.Sprintf("%T", s) // ok; does not recursively call Error
+ return fmt.Sprintln(s) // want "Sprintln arg s causes recursive call to Error method"
+}
+
+type recursivePtrError int
+
+func (p *recursivePtrError) Error() string {
+ _ = fmt.Sprintf("%v", *p)
+ _ = fmt.Sprint(&p) // ok; prints address
+ return fmt.Sprintln(p) // want "Sprintln arg p causes recursive call to Error method"
+}
+
+type recursiveStringerAndError int
+
+func (s recursiveStringerAndError) String() string {
+ _ = fmt.Sprintf("%d", s)
+ _ = fmt.Sprintf("%#v", s)
+ _ = fmt.Sprintf("%v", s) // want "Sprintf format %v with arg s causes recursive String method call"
+ _ = fmt.Sprintf("%v", &s) // want "Sprintf format %v with arg &s causes recursive String method call"
+ _ = fmt.Sprintf("%T", s) // ok; does not recursively call String
+ return fmt.Sprintln(s) // want "Sprintln arg s causes recursive call to String method"
+}
+
+func (s recursiveStringerAndError) Error() string {
+ _ = fmt.Sprintf("%d", s)
+ _ = fmt.Sprintf("%#v", s)
+ _ = fmt.Sprintf("%v", s) // want "Sprintf format %v with arg s causes recursive Error method call"
+ _ = fmt.Sprintf("%v", &s) // want "Sprintf format %v with arg &s causes recursive Error method call"
+ _ = fmt.Sprintf("%T", s) // ok; does not recursively call Error
+ return fmt.Sprintln(s) // want "Sprintln arg s causes recursive call to Error method"
+}
+
+type recursivePtrStringerAndError int
+
+func (p *recursivePtrStringerAndError) String() string {
+ _ = fmt.Sprintf("%v", *p)
+ _ = fmt.Sprint(&p) // ok; prints address
+ return fmt.Sprintln(p) // want "Sprintln arg p causes recursive call to String method"
+}
+
+func (p *recursivePtrStringerAndError) Error() string {
+ _ = fmt.Sprintf("%v", *p)
+ _ = fmt.Sprint(&p) // ok; prints address
+ return fmt.Sprintln(p) // want "Sprintln arg p causes recursive call to Error method"
+}
+
// implements a String() method but with non-matching return types
type nonStringerWrongReturn int