Roberto Bagnara, Abraham Bagnara et Federico Serafini trois chercheurs de l’Université de Parme en Italie ont produit un essai dans lequel les chercheurs présentent les avantages de Rust, en C.
Notons que Rust est un langage de programmation compilé multiparadigme, conçu par Graydon Hore alors employé chez Mozilla Research. Utilisé par plusieurs grandes entreprises et par de nombreux développeurs dans le monde, Rust est devenu le langage de base pour certaines des fonctionnalités indispensables du navigateur Firefox et de son moteur Gecko, ainsi que pour le moteur Servo de Mozilla.
Avec Rust, il est possible de développer des pilotes de périphériques, des systèmes embarqués, des systèmes d'exploitation, des jeux, des applications web, et bien d'autres choses encore. Des centaines d'entreprises dans le monde entier utilisent Rust en production pour des solutions multiplateformes et économes en ressources. Des logiciels connus et appréciés, comme Firefox, Dropbox, et Cloudflare, utilisent ce langage. De la startup à la multinationale, du système embarqué au service web à haute disponibilité, Rust serait une excellente solution.
Le langage C est un langage de programmation qui appartient au paradigme de programmation impérative. Inventé au début des 1970 dans les Laboratoires Bell pour aider la programmation du système Unix, C est devenu un des langages les plus utilisés. Il n’est pas consacré qu’à la programmation système. C’est un langage compilé, typé avec des instructions bas-niveau. Voici, ci-dessous, un exemple de programme :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include #include int somme(int); int main(int argc, char **arg){ int i = 10; printf("La somme des %d entiers est %d \n", i, somme(i)); return EXIT_SUCCESS ; } int somme(int i){ int resultat = 0; for (int k = 0; k <= i; k++) resultat += k; return resultat; } |
Selon les chercheurs, l’effervescence actuelle autour de Rust révèle que l'industrie est prête à accepter que les programmeurs prennent une approche plus disciplinée en adoptant un typage de données fort renforcé par des annotations de programme. C'est le véritable changement de perspective : la technologie pour assister cette nouvelle attitude dans la création de code C avec des garanties d'intégrité sans précédent est disponible, dans son essence, depuis des décennies.
DE C À C-RUSTED
Même si le langage de programmation C est (pour des raisons d'efficacité uniquement) statiquement typé, les types ne définissent que la représentation interne des données et rien de plus : les types en C n'offrent pas aux programmeurs un moyen d'exprimer les propriétés non triviales des données qui sont liées à la logique du programme. Par exemple :
- un fichier ouvert a le même type qu'un fichier fermé ;
- une ressource ou une transaction a le même type indépendamment de son état ;
- une référence exclusive et une référence partagée à une ressource sont indiscernables ;
- un nombre entier avec des valeurs spéciales qui représentent des conditions d'erreur est indiscernable d'un nombre entier ordinaire.
En C-rusted, toutes ces différences peuvent être exprimées de manière incrémentielle, ce qui permet d'améliorer la documentation, la lisibilité et la réutilisation du code. Plus important encore, cela permet à C-rusted Analyzer, qui est basé sur la plateforme de vérification logicielle, de vérifier l'exactitude avec n'importe quelle architecture, et pour chaque compilateur.
Pour illustrer, les chercheurs présentent le programme ci-dessous, que le compilateur GNU C compile sans aucun avertissement, même avec un niveau d'avertissement très élevé. Le programme contient beaucoup d'erreurs, y compris l'incrément numérique sans signification.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include #include #include #define BUFSIZE (100U) extern void process(char *string); int main(int argc, const char *argv[]) { if (argc != 2) return 1; int fd = open(argv[1], O_RDONLY); char *buf = (char *) malloc(BUFSIZE); ++fd; ssize_t bytes = read(fd, buf, BUFSIZE - 1U); buf[bytes] = '\0'; process(buf); return 0; } |
Un programme C compilant sans avertissement avec gcc -c -std=c18 -Wall -Wextra -Wpedantic
Lorsqu'il est soumis à l'analyseur C-rusted, le même programme déclenche plusieurs diagnostics, résumés dans le tableau ci-dessous où la notation wn sur un point du programme signifie que l'avertissement indiqué est donné à ce point du programme.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include #include #include #define BUFSIZE (100U) extern void process(char *string); int main(int argc, const char *argv[]) { if (argc != 2) return 1; int fd = open(argv[1], O_RDONLY); char *buf = (char *) malloc(BUFSIZE); ++fdw1; ssize_t bytes = read(fdw2, bufw3 , BUFSIZE - 1U); buf[bytes] w4w5 = '\0'; process(bufw6); returnw7w8 0; } |
w1 : Après avoir reçu la valeur de retour de open(), fd contient un descripteur de fichier ou la valeur erronée -1 : fd ne peut pas être incrémenté ;
w2, w3, w4, w5 : fd n'est pas un descripteur de fichier valide, bytes peut être -1, buf peut être NULL ;
w6 : Est-ce que process() prend la propriété, c'est à dire peut/doit-il désallouer son argument ?
w7 : Le descripteur de fichier contenu dans fd est certainement fui ici ;
w8 : La mémoire pointée par buf est possiblement divulguée ici ;
L'analyseur C-rusted donne plusieurs avertissements sur le même programme C
Une version beaucoup plus saine du programme est illustrée ci-dessous :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include #include #include // Include C-rusted declarations, e.g., for e_hown. #include #define BUFSIZE (100U) // The actual parameter must be a valid (hence, non-null) pointer to a char array // in the heap of which process() will take ownership, which implies the caller // must have ownership for otherwise it would be unable to pass it on. extern void process(char * e_hown string); int main(int argc, const char *argv[]) { if (argc != 2) return 1; int fd; // `fd` value is indeterminate. fd = open(argv[1], O_RDONLY); // `fd` value is either the erroneous value -1 or an open file descriptor. if (fd == -1) return 1; // `fd` value is definitely an open file descriptor. char *buf = (char *) malloc(BUFSIZE); // `buf` value is either null or points to a heap-allocated char array. if (buf == NULL) return 1; // `buf` value definitely points to a heap-allocated char array. ssize_t bytes = read(fd, buf, BUFSIZE - 1U); // `bytes` value is either the erroneous value -1 or the number of bytes // read into `buf`. if (bytes == -1) return 1; // `bytes` value is definitely the number of bytes read into `buf`. buf[bytes] = '\0'; // process() takes ownership of `buf` and will deallocate it: no memory leak. process(buf); // close() properly closes the file descriptor contained into `fd`: // no file descriptor leak. close(fd); // `fd` value is an ordinary integer and cannot be used as a file descriptor // (it can be overwritten of course). // ... return 0; } |
Elle montre clairement que, bien que le type (statique) de fd soit int, pendant toute sa durée de vie, la valeur de fd a des propriétés qui changent tout au long du corps de la fonction ; de même pour buf et les bytes. En d'autres termes, le système de types C est capable de suivre les propriétés dynamiques des objets.
Selon les chercheurs, C-rusted est une solution pragmatique et rentable pour élever le jeu de la programmation C à des garanties d'intégrité sans précédent, sans renoncer à ce que l'écosystème C offre aujourd'hui. « C'est-à-dire continuer à utiliser le C, exactement comme avant, en utilisant les mêmes compilateurs et les mêmes outils, le même personnel, mais en ajoutant progressivement au programme les informations requises pour démontrer la correction, en utilisant un système d'annotations qui n'est pas basé sur la logique mathématique et qui peut être enseigné aux programmeurs en une semaine de formation. »
Ce n'est que lorsque l'ajout d'annotations montre la présence d'un problème qu'une modification du code sera nécessaire afin de corriger le bug latent qui est alors visible : dans tous les autres cas, le comportement du code restera exactement le même. Cette technique n'est pas nouvelle : elle s'appelle le typage progressif et consiste à ajouter des informations qui ne sont pas nécessaires à la compréhension du code, mais qui permettent de vérifier son exactitude.
Source : Roberto Bagnara, Abraham Bagnara et Federico Serafini, chercheurs de l’Université de Parme
Et vous ?
Quel est votre avis sur le sujet ?
Voir aussi :
Rust 1.65 est disponible, elle apporte les types génériques associés stables, ils permettent d'avoir des génériques (type, lifetime, ou const) sur les types associés
La prise en charge de Rust pour le développement du noyau Linux fait l'objet d'une nouvelle série de discussions, après une proposition de RFC