7 "golang.org/x/tools/go/analysis"
8 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
11 var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
13 // matchArgType reports an error if printf verb t is not appropriate
16 // typ is used only for recursive calls; external callers must supply nil.
18 // (Recursion arises from the compound types {map,chan,slice} which
19 // may be printed with %d etc. if that is appropriate for their element
21 func matchArgType(pass *analysis.Pass, t printfArgType, typ types.Type, arg ast.Expr) bool {
22 return matchArgTypeInternal(pass, t, typ, arg, make(map[types.Type]bool))
25 // matchArgTypeInternal is the internal version of matchArgType. It carries a map
26 // remembering what types are in progress so we don't recur when faced with recursive
27 // types or mutually recursive types.
28 func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool {
29 // %v, %T accept any argument type.
35 typ = pass.TypesInfo.Types[arg].Type
37 return true // probably a type check problem
41 // %w accepts only errors.
43 return types.ConvertibleTo(typ, errorType)
46 // If the type implements fmt.Formatter, we have nothing to check.
50 // If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
51 if t&argString != 0 && isConvertibleToString(pass, typ) {
55 typ = typ.Underlying()
57 // We're already looking at this type. The call that started it will take care of it.
60 inProgress[typ] = true
62 switch typ := typ.(type) {
63 case *types.Signature:
64 return t == argPointer
67 return t == argPointer ||
68 // Recur: map[int]int matches %d.
69 (matchArgTypeInternal(pass, t, typ.Key(), arg, inProgress) && matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress))
72 return t&argPointer != 0
76 if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 {
77 return true // %s matches []byte
79 // Recur: []int matches %d.
80 return matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress)
84 if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 {
85 return true // %s matches []byte
88 return true // %p prints a slice's 0th element
90 // Recur: []int matches %d. But watch out for
92 // If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below.
93 return matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress)
96 // Ugly, but dealing with an edge case: a known pointer to an invalid type,
97 // probably something from a failed import.
98 if typ.Elem().String() == "invalid type" {
100 pass.Reportf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", analysisutil.Format(pass.Fset, arg))
102 return true // special case
104 // If it's actually a pointer with %p, it prints as one.
109 under := typ.Elem().Underlying()
110 switch under.(type) {
111 case *types.Struct: // see below
112 case *types.Array: // see below
113 case *types.Slice: // see below
114 case *types.Map: // see below
116 // Check whether the rest can print pointers.
117 return t&argPointer != 0
119 // If it's a top-level pointer to a struct, array, slice, or
120 // map, that's equivalent in our analysis to whether we can
121 // print the type being pointed to. Pointers in nested levels
122 // are not supported to minimize fmt running into loops.
123 if len(inProgress) > 1 {
126 return matchArgTypeInternal(pass, t, under, arg, inProgress)
129 return matchStructArgType(pass, t, typ, arg, inProgress)
131 case *types.Interface:
132 // There's little we can do.
133 // Whether any particular verb is valid depends on the argument.
134 // The user may have reasonable prior knowledge of the contents of the interface.
139 case types.UntypedBool,
141 return t&argBool != 0
143 case types.UntypedInt,
157 case types.UntypedFloat,
160 return t&argFloat != 0
162 case types.UntypedComplex,
165 return t&argComplex != 0
167 case types.UntypedString,
169 return t&argString != 0
171 case types.UnsafePointer:
172 return t&(argPointer|argInt) != 0
174 case types.UntypedRune:
175 return t&(argInt|argRune) != 0
177 case types.UntypedNil:
182 pass.Reportf(arg.Pos(), "printf argument %v has invalid or unknown type", analysisutil.Format(pass.Fset, arg))
184 return true // Probably a type check problem.
192 func isConvertibleToString(pass *analysis.Pass, typ types.Type) bool {
193 if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil {
194 // We explicitly don't want untyped nil, which is
195 // convertible to both of the interfaces below, as it
196 // would just panic anyway.
199 if types.ConvertibleTo(typ, errorType) {
200 return true // via .Error()
203 // Does it implement fmt.Stringer?
204 if obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "String"); obj != nil {
205 if fn, ok := obj.(*types.Func); ok {
206 sig := fn.Type().(*types.Signature)
207 if sig.Params().Len() == 0 &&
208 sig.Results().Len() == 1 &&
209 sig.Results().At(0).Type() == types.Typ[types.String] {
218 // hasBasicType reports whether x's type is a types.Basic with the given kind.
219 func hasBasicType(pass *analysis.Pass, x ast.Expr, kind types.BasicKind) bool {
220 t := pass.TypesInfo.Types[x].Type
224 b, ok := t.(*types.Basic)
225 return ok && b.Kind() == kind
228 // matchStructArgType reports whether all the elements of the struct match the expected
229 // type. For instance, with "%d" all the elements must be printable with the "%d" format.
230 func matchStructArgType(pass *analysis.Pass, t printfArgType, typ *types.Struct, arg ast.Expr, inProgress map[types.Type]bool) bool {
231 for i := 0; i < typ.NumFields(); i++ {
233 if !matchArgTypeInternal(pass, t, typf.Type(), arg, inProgress) {
236 if t&argString != 0 && !typf.Exported() && isConvertibleToString(pass, typf.Type()) {
237 // Issue #17798: unexported Stringer or error cannot be properly formatted.