package printf import ( "go/ast" "go/types" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" ) var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) // matchArgType reports an error if printf verb t is not appropriate // for operand arg. // // typ is used only for recursive calls; external callers must supply nil. // // (Recursion arises from the compound types {map,chan,slice} which // may be printed with %d etc. if that is appropriate for their element // types.) func matchArgType(pass *analysis.Pass, t printfArgType, typ types.Type, arg ast.Expr) bool { return matchArgTypeInternal(pass, t, typ, arg, make(map[types.Type]bool)) } // matchArgTypeInternal is the internal version of matchArgType. It carries a map // remembering what types are in progress so we don't recur when faced with recursive // types or mutually recursive types. func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool { // %v, %T accept any argument type. if t == anyType { return true } if typ == nil { // external call typ = pass.TypesInfo.Types[arg].Type if typ == nil { return true // probably a type check problem } } // %w accepts only errors. if t == argError { return types.ConvertibleTo(typ, errorType) } // If the type implements fmt.Formatter, we have nothing to check. if isFormatter(typ) { return true } // If we can use a string, might arg (dynamically) implement the Stringer or Error interface? if t&argString != 0 && isConvertibleToString(pass, typ) { return true } typ = typ.Underlying() if inProgress[typ] { // We're already looking at this type. The call that started it will take care of it. return true } inProgress[typ] = true switch typ := typ.(type) { case *types.Signature: return t == argPointer case *types.Map: return t == argPointer || // Recur: map[int]int matches %d. (matchArgTypeInternal(pass, t, typ.Key(), arg, inProgress) && matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress)) case *types.Chan: return t&argPointer != 0 case *types.Array: // Same as slice. if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { return true // %s matches []byte } // Recur: []int matches %d. return matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress) case *types.Slice: // Same as array. if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { return true // %s matches []byte } if t == argPointer { return true // %p prints a slice's 0th element } // Recur: []int matches %d. But watch out for // type T []T // If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below. return matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress) case *types.Pointer: // Ugly, but dealing with an edge case: a known pointer to an invalid type, // probably something from a failed import. if typ.Elem().String() == "invalid type" { if false { pass.Reportf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", analysisutil.Format(pass.Fset, arg)) } return true // special case } // If it's actually a pointer with %p, it prints as one. if t == argPointer { return true } under := typ.Elem().Underlying() switch under.(type) { case *types.Struct: // see below case *types.Array: // see below case *types.Slice: // see below case *types.Map: // see below default: // Check whether the rest can print pointers. return t&argPointer != 0 } // If it's a top-level pointer to a struct, array, slice, or // map, that's equivalent in our analysis to whether we can // print the type being pointed to. Pointers in nested levels // are not supported to minimize fmt running into loops. if len(inProgress) > 1 { return false } return matchArgTypeInternal(pass, t, under, arg, inProgress) case *types.Struct: return matchStructArgType(pass, t, typ, arg, inProgress) case *types.Interface: // There's little we can do. // Whether any particular verb is valid depends on the argument. // The user may have reasonable prior knowledge of the contents of the interface. return true case *types.Basic: switch typ.Kind() { case types.UntypedBool, types.Bool: return t&argBool != 0 case types.UntypedInt, types.Int, types.Int8, types.Int16, types.Int32, types.Int64, types.Uint, types.Uint8, types.Uint16, types.Uint32, types.Uint64, types.Uintptr: return t&argInt != 0 case types.UntypedFloat, types.Float32, types.Float64: return t&argFloat != 0 case types.UntypedComplex, types.Complex64, types.Complex128: return t&argComplex != 0 case types.UntypedString, types.String: return t&argString != 0 case types.UnsafePointer: return t&(argPointer|argInt) != 0 case types.UntypedRune: return t&(argInt|argRune) != 0 case types.UntypedNil: return false case types.Invalid: if false { pass.Reportf(arg.Pos(), "printf argument %v has invalid or unknown type", analysisutil.Format(pass.Fset, arg)) } return true // Probably a type check problem. } panic("unreachable") } return false } func isConvertibleToString(pass *analysis.Pass, typ types.Type) bool { if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil { // We explicitly don't want untyped nil, which is // convertible to both of the interfaces below, as it // would just panic anyway. return false } if types.ConvertibleTo(typ, errorType) { return true // via .Error() } // Does it implement fmt.Stringer? if obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "String"); obj != nil { if fn, ok := obj.(*types.Func); ok { sig := fn.Type().(*types.Signature) if sig.Params().Len() == 0 && sig.Results().Len() == 1 && sig.Results().At(0).Type() == types.Typ[types.String] { return true } } } return false } // hasBasicType reports whether x's type is a types.Basic with the given kind. func hasBasicType(pass *analysis.Pass, x ast.Expr, kind types.BasicKind) bool { t := pass.TypesInfo.Types[x].Type if t != nil { t = t.Underlying() } b, ok := t.(*types.Basic) return ok && b.Kind() == kind } // matchStructArgType reports whether all the elements of the struct match the expected // type. For instance, with "%d" all the elements must be printable with the "%d" format. func matchStructArgType(pass *analysis.Pass, t printfArgType, typ *types.Struct, arg ast.Expr, inProgress map[types.Type]bool) bool { for i := 0; i < typ.NumFields(); i++ { typf := typ.Field(i) if !matchArgTypeInternal(pass, t, typf.Type(), arg, inProgress) { return false } if t&argString != 0 && !typf.Exported() && isConvertibleToString(pass, typf.Type()) { // Issue #17798: unexported Stringer or error cannot be properly formatted. return false } } return true }