Repository: BurntSushi/toml
Pull request #274 (2021-06-08 04:35:21)

Don't allow leading zeroes in ints and floats
Some tests from toml-test were failing on this:

	toml-test -testdir="../toml-test/tests"          toml-test-decoder
	Test: float-leading-zero-neg (invalid)

	Expected an error, but no error was reported.
	-------------------------------------------------------------------------------
	Test: float-leading-zero-pos (invalid)

	Expected an error, but no error was reported.
	-------------------------------------------------------------------------------
	Test: float-leading-zero (invalid)

	Expected an error, but no error was reported.
	-------------------------------------------------------------------------------
	Test: integer-leading-zero-neg (invalid)

	Expected an error, but no error was reported.
	-------------------------------------------------------------------------------
	Test: integer-leading-zero-pos (invalid)

	Expected an error, but no error was reported.
	-------------------------------------------------------------------------------
	Test: integer-leading-zero (invalid)

	Expected an error, but no error was reported.
	-------------------------------------------------------------------------------

Should modify this later to support hex and binary literals, but at
least this gives a passing suite case to work off.


Patch:
diff --git a/parse.go b/parse.go
index 50869ef..c0be76f 100644
--- a/parse.go
+++ b/parse.go
@@ -183,6 +183,9 @@ func (p *parser) value(it item) (interface{}, tomlType) {
 				it.val)
 		}
 		val := strings.Replace(it.val, "_", "", -1)
+		if numHasLeadingZero(it.val) {
+			p.panicf("Invalid integer %q: cannot have leading zeroes", it.val)
+		}
 		num, err := strconv.ParseInt(val, 10, 64)
 		if err != nil {
 			// Distinguish integer values. Normally, it'd be a bug if the lexer
@@ -214,6 +217,9 @@ func (p *parser) value(it item) (interface{}, tomlType) {
 					"surrounded by digits", it.val)
 			}
 		}
+		if len(parts) > 0 && numHasLeadingZero(parts[0]) {
+			p.panicf("Invalid float %q: cannot have leading zeroes", it.val)
+		}
 		if !numPeriodsOK(it.val) {
 			// As a special case, numbers like '123.' or '1.e2',
 			// which are valid as far as Go/strconv are concerned,
@@ -309,6 +315,18 @@ func (p *parser) value(it item) (interface{}, tomlType) {
 	panic("unreachable")
 }
 
+// numHasLeadingZero checks if this number has leading zeroes, allowing for '0'
+// and +/- signs.
+func numHasLeadingZero(s string) bool {
+	if len(s) > 1 && s[0] == '0' { // >1 to allow "0"
+		return true
+	}
+	if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' {
+		return true
+	}
+	return false
+}
+
 // numUnderscoresOK checks whether each underscore in s is surrounded by
 // characters that are not underscores.
 func numUnderscoresOK(s string) bool {


Test patch:
diff --git a/decode_test.go b/decode_test.go
index 95bc987..79a9d7d 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -514,6 +514,8 @@ func TestDecodeInts(t *testing.T) {
 		want int64
 	}{
 		{"0", 0},
+		{"+0", 0},
+		{"-0", 0},
 		{"+99", 99},
 		{"-10", -10},
 		{"1_234_567", 1234567},
@@ -538,6 +540,8 @@ func TestDecodeFloats(t *testing.T) {
 		s    string
 		want float64
 	}{
+		{"+0.0", 0},
+		{"-0.0", 0},
 		{"+1.0", 1},
 		{"3.1415", 3.1415},
 		{"-0.01", -0.01},
@@ -548,15 +552,16 @@ func TestDecodeFloats(t *testing.T) {
 		{"9_224_617.445_991_228_313", 9224617.445991228313},
 		{"9_876.54_32e1_0", 9876.5432e10},
 	} {
-		var x struct{ N float64 }
-		input := "n = " + tt.s
-		if _, err := Decode(input, &x); err != nil {
-			t.Errorf("Decode(%q): got error: %s", input, err)
-			continue
-		}
-		if x.N != tt.want {
-			t.Errorf("Decode(%q): got %f; want %f", input, x.N, tt.want)
-		}
+		t.Run(tt.s, func(t *testing.T) {
+			var x struct{ N float64 }
+			input := "n = " + tt.s
+			if _, err := Decode(input, &x); err != nil {
+				t.Fatalf("got error: %s", err)
+			}
+			if x.N != tt.want {
+				t.Errorf("got %f; want %f", x.N, tt.want)
+			}
+		})
 	}
 }
 
@@ -576,19 +581,24 @@ func TestDecodeMalformedNumbers(t *testing.T) {
 		{"1e__23", "surrounded by digits"},
 		{"123.", "must be followed by one or more digits"},
 		{"1.e2", "must be followed by one or more digits"},
+		{"01", "cannot have leading zeroes"},
+		{"+01", "cannot have leading zeroes"},
+		{"-01", "cannot have leading zeroes"},
+		{"01.2", "cannot have leading zeroes"},
+		{"-01.2", "cannot have leading zeroes"},
+		{"+01.2", "cannot have leading zeroes"},
 	} {
-		var x struct{ N interface{} }
-		input := "n = " + tt.s
-		_, err := Decode(input, &x)
-		if err == nil {
-			t.Errorf("Decode(%q): got nil, want error containing %q",
-				input, tt.want)
-			continue
-		}
-		if !strings.Contains(err.Error(), tt.want) {
-			t.Errorf("Decode(%q): got %q, want error containing %q",
-				input, err, tt.want)
-		}
+		t.Run(tt.s, func(t *testing.T) {
+			var x struct{ N interface{} }
+			input := "n = " + tt.s
+			_, err := Decode(input, &x)
+			if err == nil {
+				t.Fatalf("got nil, want error containing %q", tt.want)
+			}
+			if !strings.Contains(err.Error(), tt.want) {
+				t.Errorf("got %q, want error containing %q", err, tt.want)
+			}
+		})
 	}
 }