diff --git a/pkg/github/params.go b/pkg/github/params.go index 416020abb7..6e62885293 100644 --- a/pkg/github/params.go +++ b/pkg/github/params.go @@ -83,7 +83,14 @@ func toInt(val any) (int, error) { if f != math.Trunc(f) { return 0, fmt.Errorf("non-integer numeric value: %v", f) } - if f > math.MaxInt || f < math.MinInt { + // Compare before converting, against the exact power of two. math.MaxInt is + // 2^(strconv.IntSize-1) - 1, which a float64 cannot represent: on 64-bit + // targets it rounds up to exactly 2^63, so comparing against it lets 2^63 + // through. Converting an out-of-range float64 to int is implementation-defined + // - MinInt on amd64, MaxInt on the targets whose conversion saturates - so the + // bound has to reject the value before the conversion rather than inspect the + // result of it. math.Ldexp gives the exact bound on every target. + if limit := math.Ldexp(1, strconv.IntSize-1); f >= limit || f < -limit { return 0, fmt.Errorf("numeric value out of int range: %v", f) } return int(f), nil diff --git a/pkg/github/params_test.go b/pkg/github/params_test.go index 3c0d7f43d1..fe70a9d1ae 100644 --- a/pkg/github/params_test.go +++ b/pkg/github/params_test.go @@ -376,6 +376,34 @@ func Test_OptionalIntParam(t *testing.T) { expected: 0, expectError: true, }, + { + name: "2^63 string parameter", + params: map[string]any{"count": "9223372036854775808"}, + paramName: "count", + expected: 0, + expectError: true, + }, + { + name: "MaxInt64 string parameter rounds up to 2^63", + params: map[string]any{"count": "9223372036854775807"}, + paramName: "count", + expected: 0, + expectError: true, + }, + { + name: "2^63 float64 parameter", + params: map[string]any{"count": math.Pow(2, 63)}, + paramName: "count", + expected: 0, + expectError: true, + }, + { + name: "MaxInt32 float64 parameter", + params: map[string]any{"count": float64(math.MaxInt32)}, + paramName: "count", + expected: math.MaxInt32, + expectError: false, + }, } for _, tc := range tests {