Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: resolve UB in _add, __sub, _mul for u8, u16, u32 #6654

Draft
wants to merge 32 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0b31117
fix: handle u32 & u16 subtract underflow manually
K1-R1 Oct 17, 2024
3f6244f
Merge branch 'master' into k1-r1-f4
K1-R1 Oct 17, 2024
699e96c
Merge branch 'master' into k1-r1-f4
K1-R1 Oct 21, 2024
f02f717
Merge branch 'master' into k1-r1-f4
K1-R1 Oct 23, 2024
260547e
fix: u32 add impl avoid UB
K1-R1 Oct 23, 2024
e83b2be
fix: u16 add impl avoid UB
K1-R1 Oct 23, 2024
b9959b1
fix: u32 add param names
K1-R1 Oct 23, 2024
75eba81
fix: u16 add param names
K1-R1 Oct 23, 2024
a735c12
fix: u32 sub impl avoid UB
K1-R1 Oct 23, 2024
8f71ac4
fix: u16 sub impl avoid UB
K1-R1 Oct 23, 2024
b1472c2
fix: u8 sub impl avoid UB
K1-R1 Oct 23, 2024
204bf27
fix: u32 mul impl avoid UB
K1-R1 Oct 23, 2024
18a71d3
fix: u16 mul impl avoid UB
K1-R1 Oct 23, 2024
ab7ac1e
chore: math_u8_underflow_sub test
K1-R1 Oct 23, 2024
2ca9571
chore: forc fmt
K1-R1 Oct 23, 2024
44915ca
Merge branch 'master' into k1-r1-f4
K1-R1 Oct 23, 2024
8cd71f3
Merge branch 'master' into k1-r1-f4
K1-R1 Oct 28, 2024
5d78172
Merge branch 'master' into k1-r1-f4
K1-R1 Oct 29, 2024
e12b912
feat: use transmute for Add on u8, u16, u32
K1-R1 Jan 31, 2025
400a878
feat: use transmute for Subtract on u8, u16, u32
K1-R1 Jan 31, 2025
0a84436
feat: use transmute for Multiply on u8, u16, u32
K1-R1 Jan 31, 2025
9e853cf
Merge branch 'master' into k1-r1-f4
K1-R1 Jan 31, 2025
55746c5
bug: add TODOs
K1-R1 Jan 31, 2025
f23571c
Merge branch 'master' into k1-r1-f4
SwayStar123 Feb 17, 2025
a14c9a9
Merge branch 'master' into k1-r1-f4
K1-R1 Feb 18, 2025
e8f0791
fix: u8 traits use as_u64
K1-R1 Feb 18, 2025
ebc0165
fix: primitive_conv import
K1-R1 Feb 18, 2025
01f6ded
chore: local read be byte fn
K1-R1 Feb 18, 2025
cf38764
chore: local read be byte fn
K1-R1 Feb 18, 2025
249a0a0
fix: transmute post modulo on overflow
K1-R1 Feb 18, 2025
916592f
Merge branch 'master' into k1-r1-f4
K1-R1 Feb 19, 2025
68acdbd
Merge branch 'master' into k1-r1-f4
K1-R1 Feb 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 108 additions & 63 deletions sway-lib-core/src/ops.sw
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ library;
use ::primitives::*;
use ::slice::*;

const MAX_U32_U64: u64 = __transmute::<u32, u64>(u32::max());
const MAX_U16_U64: u64 = __transmute::<u16, u64>(u16::max());

/// Trait for the addition of two values.
pub trait Add {
/// Add two values of the same type.
Expand Down Expand Up @@ -56,69 +59,62 @@ impl Add for u64 {
// Emulate overflowing arithmetic for non-64-bit integer types
impl Add for u32 {
fn add(self, other: Self) -> Self {
// any non-64-bit value is compiled to a u64 value under-the-hood
// constants (like Self::max() below) are also automatically promoted to u64
let res = __add(self, other);
// integer overflow
if __gt(res, Self::max()) {
let res_u64 = __add(
__transmute::<Self, u64>(self),
__transmute::<Self, u64>(other),
);

if __gt(res_u64, MAX_U32_U64) {
if panic_on_overflow_is_enabled() {
__revert(0)
} else {
// overflow enabled
// res % (Self::max() + 1)
__mod(res, __add(Self::max(), 1))
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U32_U64, 1)))
}
} else {
// no overflow
res
__transmute::<u64, Self>(res_u64)
}
}
}

impl Add for u16 {
fn add(self, other: Self) -> Self {
let res = __add(self, other);
if __gt(res, Self::max()) {
let res_u64 = __add(
__transmute::<Self, u64>(self),
__transmute::<Self, u64>(other),
);

if __gt(res_u64, MAX_U16_U64) {
if panic_on_overflow_is_enabled() {
__revert(0)
} else {
// overflow enabled
// res % (Self::max() + 1)
__mod(res, __add(Self::max(), 1))
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U16_U64, 1)))
}
} else {
res
__transmute::<u64, Self>(res_u64)
}
}
}

impl Add for u8 {
fn add(self, other: Self) -> Self {
let self_u64 = asm(input: self) {
input: u64
};
let other_u64 = asm(input: other) {
input: u64
};
let res_u64 = __add(self_u64, other_u64);
let max_u8_u64 = asm(input: Self::max()) {
input: u64
};
let res_u64 = __add(u8_as_u64(self), u8_as_u64(other));

let max_u8_u64 = u8_as_u64(Self::max());

if __gt(res_u64, max_u8_u64) {
if panic_on_overflow_is_enabled() {
__revert(0)
} else {
// overflow enabled
// res % (Self::max() + 1)
let res_u64 = __mod(res_u64, __add(max_u8_u64, 1));
asm(input: res_u64) {
input: u8
}
u64_as_u8(__mod(res_u64, __add(max_u8_u64, 1)))
}
} else {
asm(input: res_u64) {
input: u8
}
u64_as_u8(res_u64)
}
}
}
Expand Down Expand Up @@ -173,23 +169,65 @@ impl Subtract for u64 {
}
}

// unlike addition, underflowing subtraction does not need special treatment
// because VM handles underflow
impl Subtract for u32 {
fn subtract(self, other: Self) -> Self {
__sub(self, other)
let res_u64 = __sub(
__transmute::<Self, u64>(self),
__transmute::<Self, u64>(other),
);

if __gt(res_u64, MAX_U32_U64) {
if panic_on_overflow_is_enabled() {
__revert(0)
} else {
// overflow enabled
// res % (Self::max() + 1)
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U32_U64, 1)))
}
} else {
__transmute::<u64, Self>(res_u64)
}
}
}

impl Subtract for u16 {
fn subtract(self, other: Self) -> Self {
__sub(self, other)
let res_u64 = __sub(
__transmute::<Self, u64>(self),
__transmute::<Self, u64>(other),
);

if __gt(res_u64, MAX_U16_U64) {
if panic_on_overflow_is_enabled() {
__revert(0)
} else {
// overflow enabled
// res % (Self::max() + 1)
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U16_U64, 1)))
}
} else {
__transmute::<u64, Self>(res_u64)
}
}
}

impl Subtract for u8 {
fn subtract(self, other: Self) -> Self {
__sub(self, other)
let res_u64 = __sub(u8_as_u64(self), u8_as_u64(other));

let max_u8_u64 = u8_as_u64(Self::max());

if __gt(res_u64, max_u8_u64) {
if panic_on_overflow_is_enabled() {
__revert(0)
} else {
// overflow enabled
// res % (Self::max() + 1)
u64_as_u8(__mod(res_u64, __add(max_u8_u64, 1)))
}
} else {
u64_as_u8(res_u64)
}
}
}

Expand Down Expand Up @@ -246,67 +284,62 @@ impl Multiply for u64 {
// Emulate overflowing arithmetic for non-64-bit integer types
impl Multiply for u32 {
fn multiply(self, other: Self) -> Self {
// any non-64-bit value is compiled to a u64 value under-the-hood
// constants (like Self::max() below) are also automatically promoted to u64
let res = __mul(self, other);
if __gt(res, Self::max()) {
let res_u64 = __mul(
__transmute::<Self, u64>(self),
__transmute::<Self, u64>(other),
);

if __gt(res_u64, MAX_U32_U64) {
if panic_on_overflow_is_enabled() {
// integer overflow
__revert(0)
} else {
// overflow enabled
// res % (Self::max() + 1)
__mod(res, __add(Self::max(), 1))
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U32_U64, 1)))
}
} else {
// no overflow
res
__transmute::<u64, Self>(res_u64)
}
}
}

impl Multiply for u16 {
fn multiply(self, other: Self) -> Self {
let res = __mul(self, other);
if __gt(res, Self::max()) {
let res_u64 = __mul(
__transmute::<Self, u64>(self),
__transmute::<Self, u64>(other),
);

if __gt(res_u64, MAX_U16_U64) {
if panic_on_overflow_is_enabled() {
__revert(0)
} else {
__mod(res, __add(Self::max(), 1))
// overflow enabled
// res % (Self::max() + 1)
__transmute::<u64, Self>(__mod(res_u64, __add(MAX_U16_U64, 1)))
}
} else {
res
__transmute::<u64, Self>(res_u64)
}
}
}

impl Multiply for u8 {
fn multiply(self, other: Self) -> Self {
let self_u64 = asm(input: self) {
input: u64
};
let other_u64 = asm(input: other) {
input: u64
};
let res_u64 = __mul(self_u64, other_u64);
let max_u8_u64 = asm(input: Self::max()) {
input: u64
};
let res_u64 = __mul(u8_as_u64(self), u8_as_u64(other));

let max_u8_u64 = u8_as_u64(Self::max());

if __gt(res_u64, max_u8_u64) {
if panic_on_overflow_is_enabled() {
__revert(0)
} else {
// overflow enabled
// res % (Self::max() + 1)
let res_u64 = __mod(res_u64, __add(max_u8_u64, 1));
asm(input: res_u64) {
input: u8
}
u64_as_u8(__mod(res_u64, __add(max_u8_u64, 1)))
}
} else {
asm(input: res_u64) {
input: u8
}
u64_as_u8(res_u64)
}
}
}
Expand Down Expand Up @@ -1741,3 +1774,15 @@ fn panic_on_overflow_is_enabled() -> bool {
0,
)
}

fn u8_as_u64(val: u8) -> u64 {
asm(input: val) {
input: u64
}
}

fn u64_as_u8(val: u64) -> u8 {
asm(input: val) {
input: u8
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,22 @@ fn math_u256_overflow_mul_revert() {
log(b);
}

#[test(should_revert)]
fn math_u16_underflow_sub_revert() {
let a = 0u16;
let b = 1u16;
let c = a - b;
log(c);
}

#[test(should_revert)]
fn math_u32_underflow_sub_revert() {
let a = 0u32;
let b = 1u32;
let c = a - b;
log(c);
}

#[test]
fn math_u8_overflow_add() {
let _ = disable_panic_on_overflow();
Expand All @@ -924,6 +940,26 @@ fn math_u8_overflow_add() {
require(e == u8::max() - 2, e);
}

#[test]
fn math_u8_underflow_sub() {
assert((u8::max() - u8::max()) == 0u8);
assert((u8::min() - u8::min()) == 0u8);
assert((10u8 - 5u8) == 5u8);

let _ = disable_panic_on_overflow();

let a = 0u8;
let b = 1u8;

let c = a - b;
assert(c == u8::max());

let d = u8::max();

let e = a - d;
assert(e == b);
}

#[test]
fn math_u16_overflow_add() {
let _ = disable_panic_on_overflow();
Expand All @@ -946,6 +982,26 @@ fn math_u16_overflow_add() {
require(e == u16::max() - 2, e);
}

#[test]
fn math_u16_underflow_sub() {
assert((u16::max() - u16::max()) == 0u16);
assert((u16::min() - u16::min()) == 0u16);
assert((10u16 - 5u16) == 5u16);

let _ = disable_panic_on_overflow();

let a = 0u16;
let b = 1u16;

let c = a - b;
assert(c == u16::max());

let d = u16::max();

let e = a - d;
assert(e == b);
}

#[test]
fn math_u32_overflow_add() {
let _ = disable_panic_on_overflow();
Expand All @@ -968,6 +1024,26 @@ fn math_u32_overflow_add() {
require(e == u32::max() - 2, e);
}

#[test]
fn math_u32_underflow_sub() {
assert((u32::max() - u32::max()) == 0u32);
assert((u32::min() - u32::min()) == 0u32);
assert((10u32 - 5u32) == 5u32);

let _ = disable_panic_on_overflow();

let a = 0u32;
let b = 1u32;

let c = a - b;
assert(c == u32::max());

let d = u32::max();

let e = a - d;
assert(e == b);
}

#[test]
fn math_u64_overflow_add() {
let _ = disable_panic_on_overflow();
Expand Down
Loading