...

Source file src/golang.org/x/tools/go/analysis/passes/printf/types.go

Documentation: golang.org/x/tools/go/analysis/passes/printf

     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  
     5  package printf
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/types"
    11  
    12  	"golang.org/x/tools/go/analysis"
    13  	"golang.org/x/tools/internal/typeparams"
    14  )
    15  
    16  var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
    17  
    18  // matchArgType reports an error if printf verb t is not appropriate for
    19  // operand arg.
    20  //
    21  // If arg is a type parameter, the verb t must be appropriate for every type in
    22  // the type parameter type set.
    23  func matchArgType(pass *analysis.Pass, t printfArgType, arg ast.Expr) (reason string, ok bool) {
    24  	// %v, %T accept any argument type.
    25  	if t == anyType {
    26  		return "", true
    27  	}
    28  
    29  	typ := pass.TypesInfo.Types[arg].Type
    30  	if typ == nil {
    31  		return "", true // probably a type check problem
    32  	}
    33  
    34  	m := &argMatcher{t: t, seen: make(map[types.Type]bool)}
    35  	ok = m.match(typ, true)
    36  	return m.reason, ok
    37  }
    38  
    39  // argMatcher recursively matches types against the printfArgType t.
    40  //
    41  // To short-circuit recursion, it keeps track of types that have already been
    42  // matched (or are in the process of being matched) via the seen map. Recursion
    43  // arises from the compound types {map,chan,slice} which may be printed with %d
    44  // etc. if that is appropriate for their element types, as well as from type
    45  // parameters, which are expanded to the constituents of their type set.
    46  //
    47  // The reason field may be set to report the cause of the mismatch.
    48  type argMatcher struct {
    49  	t      printfArgType
    50  	seen   map[types.Type]bool
    51  	reason string
    52  }
    53  
    54  // match checks if typ matches m's printf arg type. If topLevel is true, typ is
    55  // the actual type of the printf arg, for which special rules apply. As a
    56  // special case, top level type parameters pass topLevel=true when checking for
    57  // matches among the constituents of their type set, as type arguments will
    58  // replace the type parameter at compile time.
    59  func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
    60  	// %w accepts only errors.
    61  	if m.t == argError {
    62  		return types.ConvertibleTo(typ, errorType)
    63  	}
    64  
    65  	// If the type implements fmt.Formatter, we have nothing to check.
    66  	if isFormatter(typ) {
    67  		return true
    68  	}
    69  
    70  	// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
    71  	if m.t&argString != 0 && isConvertibleToString(typ) {
    72  		return true
    73  	}
    74  
    75  	if typ, _ := typ.(*typeparams.TypeParam); typ != nil {
    76  		// Avoid infinite recursion through type parameters.
    77  		if m.seen[typ] {
    78  			return true
    79  		}
    80  		m.seen[typ] = true
    81  		terms, err := typeparams.StructuralTerms(typ)
    82  		if err != nil {
    83  			return true // invalid type (possibly an empty type set)
    84  		}
    85  
    86  		if len(terms) == 0 {
    87  			// No restrictions on the underlying of typ. Type parameters implementing
    88  			// error, fmt.Formatter, or fmt.Stringer were handled above, and %v and
    89  			// %T was handled in matchType. We're about to check restrictions the
    90  			// underlying; if the underlying type is unrestricted there must be an
    91  			// element of the type set that violates one of the arg type checks
    92  			// below, so we can safely return false here.
    93  
    94  			if m.t == anyType { // anyType must have already been handled.
    95  				panic("unexpected printfArgType")
    96  			}
    97  			return false
    98  		}
    99  
   100  		// Only report a reason if typ is the argument type, otherwise it won't
   101  		// make sense. Note that it is not sufficient to check if topLevel == here,
   102  		// as type parameters can have a type set consisting of other type
   103  		// parameters.
   104  		reportReason := len(m.seen) == 1
   105  
   106  		for _, term := range terms {
   107  			if !m.match(term.Type(), topLevel) {
   108  				if reportReason {
   109  					if term.Tilde() {
   110  						m.reason = fmt.Sprintf("contains ~%s", term.Type())
   111  					} else {
   112  						m.reason = fmt.Sprintf("contains %s", term.Type())
   113  					}
   114  				}
   115  				return false
   116  			}
   117  		}
   118  		return true
   119  	}
   120  
   121  	typ = typ.Underlying()
   122  	if m.seen[typ] {
   123  		// We've already considered typ, or are in the process of considering it.
   124  		// In case we've already considered typ, it must have been valid (else we
   125  		// would have stopped matching). In case we're in the process of
   126  		// considering it, we must avoid infinite recursion.
   127  		//
   128  		// There are some pathological cases where returning true here is
   129  		// incorrect, for example `type R struct { F []R }`, but these are
   130  		// acceptable false negatives.
   131  		return true
   132  	}
   133  	m.seen[typ] = true
   134  
   135  	switch typ := typ.(type) {
   136  	case *types.Signature:
   137  		return m.t == argPointer
   138  
   139  	case *types.Map:
   140  		if m.t == argPointer {
   141  			return true
   142  		}
   143  		// Recur: map[int]int matches %d.
   144  		return m.match(typ.Key(), false) && m.match(typ.Elem(), false)
   145  
   146  	case *types.Chan:
   147  		return m.t&argPointer != 0
   148  
   149  	case *types.Array:
   150  		// Same as slice.
   151  		if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && m.t&argString != 0 {
   152  			return true // %s matches []byte
   153  		}
   154  		// Recur: []int matches %d.
   155  		return m.match(typ.Elem(), false)
   156  
   157  	case *types.Slice:
   158  		// Same as array.
   159  		if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && m.t&argString != 0 {
   160  			return true // %s matches []byte
   161  		}
   162  		if m.t == argPointer {
   163  			return true // %p prints a slice's 0th element
   164  		}
   165  		// Recur: []int matches %d. But watch out for
   166  		//	type T []T
   167  		// If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below.
   168  		return m.match(typ.Elem(), false)
   169  
   170  	case *types.Pointer:
   171  		// Ugly, but dealing with an edge case: a known pointer to an invalid type,
   172  		// probably something from a failed import.
   173  		if typ.Elem() == types.Typ[types.Invalid] {
   174  			return true // special case
   175  		}
   176  		// If it's actually a pointer with %p, it prints as one.
   177  		if m.t == argPointer {
   178  			return true
   179  		}
   180  
   181  		if typeparams.IsTypeParam(typ.Elem()) {
   182  			return true // We don't know whether the logic below applies. Give up.
   183  		}
   184  
   185  		under := typ.Elem().Underlying()
   186  		switch under.(type) {
   187  		case *types.Struct: // see below
   188  		case *types.Array: // see below
   189  		case *types.Slice: // see below
   190  		case *types.Map: // see below
   191  		default:
   192  			// Check whether the rest can print pointers.
   193  			return m.t&argPointer != 0
   194  		}
   195  		// If it's a top-level pointer to a struct, array, slice, type param, or
   196  		// map, that's equivalent in our analysis to whether we can
   197  		// print the type being pointed to. Pointers in nested levels
   198  		// are not supported to minimize fmt running into loops.
   199  		if !topLevel {
   200  			return false
   201  		}
   202  		return m.match(under, false)
   203  
   204  	case *types.Struct:
   205  		// report whether all the elements of the struct match the expected type. For
   206  		// instance, with "%d" all the elements must be printable with the "%d" format.
   207  		for i := 0; i < typ.NumFields(); i++ {
   208  			typf := typ.Field(i)
   209  			if !m.match(typf.Type(), false) {
   210  				return false
   211  			}
   212  			if m.t&argString != 0 && !typf.Exported() && isConvertibleToString(typf.Type()) {
   213  				// Issue #17798: unexported Stringer or error cannot be properly formatted.
   214  				return false
   215  			}
   216  		}
   217  		return true
   218  
   219  	case *types.Interface:
   220  		// There's little we can do.
   221  		// Whether any particular verb is valid depends on the argument.
   222  		// The user may have reasonable prior knowledge of the contents of the interface.
   223  		return true
   224  
   225  	case *types.Basic:
   226  		switch typ.Kind() {
   227  		case types.UntypedBool,
   228  			types.Bool:
   229  			return m.t&argBool != 0
   230  
   231  		case types.UntypedInt,
   232  			types.Int,
   233  			types.Int8,
   234  			types.Int16,
   235  			types.Int32,
   236  			types.Int64,
   237  			types.Uint,
   238  			types.Uint8,
   239  			types.Uint16,
   240  			types.Uint32,
   241  			types.Uint64,
   242  			types.Uintptr:
   243  			return m.t&argInt != 0
   244  
   245  		case types.UntypedFloat,
   246  			types.Float32,
   247  			types.Float64:
   248  			return m.t&argFloat != 0
   249  
   250  		case types.UntypedComplex,
   251  			types.Complex64,
   252  			types.Complex128:
   253  			return m.t&argComplex != 0
   254  
   255  		case types.UntypedString,
   256  			types.String:
   257  			return m.t&argString != 0
   258  
   259  		case types.UnsafePointer:
   260  			return m.t&(argPointer|argInt) != 0
   261  
   262  		case types.UntypedRune:
   263  			return m.t&(argInt|argRune) != 0
   264  
   265  		case types.UntypedNil:
   266  			return false
   267  
   268  		case types.Invalid:
   269  			return true // Probably a type check problem.
   270  		}
   271  		panic("unreachable")
   272  	}
   273  
   274  	return false
   275  }
   276  
   277  func isConvertibleToString(typ types.Type) bool {
   278  	if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil {
   279  		// We explicitly don't want untyped nil, which is
   280  		// convertible to both of the interfaces below, as it
   281  		// would just panic anyway.
   282  		return false
   283  	}
   284  	if types.ConvertibleTo(typ, errorType) {
   285  		return true // via .Error()
   286  	}
   287  
   288  	// Does it implement fmt.Stringer?
   289  	if obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "String"); obj != nil {
   290  		if fn, ok := obj.(*types.Func); ok {
   291  			sig := fn.Type().(*types.Signature)
   292  			if sig.Params().Len() == 0 &&
   293  				sig.Results().Len() == 1 &&
   294  				sig.Results().At(0).Type() == types.Typ[types.String] {
   295  				return true
   296  			}
   297  		}
   298  	}
   299  
   300  	return false
   301  }
   302  

View as plain text