GoPLS Viewer

Home|gopls/go/analysis/passes/printf/types.go
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
5package printf
6
7import (
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
16var 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.
23func matchArgType(pass *analysis.Passt printfArgTypearg ast.Expr) (reason stringok 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{ttseenmake(map[types.Type]bool)}
35    ok = m.match(typtrue)
36    return m.reasonok
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.
48type 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.
59func (m *argMatchermatch(typ types.TypetopLevel boolbool {
60    // %w accepts only errors.
61    if m.t == argError {
62        return types.ConvertibleTo(typerrorType)
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        termserr := 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(underfalse)
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 := 0i < 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
277func isConvertibleToString(typ types.Typebool {
278    if btok := 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(typerrorType) {
285        return true // via .Error()
286    }
287
288    // Does it implement fmt.Stringer?
289    if obj__ := types.LookupFieldOrMethod(typfalsenil"String"); obj != nil {
290        if fnok := 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
MembersX
matchArgType.reason
matchArgType.ok
argMatcher.seen
argMatcher.match.BlockStmt.RangeStmt_3550.term
argMatcher.match.BlockStmt.under
isConvertibleToString.obj
isConvertibleToString._
matchArgType.arg
argMatcher
argMatcher.match.BlockStmt.terms
isConvertibleToString.typ
matchArgType
matchArgType.pass
matchArgType.t
argMatcher.t
argMatcher.match.m
argMatcher.match.typ
argMatcher.match.topLevel
argMatcher.match.BlockStmt.err
isConvertibleToString
matchArgType.typ
matchArgType.m
argMatcher.reason
argMatcher.match
argMatcher.match.BlockStmt.i
argMatcher.match.BlockStmt.BlockStmt.typf
Members
X