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) + } + }) + } +}