src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa5009.go (9881B)


      1 package sa5009
      2 
      3 import (
      4 	"fmt"
      5 	"go/constant"
      6 	"go/types"
      7 
      8 	"honnef.co/go/tools/analysis/callcheck"
      9 	"honnef.co/go/tools/analysis/lint"
     10 	"honnef.co/go/tools/go/ir"
     11 	"honnef.co/go/tools/go/ir/irutil"
     12 	"honnef.co/go/tools/go/types/typeutil"
     13 	"honnef.co/go/tools/internal/passes/buildir"
     14 	"honnef.co/go/tools/knowledge"
     15 	"honnef.co/go/tools/printf"
     16 
     17 	"golang.org/x/tools/go/analysis"
     18 )
     19 
     20 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     21 	Analyzer: &analysis.Analyzer{
     22 		Name:     "SA5009",
     23 		Requires: []*analysis.Analyzer{buildir.Analyzer},
     24 		Run:      callcheck.Analyzer(rules),
     25 	},
     26 	Doc: &lint.RawDocumentation{
     27 		Title:    `Invalid Printf call`,
     28 		Since:    "2019.2",
     29 		Severity: lint.SeverityError,
     30 		MergeIf:  lint.MergeIfAny,
     31 	},
     32 })
     33 
     34 var Analyzer = SCAnalyzer.Analyzer
     35 
     36 // TODO(dh): detect printf wrappers
     37 var rules = map[string]callcheck.Check{
     38 	"fmt.Errorf":                  func(call *callcheck.Call) { check(call, 0, 1) },
     39 	"fmt.Printf":                  func(call *callcheck.Call) { check(call, 0, 1) },
     40 	"fmt.Sprintf":                 func(call *callcheck.Call) { check(call, 0, 1) },
     41 	"fmt.Fprintf":                 func(call *callcheck.Call) { check(call, 1, 2) },
     42 	"golang.org/x/xerrors.Errorf": func(call *callcheck.Call) { check(call, 0, 1) },
     43 }
     44 
     45 type verbFlag int
     46 
     47 const (
     48 	isInt verbFlag = 1 << iota
     49 	isBool
     50 	isFP
     51 	isString
     52 	isPointer
     53 	// Verbs that accept "pseudo pointers" will sometimes dereference
     54 	// non-nil pointers. For example, %x on a non-nil *struct will print the
     55 	// individual fields, but on a nil pointer it will print the address.
     56 	isPseudoPointer
     57 	isSlice
     58 	isAny
     59 	noRecurse
     60 )
     61 
     62 var verbs = [...]verbFlag{
     63 	'b': isPseudoPointer | isInt | isFP,
     64 	'c': isInt,
     65 	'd': isPseudoPointer | isInt,
     66 	'e': isFP,
     67 	'E': isFP,
     68 	'f': isFP,
     69 	'F': isFP,
     70 	'g': isFP,
     71 	'G': isFP,
     72 	'o': isPseudoPointer | isInt,
     73 	'O': isPseudoPointer | isInt,
     74 	'p': isSlice | isPointer | noRecurse,
     75 	'q': isInt | isString,
     76 	's': isString,
     77 	't': isBool,
     78 	'T': isAny,
     79 	'U': isInt,
     80 	'v': isAny,
     81 	'X': isPseudoPointer | isInt | isFP | isString,
     82 	'x': isPseudoPointer | isInt | isFP | isString,
     83 }
     84 
     85 func check(call *callcheck.Call, fIdx, vIdx int) {
     86 	f := call.Args[fIdx]
     87 	var args []ir.Value
     88 	switch v := call.Args[vIdx].Value.Value.(type) {
     89 	case *ir.Slice:
     90 		var ok bool
     91 		args, ok = irutil.Vararg(v)
     92 		if !ok {
     93 			// We don't know what the actual arguments to the function are
     94 			return
     95 		}
     96 	case *ir.Const:
     97 		// nil, i.e. no arguments
     98 	default:
     99 		// We don't know what the actual arguments to the function are
    100 		return
    101 	}
    102 	checkImpl(f, f.Value.Value, args)
    103 }
    104 
    105 func checkImpl(carg *callcheck.Argument, f ir.Value, args []ir.Value) {
    106 	var msCache *typeutil.MethodSetCache
    107 	if f.Parent() != nil {
    108 		msCache = &f.Parent().Prog.MethodSets
    109 	}
    110 
    111 	elem := func(T types.Type, verb rune) ([]types.Type, bool) {
    112 		if verbs[verb]&noRecurse != 0 {
    113 			return []types.Type{T}, false
    114 		}
    115 		switch T := T.(type) {
    116 		case *types.Slice:
    117 			if verbs[verb]&isSlice != 0 {
    118 				return []types.Type{T}, false
    119 			}
    120 			if verbs[verb]&isString != 0 && types.Identical(T.Elem().Underlying(), types.Typ[types.Byte]) {
    121 				return []types.Type{T}, false
    122 			}
    123 			return []types.Type{T.Elem()}, true
    124 		case *types.Map:
    125 			key := T.Key()
    126 			val := T.Elem()
    127 			return []types.Type{key, val}, true
    128 		case *types.Struct:
    129 			out := make([]types.Type, 0, T.NumFields())
    130 			for field := range T.Fields() {
    131 				out = append(out, field.Type())
    132 			}
    133 			return out, true
    134 		case *types.Array:
    135 			return []types.Type{T.Elem()}, true
    136 		default:
    137 			return []types.Type{T}, false
    138 		}
    139 	}
    140 	isInfo := func(T types.Type, info types.BasicInfo) bool {
    141 		basic, ok := T.Underlying().(*types.Basic)
    142 		return ok && basic.Info()&info != 0
    143 	}
    144 
    145 	isFormatter := func(T types.Type, ms *types.MethodSet) bool {
    146 		sel := ms.Lookup(nil, "Format")
    147 		if sel == nil {
    148 			return false
    149 		}
    150 		fn, ok := sel.Obj().(*types.Func)
    151 		if !ok {
    152 			// should be unreachable
    153 			return false
    154 		}
    155 		sig := fn.Type().(*types.Signature)
    156 		if sig.Params().Len() != 2 {
    157 			return false
    158 		}
    159 		// TODO(dh): check the types of the arguments for more
    160 		// precision
    161 		if sig.Results().Len() != 0 {
    162 			return false
    163 		}
    164 		return true
    165 	}
    166 
    167 	var seen typeutil.Map[struct{}]
    168 	var checkType func(verb rune, T types.Type, top bool) bool
    169 	checkType = func(verb rune, T types.Type, top bool) bool {
    170 		if top {
    171 			seen = typeutil.Map[struct{}]{}
    172 		}
    173 		if _, ok := seen.At(T); ok {
    174 			return true
    175 		}
    176 		seen.Set(T, struct{}{})
    177 		if int(verb) >= len(verbs) {
    178 			// Unknown verb
    179 			return true
    180 		}
    181 
    182 		flags := verbs[verb]
    183 		if flags == 0 {
    184 			// Unknown verb
    185 			return true
    186 		}
    187 
    188 		ms := msCache.MethodSet(T)
    189 		if isFormatter(T, ms) {
    190 			// the value is responsible for formatting itself
    191 			return true
    192 		}
    193 
    194 		if flags&isString != 0 && (types.Implements(T, knowledge.Interfaces["fmt.Stringer"]) || types.Implements(T, knowledge.Interfaces["error"])) {
    195 			// Check for stringer early because we're about to dereference
    196 			return true
    197 		}
    198 
    199 		T = T.Underlying()
    200 		if flags&(isPointer|isPseudoPointer) == 0 && top {
    201 			T = typeutil.Dereference(T)
    202 		}
    203 		if flags&isPseudoPointer != 0 && top {
    204 			t := typeutil.Dereference(T)
    205 			if _, ok := t.Underlying().(*types.Struct); ok {
    206 				T = t
    207 			}
    208 		}
    209 
    210 		if _, ok := T.(*types.Interface); ok {
    211 			// We don't know what's in the interface
    212 			return true
    213 		}
    214 
    215 		var info types.BasicInfo
    216 		if flags&isInt != 0 {
    217 			info |= types.IsInteger
    218 		}
    219 		if flags&isBool != 0 {
    220 			info |= types.IsBoolean
    221 		}
    222 		if flags&isFP != 0 {
    223 			info |= types.IsFloat | types.IsComplex
    224 		}
    225 		if flags&isString != 0 {
    226 			info |= types.IsString
    227 		}
    228 
    229 		if info != 0 && isInfo(T, info) {
    230 			return true
    231 		}
    232 
    233 		if flags&isString != 0 {
    234 			isStringyElem := func(typ types.Type) bool {
    235 				if typ, ok := typ.Underlying().(*types.Basic); ok {
    236 					return typ.Kind() == types.Byte
    237 				}
    238 				return false
    239 			}
    240 			switch T := T.(type) {
    241 			case *types.Slice:
    242 				if isStringyElem(T.Elem()) {
    243 					return true
    244 				}
    245 			case *types.Array:
    246 				if isStringyElem(T.Elem()) {
    247 					return true
    248 				}
    249 			}
    250 			if types.Implements(T, knowledge.Interfaces["fmt.Stringer"]) || types.Implements(T, knowledge.Interfaces["error"]) {
    251 				return true
    252 			}
    253 		}
    254 
    255 		if flags&isPointer != 0 && typeutil.IsPointerLike(T) {
    256 			return true
    257 		}
    258 		if flags&isPseudoPointer != 0 {
    259 			switch U := T.Underlying().(type) {
    260 			case *types.Pointer:
    261 				if !top {
    262 					return true
    263 				}
    264 
    265 				if _, ok := U.Elem().Underlying().(*types.Struct); !ok {
    266 					// TODO(dh): can this condition ever be false? For
    267 					// *T, if T is a struct, we'll already have
    268 					// dereferenced it, meaning the *types.Pointer
    269 					// branch couldn't have been taken. For T that
    270 					// aren't structs, this condition will always
    271 					// evaluate to true.
    272 					return true
    273 				}
    274 			case *types.Chan, *types.Signature:
    275 				// Channels and functions are always treated as
    276 				// pointers and never recursed into.
    277 				return true
    278 			case *types.Basic:
    279 				if U.Kind() == types.UnsafePointer {
    280 					return true
    281 				}
    282 			case *types.Interface:
    283 				// we will already have bailed if the type is an
    284 				// interface.
    285 				panic("unreachable")
    286 			default:
    287 				// other pointer-like types, such as maps or slices,
    288 				// will be printed element-wise.
    289 			}
    290 		}
    291 
    292 		if flags&isSlice != 0 {
    293 			if _, ok := T.(*types.Slice); ok {
    294 				return true
    295 			}
    296 		}
    297 
    298 		if flags&isAny != 0 {
    299 			return true
    300 		}
    301 
    302 		elems, ok := elem(T.Underlying(), verb)
    303 		if !ok {
    304 			return false
    305 		}
    306 		for _, elem := range elems {
    307 			if !checkType(verb, elem, false) {
    308 				return false
    309 			}
    310 		}
    311 
    312 		return true
    313 	}
    314 
    315 	k, ok := irutil.Flatten(f).(*ir.Const)
    316 	if !ok {
    317 		return
    318 	}
    319 	actions, err := printf.Parse(constant.StringVal(k.Value))
    320 	if err != nil {
    321 		carg.Invalid("couldn't parse format string")
    322 		return
    323 	}
    324 
    325 	ptr := 1
    326 	hasExplicit := false
    327 
    328 	checkStar := func(verb printf.Verb, star printf.Argument) bool {
    329 		if star, ok := star.(printf.Star); ok {
    330 			idx := 0
    331 			if star.Index == -1 {
    332 				idx = ptr
    333 				ptr++
    334 			} else {
    335 				hasExplicit = true
    336 				idx = star.Index
    337 				ptr = star.Index + 1
    338 			}
    339 			if idx == 0 {
    340 				carg.Invalid(fmt.Sprintf("Printf format %s reads invalid arg 0; indices are 1-based", verb.Raw))
    341 				return false
    342 			}
    343 			if idx > len(args) {
    344 				carg.Invalid(
    345 					fmt.Sprintf("Printf format %s reads arg #%d, but call has only %d args",
    346 						verb.Raw, idx, len(args)))
    347 				return false
    348 			}
    349 			if arg, ok := args[idx-1].(*ir.MakeInterface); ok {
    350 				if !isInfo(arg.X.Type(), types.IsInteger) {
    351 					carg.Invalid(fmt.Sprintf("Printf format %s reads non-int arg #%d as argument of *", verb.Raw, idx))
    352 				}
    353 			}
    354 		}
    355 		return true
    356 	}
    357 
    358 	// We only report one problem per format string. Making a
    359 	// mistake with an index tends to invalidate all future
    360 	// implicit indices.
    361 	for _, action := range actions {
    362 		verb, ok := action.(printf.Verb)
    363 		if !ok {
    364 			continue
    365 		}
    366 
    367 		if !checkStar(verb, verb.Width) || !checkStar(verb, verb.Precision) {
    368 			return
    369 		}
    370 
    371 		off := ptr
    372 		if verb.Value != -1 {
    373 			hasExplicit = true
    374 			off = verb.Value
    375 		}
    376 		if off > len(args) {
    377 			carg.Invalid(
    378 				fmt.Sprintf("Printf format %s reads arg #%d, but call has only %d args",
    379 					verb.Raw, off, len(args)))
    380 			return
    381 		} else if verb.Value == 0 && verb.Letter != '%' {
    382 			carg.Invalid(fmt.Sprintf("Printf format %s reads invalid arg 0; indices are 1-based", verb.Raw))
    383 			return
    384 		} else if off != 0 {
    385 			arg, ok := args[off-1].(*ir.MakeInterface)
    386 			if ok {
    387 				if !checkType(verb.Letter, arg.X.Type(), true) {
    388 					carg.Invalid(fmt.Sprintf("Printf format %s has arg #%d of wrong type %s",
    389 						verb.Raw, ptr, args[ptr-1].(*ir.MakeInterface).X.Type()))
    390 					return
    391 				}
    392 			}
    393 		}
    394 
    395 		switch verb.Value {
    396 		case -1:
    397 			// Consume next argument
    398 			ptr++
    399 		case 0:
    400 			// Don't consume any arguments
    401 		default:
    402 			ptr = verb.Value + 1
    403 		}
    404 	}
    405 
    406 	if !hasExplicit && ptr <= len(args) {
    407 		carg.Invalid(fmt.Sprintf("Printf call needs %d args but has %d args", ptr-1, len(args)))
    408 	}
    409 }