github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/analysis/passes/printf/types.go (about) 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 "github.com/powerman/golang-tools/go/analysis" 13 "github.com/powerman/golang-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 303 // hasBasicType reports whether x's type is a types.Basic with the given kind. 304 func hasBasicType(pass *analysis.Pass, x ast.Expr, kind types.BasicKind) bool { 305 t := pass.TypesInfo.Types[x].Type 306 if t != nil { 307 t = t.Underlying() 308 } 309 b, ok := t.(*types.Basic) 310 return ok && b.Kind() == kind 311 }