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