| // Copyright 2018 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package printf |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/types" |
| |
| "golang.org/x/tools/go/analysis" |
| "golang.org/x/tools/internal/typeparams" |
| ) |
| |
| var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) |
| |
| // matchArgType reports an error if printf verb t is not appropriate for |
| // operand arg. |
| // |
| // If arg is a type parameter, the verb t must be appropriate for every type in |
| // the type parameter type set. |
| func matchArgType(pass *analysis.Pass, t printfArgType, arg ast.Expr) (reason string, ok bool) { |
| // %v, %T accept any argument type. |
| if t == anyType { |
| return "", true |
| } |
| |
| typ := pass.TypesInfo.Types[arg].Type |
| if typ == nil { |
| return "", true // probably a type check problem |
| } |
| |
| m := &argMatcher{t: t, seen: make(map[types.Type]bool)} |
| ok = m.match(typ, true) |
| return m.reason, ok |
| } |
| |
| // argMatcher recursively matches types against the printfArgType t. |
| // |
| // To short-circuit recursion, it keeps track of types that have already been |
| // matched (or are in the process of being matched) via the seen map. Recursion |
| // arises from the compound types {map,chan,slice} which may be printed with %d |
| // etc. if that is appropriate for their element types, as well as from type |
| // parameters, which are expanded to the constituents of their type set. |
| // |
| // The reason field may be set to report the cause of the mismatch. |
| type argMatcher struct { |
| t printfArgType |
| seen map[types.Type]bool |
| reason string |
| } |
| |
| // match checks if typ matches m's printf arg type. If topLevel is true, typ is |
| // the actual type of the printf arg, for which special rules apply. As a |
| // special case, top level type parameters pass topLevel=true when checking for |
| // matches among the constituents of their type set, as type arguments will |
| // replace the type parameter at compile time. |
| func (m *argMatcher) match(typ types.Type, topLevel bool) bool { |
| // %w accepts only errors. |
| if m.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 m.t&argString != 0 && isConvertibleToString(typ) { |
| return true |
| } |
| |
| if typ, _ := typ.(*typeparams.TypeParam); typ != nil { |
| // Avoid infinite recursion through type parameters. |
| if m.seen[typ] { |
| return true |
| } |
| m.seen[typ] = true |
| terms, err := typeparams.StructuralTerms(typ) |
| if err != nil { |
| return true // invalid type (possibly an empty type set) |
| } |
| |
| if len(terms) == 0 { |
| // No restrictions on the underlying of typ. Type parameters implementing |
| // error, fmt.Formatter, or fmt.Stringer were handled above, and %v and |
| // %T was handled in matchType. We're about to check restrictions the |
| // underlying; if the underlying type is unrestricted there must be an |
| // element of the type set that violates one of the arg type checks |
| // below, so we can safely return false here. |
| |
| if m.t == anyType { // anyType must have already been handled. |
| panic("unexpected printfArgType") |
| } |
| return false |
| } |
| |
| // Only report a reason if typ is the argument type, otherwise it won't |
| // make sense. Note that it is not sufficient to check if topLevel == here, |
| // as type parameters can have a type set consisting of other type |
| // parameters. |
| reportReason := len(m.seen) == 1 |
| |
| for _, term := range terms { |
| if !m.match(term.Type(), topLevel) { |
| if reportReason { |
| if term.Tilde() { |
| m.reason = fmt.Sprintf("contains ~%s", term.Type()) |
| } else { |
| m.reason = fmt.Sprintf("contains %s", term.Type()) |
| } |
| } |
| return false |
| } |
| } |
| return true |
| } |
| |
| typ = typ.Underlying() |
| if m.seen[typ] { |
| // We've already considered typ, or are in the process of considering it. |
| // In case we've already considered typ, it must have been valid (else we |
| // would have stopped matching). In case we're in the process of |
| // considering it, we must avoid infinite recursion. |
| // |
| // There are some pathological cases where returning true here is |
| // incorrect, for example `type R struct { F []R }`, but these are |
| // acceptable false negatives. |
| return true |
| } |
| m.seen[typ] = true |
| |
| switch typ := typ.(type) { |
| case *types.Signature: |
| return m.t == argPointer |
| |
| case *types.Map: |
| if m.t == argPointer { |
| return true |
| } |
| // Recur: map[int]int matches %d. |
| return m.match(typ.Key(), false) && m.match(typ.Elem(), false) |
| |
| case *types.Chan: |
| return m.t&argPointer != 0 |
| |
| case *types.Array: |
| // Same as slice. |
| if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && m.t&argString != 0 { |
| return true // %s matches []byte |
| } |
| // Recur: []int matches %d. |
| return m.match(typ.Elem(), false) |
| |
| case *types.Slice: |
| // Same as array. |
| if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && m.t&argString != 0 { |
| return true // %s matches []byte |
| } |
| if m.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 m.match(typ.Elem(), false) |
| |
| 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() == types.Typ[types.Invalid] { |
| return true // special case |
| } |
| // If it's actually a pointer with %p, it prints as one. |
| if m.t == argPointer { |
| return true |
| } |
| |
| if typeparams.IsTypeParam(typ.Elem()) { |
| return true // We don't know whether the logic below applies. Give up. |
| } |
| |
| 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 m.t&argPointer != 0 |
| } |
| // If it's a top-level pointer to a struct, array, slice, type param, 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 !topLevel { |
| return false |
| } |
| return m.match(under, false) |
| |
| case *types.Struct: |
| // report 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. |
| for i := 0; i < typ.NumFields(); i++ { |
| typf := typ.Field(i) |
| if !m.match(typf.Type(), false) { |
| return false |
| } |
| if m.t&argString != 0 && !typf.Exported() && isConvertibleToString(typf.Type()) { |
| // Issue #17798: unexported Stringer or error cannot be properly formatted. |
| return false |
| } |
| } |
| return true |
| |
| 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 m.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 m.t&argInt != 0 |
| |
| case types.UntypedFloat, |
| types.Float32, |
| types.Float64: |
| return m.t&argFloat != 0 |
| |
| case types.UntypedComplex, |
| types.Complex64, |
| types.Complex128: |
| return m.t&argComplex != 0 |
| |
| case types.UntypedString, |
| types.String: |
| return m.t&argString != 0 |
| |
| case types.UnsafePointer: |
| return m.t&(argPointer|argInt) != 0 |
| |
| case types.UntypedRune: |
| return m.t&(argInt|argRune) != 0 |
| |
| case types.UntypedNil: |
| return false |
| |
| case types.Invalid: |
| return true // Probably a type check problem. |
| } |
| panic("unreachable") |
| } |
| |
| return false |
| } |
| |
| func isConvertibleToString(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 |
| } |