forked from TrueCloudLab/neoneo-go
vm/emit: check big integer size
Fix a bug where big integer could be emitted as 0 without reporting an error. Also, add tests. Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
0394a79ef8
commit
c72b3f2176
3 changed files with 97 additions and 11 deletions
|
@ -84,6 +84,11 @@ func bigInt(w *io.BinWriter, n *big.Int, trySmall bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := stackitem.CheckIntegerSize(n); err != nil {
|
||||||
|
w.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
buf := bigint.ToPreallocatedBytes(n, make([]byte, 0, 32))
|
buf := bigint.ToPreallocatedBytes(n, make([]byte, 0, 32))
|
||||||
if len(buf) == 0 {
|
if len(buf) == 0 {
|
||||||
Opcodes(w, opcode.PUSH0)
|
Opcodes(w, opcode.PUSH0)
|
||||||
|
|
|
@ -86,6 +86,76 @@ func TestEmitInt(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEmitBigInt(t *testing.T) {
|
||||||
|
t.Run("biggest positive number", func(t *testing.T) {
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
bi := big.NewInt(1)
|
||||||
|
bi.Lsh(bi, 255)
|
||||||
|
bi.Sub(bi, big.NewInt(1))
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
require.NotPanics(t, func() { stackitem.NewBigInteger(bi) })
|
||||||
|
|
||||||
|
BigInt(buf.BinWriter, bi)
|
||||||
|
require.NoError(t, buf.Err)
|
||||||
|
|
||||||
|
expected := make([]byte, 33)
|
||||||
|
expected[0] = byte(opcode.PUSHINT256)
|
||||||
|
for i := 1; i < 32; i++ {
|
||||||
|
expected[i] = 0xFF
|
||||||
|
}
|
||||||
|
expected[32] = 0x7F
|
||||||
|
require.Equal(t, expected, buf.Bytes())
|
||||||
|
})
|
||||||
|
t.Run("smallest negative number", func(t *testing.T) {
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
bi := big.NewInt(-1)
|
||||||
|
bi.Lsh(bi, 255)
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
require.NotPanics(t, func() { stackitem.NewBigInteger(bi) })
|
||||||
|
|
||||||
|
BigInt(buf.BinWriter, bi)
|
||||||
|
require.NoError(t, buf.Err)
|
||||||
|
|
||||||
|
expected := make([]byte, 33)
|
||||||
|
expected[0] = byte(opcode.PUSHINT256)
|
||||||
|
expected[32] = 0x80
|
||||||
|
require.Equal(t, expected, buf.Bytes())
|
||||||
|
})
|
||||||
|
t.Run("biggest positive number plus 1", func(t *testing.T) {
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
bi := big.NewInt(1)
|
||||||
|
bi.Lsh(bi, 255)
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
require.Panics(t, func() { stackitem.NewBigInteger(bi) })
|
||||||
|
|
||||||
|
BigInt(buf.BinWriter, bi)
|
||||||
|
require.Error(t, buf.Err)
|
||||||
|
|
||||||
|
t.Run("do not clear previous error", func(t *testing.T) {
|
||||||
|
buf.Reset()
|
||||||
|
expected := errors.New("expected")
|
||||||
|
buf.Err = expected
|
||||||
|
BigInt(buf.BinWriter, bi)
|
||||||
|
require.Equal(t, expected, buf.Err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("smallest negative number minus 1", func(t *testing.T) {
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
bi := big.NewInt(-1)
|
||||||
|
bi.Lsh(bi, 255)
|
||||||
|
bi.Sub(bi, big.NewInt(1))
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
require.Panics(t, func() { stackitem.NewBigInteger(bi) })
|
||||||
|
|
||||||
|
BigInt(buf.BinWriter, bi)
|
||||||
|
require.Error(t, buf.Err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func getSlice(n int) []byte {
|
func getSlice(n int) []byte {
|
||||||
data := make([]byte, n)
|
data := make([]byte, n)
|
||||||
for i := range data {
|
for i := range data {
|
||||||
|
|
|
@ -384,21 +384,32 @@ type BigInteger big.Int
|
||||||
|
|
||||||
// NewBigInteger returns an new BigInteger object.
|
// NewBigInteger returns an new BigInteger object.
|
||||||
func NewBigInteger(value *big.Int) *BigInteger {
|
func NewBigInteger(value *big.Int) *BigInteger {
|
||||||
// There are 2 cases, when `BitLen` differs from actual size:
|
if err := CheckIntegerSize(value); err != nil {
|
||||||
// 1. Positive integer with highest bit on byte boundary = 1.
|
panic(err)
|
||||||
// 2. Negative integer with highest bit on byte boundary = 1
|
|
||||||
// minus some value. (-0x80 -> 0x80, -0x7F -> 0x81, -0x81 -> 0x7FFF).
|
|
||||||
sz := value.BitLen()
|
|
||||||
if sz > MaxBigIntegerSizeBits {
|
|
||||||
panic(errTooBigInteger)
|
|
||||||
} else if sz == MaxBigIntegerSizeBits {
|
|
||||||
if value.Sign() == 1 || value.TrailingZeroBits() != MaxBigIntegerSizeBits-1 {
|
|
||||||
panic(errTooBigInteger)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return (*BigInteger)(value)
|
return (*BigInteger)(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckIntegerSize checks that value size doesn't exceed VM limit for Interer.
|
||||||
|
func CheckIntegerSize(value *big.Int) error {
|
||||||
|
// There are 2 cases, when `BitLen` differs from actual size:
|
||||||
|
// 1. Positive integer with the highest bit on byte boundary = 1.
|
||||||
|
// 2. Negative integer with the highest bit on byte boundary = 1
|
||||||
|
// minus some value. (-0x80 -> 0x80, -0x7F -> 0x81, -0x81 -> 0x7FFF).
|
||||||
|
sz := value.BitLen()
|
||||||
|
// This check is not required, just an optimization for the common case.
|
||||||
|
if sz < MaxBigIntegerSizeBits {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if sz > MaxBigIntegerSizeBits {
|
||||||
|
return errTooBigInteger
|
||||||
|
}
|
||||||
|
if value.Sign() == 1 || value.TrailingZeroBits() != MaxBigIntegerSizeBits-1 {
|
||||||
|
return errTooBigInteger
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Big casts i to the big.Int type.
|
// Big casts i to the big.Int type.
|
||||||
func (i *BigInteger) Big() *big.Int {
|
func (i *BigInteger) Big() *big.Int {
|
||||||
return (*big.Int)(i)
|
return (*big.Int)(i)
|
||||||
|
|
Loading…
Reference in a new issue