Added overflow checks after multiplication operation is executed.

This commit is contained in:
Matheus Aguiar 2022-06-20 19:19:20 -03:00
parent c48be40ab8
commit 2282ea5e56
21 changed files with 243 additions and 104 deletions

View File

@ -7,6 +7,7 @@ Language Features:
Compiler Features:
* Code Generator: More efficient overflow checks for multiplication.
Bugfixes:

View File

@ -684,28 +684,46 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
function <functionName>(x, y) -> product {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
let product_raw := mul(x, y)
product := <cleanupFunction>(product_raw)
<?signed>
// overflow, if x > 0, y > 0 and x > (maxValue / y)
if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(<maxValue>, y))) { <panic>() }
// underflow, if x > 0, y < 0 and y < (minValue / x)
if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(<minValue>, x))) { <panic>() }
// underflow, if x < 0, y > 0 and x < (minValue / y)
if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(<minValue>, y))) { <panic>() }
// overflow, if x < 0, y < 0 and x < (maxValue / y)
if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(<maxValue>, y))) { <panic>() }
<?gt128bit>
<?256bit>
// special case
if and(slt(x, 0), eq(y, <minValue>)) { <panic>() }
</256bit>
// overflow, if x != 0 and y != product/x
if iszero(
or(
iszero(x),
eq(y, sdiv(product, x))
)
) { <panic>() }
<!gt128bit>
if iszero(eq(product, product_raw)) { <panic>() }
</gt128bit>
<!signed>
// overflow, if x != 0 and y > (maxValue / x)
if and(iszero(iszero(x)), gt(y, div(<maxValue>, x))) { <panic>() }
<?gt128bit>
// overflow, if x != 0 and y != product/x
if iszero(
or(
iszero(x),
eq(y, div(product, x))
)
) { <panic>() }
<!gt128bit>
if iszero(eq(product, product_raw)) { <panic>() }
</gt128bit>
</signed>
product := mul(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction(PanicCode::UnderOverflow))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("256bit", _type.numBits() == 256)
("gt128bit", _type.numBits() > 128)
.render();
});
}

View File

@ -0,0 +1,46 @@
from opcodes import AND, SDIV, MUL, EQ, ISZERO, OR, SLT
from rule import Rule
from util import BVSignedUpCast, BVSignedMin, BVSignedCleanupFunction
from z3 import BVMulNoOverflow, BVMulNoUnderflow, BitVec, Not, Or
"""
Overflow checked signed integer multiplication.
"""
# Approximation with 16-bit base types.
n_bits = 12
for type_bits in [4, 6, 8, 12]:
rule = Rule()
# Input vars
X_short = BitVec('X', type_bits)
Y_short = BitVec('Y', type_bits)
# Z3's overflow and underflow conditions
actual_overflow = Not(BVMulNoOverflow(X_short, Y_short, True))
actual_underflow = Not(BVMulNoUnderflow(X_short, Y_short))
# cast to full n_bits values
X = BVSignedUpCast(X_short, n_bits)
Y = BVSignedUpCast(Y_short, n_bits)
product_raw = MUL(X, Y)
#remove any overflown bits
product = BVSignedCleanupFunction(product_raw, type_bits)
# Constants
min_value = BVSignedMin(type_bits, n_bits)
# Overflow and underflow checks in YulUtilFunction::overflowCheckedIntMulFunction
if type_bits > n_bits / 2:
sol_overflow_check_1 = ISZERO(OR(ISZERO(X), EQ(Y, SDIV(product, X))))
if type_bits == n_bits:
sol_overflow_check_2 = AND(SLT(X, 0), EQ(Y, min_value))
sol_overflow_check = Or(sol_overflow_check_1 != 0, sol_overflow_check_2 != 0)
else:
sol_overflow_check = (sol_overflow_check_1 != 0)
else:
sol_overflow_check = (ISZERO(EQ(product, product_raw)) != 0)
rule.check(Or(actual_overflow, actual_underflow), sol_overflow_check)

View File

@ -1,43 +0,0 @@
from opcodes import AND, DIV, GT, SDIV, SGT, SLT
from rule import Rule
from util import BVSignedMax, BVSignedMin, BVSignedUpCast
from z3 import BVMulNoOverflow, BVMulNoUnderflow, BitVec, Not, Or
"""
Overflow checked signed integer multiplication.
"""
# Approximation with 16-bit base types.
n_bits = 16
type_bits = 8
while type_bits <= n_bits:
rule = Rule()
# Input vars
X_short = BitVec('X', type_bits)
Y_short = BitVec('Y', type_bits)
# Z3's overflow and underflow conditions
actual_overflow = Not(BVMulNoOverflow(X_short, Y_short, True))
actual_underflow = Not(BVMulNoUnderflow(X_short, Y_short))
# cast to full n_bits values
X = BVSignedUpCast(X_short, n_bits)
Y = BVSignedUpCast(Y_short, n_bits)
# Constants
maxValue = BVSignedMax(type_bits, n_bits)
minValue = BVSignedMin(type_bits, n_bits)
# Overflow and underflow checks in YulUtilFunction::overflowCheckedIntMulFunction
overflow_check_1 = AND(AND(SGT(X, 0), SGT(Y, 0)), GT(X, DIV(maxValue, Y)))
underflow_check_1 = AND(AND(SGT(X, 0), SLT(Y, 0)), SLT(Y, SDIV(minValue, X)))
underflow_check_2 = AND(AND(SLT(X, 0), SGT(Y, 0)), SLT(X, SDIV(minValue, Y)))
overflow_check_2 = AND(AND(SLT(X, 0), SLT(Y, 0)), SLT(X, SDIV(maxValue, Y)))
rule.check(actual_overflow, Or(overflow_check_1 != 0, overflow_check_2 != 0))
rule.check(actual_underflow, Or(underflow_check_1 != 0, underflow_check_2 != 0))
type_bits *= 2

View File

@ -1,6 +1,6 @@
from opcodes import AND, ISZERO, GT, DIV
from opcodes import ISZERO, DIV, MUL, EQ, OR
from rule import Rule
from util import BVUnsignedUpCast, BVUnsignedMax
from util import BVUnsignedUpCast, BVUnsignedCleanupFunction
from z3 import BitVec, Not, BVMulNoOverflow
"""
@ -8,10 +8,9 @@ Overflow checked unsigned integer multiplication.
"""
# Approximation with 16-bit base types.
n_bits = 16
type_bits = 8
n_bits = 12
while type_bits <= n_bits:
for type_bits in [4, 6, 8, 12]:
rule = Rule()
@ -25,13 +24,14 @@ while type_bits <= n_bits:
# cast to full n_bits values
X = BVUnsignedUpCast(X_short, n_bits)
Y = BVUnsignedUpCast(Y_short, n_bits)
product_raw = MUL(X, Y)
#remove any overflown bits
product = BVUnsignedCleanupFunction(product_raw, type_bits)
# Constants
maxValue = BVUnsignedMax(type_bits, n_bits)
# Overflow check in YulUtilFunction::overflowCheckedIntMulFunction
overflow_check = AND(ISZERO(ISZERO(X)), GT(Y, DIV(maxValue, X)))
# Overflow check in YulUtilFunction::overflowCheckedIntMulFunctions
if type_bits > n_bits / 2:
overflow_check = ISZERO(OR(ISZERO(X), EQ(Y, DIV(product, X))))
else:
overflow_check = ISZERO(EQ(product, product_raw))
rule.check(overflow_check != 0, actual_overflow)
type_bits *= 2

View File

@ -0,0 +1,41 @@
from opcodes import SIGNEXTEND
from rule import Rule
from util import BVSignedCleanupFunction, BVSignedUpCast
from z3 import BitVec, BitVecVal, Concat
"""
Overflow checked signed integer multiplication.
"""
n_bits = 256
# Check that YulUtilFunction::cleanupFunction cleanup matches BVSignedCleanupFunction
for type_bits in range(8,256,8):
rule = Rule()
# Input vars
X = BitVec('X', n_bits)
arg = BitVecVal(type_bits / 8 - 1, n_bits)
cleaned_reference = BVSignedCleanupFunction(X, type_bits)
cleaned = SIGNEXTEND(arg, X)
rule.check(cleaned, cleaned_reference)
# Check that BVSignedCleanupFunction properly cleans up values.
for type_bits in range(8,256,8):
rule = Rule()
# Input vars
X_short = BitVec('X', type_bits)
dirt = BitVec('dirt', n_bits - type_bits)
X = BVSignedUpCast(X_short, n_bits)
X_dirty = Concat(dirt, X_short)
X_cleaned = BVSignedCleanupFunction(X_dirty, type_bits)
rule.check(X, X_cleaned)

View File

@ -0,0 +1,40 @@
from opcodes import AND
from rule import Rule
from util import BVUnsignedCleanupFunction, BVUnsignedUpCast
from z3 import BitVec, BitVecVal, Concat
"""
Overflow checked unsigned integer multiplication.
"""
n_bits = 256
# Check that YulUtilFunction::cleanupFunction cleanup matches BVUnsignedCleanupFunction
for type_bits in range(8,256,8):
rule = Rule()
# Input vars
X = BitVec('X', n_bits)
mask = BitVecVal((1 << type_bits) - 1, n_bits)
cleaned_reference = BVUnsignedCleanupFunction(X, type_bits)
cleaned = AND(X, mask)
rule.check(cleaned, cleaned_reference)
# Check that BVUnsignedCleanupFunction properly cleans up values.
for type_bits in range(8,256,8):
rule = Rule()
# Input vars
X_short = BitVec('X', type_bits)
dirt = BitVec('dirt', n_bits - type_bits)
X = BVUnsignedUpCast(X_short, n_bits)
X_dirty = Concat(dirt, X_short)
X_cleaned = BVUnsignedCleanupFunction(X_dirty, type_bits)
rule.check(X, X_cleaned)

View File

@ -25,3 +25,18 @@ def BVSignedMax(type_bits, n_bits):
def BVSignedMin(type_bits, n_bits):
assert type_bits <= n_bits
return BitVecVal(-(1 << (type_bits - 1)), n_bits)
def BVSignedCleanupFunction(x, type_bits):
assert x.size() >= type_bits
sign_mask = BitVecVal(1, x.size()) << (type_bits - 1)
bit_mask = (BitVecVal(1, x.size()) << type_bits) - 1
return If(
x & sign_mask == 0,
x & bit_mask,
x | ~bit_mask
)
def BVUnsignedCleanupFunction(x, type_bits):
assert x.size() >= type_bits
bit_mask = (BitVecVal(1, x.size()) << type_bits) - 1
return x & bit_mask

View File

@ -18,10 +18,10 @@ contract C {
// EVMVersion: >homestead
// ----
// h(uint256[2][]): 0x20, 3, 123, 124, 223, 224, 323, 324 -> 32, 256, 0x20, 3, 123, 124, 223, 224, 323, 324
// gas irOptimized: 180726
// gas irOptimized: 180829
// gas legacy: 184921
// gas legacyOptimized: 181506
// i(uint256[2][2]): 123, 124, 223, 224 -> 32, 128, 123, 124, 223, 224
// gas irOptimized: 112453
// gas irOptimized: 112464
// gas legacy: 115460
// gas legacyOptimized: 112990

View File

@ -35,12 +35,12 @@ contract c {
}
// ----
// test() -> 0x02000202
// gas irOptimized: 4649903
// gas legacy: 4578320
// gas legacyOptimized: 4548312
// gas irOptimized: 4649835
// gas legacy: 4578446
// gas legacyOptimized: 4548309
// storageEmpty -> 1
// clear() -> 0, 0
// gas irOptimized: 4477229
// gas irOptimized: 4477223
// gas legacy: 4410748
// gas legacyOptimized: 4382489
// storageEmpty -> 1

View File

@ -13,6 +13,6 @@ contract c {
// ----
// test(uint256[2][]): 32, 3, 7, 8, 9, 10, 11, 12 -> 10
// gas irOptimized: 689768
// gas irOptimized: 689759
// gas legacy: 686268
// gas legacyOptimized: 685688

View File

@ -17,7 +17,7 @@ contract c {
}
// ----
// test() -> 4, 5
// gas irOptimized: 238692
// gas irOptimized: 238623
// gas legacy: 238736
// gas legacyOptimized: 237159
// storageEmpty -> 1

View File

@ -19,6 +19,6 @@ contract c {
// compileToEwasm: also
// ----
// test() -> 0xffffffff, 0x0000000000000000000000000a00090008000700060005000400030002000100, 0x0000000000000000000000000000000000000000000000000000000000000000
// gas irOptimized: 124817
// gas legacy: 186028
// gas legacyOptimized: 165692
// gas irOptimized: 124910
// gas legacy: 187414
// gas legacyOptimized: 165659

View File

@ -42,6 +42,6 @@ contract c {
// test2(uint256[][2]): 0x20, 0x40, 0x40, 2, 23, 42 -> 2, 65
// gas irOptimized: 157567
// test3(uint256[2][]): 0x20, 2, 23, 42, 23, 42 -> 2, 65
// gas irOptimized: 134633
// gas irOptimized: 134644
// test4(uint256[2][2]): 23, 42, 23, 42 -> 65
// gas irOptimized: 111271

View File

@ -17,4 +17,4 @@ contract C {
// compileViaYul: true
// ----
// f((uint128,uint64,uint128)[]): 0x20, 3, 0, 0, 12, 0, 11, 0, 10, 0, 0 -> 10, 11, 12
// gas irOptimized: 119740
// gas irOptimized: 119737

View File

@ -33,9 +33,9 @@ contract test {
// EVMVersion: >=constantinople
// ----
// constructor()
// gas irOptimized: 441142
// gas legacy: 755907
// gas legacyOptimized: 538354
// gas irOptimized: 438352
// gas legacy: 750723
// gas legacyOptimized: 536620
// encode_inline_asm(bytes): 0x20, 0 -> 0x20, 0
// encode_inline_asm(bytes): 0x20, 1, "f" -> 0x20, 4, "Zg=="
// encode_inline_asm(bytes): 0x20, 2, "fo" -> 0x20, 4, "Zm8="
@ -51,10 +51,10 @@ contract test {
// encode_no_asm(bytes): 0x20, 5, "fooba" -> 0x20, 8, "Zm9vYmE="
// encode_no_asm(bytes): 0x20, 6, "foobar" -> 0x20, 8, "Zm9vYmFy"
// encode_inline_asm_large()
// gas irOptimized: 1382042
// gas legacy: 1646033
// gas legacyOptimized: 1206033
// gas irOptimized: 1387042
// gas legacy: 1688033
// gas legacyOptimized: 1205033
// encode_no_asm_large()
// gas irOptimized: 3311099
// gas legacy: 4723077
// gas legacyOptimized: 2909077
// gas irOptimized: 3316099
// gas legacy: 4765077
// gas legacyOptimized: 2908077

View File

@ -33,10 +33,10 @@ contract test {
}
// ----
// constructor()
// gas irOptimized: 422763
// gas legacy: 654526
// gas legacyOptimized: 474842
// gas irOptimized: 430305
// gas legacy: 649335
// gas legacyOptimized: 473132
// prb_pi() -> 3141592656369545286
// gas irOptimized: 57478
// gas legacy: 98903
// gas legacy: 103112
// gas legacyOptimized: 75735

View File

@ -297,5 +297,5 @@ contract Test {
// verifyTx() -> true
// ~ emit Verified(string): 0x20, 0x16, "Successfully verified."
// gas irOptimized: 95261
// gas legacy: 113239
// gas legacy: 116473
// gas legacyOptimized: 83670

View File

@ -49,9 +49,9 @@ contract test {
}
// ----
// constructor()
// gas irOptimized: 675980
// gas legacy: 1101298
// gas legacyOptimized: 743666
// gas irOptimized: 670586
// gas legacy: 1096108
// gas legacyOptimized: 741962
// toSlice(string): 0x20, 11, "hello world" -> 11, 0xa0
// gas irOptimized: 22660
// gas legacy: 23190
@ -69,6 +69,6 @@ contract test {
// gas legacy: 31621
// gas legacyOptimized: 27914
// benchmark(string,bytes32): 0x40, 0x0842021, 8, "solidity" -> 0x2020
// gas irOptimized: 2017767
// gas legacy: 4294510
// gas legacyOptimized: 2327982
// gas irOptimized: 2017770
// gas legacy: 4294552
// gas legacyOptimized: 2327981

View File

@ -21,6 +21,6 @@ contract A {
// EVMVersion: >=constantinople
// ----
// f(), 10 ether -> 3007, 3008, 3009
// gas irOptimized: 268645
// gas legacy: 402016
// gas legacyOptimized: 288087
// gas irOptimized: 255997
// gas legacy: 387712
// gas legacyOptimized: 283266

View File

@ -5,6 +5,9 @@ contract C {
function g(int8 a, int8 b) public pure returns (int8 x) {
x = a * b;
}
function h(int160 a, int160 b) public pure returns (int160 x) {
x = a * b;
}
}
// ====
// compileToEwasm: also
@ -15,6 +18,9 @@ contract C {
// f(int256,int256): 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE
// f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, 2 -> FAILURE, hex"4e487b71", 0x11
// f(int256,int256): 2, 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE
// f(int256,int256): 2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"4e487b71", 0x11
// f(int256,int256): -1, 0x8000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"4e487b71", 0x11
// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000000, -1 -> FAILURE, hex"4e487b71", 0x11
// f(int256,int256): 2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"4e487b71", 0x11 # positive, negative #
// f(int256,int256): 2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"4e487b71", 0x11 # positive, negative #
// f(int256,int256): 2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"4e487b71", 0x11 # positive, negative #
@ -61,3 +67,18 @@ contract C {
// g(int8,int8): -64, -2 -> FAILURE, hex"4e487b71", 0x11
// g(int8,int8): -2, -63 -> 126
// g(int8,int8): -2, -64 -> FAILURE, hex"4e487b71", 0x11
// h(int160,int160): -1, 1 -> -1
// h(int160,int160): 1, -1 -> -1
// h(int160,int160): -1, 2 -> -2
// h(int160,int160): 2, -1 -> -2
// h(int160,int160): -1, 0xFFFFFFFFFFFFFFFFFFFFFFFF8000000000000000000000000000000000000000 -> FAILURE, hex"4e487b71", 0x11
// h(int160,int160): -1, 0xFFFFFFFFFFFFFFFFFFFFFFFF8000000000000000000000000000000000000000 -> FAILURE, hex"4e487b71", 0x11
// h(int160,int160): 0xFFFFFFFFFFFFFFFFFFFFFFFF8000000000000000000000000000000000000000, -1 -> FAILURE, hex"4e487b71", 0x11
// h(int160,int160): 0x0000000000000000000000004000000000000000000000000000000000000000, -2 -> 0xFFFFFFFFFFFFFFFFFFFFFFFF8000000000000000000000000000000000000000
// h(int160,int160): -2, 0x0000000000000000000000004000000000000000000000000000000000000000 -> 0xFFFFFFFFFFFFFFFFFFFFFFFF8000000000000000000000000000000000000000
// h(int160,int160): -2, 0x0000000000000000000000004000000000000000000000000000000000000001 -> FAILURE, hex"4e487b71", 0x11
// h(int160,int160): 0x0000000000000000000000004000000000000000000000000000000000000001, -2 -> FAILURE, hex"4e487b71", 0x11
// h(int160,int160): 0x0000000000000000000000004000000000000000000000000000000000000001, 2 -> FAILURE, hex"4e487b71", 0x11
// h(int160,int160): 2, 0x0000000000000000000000004000000000000000000000000000000000000001 -> FAILURE, hex"4e487b71", 0x11
// h(int160,int160): 0x0000000000000000000000003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> 0x0000000000000000000000007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE
// h(int160,int160): 2, 0x0000000000000000000000004000000000000000000000000000000000000001 -> FAILURE, hex"4e487b71", 0x11