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  }