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 {
case reflect.Bool:
return e.encodeBool(rv.Bool())
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:
return e.encodeInt(reflect.Uint, int64(rv.Uint()))
return e.encodeUint(rv.Uint())
case reflect.String:
switch rv.Interface().(type) {
case unicode:
......@@ -434,9 +434,7 @@ func (e *Encoder) encodeFloat(f float64) error {
return e.emitf("%c%g\n", opFloat, f)
}
func (e *Encoder) encodeInt(k reflect.Kind, i int64) error {
// FIXME: need support for 64-bit ints
func (e *Encoder) encodeInt(i int64) error {
// protocol >= 1: BININT*
if e.config.Protocol >= 1 {
switch {
......@@ -457,6 +455,16 @@ func (e *Encoder) encodeInt(k reflect.Kind, i int64) error {
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 {
// TODO if e.protocol >= 2 use opLong1 & opLong4
return e.emitf("%c%dL\n", opLong, b)
......
......@@ -515,10 +515,23 @@ func (d *Decoder) loadInt() error {
val = true
default:
i, err := strconv.ParseInt(string(line), 10, 64)
if err != nil {
return err
if err == nil {
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)
......
......@@ -78,8 +78,8 @@ type TestEntry struct {
// object(s) and []TestPickle. All pickles must decode to objectOut.
// Encoding objectIn at particular protocol must give particular TestPickle.
//
// In the usual case objectIn == objectOut and they can only differ if
// objectIn contains a Go struct.
// In the usual case objectIn == objectOut and they can differ if
// e.g. objectIn contains a Go struct.
objectIn interface{}
picklev []TestPickle
objectOut interface{}
......@@ -123,8 +123,6 @@ func Xustrict(name string, object interface{}, picklev ...TestPickle) TestEntry
}
// 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 {
x := X(name, objectIn, picklev...)
x.objectOut = objectOut
......@@ -224,10 +222,34 @@ var tests = []TestEntry{
P0("I74565\n."), // INT
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),
P0("I-7\n."), // INT
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),
P0("F1.23\n."), // FLOAT
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