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
|
||||
}
|
||||
|
||||
if err := stackitem.CheckIntegerSize(n); err != nil {
|
||||
w.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
buf := bigint.ToPreallocatedBytes(n, make([]byte, 0, 32))
|
||||
if len(buf) == 0 {
|
||||
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 {
|
||||
data := make([]byte, n)
|
||||
for i := range data {
|
||||
|
|
|
@ -384,21 +384,32 @@ type BigInteger big.Int
|
|||
|
||||
// NewBigInteger returns an new BigInteger object.
|
||||
func NewBigInteger(value *big.Int) *BigInteger {
|
||||
// There are 2 cases, when `BitLen` differs from actual size:
|
||||
// 1. Positive integer with highest bit on byte boundary = 1.
|
||||
// 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)
|
||||
}
|
||||
if err := CheckIntegerSize(value); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
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.
|
||||
func (i *BigInteger) Big() *big.Int {
|
||||
return (*big.Int)(i)
|
||||
|
|
Loading…
Reference in a new issue