
Vérificateur pour les durées de vie et autres types de raffinement
Checkeur de
Lifetimes et autres
Raffinements de types
pour Zig
Vidéo : https://www.youtube.com/watch?v=mf0WzTOe-40 Sponsoring : https://buymeacoffee.com/dnautics
discussion sur hn : https://news.ycombinator.com/item?id=42923829
discussion sur lobste.rs : https://lobste.rs/s/9sitsj/clr_checker_for_lifetimes_other
vidéo de démo en direct : https://www.youtube.com/watch?v=ZY_Z-aGbYm8
Ce projet crée un transpileur Zig pour le compilateur Zig, qui transforme AIR (Abstract Intermediate Representation) en code source Zig effectuant une analyse statique à la compilation. L'analyseur généré détecte les problèmes de sécurité mémoire comme l'utilisation avant affectation, l'utilisation après libération, les échappées de pointeur de pile, ainsi que les comportements indéfinis spécifiques à Zig (assertions de non-nullité, violations d'union étiquetée, mauvais usage de fieldParentPtr).
L'objectif est d'apporter les garanties de sécurité mémoire de Rust à Zig via l'analyse statique d'AIR, sans modifier le langage lui-même.
CLR dépend d'une version forkée du compilateur Zig (incluse comme sous-module dans zig/) qui ajoute le support du routage d'AIR vers des plugins externes. Lorsqu'il est invoqué avec -ofmt=air -fair-out=<plugin.so>, le compilateur charge la bibliothèque partagée spécifiée et lui passe l'AIR généré pour traitement.
CLR vise à pousser les programmes vers des modèles de cycle de vie explicites et vérifiables localement, et non simplement à reconnaître tout programme Zig techniquement valide. Lorsque deux représentations sont possibles, CLR préfère celle qui rend l'état des ressources visible dans la structure du type et du flux de contrôle.
Par exemple, évitez de fermer conditionnellement un descripteur de fichier non optionnel :
const file = try std.fs.cwd().openFile(path, .{});
if (should_close) {
file.close(); // Mauvais : le fichier est ambiguëment ouvert après cette branche.
}
Préférez représenter la possession conditionnelle avec un optionnel :
var file: ?std.fs.File = null;
if (should_open) {
file = try std.fs.cwd().openFile(path, .{});
}
if (file) |open_file| {
open_file.close();
}
Fermer conditionnellement un descripteur non optionnel laisse son cycle de vie ambigu après la branche. La politique prévue de CLR est de rejeter ce modèle plutôt que de porter un état permanent « peut-être fermé ».
Le même principe s'applique aux pointeurs alloués. Ne libérez pas via un pointeur dérivé :
const allocation = try allocator.alloc(u8, size);
const payload = allocation[header_size..];
allocator.free(payload); // Mauvais : payload n'est pas la base de l'allocation.
Gardez le pointeur de base de l'allocation disponible pour la désallocation, et utilisez les pointeurs dérivés uniquement pour l'accès :
const allocation = try allocator.alloc(u8, size);
defer allocator.free(allocation);
const payload = allocation[header_size..];
use(payload);
Libérer un pointeur de champ, une sous-tranche ou un pointeur produit par arithmétique est rejeté à moins qu'une règle interne documentée ne rétablisse la provenance de la base d'allocation.
Ces politiques sont strictes par défaut car elles produisent un code avec des cycles de vie de ressources plus simples et plus faciles à réviser. Un futur mécanisme d'annotation unsafe permettra à certains GID ou opérations de se retirer de certaines analyses. Cela supportera du code qui accepte délibérément une vérification plus faible en échange de performances, sans affaiblir le modèle par défaut pour le reste du programme.
Il s'agit d'une réécriture active en Zig de la preuve de concept originale basée sur Elixir. L'implémentation Zig se charge comme un plugin de compilateur et analyse AIR directement.
Actuellement implémenté :
std.mem.Allocator :
create/destroy - allocation d'élément uniquealloc/free - allocation de tranche (y compris alignedAlloc, allocSentinel, etc.)realloc/remap - réallocation de tranche avec suivi de l'ancienne tranche libéréedupe/dupeZ - duplication de trancheinit/deinit/allocator - cycle de vie complet de l'arènestd.process.args,
std.mem.asBytes, std.HashMap et les APIs d'allocateur/fichierstd.HashMap avec identité canonique de métadonnées/clé/valeur
à travers put, get, getPtr et itération de valeursposix.open/close/dup/dup2/socket/accept/epoll_create/pipePrévu (voir LIMITATIONS.md pour les détails) :