+++ /dev/null
-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
-}