diff --git a/internal/codeguard/checks/reliability/reliability_go_helpers.go b/internal/codeguard/checks/reliability/reliability_go_helpers.go index b4bf870..9f0ca69 100644 --- a/internal/codeguard/checks/reliability/reliability_go_helpers.go +++ b/internal/codeguard/checks/reliability/reliability_go_helpers.go @@ -4,7 +4,6 @@ import ( "fmt" "go/ast" "go/token" - "math/big" "strings" ) @@ -216,8 +215,7 @@ func isZeroDuration(expr ast.Expr) bool { if n.Kind != token.INT { return false } - value, ok := new(big.Int).SetString(strings.ReplaceAll(n.Value, "_", ""), 0) - return ok && value.Sign() == 0 + return isZeroIntegerLiteral(n.Value) case *ast.CallExpr: // Duration conversions, such as time.Duration(0), preserve zero. return len(n.Args) == 1 && isZeroDuration(n.Args[0]) @@ -232,6 +230,21 @@ func isZeroDuration(expr ast.Expr) bool { return false } +func isZeroIntegerLiteral(literal string) bool { + // Go's parser has already validated the integer literal. Avoid converting it + // to a big.Int: literals come from untrusted repositories and may be millions + // of digits long. In every supported base, a zero contains no nonzero digit. + if len(literal) >= 2 && literal[0] == '0' && strings.ContainsRune("bBoOxX", rune(literal[1])) { + literal = literal[2:] + } + for _, char := range literal { + if char != '0' && char != '_' { + return false + } + } + return true +} + func isHTTPClientType(expr ast.Expr, aliases map[string]struct{}) bool { selector, ok := expr.(*ast.SelectorExpr) if !ok || selector.Sel.Name != "Client" { diff --git a/internal/codeguard/checks/reliability/reliability_go_helpers_test.go b/internal/codeguard/checks/reliability/reliability_go_helpers_test.go new file mode 100644 index 0000000..178ce44 --- /dev/null +++ b/internal/codeguard/checks/reliability/reliability_go_helpers_test.go @@ -0,0 +1,36 @@ +package reliability + +import ( + "strings" + "testing" +) + +func TestIsZeroIntegerLiteral(t *testing.T) { + tests := map[string]bool{ + "0": true, + "0_0": true, + "0b0_0": true, + "0o0_0": true, + "0x0_0": true, + "1": false, + "0b0_1": false, + "0o0_7": false, + "0x0_f": false, + "0X0_A": false, + } + + for literal, want := range tests { + t.Run(literal, func(t *testing.T) { + if got := isZeroIntegerLiteral(literal); got != want { + t.Fatalf("isZeroIntegerLiteral(%q) = %t, want %t", literal, got, want) + } + }) + } +} + +func TestIsZeroIntegerLiteralHandlesHugeUntrustedLiteral(t *testing.T) { + literal := strings.Repeat("9", 4_000_000) + if isZeroIntegerLiteral(literal) { + t.Fatal("large nonzero literal classified as zero") + } +}