Added overflow checks after multiplication operation is executed.

This commit is contained in:
Matheus Aguiar
2022-08-12 10:18:05 -03:00
parent c48be40ab8
commit 2282ea5e56
21 changed files with 243 additions and 104 deletions
+1
View File
@@ -7,6 +7,7 @@ Language Features:
Compiler Features:
* Code Generator: More efficient overflow checks for multiplication.
Bugfixes:
+31 -13
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();
});
}
+46
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)
-43
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
@@ -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
@@ -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)
@@ -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)
+15
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
@@ -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
@@ -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
@@ -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
@@ -17,7 +17,7 @@ contract c {
}
// ----
// test() -> 4, 5
// gas irOptimized: 238692
// gas irOptimized: 238623
// gas legacy: 238736
// gas legacyOptimized: 237159
// storageEmpty -> 1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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