Skip to content

Commit 9d35d71

Browse files
committed
Add guideline for issue #156
Co-authored-by: felix91gr <felix91gr@users.noreply.github.com> Note: tables were written by hand due to limitations in our automation
1 parent 50a1542 commit 9d35d71

File tree

1 file changed

+151
-0
lines changed

1 file changed

+151
-0
lines changed

src/coding-guidelines/expressions.rst

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,3 +463,154 @@ Expressions
463463
/* ... */
464464
}
465465
466+
467+
.. guideline:: Integer shift shall only be performed through `checked_` APIs
468+
:id: gui_RHvQj8BHlz9b
469+
:category: required
470+
:status: draft
471+
:release: 1.7.0-latest
472+
:fls: fls_sru4wi5jomoe
473+
:decidability: decidable
474+
:scope: module
475+
:tags: numerics, reduce-human-error, maintainability, portability, surprising-behavior, subset
476+
477+
In particular, the user should only perform left shifts via the `\ ``checked_shl`` <https://doc.rust-lang.org/core/index.html?search=%22checked_shl%22>`_ function and right shifts via the `\ ``checked_shr`` <https://doc.rust-lang.org/core/index.html?search=%22checked_shr%22>`_ function. Both of these functions exist in `\ ``core`` <https://doc.rust-lang.org/core/index.html>`_.
478+
479+
This rule applies to the following primitive types:
480+
481+
482+
* ``i8``
483+
* ``i16``
484+
* ``i32``
485+
* ``i64``
486+
* ``i128``
487+
* ``u8``
488+
* ``u16``
489+
* ``u32``
490+
* ``u64``
491+
* ``u128``
492+
* ``usize``
493+
* ``isize``
494+
495+
.. rationale::
496+
:id: rat_3MpR8QfHodGT
497+
:status: draft
498+
499+
This is a Subset rule, directly inspired by `INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand <https://wiki.sei.cmu.edu/confluence/x/ItcxBQ>`_.
500+
501+
In Rust these out-of-range shifts don't give rise to Undefined Behavior; however, they are still problematic in Safety Critical contexts for two reasons.
502+
503+
504+
*
505+
**Reason 1: inconsistent behavior**
506+
507+
The behavior of shift operations depends on the compilation mode. Say for example, that we have a number ``x`` of type ``uN``\ , and we perform the operation
508+
509+
``x << M``
510+
511+
Then, it will behave like this:
512+
513+
+------------------+-----------------+-----------------------+-----------------------+
514+
| Compilation Mode | ``0 <= M < N`` | ``M < 0`` | ``N <= M`` |
515+
+==================+=================+=======================+=======================+
516+
| Debug | Shifts normally | Panics | Panics |
517+
+------------------+-----------------+-----------------------+-----------------------+
518+
| Release | Shifts normally | Shifts by ``M mod N`` | Shifts by ``M mod N`` |
519+
+------------------+-----------------+-----------------------+-----------------------+
520+
521+
..
522+
523+
Note: the behavior is exactly the same for the ``>>`` operator.
524+
525+
526+
Panicking in ``Debug`` is an issue by itself, however, a perhaps larger issue there is that its behavior is different from that of ``Release``. Such inconsistencies aren't acceptable in Safety Critical scenarios.
527+
528+
Therefore, a consistently-behaved operation should be required for performing shifts.
529+
530+
*
531+
**Reason 2: programmer intent**
532+
533+
There is no scenario in which it makes sense to perform a shift of negative length, or of more than ``N - 1`` bits. The operation itself becomes meaningless.
534+
535+
Therefore, an API that restricts the length of the shift to the range ``[0, N - 1]`` should be used instead of the ``<<`` and ``>>`` operators.
536+
537+
*
538+
**The Solution**
539+
540+
The ideal solution for this exists in ``core``\ : ``checked_shl`` and ``checked_shr``.
541+
542+
``<T>::checked_shl(M)`` returns a value of type ``Option<T>``\ , in the following way:
543+
544+
545+
* If ``M < 0``\ , the output is ``None``
546+
* If ``0 <= M < N`` for ``T`` of ``N`` bits, then the output is ``Some(T)``
547+
* If ``N <= M``\ , the output is ``None``
548+
549+
This API has consistent behavior across ``Debug`` and ``Release``\ , and makes the programmer intent explicit, which effectively solves this issue.
550+
551+
.. non_compliant_example::
552+
:id: non_compl_ex_O9FZuazu3Lcn
553+
:status: draft
554+
555+
As seen in the example below:
556+
557+
558+
* A ``Debug`` build **panics**\ ,
559+
*
560+
Whereas a ``Release`` build prints the values:
561+
562+
.. code-block::
563+
564+
61 << -1 = 2147483648
565+
61 << 4 = 976
566+
61 << 40 = 15616
567+
568+
This shows **Reason 1** prominently.
569+
570+
**Reason 2** is not seen in the code, because it is a reason of programmer intent: shifts by less than 0 or by more than ``N - 1`` (\ ``N`` being the bit-length of the value being shifted) are both meaningless.
571+
572+
.. code-block:: rust
573+
574+
fn bad_shl(bits: u32, shift: i32) -> u32 {
575+
bits << shift
576+
}
577+
578+
let bits : u32 = 61;
579+
let shifts = vec![-1, 4, 40];
580+
581+
for sh in shifts {
582+
println!("{bits} << {sh} = {}", bad_shl(bits, sh));
583+
}
584+
585+
.. compliant_example::
586+
:id: compl_ex_xpPQqYeEPGIo
587+
:status: draft
588+
589+
As seen in the example below:
590+
591+
592+
* Both ``Debug`` and ``Release`` give the same exact output, which addresses **Reason 1**.
593+
* Shifting by negative values is impossible due to the fact that ``checked_shl`` only accepts unsigned integers as shift lengths.
594+
* Shifting by more than ``N - 1`` (\ ``N`` being the bit-length of the value being shifted) returns a ``None`` value:
595+
.. code-block::
596+
597+
61 << 4 = Some(976)
598+
61 << 40 = None
599+
600+
The last 2 observations show how this addresses **Reason 2**.
601+
602+
.. code-block:: rust
603+
604+
fn good_shl(bits: u32, shift: u32) -> Option<u32> {
605+
bits.checked_shl(shift)
606+
}
607+
608+
let bits : u32 = 61;
609+
// let shifts = vec![-1, 4, 40];
610+
// ^--- Would not typecheck, as checked_shl
611+
// only accepts positive shift amounts
612+
let shifts = vec![4, 40];
613+
614+
for sh in shifts {
615+
println!("{bits} << {sh} = {:?}", good_shl(bits, sh));
616+
}

0 commit comments

Comments
 (0)