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