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