Commit 773b7a86 authored by Kirill Smelkov's avatar Kirill Smelkov Committed by Kamil Kisiel

encoder: Fix data corruption when encoding uint64

I discovered that uint64 is wrongly encoded. For example
uint64(0x80000000ffffffff) is encoded as

	"I-9223372032559808513\n."

instead of

	"I9223372041149743103\n."

which results in wrong data to be loaded on Python side:

	In [1]: import pickle

	In [2]: pickle.loads(b"I-9223372032559808513\n.")
	Out[2]: -9223372032559808513

	In [3]: pickle.loads(b"I9223372041149743103\n.")
	Out[3]: 9223372041149743103

Similarly uint64(0xffffffffffffffff) is wrongly encoded as

	"I-1\n."

instead of

	"I18446744073709551615\n."

with the same effect of misinforming peer:

	In [4]: pickle.loads(b"I-1\n.")
	Out[4]: -1

	In [5]: pickle.loads(b"\x80\x02I18446744073709551615\n.")
	Out[5]: 18446744073709551615

in general any uint64 that is greater than max(int64) is encoded
wrongly.

-> Fix that by encoding those integer range properly and with INT
   opcode. Unfortunately there is no BININT* variant to handle integers
   out of int32 range so we need to use text encoding for large
   uint64 similarly to how we already do for large int64.

For symmetry we need to adjust the decoder as well to handle those I...
with numbers out of signed int64 range, because without the adjustment
it fails as e.g.

    --- FAIL: TestDecode/uint(0x80000000ffffffff)/StrictUnicode=n/"I9223372041149743103\n." (0.00s)
        ogorek_test.go:619: strconv.ParseInt: parsing "9223372041149743103": value out of range
        ogorek_test.go:623: decode:
            have: <nil>
            want: 9223372041149743103

Since Python handles such input just fine (see In[3]/Out[3] above) we
should as well. The number cannot be represented as int64 though, so if
the number coming with INT opcode is out of fixed int64 range, we decode
it into big.Int .
parent 85183af0
...@@ -121,9 +121,9 @@ func (e *Encoder) encode(rv reflect.Value) error { ...@@ -121,9 +121,9 @@ func (e *Encoder) encode(rv reflect.Value) error {
case reflect.Bool: case reflect.Bool:
return e.encodeBool(rv.Bool()) return e.encodeBool(rv.Bool())
case reflect.Int, reflect.Int8, reflect.Int64, reflect.Int32, reflect.Int16: case reflect.Int, reflect.Int8, reflect.Int64, reflect.Int32, reflect.Int16:
return e.encodeInt(reflect.Int, rv.Int()) return e.encodeInt(rv.Int())
case reflect.Uint8, reflect.Uint64, reflect.Uint, reflect.Uint32, reflect.Uint16: case reflect.Uint8, reflect.Uint64, reflect.Uint, reflect.Uint32, reflect.Uint16:
return e.encodeInt(reflect.Uint, int64(rv.Uint())) return e.encodeUint(rv.Uint())
case reflect.String: case reflect.String:
switch rv.Interface().(type) { switch rv.Interface().(type) {
case unicode: case unicode:
...@@ -434,9 +434,7 @@ func (e *Encoder) encodeFloat(f float64) error { ...@@ -434,9 +434,7 @@ func (e *Encoder) encodeFloat(f float64) error {
return e.emitf("%c%g\n", opFloat, f) return e.emitf("%c%g\n", opFloat, f)
} }
func (e *Encoder) encodeInt(k reflect.Kind, i int64) error { func (e *Encoder) encodeInt(i int64) error {
// FIXME: need support for 64-bit ints
// protocol >= 1: BININT* // protocol >= 1: BININT*
if e.config.Protocol >= 1 { if e.config.Protocol >= 1 {
switch { switch {
...@@ -457,6 +455,16 @@ func (e *Encoder) encodeInt(k reflect.Kind, i int64) error { ...@@ -457,6 +455,16 @@ func (e *Encoder) encodeInt(k reflect.Kind, i int64) error {
return e.emitf("%c%d\n", opInt, i) return e.emitf("%c%d\n", opInt, i)
} }
func (e *Encoder) encodeUint(u uint64) error {
if u <= math.MaxInt64 {
return e.encodeInt(int64(u))
}
// u > math.MaxInt64 and cannot be represented as int64
// emit it as text INT
return e.emitf("%c%d\n", opInt, u)
}
func (e *Encoder) encodeLong(b *big.Int) error { func (e *Encoder) encodeLong(b *big.Int) error {
// TODO if e.protocol >= 2 use opLong1 & opLong4 // TODO if e.protocol >= 2 use opLong1 & opLong4
return e.emitf("%c%dL\n", opLong, b) return e.emitf("%c%dL\n", opLong, b)
......
...@@ -515,10 +515,23 @@ func (d *Decoder) loadInt() error { ...@@ -515,10 +515,23 @@ func (d *Decoder) loadInt() error {
val = true val = true
default: default:
i, err := strconv.ParseInt(string(line), 10, 64) i, err := strconv.ParseInt(string(line), 10, 64)
if err != nil { if err == nil {
return err val = i
} else {
e := err.(*strconv.NumError)
if e.Err != strconv.ErrRange {
return err
}
// integer that does not fit into int64 -> long
v := new(big.Int)
_, ok := v.SetString(string(line), 10)
if !ok {
// just in case (it should not fail)
return fmt.Errorf("pickle: loadInt: invalid string")
}
val = v
} }
val = i
} }
d.push(val) d.push(val)
......
...@@ -78,8 +78,8 @@ type TestEntry struct { ...@@ -78,8 +78,8 @@ type TestEntry struct {
// object(s) and []TestPickle. All pickles must decode to objectOut. // object(s) and []TestPickle. All pickles must decode to objectOut.
// Encoding objectIn at particular protocol must give particular TestPickle. // Encoding objectIn at particular protocol must give particular TestPickle.
// //
// In the usual case objectIn == objectOut and they can only differ if // In the usual case objectIn == objectOut and they can differ if
// objectIn contains a Go struct. // e.g. objectIn contains a Go struct.
objectIn interface{} objectIn interface{}
picklev []TestPickle picklev []TestPickle
objectOut interface{} objectOut interface{}
...@@ -123,8 +123,6 @@ func Xustrict(name string, object interface{}, picklev ...TestPickle) TestEntry ...@@ -123,8 +123,6 @@ func Xustrict(name string, object interface{}, picklev ...TestPickle) TestEntry
} }
// Xloosy is syntactic sugar to prepare one TestEntry with loosy encoding. // Xloosy is syntactic sugar to prepare one TestEntry with loosy encoding.
//
// It should be used only if objectIn contains Go structs.
func Xloosy(name string, objectIn, objectOut interface{}, picklev ...TestPickle) TestEntry { func Xloosy(name string, objectIn, objectOut interface{}, picklev ...TestPickle) TestEntry {
x := X(name, objectIn, picklev...) x := X(name, objectIn, picklev...)
x.objectOut = objectOut x.objectOut = objectOut
...@@ -224,10 +222,34 @@ var tests = []TestEntry{ ...@@ -224,10 +222,34 @@ var tests = []TestEntry{
P0("I74565\n."), // INT P0("I74565\n."), // INT
P1_("J\x45\x23\x01\x00.")), // BININT P1_("J\x45\x23\x01\x00.")), // BININT
X("int(0x7fffffff)", int64(0x7fffffff),
P0("I2147483647\n."), // INT
P1_("J\xff\xff\xff\x7f.")), // BININT
X("int(-7)", int64(-7), X("int(-7)", int64(-7),
P0("I-7\n."), // INT P0("I-7\n."), // INT
P1_("J\xf9\xff\xff\xff.")), // BININT P1_("J\xf9\xff\xff\xff.")), // BININT
X("int(-0x80000000)", int64(-0x80000000),
P0("I-2147483648\n."), // INT
P1_("J\x00\x00\x00\x80.")), // BININT
X("int(0x1234ffffffff)", int64(0x1234ffffffff),
P0_("I20018842566655\n.")), // INT
X("int(0x7fffffffffffffff)", int64(0x7fffffffffffffff),
P0_("I9223372036854775807\n.")), // INT
Xloosy("uint(0)", uint64(0), int64(0),
P0("I0\n."), // INT
P1_("K\x00.")), // BININT
Xloosy("uint(0x80000000ffffffff)", uint64(0x80000000ffffffff), bigInt("9223372041149743103"),
P0_("I9223372041149743103\n.")), // INT
Xloosy("uint(0xffffffffffffffff)", uint64(0xffffffffffffffff), bigInt("18446744073709551615"),
P0_("I18446744073709551615\n.")), // INT
X("float", float64(1.23), X("float", float64(1.23),
P0("F1.23\n."), // FLOAT P0("F1.23\n."), // FLOAT
P1_("G?\xf3\xae\x14z\xe1G\xae.")), // BINFLOAT P1_("G?\xf3\xae\x14z\xe1G\xae.")), // BINFLOAT
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment