sa1030.go raw

   1  package sa1030
   2  
   3  import (
   4  	"fmt"
   5  	"go/constant"
   6  
   7  	"honnef.co/go/tools/analysis/callcheck"
   8  	"honnef.co/go/tools/analysis/lint"
   9  	"honnef.co/go/tools/internal/passes/buildir"
  10  	"honnef.co/go/tools/knowledge"
  11  
  12  	"golang.org/x/tools/go/analysis"
  13  )
  14  
  15  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  16  	Analyzer: &analysis.Analyzer{
  17  		Name:     "SA1030",
  18  		Requires: []*analysis.Analyzer{buildir.Analyzer},
  19  		Run:      callcheck.Analyzer(rules),
  20  	},
  21  	Doc: &lint.RawDocumentation{
  22  		Title: `Invalid argument in call to a \'strconv\' function`,
  23  		Text: `This check validates the format, number base and bit size arguments of
  24  the various parsing and formatting functions in \'strconv\'.`,
  25  		Since:    "2021.1",
  26  		Severity: lint.SeverityError,
  27  		MergeIf:  lint.MergeIfAny,
  28  	},
  29  })
  30  
  31  var Analyzer = SCAnalyzer.Analyzer
  32  
  33  var rules = map[string]callcheck.Check{
  34  	"strconv.ParseComplex": func(call *callcheck.Call) {
  35  		validateComplexBitSize(call.Args[knowledge.Arg("strconv.ParseComplex.bitSize")])
  36  	},
  37  	"strconv.ParseFloat": func(call *callcheck.Call) {
  38  		validateFloatBitSize(call.Args[knowledge.Arg("strconv.ParseFloat.bitSize")])
  39  	},
  40  	"strconv.ParseInt": func(call *callcheck.Call) {
  41  		validateContinuousBitSize(call.Args[knowledge.Arg("strconv.ParseInt.bitSize")], 0, 64)
  42  		validateIntBaseAllowZero(call.Args[knowledge.Arg("strconv.ParseInt.base")])
  43  	},
  44  	"strconv.ParseUint": func(call *callcheck.Call) {
  45  		validateContinuousBitSize(call.Args[knowledge.Arg("strconv.ParseUint.bitSize")], 0, 64)
  46  		validateIntBaseAllowZero(call.Args[knowledge.Arg("strconv.ParseUint.base")])
  47  	},
  48  
  49  	"strconv.FormatComplex": func(call *callcheck.Call) {
  50  		validateComplexFormat(call.Args[knowledge.Arg("strconv.FormatComplex.fmt")])
  51  		validateComplexBitSize(call.Args[knowledge.Arg("strconv.FormatComplex.bitSize")])
  52  	},
  53  	"strconv.FormatFloat": func(call *callcheck.Call) {
  54  		validateFloatFormat(call.Args[knowledge.Arg("strconv.FormatFloat.fmt")])
  55  		validateFloatBitSize(call.Args[knowledge.Arg("strconv.FormatFloat.bitSize")])
  56  	},
  57  	"strconv.FormatInt": func(call *callcheck.Call) {
  58  		validateIntBase(call.Args[knowledge.Arg("strconv.FormatInt.base")])
  59  	},
  60  	"strconv.FormatUint": func(call *callcheck.Call) {
  61  		validateIntBase(call.Args[knowledge.Arg("strconv.FormatUint.base")])
  62  	},
  63  
  64  	"strconv.AppendFloat": func(call *callcheck.Call) {
  65  		validateFloatFormat(call.Args[knowledge.Arg("strconv.AppendFloat.fmt")])
  66  		validateFloatBitSize(call.Args[knowledge.Arg("strconv.AppendFloat.bitSize")])
  67  	},
  68  	"strconv.AppendInt": func(call *callcheck.Call) {
  69  		validateIntBase(call.Args[knowledge.Arg("strconv.AppendInt.base")])
  70  	},
  71  	"strconv.AppendUint": func(call *callcheck.Call) {
  72  		validateIntBase(call.Args[knowledge.Arg("strconv.AppendUint.base")])
  73  	},
  74  }
  75  
  76  func validateDiscreetBitSize(arg *callcheck.Argument, size1 int, size2 int) {
  77  	if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
  78  		val, _ := constant.Int64Val(c.Value)
  79  		if val != int64(size1) && val != int64(size2) {
  80  			arg.Invalid(fmt.Sprintf("'bitSize' argument is invalid, must be either %d or %d", size1, size2))
  81  		}
  82  	}
  83  }
  84  
  85  func validateComplexBitSize(arg *callcheck.Argument) { validateDiscreetBitSize(arg, 64, 128) }
  86  func validateFloatBitSize(arg *callcheck.Argument)   { validateDiscreetBitSize(arg, 32, 64) }
  87  
  88  func validateContinuousBitSize(arg *callcheck.Argument, min int, max int) {
  89  	if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
  90  		val, _ := constant.Int64Val(c.Value)
  91  		if val < int64(min) || val > int64(max) {
  92  			arg.Invalid(fmt.Sprintf("'bitSize' argument is invalid, must be within %d and %d", min, max))
  93  		}
  94  	}
  95  }
  96  
  97  func validateIntBase(arg *callcheck.Argument) {
  98  	if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
  99  		val, _ := constant.Int64Val(c.Value)
 100  		if val < 2 {
 101  			arg.Invalid("'base' must not be smaller than 2")
 102  		}
 103  		if val > 36 {
 104  			arg.Invalid("'base' must not be larger than 36")
 105  		}
 106  	}
 107  }
 108  
 109  func validateIntBaseAllowZero(arg *callcheck.Argument) {
 110  	if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
 111  		val, _ := constant.Int64Val(c.Value)
 112  		if val < 2 && val != 0 {
 113  			arg.Invalid("'base' must not be smaller than 2, unless it is 0")
 114  		}
 115  		if val > 36 {
 116  			arg.Invalid("'base' must not be larger than 36")
 117  		}
 118  	}
 119  }
 120  
 121  func validateComplexFormat(arg *callcheck.Argument) {
 122  	validateFloatFormat(arg)
 123  }
 124  
 125  func validateFloatFormat(arg *callcheck.Argument) {
 126  	if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
 127  		val, _ := constant.Int64Val(c.Value)
 128  		switch val {
 129  		case 'b', 'e', 'E', 'f', 'g', 'G', 'x', 'X':
 130  		default:
 131  			arg.Invalid(fmt.Sprintf("'fmt' argument is invalid: unknown format %q", val))
 132  		}
 133  	}
 134  }
 135