Skip to content

Commit 5bfd353

Browse files
committed
Add guideline for issue #174
Co-authored-by: felix91gr <[email protected]> Note: tables were written by hand due to limitations in our automation
1 parent 9bb143a commit 5bfd353

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed

src/coding-guidelines/expressions.rst

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,3 +463,137 @@ Expressions
463463
/* ... */
464464
}
465465
466+
467+
.. guideline:: 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
468+
:id: gui_LvmzGKdsAgI5
469+
:category: mandatory
470+
:status: draft
471+
:release: 1.0.0-latest
472+
:fls: fls_sru4wi5jomoe
473+
:decidability: undecidable
474+
:scope: module
475+
:tags: numerics, surprising-behavior, defect
476+
477+
In particular, the user should limit the Right Hand Side (RHS) parameter used for left shifts and right shifts (i.e. the ``<<`` and ``>>`` binary operators) to only the range ``0..=N-1``\ , where ``N`` is the number of bits of the Left Hand Side (LHS) parameter. For example, in ``a << b``\ , if ``a`` is of type ``u32``\ , then ``b`` **must belong to** the range ``0..=31``.
478+
479+
This rule applies to all types which implement the ``core::ops::Shl`` and / or ``core::ops::Shr`` traits, for Rust Version greater than or equal to ``1.6.0``.
480+
481+
For versions prior to ``1.6.0``\ , this rule applies to all types for which the ``<<`` and ``>>`` operators are valid. That is, it applies to the following primitive types:
482+
483+
484+
* ``i8``
485+
* ``i16``
486+
* ``i32``
487+
* ``i64``
488+
* ``i128``
489+
* ``isize``
490+
* ``u8``
491+
* ``u16``
492+
* ``u32``
493+
* ``u64``
494+
* ``u128``
495+
* ``usize``
496+
497+
.. rationale::
498+
:id: rat_tVkDl6gOqz25
499+
:status: draft
500+
501+
This is a Defect Avoidance 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>`_.
502+
503+
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.
504+
505+
506+
*
507+
**Reason 1: inconsistent behavior**
508+
509+
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
510+
511+
``x << M``
512+
513+
Then, it will behave like this:
514+
515+
+------------------+-----------------+-----------------------+-----------------------+
516+
| Compilation Mode | ``0 <= M < N`` | ``M < 0`` | ``N <= M`` |
517+
+==================+=================+=======================+=======================+
518+
| Debug | Shifts normally | Panics | Panics |
519+
+------------------+-----------------+-----------------------+-----------------------+
520+
| Release | Shifts normally | Shifts by ``M mod N`` | Shifts by ``M mod N`` |
521+
+------------------+-----------------+-----------------------+-----------------------+
522+
523+
524+
..
525+
526+
Note: the behavior is exactly the same for the ``>>`` operator.
527+
528+
529+
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.
530+
531+
*
532+
**Reason 2: programmer intent**
533+
534+
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.
535+
536+
For both of these reasons, the programmer must ensure the RHS operator stays in the range ``0..=N-1``.
537+
538+
.. non_compliant_example::
539+
:id: non_compl_ex_aTtUjdIuDdbv
540+
:status: draft
541+
542+
As seen in the example below:
543+
544+
545+
* A ``Debug`` build **panics**\ ,
546+
*
547+
Whereas a ``Release`` build prints the values:
548+
549+
.. code-block::
550+
551+
61 << -1 = 2147483648
552+
61 << 4 = 976
553+
61 << 40 = 15616
554+
555+
This shows **Reason 1** prominently.
556+
557+
**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.
558+
559+
.. code-block:: rust
560+
561+
let bits : u32 = 61;
562+
let shifts = vec![-1, 4, 40];
563+
564+
for sh in shifts {
565+
println!("{bits} << {sh} = {}", bits << sh);
566+
}
567+
568+
.. compliant_example::
569+
:id: compl_ex_Ux1WqHbGKV73
570+
:status: draft
571+
572+
As seen in the example below:
573+
574+
575+
* Both ``Debug`` and ``Release`` give the same exact output, which addresses **Reason 1**.
576+
* Out-of-range shifts are caught and avoided before they happen.
577+
*
578+
The output shows what's happening:
579+
580+
.. code-block::
581+
582+
Performing 61 << -1 would be meaningless and crash-prone; we avoided it!
583+
61 << 4 = 976
584+
Performing 61 << 40 would be meaningless and crash-prone; we avoided it!
585+
586+
The output shows how this addresses **Reason 2**.
587+
588+
.. code-block:: rust
589+
590+
let bits : u32 = 61;
591+
let shifts = vec![-1, 4, 40];
592+
593+
for sh in shifts {
594+
if 0 <= sh && sh < 32 {
595+
println!("{bits} << {sh} = {}", bits << sh);
596+
} else {
597+
println!("Performing {bits} << {sh} would be meaningless and crash-prone; we avoided it!");
598+
}
599+
}

0 commit comments

Comments
 (0)