Skip to content

Commit 3d8eeba

Browse files
K1-R1SwayStar123
andauthored
fix: resolve UB in _add, __sub, _mul for u8, u16, u32 (#6654)
## Description handle u32 and u16 subtract underflow manually. Mirror u8 impl's use of u64 conversion for u16 and u32. Add additional u8 test. Closes #6653. Closes #6666. ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --------- Co-authored-by: SwayStar123 <46050679+SwayStar123@users.noreply.github.com>
1 parent 2097380 commit 3d8eeba

File tree

3 files changed

+188
-63
lines changed
  • sway-lib-core/src
  • test/src
    • e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode
    • in_language_tests/test_programs/math_inline_tests/src

3 files changed

+188
-63
lines changed

sway-lib-core/src/ops.sw

+108-63
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ library;
33
use ::primitives::*;
44
use ::slice::*;
55

6+
const MAX_U32_U64: u64 = __transmute::<u32, u64>(u32::max());
7+
const MAX_U16_U64: u64 = __transmute::<u16, u64>(u16::max());
8+
69
/// Trait for the addition of two values.
710
pub trait Add {
811
/// Add two values of the same type.
@@ -56,69 +59,62 @@ impl Add for u64 {
5659
// Emulate overflowing arithmetic for non-64-bit integer types
5760
impl Add for u32 {
5861
fn add(self, other: Self) -> Self {
59-
// any non-64-bit value is compiled to a u64 value under-the-hood
60-
// constants (like Self::max() below) are also automatically promoted to u64
61-
let res = __add(self, other);
62-
// integer overflow
63-
if __gt(res, Self::max()) {
62+
let res_u64 = __add(
63+
__transmute::<Self, u64>(self),
64+
__transmute::<Self, u64>(other),
65+
);
66+
67+
if __gt(res_u64, MAX_U32_U64) {
6468
if panic_on_overflow_is_enabled() {
6569
__revert(0)
6670
} else {
6771
// overflow enabled
6872
// res % (Self::max() + 1)
69-
__mod(res, __add(Self::max(), 1))
73+
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U32_U64, 1)))
7074
}
7175
} else {
72-
// no overflow
73-
res
76+
__transmute::<u64, Self>(res_u64)
7477
}
7578
}
7679
}
7780

7881
impl Add for u16 {
7982
fn add(self, other: Self) -> Self {
80-
let res = __add(self, other);
81-
if __gt(res, Self::max()) {
83+
let res_u64 = __add(
84+
__transmute::<Self, u64>(self),
85+
__transmute::<Self, u64>(other),
86+
);
87+
88+
if __gt(res_u64, MAX_U16_U64) {
8289
if panic_on_overflow_is_enabled() {
8390
__revert(0)
8491
} else {
8592
// overflow enabled
8693
// res % (Self::max() + 1)
87-
__mod(res, __add(Self::max(), 1))
94+
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U16_U64, 1)))
8895
}
8996
} else {
90-
res
97+
__transmute::<u64, Self>(res_u64)
9198
}
9299
}
93100
}
94101

95102
impl Add for u8 {
96103
fn add(self, other: Self) -> Self {
97-
let self_u64 = asm(input: self) {
98-
input: u64
99-
};
100-
let other_u64 = asm(input: other) {
101-
input: u64
102-
};
103-
let res_u64 = __add(self_u64, other_u64);
104-
let max_u8_u64 = asm(input: Self::max()) {
105-
input: u64
106-
};
104+
let res_u64 = __add(u8_as_u64(self), u8_as_u64(other));
105+
106+
let max_u8_u64 = u8_as_u64(Self::max());
107+
107108
if __gt(res_u64, max_u8_u64) {
108109
if panic_on_overflow_is_enabled() {
109110
__revert(0)
110111
} else {
111112
// overflow enabled
112113
// res % (Self::max() + 1)
113-
let res_u64 = __mod(res_u64, __add(max_u8_u64, 1));
114-
asm(input: res_u64) {
115-
input: u8
116-
}
114+
u64_as_u8(__mod(res_u64, __add(max_u8_u64, 1)))
117115
}
118116
} else {
119-
asm(input: res_u64) {
120-
input: u8
121-
}
117+
u64_as_u8(res_u64)
122118
}
123119
}
124120
}
@@ -173,23 +169,65 @@ impl Subtract for u64 {
173169
}
174170
}
175171

176-
// unlike addition, underflowing subtraction does not need special treatment
177-
// because VM handles underflow
178172
impl Subtract for u32 {
179173
fn subtract(self, other: Self) -> Self {
180-
__sub(self, other)
174+
let res_u64 = __sub(
175+
__transmute::<Self, u64>(self),
176+
__transmute::<Self, u64>(other),
177+
);
178+
179+
if __gt(res_u64, MAX_U32_U64) {
180+
if panic_on_overflow_is_enabled() {
181+
__revert(0)
182+
} else {
183+
// overflow enabled
184+
// res % (Self::max() + 1)
185+
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U32_U64, 1)))
186+
}
187+
} else {
188+
__transmute::<u64, Self>(res_u64)
189+
}
181190
}
182191
}
183192

184193
impl Subtract for u16 {
185194
fn subtract(self, other: Self) -> Self {
186-
__sub(self, other)
195+
let res_u64 = __sub(
196+
__transmute::<Self, u64>(self),
197+
__transmute::<Self, u64>(other),
198+
);
199+
200+
if __gt(res_u64, MAX_U16_U64) {
201+
if panic_on_overflow_is_enabled() {
202+
__revert(0)
203+
} else {
204+
// overflow enabled
205+
// res % (Self::max() + 1)
206+
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U16_U64, 1)))
207+
}
208+
} else {
209+
__transmute::<u64, Self>(res_u64)
210+
}
187211
}
188212
}
189213

190214
impl Subtract for u8 {
191215
fn subtract(self, other: Self) -> Self {
192-
__sub(self, other)
216+
let res_u64 = __sub(u8_as_u64(self), u8_as_u64(other));
217+
218+
let max_u8_u64 = u8_as_u64(Self::max());
219+
220+
if __gt(res_u64, max_u8_u64) {
221+
if panic_on_overflow_is_enabled() {
222+
__revert(0)
223+
} else {
224+
// overflow enabled
225+
// res % (Self::max() + 1)
226+
u64_as_u8(__mod(res_u64, __add(max_u8_u64, 1)))
227+
}
228+
} else {
229+
u64_as_u8(res_u64)
230+
}
193231
}
194232
}
195233

@@ -246,67 +284,62 @@ impl Multiply for u64 {
246284
// Emulate overflowing arithmetic for non-64-bit integer types
247285
impl Multiply for u32 {
248286
fn multiply(self, other: Self) -> Self {
249-
// any non-64-bit value is compiled to a u64 value under-the-hood
250-
// constants (like Self::max() below) are also automatically promoted to u64
251-
let res = __mul(self, other);
252-
if __gt(res, Self::max()) {
287+
let res_u64 = __mul(
288+
__transmute::<Self, u64>(self),
289+
__transmute::<Self, u64>(other),
290+
);
291+
292+
if __gt(res_u64, MAX_U32_U64) {
253293
if panic_on_overflow_is_enabled() {
254-
// integer overflow
255294
__revert(0)
256295
} else {
257296
// overflow enabled
258297
// res % (Self::max() + 1)
259-
__mod(res, __add(Self::max(), 1))
298+
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U32_U64, 1)))
260299
}
261300
} else {
262-
// no overflow
263-
res
301+
__transmute::<u64, Self>(res_u64)
264302
}
265303
}
266304
}
267305

268306
impl Multiply for u16 {
269307
fn multiply(self, other: Self) -> Self {
270-
let res = __mul(self, other);
271-
if __gt(res, Self::max()) {
308+
let res_u64 = __mul(
309+
__transmute::<Self, u64>(self),
310+
__transmute::<Self, u64>(other),
311+
);
312+
313+
if __gt(res_u64, MAX_U16_U64) {
272314
if panic_on_overflow_is_enabled() {
273315
__revert(0)
274316
} else {
275-
__mod(res, __add(Self::max(), 1))
317+
// overflow enabled
318+
// res % (Self::max() + 1)
319+
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U16_U64, 1)))
276320
}
277321
} else {
278-
res
322+
__transmute::<u64, Self>(res_u64)
279323
}
280324
}
281325
}
282326

283327
impl Multiply for u8 {
284328
fn multiply(self, other: Self) -> Self {
285-
let self_u64 = asm(input: self) {
286-
input: u64
287-
};
288-
let other_u64 = asm(input: other) {
289-
input: u64
290-
};
291-
let res_u64 = __mul(self_u64, other_u64);
292-
let max_u8_u64 = asm(input: Self::max()) {
293-
input: u64
294-
};
329+
let res_u64 = __mul(u8_as_u64(self), u8_as_u64(other));
330+
331+
let max_u8_u64 = u8_as_u64(Self::max());
332+
295333
if __gt(res_u64, max_u8_u64) {
296334
if panic_on_overflow_is_enabled() {
297335
__revert(0)
298336
} else {
299337
// overflow enabled
300338
// res % (Self::max() + 1)
301-
let res_u64 = __mod(res_u64, __add(max_u8_u64, 1));
302-
asm(input: res_u64) {
303-
input: u8
304-
}
339+
u64_as_u8(__mod(res_u64, __add(max_u8_u64, 1)))
305340
}
306341
} else {
307-
asm(input: res_u64) {
308-
input: u8
309-
}
342+
u64_as_u8(res_u64)
310343
}
311344
}
312345
}
@@ -1741,3 +1774,15 @@ fn panic_on_overflow_is_enabled() -> bool {
17411774
0,
17421775
)
17431776
}
1777+
1778+
fn u8_as_u64(val: u8) -> u64 {
1779+
asm(input: val) {
1780+
input: u64
1781+
}
1782+
}
1783+
1784+
fn u64_as_u8(val: u64) -> u8 {
1785+
asm(input: val) {
1786+
input: u8
1787+
}
1788+
}

test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode/stdout.snap

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ exit status: 0
66
output:
77
Building test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode
88
Compiling library core (sway-lib-core)
9+
// IR: Final
10+
library {
11+
}
12+
913
Compiling library std (sway-lib-std)
1014
// IR: Final
1115
library {

test/src/in_language_tests/test_programs/math_inline_tests/src/main.sw

+76
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,22 @@ fn math_u256_overflow_mul_revert() {
902902
log(b);
903903
}
904904

905+
#[test(should_revert)]
906+
fn math_u16_underflow_sub_revert() {
907+
let a = 0u16;
908+
let b = 1u16;
909+
let c = a - b;
910+
log(c);
911+
}
912+
913+
#[test(should_revert)]
914+
fn math_u32_underflow_sub_revert() {
915+
let a = 0u32;
916+
let b = 1u32;
917+
let c = a - b;
918+
log(c);
919+
}
920+
905921
#[test]
906922
fn math_u8_overflow_add() {
907923
let _ = disable_panic_on_overflow();
@@ -924,6 +940,26 @@ fn math_u8_overflow_add() {
924940
require(e == u8::max() - 2, e);
925941
}
926942

943+
#[test]
944+
fn math_u8_underflow_sub() {
945+
assert((u8::max() - u8::max()) == 0u8);
946+
assert((u8::min() - u8::min()) == 0u8);
947+
assert((10u8 - 5u8) == 5u8);
948+
949+
let _ = disable_panic_on_overflow();
950+
951+
let a = 0u8;
952+
let b = 1u8;
953+
954+
let c = a - b;
955+
assert(c == u8::max());
956+
957+
let d = u8::max();
958+
959+
let e = a - d;
960+
assert(e == b);
961+
}
962+
927963
#[test]
928964
fn math_u16_overflow_add() {
929965
let _ = disable_panic_on_overflow();
@@ -946,6 +982,26 @@ fn math_u16_overflow_add() {
946982
require(e == u16::max() - 2, e);
947983
}
948984

985+
#[test]
986+
fn math_u16_underflow_sub() {
987+
assert((u16::max() - u16::max()) == 0u16);
988+
assert((u16::min() - u16::min()) == 0u16);
989+
assert((10u16 - 5u16) == 5u16);
990+
991+
let _ = disable_panic_on_overflow();
992+
993+
let a = 0u16;
994+
let b = 1u16;
995+
996+
let c = a - b;
997+
assert(c == u16::max());
998+
999+
let d = u16::max();
1000+
1001+
let e = a - d;
1002+
assert(e == b);
1003+
}
1004+
9491005
#[test]
9501006
fn math_u32_overflow_add() {
9511007
let _ = disable_panic_on_overflow();
@@ -968,6 +1024,26 @@ fn math_u32_overflow_add() {
9681024
require(e == u32::max() - 2, e);
9691025
}
9701026

1027+
#[test]
1028+
fn math_u32_underflow_sub() {
1029+
assert((u32::max() - u32::max()) == 0u32);
1030+
assert((u32::min() - u32::min()) == 0u32);
1031+
assert((10u32 - 5u32) == 5u32);
1032+
1033+
let _ = disable_panic_on_overflow();
1034+
1035+
let a = 0u32;
1036+
let b = 1u32;
1037+
1038+
let c = a - b;
1039+
assert(c == u32::max());
1040+
1041+
let d = u32::max();
1042+
1043+
let e = a - d;
1044+
assert(e == b);
1045+
}
1046+
9711047
#[test]
9721048
fn math_u64_overflow_add() {
9731049
let _ = disable_panic_on_overflow();

0 commit comments

Comments
 (0)