En fait cela part d'un constat assez simple: ~70% des CVE sont liées à des erreurs mémoire (buffer overflow, use after free, etc) et concernent principalement C et C++. Pour ne rien arranger, ce sont aussi les plus critiques. Les autres langages cités n'ont pas ce problème puisqu'ils font systématiquement une vérification des bornes pour lancer une exception et utilisent un GC. En gros, on perd en contrôle ce qu'on gagne en sécurité.
Je conseille la lecture de ce papier: Secure by Design: Google's Perspective on Memory Safety.
Du coup, pourquoi un focus sur Rust ? Parce qu'il se veut sécurisé par design sans compromit sur les performances. Par conséquent, il joue dans la même cour que C ou C++. Quand on reste dans le monde safe de Rust, le langage et la bibliothèque standard ont de forte garanties sur les bugs mémoire. Quand on a besoin de chose spécifique, on peut utiliser des blocs unsafe. En prenant les catégories listées dans le papier:
- Spatial Safety bugs (e.g. “buffer overflow”, “out of bounds access”): via les fonctions qui retournent un Option plutôt qu'une valeur hors borne (le pattern matching et la destructuration aident aussi à ne pas lire la valeur dans l’Option sans vérifier qu'il est valide). Si on ne veut pas de vérification sur les accès (et donc pas d'Option), on passe en unsafe (gros warning) ou on utilise des itérateurs (le système d'emprunt garantissant qu'il reste valide, par exemple, impossible d’agrandir un Vec si on itère dessus, car cela pourrait invalider l'itérateur).
- Temporal Safety: vérifier par le compilateur.
- Type Safety: on est dans du unsafe.
- Initialization Safety: le langage permet de déclarer des variables sans les initialiser, mais toutes les branches qui la lisent doivent l'initialiser (vérifier par le compilateur). Pour des workflow complexe, on est dans du unsafe.
- Data-Race Safety: en grande partie vérifié par le compilateur via les traits Send et Sync et comment sont construit les primitives de synchro (valeur à l'intérieur de l'objet de synchronisation plutôt qu'a côté).
Alors qu'en C ou C++ il n'y a aucune de ces garanties, que se soit côté compilateur ou la bibliothèque standard, on est tout le temps en mode "unsafe". Même les retours via Option que pourrait adopter la bibliothèque standard sur les accès serait une vaste blague en C++: trop de limitation et de facilité à lire sans vérification en absence de pattern matching (je crois que les propositions se battent toujours sur la syntaxe).
Tu es sûr pour export de C++03 ? J'ai l'impression que c'était surtout une facilité pour réduire les temps de compilation des templates. Mais avec plein de contrainte. Éjecté en C++11. Après il y a eu des expérimentations par Google sur des modules, un grand vide, puis finalement des propositions pour avoir quelque chose en C++20 que les compilateurs commencent à peine à bien supporter.
Par contre, je n'ai pas souvenir d'une normalisation du format. Pour moi, ce n'était pas voulu, chaque compilateur étant libre de faire comme il veut. Mais j'ai vu passé des trucs sans lire, probablement concernant le mapping source <-> pré-compilé (ça semble assez galère de gérer les dépendances à la main, clang fournit des outils spécifiques pour ça).
Personnellement, je suis plutôt contre avoir une bibliothèque standard intégrant plein de truc. Plus il y a de domaine couvert par une SL, plus il y a besoin d'expertise. Faire quelque chose qui fonctionne est "facile", mais faire quelque d'efficace l'est beaucoup moins. Je vais prendre les regexes comme exemple.
- Il faut choisir une syntaxe ou un standard. Il y en a 2 assez connu: PCRE et POSIX. À cela s'ajoute toutes les variantes. À partir du moment une syntaxe existe, elle ne peut presque plus évoluer puisque le pattern aurait un comportement différent. Par conséquent, on se trouve lié à la version d'un standard -> évolution lente, problème avec des lib header only.
- Il faut aussi définir quel est le cadre d'utilisation, ce qui va beaucoup influencer l'implémentation (et l'interface). Par exemple, préfère-t-on une recherche en temps constant avec une mémoire stable, quitte à ce qu'elle ne soit pas la plus rapide ou une implémentation le plus rapide possible avec des corners cases ? Pour info, un mauvais pattern avec PCRE peut prendre plusieurs secondes sur un texte d'une centaine de caractères.
- Il faut des personnes compétentes sur le sujet. On peut faire une implémentation triviale, c'est ce qu'à fait libstdc++. Résultat, a* sur quelques milliers de 'a' fait un dépassement de pile.
- Maintenant qu'on a une implémentation, il faut une stabilité de l'ABI (parce que C++...) -> gros frein sur les modifications.
Peaufiner un moteur regex demande beaucoup d'effort, il suffit de voir les gros existant pour s'en convaincre (PCRE2, RE2, hyperscan / vectorscan). Mais c'est pareil pour les parseurs json (nlohmann::json, glaze, daw_json_link, simdjson, etc) et ainsi de suite. Chacun vient avec ses forces et faiblesses, mais en mettant dans le standard, on se retrouve avec quelque chose qui évolue beaucoup plus lentement, beaucoup moins facilement et avec très peu de spécialiste travaillant dessus (voir pas du tout).
En plus, comme cela se voudra à usage général, on se retrouve vite avec quelques choses qui pourrait être beaucoup plus efficace en changeant de lib. Mais si c'est dans le standard, la plupart des dév feront un choix par commodité. À contrario, avec un gestionnaire de lib qui se tient, on peut facilement prendre n'importe quoi par commodité (pas spécialement plus d'effort), mais les mainteneurs de compilateur et le standard ont beaucoup moins de charge de travail.







(<- si j'ai bien tout compris)
Répondre avec citation
... le C++ devient 1 b*rdel











. Mais sinon, C++ aussi évolue, comme tous les langages. Les mises à jour de Rust sont un peu plus fréquentes, mais pas énorme non plus.

Partager