1 // Copyright 2018 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.
11 "golang.org/x/tools/go/analysis"
12 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
15 var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
17 // matchArgType reports an error if printf verb t is not appropriate
20 // typ is used only for recursive calls; external callers must supply nil.
22 // (Recursion arises from the compound types {map,chan,slice} which
23 // may be printed with %d etc. if that is appropriate for their element
25 func matchArgType(pass *analysis.Pass, t printfArgType, typ types.Type, arg ast.Expr) bool {
26 return matchArgTypeInternal(pass, t, typ, arg, make(map[types.Type]bool))
29 // matchArgTypeInternal is the internal version of matchArgType. It carries a map
30 // remembering what types are in progress so we don't recur when faced with recursive
31 // types or mutually recursive types.
32 func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool {
33 // %v, %T accept any argument type.
39 typ = pass.TypesInfo.Types[arg].Type
41 return true // probably a type check problem
45 // %w accepts only errors.
47 return types.ConvertibleTo(typ, errorType)
50 // If the type implements fmt.Formatter, we have nothing to check.
54 // If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
55 if t&argString != 0 && isConvertibleToString(pass, typ) {
59 typ = typ.Underlying()
61 // We're already looking at this type. The call that started it will take care of it.
64 inProgress[typ] = true
66 switch typ := typ.(type) {
67 case *types.Signature:
68 return t == argPointer
71 return t == argPointer ||
72 // Recur: map[int]int matches %d.
73 (matchArgTypeInternal(pass, t, typ.Key(), arg, inProgress) && matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress))
76 return t&argPointer != 0
80 if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 {
81 return true // %s matches []byte
83 // Recur: []int matches %d.
84 return matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress)
88 if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 {
89 return true // %s matches []byte
92 return true // %p prints a slice's 0th element
94 // Recur: []int matches %d. But watch out for
96 // If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below.
97 return matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress)
100 // Ugly, but dealing with an edge case: a known pointer to an invalid type,
101 // probably something from a failed import.
102 if typ.Elem().String() == "invalid type" {
104 pass.Reportf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", analysisutil.Format(pass.Fset, arg))
106 return true // special case
108 // If it's actually a pointer with %p, it prints as one.
113 under := typ.Elem().Underlying()
114 switch under.(type) {
115 case *types.Struct: // see below
116 case *types.Array: // see below
117 case *types.Slice: // see below
118 case *types.Map: // see below
120 // Check whether the rest can print pointers.
121 return t&argPointer != 0
123 // If it's a top-level pointer to a struct, array, slice, or
124 // map, that's equivalent in our analysis to whether we can
125 // print the type being pointed to. Pointers in nested levels
126 // are not supported to minimize fmt running into loops.
127 if len(inProgress) > 1 {
130 return matchArgTypeInternal(pass, t, under, arg, inProgress)
133 return matchStructArgType(pass, t, typ, arg, inProgress)
135 case *types.Interface:
136 // There's little we can do.
137 // Whether any particular verb is valid depends on the argument.
138 // The user may have reasonable prior knowledge of the contents of the interface.
143 case types.UntypedBool,
145 return t&argBool != 0
147 case types.UntypedInt,
161 case types.UntypedFloat,
164 return t&argFloat != 0
166 case types.UntypedComplex,
169 return t&argComplex != 0
171 case types.UntypedString,
173 return t&argString != 0
175 case types.UnsafePointer:
176 return t&(argPointer|argInt) != 0
178 case types.UntypedRune:
179 return t&(argInt|argRune) != 0
181 case types.UntypedNil:
186 pass.Reportf(arg.Pos(), "printf argument %v has invalid or unknown type", analysisutil.Format(pass.Fset, arg))
188 return true // Probably a type check problem.
196 func isConvertibleToString(pass *analysis.Pass, typ types.Type) bool {
197 if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil {
198 // We explicitly don't want untyped nil, which is
199 // convertible to both of the interfaces below, as it
200 // would just panic anyway.
203 if types.ConvertibleTo(typ, errorType) {
204 return true // via .Error()
207 // Does it implement fmt.Stringer?
208 if obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "String"); obj != nil {
209 if fn, ok := obj.(*types.Func); ok {
210 sig := fn.Type().(*types.Signature)
211 if sig.Params().Len() == 0 &&
212 sig.Results().Len() == 1 &&
213 sig.Results().At(0).Type() == types.Typ[types.String] {
222 // hasBasicType reports whether x's type is a types.Basic with the given kind.
223 func hasBasicType(pass *analysis.Pass, x ast.Expr, kind types.BasicKind) bool {
224 t := pass.TypesInfo.Types[x].Type
228 b, ok := t.(*types.Basic)
229 return ok && b.Kind() == kind
232 // matchStructArgType reports whether all the elements of the struct match the expected
233 // type. For instance, with "%d" all the elements must be printable with the "%d" format.
234 func matchStructArgType(pass *analysis.Pass, t printfArgType, typ *types.Struct, arg ast.Expr, inProgress map[types.Type]bool) bool {
235 for i := 0; i < typ.NumFields(); i++ {
237 if !matchArgTypeInternal(pass, t, typf.Type(), arg, inProgress) {
240 if t&argString != 0 && !typf.Exported() && isConvertibleToString(pass, typf.Type()) {
241 // Issue #17798: unexported Stringer or error cannot be properly formatted.