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. |
4 | |
5 | package printf |
6 | |
7 | import ( |
8 | "fmt" |
9 | "go/ast" |
10 | "go/types" |
11 | |
12 | "golang.org/x/tools/go/analysis" |
13 | "golang.org/x/tools/internal/typeparams" |
14 | ) |
15 | |
16 | var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) |
17 | |
18 | // matchArgType reports an error if printf verb t is not appropriate for |
19 | // operand arg. |
20 | // |
21 | // If arg is a type parameter, the verb t must be appropriate for every type in |
22 | // the type parameter type set. |
23 | func matchArgType(pass *analysis.Pass, t printfArgType, arg ast.Expr) (reason string, ok bool) { |
24 | // %v, %T accept any argument type. |
25 | if t == anyType { |
26 | return "", true |
27 | } |
28 | |
29 | typ := pass.TypesInfo.Types[arg].Type |
30 | if typ == nil { |
31 | return "", true // probably a type check problem |
32 | } |
33 | |
34 | m := &argMatcher{t: t, seen: make(map[types.Type]bool)} |
35 | ok = m.match(typ, true) |
36 | return m.reason, ok |
37 | } |
38 | |
39 | // argMatcher recursively matches types against the printfArgType t. |
40 | // |
41 | // To short-circuit recursion, it keeps track of types that have already been |
42 | // matched (or are in the process of being matched) via the seen map. Recursion |
43 | // arises from the compound types {map,chan,slice} which may be printed with %d |
44 | // etc. if that is appropriate for their element types, as well as from type |
45 | // parameters, which are expanded to the constituents of their type set. |
46 | // |
47 | // The reason field may be set to report the cause of the mismatch. |
48 | type argMatcher struct { |
49 | t printfArgType |
50 | seen map[types.Type]bool |
51 | reason string |
52 | } |
53 | |
54 | // match checks if typ matches m's printf arg type. If topLevel is true, typ is |
55 | // the actual type of the printf arg, for which special rules apply. As a |
56 | // special case, top level type parameters pass topLevel=true when checking for |
57 | // matches among the constituents of their type set, as type arguments will |
58 | // replace the type parameter at compile time. |
59 | func (m *argMatcher) match(typ types.Type, topLevel bool) bool { |
60 | // %w accepts only errors. |
61 | if m.t == argError { |
62 | return types.ConvertibleTo(typ, errorType) |
63 | } |
64 | |
65 | // If the type implements fmt.Formatter, we have nothing to check. |
66 | if isFormatter(typ) { |
67 | return true |
68 | } |
69 | |
70 | // If we can use a string, might arg (dynamically) implement the Stringer or Error interface? |
71 | if m.t&argString != 0 && isConvertibleToString(typ) { |
72 | return true |
73 | } |
74 | |
75 | if typ, _ := typ.(*typeparams.TypeParam); typ != nil { |
76 | // Avoid infinite recursion through type parameters. |
77 | if m.seen[typ] { |
78 | return true |
79 | } |
80 | m.seen[typ] = true |
81 | terms, err := typeparams.StructuralTerms(typ) |
82 | if err != nil { |
83 | return true // invalid type (possibly an empty type set) |
84 | } |
85 | |
86 | if len(terms) == 0 { |
87 | // No restrictions on the underlying of typ. Type parameters implementing |
88 | // error, fmt.Formatter, or fmt.Stringer were handled above, and %v and |
89 | // %T was handled in matchType. We're about to check restrictions the |
90 | // underlying; if the underlying type is unrestricted there must be an |
91 | // element of the type set that violates one of the arg type checks |
92 | // below, so we can safely return false here. |
93 | |
94 | if m.t == anyType { // anyType must have already been handled. |
95 | panic("unexpected printfArgType") |
96 | } |
97 | return false |
98 | } |
99 | |
100 | // Only report a reason if typ is the argument type, otherwise it won't |
101 | // make sense. Note that it is not sufficient to check if topLevel == here, |
102 | // as type parameters can have a type set consisting of other type |
103 | // parameters. |
104 | reportReason := len(m.seen) == 1 |
105 | |
106 | for _, term := range terms { |
107 | if !m.match(term.Type(), topLevel) { |
108 | if reportReason { |
109 | if term.Tilde() { |
110 | m.reason = fmt.Sprintf("contains ~%s", term.Type()) |
111 | } else { |
112 | m.reason = fmt.Sprintf("contains %s", term.Type()) |
113 | } |
114 | } |
115 | return false |
116 | } |
117 | } |
118 | return true |
119 | } |
120 | |
121 | typ = typ.Underlying() |
122 | if m.seen[typ] { |
123 | // We've already considered typ, or are in the process of considering it. |
124 | // In case we've already considered typ, it must have been valid (else we |
125 | // would have stopped matching). In case we're in the process of |
126 | // considering it, we must avoid infinite recursion. |
127 | // |
128 | // There are some pathological cases where returning true here is |
129 | // incorrect, for example `type R struct { F []R }`, but these are |
130 | // acceptable false negatives. |
131 | return true |
132 | } |
133 | m.seen[typ] = true |
134 | |
135 | switch typ := typ.(type) { |
136 | case *types.Signature: |
137 | return m.t == argPointer |
138 | |
139 | case *types.Map: |
140 | if m.t == argPointer { |
141 | return true |
142 | } |
143 | // Recur: map[int]int matches %d. |
144 | return m.match(typ.Key(), false) && m.match(typ.Elem(), false) |
145 | |
146 | case *types.Chan: |
147 | return m.t&argPointer != 0 |
148 | |
149 | case *types.Array: |
150 | // Same as slice. |
151 | if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && m.t&argString != 0 { |
152 | return true // %s matches []byte |
153 | } |
154 | // Recur: []int matches %d. |
155 | return m.match(typ.Elem(), false) |
156 | |
157 | case *types.Slice: |
158 | // Same as array. |
159 | if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && m.t&argString != 0 { |
160 | return true // %s matches []byte |
161 | } |
162 | if m.t == argPointer { |
163 | return true // %p prints a slice's 0th element |
164 | } |
165 | // Recur: []int matches %d. But watch out for |
166 | // type T []T |
167 | // If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below. |
168 | return m.match(typ.Elem(), false) |
169 | |
170 | case *types.Pointer: |
171 | // Ugly, but dealing with an edge case: a known pointer to an invalid type, |
172 | // probably something from a failed import. |
173 | if typ.Elem() == types.Typ[types.Invalid] { |
174 | return true // special case |
175 | } |
176 | // If it's actually a pointer with %p, it prints as one. |
177 | if m.t == argPointer { |
178 | return true |
179 | } |
180 | |
181 | if typeparams.IsTypeParam(typ.Elem()) { |
182 | return true // We don't know whether the logic below applies. Give up. |
183 | } |
184 | |
185 | under := typ.Elem().Underlying() |
186 | switch under.(type) { |
187 | case *types.Struct: // see below |
188 | case *types.Array: // see below |
189 | case *types.Slice: // see below |
190 | case *types.Map: // see below |
191 | default: |
192 | // Check whether the rest can print pointers. |
193 | return m.t&argPointer != 0 |
194 | } |
195 | // If it's a top-level pointer to a struct, array, slice, type param, or |
196 | // map, that's equivalent in our analysis to whether we can |
197 | // print the type being pointed to. Pointers in nested levels |
198 | // are not supported to minimize fmt running into loops. |
199 | if !topLevel { |
200 | return false |
201 | } |
202 | return m.match(under, false) |
203 | |
204 | case *types.Struct: |
205 | // report whether all the elements of the struct match the expected type. For |
206 | // instance, with "%d" all the elements must be printable with the "%d" format. |
207 | for i := 0; i < typ.NumFields(); i++ { |
208 | typf := typ.Field(i) |
209 | if !m.match(typf.Type(), false) { |
210 | return false |
211 | } |
212 | if m.t&argString != 0 && !typf.Exported() && isConvertibleToString(typf.Type()) { |
213 | // Issue #17798: unexported Stringer or error cannot be properly formatted. |
214 | return false |
215 | } |
216 | } |
217 | return true |
218 | |
219 | case *types.Interface: |
220 | // There's little we can do. |
221 | // Whether any particular verb is valid depends on the argument. |
222 | // The user may have reasonable prior knowledge of the contents of the interface. |
223 | return true |
224 | |
225 | case *types.Basic: |
226 | switch typ.Kind() { |
227 | case types.UntypedBool, |
228 | types.Bool: |
229 | return m.t&argBool != 0 |
230 | |
231 | case types.UntypedInt, |
232 | types.Int, |
233 | types.Int8, |
234 | types.Int16, |
235 | types.Int32, |
236 | types.Int64, |
237 | types.Uint, |
238 | types.Uint8, |
239 | types.Uint16, |
240 | types.Uint32, |
241 | types.Uint64, |
242 | types.Uintptr: |
243 | return m.t&argInt != 0 |
244 | |
245 | case types.UntypedFloat, |
246 | types.Float32, |
247 | types.Float64: |
248 | return m.t&argFloat != 0 |
249 | |
250 | case types.UntypedComplex, |
251 | types.Complex64, |
252 | types.Complex128: |
253 | return m.t&argComplex != 0 |
254 | |
255 | case types.UntypedString, |
256 | types.String: |
257 | return m.t&argString != 0 |
258 | |
259 | case types.UnsafePointer: |
260 | return m.t&(argPointer|argInt) != 0 |
261 | |
262 | case types.UntypedRune: |
263 | return m.t&(argInt|argRune) != 0 |
264 | |
265 | case types.UntypedNil: |
266 | return false |
267 | |
268 | case types.Invalid: |
269 | return true // Probably a type check problem. |
270 | } |
271 | panic("unreachable") |
272 | } |
273 | |
274 | return false |
275 | } |
276 | |
277 | func isConvertibleToString(typ types.Type) bool { |
278 | if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil { |
279 | // We explicitly don't want untyped nil, which is |
280 | // convertible to both of the interfaces below, as it |
281 | // would just panic anyway. |
282 | return false |
283 | } |
284 | if types.ConvertibleTo(typ, errorType) { |
285 | return true // via .Error() |
286 | } |
287 | |
288 | // Does it implement fmt.Stringer? |
289 | if obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "String"); obj != nil { |
290 | if fn, ok := obj.(*types.Func); ok { |
291 | sig := fn.Type().(*types.Signature) |
292 | if sig.Params().Len() == 0 && |
293 | sig.Results().Len() == 1 && |
294 | sig.Results().At(0).Type() == types.Typ[types.String] { |
295 | return true |
296 | } |
297 | } |
298 | } |
299 | |
300 | return false |
301 | } |
302 |
Members