You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/coding-guidelines/expressions.rst
+141Lines changed: 141 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -399,3 +399,144 @@ Expressions
399
399
/* ... */
400
400
}
401
401
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!");
0 commit comments