mirror of
https://github.com/securego/gosec.git
synced 2024-12-24 11:35:52 +00:00
Improvement the int conversion overflow logic to handle bound checks (#1194)
* add test cases Signed-off-by: czechbol <adamludes@gmail.com> * fix bounds check logic Signed-off-by: czechbol <adamludes@gmail.com> * tweak test cases Signed-off-by: czechbol <adamludes@gmail.com> * fix codestyle Signed-off-by: czechbol <adamludes@gmail.com> * improve bounds check logic Signed-off-by: czechbol <adamludes@gmail.com> * max recursion depth Signed-off-by: czechbol <adamludes@gmail.com> * add test case for len function Signed-off-by: czechbol <adamludes@gmail.com> * relax len function bounds checks Co-authored-by: Ben Krieger <ben.krieger@intel.com> * handle cases when convert instruction is after the if blocks Signed-off-by: czechbol <adamludes@gmail.com> * improve range check discovery, add tests Signed-off-by: czechbol <adamludes@gmail.com> * refactor for readability Signed-off-by: czechbol <adamludes@gmail.com> * add cap function test Signed-off-by: czechbol <adamludes@gmail.com> * calculate signed min without throwing overflow warnings Signed-off-by: czechbol <adamludes@gmail.com> * perform bounds checks int size calculations Signed-off-by: czechbol <adamludes@gmail.com> * basic equal operator logic Signed-off-by: czechbol <adamludes@gmail.com> * uintptr -> unsafe.Pointer test case Signed-off-by: czechbol <adamludes@gmail.com> * fix review comments Signed-off-by: czechbol <adamludes@gmail.com> * Rebase and fix go module Change-Id: I8da6495eaaf25b1739389aa98492bd7df338085b Signed-off-by: Cosmin Cojocar <ccojocar@google.com> * fix false positive for negated value Signed-off-by: czechbol <adamludes@gmail.com> * fix range conditions Signed-off-by: czechbol <adamludes@gmail.com> * Ignore the golangci/gosec G115 warning Change-Id: I0db56cb0a5f9ab6e815e2480ec0b66d7061b23d3 Signed-off-by: Cosmin Cojocar <ccojocar@google.com> --------- Signed-off-by: czechbol <adamludes@gmail.com> Signed-off-by: Cosmin Cojocar <ccojocar@google.com> Co-authored-by: Ben Krieger <ben.krieger@intel.com> Co-authored-by: Cosmin Cojocar <ccojocar@google.com>
This commit is contained in:
parent
ea5b2766bb
commit
eaedce9a8b
5 changed files with 838 additions and 71 deletions
|
@ -17,9 +17,12 @@ package analyzers
|
|||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/buildssa"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
|
@ -27,6 +30,30 @@ import (
|
|||
"github.com/securego/gosec/v2/issue"
|
||||
)
|
||||
|
||||
type integer struct {
|
||||
signed bool
|
||||
size int
|
||||
min int
|
||||
max uint
|
||||
}
|
||||
|
||||
type rangeResult struct {
|
||||
minValue int
|
||||
maxValue uint
|
||||
explixitPositiveVals []uint
|
||||
explicitNegativeVals []int
|
||||
isRangeCheck bool
|
||||
convertFound bool
|
||||
}
|
||||
|
||||
type branchResults struct {
|
||||
minValue *int
|
||||
maxValue *uint
|
||||
explixitPositiveVals []uint
|
||||
explicitNegativeVals []int
|
||||
convertFound bool
|
||||
}
|
||||
|
||||
func newConversionOverflowAnalyzer(id string, description string) *analysis.Analyzer {
|
||||
return &analysis.Analyzer{
|
||||
Name: id,
|
||||
|
@ -50,10 +77,10 @@ func runConversionOverflow(pass *analysis.Pass) (interface{}, error) {
|
|||
case *ssa.Convert:
|
||||
src := instr.X.Type().Underlying().String()
|
||||
dst := instr.Type().Underlying().String()
|
||||
if isIntOverflow(src, dst) {
|
||||
if isSafeConversion(instr) {
|
||||
continue
|
||||
}
|
||||
if isIntOverflow(src, dst) {
|
||||
issue := newIssue(pass.Analyzer.Name,
|
||||
fmt.Sprintf("integer overflow conversion %s -> %s", src, dst),
|
||||
pass.Fset,
|
||||
|
@ -74,23 +101,90 @@ func runConversionOverflow(pass *analysis.Pass) (interface{}, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func isIntOverflow(src string, dst string) bool {
|
||||
srcInt, err := parseIntType(src)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
dstInt, err := parseIntType(dst)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return srcInt.min < dstInt.min || srcInt.max > dstInt.max
|
||||
}
|
||||
|
||||
func parseIntType(intType string) (integer, error) {
|
||||
re := regexp.MustCompile(`^(?P<type>u?int)(?P<size>\d{1,2})?$`)
|
||||
matches := re.FindStringSubmatch(intType)
|
||||
if matches == nil {
|
||||
return integer{}, fmt.Errorf("no integer type match found for %s", intType)
|
||||
}
|
||||
|
||||
it := matches[re.SubexpIndex("type")]
|
||||
is := matches[re.SubexpIndex("size")]
|
||||
|
||||
signed := it == "int"
|
||||
|
||||
// use default system int type in case size is not present in the type.
|
||||
intSize := strconv.IntSize
|
||||
if is != "" {
|
||||
var err error
|
||||
intSize, err = strconv.Atoi(is)
|
||||
if err != nil {
|
||||
return integer{}, fmt.Errorf("failed to parse the integer type size: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if intSize != 8 && intSize != 16 && intSize != 32 && intSize != 64 && is != "" {
|
||||
return integer{}, fmt.Errorf("invalid bit size: %d", intSize)
|
||||
}
|
||||
|
||||
var min int
|
||||
var max uint
|
||||
|
||||
if signed {
|
||||
shiftAmount := intSize - 1
|
||||
|
||||
// Perform a bounds check.
|
||||
if shiftAmount < 0 {
|
||||
return integer{}, fmt.Errorf("invalid shift amount: %d", shiftAmount)
|
||||
}
|
||||
|
||||
max = (1 << uint(shiftAmount)) - 1
|
||||
min = -1 << (intSize - 1)
|
||||
|
||||
} else {
|
||||
max = (1 << uint(intSize)) - 1
|
||||
min = 0
|
||||
}
|
||||
|
||||
return integer{
|
||||
signed: signed,
|
||||
size: intSize,
|
||||
min: min,
|
||||
max: max,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isSafeConversion(instr *ssa.Convert) bool {
|
||||
dstType := instr.Type().Underlying().String()
|
||||
|
||||
// Check for constant conversions
|
||||
// Check for constant conversions.
|
||||
if constVal, ok := instr.X.(*ssa.Const); ok {
|
||||
if isConstantInRange(constVal, dstType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for explicit range checks
|
||||
if hasExplicitRangeCheck(instr) {
|
||||
// Check for string to integer conversions with specified bit size.
|
||||
if isStringToIntConversion(instr, dstType) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for string to integer conversions with specified bit size
|
||||
if isStringToIntConversion(instr, dstType) {
|
||||
// Check for explicit range checks.
|
||||
if hasExplicitRangeCheck(instr, dstType) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -114,22 +208,8 @@ func isConstantInRange(constVal *ssa.Const, dstType string) bool {
|
|||
return value >= 0 && value <= (1<<dstInt.size)-1
|
||||
}
|
||||
|
||||
func hasExplicitRangeCheck(instr *ssa.Convert) bool {
|
||||
block := instr.Block()
|
||||
for _, i := range block.Instrs {
|
||||
if binOp, ok := i.(*ssa.BinOp); ok {
|
||||
// Check if either operand of the BinOp is the result of the Convert instruction
|
||||
if (binOp.X == instr || binOp.Y == instr) &&
|
||||
(binOp.Op == token.LSS || binOp.Op == token.LEQ || binOp.Op == token.GTR || binOp.Op == token.GEQ) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isStringToIntConversion(instr *ssa.Convert, dstType string) bool {
|
||||
// Traverse the SSA instructions to find the original variable
|
||||
// Traverse the SSA instructions to find the original variable.
|
||||
original := instr.X
|
||||
for {
|
||||
switch v := original.(type) {
|
||||
|
@ -162,66 +242,285 @@ func isStringToIntConversion(instr *ssa.Convert, dstType string) bool {
|
|||
}
|
||||
}
|
||||
|
||||
type integer struct {
|
||||
signed bool
|
||||
size int
|
||||
}
|
||||
|
||||
func parseIntType(intType string) (integer, error) {
|
||||
re := regexp.MustCompile(`(?P<type>u?int)(?P<size>\d{1,2})?`)
|
||||
matches := re.FindStringSubmatch(intType)
|
||||
if matches == nil {
|
||||
return integer{}, fmt.Errorf("no integer type match found for %s", intType)
|
||||
}
|
||||
|
||||
it := matches[re.SubexpIndex("type")]
|
||||
is := matches[re.SubexpIndex("size")]
|
||||
|
||||
signed := false
|
||||
if it == "int" {
|
||||
signed = true
|
||||
}
|
||||
|
||||
// use default system int type in case size is not present in the type
|
||||
intSize := strconv.IntSize
|
||||
if is != "" {
|
||||
var err error
|
||||
intSize, err = strconv.Atoi(is)
|
||||
if err != nil {
|
||||
return integer{}, fmt.Errorf("failed to parse the integer type size: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return integer{signed: signed, size: intSize}, nil
|
||||
}
|
||||
|
||||
func isIntOverflow(src string, dst string) bool {
|
||||
srcInt, err := parseIntType(src)
|
||||
func hasExplicitRangeCheck(instr *ssa.Convert, dstType string) bool {
|
||||
dstInt, err := parseIntType(dstType)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
dstInt, err := parseIntType(dst)
|
||||
srcInt, err := parseIntType(instr.X.Type().String())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// converting uint to int of the same size or smaller might lead to overflow
|
||||
if !srcInt.signed && dstInt.signed && dstInt.size <= srcInt.size {
|
||||
return true
|
||||
}
|
||||
// converting uint to unit of a smaller size might lead to overflow
|
||||
if !srcInt.signed && !dstInt.signed && dstInt.size < srcInt.size {
|
||||
return true
|
||||
}
|
||||
// converting int to int of a smaller size might lead to overflow
|
||||
if srcInt.signed && dstInt.signed && dstInt.size < srcInt.size {
|
||||
return true
|
||||
}
|
||||
// converting int to uint of a smaller size might lead to overflow
|
||||
if srcInt.signed && !dstInt.signed && dstInt.size < srcInt.size && srcInt.size-dstInt.size > 8 {
|
||||
minValue := srcInt.min
|
||||
maxValue := srcInt.max
|
||||
explicitPositiveVals := []uint{}
|
||||
explicitNegativeVals := []int{}
|
||||
|
||||
if minValue > dstInt.min && maxValue < dstInt.max {
|
||||
return true
|
||||
}
|
||||
|
||||
visitedIfs := make(map[*ssa.If]bool)
|
||||
for _, block := range instr.Parent().Blocks {
|
||||
for _, blockInstr := range block.Instrs {
|
||||
switch v := blockInstr.(type) {
|
||||
case *ssa.If:
|
||||
result := getResultRange(v, instr, visitedIfs)
|
||||
if result.isRangeCheck {
|
||||
minValue = max(minValue, &result.minValue)
|
||||
maxValue = min(maxValue, &result.maxValue)
|
||||
explicitPositiveVals = append(explicitPositiveVals, result.explixitPositiveVals...)
|
||||
explicitNegativeVals = append(explicitNegativeVals, result.explicitNegativeVals...)
|
||||
}
|
||||
case *ssa.Call:
|
||||
// These function return an int of a guaranteed size.
|
||||
if v != instr.X {
|
||||
continue
|
||||
}
|
||||
if fn, isBuiltin := v.Call.Value.(*ssa.Builtin); isBuiltin {
|
||||
switch fn.Name() {
|
||||
case "len", "cap":
|
||||
minValue = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if explicitValsInRange(explicitPositiveVals, explicitNegativeVals, dstInt) {
|
||||
return true
|
||||
} else if minValue >= dstInt.min && maxValue <= dstInt.max {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getResultRange is a recursive function that walks the branches of the if statement to find the range of the variable.
|
||||
func getResultRange(ifInstr *ssa.If, instr *ssa.Convert, visitedIfs map[*ssa.If]bool) rangeResult {
|
||||
if visitedIfs[ifInstr] {
|
||||
return rangeResult{minValue: math.MinInt, maxValue: math.MaxUint}
|
||||
}
|
||||
visitedIfs[ifInstr] = true
|
||||
|
||||
cond := ifInstr.Cond
|
||||
binOp, ok := cond.(*ssa.BinOp)
|
||||
if !ok || !isRangeCheck(binOp, instr.X) {
|
||||
return rangeResult{minValue: math.MinInt, maxValue: math.MaxUint}
|
||||
}
|
||||
|
||||
result := rangeResult{
|
||||
minValue: math.MinInt,
|
||||
maxValue: math.MaxUint,
|
||||
isRangeCheck: true,
|
||||
}
|
||||
|
||||
thenBounds := walkBranchForConvert(ifInstr.Block().Succs[0], instr, visitedIfs)
|
||||
elseBounds := walkBranchForConvert(ifInstr.Block().Succs[1], instr, visitedIfs)
|
||||
|
||||
updateResultFromBinOp(&result, binOp, instr, thenBounds.convertFound)
|
||||
|
||||
if thenBounds.convertFound {
|
||||
result.convertFound = true
|
||||
result.minValue = max(result.minValue, thenBounds.minValue)
|
||||
result.maxValue = min(result.maxValue, thenBounds.maxValue)
|
||||
result.explixitPositiveVals = append(result.explixitPositiveVals, thenBounds.explixitPositiveVals...)
|
||||
result.explicitNegativeVals = append(result.explicitNegativeVals, thenBounds.explicitNegativeVals...)
|
||||
} else if elseBounds.convertFound {
|
||||
result.convertFound = true
|
||||
result.minValue = max(result.minValue, elseBounds.minValue)
|
||||
result.maxValue = min(result.maxValue, elseBounds.maxValue)
|
||||
result.explixitPositiveVals = append(result.explixitPositiveVals, elseBounds.explixitPositiveVals...)
|
||||
result.explicitNegativeVals = append(result.explicitNegativeVals, elseBounds.explicitNegativeVals...)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// updateResultFromBinOp updates the rangeResult based on the BinOp instruction and the location of the Convert instruction.
|
||||
func updateResultFromBinOp(result *rangeResult, binOp *ssa.BinOp, instr *ssa.Convert, successPathConvert bool) {
|
||||
x, y := binOp.X, binOp.Y
|
||||
operandsFlipped := false
|
||||
|
||||
compareVal, op := getRealValueFromOperation(instr.X)
|
||||
if x != compareVal {
|
||||
y, operandsFlipped = x, true
|
||||
}
|
||||
|
||||
constVal, ok := y.(*ssa.Const)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch binOp.Op {
|
||||
case token.LEQ, token.LSS:
|
||||
updateMinMaxForLessOrEqual(result, constVal, binOp.Op, operandsFlipped, successPathConvert)
|
||||
case token.GEQ, token.GTR:
|
||||
updateMinMaxForGreaterOrEqual(result, constVal, binOp.Op, operandsFlipped, successPathConvert)
|
||||
case token.EQL:
|
||||
if !successPathConvert {
|
||||
break
|
||||
}
|
||||
|
||||
// Determine if the constant value is positive or negative.
|
||||
if strings.Contains(constVal.String(), "-") {
|
||||
result.explicitNegativeVals = append(result.explicitNegativeVals, int(constVal.Int64()))
|
||||
} else {
|
||||
result.explixitPositiveVals = append(result.explixitPositiveVals, uint(constVal.Uint64()))
|
||||
}
|
||||
|
||||
case token.NEQ:
|
||||
if successPathConvert {
|
||||
break
|
||||
}
|
||||
|
||||
// Determine if the constant value is positive or negative.
|
||||
if strings.Contains(constVal.String(), "-") {
|
||||
result.explicitNegativeVals = append(result.explicitNegativeVals, int(constVal.Int64()))
|
||||
} else {
|
||||
result.explixitPositiveVals = append(result.explixitPositiveVals, uint(constVal.Uint64()))
|
||||
}
|
||||
}
|
||||
|
||||
if op == "neg" {
|
||||
min := result.minValue
|
||||
max := result.maxValue
|
||||
|
||||
if min >= 0 {
|
||||
result.maxValue = uint(min)
|
||||
}
|
||||
if max <= math.MaxInt {
|
||||
result.minValue = int(max) //nolint:gosec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateMinMaxForLessOrEqual(result *rangeResult, constVal *ssa.Const, op token.Token, operandsFlipped bool, successPathConvert bool) {
|
||||
// If the success path has a conversion and the operands are not flipped, then the constant value is the maximum value.
|
||||
if successPathConvert && !operandsFlipped {
|
||||
result.maxValue = uint(constVal.Uint64())
|
||||
if op == token.LEQ {
|
||||
result.maxValue--
|
||||
}
|
||||
} else {
|
||||
result.minValue = int(constVal.Int64())
|
||||
if op == token.GTR {
|
||||
result.minValue++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateMinMaxForGreaterOrEqual(result *rangeResult, constVal *ssa.Const, op token.Token, operandsFlipped bool, successPathConvert bool) {
|
||||
// If the success path has a conversion and the operands are not flipped, then the constant value is the minimum value.
|
||||
if successPathConvert && !operandsFlipped {
|
||||
result.minValue = int(constVal.Int64())
|
||||
if op == token.GEQ {
|
||||
result.minValue++
|
||||
}
|
||||
} else {
|
||||
result.maxValue = uint(constVal.Uint64())
|
||||
if op == token.LSS {
|
||||
result.maxValue--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// walkBranchForConvert walks the branch of the if statement to find the range of the variable and where the conversion is.
|
||||
func walkBranchForConvert(block *ssa.BasicBlock, instr *ssa.Convert, visitedIfs map[*ssa.If]bool) branchResults {
|
||||
bounds := branchResults{}
|
||||
|
||||
for _, blockInstr := range block.Instrs {
|
||||
switch v := blockInstr.(type) {
|
||||
case *ssa.If:
|
||||
result := getResultRange(v, instr, visitedIfs)
|
||||
bounds.convertFound = bounds.convertFound || result.convertFound
|
||||
|
||||
if result.isRangeCheck {
|
||||
bounds.minValue = toPtr(max(result.minValue, bounds.minValue))
|
||||
bounds.maxValue = toPtr(min(result.maxValue, bounds.maxValue))
|
||||
}
|
||||
case *ssa.Call:
|
||||
if v == instr.X {
|
||||
if fn, isBuiltin := v.Call.Value.(*ssa.Builtin); isBuiltin && (fn.Name() == "len" || fn.Name() == "cap") {
|
||||
bounds.minValue = toPtr(0)
|
||||
}
|
||||
}
|
||||
case *ssa.Convert:
|
||||
if v == instr {
|
||||
bounds.convertFound = true
|
||||
return bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bounds
|
||||
}
|
||||
|
||||
func isRangeCheck(v ssa.Value, x ssa.Value) bool {
|
||||
compareVal, _ := getRealValueFromOperation(x)
|
||||
|
||||
switch op := v.(type) {
|
||||
case *ssa.BinOp:
|
||||
switch op.Op {
|
||||
case token.LSS, token.LEQ, token.GTR, token.GEQ,
|
||||
token.EQL, token.NEQ:
|
||||
return op.X == compareVal || op.Y == compareVal
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getRealValueFromOperation(v ssa.Value) (ssa.Value, string) {
|
||||
switch v := v.(type) {
|
||||
case *ssa.UnOp:
|
||||
if v.Op == token.SUB {
|
||||
return v.X, "neg"
|
||||
}
|
||||
}
|
||||
return v, ""
|
||||
}
|
||||
|
||||
func explicitValsInRange(explicitPosVals []uint, explicitNegVals []int, dstInt integer) bool {
|
||||
if len(explicitPosVals) == 0 && len(explicitNegVals) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, val := range explicitPosVals {
|
||||
if val > dstInt.max {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, val := range explicitNegVals {
|
||||
if val < dstInt.min {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func min[T constraints.Integer](a T, b *T) T {
|
||||
if b == nil {
|
||||
return a
|
||||
}
|
||||
if a < *b {
|
||||
return a
|
||||
}
|
||||
return *b
|
||||
}
|
||||
|
||||
func max[T constraints.Integer](a T, b *T) T {
|
||||
if b == nil {
|
||||
return a
|
||||
}
|
||||
if a > *b {
|
||||
return a
|
||||
}
|
||||
return *b
|
||||
}
|
||||
|
||||
func toPtr[T any](a T) *T {
|
||||
return &a
|
||||
}
|
||||
|
|
139
analyzers/conversion_overflow_test.go
Normal file
139
analyzers/conversion_overflow_test.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
package analyzers
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("ParseIntType", func() {
|
||||
Context("with valid input", func() {
|
||||
DescribeTable("should correctly parse and calculate bounds for",
|
||||
func(intType string, expectedSigned bool, expectedSize int, expectedMin int, expectedMax uint) {
|
||||
result, err := parseIntType(intType)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.signed).To(Equal(expectedSigned))
|
||||
Expect(result.size).To(Equal(expectedSize))
|
||||
Expect(result.min).To(Equal(expectedMin))
|
||||
Expect(result.max).To(Equal(expectedMax))
|
||||
},
|
||||
Entry("uint8", "uint8", false, 8, 0, uint(math.MaxUint8)),
|
||||
Entry("int8", "int8", true, 8, math.MinInt8, uint(math.MaxInt8)),
|
||||
Entry("uint16", "uint16", false, 16, 0, uint(math.MaxUint16)),
|
||||
Entry("int16", "int16", true, 16, math.MinInt16, uint(math.MaxInt16)),
|
||||
Entry("uint32", "uint32", false, 32, 0, uint(math.MaxUint32)),
|
||||
Entry("int32", "int32", true, 32, math.MinInt32, uint(math.MaxInt32)),
|
||||
Entry("uint64", "uint64", false, 64, 0, uint(math.MaxUint64)),
|
||||
Entry("int64", "int64", true, 64, math.MinInt64, uint(math.MaxInt64)),
|
||||
)
|
||||
|
||||
It("should use system's int size for 'int' and 'uint'", func() {
|
||||
intResult, err := parseIntType("int")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(intResult.size).To(Equal(strconv.IntSize))
|
||||
|
||||
uintResult, err := parseIntType("uint")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(uintResult.size).To(Equal(strconv.IntSize))
|
||||
})
|
||||
})
|
||||
|
||||
Context("with invalid input", func() {
|
||||
DescribeTable("should return an error for",
|
||||
func(intType string, expectedErrorString string) {
|
||||
_, err := parseIntType(intType)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring(expectedErrorString))
|
||||
},
|
||||
Entry("empty string", "", "no integer type match found for "),
|
||||
Entry("invalid type", "float64", "no integer type match found for float64"),
|
||||
Entry("invalid size", "int65", "invalid bit size: 65"),
|
||||
Entry("negative size", "int-8", "no integer type match found for int-8"),
|
||||
Entry("non-numeric size", "intABC", "no integer type match found for intABC"),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("IsIntOverflow", func() {
|
||||
DescribeTable("should correctly identify overflow scenarios on a 64-bit system",
|
||||
func(src string, dst string, expectedOverflow bool) {
|
||||
result := isIntOverflow(src, dst)
|
||||
Expect(result).To(Equal(expectedOverflow))
|
||||
},
|
||||
// Unsigned to Signed conversions
|
||||
Entry("uint8 to int8", "uint8", "int8", true),
|
||||
Entry("uint8 to int16", "uint8", "int16", false),
|
||||
Entry("uint8 to int32", "uint8", "int32", false),
|
||||
Entry("uint8 to int64", "uint8", "int64", false),
|
||||
Entry("uint16 to int8", "uint16", "int8", true),
|
||||
Entry("uint16 to int16", "uint16", "int16", true),
|
||||
Entry("uint16 to int32", "uint16", "int32", false),
|
||||
Entry("uint16 to int64", "uint16", "int64", false),
|
||||
Entry("uint32 to int8", "uint32", "int8", true),
|
||||
Entry("uint32 to int16", "uint32", "int16", true),
|
||||
Entry("uint32 to int32", "uint32", "int32", true),
|
||||
Entry("uint32 to int64", "uint32", "int64", false),
|
||||
Entry("uint64 to int8", "uint64", "int8", true),
|
||||
Entry("uint64 to int16", "uint64", "int16", true),
|
||||
Entry("uint64 to int32", "uint64", "int32", true),
|
||||
Entry("uint64 to int64", "uint64", "int64", true),
|
||||
|
||||
// Unsigned to Unsigned conversions
|
||||
Entry("uint8 to uint16", "uint8", "uint16", false),
|
||||
Entry("uint8 to uint32", "uint8", "uint32", false),
|
||||
Entry("uint8 to uint64", "uint8", "uint64", false),
|
||||
Entry("uint16 to uint8", "uint16", "uint8", true),
|
||||
Entry("uint16 to uint32", "uint16", "uint32", false),
|
||||
Entry("uint16 to uint64", "uint16", "uint64", false),
|
||||
Entry("uint32 to uint8", "uint32", "uint8", true),
|
||||
Entry("uint32 to uint16", "uint32", "uint16", true),
|
||||
Entry("uint32 to uint64", "uint32", "uint64", false),
|
||||
Entry("uint64 to uint8", "uint64", "uint8", true),
|
||||
Entry("uint64 to uint16", "uint64", "uint16", true),
|
||||
Entry("uint64 to uint32", "uint64", "uint32", true),
|
||||
|
||||
// Signed to Unsigned conversions
|
||||
Entry("int8 to uint8", "int8", "uint8", true),
|
||||
Entry("int8 to uint16", "int8", "uint16", true),
|
||||
Entry("int8 to uint32", "int8", "uint32", true),
|
||||
Entry("int8 to uint64", "int8", "uint64", true),
|
||||
Entry("int16 to uint8", "int16", "uint8", true),
|
||||
Entry("int16 to uint16", "int16", "uint16", true),
|
||||
Entry("int16 to uint32", "int16", "uint32", true),
|
||||
Entry("int16 to uint64", "int16", "uint64", true),
|
||||
Entry("int32 to uint8", "int32", "uint8", true),
|
||||
Entry("int32 to uint16", "int32", "uint16", true),
|
||||
Entry("int32 to uint32", "int32", "uint32", true),
|
||||
Entry("int32 to uint64", "int32", "uint64", true),
|
||||
Entry("int64 to uint8", "int64", "uint8", true),
|
||||
Entry("int64 to uint16", "int64", "uint16", true),
|
||||
Entry("int64 to uint32", "int64", "uint32", true),
|
||||
Entry("int64 to uint64", "int64", "uint64", true),
|
||||
|
||||
// Signed to Signed conversions
|
||||
Entry("int8 to int16", "int8", "int16", false),
|
||||
Entry("int8 to int32", "int8", "int32", false),
|
||||
Entry("int8 to int64", "int8", "int64", false),
|
||||
Entry("int16 to int8", "int16", "int8", true),
|
||||
Entry("int16 to int32", "int16", "int32", false),
|
||||
Entry("int16 to int64", "int16", "int64", false),
|
||||
Entry("int32 to int8", "int32", "int8", true),
|
||||
Entry("int32 to int16", "int32", "int16", true),
|
||||
Entry("int32 to int64", "int32", "int64", false),
|
||||
Entry("int64 to int8", "int64", "int8", true),
|
||||
Entry("int64 to int16", "int64", "int16", true),
|
||||
Entry("int64 to int32", "int64", "int32", true),
|
||||
|
||||
// Same type conversions (should never overflow)
|
||||
Entry("uint8 to uint8", "uint8", "uint8", false),
|
||||
Entry("uint16 to uint16", "uint16", "uint16", false),
|
||||
Entry("uint32 to uint32", "uint32", "uint32", false),
|
||||
Entry("uint64 to uint64", "uint64", "uint64", false),
|
||||
Entry("int8 to int8", "int8", "int8", false),
|
||||
Entry("int16 to int16", "int16", "int16", false),
|
||||
Entry("int32 to int32", "int32", "int32", false),
|
||||
Entry("int64 to int64", "int64", "int64", false),
|
||||
)
|
||||
})
|
1
go.mod
1
go.mod
|
@ -11,6 +11,7 @@ require (
|
|||
github.com/onsi/gomega v1.34.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
|
||||
golang.org/x/text v0.17.0
|
||||
golang.org/x/tools v0.24.0
|
||||
|
|
2
go.sum
2
go.sum
|
@ -428,6 +428,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
|
|
@ -287,10 +287,54 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var a int64 = 13
|
||||
a := rand.Int63()
|
||||
if a < math.MinInt32 {
|
||||
panic("out of range")
|
||||
}
|
||||
if a > math.MaxInt32 {
|
||||
panic("out of range")
|
||||
}
|
||||
b := int32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if a < math.MinInt32 && a > math.MaxInt32 {
|
||||
panic("out of range")
|
||||
}
|
||||
b := int32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
`,
|
||||
}, 1, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if a < math.MinInt32 || a > math.MaxInt32 {
|
||||
panic("out of range")
|
||||
}
|
||||
|
@ -330,7 +374,7 @@ import (
|
|||
|
||||
func main() {
|
||||
var a int32 = math.MaxInt32
|
||||
if a < math.MinInt32 || a > math.MaxInt32 {
|
||||
if a < math.MinInt32 && a > math.MaxInt32 {
|
||||
panic("out of range")
|
||||
}
|
||||
var b int64 = int64(a) * 2
|
||||
|
@ -390,4 +434,286 @@ func main() {
|
|||
}
|
||||
`,
|
||||
}, 1, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if a < 0 {
|
||||
panic("out of range")
|
||||
}
|
||||
if a > math.MaxUint32 {
|
||||
panic("out of range")
|
||||
}
|
||||
b := uint32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if a < 0 {
|
||||
panic("out of range")
|
||||
}
|
||||
b := uint32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
`,
|
||||
}, 1, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
func foo(x int) uint32 {
|
||||
if x < 0 {
|
||||
return 0
|
||||
}
|
||||
if x > math.MaxUint32 {
|
||||
return math.MaxUint32
|
||||
}
|
||||
return uint32(x)
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
func foo(items []string) uint32 {
|
||||
x := len(items)
|
||||
if x > math.MaxUint32 {
|
||||
return math.MaxUint32
|
||||
}
|
||||
return uint32(x)
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
func foo(items []string) uint32 {
|
||||
x := cap(items)
|
||||
if x > math.MaxUint32 {
|
||||
return math.MaxUint32
|
||||
}
|
||||
return uint32(x)
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
func foo(items []string) uint32 {
|
||||
x := len(items)
|
||||
if x < math.MaxUint32 {
|
||||
return uint32(x)
|
||||
}
|
||||
return math.MaxUint32
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if a >= math.MinInt32 && a <= math.MaxInt32 {
|
||||
b := int32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
panic("out of range")
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if a >= math.MinInt32 && a <= math.MaxInt32 {
|
||||
b := int32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
panic("out of range")
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if !(a >= math.MinInt32) && a > math.MaxInt32 {
|
||||
b := int32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
panic("out of range")
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if !(a >= math.MinInt32) || a > math.MaxInt32 {
|
||||
panic("out of range")
|
||||
}
|
||||
b := int32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if math.MinInt32 <= a && math.MaxInt32 >= a {
|
||||
b := int32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
panic("out of range")
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if a == 3 || a == 4 {
|
||||
b := int32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
panic("out of range")
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if a != 3 || a != 4 {
|
||||
panic("out of range")
|
||||
}
|
||||
b := int32(a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func main() {
|
||||
i := uintptr(123)
|
||||
p := unsafe.Pointer(i)
|
||||
_ = p
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
{[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := rand.Int63()
|
||||
if a >= 0 {
|
||||
panic("no positivity allowed")
|
||||
}
|
||||
b := uint64(-a)
|
||||
fmt.Printf("%d\n", b)
|
||||
}
|
||||
`,
|
||||
}, 0, gosec.NewConfig()},
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue