From 3000471a124b3d079faad71676849ddd1ba26241 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 20 Oct 2021 08:49:28 -0400 Subject: [PATCH] parser: improve floats validation (#636) --- decode.go | 90 +++++++++++++++++++++++++++++++++++++++++--- toml_testgen_test.go | 8 ---- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/decode.go b/decode.go index 6d0d9fe..d366f1d 100644 --- a/decode.go +++ b/decode.go @@ -219,7 +219,7 @@ func parseFloat(b []byte) (float64, error) { return math.NaN(), nil } - cleaned, err := checkAndRemoveUnderscores(b) + cleaned, err := checkAndRemoveUnderscoresFloats(b) if err != nil { return 0, err } @@ -232,6 +232,30 @@ func parseFloat(b []byte) (float64, error) { return 0, newDecodeError(b, "float cannot end with a dot") } + dotAlreadySeen := false + for i, c := range cleaned { + if c == '.' { + if dotAlreadySeen { + return 0, newDecodeError(b[i:i+1], "float can have at most one decimal point") + } + if !isDigit(cleaned[i-1]) { + return 0, newDecodeError(b[i-1:i+1], "float decimal point must be preceded by a digit") + } + if !isDigit(cleaned[i+1]) { + return 0, newDecodeError(b[i:i+2], "float decimal point must be followed by a digit") + } + dotAlreadySeen = true + } + } + + start := 0 + if b[0] == '+' || b[0] == '-' { + start = 1 + } + if b[start] == '0' && isDigit(b[start+1]) { + return 0, newDecodeError(b, "float integer part cannot have leading zeroes") + } + f, err := strconv.ParseFloat(string(cleaned), 64) if err != nil { return 0, newDecodeError(b, "unable to parse float: %w", err) @@ -241,7 +265,7 @@ func parseFloat(b []byte) (float64, error) { } func parseIntHex(b []byte) (int64, error) { - cleaned, err := checkAndRemoveUnderscores(b[2:]) + cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:]) if err != nil { return 0, err } @@ -255,7 +279,7 @@ func parseIntHex(b []byte) (int64, error) { } func parseIntOct(b []byte) (int64, error) { - cleaned, err := checkAndRemoveUnderscores(b[2:]) + cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:]) if err != nil { return 0, err } @@ -269,7 +293,7 @@ func parseIntOct(b []byte) (int64, error) { } func parseIntBin(b []byte) (int64, error) { - cleaned, err := checkAndRemoveUnderscores(b[2:]) + cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:]) if err != nil { return 0, err } @@ -283,7 +307,7 @@ func parseIntBin(b []byte) (int64, error) { } func parseIntDec(b []byte) (int64, error) { - cleaned, err := checkAndRemoveUnderscores(b) + cleaned, err := checkAndRemoveUnderscoresIntegers(b) if err != nil { return 0, err } @@ -296,7 +320,7 @@ func parseIntDec(b []byte) (int64, error) { return i, nil } -func checkAndRemoveUnderscores(b []byte) ([]byte, error) { +func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) { if b[0] == '_' { return nil, newDecodeError(b[0:1], "number cannot start with underscore") } @@ -336,6 +360,60 @@ func checkAndRemoveUnderscores(b []byte) ([]byte, error) { return cleaned, nil } +func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) { + if b[0] == '_' { + return nil, newDecodeError(b[0:1], "number cannot start with underscore") + } + + if b[len(b)-1] == '_' { + return nil, newDecodeError(b[len(b)-1:], "number cannot end with underscore") + } + + // fast path + i := 0 + for ; i < len(b); i++ { + if b[i] == '_' { + break + } + } + if i == len(b) { + return b, nil + } + + before := false + cleaned := make([]byte, 0, len(b)) + + for i := 0; i < len(b); i++ { + c := b[i] + + switch c { + case '_': + if !before { + return nil, newDecodeError(b[i-1:i+1], "number must have at least one digit between underscores") + } + before = false + case 'e', 'E': + if i < len(b)-1 && b[i+1] == '_' { + return nil, newDecodeError(b[i+1:i+2], "cannot have underscore after exponent") + } + cleaned = append(cleaned, c) + case '.': + if i < len(b)-1 && b[i+1] == '_' { + return nil, newDecodeError(b[i+1:i+2], "cannot have underscore after decimal point") + } + if i > 0 && b[i-1] == '_' { + return nil, newDecodeError(b[i-1:i], "cannot have underscore before decimal point") + } + cleaned = append(cleaned, c) + default: + before = true + cleaned = append(cleaned, c) + } + } + + return cleaned, nil +} + // isValidDate checks if a provided date is a date that exists. func isValidDate(year int, month int, day int) bool { return day <= daysIn(month, year) diff --git a/toml_testgen_test.go b/toml_testgen_test.go index 6f2f868..72abd38 100644 --- a/toml_testgen_test.go +++ b/toml_testgen_test.go @@ -266,7 +266,6 @@ func TestTOMLTest_Invalid_Float_ExpDoubleUs(t *testing.T) { } func TestTOMLTest_Invalid_Float_ExpLeadingUs(t *testing.T) { - t.Skip("FIXME") input := "exp-leading-us = 1e_23\n" testgenInvalid(t, input) } @@ -277,7 +276,6 @@ func TestTOMLTest_Invalid_Float_ExpPoint1(t *testing.T) { } func TestTOMLTest_Invalid_Float_ExpPoint2(t *testing.T) { - t.Skip("FIXME") input := "exp-point-2 = 1.e2\n" testgenInvalid(t, input) } @@ -308,13 +306,11 @@ func TestTOMLTest_Invalid_Float_Inf_underscore(t *testing.T) { } func TestTOMLTest_Invalid_Float_LeadingPointNeg(t *testing.T) { - t.Skip("FIXME") input := "leading-point-neg = -.12345\n" testgenInvalid(t, input) } func TestTOMLTest_Invalid_Float_LeadingPointPlus(t *testing.T) { - t.Skip("FIXME") input := "leading-point-plus = +.12345\n" testgenInvalid(t, input) } @@ -330,13 +326,11 @@ func TestTOMLTest_Invalid_Float_LeadingUs(t *testing.T) { } func TestTOMLTest_Invalid_Float_LeadingZeroNeg(t *testing.T) { - t.Skip("FIXME") input := "leading-zero-neg = -03.14\n" testgenInvalid(t, input) } func TestTOMLTest_Invalid_Float_LeadingZeroPlus(t *testing.T) { - t.Skip("FIXME") input := "leading-zero-plus = +03.14\n" testgenInvalid(t, input) } @@ -387,13 +381,11 @@ func TestTOMLTest_Invalid_Float_TrailingUs(t *testing.T) { } func TestTOMLTest_Invalid_Float_UsAfterPoint(t *testing.T) { - t.Skip("FIXME") input := "us-after-point = 1._2\n" testgenInvalid(t, input) } func TestTOMLTest_Invalid_Float_UsBeforePoint(t *testing.T) { - t.Skip("FIXME") input := "us-before-point = 1_.2\n" testgenInvalid(t, input) }