Eliminate arithmetic overflow of both signed and unsigned integer types.
Any wraparound behavior must be explicitly specified to ensure the same behavior in both debug and release modes.
This rule applies to the following primitive types:
i8
i16
i32
i64
i128
u8
u16
u32
u64
u128
usize
isize
|
|
|
|
Eliminate arithmetic overflow to avoid runtime panics and unexpected wraparound behavior.
Arithmetic overflow will panic in debug mode, but wraparound in release mode, resulting in inconsistent behavior.
Use explicit wrapping or
saturating semantics where these behaviors are intentional.
Range checking can be used to eliminate the possibility of arithmetic overflow. |
|
|
|
|
This noncompliant code example can result in arithmetic overflow during the addition of the signed operands si_a and si_b:
fn add(si_a: i32, si_b: i32) {
let sum: i32 = si_a + si_b;
// ...
}
|
|
|
|
|
This compliant solution ensures that the addition operation cannot result in arithmetic overflow,
based on the maximum range of a signed 32-bit integer.
Functions such as
overflowing_add,
overflowing_sub, and
overflowing_mul
can also be used to detect overflow.
Code that invoked these functions would typically further restrict the range of possible values,
based on the anticipated range of the inputs.
enum ArithmeticError {
Overflow,
DivisionByZero,
}
use std::i32::{MAX as INT_MAX, MIN as INT_MIN};
fn add(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
if (si_b > 0 && si_a > INT_MAX - si_b)
|| (si_b < 0 && si_a < INT_MIN - si_b)
{
Err(ArithmeticError::Overflow)
} else {
Ok(si_a + si_b)
}
}
fn sub(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
if (si_b < 0 && si_a > INT_MAX + si_b)
|| (si_b > 0 && si_a < INT_MIN + si_b)
{
Err(ArithmeticError::Overflow)
} else {
Ok(si_a - si_b)
}
}
fn mul(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
if si_a == 0 || si_b == 0 {
return Ok(0);
}
// Detect overflow before performing multiplication
if (si_a == -1 && si_b == INT_MIN) || (si_b == -1 && si_a == INT_MIN) {
Err(ArithmeticError::Overflow)
} else if (si_a > 0 && (si_b > INT_MAX / si_a || si_b < INT_MIN / si_a))
|| (si_a < 0 && (si_b > INT_MIN / si_a || si_b < INT_MAX / si_a))
{
Err(ArithmeticError::Overflow)
} else {
Ok(si_a * si_b)
}
}
|
|
|
|
|
This compliant example uses safe checked addition instead of manual bounds checks.
Checked functions can reduce readability when complex arithmetic expressions are needed.
fn add(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
si_a.checked_add(si_b).ok_or(ArithmeticError::Overflow)
}
fn sub(a: i32, b: i32) -> Result<i32, ArithmeticError> {
a.checked_sub(b).ok_or(ArithmeticError::Overflow)
}
fn mul(a: i32, b: i32) -> Result<i32, ArithmeticError> {
a.checked_mul(b).ok_or(ArithmeticError::Overflow)
}
|
|
|
|
|
Wrapping behavior must be explicitly requested. This compliant example uses wrapping functions.
fn add(a: i32, b: i32) -> i32 {
a.wrapping_add(b)
}
fn sub(a: i32, b: i32) -> i32 {
a.wrapping_sub(b)
}
fn mul(a: i32, b: i32) -> i32 {
a.wrapping_mul(b)
}
|
|
|
|
|
Wrapping behavior call also be achieved using the Wrapping<T> type as in this compliant solution.
The Wrapping<T> type is a struct found in the std::num module that explicitly enables two’s complement
wrapping arithmetic for the inner type T (which must be an integer or usize/isize).
The Wrapping<T> type provides a consistent way to force wrapping behavior in all build modes,
which is useful in specific scenarios like implementing cryptography or hash functions where wrapping arithmetic is the intended behavior.
use std::num::Wrapping;
fn add(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> {
si_a + si_b
}
fn sub(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> {
si_a - si_b
}
fn mul(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> {
si_a * si_b
}
fn main() {
let si_a = Wrapping(i32::MAX);
let si_b = Wrapping(i32::MAX);
println!("{} + {} = {}", si_a, si_b, add(si_a, si_b))
}
|
|
|
|
|
Saturation semantics means that instead of wrapping around or resulting in an error,
any result that falls outside the valid range of the integer type is clamped:
To the maximum value, if the result were to be greater than the maximum value, or
To the minimum value, if the result were to be smaller than the minimum,
Saturation semantics always conform to this rule because they ensure that integer operations do not result in arithmetic overflow.
This compliant solution shows how to use saturating functions to provide saturation semantics for some basic arithmetic operations.
fn add(a: i32, b: i32) -> i32 {
a.saturating_add(b)
}
fn sub(a: i32, b: i32) -> i32 {
a.saturating_sub(b)
}
fn mul(a: i32, b: i32) -> i32 {
a.saturating_mul(b)
}
|
|
|
|
|
Saturating<T> is a wrapper type in Rust’s core library (core::num::Saturating<T>) that makes arithmetic operations on the wrapped value perform saturating arithmetic instead of wrapping, panicking, or overflowing.
Saturating<T> is useful when you have a section of code or a data type where all arithmetic must be saturating.
This compliant solution uses the Saturating<T> type to define several functions that perform basic integer operations using saturation semantics.
use std::num::Saturating;
fn add(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> {
si_a + si_b
}
fn sub(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> {
si_a - si_b
}
fn mul(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> {
si_a * si_b
}
fn main() {
let si_a = Saturating(i32::MAX);
let si_b = Saturating(i32::MAX);
println!("{} + {} = {}", si_a, si_b, add(si_a, si_b))
}
|
|
|
|
|
This noncompliant code example example prevents divide-by-zero errors, but does not prevent arithmetic overflow.
fn div(s_a: i64, s_b: i64) -> Result<i64, DivError> {
if s_b == 0 {
Err(DivError::DivisionByZero)
} else {
Ok(s_a / s_b)
}
}
|
|
|
|
|
This compliant solution eliminates the possibility of both divide-by-zero errors and arithmetic overflow:
fn div(s_a: i64, s_b: i64) -> Result<i64, DivError> {
if s_b == 0 {
Err("division by zero")
} else if s_a == i64::MIN && s_b == -1 {
Err("arithmetic overflow")
} else {
Ok(s_a / s_b)
}
}
|
|