Skip to content
71 changes: 71 additions & 0 deletions src/en/04_language.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,74 @@ the process.
<mark>TODO</mark>: cyclomatic complexity of the macro expanded code, recursion
limits, ...
-->

## Value displacement

Values in Rust may have three distinct semantics when considering value displacement:

- Either it has _move_ semantics, this behavior is the default one.
- Or it has move _move_ semantics plus the _drop_ semantics, i.e. its type implements the `Drop` trait.
- Or it has copy semantics, by having its type implementing the `Copy` trait.

However, some problem may appear when using the `std::ptr::read` function.
According to the [documentation](https://doc.rust-lang.org/std/ptr/fn.read.html), the function:
> Reads the value from src without moving it. This leaves the memory in src unchanged.

To be clear, this function is somehow copying the value pointed by the raw pointer, regardless of its type semantics.
This is a dangerous behavior as it can lead to double-drop and, in some cases, to double-free.

To illustrate the copy on a type having the move semantics let's consider the following snippet:

```rust
# use std::ops::Drop;
#
#[derive(Debug)]
struct MyStruct(u8);

impl Drop for MyStruct {
fn drop(&mut self) {
# println!("---Dropping an object---\nBefore zeroing: {} @ {:p}", self.0, &self.0 as *const u8);
self.0 = 0;
# println!("After zeroing: {} @ {:p}", self.0, &self.0 as *const u8);
}
}

fn main(){
let obj: MyStruct = MyStruct(100);
let ptr: *const MyStruct = &test as *const MyStruct;
println!("{:?} @ {:p}", unsafe { std::ptr::read(ptr) }, ptr);
}
```

We can see that a second object is created by the call to `std::ptr::read`, i.e. a copy of a _non-copy_ object is performed.
Here, the problem is not really a huge one, except if some cleaning outside of the drop implementation is performed (as it is recommended): some sensitive data might survive within the memory.

However, this behavior might cause some resilience issue when this function is used with a raw pointer pointing to some data allocated on the heap with move semantic as illustrated by this snippet:

```rust
# use std::boxed::Box;
# use std::ops::Drop;
#
#[derive(Debug)]
struct MyStructBoxed(Box<u8>);

impl Drop for MyStructBoxed {
fn drop(&mut self) {
# println!("---Dropping an object---\nBefore zeroing: {} @ {:p}", self.0, self.0);
let value: &mut u8 = self.0.as_mut();
*value = 0;
# println!("After zeroing: {} @ {:p}", self.0, self.0);
}
}

fn main(){
let test: MyStructBoxed = MyStructBoxed(Box::new(100));
let ptr: *const MyStructBoxed = &test as *const MyStructBoxed;
println!("{:?} @ {:p}", unsafe { std::ptr::read(ptr) }, unsafe { &*ptr }.0 );
}
```

> ### Rule {{#check LANG-RAW-PTR | Avoid the use of `std::ptr::read` }}
>
> `std::ptr::read` might have undesired side effect depending on the way the type of the raw pointer is moving through different context.
> It is preferable to use the operation of referencing/dereferencing (`&*`) to avoid those side effect.
71 changes: 71 additions & 0 deletions src/fr/04_language.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,74 @@ d'autres raisons.
<mark>TODO</mark> : complexité cyclomatique du code macro-expansé, limites de
récursion, ...
-->

## Déplacement de valeurs

Rust propose trois différents modes de déplacement de valeur:

- Soit par *déplacement*, qui est le comportement par défaut.
- Ou par *déplacement* plus un *drop* de la valeur si le type implément le trait `Drop`.
- Ou par *copie*, si son type implémente le trait `Copy`

Cependant, des problèmes peuvent être constater lors de l'utilisation de la fonction `std::ptr::read`.
Selon la [documentation](https://doc.rust-lang.org/std/ptr/fn.read.html), cette fonction:
> Lis la valeur pointée par src sans la déplacer. Ce qui laisse la mémoire pointée intact.

Cette fonction est donc responsable d'effectuer une copie de la valeur pointée, indépemment du mode de déplacement du type en question.
Ce comportement peut être dangeureux car il peut mener à des *double-free* et/ou des *double-drop*.

Pour illustrer ce comportement, considérons le code suivant :

```rust
# use std::ops::Drop;
#
#[derive(Debug)]
struct MyStruct(u8);

impl Drop for MyStruct {
fn drop(&mut self) {
# println!("---Dropping an object---\nBefore zeroing: {} @ {:p}", self.0, &self.0 as *const u8);
self.0 = 0;
# println!("After zeroing: {} @ {:p}", self.0, &self.0 as *const u8);
}
}

fn main(){
let obj: MyStruct = MyStruct(100);
let ptr: *const MyStruct = &test as *const MyStruct;
println!("{:?} @ {:p}", unsafe { std::ptr::read(ptr) }, ptr);
}
Comment on lines +256 to +260
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Donner le résultat obtenu après exécution du programme

```

On peut observer qu'un deuxième objet a implicitement été créé lors de l'appel à `std::ptr::read`, i.e. une copie d'un objet *non copiable* est effectuée.
Ici, le problème n'est pas réellement dangeureux, sauf si du nettoyage de mémoire en dehors de l'implémentation de `drop` est réalisée (tel que recommandé): des données sensibles peuvent donc persister en mémoire.

Mais ce comportement peut causer des problèmes de résilience lors de l'utilisation de cette fonction avec un *raw pointer* pointant vers des données allouées sur le tas avec un mode de déplacement par déplacement, tel qu'illustré ici :

```rust
# use std::boxed::Box;
# use std::ops::Drop;
#
#[derive(Debug)]
struct MyStructBoxed(Box<u8>);

impl Drop for MyStructBoxed {
fn drop(&mut self) {
# println!("---Dropping an object---\nBefore zeroing: {} @ {:p}", self.0, self.0);
let value: &mut u8 = self.0.as_mut();
*value = 0;
# println!("After zeroing: {} @ {:p}", self.0, self.0);
}
}

fn main(){
let test: MyStructBoxed = MyStructBoxed(Box::new(100));
let ptr: *const MyStructBoxed = &test as *const MyStructBoxed;
println!("{:?} @ {:p}", unsafe { std::ptr::read(ptr) }, unsafe { &*ptr }.0 );
}
```

> ### Règle {{#check LANG-RAW-PTR | Éviter d'utiliser `std::ptr::read`}}
>
> `std::ptr::read` peut avoir des effets de bords indésirables en fonction du mode déplacement du type pointé par le *raw pointer* source.
> Il est donc préférable d'utiliser l'opération de référencement/déréférencement (`&*`) pour les éviter.