github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa1030/sa1030.go (about) 1 package sa1030 2 3 import ( 4 "fmt" 5 "go/constant" 6 7 "github.com/amarpal/go-tools/analysis/callcheck" 8 "github.com/amarpal/go-tools/analysis/lint" 9 "github.com/amarpal/go-tools/internal/passes/buildir" 10 "github.com/amarpal/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.Documentation{ 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 }