defval: support hex and octal default values for numeric fields

Update defval.Unmarshal to use base 0 for strconv.ParseInt and strconv.ParseUint, allowing it to handle hex (0x) and octal (0) string representations in dynamic descriptors, matching the flexibility of C++ and Java runtimes.

Change-Id: I43e59ed2c0ab6aebc5554be7de342811b0115f17
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/812620
Reviewed-by: Nicolas Hillegeer <aktau@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Nicolas Hillegeer <aktau@google.com>
diff --git a/internal/encoding/defval/default.go b/internal/encoding/defval/default.go
index 328dc73..079a53d 100644
--- a/internal/encoding/defval/default.go
+++ b/internal/encoding/defval/default.go
@@ -69,19 +69,19 @@
 			}
 		}
 	case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
-		if v, err := strconv.ParseInt(s, 10, 32); err == nil {
+		if v, err := strconv.ParseInt(s, 0, 32); err == nil {
 			return protoreflect.ValueOfInt32(int32(v)), nil, nil
 		}
 	case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
-		if v, err := strconv.ParseInt(s, 10, 64); err == nil {
+		if v, err := strconv.ParseInt(s, 0, 64); err == nil {
 			return protoreflect.ValueOfInt64(int64(v)), nil, nil
 		}
 	case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
-		if v, err := strconv.ParseUint(s, 10, 32); err == nil {
+		if v, err := strconv.ParseUint(s, 0, 32); err == nil {
 			return protoreflect.ValueOfUint32(uint32(v)), nil, nil
 		}
 	case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
-		if v, err := strconv.ParseUint(s, 10, 64); err == nil {
+		if v, err := strconv.ParseUint(s, 0, 64); err == nil {
 			return protoreflect.ValueOfUint64(uint64(v)), nil, nil
 		}
 	case protoreflect.FloatKind, protoreflect.DoubleKind:
diff --git a/internal/encoding/defval/default_test.go b/internal/encoding/defval/default_test.go
index bde8ca2..bcdb9c5 100644
--- a/internal/encoding/defval/default_test.go
+++ b/internal/encoding/defval/default_test.go
@@ -103,3 +103,57 @@
 		})
 	}
 }
+
+func TestUnmarshalNonDecimal(t *testing.T) {
+	V := protoreflect.ValueOf
+	tests := []struct {
+		str  string
+		kind protoreflect.Kind
+		want protoreflect.Value
+	}{
+		{str: "0x10", kind: protoreflect.Int32Kind, want: V(int32(16))},
+		{str: "-0x10", kind: protoreflect.Int32Kind, want: V(int32(-16))},
+		{str: "0x10", kind: protoreflect.Int64Kind, want: V(int64(16))},
+		{str: "-0x10", kind: protoreflect.Int64Kind, want: V(int64(-16))},
+		{str: "0x10", kind: protoreflect.Uint32Kind, want: V(uint32(16))},
+		{str: "0x10", kind: protoreflect.Uint64Kind, want: V(uint64(16))},
+		{str: "020", kind: protoreflect.Int32Kind, want: V(int32(16))},
+		{str: "-020", kind: protoreflect.Int32Kind, want: V(int32(-16))},
+		{str: "020", kind: protoreflect.Int64Kind, want: V(int64(16))},
+		{str: "-020", kind: protoreflect.Int64Kind, want: V(int64(-16))},
+		{str: "020", kind: protoreflect.Uint32Kind, want: V(uint32(16))},
+		{str: "020", kind: protoreflect.Uint64Kind, want: V(uint64(16))},
+		{str: "0x100000000", kind: protoreflect.Int64Kind, want: V(int64(4294967296))},
+		{str: "-0x100000000", kind: protoreflect.Int64Kind, want: V(int64(-4294967296))},
+		{str: "0x100000000", kind: protoreflect.Uint64Kind, want: V(uint64(4294967296))},
+		{str: "040000000000", kind: protoreflect.Int64Kind, want: V(int64(4294967296))},
+		{str: "-040000000000", kind: protoreflect.Int64Kind, want: V(int64(-4294967296))},
+		{str: "040000000000", kind: protoreflect.Uint64Kind, want: V(uint64(4294967296))},
+		// MaxInt32
+		{str: "0x7fffffff", kind: protoreflect.Int32Kind, want: V(int32(2147483647))},
+		{str: "017777777777", kind: protoreflect.Int32Kind, want: V(int32(2147483647))},
+		// MaxInt64
+		{str: "0x7fffffffffffffff", kind: protoreflect.Int64Kind, want: V(int64(9223372036854775807))},
+		{str: "0777777777777777777777", kind: protoreflect.Int64Kind, want: V(int64(9223372036854775807))},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.str+"_"+tt.kind.String(), func(t *testing.T) {
+			gotVal, _, err := defval.Unmarshal(tt.str, tt.kind, nil, defval.Descriptor)
+			if err != nil {
+				t.Fatalf("Unmarshal(%q, %v, Descriptor) failed: %v", tt.str, tt.kind, err)
+			}
+			if !reflect.DeepEqual(gotVal.Interface(), tt.want.Interface()) {
+				t.Errorf("Unmarshal(%q, %v, Descriptor) = %v, want %v", tt.str, tt.kind, gotVal, tt.want)
+			}
+
+			gotVal, _, err = defval.Unmarshal(tt.str, tt.kind, nil, defval.GoTag)
+			if err != nil {
+				t.Fatalf("Unmarshal(%q, %v, GoTag) failed: %v", tt.str, tt.kind, err)
+			}
+			if !reflect.DeepEqual(gotVal.Interface(), tt.want.Interface()) {
+				t.Errorf("Unmarshal(%q, %v, GoTag) = %v, want %v", tt.str, tt.kind, gotVal, tt.want)
+			}
+		})
+	}
+}