En Rust, panic! est un crash délibéré. Le programme affiche une trace de pile et se termine. Dans le code de test, les panics sont le mécanisme standard pour les assertions -- chaque assert!, assert_eq! et unwrap() est un panic potentiel, et c'est par conception. Dans le code de production, les panics sont presque toujours des bugs. Ils transforment ce qui devrait être une erreur gérée en crash de processus, et dans un runtime de langage comme FLIN, un crash signifie que toutes les applications tournant sur ce runtime tombent.
L'audit de FLIN a catalogué chaque appel panic dans 186 252 lignes de code Rust. Le nombre brut était alarmant : plus de 800 sites de panic dans le codebase. Le nombre raffiné était rassurant : seuls 5 étaient dans des chemins de code de production. Les restants étaient des assertions de test, ce qui est exactement là où les panics ont leur place.
Cet article retrace ces cinq panics de production -- où ils vivent, pourquoi ils existent, s'ils devraient être éliminés, et à quoi ressemble la stratégie d'élimination.
Le recensement des panics
L'audit a compté les appels panic à trois niveaux : les invocations explicites de la macro panic!(), les appels .unwrap() sur les types Result et Option, et les appels .expect() qui fournissent un message avant de paniquer. Ensemble, ceux-ci forment l'ensemble complet des sites de panic dans le codebase.
Module Explicit panic! .unwrap() .expect() Total
codegen/bytecode.rs 2 0 0 2
vm/vm.rs 48 0 0 48
vm/renderer.rs 9 0 0 9
parser/parser.rs ~600 0 0 ~600
tests/** 0 ~120 ~20 ~140
-----
TOTAL ~659 ~120 ~20 ~799Les 600 panics du parser étaient tous dans le code de test (ligne 8866 et au-delà). Les 140 panics du répertoire tests étaient par définition du code de test. Il restait les modules codegen, VM et renderer avec 59 sites de panic de production à évaluer.
Les cinq panics de production
Après avoir filtré les panics qui n'étaient accessibles que par des chemins de code internes (jamais déclenchés par du code FLIN écrit par l'utilisateur), l'audit a identifié cinq panics qu'un développeur FLIN pourrait théoriquement déclencher :
PANIC-001 : dépassement du pool de constantes
rust// codegen/bytecode.rs line 1711
pub fn add_constant(&mut self, value: Value) -> u16 {
if self.constants.len() >= u16::MAX as usize {
panic!("Constant pool overflow: too many constants (max {})", u16::MAX);
}
self.constants.push(value);
(self.constants.len() - 1) as u16
}Un utilisateur peut-il le déclencher ? Oui, mais uniquement avec un programme FLIN extraordinairement grand -- un qui définit plus de 65 535 constantes uniques. Une application FLIN typique utilise quelques centaines de constantes. Atteindre 65 535 nécessiterait un effort délibéré.
Devrait-ce être un panic ? Possiblement oui. C'est une limite dure du format bytecode. La réponse appropriée est de refuser la compilation, ce qu'un panic fait effectivement (bien qu'un message d'erreur approprié serait mieux).
Verdict : Convertir de panic! à un CompileError::ConstantPoolOverflow qui produit un message d'erreur convivial indiquant au développeur de découper son programme en modules.
PANIC-002 : assertion de type float
rust// codegen/bytecode.rs line 2709
fn read_float_constant(&self, index: u16) -> f64 {
match &self.constants[index as usize] {
Value::Float(f) => *f,
other => panic!("Expected Float constant at index {}, got {:?}", index, other),
}
}Un utilisateur peut-il le déclencher ? Non, dans des circonstances normales. Si cette assertion se déclenche, cela signifie que le compilateur a un bug.
Verdict : Convertir en Result pour un rapport d'erreur plus propre, mais marquer comme priorité basse puisque la condition devrait être inatteignable.
PANIC-003 à PANIC-050 : assertions de types de la VM
La VM contient 48 appels panic, presque tous suivant le même pattern :
rust// vm/vm.rs -- typical type assertion pattern
fn builtin_string_upper(&mut self) -> Result<Value, VmError> {
let value = self.pop()?;
let s = match value {
Value::Object(id) => self.get_string(id)?.to_uppercase(),
Value::Text(s) => s.to_uppercase(),
_ => panic!("upper() requires a string argument, got {:?}", value),
};
Ok(Value::text(s))
}Un utilisateur peut-il les déclencher ? Oui. Si un développeur FLIN écrit upper(42), la VM recevra une valeur entière là où elle attend une chaîne, et le panic se déclenchera.
Devrait-ce être des panics ? Absolument pas. Ce sont les panics les plus importants à éliminer. Un développeur FLIN passant le mauvais type à une fonction intégrée devrait recevoir une erreur d'exécution claire, pas un crash de processus.
Verdict : Convertir tous les 48 en retours VmError::TypeError. C'est la tâche d'élimination de panics de plus haute priorité.
rust// AFTER: proper error on type mismatch
fn builtin_string_upper(&mut self) -> Result<Value, VmError> {
let value = self.pop()?;
let s = self.as_string(&value)
.map_err(|_| VmError::TypeError {
function: "upper",
expected: "text",
got: value.type_name(),
})?;
Ok(Value::text(s.to_uppercase()))
}PANIC-051 à PANIC-059 : assertions du moteur de rendu
Le moteur de rendu contient 9 appels panic, principalement dans l'évaluation d'expressions et la génération HTML.
Un utilisateur peut-il les déclencher ? Oui. Les expressions de templates qui s'évaluent à des types inattendus peuvent atteindre ces sites de panic.
Verdict : Convertir en dégradation gracieuse -- rendre un marqueur d'erreur visible dans la sortie HTML et journaliser un avertissement, mais ne jamais crasher le processus serveur.
La stratégie d'élimination
L'audit a proposé une stratégie à trois niveaux pour l'élimination des panics :
Niveau 1 : convertir en VmError (48 panics de la VM). Chaque assertion de type dans les gestionnaires de fonctions natives de la VM devrait retourner Result<Value, VmError> au lieu de paniquer.
Niveau 2 : convertir en dégradation gracieuse (9 panics du renderer). Les panics du renderer devraient être remplacés par un rendu de secours qui produit une sortie visible mais sans crash.
rust// Tier 2: graceful degradation in renderer
fn extract_text_content(&self, value: &Value) -> String {
match value {
Value::Text(s) => s.clone(),
Value::Object(id) => {
self.vm.get_string(*id)
.unwrap_or_else(|_| format!("[render error: invalid object {}]", id))
}
Value::Int(n) => n.to_string(),
Value::Float(f) => f.to_string(),
Value::Bool(b) => b.to_string(),
Value::None => String::new(),
other => format!("[render error: unexpected {:?}]", other.type_name()),
}
}Niveau 3 : convertir en CompileError (2 panics du codegen). Le dépassement du pool de constantes et l'assertion de type float devraient produire des erreurs de compilation plutôt que des crashs à l'exécution.
Les 120 panics de test
Les 120 panics dans le code de test ne sont pas des bugs. Ce sont le mécanisme standard de test de Rust. Chaque .unwrap() est un site de panic. Chaque assert_eq! est un site de panic. Ce sont des utilisations correctes de panic -- ils terminent le test avec un message d'échec, ce qui est exactement ce que les assertions de test doivent faire.
Mesurer les progrès
Production panic sites:
Start of audit: 59 (48 VM + 9 renderer + 2 codegen)
After Tier 1: 11 (0 VM + 9 renderer + 2 codegen)
After Tier 2: 2 (0 VM + 0 renderer + 2 codegen)
After Tier 3: 0 (0 VM + 0 renderer + 0 codegen)
Target: zero production panics before beta releasePourquoi zéro panic compte
Un runtime de langage est une infrastructure. Quand un développeur FLIN écrit une application web et la déploie en production, il fait confiance au fait que le runtime ne crashera sous aucune entrée que ses utilisateurs fournissent. Un panic dans le runtime n'est pas juste un bug de FLIN -- c'est une indisponibilité pour chaque application construite sur FLIN.
Zéro panic en production n'est pas un objectif théorique. C'est une propriété concrète et mesurable du binaire. Soit les appels panic! existent dans les chemins de code de production, soit ils n'existent pas. L'audit nous a donné la liste exacte. Les sessions de correction les élimineraient. Et le pipeline CI garantirait qu'ils ne reviennent jamais.
Ceci est la partie 154 de la série « Comment nous avons construit FLIN », documentant comment un CEO à Abidjan et un CTO IA ont conçu et construit un langage de programmation à partir de zéro.
Navigation de la série : - [153] Ce que l'audit nous a appris sur la construction d'un langage - [154] Appels panic en production : suivi et élimination (vous êtes ici) - [155] 93 sessions auditées en un seul passage