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 "go/ast" 9 "go/types" 10 11 "golang.org/x/tools/go/analysis" 12 "golang.org/x/tools/go/analysis/passes/internal/analysisutil" 13) 14 15var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) 16 17// matchArgType reports an error if printf verb t is not appropriate 18// for operand arg. 19// 20// typ is used only for recursive calls; external callers must supply nil. 21// 22// (Recursion arises from the compound types {map,chan,slice} which 23// may be printed with %d etc. if that is appropriate for their element 24// types.) 25func matchArgType(pass *analysis.Pass, t printfArgType, typ types.Type, arg ast.Expr) bool { 26 return matchArgTypeInternal(pass, t, typ, arg, make(map[types.Type]bool)) 27} 28 29// matchArgTypeInternal is the internal version of matchArgType. It carries a map 30// remembering what types are in progress so we don't recur when faced with recursive 31// types or mutually recursive types. 32func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool { 33 // %v, %T accept any argument type. 34 if t == anyType { 35 return true 36 } 37 if typ == nil { 38 // external call 39 typ = pass.TypesInfo.Types[arg].Type 40 if typ == nil { 41 return true // probably a type check problem 42 } 43 } 44 45 // %w accepts only errors. 46 if t == argError { 47 return types.ConvertibleTo(typ, errorType) 48 } 49 50 // If the type implements fmt.Formatter, we have nothing to check. 51 if isFormatter(typ) { 52 return true 53 } 54 // If we can use a string, might arg (dynamically) implement the Stringer or Error interface? 55 if t&argString != 0 && isConvertibleToString(pass, typ) { 56 return true 57 } 58 59 typ = typ.Underlying() 60 if inProgress[typ] { 61 // We're already looking at this type. The call that started it will take care of it. 62 return true 63 } 64 inProgress[typ] = true 65 66 switch typ := typ.(type) { 67 case *types.Signature: 68 return t == argPointer 69 70 case *types.Map: 71 return t == argPointer || 72 // Recur: map[int]int matches %d. 73 (matchArgTypeInternal(pass, t, typ.Key(), arg, inProgress) && matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress)) 74 75 case *types.Chan: 76 return t&argPointer != 0 77 78 case *types.Array: 79 // Same as slice. 80 if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { 81 return true // %s matches []byte 82 } 83 // Recur: []int matches %d. 84 return matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress) 85 86 case *types.Slice: 87 // Same as array. 88 if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { 89 return true // %s matches []byte 90 } 91 if t == argPointer { 92 return true // %p prints a slice's 0th element 93 } 94 // Recur: []int matches %d. But watch out for 95 // type T []T 96 // If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below. 97 return matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress) 98 99 case *types.Pointer: 100 // Ugly, but dealing with an edge case: a known pointer to an invalid type, 101 // probably something from a failed import. 102 if typ.Elem().String() == "invalid type" { 103 if false { 104 pass.Reportf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", analysisutil.Format(pass.Fset, arg)) 105 } 106 return true // special case 107 } 108 // If it's actually a pointer with %p, it prints as one. 109 if t == argPointer { 110 return true 111 } 112 113 under := typ.Elem().Underlying() 114 switch under.(type) { 115 case *types.Struct: // see below 116 case *types.Array: // see below 117 case *types.Slice: // see below 118 case *types.Map: // see below 119 default: 120 // Check whether the rest can print pointers. 121 return t&argPointer != 0 122 } 123 // If it's a top-level pointer to a struct, array, slice, or 124 // map, that's equivalent in our analysis to whether we can 125 // print the type being pointed to. Pointers in nested levels 126 // are not supported to minimize fmt running into loops. 127 if len(inProgress) > 1 { 128 return false 129 } 130 return matchArgTypeInternal(pass, t, under, arg, inProgress) 131 132 case *types.Struct: 133 return matchStructArgType(pass, t, typ, arg, inProgress) 134 135 case *types.Interface: 136 // There's little we can do. 137 // Whether any particular verb is valid depends on the argument. 138 // The user may have reasonable prior knowledge of the contents of the interface. 139 return true 140 141 case *types.Basic: 142 switch typ.Kind() { 143 case types.UntypedBool, 144 types.Bool: 145 return t&argBool != 0 146 147 case types.UntypedInt, 148 types.Int, 149 types.Int8, 150 types.Int16, 151 types.Int32, 152 types.Int64, 153 types.Uint, 154 types.Uint8, 155 types.Uint16, 156 types.Uint32, 157 types.Uint64, 158 types.Uintptr: 159 return t&argInt != 0 160 161 case types.UntypedFloat, 162 types.Float32, 163 types.Float64: 164 return t&argFloat != 0 165 166 case types.UntypedComplex, 167 types.Complex64, 168 types.Complex128: 169 return t&argComplex != 0 170 171 case types.UntypedString, 172 types.String: 173 return t&argString != 0 174 175 case types.UnsafePointer: 176 return t&(argPointer|argInt) != 0 177 178 case types.UntypedRune: 179 return t&(argInt|argRune) != 0 180 181 case types.UntypedNil: 182 return false 183 184 case types.Invalid: 185 if false { 186 pass.Reportf(arg.Pos(), "printf argument %v has invalid or unknown type", analysisutil.Format(pass.Fset, arg)) 187 } 188 return true // Probably a type check problem. 189 } 190 panic("unreachable") 191 } 192 193 return false 194} 195 196func isConvertibleToString(pass *analysis.Pass, typ types.Type) bool { 197 if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil { 198 // We explicitly don't want untyped nil, which is 199 // convertible to both of the interfaces below, as it 200 // would just panic anyway. 201 return false 202 } 203 if types.ConvertibleTo(typ, errorType) { 204 return true // via .Error() 205 } 206 207 // Does it implement fmt.Stringer? 208 if obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "String"); obj != nil { 209 if fn, ok := obj.(*types.Func); ok { 210 sig := fn.Type().(*types.Signature) 211 if sig.Params().Len() == 0 && 212 sig.Results().Len() == 1 && 213 sig.Results().At(0).Type() == types.Typ[types.String] { 214 return true 215 } 216 } 217 } 218 219 return false 220} 221 222// hasBasicType reports whether x's type is a types.Basic with the given kind. 223func hasBasicType(pass *analysis.Pass, x ast.Expr, kind types.BasicKind) bool { 224 t := pass.TypesInfo.Types[x].Type 225 if t != nil { 226 t = t.Underlying() 227 } 228 b, ok := t.(*types.Basic) 229 return ok && b.Kind() == kind 230} 231 232// matchStructArgType reports whether all the elements of the struct match the expected 233// type. For instance, with "%d" all the elements must be printable with the "%d" format. 234func matchStructArgType(pass *analysis.Pass, t printfArgType, typ *types.Struct, arg ast.Expr, inProgress map[types.Type]bool) bool { 235 for i := 0; i < typ.NumFields(); i++ { 236 typf := typ.Field(i) 237 if !matchArgTypeInternal(pass, t, typf.Type(), arg, inProgress) { 238 return false 239 } 240 if t&argString != 0 && !typf.Exported() && isConvertibleToString(pass, typf.Type()) { 241 // Issue #17798: unexported Stringer or error cannot be properly formatted. 242 return false 243 } 244 } 245 return true 246} 247