Interpréteur µRust 2 : gestion mémoire¶
Démarrage¶
Récupérez le squelette de code et exécutez le programme.
Exemple
shell$ cargo run
µRust # 1+1
- : isize = 2
µRust # let x = Ptr::new()
[CRASH]
shell$
Ce squelette correspond à un corrigé très incomplet du premier TP. Il est recommandé de travailler directement avec ce squelette, et à la fin de fusionner le travail de chacun des TP en un seul projet.
Espaces de noms et instructions let¶
- Ajoutez un fichier
memorycell.rs
et définissez pour commencer une version très simple de cellule mémoire contenant une valeur
struct MemoryCell {
mutable: bool,
value: Value,
}
Notes:
- laissez les champs privés et faites des méthodes
is_mutable
,get_value
,set_value
, le typeMemoryCell
va encore bien évoluer après get_value
etset_value
peuvent lever des erreurs (leur type de retour est unResult<??,EvaluationError>
). Pourget_value
, il n'y a aucune erreur pour le moment, mais cela viendra après.- vous utiliserez
None
comme argument pour construire les erreursNotMutable
,NonAllocatedCell
etNonInitializedValue
(l'expression n'étant pas connue pour le moment)
- Modifiez le fichier
src/namespace.rs
pour que laHashMap
associe des identifiants à desMemoryCell
.
pub struct NameSpace(HashMap<Identifier, MemoryCell>);
- Vous ne devez pas changer la signature des méthodes existantes (
find
,set
,declare
) mais vous devez modifier leur code pour que tout compile. - Dans
eval.rs
, complétez letodo
du casInstruction:Let
de la méthodeexec
. - Toujours dans
eval.rs
, complétez letodo
du casExpression:Identifier
de la méthodeeval
. - Compilez et testez
shell$ cargo run
µRust # let x = 0
x : isize = 0
µRust # x + 1
- : isize = 1
^D
shell$
Adresses et &
¶
- Dans un fichier
memory.rs
, définissez le type des adresses du tas et de la pile comme vu en cours.
enum Address {
StackAddress(usize, Identifier),
HeapAddress(usize)
}
Implémentez le trait Display
(ex: @2
, @[3,x]
).
Dans
namespacestack.rs
, ajoutez la méthodeget_address
comme vu en cours.Dans
eval.rs
, ajoutez la méthodeeval_to_address
pour les expressions (voir cours). Votre méthode lèvera une erreur si l'expression n'est pas un identifiant, et sinon appeleraget_address
pour renvoyer son résultat. Vous pouvez laisser untodo
pour le cas où l'expression est unDeref
ou unNewPtr
(vous traiterez ces cas après).Dans
value.rs
, ajoutez le cas où la valeur est un pointeur, et modifiez les méthodes pour tenir compte du changement. Vous devrez aussi modifiertype.rs
.
pub enum Value {
Integer(isize),
Boolean(bool),
Unit,
Pointer(Address),
}
- Dans
eval.rs
, complétez letodo
du casAmpersAnd
de la méthodeeval
. Testez.
shell$ cargo run
µRust # let x = 0
x : isize = 0
µRust # &x
- : Ptr = @[0, x]
µRust # {let x = -8; &x}
- : Ptr = @[1, x]
µRust # {let y = -8; &x}
- : Ptr = @[0, x]
^D
shell$
Tas¶
- Mettez à jour
memorycell.rs
pour prendre en compte la possibilité qu'une cellule ne soit pas allouée ou pas initialisée, comme vu en cours.
enum MemoryCell {
NotAllocated,
AllocatedCell(AllocatedCell),
}
struct AllocatedCell {
mutable: bool,
value: Option<Value,
}
Notes:
- une cellule non allouée n'est pas mutable
- une cellule non initialisée est mutable
- Ajoutez un fichier
heap.rs
et déclarez y le typeHeap
ainsi qu'un constructeurnew
et la méthodemalloc
vue en cours.
struct Heap(Vec<MemoryCell>)
impl Heap { /*... */ }
- Dans
memory.rs
, déclarez le type
struct Memory {
stack: NameSpaceStack,
heap: Heap
}
Attention, gros refactoring!
Dans eval.rs
, modifiez la signature des méthodes eval_to_address
, eval
, et exec
pour prendre en argument un emprunt mutable sur la mémoire
(et non juste sur la pile). Par exemple,
pub fn eval(&self, mem: &mut Memory) -> Result<Value, EvalError> { /* ... */ }
Il vous faut aussi mettre à jour main.rs
. Vérifiez que tout compile.
- Dans
eval.rs
, traitez le todo du casNewPtr
. Testez.
shell$ cargo run
µRust # Ptr::new()
- : Ptr = @0
µRust # let x = Ptr::new()
x : Ptr = @1
- Dans
eval.rs
, traitez le cas duFree
. Testez.
shell$ cargo run
µRust # let x = Ptr::new()
x : Ptr = @0
µRust # let y = Ptr::new()
y : Ptr = @1
µRust # free(x)
- : unit = ()
µRust # let z = Ptr::new()
z : Ptr = @0
Deref et WriteAt¶
Dans eval.rs
, modifiez les todo de Deref
et WriteAt
. Testez.
shell$ cargo run
µRust # let ptr = Ptr::new()
- : Ptr = @0
µRust # *ptr = 42
- : unit = ()
µRust # let x = *ptr
x : isize = 42
µRust # x
- : isize = 42
µRust # *x
Evaluation Error: Type mismatch in expression `*x`. Expected: Ptr. Found: isize
µRust # x = 43
Evaluation Error: Cell at `x` is not mutable.
µRust # free(ptr)
- : unit = ()
µRust # *ptr
Evaluation Error: Cell at `*ptr` is not allocated.
µRust # let ptr2 = Ptr::new()
ptr2 : Ptr = @0
µRust # *ptr2
Evaluation Error: Value in `*ptr2` is not initialized.
Horodatage et détection des use after free¶
Implémentez la détection de use after free par horodatage de cellule et de pointeurs, comme vu en cours.
Testez à l'aide des exemples vus en cours.
Détection des fuites mémoires¶
Implémentez la détection de fuites mémoires (et le GC) basé sur le marquage des cellules accessibles (voir cours).
Vous appelerez les deux méthodes mark_reachable_cells
et unmark_and_sweep
après chaque évaluation d'instruction dans main.rs
.
Possession, sémantique MOVE, RAII, et GC¶
Implémentez la sémantique MOVE, le RAII, et le "GC" basé sur la possession, voir cours.