proto: add tests for unmarshalling invalid field numbers

This change adds tests for unmarshalling fields with various invalid field
numbers. Our current behavior is that proto.Unmarshal will return an error when
it sees zero and larger than max field numbers and return nil for reserved
ones, which matches the C++ behavior. (Note: depending on which parser helper
in the C++ implementation, one may need to call additional method to check the
result, which we don't have in Go)

Change-Id: I8791fd077f25656107556f5606d55d05c1b4a120
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/191459
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/proto/decode_test.go b/proto/decode_test.go
index e296fc2..b2a3369 100644
--- a/proto/decode_test.go
+++ b/proto/decode_test.go
@@ -134,6 +134,20 @@
 	}
 }
 
+func TestDecodeInvalidFieldNumbers(t *testing.T) {
+	for _, test := range invalidFieldNumberTestProtos {
+		t.Run(test.desc, func(t *testing.T) {
+			decoded := new(testpb.TestAllTypes) // type doesn't matter since we expect errors
+			err := proto.Unmarshal(test.wire, decoded)
+			if err == nil && !test.allowed {
+				t.Error("unmarshal: got nil want error")
+			} else if err != nil && test.allowed {
+				t.Errorf("unmarshal: got %v want nil since %s is allowed by Unmarshal", err, test.desc)
+			}
+		})
+	}
+}
+
 var testProtos = []testProto{
 	{
 		desc: "basic scalar types",
@@ -1663,6 +1677,55 @@
 	},
 }
 
+var invalidFieldNumberTestProtos = []struct {
+	desc    string
+	wire    []byte
+	allowed bool
+}{
+	{
+		desc: "zero",
+		wire: pack.Message{
+			pack.Tag{pack.MinValidNumber - 1, pack.VarintType}, pack.Varint(1001),
+		}.Marshal(),
+	},
+	{
+		desc: "zero and one",
+		wire: pack.Message{
+			pack.Tag{pack.MinValidNumber - 1, pack.VarintType}, pack.Varint(1002),
+			pack.Tag{pack.MinValidNumber, pack.VarintType}, pack.Varint(1003),
+		}.Marshal(),
+	},
+	{
+		desc: "first reserved",
+		wire: pack.Message{
+			pack.Tag{pack.FirstReservedNumber, pack.VarintType}, pack.Varint(1004),
+		}.Marshal(),
+		allowed: true,
+	},
+	{
+		desc: "last reserved",
+		wire: pack.Message{
+			pack.Tag{pack.LastReservedNumber, pack.VarintType}, pack.Varint(1005),
+		}.Marshal(),
+		allowed: true,
+	},
+	{
+		desc: "max and max+1",
+		wire: pack.Message{
+			pack.Tag{pack.MaxValidNumber, pack.VarintType}, pack.Varint(1006),
+			pack.Tag{pack.MaxValidNumber + 1, pack.VarintType}, pack.Varint(1007),
+		}.Marshal(),
+		allowed: flags.ProtoLegacy,
+	},
+	{
+		desc: "max+1",
+		wire: pack.Message{
+			pack.Tag{pack.MaxValidNumber + 1, pack.VarintType}, pack.Varint(1008),
+		}.Marshal(),
+		allowed: flags.ProtoLegacy,
+	},
+}
+
 func build(m proto.Message, opts ...buildOpt) proto.Message {
 	for _, opt := range opts {
 		opt(m)