Skip to content

Commit f201f93

Browse files
PLeVasseurfelix91gr
authored andcommitted
Add guideline for issue #174
Co-authored-by: felix91gr <[email protected]>
1 parent f4b4f89 commit f201f93

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed

src/coding-guidelines/expressions.rst

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,144 @@ Expressions
399399
/* ... */
400400
}
401401
402+
403+
.. 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
404+
:id: gui_8BiWvapv0lUa
405+
:category: mandatory
406+
:status: draft
407+
:release: 1.0.0-latest
408+
:fls: fls_sru4wi5jomoe
409+
:decidability: undecidable
410+
:scope: module
411+
:tags: numerics, surprising-behavior, defect
412+
413+
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``.
414+
415+
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``.
416+
417+
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:
418+
419+
420+
* ``i8``
421+
* ``i16``
422+
* ``i32``
423+
* ``i64``
424+
* ``i128``
425+
* ``isize``
426+
* ``u8``
427+
* ``u16``
428+
* ``u32``
429+
* ``u64``
430+
* ``u128``
431+
* ``usize``
432+
433+
.. rationale::
434+
:id: rat_aMguLhw0ORnD
435+
:status: draft
436+
437+
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>`_.
438+
439+
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.
440+
441+
Reason 1: inconsistent behavior
442+
===============================
443+
444+
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
445+
446+
``x << M``
447+
448+
Then, it will behave like this:
449+
450+
.. list-table::
451+
:header-rows: 1
452+
453+
* - **Compilation Mode**
454+
- **\ ``0 <= M < N``\ **
455+
- **\ ``M < 0``\ **
456+
- **\ ``N <= M``\ **
457+
* - Debug
458+
- Shifts normally
459+
- Panics
460+
- Panics
461+
* - Release
462+
- Shifts normally
463+
- Shifts by ``M mod N``
464+
- Shifts by ``M mod N``
465+
466+
467+
..
468+
469+
Note: the behavior is exactly the same for the ``>>`` operator.
470+
471+
472+
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.
473+
474+
Reason 2: programmer intent
475+
===========================
476+
477+
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.
478+
479+
For both of these reasons, the programmer must ensure the RHS operator stays in the range ``0..=N-1``.
480+
481+
.. non_compliant_example::
482+
:id: non_compl_ex_4YFDofvjh9eV
483+
:status: draft
484+
485+
As seen in the example below:
486+
487+
488+
* A ``Debug`` build **panics**\ ,
489+
*
490+
Whereas a ``Release`` build prints the values:
491+
492+
.. code-block::
493+
494+
61 << -1 = 2147483648
495+
61 << 4 = 976
496+
61 << 40 = 15616
497+
498+
This shows **Reason 1** prominently.
499+
500+
**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.
501+
502+
.. code-block:: rust
503+
504+
let bits : u32 = 61;
505+
let shifts = vec![-1, 4, 40];
506+
507+
for sh in shifts {
508+
println!("{bits} << {sh} = {}", bits << sh);
509+
}
510+
511+
.. compliant_example::
512+
:id: compl_ex_3BkrRwwK5zhX
513+
:status: draft
514+
515+
As seen in the example below:
516+
517+
518+
* Both ``Debug`` and ``Release`` give the same exact output, which addresses **Reason 1**.
519+
* Out-of-range shifts are caught and avoided before they happen.
520+
*
521+
The output shows what's happening:
522+
523+
.. code-block::
524+
525+
Performing 61 << -1 would be meaningless and crash-prone; we avoided it!
526+
61 << 4 = 976
527+
Performing 61 << 40 would be meaningless and crash-prone; we avoided it!
528+
529+
The output shows how this addresses **Reason 2**.
530+
531+
.. code-block:: rust
532+
533+
let bits : u32 = 61;
534+
let shifts = vec![-1, 4, 40];
535+
536+
for sh in shifts {
537+
if 0 <= sh && sh < 32 {
538+
println!("{bits} << {sh} = {}", bits << sh);
539+
} else {
540+
println!("Performing {bits} << {sh} would be meaningless and crash-prone; we avoided it!");
541+
}
542+
}

0 commit comments

Comments
 (0)