Skip to content

Commit 2282ea5

Browse files
Added overflow checks after multiplication operation is executed.
1 parent c48be40 commit 2282ea5

21 files changed

+243
-104
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Language Features:
77

88

99
Compiler Features:
10+
* Code Generator: More efficient overflow checks for multiplication.
1011

1112

1213
Bugfixes:

libsolidity/codegen/YulUtilFunctions.cpp

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -684,28 +684,46 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
684684
function <functionName>(x, y) -> product {
685685
x := <cleanupFunction>(x)
686686
y := <cleanupFunction>(y)
687+
let product_raw := mul(x, y)
688+
product := <cleanupFunction>(product_raw)
687689
<?signed>
688-
// overflow, if x > 0, y > 0 and x > (maxValue / y)
689-
if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(<maxValue>, y))) { <panic>() }
690-
// underflow, if x > 0, y < 0 and y < (minValue / x)
691-
if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(<minValue>, x))) { <panic>() }
692-
// underflow, if x < 0, y > 0 and x < (minValue / y)
693-
if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(<minValue>, y))) { <panic>() }
694-
// overflow, if x < 0, y < 0 and x < (maxValue / y)
695-
if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(<maxValue>, y))) { <panic>() }
690+
<?gt128bit>
691+
<?256bit>
692+
// special case
693+
if and(slt(x, 0), eq(y, <minValue>)) { <panic>() }
694+
</256bit>
695+
// overflow, if x != 0 and y != product/x
696+
if iszero(
697+
or(
698+
iszero(x),
699+
eq(y, sdiv(product, x))
700+
)
701+
) { <panic>() }
702+
<!gt128bit>
703+
if iszero(eq(product, product_raw)) { <panic>() }
704+
</gt128bit>
696705
<!signed>
697-
// overflow, if x != 0 and y > (maxValue / x)
698-
if and(iszero(iszero(x)), gt(y, div(<maxValue>, x))) { <panic>() }
706+
<?gt128bit>
707+
// overflow, if x != 0 and y != product/x
708+
if iszero(
709+
or(
710+
iszero(x),
711+
eq(y, div(product, x))
712+
)
713+
) { <panic>() }
714+
<!gt128bit>
715+
if iszero(eq(product, product_raw)) { <panic>() }
716+
</gt128bit>
699717
</signed>
700-
product := mul(x, y)
701718
}
702719
)")
703720
("functionName", functionName)
704721
("signed", _type.isSigned())
705-
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
706-
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
707722
("cleanupFunction", cleanupFunction(_type))
708723
("panic", panicFunction(PanicCode::UnderOverflow))
724+
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
725+
("256bit", _type.numBits() == 256)
726+
("gt128bit", _type.numBits() > 128)
709727
.render();
710728
});
711729
}

test/formal/checked_int_mul_12.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from opcodes import AND, SDIV, MUL, EQ, ISZERO, OR, SLT
2+
from rule import Rule
3+
from util import BVSignedUpCast, BVSignedMin, BVSignedCleanupFunction
4+
from z3 import BVMulNoOverflow, BVMulNoUnderflow, BitVec, Not, Or
5+
6+
"""
7+
Overflow checked signed integer multiplication.
8+
"""
9+
10+
# Approximation with 16-bit base types.
11+
n_bits = 12
12+
13+
for type_bits in [4, 6, 8, 12]:
14+
15+
rule = Rule()
16+
17+
# Input vars
18+
X_short = BitVec('X', type_bits)
19+
Y_short = BitVec('Y', type_bits)
20+
21+
# Z3's overflow and underflow conditions
22+
actual_overflow = Not(BVMulNoOverflow(X_short, Y_short, True))
23+
actual_underflow = Not(BVMulNoUnderflow(X_short, Y_short))
24+
25+
# cast to full n_bits values
26+
X = BVSignedUpCast(X_short, n_bits)
27+
Y = BVSignedUpCast(Y_short, n_bits)
28+
product_raw = MUL(X, Y)
29+
#remove any overflown bits
30+
product = BVSignedCleanupFunction(product_raw, type_bits)
31+
32+
# Constants
33+
min_value = BVSignedMin(type_bits, n_bits)
34+
35+
# Overflow and underflow checks in YulUtilFunction::overflowCheckedIntMulFunction
36+
if type_bits > n_bits / 2:
37+
sol_overflow_check_1 = ISZERO(OR(ISZERO(X), EQ(Y, SDIV(product, X))))
38+
if type_bits == n_bits:
39+
sol_overflow_check_2 = AND(SLT(X, 0), EQ(Y, min_value))
40+
sol_overflow_check = Or(sol_overflow_check_1 != 0, sol_overflow_check_2 != 0)
41+
else:
42+
sol_overflow_check = (sol_overflow_check_1 != 0)
43+
else:
44+
sol_overflow_check = (ISZERO(EQ(product, product_raw)) != 0)
45+
46+
rule.check(Or(actual_overflow, actual_underflow), sol_overflow_check)

test/formal/checked_int_mul_16.py

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
from opcodes import AND, ISZERO, GT, DIV
1+
from opcodes import ISZERO, DIV, MUL, EQ, OR
22
from rule import Rule
3-
from util import BVUnsignedUpCast, BVUnsignedMax
3+
from util import BVUnsignedUpCast, BVUnsignedCleanupFunction
44
from z3 import BitVec, Not, BVMulNoOverflow
55

66
"""
77
Overflow checked unsigned integer multiplication.
88
"""
99

1010
# Approximation with 16-bit base types.
11-
n_bits = 16
12-
type_bits = 8
11+
n_bits = 12
1312

14-
while type_bits <= n_bits:
13+
for type_bits in [4, 6, 8, 12]:
1514

1615
rule = Rule()
1716

@@ -25,13 +24,14 @@
2524
# cast to full n_bits values
2625
X = BVUnsignedUpCast(X_short, n_bits)
2726
Y = BVUnsignedUpCast(Y_short, n_bits)
27+
product_raw = MUL(X, Y)
28+
#remove any overflown bits
29+
product = BVUnsignedCleanupFunction(product_raw, type_bits)
2830

29-
# Constants
30-
maxValue = BVUnsignedMax(type_bits, n_bits)
31-
32-
# Overflow check in YulUtilFunction::overflowCheckedIntMulFunction
33-
overflow_check = AND(ISZERO(ISZERO(X)), GT(Y, DIV(maxValue, X)))
31+
# Overflow check in YulUtilFunction::overflowCheckedIntMulFunctions
32+
if type_bits > n_bits / 2:
33+
overflow_check = ISZERO(OR(ISZERO(X), EQ(Y, DIV(product, X))))
34+
else:
35+
overflow_check = ISZERO(EQ(product, product_raw))
3436

3537
rule.check(overflow_check != 0, actual_overflow)
36-
37-
type_bits *= 2
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from opcodes import SIGNEXTEND
2+
from rule import Rule
3+
from util import BVSignedCleanupFunction, BVSignedUpCast
4+
from z3 import BitVec, BitVecVal, Concat
5+
6+
"""
7+
Overflow checked signed integer multiplication.
8+
"""
9+
10+
n_bits = 256
11+
12+
# Check that YulUtilFunction::cleanupFunction cleanup matches BVSignedCleanupFunction
13+
for type_bits in range(8,256,8):
14+
15+
rule = Rule()
16+
17+
# Input vars
18+
X = BitVec('X', n_bits)
19+
arg = BitVecVal(type_bits / 8 - 1, n_bits)
20+
21+
cleaned_reference = BVSignedCleanupFunction(X, type_bits)
22+
cleaned = SIGNEXTEND(arg, X)
23+
24+
rule.check(cleaned, cleaned_reference)
25+
26+
27+
# Check that BVSignedCleanupFunction properly cleans up values.
28+
for type_bits in range(8,256,8):
29+
30+
rule = Rule()
31+
32+
# Input vars
33+
X_short = BitVec('X', type_bits)
34+
dirt = BitVec('dirt', n_bits - type_bits)
35+
36+
X = BVSignedUpCast(X_short, n_bits)
37+
X_dirty = Concat(dirt, X_short)
38+
X_cleaned = BVSignedCleanupFunction(X_dirty, type_bits)
39+
40+
41+
rule.check(X, X_cleaned)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from opcodes import AND
2+
from rule import Rule
3+
from util import BVUnsignedCleanupFunction, BVUnsignedUpCast
4+
from z3 import BitVec, BitVecVal, Concat
5+
6+
"""
7+
Overflow checked unsigned integer multiplication.
8+
"""
9+
10+
n_bits = 256
11+
12+
# Check that YulUtilFunction::cleanupFunction cleanup matches BVUnsignedCleanupFunction
13+
for type_bits in range(8,256,8):
14+
15+
rule = Rule()
16+
17+
# Input vars
18+
X = BitVec('X', n_bits)
19+
mask = BitVecVal((1 << type_bits) - 1, n_bits)
20+
21+
cleaned_reference = BVUnsignedCleanupFunction(X, type_bits)
22+
cleaned = AND(X, mask)
23+
24+
rule.check(cleaned, cleaned_reference)
25+
26+
# Check that BVUnsignedCleanupFunction properly cleans up values.
27+
for type_bits in range(8,256,8):
28+
29+
rule = Rule()
30+
31+
# Input vars
32+
X_short = BitVec('X', type_bits)
33+
dirt = BitVec('dirt', n_bits - type_bits)
34+
35+
X = BVUnsignedUpCast(X_short, n_bits)
36+
X_dirty = Concat(dirt, X_short)
37+
X_cleaned = BVUnsignedCleanupFunction(X_dirty, type_bits)
38+
39+
40+
rule.check(X, X_cleaned)

test/formal/util.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,18 @@ def BVSignedMax(type_bits, n_bits):
2525
def BVSignedMin(type_bits, n_bits):
2626
assert type_bits <= n_bits
2727
return BitVecVal(-(1 << (type_bits - 1)), n_bits)
28+
29+
def BVSignedCleanupFunction(x, type_bits):
30+
assert x.size() >= type_bits
31+
sign_mask = BitVecVal(1, x.size()) << (type_bits - 1)
32+
bit_mask = (BitVecVal(1, x.size()) << type_bits) - 1
33+
return If(
34+
x & sign_mask == 0,
35+
x & bit_mask,
36+
x | ~bit_mask
37+
)
38+
39+
def BVUnsignedCleanupFunction(x, type_bits):
40+
assert x.size() >= type_bits
41+
bit_mask = (BitVecVal(1, x.size()) << type_bits) - 1
42+
return x & bit_mask

test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ contract C {
1818
// EVMVersion: >homestead
1919
// ----
2020
// h(uint256[2][]): 0x20, 3, 123, 124, 223, 224, 323, 324 -> 32, 256, 0x20, 3, 123, 124, 223, 224, 323, 324
21-
// gas irOptimized: 180726
21+
// gas irOptimized: 180829
2222
// gas legacy: 184921
2323
// gas legacyOptimized: 181506
2424
// i(uint256[2][2]): 123, 124, 223, 224 -> 32, 128, 123, 124, 223, 224
25-
// gas irOptimized: 112453
25+
// gas irOptimized: 112464
2626
// gas legacy: 115460
2727
// gas legacyOptimized: 112990

test/libsolidity/semanticTests/array/copying/array_copy_including_array.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ contract c {
3535
}
3636
// ----
3737
// test() -> 0x02000202
38-
// gas irOptimized: 4649903
39-
// gas legacy: 4578320
40-
// gas legacyOptimized: 4548312
38+
// gas irOptimized: 4649835
39+
// gas legacy: 4578446
40+
// gas legacyOptimized: 4548309
4141
// storageEmpty -> 1
4242
// clear() -> 0, 0
43-
// gas irOptimized: 4477229
43+
// gas irOptimized: 4477223
4444
// gas legacy: 4410748
4545
// gas legacyOptimized: 4382489
4646
// storageEmpty -> 1

0 commit comments

Comments
 (0)