| 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