sa5009.go raw

   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 i := 0; i < T.NumFields(); i++ {
 131  				out = append(out, T.Field(i).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  }
 410