Skip to content

Commit 1b00276

Browse files
rcseacordfelix91gr
andauthored
new arithmetic overflow rule (#220)
* new arithmetic overflow rule * Update expressions.rst fixed a bunch of small problems and started adding compliant solutions using saturation semantics * Update expressions.rst adding Wrapping<T> example * Revise arithmetic overflow guidelines and examples Revised guidelines on arithmetic overflow, emphasizing prevention and the use of explicit wrapping functions. Updated examples to reflect compliant practices with saturation semantics. * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update expressions.rst to specify integer types for overflow Clarify guidelines on eliminating arithmetic overflow for various integer types. * Update guidelines on arithmetic overflow handling Clarify behavior of arithmetic overflow in Rust. * Clarify arithmetic overflow behavior in expressions.rst * rebase for fls errors * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Apply suggestion from @rcseacord * Clarify arithmetic overflow handling and examples Clarified behavior of arithmetic overflow in debug and release modes. Updated examples to emphasize the use of explicit wrapping and saturation semantics. * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update print statement format in Rust example * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update expressions.rst --------- Co-authored-by: Félix Fischer <[email protected]>
1 parent c08c6a4 commit 1b00276

File tree

2 files changed

+448
-171
lines changed

2 files changed

+448
-171
lines changed

src/coding-guidelines/expressions.rst

Lines changed: 278 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,283 @@
66
Expressions
77
===========
88

9+
10+
.. guideline:: Ensure that integer operations do not result in arithmetic overflow
11+
:id: gui_dCquvqE1csI3
12+
:category: required
13+
:status: draft
14+
:release: 1.0 - latest
15+
:fls: fls_oFIRXBPXu6Zv
16+
:decidability: decidable
17+
:scope: system
18+
:tags: security, performance, numerics
19+
20+
Eliminate `arithmetic overflow <https://rust-lang.github.io/fls/expressions.html#arithmetic-overflow>`_ of both signed and unsigned integer types.
21+
Any wraparound behavior must be explicitly specified to ensure the same behavior in both debug and release modes.
22+
23+
This rule applies to the following primitive types:
24+
25+
* ``i8``
26+
* ``i16``
27+
* ``i32``
28+
* ``i64``
29+
* ``i128``
30+
* ``u8``
31+
* ``u16``
32+
* ``u32``
33+
* ``u64``
34+
* ``u128``
35+
* ``usize``
36+
* ``isize``
37+
38+
.. rationale::
39+
:id: rat_LvrS1jTCXEOk
40+
:status: draft
41+
42+
Eliminate arithmetic overflow to avoid runtime panics and unexpected wraparound behavior.
43+
Arithmetic overflow will panic in debug mode, but wraparound in release mode, resulting in inconsistent behavior.
44+
Use explicit `wrapping <https://doc.rust-lang.org/std/num/struct.Wrapping.html>`_ or
45+
`saturating <https://doc.rust-lang.org/std/num/struct.Saturating.html>`_ semantics where these behaviors are intentional.
46+
Range checking can be used to eliminate the possibility of arithmetic overflow.
47+
48+
.. non_compliant_example::
49+
:id: non_compl_ex_cCh2RQUXeH0N
50+
:status: draft
51+
52+
This noncompliant code example can result in arithmetic overflow during the addition of the signed operands ``si_a`` and ``si_b``:
53+
54+
.. code-block:: rust
55+
56+
fn add(si_a: i32, si_b: i32) {
57+
let sum: i32 = si_a + si_b;
58+
// ...
59+
}
60+
61+
.. compliant_example::
62+
:id: compl_ex_BgUHiRB4kc4b_1
63+
:status: draft
64+
65+
This compliant solution ensures that the addition operation cannot result in arithmetic overflow,
66+
based on the maximum range of a signed 32-bit integer.
67+
Functions such as
68+
`overflowing_add <https://doc.rust-lang.org/stable/core/primitive.u32.html#method.overflowing_add>`_,
69+
`overflowing_sub <https://doc.rust-lang.org/stable/core/primitive.u32.html#method.overflowing_sub>`_, and
70+
`overflowing_mul <https://doc.rust-lang.org/stable/core/primitive.u32.html#method.overflowing_mul>`_
71+
can also be used to detect overflow.
72+
Code that invoked these functions would typically further restrict the range of possible values,
73+
based on the anticipated range of the inputs.
74+
75+
.. code-block:: rust
76+
77+
enum ArithmeticError {
78+
Overflow,
79+
DivisionByZero,
80+
}
81+
82+
use std::i32::{MAX as INT_MAX, MIN as INT_MIN};
83+
84+
fn add(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
85+
if (si_b > 0 && si_a > INT_MAX - si_b)
86+
|| (si_b < 0 && si_a < INT_MIN - si_b)
87+
{
88+
Err(ArithmeticError::Overflow)
89+
} else {
90+
Ok(si_a + si_b)
91+
}
92+
}
93+
94+
fn sub(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
95+
if (si_b < 0 && si_a > INT_MAX + si_b)
96+
|| (si_b > 0 && si_a < INT_MIN + si_b)
97+
{
98+
Err(ArithmeticError::Overflow)
99+
} else {
100+
Ok(si_a - si_b)
101+
}
102+
}
103+
104+
fn mul(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
105+
if si_a == 0 || si_b == 0 {
106+
return Ok(0);
107+
}
108+
109+
// Detect overflow before performing multiplication
110+
if (si_a == -1 && si_b == INT_MIN) || (si_b == -1 && si_a == INT_MIN) {
111+
Err(ArithmeticError::Overflow)
112+
} else if (si_a > 0 && (si_b > INT_MAX / si_a || si_b < INT_MIN / si_a))
113+
|| (si_a < 0 && (si_b > INT_MIN / si_a || si_b < INT_MAX / si_a))
114+
{
115+
Err(ArithmeticError::Overflow)
116+
} else {
117+
Ok(si_a * si_b)
118+
}
119+
}
120+
121+
.. compliant_example::
122+
:id: compl_ex_BgUHiRB4kc4c
123+
:status: draft
124+
125+
This compliant example uses safe checked addition instead of manual bounds checks.
126+
Checked functions can reduce readability when complex arithmetic expressions are needed.
127+
128+
.. code-block:: rust
129+
130+
fn add(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
131+
si_a.checked_add(si_b).ok_or(ArithmeticError::Overflow)
132+
}
133+
134+
fn sub(a: i32, b: i32) -> Result<i32, ArithmeticError> {
135+
a.checked_sub(b).ok_or(ArithmeticError::Overflow)
136+
}
137+
138+
fn mul(a: i32, b: i32) -> Result<i32, ArithmeticError> {
139+
a.checked_mul(b).ok_or(ArithmeticError::Overflow)
140+
}
141+
142+
.. compliant_example::
143+
:id: compl_ex_BgUHiRB4kc4b
144+
:status: draft
145+
146+
Wrapping behavior must be explicitly requested. This compliant example uses wrapping functions.
147+
148+
.. code-block:: rust
149+
150+
fn add(a: i32, b: i32) -> i32 {
151+
a.wrapping_add(b)
152+
}
153+
154+
fn sub(a: i32, b: i32) -> i32 {
155+
a.wrapping_sub(b)
156+
}
157+
158+
fn mul(a: i32, b: i32) -> i32 {
159+
a.wrapping_mul(b)
160+
}
161+
162+
.. compliant_example::
163+
:id: compl_ex_BhUHiRB4kc4b
164+
:status: draft
165+
166+
Wrapping behavior call also be achieved using the ``Wrapping<T>`` type as in this compliant solution.
167+
The ``Wrapping<T>`` type is a ``struct`` found in the ``std::num`` module that explicitly enables two's complement
168+
wrapping arithmetic for the inner type ``T`` (which must be an integer or ``usize/isize``).
169+
The ``Wrapping<T>`` type provides a consistent way to force wrapping behavior in all build modes,
170+
which is useful in specific scenarios like implementing cryptography or hash functions where wrapping arithmetic is the intended behavior.
171+
172+
.. code-block:: rust
173+
174+
use std::num::Wrapping;
175+
176+
fn add(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> {
177+
si_a + si_b
178+
}
179+
180+
fn sub(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> {
181+
si_a - si_b
182+
}
183+
184+
fn mul(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> {
185+
si_a * si_b
186+
}
187+
188+
fn main() {
189+
let si_a = Wrapping(i32::MAX);
190+
let si_b = Wrapping(i32::MAX);
191+
println!("{} + {} = {}", si_a, si_b, add(si_a, si_b))
192+
}
193+
194+
.. compliant_example::
195+
:id: compl_ex_BgUHiSB4kc4b
196+
:status: draft
197+
198+
Saturation semantics means that instead of wrapping around or resulting in an error,
199+
any result that falls outside the valid range of the integer type is clamped:
200+
201+
- To the maximum value, if the result were to be greater than the maximum value, or
202+
- To the minimum value, if the result were to be smaller than the minimum,
203+
204+
Saturation semantics always conform to this rule because they ensure that integer operations do not result in arithmetic overflow.
205+
This compliant solution shows how to use saturating functions to provide saturation semantics for some basic arithmetic operations.
206+
207+
.. code-block:: rust
208+
209+
fn add(a: i32, b: i32) -> i32 {
210+
a.saturating_add(b)
211+
}
212+
213+
fn sub(a: i32, b: i32) -> i32 {
214+
a.saturating_sub(b)
215+
}
216+
217+
fn mul(a: i32, b: i32) -> i32 {
218+
a.saturating_mul(b)
219+
}
220+
221+
.. compliant_example::
222+
:id: compl_ex_BgUHiSB4kd4b
223+
:status: draft
224+
225+
``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.
226+
``Saturating<T>`` is useful when you have a section of code or a data type where all arithmetic must be saturating.
227+
This compliant solution uses the ``Saturating<T>`` type to define several functions that perform basic integer operations using saturation semantics.
228+
229+
.. code-block:: rust
230+
231+
use std::num::Saturating;
232+
233+
fn add(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> {
234+
si_a + si_b
235+
}
236+
237+
fn sub(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> {
238+
si_a - si_b
239+
}
240+
241+
fn mul(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> {
242+
si_a * si_b
243+
}
244+
245+
fn main() {
246+
let si_a = Saturating(i32::MAX);
247+
let si_b = Saturating(i32::MAX);
248+
println!("{} + {} = {}", si_a, si_b, add(si_a, si_b))
249+
}
250+
251+
.. non_compliant_example::
252+
:id: non_compl_ex_cCh2RQUXeH0O
253+
:status: draft
254+
255+
This noncompliant code example example prevents divide-by-zero errors, but does not prevent arithmetic overflow.
256+
257+
.. code-block:: rust
258+
259+
fn div(s_a: i64, s_b: i64) -> Result<i64, DivError> {
260+
if s_b == 0 {
261+
Err(DivError::DivisionByZero)
262+
} else {
263+
Ok(s_a / s_b)
264+
}
265+
}
266+
267+
.. compliant_example::
268+
:id: compl_ex_BgUHiRB4kc4d
269+
:status: draft
270+
271+
This compliant solution eliminates the possibility of both divide-by-zero errors and arithmetic overflow:
272+
273+
.. code-block:: rust
274+
275+
276+
fn div(s_a: i64, s_b: i64) -> Result<i64, DivError> {
277+
if s_b == 0 {
278+
Err("division by zero")
279+
} else if s_a == i64::MIN && s_b == -1 {
280+
Err("arithmetic overflow")
281+
} else {
282+
Ok(s_a / s_b)
283+
}
284+
}
285+
9286
.. guideline:: Avoid as underscore pointer casts
10287
:id: gui_HDnAZ7EZ4z6G
11288
:category: required
@@ -504,7 +781,6 @@ Expressions
504781
/* ... */
505782
}
506783
507-
508784
.. guideline:: Integer shift shall only be performed through `checked_` APIs
509785
:id: gui_RHvQj8BHlz9b
510786
:category: required
@@ -790,3 +1066,4 @@ Expressions
7901066
println!("Performing {bits} << {sh} would be meaningless and crash-prone; we avoided it!");
7911067
}
7921068
}
1069+

0 commit comments

Comments
 (0)